将video+生成的audio写入AVAssetWriterInput,audio口吃

我从iOS上的Unity应用程序生成video。 我使用iVidCap,它使用AVFoundation来做到这一点。 那边一切正常。 本质上,video是通过使用纹理渲染目标并将帧传递给Obj-C插件来渲染的。

现在我需要添加audio到video。 audio将是特定时间发生的声音效果,也可能是一些背景音效。 正在使用的文件实际上是Unity应用程序的内部资源。 我可能会写这些到电话存储,然后生成一个AVComposition,但我的计划是要避免这种情况,并在浮点格式缓冲区(从audio剪辑获取audio以浮点格式)复合audio。 我可能会稍后在飞行audio效果。

几个小时后,我设法录制audio,并播放video…但它结结巴巴。

目前,我只是在每个video帧的持续时间内生成一个方波,并将其写入AVAssetWriterInput。 之后,我将生成我实际需要的audio。 如果我生成一个大样本,我不会得到口吃。 如果我把它写成块(我更喜欢分配一个巨大的数组),那么audio块似乎相互剪辑:

毛刺

我似乎无法弄清楚这是为什么。 我很确定我正在为audio缓冲区获取正确的时间戳,但也许我正在做这整个部分不正确。 或者我需要一些标志来让video同步到audio? 我不能看到这是问题,因为我可以在将audio数据提取到wav之后在波形编辑器中看到问题。

编写audio的相关代码:

