首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AVAssetWriterInput -捕获音频的视频帧不足

AVAssetWriterInput -捕获音频的视频帧不足
EN

Stack Overflow用户
提问于 2021-05-05 00:09:29
回答 1查看 311关注 0票数 1

我有一个中等复杂的AVAssetWriterInput设置,我用它可以翻转相机,而我正在录音。基本上运行两个会话,当用户点击翻转相机时,我会断开会话1与输出的连接,并附加会话2。

这真的很好用。我可以输出视频,它播放得很好。

现在,我正在尝试对生成的视频进行更高级的处理,出现了一些问题,特别是导出的AVAssetTracks内部的AVAsset稍有不匹配(总是小于1帧)。具体来说,我是在尝试这样做:https://www.raywenderlich.com/6236502-avfoundation-tutorial-adding-overlays-and-animations-to-videos,但大量的时间,最终都是一个全黑帧,有时在视频的头,有时在视频的尾部,出现了一秒。时间不同,但总是小于一个框架(见下面的日志,1/30或0.0333333 s)

我做了一些反复的调试,我成功地用我的录音机录制了一段视频,它始终产生一个尾随的黑帧,但使用教程代码,我无法创建一个产生尾随黑帧的视频。我在教程代码中添加了一些类似的日志记录(粘贴在下面),我看到的增量不超过每秒的2/100。所以最多只有1/10的帧。有一次甚至是完美的0。

所以我现在的感觉是,我正在录制我的视频,两个assetInputs都开始吞噬数据,然后当我说“停止”时,他们就停止了。视频输入以最后一个完整的帧停止,音频输入也是如此。但是,由于音频输入的采样率比视频高得多,所以它们的同步并不完美,而我最终得到的音频比视频更多。这不是一个问题,直到我用两个音轨组成一个资产,然后合成引擎认为我的意思是“是的,实际上在所有曲目上使用100%的时间,即使存在不匹配”,这会导致黑屏。

