在看这篇文章前,我推荐你先看看我的另外两篇文章:
【FFmpeg(2016)】视频文件分离器(demuxing)——H264&PCM
【FFmpeg(2016)】PCM编码AAC
本文章主要介绍SwrContext的用处 和 使用方法,到底什么是重采样,为什么需要重采样,希望你耐心看完。
重采样,也就是对已得到的数据进行重新的采样;比如,原先的PCM音频数据是2 个声道,44100采样率,32bit 单精度型(sample format采样格式)。
那么通过重采样,我们改变它的声道数、采样率和采样格式。
对于改变声道数 和 采样率 ,在这里不着重探讨,主要理解 sample format.
官方对sample format进行了定义,如下:
enum AVSampleFormat { AV_SAMPLE_FMT_NOnE= -1, AV_SAMPLE_FMT_U8, ///
枚举类型带P的代表非平面类型,带P的代表平面类型。
【FFmpeg平面的概念】
所谓平面与非平面是针对在编解码的时候,数据存储的方式来判定的,那怎么判断呢?
比如,我有一个PCM文件是这样的(L代表左声道,R代表右声道):
L(一个采样点)R(一个采样点)LRLRLRLRLRLRLRLR........................................
注意,是一个采样点,如果音频sample format 是AV_SAMPLE_FMT_FLT,那就说明一个采样点是32位。
我用ffplay播放这个音频时:
图.1
-f f32le 代表这个文件sample format 是32bit 的,也就是AV_SAMPLE_FMT_FLT,
-ar 是采样率
-ac 是声道数
上面说到我这个PCM文件,对于FFmpeg来说,是AV_SAMPLE_FMT_FLT格式的,它的存储方式就是:
L(一个采样点)R(一个采样点)LRLRLRLRLRLRLRLR........................................
也就是说,对于FFmpeg来说,这个PCM文件是非平面的。
【planar“平面”概念】
我以我这篇文章的代码为例:
【FFmpeg(2016)】PCM编码AAC
如果我直接:
FILE *fp = fopen("a.pcm", "rb"); fread(....);
从L(一个采样点)R(一个采样点)LRLRLRLRLRLRLRLR.....................格式的PCM文件读取一帧,那么AVFrame结构体的data成员状态如下:data是存储着准备要被编码的音频数据,data是一个二维指针
图.2
如果直接对这样的data数组进行AAC编码,你会发现得到的AAC文件播放完成变声了,还很嚓。
到底是什么问题?
这就涉及到planar的概念了
自FFmpeg提及平面概念后,要求的就是每个data指针指向各自声道的数据,也就说,要这样:
图. 3
此时,就是所谓的平面,二维数组的第一维存储了左声道,第二维存储了右声道。
如果对这样的data进行AAC编码,那么得到的数据就是正确的。
这里还有另外一个问题,data里的一个一维数组应该多长,这就要看AVCodecContext的成员frame_size,它代表每读取一帧时读取的采样个数,默认是1024.这种情况下,进行AAC编码前的数据存储情况应该是:
图 .4
【代码】使用SwrContext,输入文件为L(一个采样点)R(一个采样点)LRLRLRLRLRLRLRLR.....................格式的PCM文件
思想是:读入PCM时,data情况是图.2,此时使用SwrContext将其转换成图.4, 然后在进行AAC编码 (注意,图2总共也是8192字节)
extern "C" { #include "libavformat/avformat.h" #include "libavutil/avutil.h" #include "libavcodec/avcodec.h" #include "libswresample/swresample.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, "avdevice.lib") #pragma comment(lib, "avformat.lib") #pragma comment(lib, "avutil.lib") #pragma comment(lib, "swscale.lib") #pragma comment(lib, "swresample.lib") /* PCM转AAC */ int main() { /* ADTS头 */ char *padts = (char *)malloc(sizeof(char) * 7); int profile = 2; //AAC LC int freqIdx = 4; //44.1KHz int chanCfg = 2; //MPEG-4 Audio Channel Configuration. 1 Channel front-center,channel_layout.h padts[0] = (char)0xFF; // 11111111 = syncword padts[1] = (char)0xF1; // 1111 1 00 1 = syncword MPEG-2 Layer CRC padts[2] = (char)(((profile - 1) <<6) + (freqIdx <<2) + (chanCfg >> 2)); padts[6] = (char)0xFC; SwrContext *swr_ctx = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame; AVPacket pkt; AVCodecID codec_id = AV_CODEC_ID_AAC; FILE *fp_in; FILE *fp_out; char filename_in[] = "audio.pcm"; char filename_out[] = "audio.aac"; uint8_t **convert_data; //存储转换后的数据,再编码AAC int i, ret, got_output; uint8_t* frame_buf; int size = 0; int y_size; int framecnt = 0; 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() <<"pCodecCtx->bit_rate ----------------> " <bit_rate; qDebug() <<"pCodecCtx->bit_rate ----------------> " < bit_rate; qDebug() < channel_layout); if ((ret = avcodec_open2(pCodecCtx, pCodec, NULL)) <0) { qDebug() <<"avcodec_open2 error ----> " < nb_samples = pCodecCtx->frame_size; //1024 pFrame->format = pCodecCtx->sample_fmt; pFrame->channels = 2; qDebug() <<"frame_size(set pFrame->nb_samples) -------------> " < frame_size; /* 由AV_SAMPLE_FMT_FLT转为AV_SAMPLE_FMT_FLTP */ swr_ctx = swr_alloc_set_opts( NULL, av_get_default_channel_layout(pCodecCtx->channels), pCodecCtx->sample_fmt, //在编码前,我希望的采样格式 pCodecCtx->sample_rate, av_get_default_channel_layout(pCodecCtx->channels), AV_SAMPLE_FMT_FLT, //PCM源文件的采样格式 pCodecCtx->sample_rate, 0, NULL); swr_init(swr_ctx); /* 分配空间 */ convert_data = (uint8_t**)calloc(pCodecCtx->channels, sizeof(*convert_data)); av_samples_alloc(convert_data, NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 0); size = av_samples_get_buffer_size(NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 0); frame_buf = (uint8_t *)av_malloc(size); /* 此时data[0],data[1]分别指向frame_buf数组起始、中间地址 */ 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; } //Input raw data fp_in = fopen(filename_in, "rb"); if (!fp_in) { printf("Could not open %s\n", filename_in); return -1; } //Output bitstream fp_out = fopen(filename_out, "wb"); if (!fp_out) { printf("Could not open %s\n", filename_out); return -1; } //Encode for (i = 0; i frame_size, (const uint8_t**)pFrame->data, pCodecCtx->frame_size); /* 将转换后的数据复制给pFrame */ int length = pCodecCtx->frame_size * av_get_bytes_per_sample(pCodecCtx->sample_fmt); for (int k = 0; k <2; ++k) for (int j = 0; j data[k][j] = convert_data[k][j]; } pFrame->pts = i; qDebug() <<"frame->nb_samples -----> " < nb_samples; qDebug() <<"size ------------------> " < linesize[0] ----> " < linesize[0]; 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 = " < 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); } } //Flush Encoder 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); av_freep(&convert_data[0]); free(convert_data); ////////////////////////////////////////////////////////////////////////////////// return 0; }
上面swr_convert函数执行后,convert_data情况是图.4
AVFrame情况是图.2,所以接下来我将convert_data直接复制给data。
此时编码出来的AAC就是正确的了。