【FFmpeg 3.x API应用三】音频解码

摘要

这篇文章介绍音频解码,示例程序是读取一个mp3格式或者aac格式的音频文件,解码输出为未压缩的pcm音频文件。

初始化FFmepg和FormatContext

和视频解码一样,先使用av_register_all注册所有相关组件,然后使用avformat_open_input打开指定的媒体文件,并使用avformat_find_stream_info获取媒体流相关信息,把这些格式信息映射到AVFormatContext *mFormatCtx这个结构中。
使用函数av_dump_format可以从控制台输出媒体文件相关信息。

bool AudioDecoding::init(const char * audioFile)
{
    av_register_all();

    if ((avformat_open_input(&mFormatCtx, audioFile, 0, 0)) < 0) {
        printf("Failed to open input file\n");
        return true;
    }

    if ((avformat_find_stream_info(mFormatCtx, 0)) < 0) {
        printf("Failed to retrieve input stream information\n");
        return true;
    }

    av_dump_format(mFormatCtx, 0, audioFile, 0);
    return false;
}

配置编解码器CodecContext

  1. 根据输入文件格式查找相应的解码器,因为使用的是aac/mp3格式的音频文件,只有一个音频流,所以就不用查找流Index了,直接使用streams[0]就可以了。
  2. 根据解码器申请CodecContext。
  3. 填充CodecContext信息
  4. 完成CodecContext初始化。
bool AudioDecoding::initCodecContext()
{
    // 根据id查找对应解码器
    AVCodec *dec = avcodec_find_decoder(mFormatCtx->streams[0]->codecpar->codec_id);
    if (!dec) {
        printf("Failed to find codec!\n");
        return true;
    }

    // 申请CodecContext
    if (!(mCodecCtx = avcodec_alloc_context3(dec))) {
        printf("Failed to allocate the codec context\n");
        return true;
    }

    // 填充CodecContext信息
    if (avcodec_parameters_to_context(mCodecCtx, mFormatCtx->streams[0]->codecpar) < 0) {
        printf("Failed to copy codec parameters to decoder context!\n");
        return true;
    }

    // 初始化AVCodecContext结构
    if (avcodec_open2(mCodecCtx, dec, NULL) < 0) {
        printf("Failed to open codec\n");
        return true;
    }
    return false;
}

音频解码,保存pcm文件

  1. 循环读取输入文件的数据,直接读取完毕。
  2. 依次处理各个读取到的packet,进行解码。
  3. 解码出来的是未压缩的音频数据,把这些音频数据写入文件。

需要注意的是,输入音频格式不同时,解码后输出的pcm音频数据格式也会不同,我们这里没有进行重采样处理。本程序当使用echo.mp3(sample_fmt = AV_SAMPLE_FMT_S16P)作为输入文件时,,一个采样点是16bit,输出文件取名为out_s16le.pcm;使用echo.aac(sample_fmt = AV_SAMPLE_FMT_FLTP)作为输入文件,一个采样点是32bit,输出文件取名为out_f32le.pcm,以便于播放。
关于音频解码输出PCM数据的分析,请参考另一篇文章《音频解码输出PCM格式数据分析》。

bool AudioDecoding::readFrameProc()
{
    // 使用echo.mp3作为输入文件,文件名为s16le.pcm
    // 使用echo.aac作为输入文件,文件名则为f32le.pcm
    FILE *fd = fopen("../assets/out_s16le.pcm", "wb");
    if (!fd) {
        printf("Failed to open input file\n");
        return true;
    }

    AVPacket packet;
    av_init_packet(&packet);

    AVFrame *frame = av_frame_alloc();

    //循环读取音频数据packet
    while (int num = av_read_frame(mFormatCtx, &packet) >= 0) {

        //音频解码,同视频解码一样都使用这对函数
        avcodec_send_packet(mCodecCtx, &packet);
        int ret = avcodec_receive_frame(mCodecCtx, frame);
        if (!ret) {

            // number of bytes per sample, 16bit is 2 Bytes
            //根据格式查询音频采样点深度,一个采样点使用多少字节表示。
            //本文使用的mp3文件(sample_fmt = AV_SAMPLE_FMT_S16P),一个采样点是16bit,则返回字节数为2;
            //使用的aac文件(sample_fmt = AV_SAMPLE_FMT_FLTP),一个采样点是32bit,则返回字节数4。
            int sampleBytes = av_get_bytes_per_sample(mCodecCtx->sample_fmt);

            // 使用Planar格式写音频文件,nb_samples为这个frame中一个声道的采样点的个数,channels为声道个数
            for (int i = 0; i < frame->nb_samples; i++)
                for (int ch = 0; ch < mCodecCtx->channels; ch++)
                    fwrite(frame->data[ch] + sampleBytes*i, 1, sampleBytes, fd);

        }
        av_packet_unref(&packet);
    }

    av_frame_free(&frame);

    fclose(fd);

    return false;
}

释放系统资源

AudioDecoding::~AudioDecoding()
{
    avcodec_free_context(&mCodecCtx);
    avformat_close_input(&mFormatCtx);
}

PCM音频文件播放方法

上面说过使用MP3输入时,输出文件名为out_s16le.pcm,使用aac格式文件作为输入时,输出文件名为out_f32le.pcm。可以使用ffplay播放pcm文件,这两个文件对应的播放命令为:

ffplay -f s16le -ac 2 -ar 44100 out_s16le.pcm
ffplay -f f32le -ac 2 -ar 44100 out_f32le.pcm

如果播放不正常,很可能是播放参数不正确。

示例程序代码

上述示例的完整代码可以从Github下载: https://github.com/lmshao/FFmpeg-Basic

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章