Подтвердить что ты не робот

Как записать звук, производимый выходом микшерного блока (iOS Core Audio & Audio Graph)

Я пытаюсь записать звук, создаваемый выходом устройства микшера.

На данный момент мой код основан на приложении Java MixerHost для iOS: A mixer node подключен к удаленный IO node на аудиографе.

И я пытаюсь установить обратный вызов ввода на вход remote IO node на выходе микшера.

Я делаю что-то неправильно, но я не могу найти ошибку.

Вот приведенный ниже код. Это делается сразу после установки Multichannel Mixer Setup:

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

Затем создайте формат потока:

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

Затем примените формат и укажите частоту дискретизации:

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

Затем я пытаюсь установить обратный вызов render.

Решение 1 → > мой обратный вызов никогда не называется

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

Решение 2 → > При запуске приложения на этом меняет приложение

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

Если у кого-то есть идея...

EDIT Solution 3 (спасибо arlo anwser) → В настоящее время существует проблема с форматом

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

и настройки файла:

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

И обратный вызов записи:

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

В выходном файле что-то отсутствует output.caf:). Я полностью потерян в форматах, которые нужно применять.

4b9b3361

Ответ 1

Я не думаю, что вам нужно включить ввод в блок ввода-вывода. Я также хотел бы прокомментировать формат и настройку частоты дискретизации, которые вы делаете на модуле ввода-вывода, пока не получите ваш обратный вызов, потому что несоответствующий или неподдерживаемый формат может препятствовать соединению аудиоустройств.

Чтобы добавить обратный вызов, попробуйте этот метод:

AudioUnitAddRenderNotify(
    iOUnit,
    &recordingCallback,
    self
);

По-видимому, другие методы заменят соединение node, но этот метод не будет - ваши аудиоустройства могут оставаться на связи, даже если вы добавили обратный вызов.

Как только ваш обратный вызов будет запущен, если вы обнаружите, что нет данных в буферах (ioData), оберните этот код вокруг кода обратного вызова:

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

Это необходимо, потому что обратный вызов, добавленный таким образом, запускается как до, так и после того, как аудиоустройство отображает его аудио, но вы просто хотите запустить свой код после его отображения.

Как только обратный вызов будет запущен, следующим шагом будет выяснить, какой аудиоформат он получает и соответствующим образом обрабатывает. Попробуйте добавить это к вашему обратному вызову:

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

Это замедлит ваше приложение настолько, что оно, вероятно, не позволит воспроизвести какой-либо звук, но вы сможете запустить его достаточно долго, чтобы увидеть, как выглядят образцы. Если обратный вызов получает 16-битный звук, образцы должны быть положительными или отрицательными целыми числами от -32000 до 32000. Если сэмплы чередуются между нормальным числом и гораздо меньшим числом, попробуйте этот код вместо вашего обратного вызова:

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

Это должно показать вам образцы 8.24.

Если вы можете сохранить данные в формате, который получает обратный вызов, тогда вы должны иметь то, что вам нужно. Если вам нужно сохранить его в другом формате, вы должны иметь возможность преобразовать формат в аудиоустройство удаленного ввода-вывода... но я не имел смог выяснить, как это сделать, когда он подключен к многоканальному микшерному блоку. В качестве альтернативы вы можете конвертировать данные с помощью Audio Converter Services. Сначала определите входные и выходные форматы:

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;

Затем определите конвертер где-то вне вашего обратного вызова и создайте временный буфер для обработки данных во время преобразования:

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

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

Затем добавьте это в свой обратный вызов, прежде чем сохранять свои данные:

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

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

Затем вы можете сохранить данные16, который находится в 16-битном формате и может быть тем, что вы хотите сохранить в своем файле. Он будет более совместимым и вдвое большим, чем канонические данные.

Когда вы закончите, вы можете очистить пару вещей:

AudioConverterDispose(formatConverterCanonicalTo16);
free(data16);