2011-08-19 16 views
14

Estoy intentando grabar el sonido producido por la salida de una unidad mezcladora.Cómo grabar el sonido producido por la salida de la unidad mezcladora (iOS Core Audio y Audio Graph)

Por el momento, mi código se basa en la demostración apple MixerHost iOS app: Un nodo mezclador está conectado a un nodo remoto IO en el graphe de audio.

y trato de establecer una devolución de llamada entrada en la nodo IO remoto de entrada en la salida del mezclador.

Hago algo mal pero no puedo encontrar el error.

Aquí está el código a continuación. Esto se hace justo después de la unidad de instalación multicanal Mezclador:

UInt32 flag = 1; 

// Enable IO for playback 
result = AudioUnitSetProperty(iOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 
           0, // Output bus 
           &flag, 
           sizeof(flag)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty EnableIO" withStatus: result]; return;} 

/* can't do that because *** AudioUnitSetProperty EnableIO error: -1073752493 00000000 
result = AudioUnitSetProperty(iOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 
           0, // Output bus 
           &flag, 
           sizeof(flag)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty EnableIO" withStatus: result]; return;} 
*/ 

continuación, crear un formato de flujo:

// I/O stream format 
iOStreamFormat.mSampleRate   = 44100.0; 
iOStreamFormat.mFormatID   = kAudioFormatLinearPCM; 
iOStreamFormat.mFormatFlags   = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
iOStreamFormat.mFramesPerPacket  = 1; 
iOStreamFormat.mChannelsPerFrame = 1; 
iOStreamFormat.mBitsPerChannel  = 16; 
iOStreamFormat.mBytesPerPacket  = 2; 
iOStreamFormat.mBytesPerFrame  = 2; 

[self printASBD: iOStreamFormat]; 

Entonces afectan el formato y especificar la frecuencia de muestreo:

result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 
           1, // Input bus 
           &iOStreamFormat, 
           sizeof(iOStreamFormat)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty StreamFormat" withStatus: result]; return;} 

result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 
           0, // Output bus 
           &iOStreamFormat, 
           sizeof(iOStreamFormat)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty StreamFormat" withStatus: result]; return;} 

// SampleRate I/O 
result = AudioUnitSetProperty (iOUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 
           0, // Output 
           &graphSampleRate, 
           sizeof (graphSampleRate)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty (set I/O unit input stream format)" withStatus: result]; return;} 

entonces, intenta establecer la devolución de llamada de renderizado.

Solución 1 >>> mi devolución de llamada de grabación nunca se llama

effectState.rioUnit = iOUnit; 

AURenderCallbackStruct renderCallbackStruct; 
renderCallbackStruct.inputProc  = &recordingCallback; 
renderCallbackStruct.inputProcRefCon = &effectState; 
result = AudioUnitSetProperty (iOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 
           0, // Output bus 
           &renderCallbackStruct, 
           sizeof (renderCallbackStruct)); 
if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty SetRenderCallback" withStatus: result]; return;} 

Solución 2 >>> mis aplicación se bloquea en el lanzamiento de esta

AURenderCallbackStruct renderCallbackStruct; 
renderCallbackStruct.inputProc  = &recordingCallback; 
renderCallbackStruct.inputProcRefCon = &effectState; 

result = AUGraphSetNodeInputCallback (processingGraph, iONode, 
             0, // Output bus 
             &renderCallbackStruct); 
if (noErr != result) {[self printErrorMessage: @"AUGraphSetNodeInputCallback (I/O unit input callback bus 0)" withStatus: result]; return;} 

Si alguien tiene una idea ...

EDIT Solución 3 (gracias a arlo anwser) >> Ahora hay un problema de formato

AudioStreamBasicDescription dstFormat = {0}; 
dstFormat.mSampleRate=44100.0; 
dstFormat.mFormatID=kAudioFormatLinearPCM; 
dstFormat.mFormatFlags=kAudioFormatFlagsNativeEndian|kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; 
dstFormat.mBytesPerPacket=4; 
dstFormat.mBytesPerFrame=4; 
dstFormat.mFramesPerPacket=1; 
dstFormat.mChannelsPerFrame=2; 
dstFormat.mBitsPerChannel=16; 
dstFormat.mReserved=0; 

result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 
        1, 
        &stereoStreamFormat, 
        sizeof(stereoStreamFormat)); 

if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty" withStatus: result]; return;} 


result = AudioUnitSetProperty(iOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 
        0, 
        &stereoStreamFormat, 
        sizeof(stereoStreamFormat)); 

if (noErr != result) {[self printErrorMessage: @"AudioUnitSetProperty" withStatus: result]; return;} 


AudioUnitAddRenderNotify(
         iOUnit, 
         &recordingCallback, 
         &effectState 
         ); 

y la configuración del archivo:

if (noErr != result) {[self printErrorMessage: @"AUGraphInitialize" withStatus: result]; return;} 

// On initialise le fichier audio 
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsDirectory = [paths objectAtIndex:0]; 
NSString *destinationFilePath = [[[NSString alloc] initWithFormat: @"%@/output.caf", documentsDirectory] autorelease]; 
NSLog(@">>> %@", destinationFilePath); 
CFURLRef destinationURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)destinationFilePath, kCFURLPOSIXPathStyle, false); 

