草庐IT

AudioToolBox 解码AAC

pengxiaochao 2023-09-28 原文

上一篇文章中,我们针对PCM 数据,通过AudioToolBoxPCM 数据编码成AAC 数据,并把AAC 数据添加ADTS Header,并把AAC格式的音频数据写入文件;

这一章呢,我们主要是用AudioToolBoxAAC数据 解码成PCM格式,并利用AVFoundation框架把PCM数据 从扬声器播放处理;

1. 音频采集

关于音频采集部分,上篇文章已经介绍过了,是采用 AVFoundation 框架 对AVCaptureSessionSession 进行封装,添加音频输入源,然后添加 output输出,通过采集音频设备,最后通过代理方法拿到音频流PCM 数据;这里不做过多赘述 ,可以参考上篇文章AudioToolBox 编码AAC 或者直接看源码:https://github.com/hunter858/OpenGL_Study

2. AAC数据获取

AAC的原始数据,也是上篇文章介绍过的,通过 AudioEncoder 拿到的编码后的AAC数据部分,不包含ADTS header部分;因为ADTS Header主要是写入文件需要的;
通过AudioEncoderaudioEncodeCallback 代理方法拿到编码后的AAC 数据;

- (void)audioEncodeCallback:(NSData *)aacData;

3. AudioToolBox 创建

关于AudioToolBox的创建 和编码部分一致,只是解码部分,把inputoutput的配置 做了一个调换(这么理解);
这里我们创建一个AudioDecoder类封装AudioToolBox对象,通过AudioConfig音频配置来初始化 硬件编码器所需要的一些参数 ;

源码如下:

- (void)setupEncoder {
   
   //输出参数pcm
   AudioStreamBasicDescription outputAudioDes = {0};
   outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采样率
   outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //输出声道数
   outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //输出格式
   outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //编码 12
   outputAudioDes.mFramesPerPacket = 1;                            //每一个packet帧数 ;
   outputAudioDes.mBitsPerChannel = 16;                             //数据帧中每个通道的采样位数。
   outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一帧大小(采样位数 / 8 *声道数)
   outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每个packet大小(帧大小 * 帧数)
   outputAudioDes.mReserved =  0;                                  //对其方式 0(8字节对齐)
   
   //输入参数aac
   AudioStreamBasicDescription inputAduioDes = {0};
   inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
   inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
   inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
   inputAduioDes.mFramesPerPacket = 1024;
   inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
   
   //填充输出相关信息
   UInt32 inDesSize = sizeof(inputAduioDes);
   AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
   
   //获取解码器的描述信息(只能传入software)
   AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
   /** 创建converter
    参数1:输入音频格式描述
    参数2:输出音频格式描述
    参数3:class desc的数量
    参数4:class desc
    参数5:创建的解码器
    */
   OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
   if (status != noErr) {
       NSLog(@"Error!:硬解码AAC创建失败, status= %d", (int)status);
       return;
   }
}

4. 解码AAC

在通过 AudioEncoder 的 代理方法 - (void)audioEncodeCallback:(NSData *)aacData; 拿到编码后的AAC 数据后,直接把 AAC数据送入解码器,还原AAC数据至PCM格式;

关于解码部分的源码如下:

- (void)decodeAudioAACData:(NSData *)aacData {
  
   if (!_audioConverter) { return; }
   
   dispatch_async(_decoderQueue, ^{
    
       //记录aac 作为参数参入解码回调函数
       CCAudioUserData userData = {0};
       userData.channelCount = (UInt32)_config.channelCount;
       userData.data = (char *)[aacData bytes];
       userData.size = (UInt32)aacData.length;
       userData.packetDesc.mDataByteSize = (UInt32)aacData.length;
       userData.packetDesc.mStartOffset = 0;
       userData.packetDesc.mVariableFramesInPacket = 0;
       
       //输出大小和packet个数
       UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
       UInt32 pcmDataPacketSize = 1024;
       
       //创建临时容器pcm
       uint8_t *pcmBuffer = malloc(pcmBufferSize);
       memset(pcmBuffer, 0, pcmBufferSize);
       
       //输出buffer
       AudioBufferList outAudioBufferList = {0};
       outAudioBufferList.mNumberBuffers = 1;
       outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
       outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
       outAudioBufferList.mBuffers[0].mData = pcmBuffer;
       
       //输出描述
       AudioStreamPacketDescription outputPacketDesc = {0};
       
       //配置填充函数,获取输出数据
       OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
       if (status != noErr) {
           NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
           return;
       }
       //如果获取到数据
       if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
           NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
           dispatch_async(_callbackQueue, ^{
               [_delegate audioDecodeCallback:rawData];
           });
       }
       free(pcmBuffer);
   });
   
}


