【FFmpeg杂记】音频解码输出PCM格式数据分析

  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格式来演示如何保存音频文件。

保存PCM格式数据到文件

// 前面代码读音频文件,初始化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音频文件

  保存的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

你可能感兴趣的:(ffmpeg,FFmpeg杂记,ffmpeg,pcm,音频解码)