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

如何通过FFmpeg编程删除MPG视频缓冲帧并保持音频同步?

我来帮你搞定这个FFmpeg视频过滤的问题——你需要精准过滤掉带缓冲图标的视频帧,同时保证音视频同步,还得保留原视频的帧率、分辨率等核心属性对吧?下面是具体的实现思路和代码调整方案:

核心思路

要实现需求,关键要做好三件事:

  1. 复用原视频/音频的编码参数,确保输出文件和原文件属性一致
  2. 基于帧检测结果过滤视频帧,同时同步跳过对应时间段的无音频缓冲段
  3. 正确转换并维护PTS/DTS时间戳,避免音视频不同步
具体代码实现

首先需要补充输出上下文的初始化、音视频流参数复制,然后修改帧处理逻辑:

1. 初始化输出上下文与流参数

在帧遍历循环前,先创建输出文件的格式上下文,并复制原流的编码参数:

// 初始化输出格式上下文
AVFormatContext *outFormatCtx = nullptr;
// 指定输出格式为MPG,也可以自动推导
avformat_alloc_output_context2(&outFormatCtx, nullptr, "mpeg", "output.mpg");
if (!outFormatCtx) {
    // 错误处理:输出上下文创建失败
    return -1;
}

// 复制视频流参数
AVStream *outVideoStream = avformat_new_stream(outFormatCtx, nullptr);
if (!outVideoStream) {
    // 错误处理
    avformat_free_context(outFormatCtx);
    return -1;
}
avcodec_parameters_copy(outVideoStream->codecpar, pFormatCtx->streams[videoStream]->codecpar);
outVideoStream->codecpar->codec_id = outFormatCtx->oformat->video_codec;

// 查找并复制音频流参数
int audioStream = -1;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
    if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioStream = i;
        break;
    }
}
AVStream *outAudioStream = nullptr;
if (audioStream != -1) {
    outAudioStream = avformat_new_stream(outFormatCtx, nullptr);
    avcodec_parameters_copy(outAudioStream->codecpar, pFormatCtx->streams[audioStream]->codecpar);
    outAudioStream->codecpar->codec_id = outFormatCtx->oformat->audio_codec;
}

// 打开输出文件IO
if (!(outFormatCtx->oformat->flags & AVFMT_NOFILE)) {
    if (avio_open(&outFormatCtx->pb, "output.mpg", AVIO_FLAG_WRITE) < 0) {
        // 错误处理:文件打开失败
        avformat_free_context(outFormatCtx);
        return -1;
    }
}

// 写入文件头
if (avformat_write_header(outFormatCtx, nullptr) < 0) {
    // 错误处理
    avio_close(outFormatCtx->pb);
    avformat_free_context(outFormatCtx);
    return -1;
}

2. 修改帧遍历与过滤逻辑

在原循环中加入音频同步处理,根据视频帧的检测结果决定是否保留对应音频:

// 标记是否需要保留当前音频段(对应有效视频帧)
bool keepAudioSegment = false;
// 记录上一个保留的视频帧时间戳,用于同步校验
int64_t lastValidVideoPts = AV_NOPTS_VALUE;

AVPacket origPacket;
while (av_read_frame(pFormatCtx, &origPacket) >= 0) {
    AVPacket packet = origPacket; // 复制原包,避免解码修改原数据

    if (packet.stream_index == videoStream) {
        int frameFinished = 0;
        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
        
        if (frameFinished) {
            // 转换为RGB帧用于图标检测
            sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 
                      0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
            
            if (ThisIsAFrameIWant(pFrameRGB)) {
                // 转换时间戳到输出流的时间基
                pFrame->pts = av_rescale_q(pFrame->pts, pFormatCtx->streams[videoStream]->time_base, 
                                          outVideoStream->time_base);
                pFrame->dts = av_rescale_q(pFrame->dts, pFormatCtx->streams[videoStream]->time_base, 
                                          outVideoStream->time_base);
                pFrame->duration = av_rescale_q(pFrame->duration, pFormatCtx->streams[videoStream]->time_base, 
                                               outVideoStream->time_base);
                
                // 编码并写入视频帧
                AVPacket outPacket = {0};
                av_init_packet(&outPacket);
                int ret = avcodec_send_frame(outVideoStream->codec, pFrame);
                if (ret >= 0) {
                    while (ret >= 0) {
                        ret = avcodec_receive_packet(outVideoStream->codec, &outPacket);
                        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
                        if (ret < 0) {
                            // 编码错误处理
                            break;
                        }
                        outPacket.stream_index = outVideoStream->index;
                        av_interleaved_write_frame(outFormatCtx, &outPacket);
                        av_packet_unref(&outPacket);
                    }
                }
                
                // 更新音频同步标记
                lastValidVideoPts = pFrame->pts;
                keepAudioSegment = true;
            } else {
                // 缓冲帧,跳过对应音频段
                keepAudioSegment = false;
            }
        }
    } else if (packet.stream_index == audioStream && keepAudioSegment) {
        // 转换音频包时间戳到输出流时间基
        packet.pts = av_rescale_q(packet.pts, pFormatCtx->streams[audioStream]->time_base, 
                                 outAudioStream->time_base);
        packet.dts = av_rescale_q(packet.dts, pFormatCtx->streams[audioStream]->time_base, 
                                 outAudioStream->time_base);
        packet.duration = av_rescale_q(packet.duration, pFormatCtx->streams[audioStream]->time_base, 
                                      outAudioStream->time_base);
        packet.stream_index = outAudioStream->index;
        
        // 直接复用原音频包(MPG音频为MP2,无需重新编码)
        if (av_interleaved_write_frame(outFormatCtx, &packet) < 0) {
            // 音频写入错误处理
        }
    }
    
    av_packet_unref(&origPacket);
}

// 写入文件尾并清理资源
av_write_trailer(outFormatCtx);
if (!(outFormatCtx->oformat->flags & AVFMT_NOFILE)) {
    avio_close(outFormatCtx->pb);
}
avformat_free_context(outFormatCtx);
关键细节说明
  • 时间基转换:必须用av_rescale_q将原流的PTS/DTS转换为输出流的时间基,否则会出现音视频不同步或时间戳异常
  • 音频复用优化:MPG的音频通常是MP2格式,和输出格式兼容,直接复用原音频包无需重新编码,既高效又能保留音质
  • 关键帧处理:MPEG是帧间编码格式,过滤帧后如果出现参考帧缺失,可以通过设置编码器的gop_size参数,或在过滤后的第一帧强制生成关键帧(avcodec_send_frame前设置pFrame->key_frame = 1)
  • 资源清理:所有FFmpeg结构体(AVPacket、AVFrame、AVFormatContext等)都需要调用对应的unref或free函数释放,避免内存泄漏

内容的提问来源于stack exchange,提问作者user7838334

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:52:22