FFmpeg 混音学习【一】FFmpeg aac音频混流学习及问题记录

FFmpeg学习记录 专栏收录该内容
4 篇文章 1 订阅

主要学习的是ffmpeg官方demo里的doc/examples/filtering_audio.c

ffmpeg混流的主要流程

  • 初始化filter
  • 读取aac数据,解码后将数据压入filter buffer中
  • 从buffer中取出数据编码成aac数据

1、初始化filter

首先定义需要使用到的变量:

 static const char *filter_desc =
    "aresample=48000,aformat=sample_fmts=fltp:channel_layouts=stereo";
  /** An instance of a filter */
  AVFilterContext *buffersink_ctx; //sink filter实例(输出)
  AVFilterContext *buffersrc_ctx;  //src filter实例(输入)
  AVFilterGraph *filter_graph;
  char args[512];  
  int ret = 0;

  /**
   * Filter definition. This defines the pads a filter contains, and all the
   * callback functions used to interact with the filter.
   */
  const AVFilter *abuffersrc = avfilter_get_by_name("abuffer");  //有多少个输入就创建多少个,当输入输出为一的时候相当于直接重采样
  const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); //有多少个输出就创建多少个

  /**
   * A linked-list of the inputs/outputs of the filter chain.
   *
   * This is mainly useful for avfilter_graph_parse() / avfilter_graph_parse2(),
   * where it is used to communicate open (unlinked) inputs and outputs from and
   * to the caller.
   * This struct specifies, per each not connected pad contained in the graph,
   * the filter context and the pad index required for establishing a link.
   */

  AVFilterInOut *outputs = avfilter_inout_alloc(); //输入filter chain
  AVFilterInOut *inputs = avfilter_inout_alloc();  //输出filter chain
  static const enum AVSampleFormat out_sample_fmts[] = {
      AV_SAMPLE_FMT_FLTP, (enum AVSampleFormat) - 1};  //输出sample_fmt
  static const int64_t out_channel_layouts[] = {3, -1};//输出channel_layouts
  static const int out_sample_rates[] = {48000, -1};   //输出sample_rates
 
  const AVFilterLink *outlink;
  AVRational time_base = fmt_ctx->streams[audio_stream_index]->time_base;

  filter_graph = avfilter_graph_alloc();
  if (!outputs || !inputs || !filter_graph) {
    ret = AVERROR(ENOMEM);
    goto end;
  }

然后初始化源数据buffer和输出数据buffer,并设置输出参数

  /* buffer audio source: the decoded frames from the decoder will be inserted
   * here. */
  if (!dec_ctx->channel_layout)
    dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels);
  snprintf(
      args, sizeof(args),
      "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64,
      time_base.num, time_base.den, dec_ctx->sample_rate,
      av_get_sample_fmt_name(dec_ctx->sample_fmt), dec_ctx->channel_layout);

  ret = avfilter_graph_create_filter(&buffersrc_ctx, abuffersrc, "in", args,
                                     NULL, filter_graph);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n");
    goto end;
  }
 /* buffer audio sink: to terminate the filter chain. */
  ret = avfilter_graph_create_filter(&buffersink_ctx, abuffersink, "out", NULL,
                                     NULL, filter_graph);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n");
    goto end;
  }

  ret = av_opt_set_int_list(buffersink_ctx, "sample_fmts", out_sample_fmts, -1,
                            AV_OPT_SEARCH_CHILDREN);//设置输出sample_fmts
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n");
    goto end;
  }

  ret = av_opt_set_int_list(buffersink_ctx, "channel_layouts", //设置输出channel_layouts
                            out_channel_layouts, -1, AV_OPT_SEARCH_CHILDREN);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n");
    goto end;
  }

  ret = av_opt_set_int_list(buffersink_ctx, "sample_rates", out_sample_rates,
                            -1, AV_OPT_SEARCH_CHILDREN);//设置输出sample_rates
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n");
    goto end;
  }

