zoukankan      html  css  js  c++  java
  • EasyPlayer iOS开源流媒体播放器中AAC解码PCM问题

    本文转自EasyDarwin开源团队成员Penggy的博客:http://www.jianshu.com/p/feeb107b6657

    最近遇到在 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
    
    void *faad_decoder_create(int sample_rate, int channels, int bit_rate);
    int faad_decode_frame(void *pParam, unsigned char *pData, int nLen, unsigned char *pPCM, unsigned int *outLen);
    void faad_decode_close(void *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"
    
    typedef struct {
        NeAACDecHandle handle;
        int sample_rate;
        int channels;
        int bit_rate;
    }FAADContext;
    
    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;
    }
    
    void *faad_decoder_create(int sample_rate, int channels, int bit_rate)
    {
        NeAACDecHandle handle = NeAACDecOpen();
        if(!handle){
            printf("NeAACDecOpen failed
    ");
            goto error;
        }
        NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(handle);
        if(!conf){
            printf("NeAACDecGetCurrentConfiguration failed
    ");
            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(void *pParam, 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
    ");
            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
    ");
            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
    ");
            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
    ");
            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
    ",nRet);
                printf("avcodec_decode_audio4 %d  sameles = %d  outSize = %d
    ", 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);
    }

    Github与源码

    EasyPlayer:https://github.com/EasyDarwin/EasyPlayer

    EasyDarwin开源流媒体云平台:https://github.com/EasyDarwin/EasyDarwin

    获取更多信息

    邮件:support@easydarwin.org

    WEB:www.EasyDarwin.org

    Copyright © EasyDarwin.org 2012-2016

    EasyDarwin

  • 相关阅读:
    STM32 ~ J-LINK V8 修复
    转移文件
    linux
    STM32 ~ MDK环境下调试程序 HardFault_Handler 相关
    HR_ROS 节点信息
    STM32 ~ 串口DMA通道查找
    CodeBackUP_node_find_serial
    Java问题排查工具箱[转载]
    JDK1.7 ConcurrentHashMap 源码浅析
    JDK1.7 HashMap 源码分析
  • 原文地址:https://www.cnblogs.com/babosa/p/5904608.html
Copyright © 2011-2022 走看看