2

标题几乎总结了我想要实现的目标。我正在尝试在渲染回调中使用 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,但我想看看是否有人可以为我在这里尝试做的事情提出解决方案。谢谢!

4

3 回答 3

1

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

补充:如果availableBytes/yourFrameSize 几乎不总是与inNumberFrames 一样大或更大,您将不会获得太多连续的声音。

于 2014-01-11T03:54:45.253 回答
0

乍一看,您似乎已正确设置了所有内容。你错过了一个电话AudioOutputUnitStart()

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

// now your callback should be being called
...
于 2014-01-10T15:59:16.640 回答
0

我相信你的一个问题是使用streamFormat.mBitsPerChannel = bytesPerSample * 8;

您分配bytesPerSamplesizeof(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 毫秒)。如果您需要更快的延迟,您可以尝试降低它。

于 2014-01-11T19:11:25.230 回答