为您推荐:
精华内容
最热下载
问答
  • 5星
    1.01MB liuzehn 2021-02-02 15:33:27
  • 在音频中,无论直播与点播,AAC都是目前最常用的一种音频编码格式,例如RTMP直播,HLS直播,RTSP直播,FLV直播,FLV点播,MP4点播等文件中都是常见的AAC音频。 与MP3相比,AAC是一种编码效果更高,编码音质更好的...

      在音频中,无论直播与点播,AAC都是目前最常用的一种音频编码格式,例如RTMP直播,HLS直播,RTSP直播,FLV直播,FLV点播,MP4点播等文件中都是常见的AAC音频。

      与MP3相比,AAC是一种编码效果更高,编码音质更好的音频编码格式,常见的使用AAC编码后的文件存储格式为m4a,如果在iphone或者m4a。FFMpeg可以组成AAC的三种编码器具体如下。

    aac: FFmpeg本身的AAC编码实现

    libaac: 第三方的AAC编码器

    libfdk_aac: 第三方的AAC编码器

      后面两种编码器为非GPL协议,所以所有起来需要注意,在预编译时需要注意采用nonfree的支持,下面就来详细介绍三种编码器的使用方法。

    FFMpeg中的AAC编码器使用

      FFmpeg中的AAC编码器在早期为实验版本,而从2015年12月5日起,FFMPEG中的AAC编码器已经可以正式开始使用,所以在使用AAC编码器之前,首先要确定自己的FFMpeg是什么时候发布的版本,如果是2015年12月5日前发布的版本,那么在编码时需要使用-strict experimental 或-strict-2参数来声明AAC为实验版本,下面列举几个使用FFmpeg中的AAC编码器的例子:

    ffmpeg -i input.mp4 -c:a aac -b:a 160k output.aac

      根据这条命令可以看到,编码为AAC音频,码率为160kbit/s,编码生成的输出文件为output.aac文件:

    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
      Metadata:
        major_brand     : isom
        minor_version   : 1
        compatible_brands: isomavc1
        creation_time   : 2013-05-03T22:51:07.000000Z
      Duration: 00:00:46.61, start: 0.000000, bitrate: 3949 kb/s
      Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 960x400 [SAR 1:1 DAR 12:5], 3859 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
        Metadata:
          creation_time   : 2013-05-03T22:50:47.000000Z
          handler_name    : GPAC ISO Video Handler
          vendor_id       : [0][0][0][0]
      Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 92 kb/s (default)
        Metadata:
          creation_time   : 2013-05-03T22:51:07.000000Z
          handler_name    : GPAC ISO Audio Handler
          vendor_id       : [0][0][0][0]
    Stream mapping:
      Stream #0:1 -> #0:0 (aac (native) -> aac (native))
    Press [q] to stop, [?] for help
    Output #0, adts, to 'output.aac':
      Metadata:
        major_brand     : isom
        minor_version   : 1
        compatible_brands: isomavc1
        encoder         : Lavf58.76.100
      Stream #0:0(und): Audio: aac (LC), 48000 Hz, stereo, fltp, 160 kb/s (default)
        Metadata:
          creation_time   : 2013-05-03T22:51:07.000000Z
          handler_name    : GPAC ISO Audio Handler
          vendor_id       : [0][0][0][0]
          encoder         : Lavc58.134.100 aac
    size=     891kB time=00:00:46.59 bitrate= 156.7kbits/s speed=  74x    
    

       接下来在列举一个例子:

    ffmpeg -i input.wav -c:a aac -q:a 2 output.m4a

        从这条命令可以看出,在编码AAC时,同样也用到qscale参数,这个q在这里设置的有效范围为0.1~2之间,其用于设置AAC音频的VBR质量,效果并不可控,可以设置几个参数来看一下效果:

    Input #0, wav, from 'input.wav':
       Duration: 00:04:13.10, bitrate: 1411 kb/s
        Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, stereo, s16 1411kb/s
    Input #1, mov,mp3,m4a,4gp,3g2,mj2,from 'output_0.1.m4a':
      Metadata:
        encoded_by      : Lavf57.66.102
      Duration: 00:04.13.12, start: 0.000000, bitrate: 23 kb/s
        Stream #1:0(und): Audio: aac(LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp , 24kb/s (default)
    Input #2, mov,mp3,m4a,4gp,3g2,mj2,from 'output_0.1.m4a':
      Metadata:
        encoded_by      : Lavf57.66.102
      Duration: 00:04.13.12, start: 0.000000, bitrate: 23 kb/s
        Stream #2:0(und): Audio: aac(LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp , 186kb/s (default)

        从以上代码可以看到一共有input文件,具体如下。

    input#0为原始文件,码率为1411kbit/s

    input#1为设置q:a为0.1的文件,码率为24kbit/s

    input#2为设置q:a为2.0的文件,码率为186kbit/s

      可以使用-q:a设置AAC的输出质量,关于AAC的输出控制很简单,这里将介绍这么多。

    FDK AAC第三方的AAC编解码Codec库

       FDK-AAC库是FFMpeg支持的第三方编码库中质量最高的AAC编码库,关于编码音质的好坏与使用方式同样有着一定的关系,下面就来介绍一下libfdk_aac的几种编码模式。

    1.恒定码率(CBR)模式

      如果使用libfdk_aac设定一个恒定的码率,改变编码后的大小,并且可以兼容HE-AAC Profile,则可以根据音频设置的经验设置码率,例如如果一个声道使用64kbit/s,那么双声道为128kbit/s,环绕立体声为384kbit/s,这种通常为5.1环绕声。可以通过b:a参数进行设置。下面就来举几个例子:

    ffmpeg -i iuput.wav -c:a libfdk_aac -b:a 128k output.m4a

      根据这条命令可以看出,FFmpeg使用libfdk_aac将input.wav转为恒定码率为128kbit/s 编码为AAC的output.m4a音频文件。

    ffmpeg -i input.m4a -c:v copy -c:a libfdk_aac -b:a 384k output.mp4

        根据这条命令可以看出,FFmpeg将input.mp4的视频文件安装原有的编码方式进行输出封装,将音频以libfdk_aac进行编码,音频通道为环绕立体声,码率为384kbit/s,封装格式为output.mp。

        以上两个例子均为使用libfdk_aac进行AAC编码的案例,使用libfdk_aac可以编码AAC的恒定码率(CBR)。

    2.动态码率(VBR)模式

       使用VBR可以有更好的音频质量,使用libfdk_aac进行VBR模式的编码的AAC编码时,可以设置5个级别。

       根据下面表的内容,第一列为VBR的类型,第二列为每通道编码后的码率,第三列中有三种AAC编码信息,具体如下。

      AAC编码基本参数

     LC:Low Complexity AAC ,这种编码相对来说体积比较大,质量稍差

    HE:High-Efficiency AAC,这种编码相对来说体积小,质量好

    HEv2: High-Efficiency AAC version2 ,这种编码相对来说体积小,质量优

      下面的表将列出LC,HE,Hev2的推荐参数。

       AAC编码LC,HE,HEv2推荐参数

      下面举个例子,将音频压缩为AAC编码的m4a容器:

    ffmpeg -i input.wav -c:a libfdk_aac -vbr 3 output.m4a

     执行完上述命令之后,FFmpeg会将input.wav的音频转为编码为libfdk_aac的output.m4a音频文件。

    3.高质量AAC设置

      根据前面的介绍,AAC音频分为三种LC,HE-AAC,HEv2-AAC,前为已经介绍过LC的编码设置,下面列举介绍HE-AAC与HEv2-AAC的设置。

      1.HE-AAC音频编码设置

    ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k output.m4a

      执行完上述命令行之后,编码后输出output.m4a的信息如下:

    Output #0, ipod, to 'out.m4a':
      Metadata:
        encoded_by      : Logic Pro X
        date            : 2016-04-12
        coding_history  : 
        time_reference  : 158760000
        umid            : 0x00000000D0615BBB94340089A067295CFF7F0000F060130000000000000000000000000080C89C0401000000D0615BBB94340089905F295CFF7F0000EDAE8B8B
        encoder         : Lavf58.76.100
      Chapters:
        Chapter #0:0: start 0.000000, end 187.086621
          Metadata:
            title           : Tempo: 127.0
      Stream #0:0: Audio: aac (HE-AAC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp (24 bit), 64 kb/s
    

    从以上代码可以看出,音频编码为HE-AAC,可见编码参数已通过-profile:a aac_he设置生效。

    2. HEv2-AAC音频编码设置

      执行如下命令:

    ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k output.m4a

      编码后输出output.m4a信息如下:

    Output #0, ipod, to 'output.m4a':
      Metadata:
        encoded_by      : Logic Pro X
        date            : 2016-04-12
        coding_history  : 
        time_reference  : 158760000
        umid            : 0x00000000D0615BBB94340089A067295CFF7F0000F060130000000000000000000000000080C89C0401000000D0615BBB94340089905F295CFF7F0000EDAE8B8B
        encoder         : Lavf58.76.100
      Chapters:
        Chapter #0:0: start 0.000000, end 187.086621
          Metadata:
            title           : Tempo: 127.0
      Stream #0:0: Audio: aac (HE-AACv2) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp (24 bit), 32 kb/s
        Metadata:
          encoder         : Lavc58.134.100 aac
    size=     771kB time=00:03:07.08 bitrate=  33.8kbits/s speed= 151x    
    video:0kB audio:739kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 4.453330%
    

    4. AAC音频质量对比

       AAC_LC的音频编码可以采用libfaac,libfdk_aac,FFmpeg内置AAC三种,其质量顺序排列如下。

    libfdk_aac音频编码质量最优

    FFmpeg内置AAC编码次于libfdk_aac但优与libfaac

    libfaac在ffmpeg内置AAC编码为实验品时是除了libfdk_aac之外的唯一选择

    注意:在新版的FFmpeg中,libffack已经被删除

    展开全文
    weixin_46309058 2021-08-10 16:34:12
  • 简介 Advanced Audio Coding(高级音频解码),是⼀种由MPEG-4标准定义的有损⾳频压缩格式,由Fraunhofer发展,Dolby, Sony和AT&T是主要的贡献者。 ADIF:Audio Data Interchange Format 音频数据交换格式。...是AAC

    简介

    Advanced Audio Coding(高级音频解码),是⼀种由MPEG-4标准定义的有损⾳频压缩格式,由Fraunhofer发展,Dolby, Sony和AT&T是主要的贡献者。

    ADIF:Audio Data Interchange Format 音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不能在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进⾏。故这种格式常⽤在磁盘⽂件中。

    ADTS:Audio Data Transport Stream。是AAC音频的传输流格式,AAC音频格式在MPEG-2(ISO-13318-7 2003)中有定义。AAC后来又被采用到MPEG-4标准中。这种格式的特征是它是⼀个有同步字的比特流,解码可以在这个流中任何位置开始,它的特征类似于mp3数据流格式。

    ADTS可以在任意帧解码,也就是说它每⼀帧都有头信息。ADIF只有⼀个统⼀的头,所以必须得到所有的数据后解码。

    目前⼀般编码后的和抽取出的都是ADTS格式的⾳频流。

    格式

    AAC的ADIF格式:
    在这里插入图片描述
    AAC的ADTS格式:

    在这里插入图片描述

    空白处表示前后帧

    有的时候当你编码AAC裸流的时候,会遇到写出来的AAC⽂件并不能在PC和⼿机上播放,很⼤的可能就是AAC⽂件的每⼀帧⾥缺少了ADTS头信息⽂件的包装拼接。只需要加⼊头⽂件ADTS即可。⼀个AAC原始数据块⻓度是可变的,对原始帧加上ADTS头进⾏ADTS的封装,就形成了ADTS帧。

    AAC⾳频⽂件的每⼀帧由ADTS HeaderAAC Audio Data组成。

    结构体如下:

    在这里插入图片描述

    每⼀帧的ADTS的头⽂件都包含了⾳频的采样率,声道,帧⻓度等信息,这样解码器才能解析读取。

    ⼀般情况下ADTS的头信息都是7个字节,分为2部分:

    • adts_fixed_header();

    • adts_variable_header();

    其⼀为固定头信息,紧接着是可变头信息。固定头信息中的数据每⼀帧都相同,而可变头信息则在帧与帧之间可变。

    adts_fixed_header

    在这里插入图片描述

    syncword:同步头 总是0xFFF, all bits must be 1,代表着⼀个ADTS帧的开始。

    ID:MPEG标识符,0标识MPEG-4,1标识MPEG-2。

    Layer:always: ‘00’

    protection_absent:表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if there is CRC。

    profile:表示使⽤哪个级别的AAC,如01 Low Complexity(LC)— AAC LC,有些芯⽚只⽀持AAC LC 。

    在MPEG-2 AAC中定义了3种:
    在这里插入图片描述

    在MPEG-4 AAC中定义了多种:

    在这里插入图片描述

    profile的值等于MPEG-4 Audio Object Type - 1

    #define FF_PROFILE_AAC_MAIN 0
    #define FF_PROFILE_AAC_LOW  1
    #define FF_PROFILE_AAC_SSR  2
    #define FF_PROFILE_AAC_LTP  3
    #define FF_PROFILE_AAC_HE   4
    #define FF_PROFILE_AAC_HE_V2 28
    #define FF_PROFILE_AAC_LD   22
    #define FF_PROFILE_AAC_ELD  38
    #define FF_PROFILE_MPEG2_AAC_LOW 128
    #define FF_PROFILE_MPEG2_AAC_HE  131
    

    sampling_frequency_index:表示使⽤的采样率下标,通过这个下标在Sampling Frequencies[ ]数组中查找得知采样率的值。

    在这里插入图片描述

    channel_configuration: 表示声道数,⽐如2表示⽴体声双声道

    在这里插入图片描述

    0: Defined in AOT Specifc Config
    1: 1 channel: front-center
    2: 2 channels: front-left, front-right
    3: 3 channels: front-center, front-left, front-right
    4: 4 channels: front-center, front-left, front-right, back-center
    5: 5 channels: front-center, front-left, front-right, back-left, back-
    right
    6: 6 channels: front-center, front-left, front-right, back-left, back-
    right, LFE-channel
    7: 8 channels: front-center, front-left, front-right, side-left, side-right,
    back-left, back-right, LFE-channel
    8-15: Reserved

    adts_variable_header

    在这里插入图片描述

    copyright_identification_bit:固定为0

    copyright_identification_bit:固定为0

    frame_length : ⼀个ADTS帧的⻓度包括ADTS头和AAC原始流

    frame length, this value must include 7 or 9 bytes of header length: aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame)

    protection_absent=0时, header length=9bytes。

    protection_absent=1时, header length=7bytes。

    adts_buffer_fullness:0x7FF 说明是码率可变的码流。

    number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有⼀个AAC数据块。

    帧长度计算方法:

     unsigned int getFrameLength(unsigned char* str)
    {
    	if ( !str ){
    		return 0;
    	}
    	unsigned int len = 0;
    	int f_bit = str[3];
    	int m_bit = str[4];
    	int b_bit = str[5];
         
    	len += (b_bit>>5);
    	len += (m_bit<<3);
    	len += ((f_bit&3)<<11);
    	
         return len;
    }
    

    示例

    在这里插入图片描述

    1111 1111 1111 0001 0100 1100 1000 0000 0110 1010 1011 1111 1111 1100

    111111111111 0 00 1 01 0011 0 010 0 0

    0 0 00011 0101 0101 11111111111 00

    syncword:111111111111 表示ADTS帧的开始

    id:0 mpeg标识,0表示MPEG-4

    layer:00 总是00

    protection_absent:1 表示不使用crc

    profile:01 aac的级别 LC

    sampling_frequency_index:0011 采样率的下标 3表示采样率为48000

    channel_configuration:010 声道数 2表示双声道

    frame_length:00011 0101 0101 帧长度 355

    adts_buffer_fullness:11111111111 0x7ff表示码率是可变码流

    number_of_raw_data_blocks_in_frame:00 0表示ADTS帧中有一个AAC数据块

    在这里插入图片描述

    展开全文
    qiuguolu1108 2021-10-02 21:57:41
  • 实时AAC音频/本地AAC音视频硬解码详细介绍附带Demo一、使用AAC音频硬解码的背景开发成本维护成本二、使用AAC音频硬解码的优缺点优点缺点三、AAC音频硬解码的API介绍MediaCodec 方法介绍MediaCodec 参数介绍插入链接...

    一、使用AAC音频硬解码的背景

    因为各种原因,在日常的开发中开发者或多或少都要接触一些音视频编解码相关的功能,所以有时候选择编解码工具就变得尤为重要,取决于你的项目属性又或者知识广度等等,下面作者结合自己的实际项目经验给大家分析一下

    开发成本

    开发成本在企业管理者的角度来说尤为重要,关系到企业的盈利与生存。所以为了降低成本很多开发者会考虑去使用Android 原生提供的一些API,而不是去使用第三方的一些开源库或者收费库,因为那样急需要花费额外的金钱并且还需要花费时间与精力去熟悉,所以也不推荐,除非时间和成本都在允许的范围内

    维护成本

    当项目迭代至成熟期时,维护成本就成了后续开发者要关注的事情,首先假设我们使用了第三方的库,如果你的产品已经卖出去了,而这时候第三方库不维护并且出现了一个致命的问题,那这样就会导致卖出去的产品都会被投诉并且短时间内还要花时间去移除之前使用的第三方库,如果耦合性过多,将导致无法挽回的经济损失。而如果使用的是Android 原生的API的话,因为本身是做产品的,所以只考虑当前设备,无须关心移植到其他平台或其他系统版本,前期做稳定,后期就不会有任何问题

    二、使用AAC音频硬解码的优缺点

    优点

    开发方便快捷,有成熟的API调用,使用简单,网上也有大部分的参考资料

    缺点

    可移植性差,如果公司其他项目需要移植到新的硬件平台时,会有兼容性问题,大部分需要向原厂提工单才可解决

    三、AAC音频硬解码的API介绍

    MediaCodec 方法介绍

    MediaCodec是Android原生提供的API,支持音视频的硬编码和硬解码,Android常用的源文件格式与编码后格式是音频的PCM编码成AAC,视频的NV21/YV12编码成H264,值得一提的是在选择和设置视频编码质量的时候,MediaFormat.KEY_PROFILE 在官方API介绍中,其可以控制视频的质量,实际则是Android7.0以下默认baseline,不管怎么设置都是默认baseline,所以这个变量属性,作者采用了删除线,在视频编码时,不推荐大家使用,避免出现问题

    getInputBuffers()

    从当前编解码器中获取输入缓冲区数组,用于向输入缓冲区中添加要编解码的数据

    getOutputBuffers()

    从当前编解码器中获取输出缓冲区数组,用于提取编解码之后的数据缓冲区

    dequeueInputBuffer(long timeoutUs)

    获取输入缓冲区数组中待使用(空闲)的缓冲区数组下标索引,timeoutUs为0时立即返回,小于0时表示一直等待直至输入缓冲区数组中有可用的缓冲区为止,大于0则表示等待时间为timeoutUs

    getInputBuffer(int index)

    获取输入缓冲区数组中待使用(空闲)的缓冲区,index参数为dequeueInputBuffer(long timeoutUs)的返回值,返回值大于等于0即表示有可用的输入缓冲区

    queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)

    向输入缓冲区数组中添加要编解码的数据,index参数为dequeueInputBuffer(long timeoutUs)的返回值,offset为要编解码数据的起始偏移,size为要编解码数据的长度,presentationTimeUs为PTS,flags为标记,正常使用时可默认填0,编解码至结尾时可填MediaCodec.BUFFER_FLAG_END_OF_STREAM值

    dequeueOutputBuffer(BufferInfo info, long timeoutUs)

    从输出缓冲区数组中获取编解码成功的缓冲区下标索引,info参数表示传入一个BufferInfo Java bean class , 编解码器会把处理完后的数据信息等以bean类型返回给开发者,timeoutUs意义跟之前介绍的dequeueInputBuffer(long timeoutUs)方法大致相同,返回值大于等于0即表示有可用的输出缓冲区

    getOutputBuffer(int index)

    获取输出缓冲区数组中编解码完成的缓冲区,index参数为dequeueOutputBuffer(BufferInfo info, long timeoutUs)方法的返回值,返回值大于等于0即表示有可用的输出缓冲区

    releaseOutputBuffer(int index, boolean render)

    释放编解码器输出缓冲区数组中的缓冲区,index为要释放的缓冲区数组下标索引,它为dequeueOutputBuffer(BufferInfo info, long timeoutUs)方法的返回值,render参数为渲染控制,如果在编解码时设置了可用的surface,render为true时则表示将此数据缓冲区输出到surface渲染

    stop()

    关闭编解码

    release()

    释放编解码资源

    MediaCodec 参数介绍

    本篇文章关于MediaCodec 参数的介绍只描述日常开发中出现频率最频繁的,其他一些参数很少使用或者使用之后没效果,这里就不再做过多阐述

    MediaFormat.KEY_AAC_PROFILE

    要使用的AAC配置文件的键(仅AAC音频格式时使用),常量在android.media.MediaCodecInfo.CodecProfileLevel 中声明,音频编码中最常用的变量是MediaCodecInfo.CodecProfileLevel.AACObjectLC

    MediaFormat.KEY_CHANNEL_MASK

    音频内容的通道组成的键,在音频编码中需要根据硬件支持去有选择性的选择支持范围内的通道号

    MediaFormat.KEY_BIT_RATE

    音视频平均比特率,以位/秒为单位(bit/s)的键

    MediaFormat.KEY_CHANNEL_COUNT

    音频通道数的键

    MediaFormat.KEY_COLOR_FORMAT

    输入视频源的颜色格式,日常开发中可根据查询设备颜色格式支持进行选择

    MediaFormat.KEY_FRAME_RATE

    视频帧速率的键,以帧/秒(frame/s)为单位

    MediaFormat.KEY_I_FRAME_INTERVAL

    关键帧间隔的键

    MediaFormat.KEY_MAX_INPUT_SIZE

    编解码器中数据缓冲区最大大小的键,以字节(byte)为单位

    四、AAC音频硬解码

    本地音视频文件里的AAC音频硬解码介绍,MediaExtractor方法详解

    解析本地音视频文件里的AAC音频,需要我们借助一些MediaCodec之外的API即MediaExtractor,如果不熟悉或之前没使用过,没关系!作者会在本篇文章中做一个详细的概述,帮助你加深印象

    setDataSource(String path)

    设置音视频文件的绝对路径或音视频文件的http地址,path参数可以是本地音视频文件的绝对路径或网络上的音视频文件http地址

    getTrackCount()

    获取音视频数据中的轨道数,正常情况下的音视频有audio/xxx及video/xxx

    getTrackFormat(int index)

    获取音视频数据中音频或视频的 android.media.MediaFormat,这个很重要后面还会有代码示例来介绍,index参数为音频或视频数据轨道的索引,返回值是 android.media.MediaFormat

    selectTrack(int index)

    选择要extract的数据轨道,index参数为指定的音频或视频轨道的索引,后面也是会通过代码示例详细介绍

    readSampleData(ByteBuffer byteBuf, int offset)

    读取音频或视频轨道中的数据到给定的 ByteBuffer 缓冲区中,byteBuf参数为要保存数据的目标缓冲区,offset参数为音频或视频的数据起始偏移量,返回值为int类型,大于0表示还有数据未处理完,否则表示数据已经全部处理完成

    getSampleTime()

    获取该帧音频或视频的的时间戳即PTS,返回值为long类型,以微秒(us)为单位,如无可用返回-1

    advance()

    此方法表示开始处理下一帧音频或视频,如果还有数据返回true,已无数据则返回false

    release()

    释放资源,在 advance() 返回 false或中断read操作后使用,表示数据处理完毕或不再读取数据

    实时AAC音频硬解码介绍

    实时AAC音频硬解码其实跟本地音视频AAC音频硬解码大同小异,唯一差异就是实时的不需要去使用MediaExtractor进行音频轨与视频轨进行分离,可以直接使用MediaCodec进行音频硬解码,但需要解析实时流里的ADTS音频头,否则MediaCodec解码器是无法识别出该数据源是否是AAC音频。正常情况下需要开发者解析ADTS头中的一些关键信息,如采样率索引(可根据采样率进行换算)、通道数。

    下面作者就给大家介绍关于ADTS头的解析及ADTS其他位的意义:

    ADTS头的解析及ADTS其他位的意义

    ADTS头结构:
    AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)

    序号字段位数含义
    Asyncword12AAC音频头固定起始标记
    BMPEG Version10是MPEG-4,1是MPEG-2
    CLayer2always 0
    Dprotection absent1没有CRC设置为1,如果有CRC则设置为0
    Eprofile2the MPEG-4 Audio Object Type minus 1
    FSampling Frequency Index4MPEG-4 Sampling Frequency Index(15 is forbidden)
    Gprivate bit1编码时设置为0,解码时忽略
    HChannel Configuration3MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE)
    Ioriginality1编码时设置为0,解码时忽略
    Jhome1编码时设置为0,解码时忽略
    Kcopyrighted id bit1编码时设置为0,解码时忽略
    Lcopyright id start1编码时设置为0,解码时忽略
    Mframe length13此值必须包含7或9个字节的标头长度:FrameLength = header(protection absent == 1?7:9)+ size(AAC Frame Length)
    OBuffer fullness11No Description
    Prdbs2ADTS帧中的AAC帧数(RDBs)减去1,为获得最大兼容性,请始终为每个ADTS帧使用1个AAC帧
    Qcrc16如果 protection absent 字段为0,表示携带CRC 2字节的数据

    在实时AAC音频硬解码时,我们只需要解析采样率索引(可根据采样率进行换算)、通道数即可,音频采样率索引见MPEG-4 Sampling Frequency Index,接下来还会向各位介绍更重要的音视频参数

    五、音视频编解码的CSD参数

    音频编解码的CSD参数介绍

    在Android中如果调用麦克风进行录音,结合视频使用MediaMuxer进行音视频合成时,是需要开发者传入CSD参数的,否则Android在播放或展示时会出现不识别等其他问题,所以需要开发者在编解码时需要调用MediaFormat设置CSD参数

    在音频编解码中,CSD参数只需要设置一个,那就是csd-0即ADTS音频头,在解析本地音视频中的AAC音频时,开发者可以调用MediaFormat取到这个csd-0参数对应的ADTS音频头,然后进行后续的其他操作,后续代码示例还会再次介绍。如果解析的是实时AAC音频,那就需要参照第四步骤对ADTS头进行解析,然后计算CSD参数并设置到MediaFormat中,然后配置到MediaCodec中进行解码,具体算法将在后面的代码示例中提到

    视频编解码的CSD参数介绍

    在Android中如果调用摄像头进行录像,结合音频使用MediaMuxer进行音视频合成时,是需要开发者传入CSD参数的,否则Android在播放或展示时会出现不识别等其他问题,所以需要开发者在编解码时需要调用MediaFormat设置CSD参数

    在视频编解码中,CSD参数需要设置2个,那就是csd-0csd-1sps视频头和pps视频头,在解码本地h264编码视频时可以调用MediaFormat获取sps视频头和pps视频头,减少sps/pps视频头运算和查找的操作,简单快捷且高效!具体使用会在代码示例中再次提及

    六、代码示例

    本地音视频文件中的AAC音频硬解码

        /**
         * set decode file path
         *
         * @param decodeFilePath decode file path
         */
        public void setDecodeFilePath(String decodeFilePath) {
            if (TextUtils.isEmpty(decodeFilePath)) {
                throw new RuntimeException("decode file path must not be null!");
            }
            mediaExtractor = getMediaExtractor(decodeFilePath);
        }
    

    上述代码片段为设置一个需要解码的文件的绝对路径,路径为null时抛出一个运行时异常,提示路径不能为null,然后就是获取MediaExtractor对象,为提取音频做准备

        /**
         * get media extractor
         *
         * @param videoPath need extract of tht video file absolute path
         * @return {@link MediaExtractor} media extractor instance object
         * @throws IOException
         */
        protected MediaExtractor getMediaExtractor(String videoPath) {
            MediaExtractor mMediaExtractor = new MediaExtractor();
            try {
                // set file path
                mMediaExtractor.setDataSource(videoPath);
                // get source file track count
                int trackCount = mMediaExtractor.getTrackCount();
                for (int i = 0; i < trackCount; i++) {
                    // get current media track media format
                    MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(i);
                    // if media format object not be null
                    if (mediaFormat != null) {
                        // get media mime type
                        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
                        // media mime type match audio
                        if (mimeType.startsWith(AUDIO_MIME_TYE)) {
                            // set media track is audio
                            mMediaExtractor.selectTrack(i);
                            // you can using media format object call getByteBuffer method and input key "csd-0" get it value , if you want.
                            // it is aac adts audio header.
                            adtsAudioHeader = mediaFormat.getByteBuffer(CSD_MIME_TYPE_0).array();
                            // get audio sample
                            sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                            // get audio channel count
                            channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                            return mMediaExtractor;
                        }
                        // >>>>>>>>>>> expand start >>>>>>>>>>>
                        // media mime type match video
                        // else if (mimeType.startsWith(VIDEO_MIME_TYE)) {
                        // get video sps header
                        // byte[] spsVideoHeader = mediaFormat.getByteBuffer(CSD_MIME_TYPE_0).array();
                        // get video pps header
                        // byte[] ppsVideoHeader = mediaFormat.getByteBuffer(CSD_MIME_TYPE_1).array();
                        // }
                        // <<<<<<<<<<< expand end <<<<<<<<<<<
                    }
                }
            } catch (IOException e) {
                Log.d(TAG, "happened io exception : " + e.toString());
                if (mMediaExtractor != null) {
                    mMediaExtractor.release();
                }
            }
            return null;
        }
    

    上述代码片段为获取MediaExtractor对象,在设置文件路径后调用其getTrackCount()方法获取文件的所有轨道数,再使用for循环去逐一匹配我们需要的媒体源轨道,调用其getTrackFormat(int index)方法获取该轨道的MediaFormat,最后再去匹配该轨道MediaFormat的mime type,如果匹配到其mime type以关注的mime type字符开始时,获取其csd-0参数的值(音频中对应ADTS头)、采样率、通道数并调用selectTrack(int index)方法将该轨道设置为选定的轨道。

    视频相关的参数获取也在代码片段中的expand范围内给出,大家可以了解一下,作者也将其添加上来了,只不过是在代码中注释了,为的就是给大家拓展一下这方面的知识

        @Override
        public void start() {
            if (mediaExtractor == null) {
                Log.e(TAG, "media extractor is null , so return!");
                return;
            }
            if (adtsAudioHeader == null || adtsAudioHeader.length == 0) {
                Log.e(TAG, "aac audio adts header is null , so return!");
                return;
            }
            aacDecoder = createDefaultDecoder();
            if (aacDecoder == null) {
                Log.e(TAG, "aac audio decoder is null , so return!");
                return;
            }
            if (worker == null) {
                isDecoding = true;
                worker = new Thread(this, TAG);
                worker.start();
            }
        }
    

    上述代码片段为准备开始提取AAC音频并进行MediaCodec硬解码,首先判断前面代码片段中MediaExtractor对象是否为空,完事在判断获取轨道时的ADTS头是否正常取到,最后生成一个AAC音频解码器,如果生成无异常,开启一个工作线程进行音频的提取和解码

        /**
         * create default aac decoder
         *
         * @return {@link MediaCodec} aac audio decoder
         */
        private MediaCodec createDefaultDecoder() {
            try {
                MediaFormat mediaFormat = new MediaFormat();
                mediaFormat.setString(MediaFormat.KEY_MIME, AUDIO_DECODE_MIME_TYPE);
                mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
                mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelCount);
                ByteBuffer byteBuffer = ByteBuffer.allocate(adtsAudioHeader.length);
                byteBuffer.put(adtsAudioHeader);
                byteBuffer.flip();
                mediaFormat.setByteBuffer(CSD_MIME_TYPE_0, byteBuffer);
                MediaCodec aacDecoder = MediaCodec.createDecoderByType(AUDIO_DECODE_MIME_TYPE);
                aacDecoder.configure(mediaFormat, null, null, 0);
                aacDecoder.start();
                return aacDecoder;
            } catch (IOException e) {
                Log.e(TAG, "create aac audio decoder happened io exception : " + e.toString());
            }
            return null;
        }
    

    上述代码片段为创建音频解码器,sampleRatechannelCountadtsAudioHeader都是前面代码片段中通过MediaExtractor从文件的媒体轨道中的MediaFormat获取的

        /**
         * aac audio format decode to pcm audi format
         */
        private void aacDecodeToPcm() {
            isLowVersion = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP;
            ByteBuffer[] aacDecodeInputBuffers = null;
            ByteBuffer[] aacDecodeOutputBuffers = null;
            if (isLowVersion) {
                aacDecodeInputBuffers = aacDecoder.getInputBuffers();
                aacDecodeOutputBuffers = aacDecoder.getOutputBuffers();
            }
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    
            // initialization audio track , use for play pcm audio data
            // audio output channel param channelConfig according device support select
            int buffsize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
            AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
                    AudioFormat.ENCODING_PCM_16BIT, buffsize, AudioTrack.MODE_STREAM);
            audioTrack.play();
    
            Log.d(TAG, "aac audio decode thread start");
            while (isDecoding) {
                // This method will return immediately if timeoutUs == 0
                // wait indefinitely for the availability of an input buffer if timeoutUs < 0
                // wait up to "timeoutUs" microseconds if timeoutUs > 0.
                int aacDecodeInputBuffersIndex = aacDecoder.dequeueInputBuffer(2000);
                // no such buffer is currently available , if aacDecodeInputBuffersIndex is -1
                if (aacDecodeInputBuffersIndex >= 0) {
                    ByteBuffer sampleDataBuffer;
                    if (isLowVersion) {
                        sampleDataBuffer = aacDecodeInputBuffers[aacDecodeInputBuffersIndex];
                    } else {
                        sampleDataBuffer = aacDecoder.getInputBuffer(aacDecodeInputBuffersIndex);
                    }
                    int sampleDataSize = mediaExtractor.readSampleData(sampleDataBuffer, 0);
                    if (sampleDataSize < 0) {
                        aacDecoder.queueInputBuffer(aacDecodeInputBuffersIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    } else {
                        try {
                            long presentationTimeUs = mediaExtractor.getSampleTime();
                            aacDecoder.queueInputBuffer(aacDecodeInputBuffersIndex, 0, sampleDataSize, presentationTimeUs, 0);
                            mediaExtractor.advance();
                        } catch (Exception e) {
                            Log.e(TAG, "aac decode to pcm happened Exception : " + e.toString());
                            continue;
                        }
                    }
    
                    int aacDecodeOutputBuffersIndex = aacDecoder.dequeueOutputBuffer(info, 2000);
                    if (aacDecodeOutputBuffersIndex >= 0) {
                        if (((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)) {
                            Log.d(TAG, "aac decode thread read sample data done!");
                            break;
                        } else {
                            ByteBuffer pcmOutputBuffer;
                            if (isLowVersion) {
                                pcmOutputBuffer = aacDecodeOutputBuffers[aacDecodeOutputBuffersIndex];
                            } else {
                                pcmOutputBuffer = aacDecoder.getOutputBuffer(aacDecodeOutputBuffersIndex);
                            }
                            ByteBuffer copyBuffer = ByteBuffer.allocate(pcmOutputBuffer.remaining());
                            copyBuffer.put(pcmOutputBuffer);
                            copyBuffer.flip();
    
                            final byte[] pcm = new byte[info.size];
                            copyBuffer.get(pcm);
                            copyBuffer.clear();
                            audioTrack.write(pcm, 0, info.size);
                            aacDecoder.releaseOutputBuffer(aacDecodeOutputBuffersIndex, false);
                        }
                    }
                }
            }
            Log.d(TAG, "aac audio decode thread stop");
    
            aacDecoder.stop();
            aacDecoder.release();
            aacDecoder = null;
            mediaExtractor.release();
            mediaExtractor = null;
            isDecoding = false;
            worker = null;
        }
    

    上述代码片段稍长,作者就做一个简单的概括吧,先获取MediaCodec的输入输出缓冲区数组,然后读取文件的音频轨道数据填充到可用的输入缓冲区中,在进行音频的硬解码,最后从解码成功后存放的输出缓冲区数组中拿到解码后的PCM数据,通过AudioTrack播放出来,这个播放动作是为了验证解码出来的数据是否有异常

    实时AAC音频文件的硬解码

    实时的解码先不上代码,而是先帮助大家理解,我们需要怎么去解析AAC音频的ADTS头?要取哪些对我们有用的字节?别急,作者会细细的说

    FF F1 6C 40 18 02 3C

    上述数据是作者从实际项目开发中提取出来的AAC实时流的ADTS音频头,想通过这样的方式来解答之前提到的两个问题,这段字符表示7个16进制的字节,将其进行补全则为如下数据:

    0xFF 0xF1 0x6C 0x40 0x18 0x02 0x3C

    根据最前面提到的ADTS头结构可知,我们只需要关注7个字节中的前面个4字节,也就是0~3字节即可并取出其对应位的值用于生成解码器,所以我们只需要关心如下数据:

    0xFF 0xF1 0x6C 0x40

    然后接下来一一解析给大家看,首先是0xFF 0xF1

            // 解析 0xFF 0xF1
            // 将第0字节0xFF和第1字节0xF1通过位运算,将其转换成int类型,即65521
            // 再将65521 转换成二进制类型,即 1111111111110001
    
            // syncword : 111111111111 (即固定0xfff) 12位
            // MPEG Version: 0 (表 MPEG-4) 1位
            // Layer: 00 (固定 0) 2位
            // protection absent : 1 (表无CRC数据) 1位
    

    接下来再解析0x6C 0x40,计算到前面低10位就行了,后续位用不上

            // 解析 0x6C 0x40
            // 将第2字节0x6C和第3字节0x40通过位运算,将其转换成int类型,即27712
            // 再将27712 转换成二进制类型,即 110110001000000,因不足16位,所以在高位补0,满足16位,补足后 0110110001000000
    
            // profile : 01 (aac profile) 2位
            // Sampling Frequency Index : 1011 (值为11,即采样率8000) 4位
            // private bit :0 (编码时设为0 ,解码可忽略) 1位
            // Channel Configuration : 001 (通道参数) 3位
            // ......
    

    结合作者刚刚举例的案例,也可以自己写出ADTS音频头解析,下面作者开始贴实时AAC音频硬解码的代码片段

        @Override
        public void start() {
            if (worker == null) {
                isDecoding = true;
                waitTimeSum = 0;
                worker = new Thread(this, TAG);
                worker.start();
            }
        }
    

    上述代码片段为启动工作线程,开始进行MediaCodec硬解码操作

        @Override
        public void run() {
            final long timeOut = 5 * 1000;
            final long waitTime = 500;
            while (isDecoding) {
                while (!aacFrameQueue.isEmpty()) {
                    byte[] aac = aacFrameQueue.poll();
                    if (aac != null) {
                        if (!hasAacDecoder(aac)) {
                            Log.d(TAG, "aac decoder create failure , so break!");
                            break;
                        }
                        // todo decode aac audio data.
                        // remove aac audio adts header
                        byte[] aacTemp = new byte[aac.length - 7];
                        // data copy
                        System.arraycopy(aac, 7, aacTemp, 0, aacTemp.length);
                        // decode aac audio
                        decode(aacTemp, aacTemp.length);
                    }
                }
                // Waiting for next frame
                synchronized (decodeLock) {
                    try {
                        // isEmpty() may take some time, so we set timeout to detect next frame
                        decodeLock.wait(waitTime);
                        waitTimeSum += waitTime;
                        if (waitTimeSum >= timeOut) {
                            Log.d(TAG, "realtime aac decode thread read timeout , so break!");
                            break;
                        }
                    } catch (InterruptedException ie) {
                        worker.interrupt();
                    }
                }
            }
            Log.d(TAG, "realtime aac decode thread stop!");
            if (aacDecoder != null) {
                aacDecoder.stop();
                aacDecoder.release();
                aacDecoder = null;
            }
            if (audioTrack != null) {
                audioTrack.stop();
                audioTrack.release();
                audioTrack = null;
            }
            aacFrameQueue.clear();
            adtsAudioHeader = null;
            isDecoding = false;
            worker = null;
        }
    

    上述代码片段为线程执行解码,判断AAC队列中是否有数据,如果有就取出一个数据,先判空然后再检测AAC的ADTS音频头是否符合规范,如不符合或创建解码器发生异常都将直接退出循环结束线程工作,如果队列中没有数据则等待500ms,继续轮询队列里的数据,当线程工作结束,释放相关API的资源,任何时候都要对相关的一些创建操作进行回收且形成闭环,避免发生内存泄漏

        /**
         * put realtime aac audio data
         *
         * @param aac aac audio data
         */
        public void putAacData(byte[] aac) {
            if (isDecoding) {
                aacFrameQueue.add(aac);
                synchronized (decodeLock) {
                    waitTimeSum = 0;
                    decodeLock.notifyAll();
                }
            }
        }
    

    上述代码为添加AAC实时数据到缓存队列中,这个数据可以是来自TCP等的实时流媒体数据,如果当前解码工作线程正在解码,则添加一个AAC到缓存队列中,重置等待时间并且唤醒等待中的对象锁,让线程拿到锁后继续执行

        /**
         * @param aac aac audio data
         * @return true means has aad decoder
         */
        private boolean hasAacDecoder(byte[] aac) {
            if (aacDecoder != null) {
                return true;
            }
            return checkAacAdtsHeader(aac);
        }
    

    上述代码片段为校验AAC的ADTS音频头,如果accDecoder非空表示之前已经判断过,该AAC数据为正常AAC数据,这里不考虑极端情况,AAC音频混搭其他格式的音频,这样会导致播放出问题,正常情况交互下也不会这样干!如果accDecoder为空则先对ADTS进行一次校验

        /**
         * check aac adts audio header
         *
         * @param aac aac audio data
         */
        private boolean checkAacAdtsHeader(byte[] aac) {
            byte[] dtsFixedHeader = new byte[2];
            System.arraycopy(aac, 0, dtsFixedHeader, 0, dtsFixedHeader.length);
            int bitMoveValue = dtsFixedHeader.length * 8 - ADTS_HEADER_START_FLAG_BIT_SIZE;
            int adtsFixedHeaderValue = bytesToInt(dtsFixedHeader);
            int syncwordValue = ADTS_HEADER_START_FLAG << bitMoveValue;
            boolean isAdtsHeader = (adtsFixedHeaderValue & syncwordValue) >> bitMoveValue == ADTS_HEADER_START_FLAG;
            if (!isAdtsHeader) {
                Log.e(TAG, "adts header start flag not match , so return!");
                return false;
            }
            System.arraycopy(aac, 2, dtsFixedHeader, 0, dtsFixedHeader.length);
            return parseAdtsHeaderKeyData(dtsFixedHeader);
        }
    

    上述代码片段为取出AAC音频中的第0、1两个字节,因为short双字节转换成Int不会造成精度丢失,先进行数据拷贝,完后计算数据bit的左右移动值,然后将第0、1两个字节转换成int类型,经过位运算得到ADTS的syncword即AAC的ADTS固定标识,如匹配不上表示不是AAC数据直接return,否则接着处理第2、3两个字节然后将其拷贝到数组中,接着再进行ADTS音频头的关键数据的解析

        /**
         * parse adts header key byte array data
         *
         * @param adtsHeaderValue adts fixed header byte array
         */
        private boolean parseAdtsHeaderKeyData(byte[] adtsHeaderValue) {
            int adtsFixedHeaderValue = bytesToInt(adtsHeaderValue);
    
            // bitMoveValue = 16(2 * 8) - 2(aac profile 3bit)
            int bitMoveValue = adtsHeaderValue.length * 8 - ADTS_HEADER_PROFILE_BIT_SIZE;
            // profile : 01 (aac profile) 2 bit
            int audioProfile = adtsFixedHeaderValue & (ADTS_HEADER_PROFILE_FLAG << bitMoveValue);
            // 1: AAC Main -- MediaCodecInfo.CodecProfileLevel.AACObjectMain
            // 2: AAC LC (Low Complexity)  -- MediaCodecInfo.CodecProfileLevel.AACObjectLC
            // 3: AAC SSR (Scalable Sample Rate) -- MediaCodecInfo.CodecProfileLevel.AACObjectSSR
            audioProfile = audioProfile >> bitMoveValue;
    
            // bitMoveValue = 16(2 * 8) - 2(aac profile 3bit) - 4(Sampling Frequency Index 4 bit)
            bitMoveValue -= ADTS_HEADER_SAMPLE_INDEX_BIT_SIZE;
            // Sampling Frequency Index : 1011 (value is 11,sample rate 8000) 4 bit
            int sampleIndex = adtsFixedHeaderValue & (ADTS_HEADER_SAMPLE_INDEX_FLAG << bitMoveValue);
            sampleIndex = sampleIndex >> bitMoveValue;
            sampleRate = samplingFrequencys[sampleIndex];
    
            // private bit :0 (encoding set 0 ,decoding ignore) 1 bit
            // Channel Configuration : 001 (Channel Configuration) 3 bit
            // bitMoveValue = bitMoveValue - 1(private bit 1bit) +  3(Channel Configuration 3bit)
            bitMoveValue -= (1 + ADTS_HEADER_CHANNEL_CONFIG_BIT_SIZE);
            channelConfig = adtsFixedHeaderValue & (ADTS_HEADER_SAMPLE_INDEX_FLAG << bitMoveValue);
            channelConfig = channelConfig >> bitMoveValue;
            // ......
            // create csd-0(audio adts header)
            adtsAudioHeader = new byte[2];
            adtsAudioHeader[0] = (byte) ((audioProfile << 3) | (sampleIndex >> 1));
            adtsAudioHeader[1] = (byte) ((byte) ((sampleIndex << 7) & 0x80) | (channelConfig << 3));
    
            Log.d(TAG, "audioProfile = " + audioProfile + " , sampleIndex = " + sampleIndex + "(" + sampleRate + ")" + " , channelConfig = " + channelConfig
                    + " , audio csd-0 = " + Utils.bytesToHexStringNo0xChar(adtsAudioHeader));
    
            return createDefaultDecoder();
        }
    

    上述代码片段为先将前面取到的AAC的第2、3字节转换成int类型,接着计算数据位的位移值,然后分别计算audioProfilesampleIndexsampleRatechannelConfig,最后再根据这些参数中的部分参数进行音频csd-0配置头的计算,如果一切正常,最后会调用createDefaultDecoder方法进行解码器的创建

        /**
         * create default decoder
         */
        private boolean createDefaultDecoder() {
            if (adtsAudioHeader == null || adtsAudioHeader.length == 0) {
                Log.e(TAG, "realtime aac decoder create failure , adts audio header is null , so return false!");
                return false;
            }
            try {
                aacDecoder = MediaCodec.createDecoderByType(AUDIO_DECODE_MIME_TYPE);
                MediaFormat mediaFormat = new MediaFormat();
                mediaFormat.setString(MediaFormat.KEY_MIME, AUDIO_DECODE_MIME_TYPE);
                mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
                mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channelConfig);
                ByteBuffer byteBuffer = ByteBuffer.allocate(adtsAudioHeader.length);
                byteBuffer.put(adtsAudioHeader);
                byteBuffer.flip();
                mediaFormat.setByteBuffer(CSD_MIME_TYPE_0, byteBuffer);
                aacDecoder.configure(mediaFormat, null, null, 0);
            } catch (IOException e) {
                Log.e(TAG, "realtime aac decoder create failure , happened exception : " + e.toString());
                if (aacDecoder != null) {
                    aacDecoder.stop();
                    aacDecoder.release();
                }
                aacDecoder = null;
            }
            if (aacDecoder == null) {
                return false;
            }
            // initialization audio track , use for play pcm audio data
            // audio output channel param channelConfig according device support select
            int buffsize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT);
            // author using channelConfig is AudioFormat.CHANNEL_OUT_MONO
            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
                    AudioFormat.ENCODING_PCM_16BIT, buffsize, AudioTrack.MODE_STREAM);
            audioTrack.play();
            aacDecoder.start();
            return true;
        }
    

    上述代码片段为如果前面代码的音频csd-0配置头不合法则直接return,接着就是把之前步骤解析AAC音频头得到的参数设置到解码器当中,如发生异常则return false,否则创建一个AudioTrack进行解码音频数据后的播放,验证数据是否正常被解析

        /**
         * aac audio data decode
         *
         * @param buf    aac audio data
         * @param length aac audio data length
         */
        private void decode(byte[] buf, int length) {
            try {
                ByteBuffer[] codecInputBuffers = aacDecoder.getInputBuffers();
                ByteBuffer[] codecOutputBuffers = aacDecoder.getOutputBuffers();
                long kTimeOutUs = 0;
                int inputBufIndex = aacDecoder.dequeueInputBuffer(kTimeOutUs);
                if (inputBufIndex >= 0) {
                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
                    dstBuf.clear();
                    dstBuf.put(buf, 0, length);
                    aacDecoder.queueInputBuffer(inputBufIndex, 0, length, 0, 0);
                }
                ByteBuffer outputBuffer;
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int outputBufferIndex = aacDecoder.dequeueOutputBuffer(info, kTimeOutUs);
                while (outputBufferIndex >= 0) {
                    outputBuffer = codecOutputBuffers[outputBufferIndex];
                    byte[] outData = new byte[info.size];
                    outputBuffer.get(outData);
                    outputBuffer.clear();
                    if (audioTrack != null) {
                        audioTrack.write(outData, 0, info.size);
                    }
                    aacDecoder.releaseOutputBuffer(outputBufferIndex, false);
                    outputBufferIndex = aacDecoder.dequeueOutputBuffer(info, kTimeOutUs);
                }
            } catch (Exception e) {
                Log.e(TAG, "realtime aac decode happened exception : " + e.toString());
            }
        }
    

    最后代码片段就是介绍MediaCodec的硬解码,这里就大概描述一下,因为跟本地音视频解码是一样的流程了,首先是获取输入输出缓冲区数组,然后将要解码的AAC音频数据填充到可用的输入缓冲区中,具体那个输入缓冲区可用,可以调用dequeueInputBuffer方法,该方法返回值大于等于0时表示该返回值对应的输入缓冲区数组的索引,然后就是解码了,从输出缓冲区数组中取得已经解码成功的输出缓冲区,可以调dequeueOutputBuffer方法获取它的有效索引,最后就是取出解码后的PCM数据进行播放,播放完毕后释放索引对应的输出缓冲区

    七、Demo地址

    AacDecoder

    感谢信,致热爱编程的你

    感谢各位粉丝、看管老爷们一直以来的支持和厚爱!以后作者出博客只出精品只出对大家有用的干货!让你在看博客的同时也能一起思考问题,从而达到边读博客边提升自身的知识软实力!在即将到来的新年,作者在此给你们提前拜一个早年了,祝大家新年快乐,完事如果,工作如意,身体健康!

    展开全文
    jspping 2021-01-30 10:11:06
  • 上一篇 已经参考其他的静态库,添加了aac 编解码器配置,加入到这里: 实现的功能,如同这些interface 加入gn文件后生成对应ninja 文件 build.ninja写入构建lib指令 G:\GERRIT\src\out\debug-x86\build....

    • 上一篇 已经参考其他的静态库,添加了aac 编解码器配置,加入到这里:

    在这里插入图片描述

    在api中也要加入audio encoder aac

    • 这里就是融合到框架中,并调用module去实现了。
      在这里插入图片描述

    加入gn文件后生成对应ninja 文件

    展开全文
    commshare 2021-03-10 16:06:10
  • King_weng 2021-03-15 16:40:43
  • shaosunrise 2021-12-04 13:02:22
  • u011391734 2021-02-02 16:47:22
  • qq_15255121 2021-04-25 19:48:56
  • qq_15255121 2021-08-13 18:59:19
  • u010029439 2021-01-11 15:08:20
  • qq_15255121 2021-04-24 23:22:51
  • fangye945a 2021-08-04 11:25:59
  • Zebar01 2021-08-17 23:19:42
  • commshare 2021-02-04 11:37:13
  • weixin_30974667 2021-05-15 07:58:29
  • VNanyesheshou 2021-03-17 17:43:12
  • weixin_31520435 2021-03-04 08:49:19
  • VNanyesheshou 2021-03-08 21:29:02
  • weixin_37921201 2021-07-24 07:59:29
  • weixin_39946534 2021-03-08 03:55:16
  • tqs_1220 2021-01-23 16:55:17
  • czhxinyu 2021-07-28 17:12:40
  • weixin_29752191 2021-05-26 17:56:24
  • weixin_43796767 2021-05-20 23:24:48
  • weixin_43796767 2021-05-22 23:38:11
  • yoonerloop 2021-08-07 17:11:48
  • sun007700 2020-12-24 10:40:19
  • weixin_43892073 2021-12-07 13:56:03
  • qq_15255121 2021-08-03 16:48:46
  • weixin_42525189 2021-07-16 02:05:39

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 69,002
精华内容 27,600
关键字:

aac

友情链接: Simple_notepad.rar