使用AVFoundation准确提取音频块

时间:2017-11-06 02:50:46

标签: audio avfoundation core-audio avasset avassetreader

问题

我希望从视频文件中的音轨中提取样本精确的LPCM音频范围。目前,我希望使用AVAssetReaderTrackOutput针对阅读AVAssetTrack时提供的AVURLAsset来实现此目标。

尽管准备并确保使用设置为AVURLAssetPreferPreciseDurationAndTimingKey的{​​{1}}初始化资产,但在资产中寻找样本准确的位置似乎是不准确的。

YES

这体现在例如可变比特率编码的AAC流。虽然我知道VBR音频流在准确搜索时会产生性能开销,但我愿意为此付费,前提是我已经提供了准确的样本。

使用时,例如扩展音频文件服务和NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @(YES) }; _asset = [[AVURLAsset alloc] initWithURL:fileURL options:options]; API,我可以实现样本精确的搜索和音频提取。与ExtAudioFileRef类似,因为它建立在AVAudioFile之上。

但问题是,我还希望从仅有音频文件的API拒绝的媒体容器中提取音频,但是通过ExtAudioFileRef在AVFoundation中支持

方法

使用AVURLAssetCMTime定义提取的准确时间范围,并在CMTimeRange上设置。然后迭代地提取样本。

AVAssetReaderTrackOutput

注释

-(NSData *)readFromFrame:(SInt64)startFrame requestedFrameCount:(UInt32)frameCount { NSUInteger expectedByteCount = frameCount * _bytesPerFrame; NSMutableData *data = [NSMutableData dataWithCapacity:expectedByteCount]; // // Configure Output // NSDictionary *settings = @{ AVFormatIDKey : @( kAudioFormatLinearPCM ), AVLinearPCMIsNonInterleaved : @( NO ), AVLinearPCMIsBigEndianKey : @( NO ), AVLinearPCMIsFloatKey : @( YES ), AVLinearPCMBitDepthKey : @( 32 ), AVNumberOfChannelsKey : @( 2 ) }; AVAssetReaderOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:_track outputSettings:settings]; CMTime startTime = CMTimeMake( startFrame, _sampleRate ); CMTime durationTime = CMTimeMake( frameCount, _sampleRate ); CMTimeRange range = CMTimeRangeMake( startTime, durationTime ); // // Configure Reader // NSError *error = nil; AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:_asset error:&error]; if( !reader ) { fprintf( stderr, "avf : failed to initialize reader\n" ); fprintf( stderr, "avf : %s\n%s\n", error.localizedDescription.UTF8String, error.localizedFailureReason.UTF8String ); exit( EXIT_FAILURE ); } [reader addOutput:output]; [reader setTimeRange:range]; BOOL startOK = [reader startReading]; NSAssert( startOK && reader.status == AVAssetReaderStatusReading, @"Ensure we've started reading." ); NSAssert( _asset.providesPreciseDurationAndTiming, @"We expect the asset to provide accurate timing." ); // // Start reading samples // CMSampleBufferRef sample = NULL; while(( sample = [output copyNextSampleBuffer] )) { CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp( sample ); if( data.length == 0 ) { // First read - we should be at the expected presentation time requested. int32_t comparisonResult = CMTimeCompare( presentationTime, startTime ); NSAssert( comparisonResult == 0, @"We expect sample accurate seeking" ); } CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer( sample ); if( !buffer ) { fprintf( stderr, "avf : failed to obtain buffer" ); exit( EXIT_FAILURE ); } size_t lengthAtOffset = 0; size_t totalLength = 0; char *bufferData = NULL; if( CMBlockBufferGetDataPointer( buffer, 0, &lengthAtOffset, &totalLength, &bufferData ) != kCMBlockBufferNoErr ) { fprintf( stderr, "avf : failed to get sample\n" ); exit( EXIT_FAILURE ); } if( bufferData && lengthAtOffset ) { [data appendBytes:bufferData length:lengthAtOffset]; } CFRelease( sample ); } NSAssert( reader.status == AVAssetReaderStatusCompleted, @"Completed reading" ); [output release]; [reader release]; return [NSData dataWithData:data]; } 给我的呈现时间似乎与我追求的相符 - 但由于它似乎不准确,所以我没有机会纠正并对齐我检索的样本。

关于如何做到这一点的任何想法?

或者,有没有办法让CMSampleBufferGetPresentationTimeStampAVAssetTrack使用AVAudioFile

是否可以通过ExtAudioFile

访问音轨

是否可以在macOS中以不同的方式从视频容器中获取音频流?

2 个答案:

答案 0 :(得分:3)

一个有效的程序是使用AVAssetReader,与AVAssetWriter一起读取压缩的AV文件,以编写音频样本的新原始LPCM文件。然后,可以快速索引这个新的PCM文件(或存储器映射的阵列,如果需要),以提取精确的样本精确范围,而不会导致VBR每包解码大小异常或取决于在一个控件之外的iOS CMTimeStamp算法。

这可能不是最有时间或内存效率的程序,但它确实有效。

答案 1 :(得分:0)

我写了另一个答案,其中我错误地声称  AVAssetReader / AVAssetReaderTrackOutput没有进行样本准确搜索,他们这样做,但是当您的音轨嵌入电影文件时看起来很糟糕,所以您发现了一个错误。恭喜!

在@ hotpaw2的答案评论中提到的通过AVAssetExportSession转发的音频轨道工作正常,即使你在非数据包边界上寻找(你碰巧正在寻求数据包边界,链接文件每个数据包有1024帧 - 寻找数据包边界,你的差异不再是零,但它们非常非常小/不可听见。

我没有找到解决方法,所以重新考虑转储压缩轨道。那是否代价高昂?如果您真的不想这样做,可以通过将nil outputSettings:传递到AVAssetReaderOutput并通过AudioQueue运行其输出来自行解码原始数据包或(最好是?)AudioConverter以获得LPCM。

NB 在后一种情况下,在搜索时需要处理数据包边界。