本文共 3246 字,大约阅读时间需要 10 分钟。
FFmpeg音频解码后输出的为PCM数据,PCM中的声音数据没有被压缩。
FFmpeg中音视频数据基本上都有Packed和Planar两种存储方式,对于双声道音频来说,Packed方式为两个声道的数据交错存储,交织在一起;Planar方式为两个声道分开存储,也就是平铺分开。假设一个L/R为一个采样点的话(一个采样点可能是8位16位32位等),可以这么表示: Packed: L R L R L R L R Planar: L L L L R R R R FFmpeg音频解码后的数据是存放在AVFrame frame结构中的,如果是Packed格式的话,所有的音频数据都放在frame.data[0]结构中;如果是Planar格式的话,不同声道的数据分别放在frame.data[0]和frame.data[1]中。 下面为FFmpeg音频采样格式,所有的Planar格式后面都有字母P标识。enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_S64, ///< signed 64 bits AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically};
不同的格式的输入音频解码后输出的音频采样格式不是固定的,最常见的音频格式有AAC和MP3两种,我测试中,其中AAC解码输出的数据为浮点型的 AV_SAMPLE_FMT_FLTP 格式,MP3解码输出的数据为 AV_SAMPLE_FMT_S16P 格式(使用的mp3文件为16位深)。具体采样格式可以查看解码后的AVFrame中的format成员或解码器的AVCodecContext中的sample_fmt成员。
这里AAC和MP3音频解码后的数据都是Planar模式的,两个声道的声音数据分别存在frame.data[0]和frame.data[1]中,多声道音频可能还会使用data[2] data[3]等。需要注意的是: 我们刚分析的Planar和Packed模式是ffmpeg内部存储模式,我们实际使用的音频文件都是LRLR左右声道交替存储的,设想如果音频文件3MB大小的话,不可能前面1.5MB存左声道,后面1.5MB存右声道。 Planar或者Packed模式直接影响到保存文件时写文件的操作,所以操作数据的时候一定要先检测音频采样格式。下面以Planar格式来演示如何保存音频文件。// 前面代码读音频文件,初始化FFmpeg并打开了AVCodecContext// 下面代码进行解码和保存文件bool AudioDecoder::readFrameProc(){ FILE *fd = fopen("out.pcm", "wb"); AVPacket packet; //av_init_packet(&packet); AVFrame *frame = av_frame_alloc(); // 读取一个帧packet的音频数据 while (int num = av_read_frame(mFormatCtx, &packet) >= 0) { // 解码(发送一个packet,获取到的frame就是解码后的数据了),这是FFmpeg 3的新解码函数 avcodec_send_packet(mCodecCtx, &packet); int ret = avcodec_receive_frame(mCodecCtx, frame); if (!ret) { // 获取一个采样点字节数,比如16位采样点值为2字节 int data_size = av_get_bytes_per_sample(mCodecCtx->sample_fmt); // frame->nb_samples为这个frame中一个声道的采样点的个数 for (int i = 0; i < frame->nb_samples; i++) for (int ch = 0; ch < mCodecCtx->channels; ch++) fwrite(frame->data[ch] + data_size*i, 1, data_size, fd); } av_packet_unref(&packet); } av_frame_free(&frame); fclose(fd); return false;}
上面这段代码frame为解码后的数据,因为是Planar模式的数据,所以写文件的时候,存储每采样点的时候两个声道LRLRLR这样交错写入文件,相当于把AV_SAMPLE_FMT_S16P 采样格式保存为 不带字母P的AV_SAMPLE_FMT_S16 采样格式,。
举个例子,假如这个frame中有20个采样点(nb_samples=20),每个采样点为2字节(16位深,每个声道的一个采样点2字节)。左声道数据为data[0][40]数组,右声道数据为data[1][40]数组。写文件的时候依次写入 data[0][0],data[0][1] – data[1][0],data[1][1] – data[0][2],data[0][3] – data[1][2]-data[1][3]。保存的PCM文件可以使用ffplay指定参数进行播放:
ffplay -f 格式名 -ac 声道数 -ar 采样率 文件名
本文没有讨论大端存储还是小端存储的问题,因为我们通用的PC机默认都是小端存储的。比如我使用的MP3解码为AV_SAMPLE_FMT_S16P格式的,对应解码器格式名为s16le
,ffplay播放指令为:
ffplay -f s16le -ac 2 -ar 44100 out.pcm
AAC解码为AV_SAMPLE_FMT_FLTP格式,对应格式名为f32le
。 转载地址:http://camci.baihongyu.com/