【FFmpeg(2016)】PCM编码AAC

http://www.voidcn.com/article/p-qrukcans-gc.html

【前言】


本文章主要是将 PCM原始数据编码为AAC。


测试文件则是上一篇文章生成的PCM文件: 

【FFmpeg(2016)】视频文件分离器(demuxing)——H264&PCM


音频数据format分很多种类型,16bit,32bit等,而2016 ffmpeg只支持最新的AAC格式,32bit,也就是AV_SAMPLE_FMT_FLTP。


所以,想对PCM进行编码得先确保PCM是AV_SAMPLE_FMT_FLTP类型的。


【AAC封装格式】

AAC有两种封装格式,分别是ADIF ADTS,多与流媒体一般使用ADTS格式。见:

AAC ADTS格式分析


【FFmpeg数据结构】

[cpp]  view plain  copy
  1. AVCodecContext  
  2. AVCodec  
  3. AVCodecID  
  4. AVFrame  
  5. AVPacket  


对PCM文件的读写直接使用FILE文件指针。

AVCodec是一个编码器,可以单纯的理解为一个编解码算法的结构。


AVCodecContext是AVCodec的一个上下文,打个比如,在视频编码h264时,有i p b三种帧,如果有一个视频流是 I B B P这种顺序到达,由于B帧需要依靠前后的帧来计算出本帧现实的内容,所有需要一些buffer保存一些,以根据这些来计算出B帧的内容,当然还有很多其他的内容。


AVCodecID是编码器的ID,如编码AAC是,就使用AV_CODEC_ID_AAC。


AVFrame 是编码前、解码后保存的数据。
AVPacket是编码后、解码前保存的数据。


关于官方定义的AVFrame:

[cpp]  view plain  copy
  1. typedef struct AVFrame {  
  2. #define AV_NUM_DATA_POINTERS 8  
  3.     /** 
  4.      * pointer to the picture/channel planes. 
  5.      * This might be different from the first allocated byte 
  6.      * 
  7.      * Some decoders access areas outside 0,0 - width,height, please 
  8.      * see avcodec_align_dimensions2(). Some filters and swscale can read 
  9.      * up to 16 bytes beyond the planes, if these filters are to be used, 
  10.      * then 16 extra bytes must be allocated. 
  11.      * 
  12.      * NOTE: Except for hwaccel formats, pointers not needed by the format 
  13.      * MUST be set to NULL. 
  14.      */  
  15.     uint8_t *data[AV_NUM_DATA_POINTERS];  
  16.   
  17.     /** 
  18.      * For video, size in bytes of each picture line. 
  19.      * For audio, size in bytes of each plane. 
  20.      * 
  21.      * For audio, only linesize[0] may be set. For planar audio, each channel 
  22.      * plane must be the same size. 
  23.      * 
  24.      * For video the linesizes should be multiples of the CPUs alignment 
  25.      * preference, this is 16 or 32 for modern desktop CPUs. 
  26.      * Some code requires such alignment other code can be slower without 
  27.      * correct alignment, for yet other it makes no difference. 
  28.      * 
  29.      * @note The linesize may be larger than the size of usable data -- there 
  30.      * may be extra padding present for performance reasons. 
  31.      */  
  32.     int linesize[AV_NUM_DATA_POINTERS];  
  33.   
  34.     /** 
  35.      * pointers to the data planes/channels. 
  36.      * 
  37.      * For video, this should simply point to data[]. 
  38.      * 
  39.      * For planar audio, each channel has a separate data pointer, and 
  40.      * linesize[0] contains the size of each channel buffer. 
  41.      * For packed audio, there is just one data pointer, and linesize[0] 
  42.      * contains the total size of the buffer for all channels. 
  43.      * 
  44.      * Note: Both data and extended_data should always be set in a valid frame, 
  45.      * but for planar audio with more channels that can fit in data, 
  46.      * extended_data must be used in order to access all channels. 
  47.      */  
  48.     uint8_t **extended_data;  
  49.   
  50.     ......其他成员  
  51.   
  52. } AVFrame;  

对于视频,目前比较流行的是H264压缩标准,好像没见过其他编码方式,而H264只能由YUV图像编码,也就是说H264解码后就是三个YUV分量,他们的数据会分别存在,data[0],data[1],data[2] ,而linesize[0],linesize[1],linesize[2]分别代表各个数据的长度。