- (id)init { self = [super init]; if (self) { // [snip] rateDenominator = 44100; rateMultiplier = rateDenominator / frameRate; sample_position_ = 0; audio_fmt_desc_ = nil; int nchannels = 2; AudioStreamBasicDescription audioFormat; bzero(&audioFormat, sizeof(audioFormat)); audioFormat.mSampleRate = 44100; audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFramesPerPacket = 1; audioFormat.mChannelsPerFrame = nchannels; int bytes_per_sample = sizeof(float); audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsAlignedHigh; audioFormat.mBitsPerChannel = bytes_per_sample * 8; audioFormat.mBytesPerPacket = bytes_per_sample * nchannels; audioFormat.mBytesPerFrame = bytes_per_sample * nchannels; CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &audioFormat, 0, NULL, 0, NULL, NULL, &audio_fmt_desc_ ); } return self; } -(BOOL) beginRecordingSession { NSError* error = nil; isAborted = false; abortCode = No_Abort; // Allocate the video writer object. videoWriter = [[AVAssetWriter alloc] initWithURL:[self getVideoFileURLAndRemoveExisting: recordingPath] fileType:AVFileTypeMPEG4 error:&error]; if (error) { NSLog(@"Start recording error: %@", error); } //Configure video compression settings. NSDictionary* videoCompressionProps = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithDouble:1024.0 * 1024.0], AVVideoAverageBitRateKey, [NSNumber numberWithInt:10],AVVideoMaxKeyFrameIntervalKey, nil ]; //Configure video settings. NSDictionary* videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: AVVideoCodecH264, AVVideoCodecKey, [NSNumber numberWithInt:frameSize.width], AVVideoWidthKey, [NSNumber numberWithInt:frameSize.height], AVVideoHeightKey, videoCompressionProps, AVVideoCompressionPropertiesKey, nil]; // Create the video writer that is used to append video frames to the output video // stream being written by videoWriter. videoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings] retain]; //NSParameterAssert(videoWriterInput); videoWriterInput.expectsMediaDataInRealTime = YES; // Configure settings for the pixel buffer adaptor. NSDictionary* bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil]; // Create the pixel buffer adaptor, used to convert the incoming video frames and // append them to videoWriterInput. avAdaptor = [[AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput sourcePixelBufferAttributes:bufferAttributes] retain]; [videoWriter addInput:videoWriterInput]; // <pb> Added audio input. sample_position_ = 0; AudioChannelLayout acl; bzero( &acl, sizeof(acl)); acl.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; NSDictionary* audioOutputSettings = nil; audioOutputSettings = [NSDictionary dictionaryWithObjectsAndKeys: [ NSNumber numberWithInt: kAudioFormatMPEG4AAC ], AVFormatIDKey, [ NSNumber numberWithInt: 2 ], AVNumberOfChannelsKey, [ NSNumber numberWithFloat: 44100.0 ], AVSampleRateKey, [ NSNumber numberWithInt: 64000 ], AVEncoderBitRateKey, [ NSData dataWithBytes: &acl length: sizeof( acl ) ], AVChannelLayoutKey, nil]; audioWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType: AVMediaTypeAudio outputSettings: audioOutputSettings ] retain]; //audioWriterInput.expectsMediaDataInRealTime = YES; audioWriterInput.expectsMediaDataInRealTime = NO; // seems to work slightly better [videoWriter addInput:audioWriterInput]; rateDenominator = 44100; rateMultiplier = rateDenominator / frameRate; // Add our video input stream source to the video writer and start it. [videoWriter startWriting]; [videoWriter startSessionAtSourceTime:CMTimeMake(0, rateDenominator)]; isRecording = true; return YES; } - (int) writeAudioBuffer: (float*) samples sampleCount: (size_t) n channelCount: (size_t) nchans { if ( ![self waitForAudioWriterReadiness]) { NSLog(@"WARNING: writeAudioBuffer dropped frame after wait limit reached."); return 0; } //NSLog(@"writeAudioBuffer"); OSStatus status; CMBlockBufferRef bbuf = NULL; CMSampleBufferRef sbuf = NULL; size_t buflen = n * nchans * sizeof(float); // Create sample buffer for adding to the audio input. status = CMBlockBufferCreateWithMemoryBlock( kCFAllocatorDefault, samples, buflen, kCFAllocatorNull, NULL, 0, buflen, 0, &bbuf); if (status != noErr) { NSLog(@"CMBlockBufferCreateWithMemoryBlock error"); return -1; } CMTime timestamp = CMTimeMake(sample_position_, 44100); sample_position_ += n; status = CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault, bbuf, TRUE, 0, NULL, audio_fmt_desc_, 1, timestamp, NULL, &sbuf); if (status != noErr) { NSLog(@"CMSampleBufferCreate error"); return -1; } BOOL r = [audioWriterInput appendSampleBuffer:sbuf]; if (!r) { NSLog(@"appendSampleBuffer error"); } CFRelease(bbuf); CFRelease(sbuf); return 0; } 

任何想法是怎么回事?

我应该以不同的方式创build/追加样本吗?

这与AAC压缩有关吗? 如果我尝试使用未压缩的audio(它抛出),它不工作。

据我所知,我正在计算正确的PTS。 为什么这甚至需要audio通道? video不应该同步到audio时钟?

更新:我试过在1024个样本的固定块中提供audio,因为这是AAC压缩器使用的DCT的大小。 没有任何区别。

我已经尝试过在编写任何video之前一次推送所有的块。 不起作用。

我已经尝试使用CMSampleBufferCreate为剩余的块和CMAudioSampleBufferCreateWithPacketDescriptions只为第一个块。 不用找了。

我试过这些的组合。 还是不对。

解:

看起来:

 audioWriterInput.expectsMediaDataInRealTime = YES; 

是必不可less的,否则就会搅乱它的思想。 也许这是因为video是用这个标志设置的。 此外,即使您将标志kCMBlockBufferAlwaysCopyDataFlag传递给它, CMBlockBufferCreateWithMemoryBlock也不复制样本数据。

所以,可以用这个缓冲区创build,然后使用CMBlockBufferCreateContiguous复制,以确保它得到一个带有audio数据副本的块缓冲区。 否则,它会引用你最初传入的内存,事情会变得混乱。

它看起来不错,虽然我会使用CMBlockBufferCreateWithMemoryBlock因为它复制样品。 你的代码不知道什么时候audioWriterInput已经完成了吗?

不应该kAudioFormatFlagIsAlignedHighkAudioFormatFlagIsPacked

CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault,bbuf,TRUE,0,NULL,audio_fmt_desc_,1,timestamp,NULL,&sbuf); 应该是CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault,bbuf,TRUE,0,NULL,audio_fmt_desc_,n,timestamp,NULL,&sbuf);我做到了。