EasyPlayer iOS开源流媒体播放器中AAC解码PCM问题

来源:互联网 发布:冰川网络 300533 股吧 编辑:程序博客网 时间:2024/06/06 12:55

本文转自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/shCONFIGURE_FLAGS="--enable-static --with-pic"ARCHS="arm64 armv7s armv7 x86_64 i386"# directoriesSOURCE="faad"FAT="fat-faad"SCRATCH="scratch-faad"# must be an absolute pathTHIN=`pwd`/"thin-faad"COMPILE="y"LIPO="y"if [ "$*" ]thenif [ "$*" = "lipo" ]then# skip compileCOMPILE=elseARCHS="$*"if [ $# -eq 1 ]then# skip lipoLIPO=fififiif [ "$COMPILE" ]thenCWD=`pwd`for ARCH in $ARCHSdoecho "building $ARCH..."mkdir -p "$SCRATCH/$ARCH"cd "$SCRATCH/$ARCH"if [ "$ARCH" = "i386" -o "$ARCH" = "x86_64" ]thenPLATFORM="iPhoneSimulator"CPU=if [ "$ARCH" = "x86_64" ]thenSIMULATOR="-mios-simulator-version-min=7.0"HOST=elseSIMULATOR="-mios-simulator-version-min=5.0"HOST="--host=i386-apple-darwin"fielsePLATFORM="iPhoneOS"if [ $ARCH = "armv7s" ]thenCPU="--cpu=swift"elseCPU=fiSIMULATOR=HOST="--host=arm-apple-darwin"fiXCRUN_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-mp4v2make clean && make && make install-stripcd $CWDdonefiif [ "$LIPO" ]thenecho "building fat binaries..."mkdir -p $FAT/libset - $ARCHSCWD=`pwd`cd $THIN/$1/libfor LIB in *.adocd $CWDlipo -create `find $THIN -name $LIB` -output $FAT/lib/$LIBdonecd $CWDcp -rf $THIN/$1/include $FATfi

保存编译脚本到解压出的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_hvoid *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\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(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\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


  • 下载编译
    参考 https://github.com/kewlbear/FFmpeg-iOS-build-script

  • 添加 ffmpeg 静态库到工程依赖, 创建解码文件AACDecoder.h, AACDecoder.m

  • AACDecoder.h

#ifndef _AACDecoder_h#define _AACDecoder_hvoid *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);}

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

0 0
原创粉丝点击