You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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的滤镜,所以源码实现的核心是手动构建滤镜图,而不是修改编码器或重采样上下文的参数。步骤如下:

  1. 初始化FFmpeg组件,打开输入文件并获取音频流信息
  2. 创建滤镜图,包含三个关键部分:
    • buffer滤镜:作为输入,承接原始音频帧
    • channelsplit滤镜:配置5.1声道布局,拆分出6个独立声道
    • 6个buffersink滤镜:分别接收每个声道的输出帧
  3. 将滤镜图连接起来,处理每一帧数据
  4. 从每个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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 00:18:23