我希望从视频文件中的音轨中提取样本精确的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中支持 。
使用AVURLAsset
和CMTime
定义提取的准确时间范围,并在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];
}
给我的呈现时间似乎与我追求的相符 - 但由于它似乎不准确,所以我没有机会纠正并对齐我检索的样本。
关于如何做到这一点的任何想法?
或者,有没有办法让CMSampleBufferGetPresentationTimeStamp
或AVAssetTrack
使用AVAudioFile
?
是否可以通过ExtAudioFile
?
是否可以在macOS中以不同的方式从视频容器中获取音频流?
答案 0 :(得分:3)
一个有效的程序是使用AVAssetReader,与AVAssetWriter一起读取压缩的AV文件,以编写音频样本的新原始LPCM文件。然后,可以快速索引这个新的PCM文件(或存储器映射的阵列,如果需要),以提取精确的样本精确范围,而不会导致VBR每包解码大小异常或取决于在一个控件之外的iOS CMTimeStamp算法。
这可能不是最有时间或内存效率的程序,但它确实有效。
答案 1 :(得分:0)
我写了另一个答案,其中我错误地声称
AVAssetReader
/ AVAssetReaderTrackOutput
没有进行样本准确搜索,他们这样做,但是当您的音轨嵌入电影文件时看起来很糟糕,所以您发现了一个错误。恭喜!
在@ hotpaw2的答案评论中提到的通过AVAssetExportSession
转发的音频轨道工作正常,即使你在非数据包边界上寻找(你碰巧正在寻求数据包边界,链接文件每个数据包有1024帧 - 寻找数据包边界,你的差异不再是零,但它们非常非常小/不可听见。
我没有找到解决方法,所以重新考虑转储压缩轨道。那是否代价高昂?如果您真的不想这样做,可以通过将nil
outputSettings:
传递到AVAssetReaderOutput
并通过AudioQueue
运行其输出来自行解码原始数据包或(最好是?)AudioConverter
以获得LPCM。
NB 在后一种情况下,将在搜索时需要处理数据包边界。