http://blog.csdn.net/Jammg/article/details/52684894
算法
【前言】
本文章主要是将 PCM原始数据编码为AAC。数组
测试文件则是上一篇文章生成的PCM文件: 数据结构
音频数据format分不少种类型,16bit,32bit等,而2016 ffmpeg只支持最新的AAC格式,32bit,也就是AV_SAMPLE_FMT_FLTP。ide
因此,想对PCM进行编码得先确保PCM是AV_SAMPLE_FMT_FLTP类型的。函数
【AAC封装格式】测试
AAC有两种封装格式,分别是ADIF ADTS,多与流媒体通常使用ADTS格式。见:ui
AAC ADTS格式分析
this
【FFmpeg数据结构】
- AVCodecContext
- AVCodec
- AVCodecID
- AVFrame
- AVPacket
对PCM文件的读写直接使用FILE文件指针。
AVCodec是一个编码器,能够单纯的理解为一个编解码算法的结构。编码
AVCodecContext是AVCodec的一个上下文,打个好比,在视频编码h264时,有i p b三种帧,若是有一个视频流是 I B B P这种顺序到达,因为B帧须要依靠先后的帧来计算出本帧现实的内容,全部须要一些buffer保存一些,以根据这些来计算出B帧的内容,固然还有不少其余的内容。spa
AVCodecID是编码器的ID,如编码AAC是,就使用AV_CODEC_ID_AAC。
AVFrame 是编码前、解码后保存的数据。
AVPacket是编码后、解码前保存的数据。
关于官方定义的AVFrame:
- typedef struct AVFrame {
- #define AV_NUM_DATA_POINTERS 8
-
-
-
-
-
-
-
-
-
-
-
-
- uint8_t *data[AV_NUM_DATA_POINTERS];
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- int linesize[AV_NUM_DATA_POINTERS];
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- uint8_t **extended_data;
-
- ......其余成员
-
- } 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个声道。
【代码】
- extern "C"
- {
- #include "libavformat/avformat.h"
- #include "libavutil/avutil.h"
- #include "libavcodec/avcodec.h"
- #include "libavutil/frame.h"
- #include "libavutil/samplefmt.h"
- #include "libavformat/avformat.h"
- #include "libavcodec/avcodec.h"
- }
-
- #pragma comment(lib, "avcodec.lib")
- #pragma comment(lib, "avfilter.lib")
- #pragma comment(lib, "avformat.lib")
- #pragma comment(lib, "avutil.lib")
-
-
- int main()
- {
-
- char *padts = (char *)malloc(sizeof(char) * 7);
- int profile = 2;
- int freqIdx = 4;
- int chanCfg = 2;
- padts[0] = (char)0xFF;
- padts[1] = (char)0xF1;
- padts[2] = (char)(((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
- padts[6] = (char)0xFC;
-
- AVCodec *pCodec;
- AVCodecContext *pCodecCtx = NULL;
- int i, ret, got_output;
- FILE *fp_in;
- FILE *fp_out;
-
- AVFrame *pFrame;
- uint8_t* frame_buf;
- int size = 0;
-
- AVPacket pkt;
- int y_size;
- int framecnt = 0;
-
- char filename_in[] = "audio.pcm";
-
- AVCodecID codec_id = AV_CODEC_ID_AAC;
- char filename_out[] = "audio.aac";
-
- int framenum = 100000;
-
- avcodec_register_all();
-
- pCodec = avcodec_find_encoder(codec_id);
- if (!pCodec) {
- printf("Codec not found\n");
- return -1;
- }
-
- pCodecCtx = avcodec_alloc_context3(pCodec);
- if (!pCodecCtx) {
- printf("Could not allocate video codec context\n");
- return -1;
- }
-
- pCodecCtx->codec_id = codec_id;
- pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
- pCodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
- pCodecCtx->sample_rate = 44100;
-
-
- pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
- pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
- qDebug() << av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);
-
-
-
- if ((ret = avcodec_open2(pCodecCtx, pCodec, NULL)) < 0) {
- qDebug() << "avcodec_open2 error ----> " << ret;
-
- printf("Could not open codec\n");
- return -1;
- }
-
- pFrame = av_frame_alloc();
-
- pFrame->nb_samples = pCodecCtx->frame_size;
- pFrame->format = pCodecCtx->sample_fmt;
- pFrame->channels = 2;
-
- size = av_samples_get_buffer_size(NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 0);
- frame_buf = (uint8_t *)av_malloc(size);
-
-
-
-
-
-
- ret = avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt, (const uint8_t*)frame_buf, size, 0);
-
- if (ret < 0)
- {
- qDebug() << "avcodec_fill_audio_frame error ";
- return 0;
- }
-
-
- fp_in = fopen(filename_in, "rb");
- if (!fp_in) {
- printf("Could not open %s\n", filename_in);
- return -1;
- }
-
-
- fp_out = fopen(filename_out, "wb");
- if (!fp_out) {
- printf("Could not open %s\n", filename_out);
- return -1;
- }
-
-
- for (i = 0; i < framenum; i++) {
- av_init_packet(&pkt);
- pkt.data = NULL;
- pkt.size = 0;
-
- if (fread(frame_buf, 1, size, fp_in) <= 0) {
- printf("Failed to read raw data! \n");
- return -1;
- }
- else if (feof(fp_in)) {
- break;
- }
-
- pFrame->pts = i;
-
- ret = avcodec_encode_audio2(pCodecCtx, &pkt, pFrame, &got_output);
-
- if (ret < 0) {
- qDebug() << "error encoding";
- return -1;
- }
-
- if (pkt.data == NULL)
- {
- av_free_packet(&pkt);
- continue;
- }
-
- qDebug() << "got_ouput = " << got_output;
- if (got_output) {
- qDebug() << "Succeed to encode frame : " << framecnt << " size :" << pkt.size;
-
- framecnt++;
-
- padts[3] = (char)(((chanCfg & 3) << 6) + ((7 + pkt.size) >> 11));
- padts[4] = (char)(((7 + pkt.size) & 0x7FF) >> 3);
- padts[5] = (char)((((7 + pkt.size) & 7) << 5) + 0x1F);
- fwrite(padts, 7, 1, fp_out);
- fwrite(pkt.data, 1, pkt.size, fp_out);
-
- av_free_packet(&pkt);
- }
- }
-
- for (got_output = 1; got_output; i++) {
- ret = avcodec_encode_audio2(pCodecCtx, &pkt, NULL, &got_output);
- if (ret < 0) {
- printf("Error encoding frame\n");
- return -1;
- }
- if (got_output) {
- printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", pkt.size);
- padts[3] = (char)(((chanCfg & 3) << 6) + ((7 + pkt.size) >> 11));
- padts[4] = (char)(((7 + pkt.size) & 0x7FF) >> 3);
- padts[5] = (char)((((7 + pkt.size) & 7) << 5) + 0x1F);
-
- fwrite(padts, 7, 1, fp_out);
- fwrite(pkt.data, 1, pkt.size, fp_out);
- av_free_packet(&pkt);
- }
- }
-
- fclose(fp_out);
- avcodec_close(pCodecCtx);
- av_free(pCodecCtx);
- av_freep(&pFrame->data[0]);
- av_frame_free(&pFrame);
-
- return 0;
- }
【错误的结果】因为PCM格式不正确
使用上一篇文章产生的PCM来转换AAC是错误的。
由于上一篇文章保存的音频格式: L(一个采样点)R(一个采样点)LRLRLR..............
因为AVframe结构体data指针数组不一样指针表明指向不一样声道的数据,因此产生错误。
上述代码,data指向状况:
而FFmpeg编码PCM为AAC时,须要的是:
因此,我要让到读取一帧时,恰好让data[0]指向一个声道的数据,而data[1]指向另外一个声道的数据。
【解决方法】
由上述代码咱们知道AVFrame->nb_samples 默认是1024,因此每一帧一个声道读取的数据为:
- int length = AVFrame->nb_samples * av_get_byte_per_sample((AVSampleFormat)AVFrame->format);
这里也就是4096字节。
因此写PCM文件时,应该是:
【FFmpeg(2016)】视频文件分离器(demuxing)——H264&PCM
写PCM文件的代码:
-
-
-
-
- int k=0, h=0;
- for (int i = 0; i < 4; ++i)
- {
- if (i % 2 == 0)
- {
- int tmp = data_size / 4;
- for (int j = 0; j < tmp; j+=4,k++ )
- {
- data[i * 4096 + j+0] = (char)(l[k] & 0xff);
- data[i * 4096 + j+1] = (char)(l[k] >> 8 & 0xff);
- data[i * 4096 + j+2] = (char)(l[k] >> 16 & 0xff);
- data[i * 4096 + j+3] = (char)(l[k] >> 24 & 0xff);
- }
- }
- else
- {
- int tmp = data_size / 4;
- for (int j = 0; j < tmp; j += 4,h++)
- {
- data[i * 4096 + j+0] = (char)(r[h] & 0xff);
- data[i * 4096 + j+1] = (char)(r[h] >> 8 & 0xff);
- data[i * 4096 + j+2] = (char)(r[h] >> 16 & 0xff);
- data[i * 4096 + j+3] = (char)(r[h] >> 24 & 0xff);
- }
- }
- }
【Planar】这是FFmpeg的一个新概念,貌似在2014版ffmpeg尚未这个概念
Planar便是:平面
官方对AVSampleFormat的定义以下:
- enum AVSampleFormat {
- AV_SAMPLE_FMT_NONE = -1,
- AV_SAMPLE_FMT_U8,
- AV_SAMPLE_FMT_S16,
- AV_SAMPLE_FMT_S32,
- AV_SAMPLE_FMT_FLT,
- AV_SAMPLE_FMT_DBL,
-
- AV_SAMPLE_FMT_U8P,
- AV_SAMPLE_FMT_S16P,
- AV_SAMPLE_FMT_S32P,
- AV_SAMPLE_FMT_FLTP,
- AV_SAMPLE_FMT_DBLP,
- AV_SAMPLE_FMT_S64,
- AV_SAMPLE_FMT_S64P,
-
- AV_SAMPLE_FMT_NB
- };
后面有P的就表明是平面类型,所谓平面,便是音频数据再也不是如此存储:
L(一个采样点)R(一个采样点)LRLRLRLR...............
对AVFrame而言,应该是data[0],data[1]分别指向左右声道数据,这就是平面的概念。(能够类比视频解码时的YUV存储方式)
L(一帧)R(一帧)LRLRLR............................
打开AAC编码器,能够看到只支持AV_SAMPLE_FMT_FLTP类型:
- AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
- AVCodecContext *codec_ctx ;
- avcodec_open2(codec_ctx, codec);
因此在编码AAC前,必须先却倒存储格式是正确的(虽然播放PCM时有点问题)。
【关于平面概念】
【FFmpeg(2016)】SwrContext重采样结构体