如何在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]; }
- 刚用
avformat_alloc_context()创建的afctx[i],它的streams数组是未初始化的NULL,nb_streams(流的数量)也是0,直接访问afctx[i]->streams[0]就是在访问非法内存,必然触发段错误。 - 直接把原上下文的
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
相关产品推荐
相关产品推荐

