Comment save le son produit par la sortie de l'unité de mixage (iOS Core Audio & Audio Graph)

J'essaie d'save le son produit par une sortie de l'unité de mixage.

Pour l'instant, mon code est basé sur la démo de l' application Apple MixerHost iOS : Un nœud de mixage est connecté à un nœud E / S distant sur le graphe audio.

Et j'essaye de placer un callback d'input sur l'input de noeud d'E / S distante sur la sortie de mélangeur.

Je fais quelque chose de mal mais je ne peux pas find l'erreur.

Voici le code ci-dessous. Ceci est fait juste après l'installation de l'unité de mixage multicanal:

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;} */ 

Ensuite, créez un format de stream:

 // 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]; 

Affectez ensuite le format et spécifiez la fréquence d'échantillonnage:

 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;} 

Ensuite, j'essaie de définir le callback de rendu.

Solution 1 >>> mon callback d'logging n'est jamais appelé

 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;} 

Solution 2 >>> mon application se bloque au lancement sur cette

 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 quelqu'un a une idée …

EDIT Solution 3 (merci à arlo anwser) >> Il y a maintenant un problème de format

 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 ); 

et la configuration du file:

 if (noErr != result) {[self printErrorMessage: @"AUGraphInitialize" withStatus: result]; return;} // On initialise le file audio NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSSsortingng *documentsDirectory = [paths objectAtIndex:0]; NSSsortingng *destinationFilePath = [[[NSSsortingng alloc] initWithFormat: @"%@/output.caf", documentsDirectory] autorelease]; NSLog(@">>> %@", destinationFilePath); CFURLRef destinationURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFSsortingngRef)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"); 

Et le callback d'logging:

 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; } 

Il manque quelque chose dans le file de sortie output.caf :). Je suis totalement perdu dans les formats à appliquer.

    Je ne pense pas que vous devez activer l'input sur l'unité d'E / S. Je voudrais également commenter la configuration du format et de la fréquence d'échantillonnage que vous effectuez sur l'unité d'E / S jusqu'à ce que le callback soit activé, car un format incompatible ou non pris en charge peut empêcher les unités audio d'être liées set.

    Pour append le callback, essayez cette méthode:

     AudioUnitAddRenderNotify( iOUnit, &recordingCallback, self ); 

    Apparemment, les autres methods replaceont la connection de nœud, mais cette méthode ne le fera pas – ainsi vos unités audio peuvent restr connectées même si vous avez ajouté un callback.

    Une fois que votre callback est en cours d'exécution, si vous trouvez qu'il n'y a pas de données dans les tampons (ioData), enveloppez ce code autour de votre code de callback:

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

    Cela est nécessaire car un callback ajouté de cette manière s'exécute avant et après que l'unité audio restitue son audio, mais vous voulez juste exécuter votre code après son rendu.

    Une fois le callback en cours, l'étape suivante consiste à déterminer quel format audio il reçoit et à le gérer de manière appropriée. Essayez d'append ceci à votre callback:

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

    Cela ralentira tellement votre application que cela empêchera probablement toute lecture audio, mais vous devriez pouvoir l'exécuter assez longtime pour voir à quoi ressemblent les samples. Si le callback reçoit du son 16 bits, les échantillons doivent être des entiers positifs ou négatifs compris entre -32000 et 32000. Si les échantillons alternent entre un nombre normal et un nombre beaucoup plus petit, essayez plutôt ce code dans votre callback:

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

    Cela devrait vous montrer les 8.24 échantillons complets.

    Si vous pouvez save datatables dans le format que le callback reçoit, vous devriez avoir ce dont vous avez besoin. Si vous avez besoin de le sauvegarder dans un format différent, vous devriez pouvoir convertir le format dans l'unité audio d'E / S distantes … mais je n'ai pas été capable de comprendre comment le faire quand il est connecté à un Multichannel Unité de mélangeur. Comme alternative, vous pouvez convertir datatables en utilisant les services de convertisseur audio . D'abord, définissez les formats d'input et de sortie:

     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; 

    Ensuite, définissez un convertisseur quelque part en dehors de votre callback, et créez un tampon temporaire pour gérer datatables pendant la conversion:

     AudioConverterRef formatConverterCanonicalTo16; @property AudioConverterRef formatConverterCanonicalTo16; @synthesize AudioConverterRef; AudioConverterNew( &monoCanonicalFormat, &mono16Format, &formatConverterCanonicalTo16 ); SInt16 *data16; @property (readwrite) SInt16 *data16; @synthesize data16; data16 = malloc(sizeof(SInt16) * 4096); 

    Ajoutez-le ensuite à votre callback avant de sauvegarder vos données:

     UInt32 dataSizeCanonical = ioData->mBuffers[0].mDataByteSize; SInt32 *dataCanonical = (SInt32 *)ioData->mBuffers[0].mData; UInt32 dataSize16 = dataSizeCanonical; AudioConverterConvertBuffer( effectState->formatConverterCanonicalTo16, dataSizeCanonical, dataCanonical, &dataSize16, effectState->data16 ); 

    Ensuite, vous pouvez save data16, qui est au format 16 bits et pourrait être ce que vous voulez enregistré dans votre file. Il sera plus compatible et moitié less grand que datatables canoniques.

    Lorsque vous avez terminé, vous pouvez nettoyer quelques choses:

     AudioConverterDispose(formatConverterCanonicalTo16); free(data16);