对于音频,由于有多声道的音频,那么音频解码出来的数据不同声道也储存在不同的指针,如data[0]是左声道,data[1]是右声道,由于各个声道的数据长度是一样的,所以linesize[0]就代表了所有声道数据的长度。

成员extended_data则指向了data,是一个拓展,上面可以看到data 是包含8个指针的数组,也就是说对于音频,最多只支持8个声道。



【代码】


[cpp]  view plain  copy
  1. extern "C"  
  2. {  
  3. #include "libavformat/avformat.h"  
  4. #include "libavutil/avutil.h"  
  5. #include "libavcodec/avcodec.h"  
  6. #include "libavutil/frame.h"  
  7. #include "libavutil/samplefmt.h"  
  8. #include "libavformat/avformat.h"  
  9. #include "libavcodec/avcodec.h"  
  10. }  
  11.   
  12. #pragma comment(lib, "avcodec.lib")  
  13. #pragma comment(lib, "avfilter.lib")  
  14. #pragma comment(lib, "avformat.lib")  
  15. #pragma comment(lib, "avutil.lib")  
  16.   
  17. /* PCM转AAC */  
  18. int main()  
  19. {  
  20.   
  21.     char *padts = (char *)malloc(sizeof(char) * 7);  
  22.     int profile = 2;                                            //AAC LC  
  23.     int freqIdx = 4;                                            //44.1KHz  
  24.     int chanCfg = 2;            //MPEG-4 Audio Channel Configuration. 1 Channel front-center  
  25.     padts[0] = (char)0xFF;      // 11111111     = syncword  
  26.     padts[1] = (char)0xF1;      // 1111 1 00 1  = syncword MPEG-2 Layer CRC  
  27.     padts[2] = (char)(((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));  
  28.     padts[6] = (char)0xFC;  
  29.   
  30.     AVCodec *pCodec;  
  31.     AVCodecContext *pCodecCtx = NULL;  
  32.     int i, ret, got_output;  
  33.     FILE *fp_in;  
  34.     FILE *fp_out;  
  35.   
  36.     AVFrame *pFrame;  
  37.     uint8_t* frame_buf;  
  38.     int size = 0;  
  39.   
  40.     AVPacket pkt;  
  41.     int y_size;  
  42.     int framecnt = 0;  
  43.   
  44.     char filename_in[] = "audio.pcm";  
  45.   
  46.     AVCodecID codec_id = AV_CODEC_ID_AAC;  
  47.     char filename_out[] = "audio.aac";  
  48.   
  49.     int framenum = 100000;  
  50.   
  51.     avcodec_register_all();  
  52.   
  53.     pCodec = avcodec_find_encoder(codec_id);  
  54.     if (!pCodec) {  
  55.         printf("Codec not found\n");  
  56.         return -1;  
  57.     }  
  58.   
  59.     pCodecCtx = avcodec_alloc_context3(pCodec);  
  60.     if (!pCodecCtx) {  
  61.         printf("Could not allocate video codec context\n");  
  62.         return -1;  
  63.     }  
  64.   
  65.     pCodecCtx->codec_id = codec_id;  
  66.     pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;  
  67.     pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;  
  68.     pCodecCtx->sample_rate = 44100;  
  69.   
  70.   
  71.     pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;  
  72.     pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);  
  73.     qDebug() << av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);  
  74.   
  75.   
  76.   
  77.     if ((ret = avcodec_open2(pCodecCtx, pCodec, NULL)) < 0) {  
  78.         qDebug() << "avcodec_open2 error ----> " << ret;  
  79.   
  80.         printf("Could not open codec\n");  
  81.         return -1;  
  82.     }  
  83.   
  84.     pFrame = av_frame_alloc();  
  85.   
  86.     pFrame->nb_samples = pCodecCtx->frame_size;   //1024,默认每一帧的采样个数是frame_size,貌似也改变不了  
  87.     pFrame->format = pCodecCtx->sample_fmt;  
  88.     pFrame->channels = 2;  
  89.   
  90.     size = av_samples_get_buffer_size(NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 0);  
  91.     frame_buf = (uint8_t *)av_malloc(size);  
  92.     /** 
  93.     *   avcodec_fill_audio_frame 实现: 
  94.     *   frame_buf是根据声道数、采样率和采样格式决定大小的。 
  95.     *   调用次函数后,AVFrame存储音频数据的成员有以下变化:data[0]指向frame_buf,data[1]指向frame_buf长度的一半位置 
  96.     *   data[0] == frame_buf , data[1] == frame_buf + pCodecCtx->frame_size * av_get_bytes_per_sample(pCodecCtx->sample_fmt) 
  97.     */  
  98.     ret = avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt, (const uint8_t*)frame_buf, size, 0);  
  99.   
  100.     if (ret < 0)  
  101.     {  
  102.         qDebug() << "avcodec_fill_audio_frame error ";  
  103.         return 0;  
  104.     }  
  105.   
  106.     //Input raw data  
  107.     fp_in = fopen(filename_in, "rb");  
  108.     if (!fp_in) {  
  109.         printf("Could not open %s\n", filename_in);  
  110.         return -1;  
  111.     }  
  112.   
  113.     //Output bitstream  
  114.     fp_out = fopen(filename_out, "wb");  
  115.     if (!fp_out) {  
  116.         printf("Could not open %s\n", filename_out);  
  117.         return -1;  
  118.     }  
  119.   
  120.     //Encode  
  121.     for (i = 0; i < framenum; i++) {  
  122.         av_init_packet(&pkt);  
  123.         pkt.data = NULL;    // packet data will be allocated by the encoder  
  124.         pkt.size = 0;  
  125.         //Read raw data  
  126.         if (fread(frame_buf, 1, size, fp_in) <= 0) {  
  127.             printf("Failed to read raw data! \n");  
  128.             return -1;  
  129.         }  
  130.         else if (feof(fp_in)) {  
  131.             break;  
  132.         }  
  133.   
  134.         pFrame->pts = i;  
  135.   
  136.         ret = avcodec_encode_audio2(pCodecCtx, &pkt, pFrame, &got_output);  
  137.   
  138.         if (ret < 0) {  
  139.             qDebug() << "error encoding";  
  140.             return -1;  
  141.         }  
  142.   
  143.         if (pkt.data == NULL)  
  144.         {  
  145.             av_free_packet(&pkt);  
  146.             continue;  
  147.         }  
  148.   
  149.         qDebug() << "got_ouput = " << got_output;  
  150.         if (got_output) {  
  151.             qDebug() << "Succeed to encode frame : " << framecnt << " size :" << pkt.size;  
  152.   
  153.             framecnt++;  
  154.   
  155.             padts[3] = (char)(((chanCfg & 3) << 6) + ((7 + pkt.size) >> 11));  
  156.             padts[4] = (char)(((7 + pkt.size) & 0x7FF) >> 3);  
  157.             padts[5] = (char)((((7 + pkt.size) & 7) << 5) + 0x1F);  
  158.             fwrite(padts, 7, 1, fp_out);  
  159.             fwrite(pkt.data, 1, pkt.size, fp_out);  
  160.   
  161.             av_free_packet(&pkt);  
  162.         }  
  163.     }  
  164.     //Flush Encoder  
  165.     for (got_output = 1; got_output; i++) {  
  166.         ret = avcodec_encode_audio2(pCodecCtx, &pkt, NULL, &got_output);  
  167.         if (ret < 0) {  
  168.             printf("Error encoding frame\n");  
  169.             return -1;  
  170.         }  
  171.         if (got_output) {  
  172.             printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", pkt.size);  
  173.             padts[3] = (char)(((chanCfg & 3) << 6) + ((7 + pkt.size) >> 11));  
  174.             padts[4] = (char)(((7 + pkt.size) & 0x7FF) >> 3);  
  175.             padts[5] = (char)((((7 + pkt.size) & 7) << 5) + 0x1F);  
  176.   
  177.             fwrite(padts, 7, 1, fp_out);  
  178.             fwrite(pkt.data, 1, pkt.size, fp_out);  
  179.             av_free_packet(&pkt);  
  180.         }  
  181.     }  
  182.   
  183.     fclose(fp_out);  
  184.     avcodec_close(pCodecCtx);  
  185.     av_free(pCodecCtx);  
  186.     av_freep(&pFrame->data[0]);  
  187.     av_frame_free(&pFrame);  
  188.   
  189.     return 0;  
  190. }  

