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

如何在C语言中提取音频流至AVFormatContext?解决段错误问题

解决FFmpeg多音频流提取并复制到新AVFormatContext的问题

嘿,我来帮你搞定这个FFmpeg音频流复制的问题!你之前的代码触发段错误,核心原因是直接操作AVFormatContext的streams数组指针,完全忽略了FFmpeg内部的内存管理规则——咱们一步步拆解问题,再给出正确的实现方案。

为什么你的代码会触发段错误?

你这段代码的问题很明显:

AVFormatContext *afctx[no_of_audio_streams]; 
for(i=0; i<no_of_audio_streams; i++){ 
    afctx[i] = avformat_alloc_context(); 
    afctx[i]->streams[0] = format_ctx->streams[i+1]; 
}
  1. 刚用avformat_alloc_context()创建的afctx[i],它的streams数组是未初始化的NULL,nb_streams(流的数量)也是0,直接访问afctx[i]->streams[0]就是在访问非法内存,必然触发段错误。
  2. 直接把原上下文的AVStream指针赋值给新上下文,会导致两个上下文共享同一个AVStream结构体。后续任何一个上下文释放资源时,另一个都会访问已释放的内存,引发未定义行为。

正确的实现步骤(含编解码器信息复制)

FFmpeg提供了专门的API来安全地复制流信息,核心是用avformat_new_stream()创建新流,再用avcodec_parameters_copy()复制编解码器参数。下面分两种常见场景给出示例:

场景1:将所有音频流整合到同一个输出AVFormatContext(生成多音频流文件)

如果想把原视频里的所有音频流放到同一个输出文件/上下文里,可以这么做:

#include <libavformat/avformat.h>

int main() {
    AVFormatContext *input_ctx = NULL;
    const char *input_path = "your_input_video.mp4";
    
    // 1. 打开输入文件并读取流信息
    if (avformat_open_input(&input_ctx, input_path, NULL, NULL) < 0) {
        fprintf(stderr, "无法打开输入文件\n");
        return -1;
    }
    if (avformat_find_stream_info(input_ctx, NULL) < 0) {
        fprintf(stderr, "无法获取流信息\n");
        return -1;
    }

    // 2. 创建输出AVFormatContext(这里以MKV为例,它支持多音频流)
    AVFormatContext *output_ctx = NULL;
    if (avformat_alloc_output_context2(&output_ctx, NULL, NULL, "output_multi_audio.mkv") < 0) {
        fprintf(stderr, "无法创建输出上下文\n");
        return -1;
    }

    // 3. 遍历输入流,复制所有音频流到输出上下文
    for (int i = 0; i < input_ctx->nb_streams; i++) {
        AVStream *in_stream = input_ctx->streams[i];
        // 只处理音频流
        if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
            continue;
        }

        // 创建新的输出流(FFmpeg会自动管理streams数组的内存)
        AVStream *out_stream = avformat_new_stream(output_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "无法创建输出流\n");
            return -1;
        }

        // 复制编解码器参数(包含编码格式、采样率、声道数等核心信息)
        if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0) {
            fprintf(stderr, "无法复制编解码器参数\n");
            return -1;
        }
        // 让FFmpeg自动处理codec tag,避免格式兼容问题
        out_stream->codecpar->codec_tag = 0;
        // 复制时间基等关键流信息
        out_stream->time_base = in_stream->time_base;
        out_stream->r_frame_rate = in_stream->r_frame_rate;
    }

    // 4. 打开输出文件并写入头信息
    if (avio_open(&output_ctx->pb, "output_multi_audio.mkv", AVIO_FLAG_WRITE) < 0) {
        fprintf(stderr, "无法打开输出文件\n");
        return -1;
    }
    if (avformat_write_header(output_ctx, NULL) < 0) {
        fprintf(stderr, "无法写入输出头\n");
        return -1;
    }

    // 5. 读取输入包,过滤音频流并写入输出
    AVPacket pkt;
    while (av_read_frame(input_ctx, &pkt) >= 0) {
        AVStream *in_stream = input_ctx->streams[pkt.stream_index];
        if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
            av_packet_unref(&pkt);
            continue;
        }

        // 转换包的时间基到输出流的时间基
        AVStream *out_stream = output_ctx->streams[pkt.stream_index];
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.stream_index = out_stream->index;
        pkt.pos = -1; // 输出文件的位置由FFmpeg自动管理

        if (av_interleaved_write_frame(output_ctx, &pkt) < 0) {
            fprintf(stderr, "写入帧时出错\n");
            break;
        }
        av_packet_unref(&pkt);
    }

    // 6. 写入尾信息并清理资源
    av_write_trailer(output_ctx);
    avio_closep(&output_ctx->pb);
    avformat_free_context(output_ctx);
    avformat_close_input(&input_ctx);

    return 0;
}

场景2:每个音频流单独一个AVFormatContext(生成多个单音频文件)

如果想把每个音频流单独存为一个文件,只需要在遍历音频流时,为每个流创建独立的输出上下文即可:

// 遍历输入流,为每个音频流创建独立输出上下文
for (int i = 0; i < input_ctx->nb_streams; i++) {
    AVStream *in_stream = input_ctx->streams[i];
    if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) {
        continue;
    }

    // 生成每个音频流的输出文件名,比如audio_0.mp3, audio_1.mp3
    char output_path[100];
    snprintf(output_path, sizeof(output_path), "audio_%d.mp3", i);

    AVFormatContext *output_ctx = NULL;
    if (avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_path) < 0) {
        fprintf(stderr, "无法创建输出上下文\n");
        return -1;
    }

    // 创建输出流并复制参数(和场景1的步骤一致)
    AVStream *out_stream = avformat_new_stream(output_ctx, NULL);
    if (!out_stream) { /* 错误处理 */ }
    if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0) { /* 错误处理 */ }
    out_stream->codecpar->codec_tag = 0;
    out_stream->time_base = in_stream->time_base;

    // 打开文件、写头、写包、写尾、清理资源的逻辑和场景1一致,这里省略
    // ...
}

关键注意事项

  • Stream Copy模式:上面的示例都是用流复制(不重新编码),所以不需要打开编解码器,只复制codecpar即可。如果需要重新编码,才需要打开编解码器并处理帧数据。
  • 时间基转换:输入和输出流的时间基可能不同,必须用av_rescale_q系列函数转换包的pts/dts/duration,否则会出现音视频不同步或播放错误。
  • 资源清理:所有FFmpeg分配的资源(AVFormatContext、AVPacket等)都要记得用对应的API释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者geo-freak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:11:20