OSStatus setupErr = ExtAudioFileCreateWithURL(destinationURL, kAudioFileWAVEType, &dstFormat, NULL, kAudioFileFlags_EraseFile, &effectState.audioFileRef); 
CFRelease(destinationURL); 
NSAssert(setupErr == noErr, @"Couldn't create file for writing"); 

setupErr = ExtAudioFileSetProperty(effectState.audioFileRef, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &stereoStreamFormat); 
NSAssert(setupErr == noErr, @"Couldn't create file for format"); 

setupErr = ExtAudioFileWriteAsync(effectState.audioFileRef, 0, NULL); 
NSAssert(setupErr == noErr, @"Couldn't initialize write buffers for audio file"); 

Y la devolución de llamada de grabación:

static OSStatus recordingCallback  (void *       inRefCon, 
           AudioUnitRenderActionFlags *  ioActionFlags, 
           const AudioTimeStamp *   inTimeStamp, 
           UInt32       inBusNumber, 
           UInt32       inNumberFrames, 
           AudioBufferList *     ioData) { 
if (*ioActionFlags == kAudioUnitRenderAction_PostRender && inBusNumber == 0) 
{ 
    EffectState *effectState = (EffectState *)inRefCon; 

    ExtAudioFileWriteAsync(effectState->audioFileRef, inNumberFrames, ioData); 
} 
return noErr;  
} 

hay algo que falta en el archivo de salida output.caf :). Estoy totalmente perdido en formatos para aplicar.

+0

que estoy tratando de hacer lo mismo bt incapaz de implementar código ur sobre MixerHost Ejemplo puede u PLZ me ayuda .. – Aadil

+0

Hola lefakir y arlomedia, ¿sería posible que alguno de ustedes pudiera publicar la clase EffectState? Estoy tratando de reproducir el código de trabajo usando MixerHost y lo anterior. Mejor, Gregor –

+0

Hola Gregor, deberías mirar aquí http://stackoverflow.com/questions/7032468/record-sounds-played-by-my-iphone-app-with-audiounits. La estructura EffectState se declara en esta pregunta. – lefakir

Respuesta

15

No creo que deba habilitar la entrada en la unidad de E/S. También comentaría el formato y la configuración de frecuencia de muestreo que está haciendo en la unidad de E/S hasta que ejecute la devolución de llamada, ya que un formato no compatible o incompatible puede evitar que las unidades de audio se vinculen entre sí.

Para añadir la devolución de llamada, probar este método:

AudioUnitAddRenderNotify(
    iOUnit, 
    &recordingCallback, 
    self 
); 

Al parecer, los otros métodos reemplazará la conexión de nodo, pero este método no - por lo que sus unidades de audio pueden permanecer conectados a pesar de que ha añadido una devolución de llamada.

Una vez que su devolución de llamada se está ejecutando, si usted encuentra que no hay datos en las memorias intermedias (IODATA), envuelva el código alrededor de su código de devolución de llamada:

if (*ioActionFlags == kAudioUnitRenderAction_PostRender) { 
    // your code 
} 

Esto es necesario debido a una devolución de llamada añadido en este camino carreras tanto antes como después de que la unidad de audio presente su audio, pero solo quiere ejecutar su código después de que se renderice.

Una vez que se ejecuta la devolución de llamada, el siguiente paso es averiguar qué formato de audio está recibiendo y manejarlo adecuadamente. Trate de añadir esto a su devolución de llamada:

SInt16 *dataLeftChannel = (SInt16 *)ioData->mBuffers[0].mData; 
for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) { 
    NSLog(@"sample %lu: %d", frameNumber, dataLeftChannel[frameNumber]); 
} 

