使用AVAssetWriter将资源写入多个文件

3
我有一个包含AVCaptureScreenInputAVCaptureDeviceInputAVCaptureSession。两者都作为数据输出代理进行连接,并使用AVAssetWriter将数据写入单个MP4文件。
当写入单个MP4文件时,一切正常。但是当我尝试在每5秒钟保存到连续文件时,通过FFMPEG连接所有文件时会出现轻微的音频中断。
示例合并视频(请注意每5秒钟的小音频中断):

https://youtu.be/lrqD5dcbUXg

经过大量调查,我认为这可能是由于音频和视频片段被分割或未在同一时间戳上启动所致。
现在,我已经知道我的算法应该可以工作,但我不知道如何分割音频CMBufferSample。似乎CMSampleBufferCopySampleBufferForRange可能有用,但不确定如何基于时间进行分割(想要一个包含该时间之前和之后所有样本的缓冲区)。
func getBufferUpToTime(sample: CMSampleBuffer, to: CMTime) -> CMSampleBuffer {
  var numSamples = CMSampleBufferGetNumSamples(sample)
  var sout: CMSampleBuffer?

  let endSampleIndex = // how do I get this?

  CMSampleBufferCopySampleBufferForRange(nil, sample, CFRangeMake(0, numSamples), &sout)

  return sout!
}
1个回答

2
如果你正在使用AVCaptureScreenInput,那么你不是在iOS上,对吧?因此我将写一些关于拆分示例缓冲区的内容,但我记得在OSX上,AVCaptureFileOutput.startRecording(而不是AVAssetWriter)有一个令人兴奋的注释:

在Mac OS X上,如果在captureOutput:didOutputSampleBuffer:fromConnection:委托方法内调用此方法,则新文件中写入的第一个样本保证是包含在传递给该方法的样本缓冲区中的样本。

不丢失样本听起来非常有前途,因此如果您可以接受mov而不是mp4文件,那么您应该能够通过使用AVCaptureMovieFileOutput,实现fileOutputShouldProvideSampleAccurateRecordingStart并从didOutputSampleBuffer调用startRecording来获得无音频中断的结果,就像这样:
import Cocoa
import AVFoundation

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    let session = AVCaptureSession()
    let movieFileOutput = AVCaptureMovieFileOutput()

    var movieChunkNumber = 0
    var chunkDuration = kCMTimeZero // TODO: synchronize access? probably fine.

    func startRecordingChunkFile() {
        let filename = String(format: "capture-%.2i.mov", movieChunkNumber)
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(filename)
        movieFileOutput.startRecording(to: url, recordingDelegate: self)

        movieChunkNumber += 1
    }

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let displayInput = AVCaptureScreenInput(displayID: CGMainDisplayID())

        let micInput = try! AVCaptureDeviceInput(device: AVCaptureDevice.default(for: .audio)!)

        session.addInput(displayInput)
        session.addInput(micInput)

        movieFileOutput.delegate = self

        session.addOutput(movieFileOutput)

        session.startRunning()

        self.startRecordingChunkFile()
    }
}

extension AppDelegate: AVCaptureFileOutputRecordingDelegate {
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        // NSLog("error \(error)")
    }
}

extension AppDelegate: AVCaptureFileOutputDelegate {
    func fileOutputShouldProvideSampleAccurateRecordingStart(_ output: AVCaptureFileOutput) -> Bool {
        return true
    }

    func fileOutput(_ output: AVCaptureFileOutput, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        if let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) {
            if CMFormatDescriptionGetMediaType(formatDescription) == kCMMediaType_Audio {
                let duration = CMSampleBufferGetDuration(sampleBuffer)
                chunkDuration = CMTimeAdd(chunkDuration, duration)

                if CMTimeGetSeconds(chunkDuration) >= 5 {
                    startRecordingChunkFile()
                    chunkDuration = kCMTimeZero
                }
            }
        }
    }
}

谢谢你指引我另一个方向!虽然我还在一个兔子洞里,但我会在一周内尝试这个方法,如果成功了就标记为正确答案! - Vinay
不用谢!你的问题很合理,但是答案有点棘手——CMSampleBuffer调用非常冗长,而你想要在样本边界上进行拆分,但似乎只有CMTime,当它们由AVCaptureSession提供给你时,它们的时间基准肯定不是音频采样率,然后OSX为你提供了这个免费的出狱卡。 - Rhythmic Fistman
没错,另一个问题是CMSampleBufferCopySampleBufferForRange无法工作,因为CMSampleBuffer仅对第一个样本有演示时间戳,这意味着我可以假设之后每个样本的演示都是均匀分布的(根据文档)。所以我可能需要手动从底层缓冲区中获取数据并创建两个CMSampleBuffer。 :-) - Vinay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接