Использование аудиоданных из кольцевого буфера при обратном вызове рендеринга, прикрепленном к области ввода аудиоустройства

Название в значительной степени подводит итог того, что я пытаюсь достичь. Я пытаюсь использовать TPCircularBuffer Майкла Тайсона внутри обратного вызова рендеринга, пока кольцевой буфер заполняется входящими аудиоданными. Я хочу отправить звук из обратного вызова рендеринга на выходной элемент аудиоустройства RemoteIO, чтобы я мог слышать его через динамики устройства.

Звук представляет собой 16-битное стерео чередование, поступающее в виде пакетов по 2048 кадров. Вот как я настроил свой аудио сеанс:

#define kInputBus 1
#define kOutputBus 0
NSError *err = nil;
NSTimeInterval ioBufferDuration = 46;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&err];
[session setPreferredIOBufferDuration:ioBufferDuration error:&err];
[session setActive:YES error:&err];
AudioComponentDescription defaultOutputDescription;
defaultOutputDescription.componentType = kAudioUnitType_Output;
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
defaultOutputDescription.componentFlags = 0;
defaultOutputDescription.componentFlagsMask = 0;

AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
NSAssert(defaultOutput, @"Can't find default output.");

AudioComponentInstanceNew(defaultOutput, &remoteIOUnit);
UInt32 flag = 0;

OSStatus status = AudioUnitSetProperty(remoteIOUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kOutputBus, &flag, sizeof(flag));
size_t bytesPerSample = sizeof(AudioUnitSampleType);
AudioStreamBasicDescription streamFormat = {0};
streamFormat.mSampleRate = 44100.00;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags = kAudioFormatFlagsCanonical;
streamFormat.mBytesPerPacket = bytesPerSample;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBytesPerFrame = bytesPerSample;
streamFormat.mChannelsPerFrame = 2;
streamFormat.mBitsPerChannel = bytesPerSample * 8;
streamFormat.mReserved = 0;

status = AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &streamFormat, sizeof(streamFormat));

AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = render;
callbackStruct.inputProcRefCon = self;
status = AudioUnitSetProperty(remoteIOUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &callbackStruct, sizeof(callbackStruct));

И вот где аудиоданные загружаются в кольцевой буфер и используются в обратном вызове рендеринга:

#define kBufferLength 2048
-(void)loadBytes:(Byte *)byteArrPtr{
TPCircularBufferProduceBytes(&buffer, byteArrPtr, kBufferLength);
}

OSStatus render(
                void *inRefCon,
                AudioUnitRenderActionFlags *ioActionFlags,
                const AudioTimeStamp *inTimeStamp,
                UInt32 inBusNumber,
                UInt32 inNumberFrames,
                AudioBufferList *ioData)
{
AUDIOIO *audio = (__bridge AUDIOIO *)inRefCon;
AudioSampleType *outSample = (AudioSampleType *)ioData->mBuffers[0].mData;
//Zero outSample
memset(outSample, 0, kBufferLength);
int bytesToCopy = ioData->mBuffers[0].mDataByteSize;
SInt16 *targetBuffer = (SInt16 *)ioData->mBuffers[0].mData;
//Pull audio
int32_t availableBytes;
SInt16 *buffer = TPCircularBufferTail(&audio->buffer, &availableBytes);
memcpy(targetBuffer, buffer, MIN(bytesToCopy, availableBytes));
TPCircularBufferConsume(&audio->buffer, MIN(bytesToCopy, availableBytes));
return noErr;
}

Что-то не так с этой настройкой, потому что я не получаю звук через динамики, но я также не получаю никаких ошибок при тестировании на моем устройстве. Насколько я могу судить, TPCircularBuffer заполняется и читается правильно. Я следовал документации Apple для настройки аудио сеанса. Я подумываю попытаться настроить AUGraph, но хочу посмотреть, сможет ли кто-нибудь предложить решение для того, что я пытаюсь сделать здесь. Спасибо!

3 ответа

Для стерео (2 канала на кадр) ваши байты на кадр и байты на пакет должны быть в два раза больше размера выборки в байтах. То же самое с битами на канал в терминах битов.

Добавлено: если availableBytes/yourFrameSize не всегда так велики или больше, чем inNumberFrames, вы не получите много непрерывного звука.

На первый взгляд кажется, что все настроено правильно. Вы пропускаете звонок AudioOutputUnitStart() хоть:

...
// returns an OSStatus indicating success / fail
AudioOutputUnitStart(remoteIOUnit);

// now your callback should be being called
...

Я считаю, что одна ваша проблема с использованием streamFormat.mBitsPerChannel = bytesPerSample * 8;

Вы назначаете bytesPerSample быть sizeof(AudioUnitSampleType) что по сути 4 байта.

Так streamFormat.mBytesPerPacket = bytesPerSample; в порядке Но назначение streamFormat.mBitsPerChannel = bytesPerSample * 8; говорит, что вы хотите 32 бит на семпл вместо 16 бит на семпл.

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

#define BITS_PER_CHANNEL 16
#define SAMPLE_RATE 44100.0
#define CHANNELS_PER_FRAME 2
#define BYTES_PER_FRAME CHANNELS_PER_FRAME * (BITS_PER_CHANNEL / 8)  //ie 4
#define FRAMES_PER_PACKET 1
#define BYTES_PER_PACKET FRAMES_PER_PACKET * BYTES_PER_FRAME




    streamFormat.mSampleRate = SAMPLE_RATE;  // 44100.0
    streamFormat.mBitsPerChannel = BITS_PER_CHANNEL; //16
    streamFormat.mChannelsPerFrame = CHANNELS_PER_FRAME; // 2
    streamFormat.mFramesPerPacket = FRAMES_PER_PACKET; //1
    streamFormat.mBytesPerFrame = BYTES_PER_FRAME; // 4 total,  2 for left ch,  2 for right ch

    streamFormat.mBytesPerPacket = BYTES_PER_PACKET;

    streamFormat.mReserved = 0;
    streamFormat.mFormatID = kAudioFormatLinearPCM;  // double check this also
    streamFormat.mFormatFlags = kAudioFormatFlagsCanonical;`

Вам также необходимо просмотреть возвращаемые значения, установленные для err и status, сразу после каждого запуска. Вам все еще нужно добавить проверку ошибок при некоторых вызовах, таких как

checkMyReturnValueToo = AudioComponentInstanceNew(defaultOutput, &remoteIOUnit);

У вас также есть чрезвычайно высокое значение для вашей длительности буфера. У вас есть 46, и я не уверен, откуда это взялось. Это означает, что вы хотите получить звук за 46 секунд во время каждого обратного вызова. Обычно вы хотите что-то менее одной секунды, в зависимости от ваших требований к задержке. Скорее всего, iOS не будет использовать ничего более высокого, но вы должны попробовать установить значение 0.025 или около того (25 мс). Вы можете попытаться уменьшить его, если вам нужно более быстрое время ожидания.

Другие вопросы по тегам