AAC 到 PCM 音频解码

作者: penggy | 来源:发表于2016-09-04 09:26 被阅读5588次

    最近遇到在 iOS 平台上实时播放 AAC 音频数据流, 一开始尝试用 AudioQueue 直接解 AAC 未果, 转而将 AAC 解码为 PCM, 最终实现了 AAC 实时流在 iOS 平台下的播放问题.

    AAC 转 PCM 需要借助解码库来实现, 目前了解到有两个库能干这个事 : faadffmpeg.

    • faad 算是轻量级的解码库, 编译出来全平台静态库文件大小 2M 左右, API 也比较简单, 缺点是功能单一只处理 AAC , 它还有一个对应的编码库叫 faac.
    • ffmpeg 体积庞大, 功能丰富, API 略显复杂.

    下面分别梳理使用这两个库完成解码的过程.

    faad


    • 下载源码
    #下载
    wget http://downloads.sourceforge.net/faac/faad2-2.7.tar.gz
    #解压缩
    tar xvzf faad2-2.7.tar.gz
    #重命名
    mv faad2-2.7 faad
    
    • 写编译脚本, vi build-faad.sh
    #!/bin/sh
    
    CONFIGURE_FLAGS="--enable-static --with-pic"
    
    ARCHS="arm64 armv7s armv7 x86_64 i386"
    
    # directories
    SOURCE="faad"
    FAT="fat-faad"
    
    SCRATCH="scratch-faad"
    # must be an absolute path
    THIN=`pwd`/"thin-faad"
    
    COMPILE="y"
    LIPO="y"
    
    if [ "$*" ]
    then
    if [ "$*" = "lipo" ]
    then
    # skip compile
    COMPILE=
    else
    ARCHS="$*"
    if [ $# -eq 1 ]
    then
    # skip lipo
    LIPO=
    fi
    fi
    fi
    
    if [ "$COMPILE" ]
    then
    CWD=`pwd`
    for ARCH in $ARCHS
    do
    echo "building $ARCH..."
    mkdir -p "$SCRATCH/$ARCH"
    cd "$SCRATCH/$ARCH"
    
    if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]
    then
    PLATFORM="iPhoneSimulator"
    CPU=
    if [ "$ARCH" = "x86_64" ]
    then
    SIMULATOR="-mios-simulator-version-min=7.0"
    HOST=
    else
    SIMULATOR="-mios-simulator-version-min=5.0"
    HOST="--host=i386-apple-darwin"
    fi
    else
    PLATFORM="iPhoneOS"
    if [ $ARCH = "armv7s" ]
    then
    CPU="--cpu=swift"
    else
    CPU=
    fi
    SIMULATOR=
    HOST="--host=arm-apple-darwin"
    fi
    
    XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'`
    CC="xcrun -sdk $XCRUN_SDK clang -Wno-error=unused-command-line-argument-hard-error-in-future"
    AS="$CWD/$SOURCE/extras/gas-preprocessor.pl $CC"
    CFLAGS="-arch $ARCH $SIMULATOR"
    CXXFLAGS="$CFLAGS"
    LDFLAGS="$CFLAGS"
    
    CC=$CC CFLAGS=$CXXFLAGS LDFLAGS=$LDFLAGS CPPFLAGS=$CXXFLAGS CXX=$CC CXXFLAGS=$CXXFLAGS  $CWD/$SOURCE/configure \
    $CONFIGURE_FLAGS \
    $HOST \
    --prefix="$THIN/$ARCH" \
    --disable-shared \
    --without-mp4v2
    
    make clean && make && make install-strip
    cd $CWD
    done
    fi
    
    if [ "$LIPO" ]
    then
    echo "building fat binaries..."
    mkdir -p $FAT/lib
    set - $ARCHS
    CWD=`pwd`
    cd $THIN/$1/lib
    for LIB in *.a
    do
    cd $CWD
    lipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIB
    done
    
    cd $CWD
    cp -rf $THIN/$1/include $FAT
    fi
    

    保存编译脚本到解压出的 faad 目录同一级目录下, 并添加可执行权限
    chmod a+x build-faad.sh

    • 编译
      ./build-faad.sh
      当前目录下 fat-faad 即为编译结果所在位置, 里面有头文件和支持全平台(armv7, armv7s ,i386, x86_64, arm64)的静态库

    • 添加静态库到工程依赖 (鼠标拖 fat-faad 目录到 xcode 工程目录下), 创建解码文件FAACDecoder.h,FAACDecoder.m

    • FAACDecoder.h

    //
    //  FAACDecoder.h
    //  EasyClient
    //
    //  Created by 吴鹏 on 16/9/3.
    //  Copyright © 2016年 EasyDarwin. All rights reserved.
    //
    
    #ifndef FAACDecoder_h
    #define FAACDecoder_h
    
    typedef struct {
        NeAACDecHandle handle;
        int sample_rate;
        int channels;
        int bit_rate;
    }FAADContext;
    
    FAADContext* faad_decoder_create(int sample_rate, int channels, int bit_rate);
    int faad_decode_frame(FAADContext *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen);
    void faad_decode_close(FAADContext *pParam);
    
    #endif /* FAACDecoder_h */
    
    • FAACDecoder.m
    //
    //  FAACDecoder.m
    //  EasyClient
    //
    //  Created by 吴鹏 on 16/9/3.
    //  Copyright © 2016年 EasyDarwin. All rights reserved.
    //
    #import <Foundation/Foundation.h>
    #import "FAACDecoder.h"
    #import "faad.h"
    
    uint32_t _get_frame_length(const unsigned char *aac_header)
    {
        uint32_t len = *(uint32_t *)(aac_header + 3);
        len = ntohl(len); //Little Endian
        len = len << 6;
        len = len >> 19;
        return len;
    }
    
    FAADContext* faad_decoder_create(int sample_rate, int channels, int bit_rate)
    {
        NeAACDecHandle handle = NeAACDecOpen();
        if(!handle){
            printf("NeAACDecOpen failed\n");
            goto error;
        }
        NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(handle);
        if(!conf){
            printf("NeAACDecGetCurrentConfiguration failed\n");
            goto error;
        }
        conf->defSampleRate = sample_rate;
        conf->outputFormat = FAAD_FMT_16BIT;
        conf->dontUpSampleImplicitSBR = 1;
        NeAACDecSetConfiguration(handle, conf);
        
        FAADContext* ctx = malloc(sizeof(FAADContext));
        ctx->handle = handle;
        ctx->sample_rate = sample_rate;
        ctx->channels = channels;
        ctx->bit_rate = bit_rate;
        return ctx;
        
    error:
        if(handle){
            NeAACDecClose(handle);
        }
        return NULL;
    }
    
    int faad_decode_frame(FAADContext *p, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen)
    {
        FAADContext* pCtx = (FAADContext*)pParam;
        NeAACDecHandle handle = pCtx->handle;
        long res = NeAACDecInit(handle, pData, nLen, (unsigned long*)&pCtx->sample_rate, (unsigned char*)&pCtx->channels);
        if (res < 0) {
            printf("NeAACDecInit failed\n");
            return -1;
        }
        NeAACDecFrameInfo info;
        uint32_t framelen = _get_frame_length(pData);
        unsigned char *buf = (unsigned char *)NeAACDecDecode(handle, &info, pData, framelen);
        if (buf && info.error == 0) {
            if (info.samplerate == 44100) {
                //src: 2048 samples, 4096 bytes
                //dst: 2048 samples, 4096 bytes
                int tmplen = (int)info.samples * 16 / 8;
                memcpy(pPCM,buf,tmplen);
                *outLen = tmplen;
            } else if (info.samplerate == 22050) {
                //src: 1024 samples, 2048 bytes
                //dst: 2048 samples, 4096 bytes
                short *ori = (short*)buf;
                short tmpbuf[info.samples * 2];
                int tmplen = (int)info.samples * 16 / 8 * 2;
                for (int32_t i = 0, j = 0; i < info.samples; i += 2) {
                    tmpbuf[j++] = ori[i];
                    tmpbuf[j++] = ori[i + 1];
                    tmpbuf[j++] = ori[i];
                    tmpbuf[j++] = ori[i + 1];
                }
                memcpy(pPCM,tmpbuf,tmplen);
                *outLen = tmplen;
            }else if(info.samplerate == 8000){
                //从双声道的数据中提取单通道
                for(int i=0,j=0; i<4096 && j<2048; i+=4, j+=2)
                {
                    pPCM[j]= buf[i];
                    pPCM[j+1]=buf[i+1];
                }
                *outLen = (unsigned int)info.samples;
            }
        } else {
            printf("NeAACDecDecode failed\n");
            return -1;
        }
        return 0;
    }
    
    void faad_decode_close(void *pParam)
    {
        if(!pParam){
            return;
        }
        FAADContext* pCtx = (FAADContext*)pParam;
        if(pCtx->handle){
            NeAACDecClose(pCtx->handle);
        }
        free(pCtx);
    }
    

    几个主要 API :

    1. NeAACDecOpen
    2. NeAACDecGetCurrentConfiguration
    3. NeAACDecSetConfiguration
    4. NeAACDecInit
    5. NeAACDecDecode
    6. NeAACDecClose

    ffmpeg


    #ifndef _AACDecoder_h
    #define _AACDecoder_h
    
    void *aac_decoder_create(int sample_rate, int channels, int bit_rate);
    int aac_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen);
    void aac_decode_close(void *pParam);
    
    #endif
    
    • AACDecoder.m
    #include "AACDecoder.h"
    #include "libavformat/avformat.h"
    #include "libswresample/swresample.h"
    #include "libavcodec/avcodec.h"
    
    typedef struct AACDFFmpeg {
        AVCodecContext *pCodecCtx;
        AVFrame *pFrame;
        struct SwrContext *au_convert_ctx;
        int out_buffer_size;
    } AACDFFmpeg;
    
    void *aac_decoder_create(int sample_rate, int channels, int bit_rate)
    {
        AACDFFmpeg *pComponent = (AACDFFmpeg *)malloc(sizeof(AACDFFmpeg));
        AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_AAC);
        if (pCodec == NULL)
        {
            printf("find aac decoder error\r\n");
            return 0;
        }
        // 创建显示contedxt
        pComponent->pCodecCtx = avcodec_alloc_context3(pCodec);
        pComponent->pCodecCtx->channels = channels;
        pComponent->pCodecCtx->sample_rate = sample_rate;
        pComponent->pCodecCtx->bit_rate = bit_rate;
        if(avcodec_open2(pComponent->pCodecCtx, pCodec, NULL) < 0)
        {
            printf("open codec error\r\n");
            return 0;
        }
        
        pComponent->pFrame = av_frame_alloc();
        
    
        uint64_t out_channel_layout = channels < 2 ? AV_CH_LAYOUT_MONO:AV_CH_LAYOUT_STEREO;
        int out_nb_samples = 1024;
        enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
        
        pComponent->au_convert_ctx = swr_alloc();
        pComponent->au_convert_ctx = swr_alloc_set_opts(pComponent->au_convert_ctx, out_channel_layout, out_sample_fmt, sample_rate,
                                          out_channel_layout, AV_SAMPLE_FMT_FLTP, sample_rate, 0, NULL);
        swr_init(pComponent->au_convert_ctx);
        int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
        pComponent->out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples, out_sample_fmt, 1);
    
        return (void *)pComponent;
    }
    
    int aac_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen)
    {
        AACDFFmpeg *pAACD = (AACDFFmpeg *)pParam;
        AVPacket packet;
        av_init_packet(&packet);
        
        packet.size = nLen;
        packet.data = pData;
        
        int got_frame = 0;
        int nRet = 0;
        if (packet.size > 0)
        {
            nRet = avcodec_decode_audio4(pAACD->pCodecCtx, pAACD->pFrame, &got_frame, &packet);
            if (nRet < 0)
            {
       printf("avcodec_decode_audio4:%d\r\n",nRet);
                printf("avcodec_decode_audio4 %d  sameles = %d  outSize = %d\r\n", nRet, pAACD->pFrame->nb_samples, pAACD->out_buffer_size);
                return nRet;
            }
    
            if(got_frame)
            {
                swr_convert(pAACD->au_convert_ctx, &pPCM, pAACD->out_buffer_size, (const uint8_t **)pAACD->pFrame->data, pAACD->pFrame->nb_samples);
                *outLen = pAACD->out_buffer_size;
            }
        }
    
        av_free_packet(&packet);
        if (nRet > 0)
        {
            return 0;
        }
        return -1;
    }
    
    void aac_decode_close(void *pParam)
    {
        AACDFFmpeg *pComponent = (AACDFFmpeg *)pParam;
        if (pComponent == NULL)
        {
            return;
        }
        
        swr_free(&pComponent->au_convert_ctx);
        
        if (pComponent->pFrame != NULL)
        {
            av_frame_free(&pComponent->pFrame);
            pComponent->pFrame = NULL;
        }
        
        if (pComponent->pCodecCtx != NULL)
        {
            avcodec_close(pComponent->pCodecCtx);
            avcodec_free_context(&pComponent->pCodecCtx);
            pComponent->pCodecCtx = NULL;
        }
        
        free(pComponent);
    }
    

    相关文章

      网友评论

      • 69de3dac300f:大神,能否发个demo 非常感谢 535771596@qq.com
      • wenduC:楼主,您好!请教一下在电脑端如何实时播放在安卓端PCM转换出来的AAC流的音频?麻烦了。
      • 91339b6e8fb5:按照教程编出来了 发现没有x86_64 这个架构 看了下脚本 发现 在
        if [ "$ARCH" = "x86_64" ]
        then
        SIMULATOR="-mios-simulator-version-min=7.0"
        HOST=

        这里HOST 没有值 给HOST添上 HOST="--host=x86_64-apple-darwin"
        就好了

        91339b6e8fb5:@penggy 谢谢,其实不太懂脚本,在这里被坑了一下然后仔细看了看 :smiley:
        penggy:很有用的反馈信息
      • 956bf91b67f7:大兄弟 能发个.a吗 我编不过 估计是环境问题。328014942@qq.com 多谢了
        956bf91b67f7:已经编过了,没有用.sh 直接执行./configure --prefix=/share/faadlib/ --host=arm-apple-darwin --enable-static=yes --enable-shared=no CC=/share/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc CFLAGS="-arch armv7 -pipe -std=c99 -Wno-extended-offsetof -Wno-trigraphs -fpascal-strings -O2 -Wreturn-type -Wunused-variable -fmessage-length=0 -fvisibility=hidden -miphoneos-version-min=9.0 -I/share/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include -isysroot /share/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk" AR=/share/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ar LDFLAGS="-arch armv7 -isysroot /share/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -miphoneos-version-min=9.0" 然后make;make install

        这里面的xcode路径和放库的路径要根据自己的修改一下 分别编译arm64和armv7的库,然后用lipo合成一个库,再在xcode中把bitcode关闭,就可以用了。

        目前还只是编译过了,不知道怎么调用接口
      • 远方的枸杞:大兄弟,有oc的代码吗
      • shuolol:你好 大神 因为我用faac 压缩的pcm 是16000采样率 单声道 但是使用NeAACDecInit 初始化出来每次都是16000 双声道 conf->dontUpSampleImplicitSBR = 1; 设置了这个也没用 难道要改源码吗?
        penggy:@shuolol 我也了解不深,就会这些简单的编译调用,写这文章是记录过程备忘,不好意思啊
      • 2324164623a5:楼主,求demo,急
      • 2324164623a5:大神,求编译脚本,能否发一份编译好的libfaad.a到我的邮箱153751320@qq.com,谢谢
      • 9c5dda29cf12:楼主你好,我按你写的脚本编译faad出错了,能否发一份编译好的libfaad.a到我的邮箱66682060@qq.com,谢谢
      • 2c5faf7a2f78:你好,文中提到用info.samplerate来计算outLen,有44100、22050和8000;请问其他samplerate时如何计算outLen?
      • 2fccb0689f51:你好?能给你个demo吗?Faad解码AAC的方法怎么调用呢?后面的参数应该怎么填呢?谢谢
      • dbbe0167ce9d:楼主你好,我按你写的脚本编译faad出错了,能否发一份编译好的libfaad.a到我的邮箱1160090198@qq.com,谢谢
        Mo丿小冷:能给我发一份.a吗?392056807@qq.com谢谢了
        Mo丿小冷:@penggy 能给我发一份.a吗?392056807@qq.com谢谢了
        penggy:@星红 已发
      • 899e2d74bdeb:我看文档上,是可以直接播放aac的,苹果会自动转码的
        penggy: @哎疯 我也试过直接解AAC,没成功,没办法才转PCM的
        899e2d74bdeb:@penggy http://www.jianshu.com/p/279a9e5b36b5 这遍文章上没经过转换,可能是播放本地的码流的原因,情况不一样。
        penggy:@哎疯 不是自动转码, 是先转PCM,再给苹果解码
      • c1e0f39af20a:请问下你这里的脚本文件在哪里呢。不太懂这个
        penggy:@feng5f pParam->faad_decoder_create 返回的上下文句柄, pData->aac码流, nLen->aac码流长度,pPCM->解出来的pcm码流,outLen->解出来的pcm码流长度
        c1e0f39af20a:@penggy 谢谢,能不能再跟我说一下*pParam, *pData, nLen, *pPCM, unsigned *outLen 这几个参数的含义呢,,我在用live555接受的时候好像没找到这几个数据,能不能稍微解释下呢
        penggy:@feng5f 直接copy保存就可以, 也是从网上找的,亲测可以编译

      本文标题:AAC 到 PCM 音频解码

      本文链接:https://www.haomeiwen.com/subject/ysdlettx.html