【错误的结果】由于PCM格式不正确


使用上一篇文章产生的PCM来转换AAC是错误的。

因为上一篇文章保存的音频格式: L(一个采样点)R(一个采样点)LRLRLR..............

由于AVframe结构体data指针数组不同指针代表指向不同声道的数据,所以产生错误。

上述代码,data指向情况:


而FFmpeg编码PCM为AAC时,需要的是:


所以,我要让到读取一帧时,刚好让data[0]指向一个声道的数据,而data[1]指向另一个声道的数据。


【解决方法】

由上述代码我们知道AVFrame->nb_samples 默认是1024,所以每一帧一个声道读取的数据为:

[cpp]  view plain  copy
  1. int length = AVFrame->nb_samples * av_get_byte_per_sample((AVSampleFormat)AVFrame->format);  

这里也就是4096字节。

所以写PCM文件时,应该是:


【FFmpeg(2016)】视频文件分离器(demuxing)——H264&PCM

写PCM文件的代码:

[cpp]  view plain  copy
  1. /** 
  2. *   在这里写入文件时我做了一些处理,这是有原因的。 
  3. *   下面的意思是,LRLRLR...的方式写入文件,每次写入4096个字节 
  4. */  
  5. int k=0, h=0;  
  6. for (int i = 0; i < 4; ++i)  
  7. {  
  8.     if (i % 2 == 0)  
  9.     {  
  10.         int tmp = data_size / 4;  
  11.         for (int j = 0; j < tmp; j+=4,k++ )  
  12.         {  
  13.             data[i * 4096 + j+0] = (char)(l[k]       & 0xff);  
  14.             data[i * 4096 + j+1] = (char)(l[k] >> 8  & 0xff);  
  15.             data[i * 4096 + j+2] = (char)(l[k] >> 16 & 0xff);  
  16.             data[i * 4096 + j+3] = (char)(l[k] >> 24 & 0xff);  
  17.         }  
  18.     }  
  19.     else  
  20.     {  
  21.         int tmp = data_size / 4;  
  22.         for (int j = 0; j < tmp; j += 4,h++)  
  23.         {  
  24.             data[i * 4096 + j+0] = (char)(r[h]       & 0xff);  
  25.             data[i * 4096 + j+1] = (char)(r[h] >> 8  & 0xff);  
  26.             data[i * 4096 + j+2] = (char)(r[h] >> 16 & 0xff);  
  27.             data[i * 4096 + j+3] = (char)(r[h] >> 24 & 0xff);  
  28.         }  
  29.     }  
  30. }  

