una - Muestra de extracción precisa de fragmentos de audio con AVFoundation
como seleccionar para cortar en audacity (2)
Escribí otra respuesta en la que AVAssetReader
incorrectamente que AVAssetReader
/ AVAssetReaderTrackOutput
no buscaba la muestra con precisión, pero parece estar rota cuando la pista de audio está incrustada dentro de un archivo de película, por lo que ha encontrado un error. ¡Felicitaciones!
La pista de audio descargada con un pase a través de AVAssetExportSession
, como se menciona en un comentario en la respuesta de @ hotpaw2, funciona bien, incluso cuando busca límites fuera del paquete (por casualidad buscaba límites de paquetes, el archivo vinculado tiene 1024 marcos por paquete) - buscando los límites del paquete, sus diferencias ya no son cero, pero son muy, muy pequeñas / no audibles).
No encontré una solución alternativa, así que reconsidere el volcado de la pista comprimida. ¿Es tan costoso? Si realmente no desea hacer eso, puede decodificar los paquetes crudos usted mismo al pasar nil
outputSettings:
a su AVAssetReaderOutput
y ejecutar su salida a través de un AudioQueue
o (preferiblemente?) Un AudioConverter
para obtener LPCM.
NB en este último caso, tendrá que manejar el redondeo hasta los límites del paquete cuando lo busque.
Problema
Estoy buscando extraer rangos precisos de muestreo de audio LPCM a partir de pistas de audio dentro de archivos de video. Actualmente, estoy buscando lograr esto usando AVAssetReaderTrackOutput
comparación con un AVAssetTrack
entregado al leer un AVURLAsset
.
A pesar de preparar y garantizar que el activo se inicialice con AVURLAssetPreferPreciseDurationAndTimingKey
establecido en YES
, buscar una posición exacta de la muestra dentro de un activo parece ser inexacto.
NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @(YES) };
_asset = [[AVURLAsset alloc] initWithURL:fileURL options:options];
Esto se manifiesta con, por ejemplo, flujos AAC codificados a velocidad de bits variable. Si bien sé que los flujos de audio VBR presentan una sobrecarga de rendimiento en la búsqueda precisa, estoy dispuesto a pagar esto siempre que se me entreguen muestras precisas.
Cuando uso, por ejemplo, Extended Audio File Services y las API ExtAudioFileRef
, puedo lograr búsquedas con precisión de muestra y extracción de audio. Del mismo modo con AVAudioFile
, ya que esto se basa en ExtAudioFileRef
.
El problema, sin embargo, es que también me gustaría extraer el audio de los contenedores de medios que rechazan las API de audio solo, pero que son compatibles con AVFLAundation a través de AVURLAsset
.
Método
Se define un intervalo de tiempo preciso de muestra para la extracción utilizando CMTime
y CMTimeRange
, y se establece en AVAssetReaderTrackOutput
. Las muestras se extraen iterativamente.
-(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];
}
Notas
El tiempo de presentación que me da CMSampleBufferGetPresentationTimeStamp
parece coincidir con lo que buscaba, pero como parece inexacto, no tengo la oportunidad de corregir y alinear las muestras que recupero.
¿Alguna idea de cómo hacer esto?
Alternativamente, ¿hay alguna manera de adaptar AVAssetTrack
para ser utilizado por AVAudioFile
o ExtAudioFile
?
¿Es posible acceder a la pista de audio a través de AudioFileOpenWithCallbacks
?
¿Es posible acceder a la transmisión de audio desde un contenedor de video de una manera diferente en macOS?
Un procedimiento que funciona es usar AVAssetReader, para leer su archivo AV comprimido, junto con AVAssetWriter, para escribir un nuevo archivo LPCM sin procesar de las muestras de audio. Entonces uno puede rápidamente indexar a través de este nuevo archivo PCM (o matriz mapeada en memoria, si es necesario) para extraer rangos exactos de muestras exactas, sin incurrir en anomalías de tamaño de decodificación por paquete VBR o dependiendo de los algoritmos iOS CMTimeStamp fuera del control de uno.
Puede que este no sea el procedimiento más eficiente en cuanto a tiempo o memoria, pero funciona.