核心音频:文件播放渲染回调函数
Core audio: file playback render callback function
我正在使用 RemoteIO 音频单元 在我的应用程序中使用 kAudioUnitProperty_ScheduledFileIDs
进行音频播放。
音频文件采用 PCM 格式。我如何为这种情况实现渲染回调函数,以便我可以手动修改缓冲区样本?
这是我的代码:
static AudioComponentInstance audioUnit;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
CheckError(AudioComponentInstanceNew(comp, &audioUnit), "error AudioComponentInstanceNew");
NSURL *playerFile = [[NSBundle mainBundle] URLForResource:@"short" withExtension:@"wav"];
AudioFileID audioFileID;
CheckError(AudioFileOpenURL((__bridge CFURLRef)playerFile, kAudioFileReadPermission, 0, &audioFileID), "error AudioFileOpenURL");
// Determine file properties
UInt64 packetCount;
UInt32 size = sizeof(packetCount);
CheckError(AudioFileGetProperty(audioFileID, kAudioFilePropertyAudioDataPacketCount, &size, &packetCount),
"AudioFileGetProperty(kAudioFilePropertyAudioDataPacketCount)");
AudioStreamBasicDescription dataFormat;
size = sizeof(dataFormat);
CheckError(AudioFileGetProperty(audioFileID, kAudioFilePropertyDataFormat, &size, &dataFormat),
"AudioFileGetProperty(kAudioFilePropertyDataFormat)");
// Assign the region to play
ScheduledAudioFileRegion region;
memset (®ion.mTimeStamp, 0, sizeof(region.mTimeStamp));
region.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
region.mTimeStamp.mSampleTime = 0;
region.mCompletionProc = NULL;
region.mCompletionProcUserData = NULL;
region.mAudioFile = audioFileID;
region.mLoopCount = 0;
region.mStartFrame = 0;
region.mFramesToPlay = (UInt32)packetCount * dataFormat.mFramesPerPacket;
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, ®ion, sizeof(region)),
"AudioUnitSetProperty(kAudioUnitProperty_ScheduledFileRegion)");
// Prime the player by reading some frames from disk
UInt32 defaultNumberOfFrames = 0;
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ScheduledFilePrime, kAudioUnitScope_Global, 0, &defaultNumberOfFrames, sizeof(defaultNumberOfFrames)),
"AudioUnitSetProperty(kAudioUnitProperty_ScheduledFilePrime)");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = MyCallback;
callbackStruct.inputProcRefCon = (__bridge void * _Nullable)(self);
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct)), "error AudioUnitSetProperty[kAudioUnitProperty_setRenderCallback]");
CheckError(AudioUnitInitialize(audioUnit), "error AudioUnitInitialize");
回调函数:
static OSStatus MyCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData){
printf("my callback");
return noErr;
}
音频单元在按下按钮时开始播放:
- (IBAction)playSound:(id)sender {
CheckError(AudioOutputUnitStart(audioUnit), "error AudioOutputUnitStart");
}
此代码在编译期间失败并出现 kAudioUnitErr_InvalidProperty(-10879) 错误。目标是修改从 AudioFileID 读取的缓冲区样本,并将结果发送给扬声器。
鉴于您刚刚熟悉核心音频,我建议您首先让您的 remoteIO 回调独立于您的文件播放器工作。只需删除所有与文件播放器相关的代码,然后尝试让它先工作。
然后,一旦你成功了,就继续合并你的文件播放器。
据我所知,这是错误的,我认为您将音频文件服务 API 与音频单元混淆了。此 API 用于将文件读入缓冲区,您可以手动将其提供给 remoteIO,如果您确实想走这条路,请使用扩展音频文件服务 API,这会容易得多。 kAudioUnitProperty_ScheduledFileRegion
属性 应该在文件播放器音频单元上调用。要获得其中之一,您需要以与 remoteIO 相同的方式创建它,但 AudioComponentDescription 的 componentSubType 和 componentType 分别为 kAudioUnitSubType_AudioFilePlayer
和 kAudioUnitType_Generator
除外。然后,一旦你有了那个单元,你需要使用 kAudioUnitProperty_MakeConnection
属性.
将它连接到 remoteIO
但说真的,首先让你的 remoteIO 回调正常工作,然后尝试制作一个文件播放器音频单元并连接它(没有回调),然后从那里开始。
独立询问有关每个步骤的非常具体的问题,发布您尝试过但不起作用的代码,您将获得大量帮助。
我正在使用 RemoteIO 音频单元 在我的应用程序中使用 kAudioUnitProperty_ScheduledFileIDs
进行音频播放。
音频文件采用 PCM 格式。我如何为这种情况实现渲染回调函数,以便我可以手动修改缓冲区样本?
这是我的代码:
static AudioComponentInstance audioUnit;
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
CheckError(AudioComponentInstanceNew(comp, &audioUnit), "error AudioComponentInstanceNew");
NSURL *playerFile = [[NSBundle mainBundle] URLForResource:@"short" withExtension:@"wav"];
AudioFileID audioFileID;
CheckError(AudioFileOpenURL((__bridge CFURLRef)playerFile, kAudioFileReadPermission, 0, &audioFileID), "error AudioFileOpenURL");
// Determine file properties
UInt64 packetCount;
UInt32 size = sizeof(packetCount);
CheckError(AudioFileGetProperty(audioFileID, kAudioFilePropertyAudioDataPacketCount, &size, &packetCount),
"AudioFileGetProperty(kAudioFilePropertyAudioDataPacketCount)");
AudioStreamBasicDescription dataFormat;
size = sizeof(dataFormat);
CheckError(AudioFileGetProperty(audioFileID, kAudioFilePropertyDataFormat, &size, &dataFormat),
"AudioFileGetProperty(kAudioFilePropertyDataFormat)");
// Assign the region to play
ScheduledAudioFileRegion region;
memset (®ion.mTimeStamp, 0, sizeof(region.mTimeStamp));
region.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
region.mTimeStamp.mSampleTime = 0;
region.mCompletionProc = NULL;
region.mCompletionProcUserData = NULL;
region.mAudioFile = audioFileID;
region.mLoopCount = 0;
region.mStartFrame = 0;
region.mFramesToPlay = (UInt32)packetCount * dataFormat.mFramesPerPacket;
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, ®ion, sizeof(region)),
"AudioUnitSetProperty(kAudioUnitProperty_ScheduledFileRegion)");
// Prime the player by reading some frames from disk
UInt32 defaultNumberOfFrames = 0;
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_ScheduledFilePrime, kAudioUnitScope_Global, 0, &defaultNumberOfFrames, sizeof(defaultNumberOfFrames)),
"AudioUnitSetProperty(kAudioUnitProperty_ScheduledFilePrime)");
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = MyCallback;
callbackStruct.inputProcRefCon = (__bridge void * _Nullable)(self);
CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct)), "error AudioUnitSetProperty[kAudioUnitProperty_setRenderCallback]");
CheckError(AudioUnitInitialize(audioUnit), "error AudioUnitInitialize");
回调函数:
static OSStatus MyCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData){
printf("my callback");
return noErr;
}
音频单元在按下按钮时开始播放:
- (IBAction)playSound:(id)sender {
CheckError(AudioOutputUnitStart(audioUnit), "error AudioOutputUnitStart");
}
此代码在编译期间失败并出现 kAudioUnitErr_InvalidProperty(-10879) 错误。目标是修改从 AudioFileID 读取的缓冲区样本,并将结果发送给扬声器。
鉴于您刚刚熟悉核心音频,我建议您首先让您的 remoteIO 回调独立于您的文件播放器工作。只需删除所有与文件播放器相关的代码,然后尝试让它先工作。
然后,一旦你成功了,就继续合并你的文件播放器。
据我所知,这是错误的,我认为您将音频文件服务 API 与音频单元混淆了。此 API 用于将文件读入缓冲区,您可以手动将其提供给 remoteIO,如果您确实想走这条路,请使用扩展音频文件服务 API,这会容易得多。 kAudioUnitProperty_ScheduledFileRegion
属性 应该在文件播放器音频单元上调用。要获得其中之一,您需要以与 remoteIO 相同的方式创建它,但 AudioComponentDescription 的 componentSubType 和 componentType 分别为 kAudioUnitSubType_AudioFilePlayer
和 kAudioUnitType_Generator
除外。然后,一旦你有了那个单元,你需要使用 kAudioUnitProperty_MakeConnection
属性.
但说真的,首先让你的 remoteIO 回调正常工作,然后尝试制作一个文件播放器音频单元并连接它(没有回调),然后从那里开始。
独立询问有关每个步骤的非常具体的问题,发布您尝试过但不起作用的代码,您将获得大量帮助。