(编辑:这基本上就是发生了什么- https://blender.stackexchange.com/questions/6268/audio-track-and-video-track-are-not-the-same-length)

我认为正确的解决方案是,不要担心构图的构造和时间,而要确保没有问题,只需使捕获的音频和视频尽可能地匹配。理想情况下,0,但我对任何1/10左右的帧都没问题。

因此,我的问题是:如何使两个AVAssetWriterInputs,一个音频和一个视频,连接到一个AVAssetWriter线更好?有什么地方吗?我会把框架弄乱吗?我应该把输出的资产缩小到视频轨道的长度吗?当我停止录制时,我能复制最后捕获的帧吗?我可以有它,以便输入停止在不同的时间-基本上有音频停止首先,然后等待视频‘赶上’,然后停止视频?还有别的吗?我对这里的想法无所适从:

我的日志记录

代码语言:javascript
复制
BUFFER | VIdeo SETTINGS: Optional(["AVVideoCompressionPropertiesKey": {
    AllowFrameReordering = 1;
    AllowOpenGOP = 1;
    AverageBitRate = 7651584;
    **ExpectedFrameRate = 30;**
    MaxKeyFrameIntervalDuration = 1;
    MaxQuantizationParameter = 41;
    MinimizeMemoryUsage = 1;
    Priority = 80;
    ProfileLevel = "HEVC_Main_AutoLevel";
    RealTime = 1;
    RelaxAverageBitRateTarget = 1;
    SoftMinQuantizationParameter = 18;
}, "AVVideoCodecKey": hvc1, "AVVideoWidthKey": 1080, "AVVideoHeightKey": 1920])

BUFFER | AUDIO SETTINGS Optional(["AVNumberOfChannelsKey": 1, "AVFormatIDKey": 1633772320, **"AVSampleRateKey": 48000**])


BUFFER | asset duration: 0.5333333333333333
BUFFER | video track duration: 0.5066666666666667
BUFFER | Audio track duration: 0.5333333333333333
**BUFFER | Asset Delta: -0.026666666666666616**

BUFFER | asset duration: 0.384
BUFFER | video track duration: 0.37333333333333335
BUFFER | Audio track duration: 0.384
**BUFFER | Asset Delta: -0.010666666666666658**

BUFFER | asset duration: 0.9405416666666667
BUFFER | video track duration: 0.935
BUFFER | Audio track duration: 0.9405416666666667
**BUFFER | Asset Delta: -0.005541666666666667**

教程日志记录

代码语言:javascript
复制
COMPOSE | asset duration: 0.7333333333333333
COMPOSE | video track duration: 0.7333333333333333
COMPOSE | audio track duration: 0.7316666666666667
**Delta: ~0.01667**

COMPOSE | asset duration: 1.3333333333333333
COMPOSE | video track duration: 1.3333333333333333
COMPOSE | audio track duration: 1.3316666666666668
**Delta: ~0.01667**

COMPOSE | asset duration: 1.0316666666666667
COMPOSE | video track duration: 1.0316666666666667
COMPOSE | audio track duration: 1.0316666666666667
**Delta: 0 (wow)**
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-05-05 19:44:03

不只是AVAssetWriter.finishWriting {},因为最后一个编写的框架是T_End。相反,使用AVAssetWriter.endSession(atSourceTime:)将T_End设置为最后编写的视频帧的时间。

AVCaptureVideoDataOutputSampleBufferDelegate去营救!!

使用AVCapture(Video|Audio)DataOutputSampleBufferDelegate将缓冲区写入AVAssetWriter (将委托附加到AVCaptureVideoDataOutput和AVCaptureAudioDataOutput)

一旦会话启动,并且输出正在进行,它们就会不断地将数据泄漏到这个委托上。

  1. canWrite是跟踪您是否应该录制(将sampleBuffers写入AVAssetWriter)或不记录
  2. 的标志,为了防止导致黑帧,我们需要确保第一个帧是视频帧。直到我们得到一个视频帧,即使我们正在录音,我们忽略了这些帧。startSession(atSourceTime:)为资产设置T0,我们将其设置为每次写入视频帧时第一个视频帧
  3. 的时间,将该时间记录在一个单独的队列中。这将使lastVideoFrameWrite

释放delegateQueue只进行帧处理//写入,并确保在读取main队列时停止记录(这将从main队列触发)不会出现冲突或内存问题。

现在是有趣的部分!

为了防止跟踪黑帧,我们让

  1. 在T_lastVideoFrameTime结束它的会话。这将丢弃在T_lastVideoFrameTime之后编写的所有帧(音频和视频),确保AVAssetWriter中的两个assetTracks尽可能同步。

结果

代码语言:javascript
复制
BUFFER | asset duration: 1.8683333333333334
BUFFER | video track duration: 1.8683333333333334
BUFFER | Audio track duration: 1.868
BUFFER | Asset Delta: 0.0003333333333332966

BUFFER | asset duration: 1.435
BUFFER | video track duration: 1.435
BUFFER | Audio track duration: 1.4343333333333332
BUFFER | Asset Delta: 0.0006666666666668153

BUFFER | asset duration: 1.8683333333333334
BUFFER | video track duration: 1.8683333333333334
BUFFER | Audio track duration: 1.8682291666666666
BUFFER | Asset Delta: 0.00010416666666679397

BUFFER | asset duration: 1.435
BUFFER | video track duration: 1.435
BUFFER | Audio track duration: 1.4343541666666666
BUFFER | Asset Delta: 0.0006458333333334565

看看那些三角洲!都是亚毫秒。非常好。

记录

代码语言:javascript
复制
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    guard CMSampleBufferDataIsReady(sampleBuffer) else {
        return
    }
    if output == audioDataOutput {
        // PROCESS AUDIO BUFFER
    }
    if output == videoDataOutput {
        // PROCESS VIDEO BUFFER
    }
    
    // 1
    let writable = canWrite
    let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
    if writable && sessionAtSourceTime == nil {
        // 2
        if output == videoDataOutput {
            sessionAtSourceTime = time
            videoWriter.startSession(atSourceTime: sessionAtSourceTime!)
        } else {
            return
        }
    }
    
    if output == videoDataOutput && writable {
        if videoWriterInput != nil {
            if videoWriterInput.isReadyForMoreMediaData {
                //Write video buffer
                videoWriterInput.append(sampleBuffer)
                // 3
                WBufferCameraSessionController.finishRecordQueue.async {
                    self.lastVideoFrameWrite = time
                }
            }
        }
    } else if writable,
              output == audioDataOutput,
              audioWriterInput != nil,
              audioWriterInput.isReadyForMoreMediaData {
        //Write audio buffer
        audioWriterInput.append(sampleBuffer)
    }
    if output == videoDataOutput {
        bufferDelegate?.didOuputVideoBuffer(buffer: sampleBuffer)
    }
}

停止录制

代码语言:javascript
复制
func stopRecording() {
    guard isRecording else {
        return
    }
    guard isStoppingRecording == false else {
        return
    }
    isStoppingRecording = true
    WBufferCameraSessionController.finishRecordQueue.async {
        // 4
        if self.lastVideoFrameWrite != nil {
            self.videoWriter.endSession(atSourceTime: self.lastVideoFrameWrite)
        }
        self.videoWriter.finishWriting { 
             // cleanup, do stuff with finished file if writing was successful
             ...
        }
    ...
    }
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67393648

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档