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 需要借助解码库来实现, 目前了解到有两个库能干这个事 : faad
和 ffmpeg
.
- 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
- EasyPlayer iOS开源流媒体播放器中AAC解码PCM问题
- 开源流媒体播放器EasyPlayer
- faad2解码aac到pcm
- IOS旗下基于FFmpeg开发的开源流媒体播放器汇总推荐
- faad2解码aac到pcm (采样频率和通道数问题)
- EasyDarwin EasyClient开源流媒体播放器,支持多窗口显示
- 嵌入式linux:音频编解码PCM转至AAC
- EasyPlayer开源流媒体移动端播放器推出RTSP-RTMP-HTTP-HLS全功能Pro版
- 音频编解码·实战篇(1)PCM转至AAC(AAC编码)
- (转)音频编解码·实战篇(1)PCM转至AAC(AAC编码)
- 音频编解码·实战篇(1)PCM转至AAC(AAC编码)
- 音频编解码·实战篇(1)PCM转至AAC(AAC编码)
- PCM编码AAC
- PCM 编码为AAC
- EasyDarwin开源流媒体云平台中boost Base64编解码后与源长度不匹配的bug
- EasyDarwin开源流媒体云平台中boost Base64编解码后与源长度不匹配的bug
- EasyPlayer支持H265视频解码
- IOS中url的编码,解码问题
- 十八.多人协作
- Context——Android中Context简介
- FFMPEG3.2+SDL2.0 +Qt5.5
- php正则的简单使用
- 十九.标签管理
- EasyPlayer iOS开源流媒体播放器中AAC解码PCM问题
- Windows 下搭建 React Native for Android 之 基础篇
- 微信公众号开发---步骤1(生成java项目)
- HibernateException: Unable to instantiate default tuplizer [org.hibernate.tuple.entity.PojoEntityTup
- 二十.创建标签
- Android面试基础知识总结
- 【opencv练习24 - 霍夫圆变换——圆检测】
- 二十一.操作标签
- 数组求和相关算法