买域名可以自己做网站吗,沧州做网站价格,网络推广公司专业网络,工会网站建设方案今天记录是的是 使用 AudioToolbox 框架 使用 AudioConverterRef 工具进行本地音频文件的编码和解码。 本文打仓库代码为#xff1a; JBLocalAudioFileConvecter
分别实现了#xff1a;
flac,mp3等其他音频编码文件 转换成 pcm文件。 #xff08;解码#xff09;pcm文件 …今天记录是的是 使用 AudioToolbox 框架 使用 AudioConverterRef 工具进行本地音频文件的编码和解码。 本文打仓库代码为 JBLocalAudioFileConvecter
分别实现了
flac,mp3等其他音频编码文件 转换成 pcm文件。 解码pcm文件 转换成 flac,mp3等其他音频编码文件。 编码 两者的代码基本一样只是在输入和输出的时候对应的 静态码率(CBR)和动态码率(VBR)在处理和取值的时候有所区别。
运行本demo对应的ui 界面的按钮入口为 在这里插入图片描述
1. 全局变量
列出来头部 所申明的所有全局属性和成员变量。都有说明。
interface JBLocalAudioFileConvecter()
{
publicAudioFileID _inFile; //输入文件指针AudioFileID _outFile; //输出文件1 .caf 文件会在头部包含必要的asbd信息FILE *_outFile_2; //输出文件2 .pcm全是裸数据char * _inBuffer; //复用的输入缓冲区
}property (atomic, assign) BOOL isRunning; //代表是否正在编解码
property (nonatomic, strong) dispatch_queue_t workQueue;
property (nonatomic, strong) NSURL *inFileURL;
property (nonatomic, strong) NSURL *outFileURL1; //输出文件 使用 AudioFileID
property (nonatomic, strong) NSURL *outFileURL2; //输出文件 使用 FILE */**------ 输入 ---------**/
property (nonatomic, assign) AudioStreamBasicDescription inASBD;
property (nonatomic, assign) UInt32 inMaxSizePerPacket; //输入文件包里面的最大尺寸的包的尺寸
property (nonatomic, assign) UInt32 inNumPacketPerRead; //输入文件的自己malloc的buffer内能容纳的最大packet数量
property (nonatomic, assign) UInt32 inPacketReadIndex; //输入文件读取的包的下标
property (nonatomic, assign) AudioStreamPacketDescription *inASPDs;/**------ 输出 ---------**/
property (atomic, assign) AudioFormatID outputFormat; //输出文件格式pcm? aac? flac? mp3?
property (nonatomic, assign) AudioStreamBasicDescription outASBD;
property (nonatomic, assign) AudioStreamPacketDescription *outASPDs;end2. 入口函数
这里输入一个文件然后输出了两种文件两种输出文件的写入方式稍微有点区别。 并且 工作的显示是在 workQueue 的子线程中不阻塞主线程。
flac - pcm 解码入口
outputFormat 设置为 kAudioFormatLinearPCM, 代表着后面会 已 PCM 进行输出。
- (void)startConvertFlac_to_pcm {if (self.isRunning) {return;};[self setupStartData];dispatch_async(self.workQueue, ^{self.inFileURL [[NSBundle mainBundle] URLForResource:句号_10s withExtension:flac];self.outFileURL1 [JBHelper getOutputPathWithFile:output.caf]; //包含头信息self.outFileURL2 [JBHelper getOutputPathWithFile:pcm_output.pcm]; //纯裸数据self.outputFormat kAudioFormatLinearPCM;[self workSubThread];});
}pcm - flac 编码入口
outputFormat 设置为 kAudioFormatFLAC, 代表着后面会 以 FLAC 格式进行输出
- (void)startConvertPcm_to_flac {if (self.isRunning) {return;};[self setupStartData];dispatch_async(self.workQueue, ^{self.inFileURL [[NSBundle mainBundle] URLForResource:pcm_44100_setro_s16be withExtension:caf];self.outFileURL1 [JBHelper getOutputPathWithFile:apple_out.flac];self.outFileURL2 [JBHelper getOutputPathWithFile:c_out.flac];self.outputFormat kAudioFormatFLAC; //可以改成其他类型比如aacMP3等[self workSubThread];});
}其中两者都调用了setupStartData 函数它是重置所有变量的。
- (void)setupStartData {self.isRunning YES;self.inASPDs NULL;self.outASPDs NULL;self.inASPDs NULL;_outFile_2 NULL;_inBuffer NULL;_inFile NULL;_outFile NULL;self.outputFormat kAudioFormatLinearPCM;self.inPacketReadIndex 0;
}3. 首先打开 输入音频文件
工作函数子线程 workSubThread 是一个很长的函数基本操作都在这里之所以没抽多少出去是因为看流程的时候比较方便。
首先打开 输入音频文件 JBAssertNoError(AudioFileOpenURL((__bridge CFURLRef)self.inFileURL, kAudioFileReadPermission, 0, _inFile), AudioFileOpenURL);这里开始有个宏 JBAssertNoError, 需要说下是定义在 JBHelper.h 中的会判断这行代码的返回status 是否有问题出错了就 进入断言停止程序。
#define JBAssertNoError(inError, inMessage) \
{ \
SInt32 __Err (inError); \
if(__Err ! 0) \
{ \
NSLog( 出现错误: % code: %d(%s), inMessage, __Err, FourCC2Str(__Err));\
NSAssert(__Err 0, inMessage);\
}\
}4. 完善输入输出的ASBD
4.1 获取输入文件的ASBD
设置到全局变量 _inASBD 里 UInt32 size sizeof(AudioStreamBasicDescription);JBAssertNoError(AudioFileGetProperty(_inFile, kAudioFilePropertyDataFormat, size, _inASBD), AudioFileGetProperty kAudioFilePropertyDataFormat);4.2 填充输出文件的ASBD
这儿是根据输入文件的ASBD和前面设置的输出的文件格式self.outputFormat综合生成的需要注意的是如果是非 PCM的话大部分的ASBD的值都需要使用AudioFormatGetProperty函数去自动获取因为有些格式是VBR数据是不定的不能手动填充 //填充了一个 16字节的 整型的音频数据格式
- (void)fillUpOutASBDWithInputFile {AudioStreamBasicDescription asbd {0};//mSampleRate 使用 输入文件的 的值asbd.mSampleRate self.inASBD.mSampleRate;//输入固定为pcm文件asbd.mFormatID self.outputFormat;if (asbd.mFormatID kAudioFormatLinearPCM) {//kAudioFormatFlagIsSignedInteger 还是 kAudioFormatFlagIsFloat 看自己输入而定//大端小端根据自己情况而定asbd.mFormatFlags kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;//下面的数据都是计算得出asbd.mChannelsPerFrame self.inASBD.mChannelsPerFrame;asbd.mBitsPerChannel 16;asbd.mBytesPerFrame (asbd.mBitsPerChannel 3) * asbd.mChannelsPerFrame;//pcm的一个包里面只有一个小样本帧asbd.mFramesPerPacket 1;asbd.mBytesPerPacket asbd.mFramesPerPacket * asbd.mBytesPerFrame;asbd.mReserved self.inASBD.mReserved;} else {//非pcm的话其他参数可能设置不正确需要调用api来自动赋值。asbd.mChannelsPerFrame (asbd.mFormatID kAudioFormatiLBC ? 1 : self.inASBD.mChannelsPerFrame);UInt32 size sizeof(asbd);//使用format api 自动填充对应的参数数据JBAssertNoError(AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, size, asbd), AudioFormatGetProperty kAudioFormatProperty_FormatInfo);}self.outASBD asbd;
}5. 创建和打开输入文件
分别创建了两个输出文件进行测试对比 //打开 输出 音频 文件1JBAssertNoError(AudioFileCreateWithURL((__bridge CFURLRef)self.outFileURL1, kAudioFileCAFType, _outASBD, kAudioFileFlags_EraseFile, _outFile),AudioFileCreateWithURL);//打开 输出 音频 文件2if (_outFile_2 NULL) {NSString *pathStr_cfile [self.outFileURL2.absoluteString stringByReplacingOccurrencesOfString:file:// withString:];_outFile_2 fopen([pathStr_cfile UTF8String], wb);}6. 创建AudioConverterRef 转换对象
根据输入和输出的 ASBD 进行创建绑定 //创建转换对象AudioConverterRef audioConverter;JBAssertNoError(AudioConverterNew(_inASBD, _outASBD, audioConverter),AudioConverterNew);7. 读取和设置magic cookie
magic cookie 的简单作用就是 存储metadata 和部分编解码所需要的数据。 我们可以看下下图 flac 转 pcm 解码出来的文件里面的 magic cookie 的前面是啥样的 可以看到 16进制转换后有 caff 类型里面包含的格式是 lpcm 的格式。
从输入文件中读取设置到audioConver中后面会在 第11 节设置到 输出文件中
- (void)readMagicCookie:(AudioConverterRef)converter {//先获取长度UInt32 cookieDataSize 0;UInt32 isWriteAble 0;//注意这里是AudioFileGetPropertyInfo 获取长度和是否可以写OSStatus status AudioFileGetPropertyInfo(_inFile, kAudioFilePropertyMagicCookieData, cookieDataSize, isWriteAble);//有些没有 magic cookie 所以不管if (status ! noErr) {NSLog(读取 magic cookie 不存在忽略掉);return;}if (cookieDataSize 0) {NSLog(AudioFileGetPropertyInfo kAudioFilePropertyMagicCookieData get zero size data);return;}//根据长度获取对应的magic data 的内容Byte *cookieData malloc(cookieDataSize *sizeof(Byte));//这里是AudioFileGetPropertyJBAssertNoError(AudioFileGetProperty(_inFile, kAudioFilePropertyMagicCookieData, cookieDataSize, cookieData),AudioFileGetProperty kAudioFilePropertyMagicCookieData);//将获取的MagicCookie 设置到 converter 中JBAssertNoError(AudioConverterSetProperty(converter,kAudioConverterDecompressionMagicCookie,cookieDataSize,cookieData),AudioConverterSetProperty kAudioConverterDecompressionMagicCookie);// malloc 后必须 freefree(cookieData);
}8. 重置获取校验过的ASBD
ASBD 设置到 AudioConverter 后被在里面被自动修正某些有问题的值 所以我们重新从里面获取修正后的值 //重新从 Audio Convert 获取被校正过的 ASBD数据JBAssertNoError(AudioConverterGetProperty(audioConverter, kAudioConverterCurrentInputStreamDescription, size, _inASBD), kAudioConverterCurrentInputStreamDescription get infile);JBAssertNoError(AudioConverterGetProperty(audioConverter, kAudioConverterCurrentOutputStreamDescription, size, _outASBD), kAudioConverterCurrentInputStreamDescription get outfile);9. 开辟输入缓冲区
大体流程如下
创建_inBuffer 全局复用大小手动设定的VBR 的话需要获取 每次读取的 packet的数量根据数量创建 输入的 aspd 的内存大小供后面解码使用。 CBR的话就不需要。 //设置输入缓冲区的数据大小UInt32 inBufferSize 4096*8;_inBuffer malloc(inBufferSize);if (self.inASBD.mBytesPerPacket 0) {//输入文件的 单个packet 理论上的最大大小。/**据了解 kAudioFilePropertyPacketSizeUpperBound 不会打开整个文件只是预测kAudioFilePropertyMaximumPacketSize 有可能会打开文件来计算*/UInt32 inSizePerPacket 0; //18466 in this file aacsize sizeof(inSizePerPacket);JBAssertNoError(AudioFileGetProperty(_inFile, kAudioFilePropertyPacketSizeUpperBound, size, inSizePerPacket), kAudioFilePropertyPacketSizeUpperBound);self.inMaxSizePerPacket inSizePerPacket;//每次读取的packet的数量必须根据我们 输入缓冲区的大小来决定self.inNumPacketPerRead inBufferSize / inSizePerPacket;// VBR 可变比特率self.inASPDs calloc(self.inNumPacketPerRead, sizeof(AudioStreamPacketDescription));} else {// CBR 固定比特率self.inMaxSizePerPacket self.inASBD.mBytesPerPacket;self.inNumPacketPerRead inBufferSize / self.inASBD.mBytesPerPacket;self.inASPDs NULL;}10. 开辟输出缓冲区
这里输出缓冲区的大小和输入一样当然也可以设置成不一样的值。 如果 输出是 VBR 的话也需要获取每次输出 的 包的数量根据数量创建 输出的 aspd 的内存大小供后面编码使用。 CBR的话就不需要。 /** 配置输出信息 */char *outBuffer;UInt32 outBufferSize 4096*8; //输出缓冲区的大小这里设置成和输入一样的值outBuffer (char *)malloc(outBufferSize);memset(outBuffer, 0, outBufferSize);UInt32 outSizePerPacket self.outASBD.mBytesPerPacket; //4//理论上输入缓冲区能容纳下的最大 packet 数量UInt numPaketPerOut outBufferSize / outSizePerPacket;if(outSizePerPacket 0) {//输出的 VBR 需要重新计算每个包的 包大小getAudioConverterProperty(audioConverter,kAudioConverterPropertyMaximumOutputPacketSize,outSizePerPacket,kAudioConverterPropertyMaximumOutputPacketSize);numPaketPerOut outBufferSize / outSizePerPacket;self.outASPDs calloc(numPaketPerOut, sizeof(AudioStreamPacketDescription));}11. 写入magic cookie
写入 第7节 设置的值
- (void)writeMagicCookie:(AudioConverterRef)converter {UInt32 cookieDataSize 0;OSStatus status AudioConverterGetPropertyInfo(converter, kAudioConverterCompressionMagicCookie, cookieDataSize, NULL);if (status ! noErr cookieDataSize 0) {NSLog(写入 magic cookie 不存在忽略掉);return;}void *cookies malloc(cookieDataSize);status AudioConverterGetProperty(converter, kAudioConverterDecompressionMagicCookie, cookieDataSize, cookies);if (status noErr) {status AudioFileSetProperty(_outFile, kAudioFilePropertyMagicCookieData, cookieDataSize, cookies);printErr(AudioFileSetProperty kAudioFilePropertyMagicCookieData, status);} else {NSLog(magic cookie 是空的忽略);}free(cookies);
}12. while 循环进行 数据处理
这里就是 最重要的 读取文件编解码写入文件的环节了。 一个while 循环进行处理数据当读完了或者出错了才退出循环。
首先每次创建 outBufferList的栈变量供输出数据使用其中里面的 data 为 前面开辟的outBuffer 复用内存大小也是前面算出来的。AudioConverterFillComplexBuffer调用这个函数进行音频转换填充其中第二个参数为函数指针在 下一节讲里面的代码会在里面进行读取数据并塞入上一步创建debuffer 中系统自动进行数据转换转换完成后这个函数会有个返回值。所以说这个函数是阻塞式的。至此 数据转换完成并放入了outBuffer,也就是 outBufferList.mBuffers[0].mData中就是我们编解码好的数据我们将 调用AudioFileWritePackets 和 fwrite 分别写入 输出文件1 和 2 中。两者写入时候传的参数有所区别。最后不要完了outFilePacketOffset ioOutDataPacketsPerOut;, 不然下次写入的位置不对 UInt64 totalOutputFrames_debug 0;UInt32 outFilePacketOffset 0; //输出文件的写入偏移量OSStatus status noErr;//阻塞式while (true) {if (!_isRunning) {break;}//创建输出的AudioBufferAudioBufferList outBufferList {0};outBufferList.mNumberBuffers 1;outBufferList.mBuffers[0].mNumberChannels self.outASBD.mChannelsPerFrame;outBufferList.mBuffers[0].mDataByteSize outBufferSize;outBufferList.mBuffers[0].mData outBuffer; //这里直接将我们上面malloc的对象赋值进去每次重用堆空间//malloc的输入缓冲区能够装下的最大包数量UInt32 ioOutDataPacketsPerOut numPaketPerOut;status AudioConverterFillComplexBuffer(audioConverter,JBAudioConverterCallback,(__bridge void *)self,ioOutDataPacketsPerOut, //输出的数量outBufferList, //输出的bufferself.outASPDs //输出文件的aspd我们在上面开辟了内存);printErr(AudioConverterFillComplexBuffer, status);if(status ! noErr) {NSLog(AudioConverterFillComplexBuffer--- 失败了退出);break;} else if (ioOutDataPacketsPerOut 0) {//EOF, 文件读完了status noErr;break;}NSLog(convert 输出写入包数量%d, offset:%d size:%d,, ioOutDataPacketsPerOut, outFilePacketOffset, outBufferList.mBuffers[0].mDataByteSize);//写入文件 到 AudioFileJBAssertNoError(AudioFileWritePackets(_outFile,false,outBufferList.mBuffers[0].mDataByteSize,self.outASPDs,outFilePacketOffset,ioOutDataPacketsPerOut,outBuffer),AudioFileWritePackets);//写入文件到FILE *fwrite((char *)outBufferList.mBuffers[0].mData, sizeof(char), outBufferList.mBuffers[0].mDataByteSize, _outFile_2);//debug 统计if (self.outASBD.mBytesPerPacket 0) {totalOutputFrames_debug (ioOutDataPacketsPerOut * self.outASBD.mFramesPerPacket);} else if (self.outASPDs) {for(UInt32 i 0; i ioOutDataPacketsPerOut; i) {totalOutputFrames_debug self.outASPDs[i].mVariableFramesInPacket;}}//下次输出文件的时候需要增加这次输出的数量outFilePacketOffset ioOutDataPacketsPerOut;} //end while13. 音频转换的函数指针
在上一步 AudioConverterFillComplexBuffer 中我们配置了这函数系统会在特定时候调用这个函数我们只需要读取特定大小的数据写入ioData 中也就是上一步的 outBufferList 中。
首先我们需要ioNumberDataPackets 判断读取的数量是否 会超出我们开辟的空间大小AudioFileReadPacketData然后调用函数进行读取文件并输出到_inBuffer 这个全局变量中jbClass.inPacketReadIndex 输入文件的偏移量往后移ioData 根据自己实际获取的数据填充这个对象。如果输入文件时VBR的话也需要填充 outDataPacketDescription, 方便进行解码和计算
最后系统会自动对ioData进行编解码数据转换并在上一节的 outBufferList对象里面获取 转换好的值。
static OSStatus JBAudioConverterCallback (AudioConverterRef inAudioConverter,UInt32 * ioNumberDataPackets,AudioBufferList * ioData,AudioStreamPacketDescription * __nullable * __nullable outDataPacketDescription,void * __nullable inUserData) {JBLocalAudioFileConvecter *jbClass (__bridge JBLocalAudioFileConvecter *)inUserData;//不能超过我们开的的内存的最大空间UInt32 maxPackets jbClass.inNumPacketPerRead;if(*ioNumberDataPackets maxPackets) {*ioNumberDataPackets maxPackets;}if (*ioNumberDataPackets 0) {NSLog(*ioNumberDataPackets 0);//读完了没有了return noErr;}//读取文件到 内存UInt32 outNumBytes jbClass.inMaxSizePerPacket * (*ioNumberDataPackets); //这个值必须非0根据最大包大小计算的值读取的时候会改成实际值OSStatus status AudioFileReadPacketData(jbClass-_inFile,false,outNumBytes,jbClass.inASPDs,jbClass.inPacketReadIndex,ioNumberDataPackets,jbClass-_inBuffer);printErr(AudioFileReadPacketData, status);NSLog(io读取%d, outNumBytes);if (eofErr status) return noErr;jbClass.inPacketReadIndex *ioNumberDataPackets;//将自己获取的到buffer塞入 io队列中。ioData-mBuffers[0].mData jbClass-_inBuffer;ioData-mBuffers[0].mDataByteSize outNumBytes;ioData-mBuffers[0].mNumberChannels jbClass.inASBD.mChannelsPerFrame;//aspdif (outDataPacketDescription) {*outDataPacketDescription jbClass.inASPDs;}return noErr;
}14. 处理后续需要写入的尾巴数据
while 循环结束转换结束的时候可能还有些末尾的meta数据需要进行追加。 if (status noErr) {if (self.outASBD.mBitsPerChannel 0) {NSLog(总共写入frame数量: %lld, totalOutputFrames_debug);[self writePacketTableInfo_inTrailer:audioConverter];}//在写一次cookie有时编解码器会在转换结束时更新 cookie[self writeMagicCookie:audioConverter];}writePacketTableInfo_inTrailer 函数如下
/**kAudioConverterPrimeInfoAudioConverter的启动信息。一些音频数据格式转换特别是那些涉及采样率转换的音频数据格式转换当有leadingFrames或trailingFrames可用时会产生更高质量的输出。 这些启动信息的适当数量取决于输入的音频数据格式。*/
- (void)writePacketTableInfo_inTrailer:(AudioConverterRef)converter {/**mNumberPackets是包含在文件中的音频数据的总的分组数mPrimingFrames是经过分组(“packetized”)的流用作准备和/或处理等待时间的帧数mRemainderFrames是最后分组遗留下的帧数。例如AAC位流可能仅有在其最后分组中有效的313帧。每分组的帧为1024所以在此情形mRemainderFrames是(1024-313)其表示了当进行解码时应该从最后分组的输出中裁剪下来的样本数。如果经过编码的位流正被编辑那么就推荐在将会占据至少mPrimingFrames的编辑点之前的分组应该被所述编辑所采用以从编辑点中确保音频的完美再现。当然在随机访问文件中的不同分组以便播放时mPrimingFrames就应该被用来在所想要的点上重新构造音频。*/UInt32 size 0;UInt32 isWritable;OSStatus status AudioFileGetPropertyInfo(_outFile, kAudioFilePropertyPacketTableInfo, size, isWritable);printErr(AudioFileGetPropertyInfo kAudioFilePropertyPacketTableInfo, status);if (noErr ! status || isWritable 0) {NSLog(AudioFileGetPropertyInfo kAudioFilePropertyPacketTableInfo failed return);return;}/**UInt32 leadingFrames; - 0UInt32 trailingFrames; - 1368*/AudioConverterPrimeInfo primeInfo;size sizeof(primeInfo);status AudioConverterGetProperty(converter, kAudioConverterPrimeInfo, size, primeInfo);printErr(AudioConverterGetProperty kAudioConverterPrimeInfo, status);if (status !noErr) return;/**SInt64 mNumberValidFrames; - 442368SInt32 mPrimingFrames; - 0SInt32 mRemainderFrames; - 0*/AudioFilePacketTableInfo pTableInfo;size sizeof(pTableInfo);status AudioFileGetProperty(_outFile, kAudioFilePropertyPacketTableInfo, size, pTableInfo);printErr(AudioFileGetProperty kAudioFilePropertyPacketTableInfo, status);if (status !noErr) return;//获取总数量的帧数UInt64 totalFrames pTableInfo.mNumberValidFrames pTableInfo.mPrimingFrames pTableInfo.mRemainderFrames;pTableInfo.mPrimingFrames primeInfo.leadingFrames;pTableInfo.mRemainderFrames primeInfo.trailingFrames;pTableInfo.mNumberValidFrames totalFrames - pTableInfo.mPrimingFrames - pTableInfo.mRemainderFrames;NSLog(table info 里面包含的总数量的帧数: %llu,\t mNumberValidFrames:%lld,\t mPrimingFrames%d,\t mRemainderFrames:%d, totalFrames, pTableInfo.mNumberValidFrames, pTableInfo.mPrimingFrames, pTableInfo.mRemainderFrames);/**SInt64 mNumberValidFrames; - 441100SInt32 mPrimingFrames; - 0SInt32 mRemainderFrames; - 1368 (1368 不够组成一个 flac packet所以被遗留下来)*/status AudioFileSetProperty(_outFile, kAudioFilePropertyPacketTableInfo, sizeof(pTableInfo), pTableInfo);printErr(AudioFileSetProperty kAudioFilePropertyPacketTableInfo, status);
}15. 释放相关内存 NSLog(convert 结束);if (outBuffer) {free(outBuffer);outBuffer NULL;}if(_inBuffer) {free(_inBuffer);_inBuffer NULL;}if (_outFile_2) {fclose(_outFile_2);_outFile_2 NULL;}//关闭和释放AudioConverter的资源JBAssertNoError( AudioConverterDispose(audioConverter),AudioConverterFillComplexBuffer);if (_inFile) {JBAssertNoError(AudioFileClose(_inFile), AudioFileClose in);_inFile NULL;}if (_outFile) {JBAssertNoError(AudioFileClose(_outFile),AudioFileClose out);_outFile NULL;}NSLog(所有结束\n);self.isRunning NO;16. 打印输出文件
调用辅助函数打印两个输出文件并可以使用 打印中的log直接在 命令行 工具中进行播放验证效果
[JBHelper prisnFFmpegLogWithASBD:self.outASBD path:[self.outFileURL1.absoluteString stringByReplacingOccurrencesOfString:file:// withString:] preLog:apple caf file:\t];[JBHelper prisnFFmpegLogWithASBD:self.outASBD path:[self.outFileURL2.absoluteString stringByReplacingOccurrencesOfString:file:// withString:] preLog:c write pcm file:\t];打印如下
apple caf file: ffplay /Users/jimbo/Library/Caches/com.jimbo.mac.coreaudio.CoreAudioDemo/output.caf
c write pcm file: ffplay -ar 44100 -ac 2 -f s16be /Users/jimbo/Library/Caches/com.jimbo.mac.coreaudio.CoreAudioDemo/pcm_output.pcm直接使用 ffplay命令播放 pcm文件. ffplay 需要使用brew install ffmpeg 安装
最终我们的音频编解码完成了。
原版地址 http://t.csdn.cn/RA9dv