【Planar】这是FFmpeg的一个新概念,貌似在2014版ffmpeg还没有这个概念

Planar即是:平面

官方对AVSampleFormat的定义如下:

[cpp]  view plain  copy
  1. enum AVSampleFormat {  
  2.     AV_SAMPLE_FMT_NONE = -1,  
  3.     AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits  
  4.     AV_SAMPLE_FMT_S16,         ///< signed 16 bits  
  5.     AV_SAMPLE_FMT_S32,         ///< signed 32 bits  
  6.     AV_SAMPLE_FMT_FLT,         ///< float  
  7.     AV_SAMPLE_FMT_DBL,         ///< double  
  8.   
  9.     AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar  
  10.     AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar  
  11.     AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar  
  12.     AV_SAMPLE_FMT_FLTP,        ///< float, planar  
  13.     AV_SAMPLE_FMT_DBLP,        ///< double, planar  
  14.     AV_SAMPLE_FMT_S64,         ///< signed 64 bits  
  15.     AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar  
  16.   
  17.     AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically  
  18. };  

后面有P的就代表是平面类型,所谓平面,即是音频数据不再是如此存储:

L(一个采样点)R(一个采样点)LRLRLRLR...............

对AVFrame而言,应该是data[0],data[1]分别指向左右声道数据,这就是平面的概念。(可以类比视频解码时的YUV存储方式)

L(一帧)R(一帧)LRLRLR............................


打开AAC编码器,可以看到只支持AV_SAMPLE_FMT_FLTP类型:

[cpp]  view plain  copy
  1. AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);  
  2. AVCodecContext *codec_ctx ;  
  3. avcodec_open2(codec_ctx, codec);  


所以在编码AAC前,必须先却倒存储格式是正确的(虽然播放PCM时有点问题)。


【关于平面概念】

【FFmpeg(2016)】SwrContext重采样结构体

相关文章
相关标签/搜索