AudioToolbox
1. AudioUnit 驱动扬声器
参考官方 DefaultOutputUnit,经修改使其产生扬声器可播放范围内且人耳可以听到的音频,官方给的示例无法听到声音,通过本例学会驱动默认扬声器;
输出声音的时候,AudioOutputUnitStart 是非阻塞的,且要运行在事件循环下:
CFRunLoopRunInMode 或 CFRunLoopRun
扬声器会在回调函数里面传入它想要的数据量,通过以下公式产生sin波形数据:
Float32 nextFloat = sin(j / cycleLength * (M_PI * 2.0)) * amplitude;
配置参数, Amplitude 在0~1之间即可,扬声器支持16位、32位的采样,精度对音质有稍许影响,不过我的耳朵是分辨不出来:
AuContext AuCtx = { .WhichFormat = kAs16Bit, // kAsFloat, .SampleRate = 44100, .NumChannels = 2, .SinWaveFrameCount = 0, .Amplitude = 0.25, .ToneFrequency = 128000, .sampleNextPrinted = 0, };
注意通道数据的拷贝,0为左声道,1为右声道,同样是 AudioBufferList 的结构。

1.1 应用场景
测试扬声器;
可以很方便的和 AUGraph 整合,完成混音等操作,然后使用音频输出单元播放声音;
通过改变部分参数,测试特定参数下的扬声器音效、质量;
2. AudioUnit 驱动麦克风
本例使用 AUHAL 采集音频数据,通过 AudioToolbox 的 AudioConverterFillComplexBuffer 接口将数据转码为AAC数据,同时完成了降速采样然后编码的功能,因为硬件是192000hz的采样率,AAC一般为44100hz,然后封装为 flv 数据流,通过 librtmp 发送到 srs 服务器,通过 ffplay 播放音频,音质完美呈现!
同样,AUHAL的缓冲区会受到节能模式的影响,上面这几点在voip的那片文章里面已经说明过了。
另外提一点,AUHAL这一层在网上能找到很多文章介绍,与之对应的视频的这一层叫做 DAL,相关的 SampleCode 叫 MediaIO。

3. 从AVCapptureSession记录aac文件
Apple为音频记录提供了更简易的接口,但是某些场景下又是调用的该 API 采集数据,那就要与之对应的方法记录文件。
主要使用的是 ExtAudioFile 这一些列接口。
官方的 Demo 要想很好的使用,要构造合适的的buffer存储数据,里面的每一点都要吃透,在RTMP推流的那个项目里面有这些内容的应用。
该项目是非常好的示例,但是需要对各种音频格式有清晰的认识才能熟练运用、修改,譬如:


4. 获取所有音视频设备列表
这个就比较基础了,获取设备类型、数量、对应的ID、通道数、采样频率等。
注意到通道数是采样了一帧数据,通过AudioBufferList计算的。部分设备的采样率、通道数等参数是可以设置的,但是设置之前要查询硬件是否支持设置才能设置。

5. 音频文件转码
主要涉及CBR-固定比特率、VBR-变比特率、ABR-平均比特率等模式,其中前两种用的多,苹果官方规定以PCM作为中间类型文件,可以从苹果支持的各种编码格式转为PCM,也可以从PCM转码为各种格式。
转码的Demo在Mac、IOS都有,同时,某些编码器如果采用了硬件编码,那就是独占的,比如如果硬件编码aac,那么就不能同时多线程编码。
该程序参考官方Demo,做了简化和重构,方法如下:
将PCM转为AAC,转码的时候打印 LOG,看哪些分支有执行到,哪些分支没有执行到,那么对格式相关的内容就很清楚,最终就能清除完成一种VBR到CBR的转码需要执行哪些代码去处理数据,反之亦然。
网上转码的代码示例并不多,这部分的需求应该不大,主要是PCM转AAC会用到,其它格式的很少涉及,就不详细描写了。在RTMP推流的程序中就是用的该部分作为参考。
6. AUGraph播放音频文件
直接放代码吧,反正就一个文件可以解决:
// clang main_AUG.m -framework AVFoundation -framework CoreAudio -framework AudioToolbox -framework CoreFoundation -o app #import <AudioToolbox/AudioToolbox.h> #define kInputFileLocation CFSTR("/Users/alex/Desktop/Audio/qianzhihe.m4a") #pragma mark user-data struct typedef struct MyAUGraphPlayer{ AudioStreamBasicDescription inputFormat; AudioFileID inputFile; AUGraph graph; AudioUnit fileAU; }MyAUGraphPlayer; #pragma mark utility functions static void CheckError(OSStatus err, const char* operation){ if (err == noErr)return; char errorString[20]; *(UInt32 *)(errorString+1) = CFSwapInt32HostToBig(err); if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])){ errorString[0] = errorString[5] = '\''; errorString[6] = '\0'; } else{ sprintf(errorString, "%d", (int)err); } fprintf(stderr, "Error: %s (%s)\n",operation, errorString); exit(1); } void CreateMyAUGraph(MyAUGraphPlayer *player){ // 0. 创建Graph CheckError(NewAUGraph(&player->graph), "NewAUGraph failed"); // 1.1 创建输出节点(扬声器) AudioComponentDescription outputcd = {0}; outputcd.componentType = kAudioUnitType_Output; outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; // 1.2 添加输出节点至Graph AUNode outputNode; CheckError(AUGraphAddNode(player->graph, &outputcd, &outputNode), "AUGraphAddNode output node failed"); // 2.1 创建输入节点(音频文件) AudioComponentDescription fileplayercd = {0}; fileplayercd.componentType = kAudioUnitType_Generator; fileplayercd.componentSubType = kAudioUnitSubType_AudioFilePlayer; fileplayercd.componentManufacturer = kAudioUnitManufacturer_Apple; // 2.2 输入节点添加至Graph AUNode fileplayerNode; CheckError(AUGraphAddNode(player->graph, &fileplayercd, &fileplayerNode), "AUGraphAddNode File Player failed"); // 3. 打开Graph CheckError(AUGraphOpen(player->graph), "AUGraphOpen failed"); CheckError(AUGraphNodeInfo(player->graph, fileplayerNode, NULL, &player->fileAU), "AUGraphNodeInfo failed"); // 4. 连接至输入节点 CheckError(AUGraphConnectNodeInput(player->graph, fileplayerNode, 0, outputNode, 0), "Graph connect failed"); // 5. 初始化Graph CheckError(AUGraphInitialize(player->graph), "AUGraphInitialize failed"); } Float64 PrepareFileAU(MyAUGraphPlayer *player){ // 1. 设置AU输入源为文件 CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduledFileIDs, kAudioUnitScope_Global, 0, &player->inputFile, sizeof(player->inputFile)), "AudioUnitSetProperty failed"); // 2. 获取文件的Packets总数 UInt64 nPackets; UInt32 propSize = sizeof(nPackets); CheckError(AudioFileGetProperty(player->inputFile, kAudioFilePropertyAudioDataPacketCount, &propSize, &nPackets), "AudioFileGetProperty"); // 3. 设置播放模式 ScheduledAudioFileRegion rgn = {0}; rgn.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; rgn.mTimeStamp.mSampleTime = 0; rgn.mCompletionProc = NULL; rgn.mCompletionProcUserData = NULL; // 回调?自动处理 rgn.mAudioFile = player->inputFile; // 类似文件列表 rgn.mLoopCount = 1; // 循环播放次数 rgn.mStartFrame = 0; // 起始位置 rgn.mFramesToPlay = nPackets * (player->inputFormat.mFramesPerPacket); CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &rgn, sizeof(rgn)), "AudioUnitSetProperty failed"); // 4. 设置起始时间戳 AudioTimeStamp startTime = {0}; startTime.mFlags = kAudioTimeStampSampleTimeValid; startTime.mSampleTime = -1; CheckError(AudioUnitSetProperty(player->fileAU, kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, 0, &startTime, sizeof(startTime)), "AudioUnitSetProperty Schedule Start time stamp failed"); return (nPackets * player->inputFormat.mFramesPerPacket) / player->inputFormat.mSampleRate; } #pragma mark main function int main(int argc, const char * argv[]) { // 初始化文件 CFURLRef inputFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, kInputFileLocation, kCFURLPOSIXPathStyle, false); MyAUGraphPlayer player = {0}; CheckError(AudioFileOpenURL(inputFileURL, kAudioFileReadPermission, 0, &player.inputFile), "Audio File Open failed"); CFRelease(inputFileURL); // 用文件初始化ASBD UInt32 propSize = sizeof(player.inputFormat); CheckError(AudioFileGetProperty(player.inputFile, kAudioFilePropertyDataFormat, &propSize, &player.inputFormat), // ASBD "Audio File Get Property failed"); CreateMyAUGraph(&player); // 创建AUG Float64 fileDuration = PrepareFileAU(&player); // 创建文件输入 CheckError(AUGraphStart(player.graph), // 启动AUG "Audio Unit start failed"); usleep((int)fileDuration * 1000.0 * 1000.0); // AUG相当于多个子线程。主线程在这里阻塞等待。 AUGraphStop(player.graph); AUGraphUninitialize(player.graph); AUGraphClose(player.graph); AudioFileClose(player.inputFile); return 0; }
编译命令就在文件开头,需要修改开头的文件路径为正确的音频文件路径。
m4a格式的文件就是aac格式的封装,可以使用 mac 上的 avconfert 命令转码音频文件或从mp4文件里面提取该格式的文件。
7. 音频队列采集aac数据
也不讲了,会AudioUnit,这个也不会是问题。直接放代码:
// clang main.m -framework AVFoundation -framework CoreAudio -framework AudioToolbox -framework CoreFoundation -o app // Audio Queue 录制 aac 音频文件 // .caf格式的音频可以包含许多不同的音频格式,元数据,可以不用写 Magic Cookie。如果保存为ma4的文件需要写Cookie,就麻烦了。 // 参考:https://blog.csdn.net/tianjiqcs/article/details/59106844 #import <AudioToolbox/AudioToolbox.h> #define kNumberRecordBuffers 3 #pragma mark user data struct typedef struct MyRecorder{ AudioFileID recordFile; SInt64 recordPacket; Boolean running; }MyRecorder; #pragma mark utility functions static void CheckError(OSStatus err, const char* operation){ if (err == noErr)return; char errorString[20]; *(UInt32 *)(errorString+1) = CFSwapInt32HostToBig(err); if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])){ errorString[0] = errorString[5] = '\''; errorString[6] = '\0'; } else{ sprintf(errorString, "%d", (int)err); } fprintf(stderr, "Error: %s (%s)\n",operation, errorString); exit(1); } OSStatus MyGetDefaultInputDeviceSampleRate(Float64 *outSampleRate) { OSStatus error = noErr; AudioDeviceID deviceID = 0; UInt32 propertySize = sizeof(AudioDeviceID); // 1. 从麦克风获取硬件采样率,先从AOPA获取默认输入设备 AudioObjectPropertyAddress propertyAddress = {0}; propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; propertyAddress.mElement = 0; error = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &deviceID); if (error) { return error; } // 2. 再从默认输入设备获取 NominalSampleRate propertyAddress.mSelector = kAudioDevicePropertyNominalSampleRate; propertyAddress.mScope = kAudioObjectPropertyScopeGlobal; propertyAddress.mElement = 0; propertySize = sizeof(Float64); error = AudioHardwareServiceGetPropertyData(deviceID, &propertyAddress, 0, NULL, &propertySize, outSampleRate); return error; } static void MyCopyEncoderCookieToFile(AudioQueueRef queue, AudioFileID theFile) { OSStatus error; UInt32 propertySize; // 1. 从 Audio Queue 获取转码/压缩后的 Magic Cookie 的大小,如果获取到说明录制的数据输出格式是 VBR 的,支持 Magic Cookie error = AudioQueueGetPropertySize(queue, kAudioConverterCompressionMagicCookie, &propertySize); if(error == noErr && propertySize > 0) { Byte *magicCookie = (Byte*)malloc(propertySize); CheckError(AudioQueueGetProperty(queue, kAudioQueueProperty_MagicCookie, magicCookie, &propertySize), // 按该大小去获取 Cookie 数据 "Couldn't get audio queue's magic cookie"); CheckError(AudioFileSetProperty(theFile, kAudioFilePropertyMagicCookieData, propertySize, magicCookie), // 将获取到的Cookie设置到文件中去 "Couldn't set file's magic cookie"); free(magicCookie); } } static int MyComputeRecordBufferSize(const AudioStreamBasicDescription *format, AudioQueueRef queue, float seconds) { // 根据时长计算总帧数 int packets, frames, bytes; frames = (int)ceil(seconds * format->mSampleRate); // 先计算帧数。固定时间的帧数是一定的,帧的大小是固定的 if (format->mBytesPerFrame > 0) { bytes = frames * format->mBytesPerFrame; // 按照固定的容量计算buffer的大小。for CBR } else { // 从 AQ 获取最大 Packets 的大小。按理说,一个 Packet 是一个完整的可播放单位 UInt32 maxPacketSize; if (format->mBytesPerPacket) { maxPacketSize = format->mBytesPerPacket; // 获取到实际的大小 } else { UInt32 propertySize = sizeof(maxPacketSize); CheckError(AudioQueueGetProperty(queue, kAudioConverterPropertyMaximumOutputPacketSize, &maxPacketSize, // 获取最大值 &propertySize), "Couldn't get queue's maximum output size"); } if (format->mFramesPerPacket > 0) { // 获取到了则计算。总的packets数 = frames总数 / mFramesPerPacket每个包的frame数 packets = frames / format->mFramesPerPacket; // 注意: format->mBytesPerPacket format->mFramesPerPacket format->mBytesPerFrame } else{ packets = frames; // CBR,没获取到按照PCM计算。 } if (packets == 0) { packets = 1; } bytes = packets * maxPacketSize; // 按照最大的 packets 大小计算 buffer 容量,for VBR } return bytes; } #pragma mark record callback function // 由AQ调用,AQ满了则调用,从硬件获取数据,在回调中输出数据,写到文件中 // 边录音边转码。从硬件获取的数据,每填充一个buffer就调用一次该回调函数。 // 由此,该回调函数可以选择性处理数据? // 回调将 inBuffer 重新入队以便重用?说明在回调之前,inBuffer被出队了一次。影响的应该是转码 // 在回调里面控制AQ的结束(录制结束或播放结束时) // 录制和播放流程相反,录制是从回调函数中获取数据写入到文件,播放是播放完第一个Buffer时,从回调中喂给播放器数据以继续播放 // 假设录制到文件、从文件播放,那么,前者的AQ是硬件的 Raw data 的数据,后者的 AQ 是从文件获取到的数据。 // Queue的作用就是为了流畅录制、流畅播放。一个紧接麦克风,一个紧接扬声器 static void MyAQInputCallback(void *inUserData, AudioQueueRef inQueue, AudioQueueBufferRef inBuffer, const AudioTimeStamp *inStartTime, UInt32 inNumPackets, const AudioStreamPacketDescription *inPacketDesc) { MyRecorder *recorder = (MyRecorder*)inUserData; // 写入数据到文件 if (inNumPackets > 0) { CheckError(AudioFileWritePackets(recorder->recordFile, FALSE, inBuffer->mAudioDataByteSize, inPacketDesc, recorder->recordPacket, &inNumPackets, inBuffer->mAudioData), "Writing Audio File Packets failed"); } recorder->recordPacket += inNumPackets; // 入队以重用 if (recorder->running) // 不入队了就结束了 { CheckError(AudioQueueEnqueueBuffer(inQueue, inBuffer, 0, NULL), "AudioQueueEnqueueBuffer failed"); } } #pragma mark main function int main(int argc, const char * argv[]) { // 初始化 outASBD,用于录制文件的格式 MyRecorder recorder = {0}; AudioStreamBasicDescription recordFormat = {0}; MyGetDefaultInputDeviceSampleRate(&recordFormat.mSampleRate); // Configure the output data format to be AAC recordFormat.mFormatID = kAudioFormatAppleIMA4; // kAudioFormatMPEG4AAC; recordFormat.mChannelsPerFrame = 2; UInt32 propSize = sizeof(recordFormat); CheckError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &propSize, &recordFormat), "AudioFormatGetProperty failed"); // 创建音频队列、回调函数 AudioQueueRef queue = {0}; CheckError(AudioQueueNewInput(&recordFormat, MyAQInputCallback, &recorder, // User data NULL, // inCallbackRunLoop NULL, // inCallbackRunLoopMode 0, // Reserved for future use. Pass 0. &queue), // outAQ,返回时指向最新创建的 AQ 对象 "AudioQueueNewInput failed"); // 自动填充 UInt32 size = sizeof(recordFormat); CheckError(AudioQueueGetProperty(queue, kAudioConverterCurrentOutputStreamDescription, &recordFormat, &size), "AudioQueueGetProperty failed"); // 创建输出文件 CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("output.caf"), kCFURLPOSIXPathStyle, false); CheckError(AudioFileCreateWithURL(myFileURL, kAudioFileCAFType, &recordFormat, kAudioFileFlags_EraseFile, &recorder.recordFile), "AudioFileCreateWithURL failed"); CFRelease(myFileURL); // 从 AQ 拷贝 Magic Cookie 到输出文件 MyCopyEncoderCookieToFile(queue, recorder.recordFile); // 分配输出的 buffer。AQ由这三个 Buffer 组成。 int bufferByteSize = MyComputeRecordBufferSize(&recordFormat, queue, 0.5); // 分配三个 buffer 入队列 int bufferIndex; for (bufferIndex = 0; bufferIndex < kNumberRecordBuffers; ++bufferIndex) { AudioQueueBufferRef buffer; CheckError(AudioQueueAllocateBuffer(queue, bufferByteSize, &buffer), "AudioQueueAllocateBuffer failed"); CheckError(AudioQueueEnqueueBuffer(queue, buffer, 0, NULL), "AudioQueueEnqueueBuffer failed"); } recorder.running = TRUE; // 开始录制 CheckError(AudioQueueStart(queue, NULL),"AudioQueueStart failed"); printf("Recording... press <enter> to end:\n"); getchar(); printf("Recording done...\n"); recorder.running = FALSE; CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed"); MyCopyEncoderCookieToFile(queue,recorder.recordFile); AudioQueueDispose(queue, TRUE); AudioFileClose(recorder.recordFile); return 0; }
im4a 也是 aac 数据的封装
8. 音频队列播放aac数据
// clang main.m -framework AVFoundation -framework CoreAudio -framework AudioToolbox -framework CoreFoundation -o app #import <AudioToolbox/AudioToolbox.h> //Change the filename to something on your computer... #define kPlaybackFileLocation CFSTR("/Users/alex/Desktop/Audio/qianzhihe.m4a") #define kNumberPlaybackBuffers 3 #pragma mark user data struct //5.2 typedef struct MyPlayer{ AudioFileID playbackFile; SInt64 packetPosition; UInt32 numPacketsToRead; AudioStreamPacketDescription *packetDesc; Boolean isDone; }MyPlayer; #pragma mark utility functions //4.2 static void CheckError(OSStatus err, const char* operation){ if (err == noErr)return; char errorString[20]; *(UInt32 *)(errorString+1) = CFSwapInt32HostToBig(err); if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])){ errorString[0] = errorString[5] = '\''; errorString[6] = '\0'; } else{ sprintf(errorString, "%d", (int)err); } fprintf(stderr, "Error: %s (%s)\n",operation, errorString); exit(1); } //5.14 static void MyCopyEncoderCookieToQueue(AudioFileID theFile, AudioQueueRef queue) { UInt32 propertySize; OSStatus result = AudioFileGetProperty(theFile, kAudioFilePropertyMagicCookieData, &propertySize, NULL); if (result == noErr && propertySize > 0) { Byte* magicCookie = (UInt8*)malloc(sizeof(UInt8)*propertySize); CheckError(AudioFileGetProperty(theFile, kAudioFilePropertyMagicCookieData, &propertySize, magicCookie), "Audio File get magic cookie failed"); CheckError(AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, magicCookie, propertySize), "Audio Queue set magic cookie failed"); free(magicCookie); } } //5.15 void CalculateBytesForTime(AudioFileID inAudioFile, AudioStreamBasicDescription inDesc, Float64 inSeconds, UInt32 *outBufferSize, UInt32 *outNumPackets) { UInt32 maxPacketSize; UInt32 propSize = sizeof(maxPacketSize); CheckError(AudioFileGetProperty(inAudioFile, kAudioFilePropertyPacketSizeUpperBound, &propSize, &maxPacketSize), "Couldn't get file's max packet size"); static const int maxBufferSize = 0x10000; static const int minBufferSize = 0x4000; if (inDesc.mFramesPerPacket) { Float64 numPacketsForTime = inDesc.mSampleRate / inDesc.mFramesPerPacket * inSeconds; *outBufferSize = numPacketsForTime * maxPacketSize; } else{ *outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize; } if(*outBufferSize > maxBufferSize && *outBufferSize > maxPacketSize){ *outBufferSize = maxBufferSize; } else{ if(*outBufferSize < minBufferSize){ *outBufferSize = minBufferSize; } } *outNumPackets = *outBufferSize / maxPacketSize; } #pragma mark playback callback function //replace with listings 5.16-5.19 static void MyAQOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer) { MyPlayer *aqp = (MyPlayer*)inUserData; if (aqp->isDone) return; UInt32 numBytes; UInt32 nPackets = aqp->numPacketsToRead; CheckError(AudioFileReadPackets(aqp->playbackFile, false, &numBytes, aqp->packetDesc, aqp->packetPosition, &nPackets, inCompleteAQBuffer->mAudioData), "AudioFileReadPackets failed"); if (nPackets > 0) { inCompleteAQBuffer->mAudioDataByteSize = numBytes; AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, (aqp->packetDesc ? nPackets : 0), aqp->packetDesc); aqp->packetPosition += nPackets; } else { CheckError(AudioQueueStop(inAQ, false), "AudioQueueStop failed"); aqp->isDone = true; } } #pragma mark main function int main(int argc, const char *argv[]) { MyPlayer player = {0}; CFURLRef myFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, kPlaybackFileLocation, kCFURLPOSIXPathStyle, false); CheckError(AudioFileOpenURL(myFileURL, kAudioFileReadPermission, 0, &player.playbackFile), "AudioFileOpenURL failed"); CFRelease(myFileURL); AudioStreamBasicDescription dataFormat; UInt32 propSize = sizeof(dataFormat); CheckError(AudioFileGetProperty(player.playbackFile, kAudioFilePropertyDataFormat, &propSize, &dataFormat), "couldn't get file's data format"); AudioQueueRef queue; CheckError(AudioQueueNewOutput(&dataFormat, // ASBD MyAQOutputCallback, // Callback &player, // user data NULL, // run loop NULL, // run loop mode 0, // flags (always 0) &queue), // output: reference to AudioQueue object "AudioQueueNewOutput failed"); UInt32 bufferByteSize; CalculateBytesForTime(player.playbackFile, dataFormat, 0.5, &bufferByteSize, &player.numPacketsToRead); bool isFormatVBR = (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0); if (isFormatVBR) player.packetDesc = (AudioStreamPacketDescription*)malloc(sizeof(AudioStreamPacketDescription) * player.numPacketsToRead); else player.packetDesc = NULL; MyCopyEncoderCookieToQueue(player.playbackFile, queue); AudioQueueBufferRef buffers[kNumberPlaybackBuffers]; player.isDone = false; player.packetPosition = 0; int i; for (i = 0; i < kNumberPlaybackBuffers; ++i) { CheckError(AudioQueueAllocateBuffer(queue, bufferByteSize, &buffers[i]), "AudioQueueAllocateBuffer failed"); MyAQOutputCallback(&player, queue, buffers[i]); if (player.isDone) break; } CheckError(AudioQueueStart(queue, NULL), "AudioQueueStart failed"); printf("Playing...\n"); do { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false); } while (!player.isDone /*|| gIsRunning*/); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, false); player.isDone = true; CheckError(AudioQueueStop(queue, TRUE), "AudioQueueStop failed"); AudioQueueDispose(queue, TRUE); AudioFileClose(player.playbackFile); return 0; }
以上都是oc的代码,直接用代码开头的命令编译即可使用。

浙公网安备 33010602011771号