【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

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要把音频解码后的AVFrame保存为PCM格式音频文件,你可以使用FFmpeg提供的API进行处理。具体步骤如下: 1. 打开音频文件并获取音频流信息: ``` AVFormatContext *formatCtx = avformat_alloc_context(); if (avformat_open_input(&formatCtx, inputPath, NULL, NULL) != 0) { // 打开音频文件失败 return; } if (avformat_find_stream_info(formatCtx, NULL) < 0) { // 获取音频流信息失败 return; } int audioStreamIndex = -1; for (int i = 0; i < formatCtx->nb_streams; i++) { if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audioStreamIndex = i; break; } } if (audioStreamIndex == -1) { // 找不到音频流 return; } AVCodecParameters *audioCodecParams = formatCtx->streams[audioStreamIndex]->codecpar; ``` 2. 获取音频解码器并打开解码器: ``` AVCodec *audioCodec = avcodec_find_decoder(audioCodecParams->codec_id); AVCodecContext *audioCodecCtx = avcodec_alloc_context3(audioCodec); avcodec_parameters_to_context(audioCodecCtx, audioCodecParams); if (avcodec_open2(audioCodecCtx, audioCodec, NULL) < 0) { // 打开音频解码器失败 return; } ``` 3. 循环读取音频帧并解码: ``` AVPacket pkt; av_init_packet(&pkt); AVFrame *frame = av_frame_alloc(); while (av_read_frame(formatCtx, &pkt) >= 0) { if (pkt.stream_index == audioStreamIndex) { int ret = avcodec_send_packet(audioCodecCtx, &pkt); if (ret < 0) { // 发送音频数据包到解码器失败 break; } while (ret >= 0) { ret = avcodec_receive_frame(audioCodecCtx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 无法从解码器中接收更多音频帧 break; } else if (ret < 0) { // 解码音频帧失败 break; } // 将音频帧保存为PCM格式的文件 // 这里省略将AVFrame保存为PCM格式文件的代码 } } av_packet_unref(&pkt); } av_frame_free(&frame); ``` 4. 关闭解码器和音频文件: ``` avcodec_free_context(&audioCodecCtx); avformat_close_input(&formatCtx); ``` 在将AVFrame保存为PCM格式文件时,你需要将AVFrame中的音频数据读取出来,并将其保存为PCM格式的文件。具体的方法可以参考FFmpeg的文档和示例代码。另外,需要注意的是,在将音频帧保存为PCM格式的文件时,你还需要指定音频的采样率、采样格式、声道布局等参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值