ios - que - musica itunes formato
¿Cómo uso AudioConverter de CoreAudio para codificar AAC en tiempo real? (2)
Todo el código de muestra que puedo encontrar que usa AudioConverterRef
enfoca en casos de uso donde tengo todos los datos por adelantado (como convertir un archivo en un disco). Comúnmente llaman a AudioConverterFillComplexBuffer
con el PCM para convertirse como inInputDataProcUserData
y simplemente lo completan en la devolución de llamada. (¿Así es como se supone que debe usarse? ¿Por qué necesita una devolución de llamada, entonces?) Para mi caso de uso, estoy tratando de transmitir audio aac desde el micrófono, por lo que no tengo ningún archivo, y mi búfer PCM está siendo Completado en tiempo real.
Como no tengo todos los datos por adelantado, intenté hacer *ioNumberDataPackets = 0
en la devolución de llamada una vez que mis datos de entrada están fuera, pero eso solo pone el AudioConverter en un estado muerto donde necesita ser AudioConverterReset()
ted, y no obtengo ningún dato de eso.
Un enfoque que he visto sugerido en línea es devolver un error de la devolución de llamada si los datos que he almacenado son demasiado pequeños, e intentarlo de nuevo una vez que tenga más datos, pero eso parece una pérdida de recursos que no puedo incluso intentarlo.
¿De verdad tengo que hacer el "reintento hasta que mi buffer de entrada sea lo suficientemente grande", o hay una mejor manera?
Para referencia futura, hay una opción mucho más fácil.
El estado del encabezado de CoreAudio:
Si la devolución de llamada devuelve un error, debe devolver cero paquetes de datos. AudioConverterFillComplexBuffer dejará de producir y devolverá la salida que ya se haya producido a la persona que llama, junto con el código de error. Este mecanismo se puede usar cuando un proceso de entrada se ha quedado temporalmente sin datos, pero aún no ha llegado al final de la transmisión.
Entonces, haz exactamente eso. En lugar de devolver noErr con * ioNumberDataPackets = 0, devuelve cualquier error (solo crea uno, utilicé -1), y se devolverán los datos ya convertidos, mientras que el convertidor de audio se mantiene activo y no necesita reiniciarse.
AudioConverterFillComplexBuffer
no significa "llenar el codificador con mi buffer de entrada que tengo aquí". Significa "llenar este búfer de salida aquí con datos codificados del codificador". Con esta perspectiva, la devolución de llamada de repente tiene sentido: se usa para obtener datos de origen para satisfacer la solicitud "llenar este búfer de salida". Tal vez esto sea obvio para otros, pero me llevó mucho tiempo entender esto (y de todo el código de muestra AudioConverter que veo flotando por donde las personas envían datos de entrada a través de inInputDataProcUserData
, supongo que no soy el único).
La llamada AudioConverterFillComplexBuffer
está bloqueando y espera que le entregue datos sincrónicamente a partir de la devolución de llamada. Si está codificando en tiempo real, necesitará llamar a FillComplexBuffer
en un hilo separado que usted mismo configure. En la devolución de llamada, puede verificar los datos de entrada disponibles, y si no está disponible, debe bloquear en un semáforo. Usando una NSCondition, el hilo del codificador se vería así:
- (void)startEncoder
{
OSStatus creationStatus = AudioConverterNew(&_fromFormat, &_toFormat, &_converter);
_running = YES;
_condition = [[NSCondition alloc] init];
[self performSelectorInBackground:@selector(_encoderThread) withObject:nil];
}
- (void)_encoderThread
{
while(_running) {
// Make quarter-second buffers.
size_t bufferSize = (_outputBitrate/8) * 0.25;
NSMutableData *outAudioBuffer = [NSMutableData dataWithLength:bufferSize];
AudioBufferList outAudioBufferList;
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = _toFormat.mChannelsPerFrame;
outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)bufferSize;
outAudioBufferList.mBuffers[0].mData = [outAudioBuffer mutableBytes];
UInt32 ioOutputDataPacketSize = 1;
_currentPresentationTime = kCMTimeInvalid; // you need to fill this in during FillComplexBuffer
const OSStatus conversionResult = AudioConverterFillComplexBuffer(_converter, FillBufferTrampoline, (__bridge void*)self, &ioOutputDataPacketSize, &outAudioBufferList, NULL);
// here I convert the AudioBufferList into a CMSampleBuffer, which I''ve omitted for brevity.
// Ping me if you need it.
[self.delegate encoder:self encodedSampleBuffer:outSampleBuffer];
}
}
Y la devolución de llamada podría ser así: (tenga en cuenta que normalmente uso este trampolín para reenviar inmediatamente a un método en mi instancia (reenviando mi instancia en inUserData
; este paso se omite por brevedad)):
static OSStatus FillBufferTrampoline(AudioConverterRef inAudioConverter,
UInt32* ioNumberDataPackets,
AudioBufferList* ioData,
AudioStreamPacketDescription** outDataPacketDescription,
void* inUserData)
{
[_condition lock];
UInt32 countOfPacketsWritten = 0;
while (true) {
// If the condition fires and we have shut down the encoder, just pretend like we have written 0 bytes and are done.
if(!_running) break;
// Out of input data? Wait on the condition.
if(_inputBuffer.length == 0) {
[_condition wait];
continue;
}
// We have data! Fill ioData from your _inputBuffer here.
// Also save the input buffer''s start presentationTime here.
// Exit out of the loop, since we''re done waiting for data
break;
}
[_condition unlock];
// 2. Set ioNumberDataPackets to the amount of data remaining
// if running is false, this will be 0, indicating EndOfStream
*ioNumberDataPackets = countOfPacketsWritten;
return noErr;
}
Y para completar, así es como alimentaría este codificador con datos y cómo cerrarlo correctamente:
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
[_condition lock];
// Convert sampleBuffer and put it into _inputBuffer here
[_condition broadcast];
[_condition unlock];
}
- (void)stopEncoding
{
[_condition lock];
_running = NO;
[_condition broadcast];
[_condition unlock];
}