Пример точного извлечения фрагментов аудио с использованием AVFoundation
проблема
Я стремлюсь извлекать точные выборочные диапазоны аудио LPCM из звуковых дорожек в видеофайлах. В настоящее время я хочу добиться этого с помощью AVAssetReaderTrackOutput
против AVAssetTrack
избавлен от чтения AVURLAsset
,
Несмотря на подготовку и обеспечение актива инициализируется с помощью AVURLAssetPreferPreciseDurationAndTimingKey
установлен в YES
поиск точной выборки позиции в активе кажется неточным.
NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @(YES) };
_asset = [[AVURLAsset alloc] initWithURL:fileURL options:options];
Это проявляется, например, в кодированных потоках AAC с переменной скоростью передачи битов. Несмотря на то, что я знаю, что аудиопотоки VBR приводят к потере производительности при точном поиске, я готов заплатить это при условии получения точных образцов.
При использовании, например, расширенных файловых служб аудио и ExtAudioFileRef
API, я могу добиться точного сэмпла поиска и извлечения аудио. Аналогично с AVAudioFile
так как это строится поверх ExtAudioFileRef
,
Проблема, однако, заключается в том, что я также хотел бы извлечь аудио из медиа-контейнеров, которые отклоняются только API-интерфейсом для аудиофайлов, но которые поддерживаются в 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
?
Можно ли получить доступ к звуковой дорожке через AudioFileOpenWithCallbacks
?
Можно ли другим способом получить аудиопоток из контейнера видео в macOS?
2 ответа
Одна из процедур, которая работает, - это использовать AVAssetReader для чтения сжатого AV-файла вместе с AVAssetWriter для записи нового необработанного LPCM-файла аудиосэмплов. Затем можно быстро выполнить индексацию этого нового файла PCM (или массива с отображением памяти, если необходимо), чтобы извлечь точные диапазоны точности выборки, не вызывая аномалий размера декодирования VBR на пакет или в зависимости от алгоритмов iOS CMTimeStamp, находящихся вне его контроля.
Это может быть не самая эффективная процедура, занимающая много времени или памяти, но она работает.
Я написал другой ответ, в котором я неправильно заявил AVAssetReader
/ AVAssetReaderTrackOutput
они не выполняли точный поиск сэмплов, но они выглядят испорченными, когда ваша звуковая дорожка встроена в файл фильма, поэтому вы обнаружили ошибку. Поздравляем!
Звуковая дорожка сброшена с пропуском AVAssetExportSession
, как упоминалось в комментарии к ответу @hotpaw2, работает нормально, даже если вы ищете на границах, отличных от пакетов (вы, по-видимому, искали на границах пакетов, у связанного файла есть 1024 кадра на пакет - при поиске за пределами пакетов, ваши различия больше не ноль, но они очень, очень маленькие / не слышно) .
Я не нашел обходного пути, так что пересмотрите дамп сжатого трека. Это дорого? Если вы действительно не хотите этого делать, вы можете самостоятельно декодировать необработанные пакеты, передав nil
outputSettings:
на ваш AVAssetReaderOutput
и запустить свой выход через AudioQueue
или (предпочтительно?) AudioConverter
чтобы получить LPCM.
Примечание: в этом последнем случае при поиске вам потребуется обработать округление до границ пакета.