Esto retrasará su aplicación tanto que probablemente evitará cualquier audio de realmente jugar, pero usted debería ser capaz de correr el tiempo suficiente para ver lo que las muestras parecen. Si la devolución de llamada recibe audio de 16 bits, las muestras deben ser enteros positivos o negativos entre -32000 y 32000. Si las muestras alternan entre un número de aspecto normal y un número mucho más pequeño, intente este código en su devolución de llamada:

SInt32 *dataLeftChannel = (SInt32 *)ioData->mBuffers[0].mData; 
for (UInt32 frameNumber = 0; frameNumber < inNumberFrames; ++frameNumber) { 
    NSLog(@"sample %lu: %ld", frameNumber, dataLeftChannel[frameNumber]); 
} 

Esto debería mostrarle las 8,24 muestras completas.

Si puede guardar los datos en el formato que recibe la devolución de llamada, entonces debe tener lo que necesita. Si necesita guardarlo en un formato diferente, debe poder convertir el formato en la unidad de audio de E/S remotas ... pero I haven't been able to figure out how to do that cuando está conectado a una unidad de mezclador multicanal. Como alternativa, puede convertir los datos usando Audio Converter Services. En primer lugar, definir los formatos de entrada y de salida:

AudioStreamBasicDescription monoCanonicalFormat; 
size_t bytesPerSample = sizeof (AudioUnitSampleType); 
monoCanonicalFormat.mFormatID   = kAudioFormatLinearPCM; 
monoCanonicalFormat.mFormatFlags  = kAudioFormatFlagsAudioUnitCanonical; 
monoCanonicalFormat.mBytesPerPacket = bytesPerSample; 
monoCanonicalFormat.mFramesPerPacket = 1; 
monoCanonicalFormat.mBytesPerFrame  = bytesPerSample; 
monoCanonicalFormat.mChannelsPerFrame = 1; 
monoCanonicalFormat.mBitsPerChannel = 8 * bytesPerSample; 
monoCanonicalFormat.mSampleRate  = graphSampleRate; 

AudioStreamBasicDescription mono16Format; 
bytesPerSample = sizeof (SInt16); 
mono16Format.mFormatID   = kAudioFormatLinearPCM; 
mono16Format.mFormatFlags  = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; 
mono16Format.mChannelsPerFrame = 1; 
mono16Format.mSampleRate  = graphSampleRate; 
mono16Format.mBitsPerChannel = 16; 
mono16Format.mFramesPerPacket = 1; 
mono16Format.mBytesPerPacket = 2; 
mono16Format.mBytesPerFrame = 2; 

a continuación, definir un convertidor de algún lugar fuera de su devolución de llamada, y crear un buffer temporal para el manejo de los datos durante la conversión:

AudioConverterRef formatConverterCanonicalTo16; 
@property AudioConverterRef formatConverterCanonicalTo16; 
@synthesize AudioConverterRef; 
AudioConverterNew(
    &monoCanonicalFormat, 
    &mono16Format, 
    &formatConverterCanonicalTo16 
); 

SInt16 *data16; 
@property (readwrite) SInt16 *data16; 
@synthesize data16; 
data16 = malloc(sizeof(SInt16) * 4096); 

A continuación, agregue esto a su devolución de llamada , antes de guardar los datos:

UInt32 dataSizeCanonical = ioData->mBuffers[0].mDataByteSize; 
SInt32 *dataCanonical = (SInt32 *)ioData->mBuffers[0].mData; 
UInt32 dataSize16 = dataSizeCanonical; 

AudioConverterConvertBuffer(
    effectState->formatConverterCanonicalTo16, 
    dataSizeCanonical, 
    dataCanonical, 
    &dataSize16, 
    effectState->data16 
); 

continuación, puede guardar Data16, que está en formato de 16 bits y puede ser lo que desea guardó en su archivo. Será más compatible y la mitad de grande que los datos canónicos.

Cuando haya terminado, usted puede limpiar un par de cosas:

AudioConverterDispose(formatConverterCanonicalTo16); 
free(data16); 
+0

gracias! mi devolución de llamada se maneja pero estoy grabando ruido! – lefakir

+0

Acabo de actualizar mi respuesta para incluir los próximos pasos ahora que tiene la devolución de llamada en ejecución. – arlomedia

+0

Acabo de editar mi pregunta para proporcionar más código de lo que estoy tratando de hacer – lefakir

Cuestiones relacionadas