static OSStatus AudioDecoderConverterComplexInputDataProc(  AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,  AudioStreamPacketDescription **outDataPacketDescription,  void *inUserData) {
    
    
    CCAudioUserData *audioDecoder = (CCAudioUserData *)(inUserData);
    if (audioDecoder->size <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
   
    //填充数据
    *outDataPacketDescription = &audioDecoder->packetDesc;
    (*outDataPacketDescription)[0].mStartOffset = 0;
    (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
    (*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
    
    ioData->mBuffers[0].mData = audioDecoder->data;
    ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
    ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
    
    return noErr;
}

5. PCM 播放

拿到PCM数据后,如何验证我们解码是否成功,我们有2种办法;

  • 第一种方法是把 PCM保存下来使用 ffmpeg 进行播放;
  • 第二种方法是把 PCM 从扬声器播放出来;
5.1 PCM播放(方法一):

从沙盒拿到PCM 文件后,在终端键入如下命令 (记得安装ffmpeg
例子:ffplay -ar 44100 -ac 1 -f s16le -i /Users/pengchao/Desktop/2022_06_25_20:44:34.pcm

image.png
fplay -ar 44100 -ac 1 -f s16le -i ./201904091310_test.pcm

-ar 表示采样率

-ac 表示音频通道数
    单声道是 1,Android 中为 AudioFormat.CHANNEL_IN_MONO
    双声道是 2,Android 中为 AudioFormat.CHANNEL_IN_STEREO

-f 表示 pcm 格式,sample_fmts + le(小端)或者 be(大端)
    sample_fmts可以通过ffplay -sample_fmts来查询

-i 表示输入文件,这里就是 pcm 文件
5.2 PCM播放(方法二)

通过 AudioPCMPlayer 类,对PCM数据进行播放,这里AudioPCMPlayer 是一个 基于 AudioQueue的封装;有兴趣的同学可以去看源码;

总结

源码地址: 源码地址 源码地址: https://github.com/hunter858/OpenGL_Study/AVFoundation/AudiotoolBox-decoder

有关AudioToolBox 解码AAC的更多相关文章

  1. ruby - 如何以编程方式将 mp3 转换为 itunes 可播放的 aac/m4a 文件? - 2

    我一直在寻找一种以编程方式或通过命令行将mp3转换为aac的方法,但没有成功。理想情况下,我有一段代码可以从我的Rails应用程序中调用,将mp3转换为aac。我安装了ffmpeg和libfaac,并能够使用以下命令创建aac文件:ffmpeg-itest.mp3-acodeclibfaac-ab163840dest.aac当我将输出文件的名称更改为dest.m4a时,它无法在iTunes中播放。谢谢! 最佳答案 FFmpeg提供AAC编码功能(如果您已编译它们)。如果您使用的是Windows,则可以从here获取完整的二进制文件。

  2. ruby-on-rails - JSON解码参数问题 - 2

    我有一个使用postgresql的Rails4应用程序。我还有一个backbone.js应用程序,可将JSON推送到Rails4应用程序。这是我的Controller:defcreate@product=Product.new(ActiveSupport::JSON.decodeproduct_params)respond_todo|format|if@product.saveformat.json{renderaction:'show',status::created,location:@product}elseformat.json{renderjson:@product.erro

  3. ruby-on-rails - ActiveSupport::JSON 解码散列丢失符号 - 2

    我正在尝试序列化和反序列化哈希。当散列被反序列化时,键被去符号化;例如不是更多:一个,而是“一个”。从Rails控制台:>>h={:one=>1,:two=>"two"}{:one=>1,:two=>"two"}>>j=ActiveSupport::JSON.encode(h)"{\"one\":1,\"two\":\"two\"}">>h2=ActiveSupport::JSON.decode(j){"one"=>1,"two"=>"two"}>>h2[:one]nil>>h[:one]1我现在已经切换到使用Marshal.dump/load。但是,我想把它扔出去看看是否有办法将它保

  4. html - 如何在 Ruby 中编码/解码 HTML 实体? - 2

    我正在尝试解码一些HTML实体,例如'&lt;'成为'.我有一个旧gem(html_helpers),但它似乎已经被遗弃了两次。有什么建议吗?我需要在模型中使用它。 最佳答案 要对字符进行编码,可以使用CGI.escapeHTML:string=CGI.escapeHTML('test"escaping"')要解码它们,有CGI.unescapeHTML:CGI.unescapeHTML("test"unescaping"<characters>")当然,在此之前你需要包含CGI库:requi

  5. ruby - 在 Ruby 中解码十六进制字符串 - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Hextobinaryinruby在Python中,我可以执行以下操作:>>>str='000E0000000000'>>>str.decode('hex')'\x00\x0e\x00\x00\x00\x00\x00'如果我必须在ruby​​中实现相同的输出,我可以调用哪个?to_s(16)试过了,好像不行。我需要采用该特定格式的输出,因此我希望得到以下内容:"\\x00\\x0e\\x00\\x00\\x00\\x00\\x00"

  6. ruby - 在 Ruby/Sinatra 中解码 Facebook 的签名请求 - 2

    由于Facebook弃用新的FBML,我正在寻找一种创建“显示”选项卡(向粉丝显示一个版本而向非粉丝显示另一个版本的页面选项卡)的新方法。Facebook已将数据添加到signed_request:Whenauserselectsyourappintheleft-handmenu,theappwillreceivethesigned_requestparameterwithoneadditionalparameter,page,aJSONarraywhichcontainsthe‘id’oftheFacebookPageyourTabishostedwithin,aboolean(‘l

  7. ruby-on-rails - 使用 Ruby/Rails 进行 base 64 URL 解码? - 2

    我正在使用FacebookAPI和RubyonRails,我正在尝试解析返回的JSON。我遇到的问题是Facebookbase64URL对其数据进行编码。Ruby没有内置的base64URL解码。关于base64编码和base64URL编码的区别,seewikipedia.我如何使用Ruby/Rails对此进行解码?编辑:因为有些人阅读有困难——base64URL与base64不同 最佳答案 Dmitry的回答是正确的。它说明了在字符串解码之前必须出现的“=”符号填充。我一直收到格式错误的JSON,最后发现这是由于填充造成的。Rea

  8. ruby - 将十六进制字符串转换(解码)为二进制字符串 - 2

    如何在Ruby中将"1234567890"转换为"\x12\x34\x56\x78\x90"? 最佳答案 试试这个:["1234567890"].pack('H*') 关于ruby-将十六进制字符串转换(解码)为二进制字符串,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/862140/

  9. ruby - 解码base64字符串并写入文件 - 2

    我正在尝试读取包含编码的base64字符串的文件,并将解码后的输出写入另一个文件。我的Input.txt包含一个base64字符串,类似于:PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48cmV2aWV3LWNhc2UgY3JlYXRl\r\nZGF0ZT0iMTMvTWFyLzIwMTQgMDk6MDQ6NTEiIHN5c3RlbT0iVHJhZmlndXJhX1RlbXBsYXRlX01h\r\nbmFnZW1lbnRfdjUuMSIgYmF0Y2hpZD0iMCIgdHJhbnNhY3Rpb25ubz0iMSIgYmF0Y2

  10. javascript - JWT解码返回null - 2

    我是Node.js的新手,正在阅读FabianCook的Node.jsEssentials。当尝试使用JWT进行身份验证时,我从jwt.decode(token)得到了一个NULL,但该token可以由jwt.io上的调试器解析。代码有什么问题?varPassport=require('passport');varLocalStrategy=require('passport-local').Strategy;varExpress=require('express');varBodyParser=require('body-parser');varjwt=require('jsonwe

随机推荐