文章描述: 本文主要描述了通过ffmpeg接口对音频进行编解码的例程,及主要函数详细说明。
操作系统: Debian8

/* 
 * 功能描述: 打开指定音频文件,转换成PCM数据格式,并保存至文件中
 *           支持多种格式文件输入mp3,m4a等
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libavcodec/avcodec.h>       // 解码器
#include <libavformat/avformat.h>     // 封装格式
#include <libswresample/swresample.h> // 重采样格式转换

int main(int argc, char **argv)
{
  // 1.注册组件 -----------------------------------------------------
  av_register_all();
  // 封装格式上下文
  AVFormatContext *pFormatCtx = avformat_alloc_context();
  // 2.打开输入音频文件 ---------------------------------------------
  if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0)
  {
    printf("打开输入音频文件失败.\r\n");
    return -1;
  }
  // 3.获取音频信息 -------------------------------------------------
  if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
  {
    printf("获取音频信息失败.\r\n");
    return -2;
  }
  // 音频解码: 找到对应的AVStream所在的pFormatCtx->streams的索引位置
  int i;
  int audio_stream_idx = -1;
  for (i = 0; i < pFormatCtx->nb_streams; i++)
  {
    // 根据类型判断是否是音频流
    if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
    {
      printf("查找到音频流\r\n");
      audio_stream_idx = i;
    }
    // 根据类型判断是否是视频流
    else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
      printf("查找到视频流\r\n");
    }
    else
    {
      printf("未知的编码类型: %d\r\n", pFormatCtx->streams[i]->codec->codec_type);
    }  
  }

  // 4.获取解码器 ---------------------------------------------------
  // 通过stream获取解码器上下文
  AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_idx]->codec;
  // 通过编解码id获取解码器
  AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
  if (pCodec == NULL)
  {
    printf("未找到对应解码器.\r\n");
    return -3;
  }

  // 5.打开解码器 ---------------------------------------------------
  if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0)
  {
    printf("解码器无法打开");
    return -4;
  }

  // 申请数据内存:编码数据(原始数据)
  AVPacket *packet = av_malloc(sizeof(AVPacket));
  // 申请数据内存:解码数据
  AVFrame *frame = av_frame_alloc();

  // 设置解码器选项--------------------------------------------------
  SwrContext *swrCtx = swr_alloc();

  // 输出的采样格式 16bit PCM
  enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
  // 输出的采样率
  int out_sample_rate = 44100;
  // 输出的声道布局
  uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO; // (立体声)

  swr_alloc_set_opts(swrCtx,
                     out_ch_layout,            // 输出的声道布局
                     out_sample_fmt,           // 输出的采样格式 16bit PCM
                     out_sample_rate,          // 输出的采样率
                     pCodeCtx->channel_layout, // 输入的声道布局
                     pCodeCtx->sample_fmt,     // 输入的采样格式
                     pCodeCtx->sample_rate,    // 输入的采样率
                     0,                        // 日志偏移 log_offset
                     NULL);                    // 日志指针 log_ctx

  swr_init(swrCtx); // 初始化解码器
  // ----------------------------------------------------------------
  // 通过声道布局获取声道数
  int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
  int ret;
  int got_frame;

  // 申请单个采样点PCM数据所需要的内存(2*2*44100)
  uint8_t *out_buffer = (uint8_t *)av_malloc(2 * 2 * 44100);
  if (out_buffer == NULL)
  {
    exit(1);
  }
  FILE *fp_pcm = fopen("out.pcm", "wb");

  // 6.一帧一帧读取压缩的音频数据AVPacket
  while (av_read_frame(pFormatCtx, packet) >= 0)
  {
    if (packet->stream_index == audio_stream_idx)
    {
      // 解码:AVPacket->AVFrame
      ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
      if (ret < 0)
      {
        printf("解码出错.\r\n");
      }

      // 获取有效帧
      if (got_frame)
      {
        // 由于解码器输出的数据与PCM播放的格式不一致,所以需要转换一下
        swr_convert(swrCtx,
                    &out_buffer,                   // 输出数据缓冲区
                    2 * 2 * 44100,                 // 输出缓冲区大小
                    (const uint8_t **)frame->data, // 输入数据缓冲区
                    frame->nb_samples);            // 每通道的采样数(帧数)

        // 通过帧数,音频格式,通道数计算每次转换后输出的字节数
        int out_buffer_size = av_samples_get_buffer_size(NULL,              // 传出值,与返回值相同
                                                         out_channel_nb,    // 通道数
                                                         frame->nb_samples, // 采样数(帧数)
                                                         out_sample_fmt,    // 音频格式
                                                         1);                // 对齐模式(1 - 紧凑,不对齐)

        // 转码后的数据写入文件(也可直接写入PCM进行播放)
        fwrite(out_buffer, 1, out_buffer_size, fp_pcm);
      }
    }
    av_free_packet(packet);
  }

  // 释放动态申请的内存
  av_frame_free(&frame);
  av_free(out_buffer);
  swr_free(&swrCtx);

  fclose(fp_pcm);                    // 关闭文件
  avcodec_close(pCodeCtx);           // 关闭解码器
  avformat_close_input(&pFormatCtx); // 关闭输入文件
  return 0;
}

编译选项:

-lavformat -lavcodec -lavutil -lswresample

转换后的文件测试:
由于没有文件头来指定音频格式,所以需要-f选项指定音频格式:

$ aplay -f cd out.pcm

数据结构及函数说明:

/*
 * 功能描述: 打开文件,解析文件格式
 * 返 回 值: 0 - 成功,负值 - 失败,发生错误
 */
int avformat_open_input(AVFormatContext **ps,    // 封装句柄
                        const char *url,         // 文件路径
                        AVInputFormat *fmt,      // 强制指定格式,一般为NULL
                        AVDictionary **options); // 一般设置为NULL