FFMPEG混合不同时长音视频流的处理方案咨询
针对你这个基于FFmpeg muxing.c示例改造的音视频混流场景——用生成的图像做视频源、AAC文件做音频源,且需要截断过长的音频流来匹配设定的视频总时长(由编码帧数控制),我来分享下具体的实现思路和关键代码调整:
核心思路
我们需要先根据视频的总编码帧数和帧率/时间基准计算出视频总时长,然后在读取并复用AAC音频包的过程中,实时判断音频的当前时间是否超过这个总时长,一旦超过就停止音频流的复用,继续完成剩余视频帧的编码输出即可。
关键步骤与代码调整
1. 预计算视频总时长
在初始化阶段,先根据你设定的总视频帧数、视频流的时间基准和帧率,算出视频的总时长(这里用时间戳和秒两种方式都可以,按需选择):
// 假设已初始化好输出视频流video_stream,总帧数total_video_frames,视频帧率fps AVRational video_timebase = video_stream->time_base; // 总视频时长对应的时间戳(以视频流时间基准为单位) int64_t total_video_pts = total_video_frames * av_rescale_q(1, av_make_q(1, fps), video_timebase); // 或者转换成更直观的秒数 double total_video_duration = (double)total_video_pts * av_q2d(video_timebase);
2. 初始化AAC音频流的读取与复用参数
因为音频来自外部AAC文件,我们需要先打开这个文件、解析流信息,并将输入音频流的参数复制到输出上下文的音频流中(和muxing.c创建音频流的逻辑类似,但参数来自输入文件):
AVFormatContext *audio_input_ctx = NULL; // 打开AAC输入文件 if (avformat_open_input(&audio_input_ctx, "your_input.aac", NULL, NULL) < 0) { // 错误处理:比如打印日志、释放资源 goto cleanup; } // 读取流信息 if (avformat_find_stream_info(audio_input_ctx, NULL) < 0) { // 错误处理 goto cleanup; } // 找到音频流索引 int audio_stream_idx = av_find_best_stream(audio_input_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); AVStream *input_audio_stream = audio_input_ctx->streams[audio_stream_idx]; // 在输出上下文创建音频流 AVStream *output_audio_stream = avformat_new_stream(out_fmt_ctx, NULL); if (!output_audio_stream) { // 错误处理 goto cleanup; } // 复制音频流参数(保证编码格式一致) if (avcodec_parameters_copy(output_audio_stream->codecpar, input_audio_stream->codecpar) < 0) { // 错误处理 goto cleanup; } output_audio_stream->time_base = input_audio_stream->time_base;
3. 核心混流循环:控制音频流的停止
在原muxing.c的视频编码循环基础上,加入音频包的读取、时间判断与复用逻辑。核心是:优先完成所有视频帧的编码,同时在音频时间未超过视频总时长时,持续写入音频包:
AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; int video_frame_count = 0; int audio_finished = 0; // 循环处理直到视频帧全部编码完成,且音频流处理完毕(或被截断) while (video_frame_count < total_video_frames || !audio_finished) { // 先处理视频帧,确保视频时长符合设定 if (video_frame_count < total_video_frames) { // 生成你的视频帧(和muxing.c中的逻辑一致,比如生成纯色帧、从图像数据构造帧) AVFrame *frame = generate_your_video_frame(video_frame_count); // 编码并写入视频帧 encode_video_frame(out_fmt_ctx, video_stream, frame); av_frame_free(&frame); video_frame_count++; } else { // 视频已全部编码完成,标记音频处理结束 audio_finished = 1; } // 处理音频流,直到音频时间超时长或文件读完 if (!audio_finished) { int ret = av_read_frame(audio_input_ctx, &pkt); if (ret == AVERROR_EOF) { // 音频文件读完 audio_finished = 1; av_packet_unref(&pkt); continue; } else if (ret < 0) { // 音频读取出错,直接终止音频处理 audio_finished = 1; av_packet_unref(&pkt); continue; } // 只处理音频流的数据包 if (pkt.stream_index == audio_stream_idx) { // 将音频包的时间戳转换为输出流的时间基准 pkt.pts = av_rescale_q_rnd(pkt.pts, input_audio_stream->time_base, output_audio_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); pkt.dts = av_rescale_q_rnd(pkt.dts, input_audio_stream->time_base, output_audio_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); pkt.duration = av_rescale_q(pkt.duration, input_audio_stream->time_base, output_audio_stream->time_base); pkt.stream_index = output_audio_stream->index; // 计算当前音频包的起始时间(秒) double audio_current_time = (double)pkt.pts * av_q2d(output_audio_stream->time_base); // 判断是否超过视频总时长 if (audio_current_time > total_video_duration) { audio_finished = 1; av_packet_unref(&pkt); continue; } // 写入音频包到输出文件 if (av_interleaved_write_frame(out_fmt_ctx, &pkt) < 0) { // 错误处理:比如打印日志 } } av_packet_unref(&pkt); } }
4. 收尾工作
和muxing.c的收尾逻辑一致,别忘了写入文件尾、关闭所有上下文:
// 写入文件尾 av_write_trailer(out_fmt_ctx); cleanup: // 关闭音频输入上下文 if (audio_input_ctx) { avformat_close_input(&audio_input_ctx); } // 关闭输出上下文、释放编码器等资源(和muxing.c一致) if (out_fmt_ctx) { avio_closep(&out_fmt_ctx->pb); avformat_free_context(out_fmt_ctx); } // 其他资源释放...
注意细节
- 时间基准转换一定要准确:FFmpeg中不同流的时间基准可能不同,必须用
av_rescale_q系列函数转换后再做时间比较,否则会出现时长判断错误。 - 若要避免截断在音频包中间,可以判断音频包的结束时间(
pkt.pts + pkt.duration转换后的时间)是否超过视频总时长,这样能保证写入的音频包都是完整的。 - 音视频同步:如果你的视频帧生成有固定帧率,那时间计算会很准确;如果帧率不固定,需要基于视频帧的实际pts来计算总时长。
内容的提问来源于stack exchange,提问作者Michael IV




