将AudioBuffer准确地转换为CMSampleBuffer的CMTime

4
这里的目标是通过AVCaptureDataOutput捕获视频和CoreAudio录制音频来创建mp4文件。然后将两者的CMSampleBuffer发送到具有相应AVAssetWriterInput(AVMediaTypeVideo)和AVAssetWriterInput(AVMediaTypeAudio)的AVAssetWriter中。
我的音频编码器会将AudioBuffer复制到新的CMSampleBuffer,然后将其传递给AVAssetWriterInput(AVMediaTypeAudio)。以下示例演示了如何完成从AudioBuffer转换为CMSampleBuffer:转换为CMSampleBuffer 长话短说,它不起作用。视频显示出来,但没有音频。
但是,如果我注释掉视频编码,那么音频就会写入文件并且可以听到。
这告诉我有一个时间问题。 CMSampleBuffer的转换可能存在问题。
   CMSampleTimingInfo timing = { CMTimeMake(1, 44100.0), kCMTimeZero, kCMTimeInvalid };

它生成一个时间 CMTimeCopyDescription 的{0/1 = 0.000},我认为这完全是错误的。我尝试跟踪呈现的帧并将帧计数作为时间值以及采样率作为时间刻度传递,如下所示

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

我创建的资产编写器

    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个回答

3
当然,问题已经存在两个星期了。我在一个星期五的晚上发布了问题,周一早上找到了解决方案。我发现的研究使我走上了正确的轨道...
时间刻度为1000000000纳秒。但是,时间值必须是设备绝对时间的纳秒。
这篇文章比我写的更好,讲解了“mach time”: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和info是什么? - Pablo Martinez
嘿,Pablo,inTimeStamp是与从音频单元设置的回调函数关联的采样缓冲区相关联的时间戳。它通过AURenderCallbackStruct进行分配。我强烈推荐学习核心音频,如果你想了解更多信息。 - mylegfeelsfunny
谢谢!问题是,我从流媒体服务获取AudioBufferList。你知道我该怎么做吗? - Pablo Martinez
我一时半会儿不确定,AURenderCallback 会将 AudioBufferListAudioTimeStamp 发送给我。但是如果可以的话,我会尝试自己填充时间戳并向前传递它。这样会改变时间,但如果改变始终保持一致,那么可能会起作用。请记住,我现在只是在推测。 - mylegfeelsfunny
嗨,我知道这是一个旧的线程,但我正在做与你相同的事情,但是从音频创建的CMSampleBuffer的演示时间戳和从AVCaptureSession中传入的视频的CMSampleBuffer的时间戳确实不同,你也遇到了这个问题吗? - YYfim
缓冲区泄漏了。没有必要分配。 - undefined

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