最后链接各输入、各输出,初始化filter_graph

  /*
   * Set the endpoints for the filter graph. The filter_graph will
   * be linked to the graph described by filters_descr.
   */

  /*
   * The buffer source output must be connected to the input pad of
   * the first filter described by filters_descr; since the first
   * filter input label is not specified, it is set to "in" by
   * default.
   */
  outputs->name = av_strdup("in");
  outputs->filter_ctx = buffersrc_ctx;
  outputs->pad_idx = 0;
  outputs->next = NULL;

  //如果输入的源不止一个需要连接起来,输出同理,例如有两个输入时如下:
  //outputs1->name = av_strdup("in0");
  //outputs1->filter_ctx = buffersrc_ctx1;
  //outputs1->pad_idx = 0;
  //outputs1->next = outputs2; //此处指向下一个输入

  //outputs2->name = av_strdup("in1");
  //outputs2->filter_ctx = buffersrc_ctx2;
  //outputs2->pad_idx = 0;
  //outputs2->next = NULL;

  /*
   * The buffer sink input must be connected to the output pad of
   * the last filter described by filters_descr; since the last
   * filter output label is not specified, it is set to "out" by
   * default.
   */
  inputs->name = av_strdup("out");
  inputs->filter_ctx = buffersink_ctx;
  inputs->pad_idx = 0;
  inputs->next = NULL;

  if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, &inputs,
                                      &outputs, NULL)) < 0)
    goto end;

  if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end;

2、将数据压入filter buffer中,关于函数av_buffersrc_add_frame_flags的详细用法可见博客 https://blog.csdn.net/Martin_chen2/article/details/99641498

/* push the audio data from decoded frame into the filtergraph */
if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, 0) < 0) {
  av_log(NULL, AV_LOG_ERROR, "Error while feeding the audio filtergraph\n");
  break;
} //注意不同的流对应不同的buffersrc_ctx

3、从buffer中取出数据

/* pull filtered audio from the filtergraph */
while (1) {
  ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
  if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
  if (ret < 0) goto end;
  print_frame(filt_frame); //此处处理得到的数据
  av_frame_unref(filt_frame);
}

遇到的问题:

  • 官网的demo运行后,生成的文件都不能用,具体的原因没找到,但发现是读取文件数据的问题。解决不使用av_read_frame(fmt_ctx, &packet)的方式从fmt_ctx读取数据,而是通过解析adts头一帧一帧的读取数据。

  • 混音后的文件含有杂音。原因是使用了同一个解码AVCodecContext解码不同的流,使用不同解码AVCodecContext后该问题解决。背后具体的原因应该是解码AVCodecContext会记住当前的状态,输入不同的流就会产生和混乱。

  • 有时初始化的时候会出现错误:Output pad “default” with type audio of the filter instance “in0” of abuffer not connected to any destination
    在这里插入图片描述
    这说明设置的filter_descr和链接时的名字不对应,改成一致即可如下两图:
    在这里插入图片描述
    在这里插入图片描述

一些发现

  • filter过程自带重采样,也就是从输出buffer中取出frame时已经是重采样后的,无需再次resample,这个重采样的参数可在初始化filter时设置
  • 输入的几路流中如果当前没有帧,那么可以非第一个buffer里放入NULL,这样或许可以实现一个混流n人的filter同时可以混流m(m < n)人,是否可应用还需进一步验证
  • 添加NULL已经验证不行,但是可以通过构造静默帧的方式解决,如下:
int gen_silence_frame(AVFrame **in_frame) {
  int ret;
  silence_frame = av_frame_alloc();
  if (!silence_frame) {
    return NULL;
  }

  silence_frame->sample_rate = DEFAULT_SR;
  silence_frame->format = DEFAULT_SF;
  silence_frame->channel_layout =
      av_get_default_channel_layout(DEFAULT_CHANNEL);
  silence_frame->channels = DEFAULT_CHANNEL;
  silence_frame->nb_samples =
      SILENCE_BUFF;  //默认一帧数据大小为1024,可通过输入流里的一帧
                     // frame.nb_samples查看

  ret = av_frame_get_buffer(silence_frame, 0);

  if (ret < 0) {
    av_frame_unref(silence_frame);
    *in_frame = NULL;
    return -1;
  }
  av_samples_set_silence(silence_frame->data, 0, silence_frame->nb_samples,
                         silence_frame->channels,
                         (enum AVSampleFormat)silence_frame->format);

  *in_frame = silence_frame;

  return 0;
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值