You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动