使用准确的 CMTime 将 AudioBuffer 转换为 CMSampleBuffer

Posted

技术标签:

【中文标题】使用准确的 CMTime 将 AudioBuffer 转换为 CMSampleBuffer【英文标题】:Converting AudioBuffer to CMSampleBuffer with accurate CMTime 【发布时间】:2015-04-24 20:50:28 【问题描述】:

这里的目标是通过视频通过 AVCaptureDataOutput 创建一个 mp4 文件,并将音频录制到一个 CoreAudio。然后将两者的 CMSampleBuffers 发送到具有随附 AVAssetWriterInput(AVMediaTypeVideo) 和 AVAssetWriterInput(AVMediaTypeAudio) 的 AVAssetWriter

我的音频编码器将 AudioBuffer 复制到一个新的 CMSampleBuffer 并将其传递给 AVAssetWriterInput(AVMediaTypeAudio)。这个例子是如何完成 AudioBuffer 到 CMSampleBuffer 的转换。 Converstion to CMSampleBuffer

长话短说,它不起作用。视频显示,但没有音频。

但是,如果我注释掉视频编码,那么音频将被写入文件并且可以听到。

根据经验告诉我这是一个时间问题。 Converstion to CMSampleBuffer 确实显示

   CMSampleTimingInfo timing =  CMTimeMake(1, 44100.0), kCMTimeZero, kCMTimeInvalid ;

它产生0/1 = 0.000 的时间CMTimeCopyDescription,这对我来说似乎完全错误。我尝试跟踪渲染的帧,并像这样传递时间值的帧数和时间尺度的采样率

   CMSampleTimingInfo timing =  CMTimeMake(1, 44100.0), CMTimeMake(self.frameCount, 44100.0), kCMTimeInvalid ;

但没有骰子。更好看的 CMSampleTimingInfo 107520/44100 = 2.438,但文件中仍然没有音频。

视频 CMSampleBuffer 会产生类似 65792640630624/1000000000 = 65792.641, rounded 的内容。这告诉我 AVCaptureVideoOutput 的时间尺度为 10 亿,可能是纳秒。我来宾时间值是设备时间之类的东西。我找不到任何关于 AVCaptureVideoOutput 使用的信息。

谁有任何有用的指导?我是否走在正确的轨道上?

这就是转换

    CMSampleBufferRef buff = malloc(sizeof(CMSampleBufferRef));
    CMFormatDescriptionRef format = NULL;

    self.frameCount += inNumberFrames;

    CMTime presentationTime = CMTimeMake(self.frameCount, self.pcmASBD.mSampleRate);

    AudiostreamBasicDescription audioFormat = self.pcmASBD;
    CheckError(CMAudioFormatDescriptionCreate(kCFAllocatorDefault,
                                              &audioFormat,
                                              0,
                                              NULL,
                                              0,
                                              NULL,
                                              NULL,
                                              &format),
               "Could not create format from AudioStreamBasicDescription");

    CMSampleTimingInfo timing =  CMTimeMake(1, self.pcmASBD.mSampleRate), presentationTime, kCMTimeInvalid ;

    CheckError(CMSampleBufferCreate(kCFAllocatorDefault,
                                    NULL,
                                    false,
                                    NULL,
                                    NULL,
                                    format,
                                    (CMItemCount)inNumberFrames,
                                    1,
                                    &timing,
                                    0,
                                    NULL,
                                    &buff),
               "Could not create CMSampleBufferRef");

    CheckError(CMSampleBufferSetDataBufferFromAudioBufferList(buff,
                                                              kCFAllocatorDefault,
                                                              kCFAllocatorDefault,
                                                              0,
                                                              audioBufferList),
               "Could not set data in CMSampleBufferRef");

    [self.delegate didRenderAudioSampleBuffer:buff];

    CFRelease(buff);

