从附加到remoteio音频单元输入范围的渲染回调中的循环缓冲区中消耗音频数据

时间:2014-01-09 22:08:45

标签: ios core-audio remoteio

标题几乎总结了我正在努力实现的目标。我正在尝试在渲染回调中使用Michael Tyson的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 个答案:

答案 0 :(得分:1)

对于立体声(每帧2个通道),每帧的字节数和每个数据包的字节数必须是样本大小的两倍(以字节为单位)。与每个通道的位数相同。

补充:如果availableBytes / yourFrameSize几乎不总是大于或者大于inNumberFrames,那么你将无法获得更多的连续声音。

答案 1 :(得分:0)

乍一看,看起来你已经正确设置了一切。你错过了对AudioOutputUnitStart()的电话:

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

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

答案 2 :(得分:0)

我相信您的问题在于使用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毫秒)。如果您需要更快的延迟,可以尝试降低它。