【问题标题】:AVAssetWriterInput - Insufficient video frames for Captured AudioAVAssetWriterInput - 捕获音频的视频帧不足
【发布时间】:2021-05-05 00:09:29
【问题描述】:

我有一个相当复杂的 AVAssetWriterInput 设置,我正在使用它来在录制时翻转相机。基本上运行两个会话,当用户点击翻转相机时,我将会话 1 从输出断开并附加会话 2。

这真的很棒。我可以导出视频,它可以正常播放。

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

我做了一些来回调试,并设法使用我的录像机录制了一个视频,该录像机始终产生一个尾随黑框,但是 使用教程代码我无法创建一个视频产生一个尾随的黑框。我在教程代码中添加了一些类似的日志记录(下面粘贴的内容),我看到的增量不超过 2/100 秒。所以最多大约是 1 帧的 1/10。有一次甚至是完美的 0。

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

(编辑:这基本上是正在发生的事情 - https://blender.stackexchange.com/questions/6268/audio-track-and-video-track-are-not-the-same-length

我认为正确的解决方案是,与其担心构图结构和时间安排并确保一切正常,不如让捕获的音频和视频尽可能匹配。理想情况下为 0,但我可以处理大约 1/10 帧的任何内容。

所以我的问题是:如何使两个 AVAssetWriterInputs,一个音频和一个视频,连接到 AVAssetWriter 更好地排列?有地方设置吗?我会弄乱帧率吗?我应该将导出的资产修剪到视频轨道的长度吗?停止录制时可以复制最后捕获的帧吗?我可以让输入在不同的时间停止 - 基本上先停止音频然后等待视频“赶上”然后停止视频?还有什么?我在这里不知所措:|

我的日志记录

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**

教程日志

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)**

【问题讨论】:

    标签: swift avfoundation avassetwriter avassetwriterinput


    【解决方案1】:

    TL;DR - 不要只是AVAssetWriter.finishWriting {},因为最后写入的帧是 T_End。而是使用AVAssetWriter.endSession(atSourceTime:) 将 T_End 设置为最后写入视频帧的时间。

    AVCaptureVideoDataOutputSampleBuffer 委托救援!!

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

    一旦会话开始并且你的输出开始了,他们就会不断地向这个代表吐出数据

    1. canWrite 是一个标志,用于跟踪您是否应该记录(将 sampleBuffers 写入 AVAssetWriter)
    2. 为了防止出现前导黑帧,我们需要确保第一个帧是视频帧。直到我们得到一个视频帧,即使我们正在录制,我们也会忽略这些帧。 startSession(atSourceTime:) 为资产设置 T0,我们将其设置为第一个视频帧的时间
    3. 每次写入视频帧时,将该时间记录在单独的队列中。这释放了delegateQueue,使其只进行帧处理//写入,并保证在读取lastVideoFrameWrite 时停止记录(将从main 队列触发)不会发生冲突或内存问题

    现在是有趣的部分!

    1. 为了防止出现黑帧,我们让 AVAssetWriter 在 T_lastVideoFrameTime 结束其会话。这将丢弃在 T_lastVideoFrameTime 之后写入的所有帧(音频和视频),确保 AVAssetWriter 中的两个assetTrack 尽可能同步。

    结果

    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
    

    看看那些三角洲!!!!!!所有亚毫秒。很不错。

    代码

    录制

        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)
        }
    }
    

    停止录制

    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
                 ...
            }
        ...
        }
    }
    

    【讨论】:

    • 开头没有黑框?
    • 再也没有黑框了!
    • 哇!太好了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-11
    • 2014-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多