还有我创建的assetWriters

    func createVideoInputWriter()->AVAssetWriterInput? 
        let numPixels                               = Int(self.size.width * self.size.height)
        let bitsPerPixel:Int                        = 11
        let bitRate                                 = Int64(numPixels * bitsPerPixel)
        let fps:Int                                 = 30
        let settings:[NSObject : AnyObject]         = [
            AVVideoCodecKey                         : AVVideoCodecH264,
            AVVideoWidthKey                         : self.size.width,
            AVVideoHeightKey                        : self.size.height,
            AVVideoCompressionPropertiesKey         : [
                AVVideoAverageBitRateKey            : NSNumber(longLong: bitRate),
                AVVideoMaxKeyFrameIntervalKey       : NSNumber(integer: fps)
            ]
        ]

        var assetWriter:AVAssetWriterInput!
        if self.mainAssetWriter.canApplyOutputSettings(settings, forMediaType:AVMediaTypeVideo) 
            assetWriter                             = AVAssetWriterInput(mediaType:AVMediaTypeVideo, outputSettings:settings)
            assetWriter.expectsMediaDataInRealTime  = true
            if self.mainAssetWriter.canAddInput(assetWriter) 
                self.mainAssetWriter.addInput(assetWriter)
            
        
        return assetWriter;
    

    func createAudioInputWriter()->AVAssetWriterInput? 
        let settings:[NSObject : AnyObject]         = [
            AVFormatIDKey                           : kAudioFormatMPEG4AAC,
            AVNumberOfChannelsKey                   : 2,
            AVSampleRateKey                         : 44100,
            AVEncoderBitRateKey                     : 64000
        ]

        var assetWriter:AVAssetWriterInput!
        if self.mainAssetWriter.canApplyOutputSettings(settings, forMediaType:AVMediaTypeAudio) 
            assetWriter                             = AVAssetWriterInput(mediaType:AVMediaTypeAudio, outputSettings:settings)
            assetWriter.expectsMediaDataInRealTime  = true
            if self.mainAssetWriter.canAddInput(assetWriter) 
                self.mainAssetWriter.addInput(assetWriter)
             else 
                let error = NSError(domain:CMHDFileEncoder.Domain, code:CMHDFileEncoderErrorCode.CantAddInput.rawValue, userInfo:nil)
                self.errorDelegate.hdFileEncoderError(error)
            
         else 
            let error = NSError(domain:CMHDFileEncoder.Domain, code:CMHDFileEncoderErrorCode.CantApplyOutputSettings.rawValue, userInfo:nil)
            self.errorDelegate.hdFileEncoderError(error)
        
        return assetWriter
    

【问题讨论】:

【参考方案1】:

当然,问题持续了 2 周,周五晚上发布了问题,周一早上找到了解决方案。

我遇到的研究让我走上了正确的轨道......

1000000000 时间刻度是纳秒。 但是时间值必须是设备绝对时间的纳秒。

这篇文章比我解释得更好 - mach time

我最终使用此代码来修复它

    CMSampleBufferRef buff = malloc(sizeof(CMSampleBufferRef));
    CMFormatDescriptionRef format = NULL;

    AudioStreamBasicDescription audioFormat = self.pcmASBD;
    CheckError(CMAudioFormatDescriptionCreate(kCFAllocatorDefault,
                                              &audioFormat,
                                              0,
                                              NULL,
                                              0,
                                              NULL,
                                              NULL,
                                              &format),
               "Could not create format from AudioStreamBasicDescription");

    uint64_t time = inTimeStamp->mHostTime;
    /* Convert to nanoseconds */
    time *= info.numer;
    time /= info.denom;
    CMTime presentationTime                 = CMTimeMake(time, kDeviceTimeScale);
    CMSampleTimingInfo timing               =  CMTimeMake(1, self.pcmASBD.mSampleRate), presentationTime, kCMTimeInvalid ;

    CheckError(CMSampleBufferCreate(kCFAllocatorDefault,
                                    NULL,
                                    false,
                                    NULL,
                                    NULL,
                                    format,
                                    (CMItemCount)inNumberFrames,
                                    1,
                                    &timing,
                                    0,
                                    NULL,
                                    &buff),
               "Could not create CMSampleBufferRef");

    CheckError(CMSampleBufferSetDataBufferFromAudioBufferList(buff,
                                                              kCFAllocatorDefault,
                                                              kCFAllocatorDefault,
                                                              0,
                                                              audioBufferList),
               "Could not set data in CMSampleBufferRef");

【讨论】:

你好,感谢这个解决方案,你能解释一下什么是 inTimeStamp 吗?和信息? 嘿 Pablo,inTimeStamp 是与音频单元上设置的回调函数中的采样缓冲区相关联的时间戳。它通过AURenderCallbackStruct 分配。如果您想了解更多信息,我强烈推荐Learning Core Audio。 谢谢!问题是,我从流媒体服务中获取了 AudioBufferList。你知道我该怎么做吗? 副手我不确定,AURenderCallback 给我发了AudioBufferListAudioTimeStamp。但是我会考虑自己填充该时间戳,然后如果可以的话将其传递。那会改变时间,但如果改变总是一致的,它就可以工作。请记住,我现在是在推测。 嗨,我知道这是一个旧线程,但我正在做和你一样的事情,但是我的你 CMSampleBuffer 的演示时间戳是根据音频和 CMSampleBuffer 的时间戳创建的来自AVCaptureSession 的视频真的不一样,你也有这个问题吗?

以上是关于使用准确的 CMTime 将 AudioBuffer 转换为 CMSampleBuffer的主要内容,如果未能解决你的问题,请参考以下文章

尝试将 CMTime 存储为 Core Data 上的可转换属性

Xcode 6- SWIFT- 将 CMTime 转换为浮点数

iOS AVFoundation 如何使用 CMTime 转换每秒相机帧数以创建延时摄影?

如何使用 NSTimeInterval 进行 CMTime 引用

试图了解CMTime

Swift 3 中的 NSValue(CMTime: )?