如何通过FFmpeg源码将5.1/7.1音频拆分为6路单声道输出?
用FFmpeg源码实现5.1声道分离(对应channelsplit命令行功能)
我已经能通过命令行实现5.1声道的分离,命令如下:
ffmpeg -i in.wav \ -filter_complex "channelsplit=channel_layout=5.1[FL][FR][FC][LFE][BL][BR]" \ -map "[FL]" front_left.wav \ -map "[FR]" front_right.wav \ -map "[FC]" front_center.wav \ -map "[LFE]" lfe.wav \ -map "[BL]" back_left.wav \ -map "[BR]" back_right.wav
但不知道怎么直接用FFmpeg的C API实现同样的功能。我原本以为可以在设置编码器时给AVCodecContext或SwrContext配置参数,但没找到对应的选项;尝试过av_set_options_string(...)和av_opt_set(...)也没成功,应该是方法不对。需要一个基于FFmpeg源码的实现方案,最好是C语言的代码示例,其他语言的也可以。
核心思路:构建Filter Graph实现声道分离
命令行里的channelsplit本质是FFmpeg的滤镜,所以源码实现的核心是手动构建滤镜图,而不是修改编码器或重采样上下文的参数。步骤如下:
- 初始化FFmpeg组件,打开输入文件并获取音频流信息
- 创建滤镜图,包含三个关键部分:
buffer滤镜:作为输入,承接原始音频帧channelsplit滤镜:配置5.1声道布局,拆分出6个独立声道- 6个
buffersink滤镜:分别接收每个声道的输出帧
- 将滤镜图连接起来,处理每一帧数据
- 从每个
buffersink取出对应声道的帧,编码后写入输出文件
C语言代码示例
#include <libavformat/avformat.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavcodec/avcodec.h> #define OUTPUT_FILES_NUM 6 const char* output_filenames[OUTPUT_FILES_NUM] = { "front_left.wav", "front_right.wav", "front_center.wav", "lfe.wav", "back_left.wav", "back_right.wav" }; typedef struct { AVFormatContext *fmt_ctx; AVCodecContext *codec_ctx; AVStream *stream; } OutputContext; static int open_output_file(OutputContext *out_ctx, const char *filename, AVCodecContext *src_codec_ctx) { int ret; const AVCodec *codec; // 初始化输出格式上下文 if ((ret = avformat_alloc_output_context2(&out_ctx->fmt_ctx, NULL, NULL, filename)) < 0) { return ret; } // 找到对应的编码器 codec = avcodec_find_encoder(out_ctx->fmt_ctx->oformat->audio_codec); if (!codec) { return AVERROR_ENCODER_NOT_FOUND; } // 创建编码器上下文 out_ctx->stream = avformat_new_stream(out_ctx->fmt_ctx, NULL); if (!out_ctx->stream) { return AVERROR(ENOMEM); } out_ctx->codec_ctx = avcodec_alloc_context3(codec); if (!out_ctx->codec_ctx) { return AVERROR(ENOMEM); } // 配置编码器参数(单声道,和输入相同的采样率、格式) out_ctx->codec_ctx->codec_id = codec->id; out_ctx->codec_ctx->sample_fmt = src_codec_ctx->sample_fmt; out_ctx->codec_ctx->sample_rate = src_codec_ctx->sample_rate; out_ctx->codec_ctx->channel_layout = AV_CH_LAYOUT_MONO; out_ctx->codec_ctx->channels = 1; out_ctx->codec_ctx->bit_rate = src_codec_ctx->bit_rate; // 如果是MP4等格式,需要设置编码器参数到流 if (out_ctx->fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { out_ctx->codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // 打开编码器 if ((ret = avcodec_open2(out_ctx->codec_ctx, codec, NULL)) < 0) { return ret; } // 复制编码器参数到流 ret = avcodec_parameters_from_context(out_ctx->stream->codecpar, out_ctx->codec_ctx); if (ret < 0) { return ret; } // 打开输出文件 if (!(out_ctx->fmt_ctx->oformat->flags & AVFMT_NOFILE)) { if ((ret = avio_open(&out_ctx->fmt_ctx->pb, filename, AVIO_FLAG_WRITE)) < 0) { return ret; } } // 写入文件头 if ((ret = avformat_write_header(out_ctx->fmt_ctx, NULL)) < 0) { return ret; } return 0; } static int encode_write_frame(OutputContext *out_ctx, AVFrame *frame) { int ret; AVPacket pkt = {0}; av_init_packet(&pkt); // 发送帧到编码器 ret = avcodec_send_frame(out_ctx->codec_ctx, frame); if (ret < 0) { return ret; } // 接收编码后的数据包并写入文件 while (ret >= 0) { ret = avcodec_receive_packet(out_ctx->codec_ctx, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { return ret; } pkt.stream_index = out_ctx->stream->index; av_packet_rescale_ts(&pkt, out_ctx->codec_ctx->time_base, out_ctx->stream->time_base); ret = av_interleaved_write_frame(out_ctx->fmt_ctx, &pkt); av_packet_unref(&pkt); if (ret < 0) { return ret; } } return 0; } int main(int argc, char **argv) { int ret; AVFormatContext *fmt_ctx = NULL; AVCodecContext *codec_ctx = NULL; AVStream *audio_stream = NULL; int audio_stream_idx = -1; const AVCodec *codec = NULL; AVFrame *frame = NULL; // 滤镜相关 AVFilterGraph *filter_graph = NULL; AVFilterContext *buffersrc_ctx = NULL; AVFilterContext *buffersink_ctx[OUTPUT_FILES_NUM] = {NULL}; const AVFilter *buffersrc = avfilter_get_by_name("abuffer"); const AVFilter *buffersink = avfilter_get_by_name("abuffersink"); AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); char args[512]; enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE }; int64_t channel_layouts[] = { AV_CH_LAYOUT_MONO, -1 }; int sample_rates[] = { 0, -1 }; OutputContext out_ctx[OUTPUT_FILES_NUM] = { {0} }; if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; } // 初始化FFmpeg组件 av_register_all(); avfilter_register_all(); // 打开输入文件 if ((ret = avformat_open_input(&fmt_ctx, argv[1], NULL, NULL)) < 0) { fprintf(stderr, "Could not open input file: %s\n", av_err2str(ret)); goto end; } // 获取流信息 if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) { fprintf(stderr, "Could not find stream information: %s\n", av_err2str(ret)); goto end; } // 找到音频流 audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (audio_stream_idx < 0) { fprintf(stderr, "Could not find audio stream in input file\n"); goto end; } audio_stream = fmt_ctx->streams[audio_stream_idx]; codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { ret = AVERROR(ENOMEM); fprintf(stderr, "Could not allocate codec context: %s\n", av_err2str(ret)); goto end; } // 复制流参数到编码器上下文 ret = avcodec_parameters_to_context(codec_ctx, audio_stream->codecpar); if (ret < 0) { fprintf(stderr, "Could not copy codec parameters to context: %s\n", av_err2str(ret)); goto end; } // 打开解码器 if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0) { fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret)); goto end; } // 检查输入是否是5.1声道 if (codec_ctx->channel_layout != AV_CH_LAYOUT_5POINT1) { fprintf(stderr, "Input file is not 5.1 channel\n"); ret = AVERROR(EINVAL); goto end; } // 打开所有输出文件 for (int i = 0; i < OUTPUT_FILES_NUM; i++) { if ((ret = open_output_file(&out_ctx[i], output_filenames[i], codec_ctx)) < 0) { fprintf(stderr, "Could not open output file %s: %s\n", output_filenames[i], av_err2str(ret)); goto end; } } // 构建滤镜图 filter_graph = avfilter_graph_alloc(); if (!filter_graph || !outputs || !inputs) { ret = AVERROR(ENOMEM); fprintf(stderr, "Could not allocate filter graph or in/out: %s\n", av_err2str(ret)); goto end; } // 配置输入滤镜(abuffer)的参数 snprintf(args, sizeof(args), "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, audio_stream->time_base.num, audio_stream->time_base.den, codec_ctx->sample_rate, av_get_sample_fmt_name(codec_ctx->sample_fmt), codec_ctx->channel_layout); ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { fprintf(stderr, "Could not create buffer source: %s\n", av_err2str(ret)); goto end; } // 创建6个输出滤镜(abuffersink),每个对应一个声道 for (int i = 0; i < OUTPUT_FILES_NUM; i++) { ret = avfilter_graph_create_filter(&buffersink_ctx[i], buffersink, output_filenames[i], NULL, NULL, filter_graph); if (ret < 0) { fprintf(stderr, "Could not create buffer sink %d: %s\n", i, av_err2str(ret)); goto end; } // 设置输出滤镜的参数(单声道,和输入相同的采样格式、采样率) ret = av_opt_set_int_list(buffersink_ctx[i], "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { fprintf(stderr, "Could not set sample formats for sink %d: %s\n", i, av_err2str(ret)); goto end; } ret = av_opt_set_int_list(buffersink_ctx[i], "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { fprintf(stderr, "Could not set channel layouts for sink %d: %s\n", i, av_err2str(ret)); goto end; } ret = av_opt_set_int_list(buffersink_ctx[i], "sample_rates", sample_rates, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { fprintf(stderr, "Could not set sample rates for sink %d: %s\n", i, av_err2str(ret)); goto end; } } // 配置滤镜链:输入 -> channelsplit -> 6个输出 outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out0"); inputs->filter_ctx = buffersink_ctx[0]; inputs->pad_idx = 0; inputs->next = NULL; AVFilterInOut *tmp_input = inputs; for (int i = 1; i < OUTPUT_FILES_NUM; i++) { AVFilterInOut *new_input = avfilter_inout_alloc(); if (!new_input) { ret = AVERROR(ENOMEM); goto end; } char out_name[10]; snprintf(out_name, sizeof(out_name), "out%d", i); new_input->name = av_strdup(out_name); new_input->filter_ctx = buffersink_ctx[i]; new_input->pad_idx = 0; new_input->next = NULL; tmp_input->next = new_input; tmp_input = new_input; } // 解析滤镜描述符,构建滤镜图 ret = avfilter_graph_parse_ptr(filter_graph, "channelsplit=channel_layout=5.1", &inputs, &outputs, NULL); if (ret < 0) { fprintf(stderr, "Could not parse filter graph: %s\n", av_err2str(ret)); goto end; } // 配置滤镜图 ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { fprintf(stderr, "Could not configure filter graph: %s\n", av_err2str(ret)); goto end; } // 分配帧 frame = av_frame_alloc(); if (!frame) { ret = AVERROR(ENOMEM); fprintf(stderr, "Could not allocate frame: %s\n", av_err2str(ret)); goto end; } // 读取输入帧并处理 while (1) { AVPacket pkt = {0}; ret = av_read_frame(fmt_ctx, &pkt); if (ret < 0) { if (ret == AVERROR_EOF) { break; } fprintf(stderr, "Could not read frame: %s\n", av_err2str(ret)); goto end; } if (pkt.stream_index != audio_stream_idx) { av_packet_unref(&pkt); continue; } // 发送数据包到解码器 ret = avcodec_send_packet(codec_ctx, &pkt); av_packet_unref(&pkt); if (ret < 0) { fprintf(stderr, "Could not send packet to decoder: %s\n", av_err2str(ret)); goto end; } // 接收解码后的帧 while (ret >= 0) { ret = avcodec_receive_frame(codec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { fprintf(stderr, "Could not receive frame from decoder: %s\n", av_err2str(ret)); goto end; } // 发送帧到滤镜图输入 ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) { fprintf(stderr, "Could not add frame to buffer source: %s\n", av_err2str(ret)); goto end; } // 从每个输出滤镜取出帧并编码写入文件 for (int i = 0; i < OUTPUT_FILES_NUM; i++) { AVFrame *filtered_frame = av_frame_alloc(); if (!filtered_frame) { ret = AVERROR(ENOMEM); fprintf(stderr, "Could not allocate filtered frame: %s\n", av_err2str(ret)); goto end; } ret = av_buffersink_get_frame(buffersink_ctx[i], filtered_frame); if (ret == AVERROR(EAGAIN) || ret
相关产品推荐
相关产品推荐

