当前位置: 首页 > news >正文

自动化多段视频删除:FFmpeg.AutoGen 与 C# 的完整实现​

一、功能概述

通过 FFmpeg.AutoGen 库实现视频剪辑功能,支持删除多个指定时间段(如 [8,10] 和 [15,22]),并重新生成连续时间戳。核心逻辑如下:

ffmpeg  -i input.mp4 -vf "select='not(between(t,{8},{10}) + between(t,{15},{22}))',setpts=N/29/TB" -af "aselect='not(between(t,{8},{10}) + between(t,{15},{22}))',asetpts=N/44100/TB" -c:a aac -b:a 128k  output.mp4

 关键参数说明

  • setpts 中的 29:视频帧率(通过 ffprobe 获取)
  • asetpts 中的 44100:音频采样率(通过 ffprobe 获取)
二、环境配置
  1. FFmpeg 库
    下载 FFmpeg 6.1.1 Shared Build,将 DLL 文件放入项目 ffmpeg 目录,并设置为“始终复制”。

  2. 安装 NuGet 包

Install-Package FFmpeg.AutoGen -Version 6.1.0.1

三、核心处理流程

OpenInputFile();        //初始化输入上下文、解码上下文
 OpenOutputFile();       //初始化输出上下文,编码上下文
 InitVideoFilterGraph();//构建视频滤镜
 InitAudioFilterGraph();//构建音频滤镜
 ProcessFrames();      //媒体资源帧处理
 WriteTrailer();             //文件结尾信息写入

四、关键函数实现

 1. 输入文件初始化 (OpenInputFile)

private unsafe void OpenInputFile()
{AVCodec* dec;AVCodec* audioCodec;fixed (AVFormatContext** formatContextPtr = &_inputFormatContext){ffmpeg.avformat_open_input(formatContextPtr, _inputPath, null, null).ThrowExceptionIfError();}// 获取资源信息ffmpeg.avformat_find_stream_info(_inputFormatContext, null).ThrowExceptionIfError();// 查找视频流_videoStreamIndex = ffmpeg.av_find_best_stream(_inputFormatContext, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0).ThrowExceptionIfError();// 查找音频流_audioStreamIndex = ffmpeg.av_find_best_stream(_inputFormatContext, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &audioCodec, 0);if (_videoStreamIndex == -1 && _audioStreamIndex == -1)throw new Exception("未找到视频或音频流");AVStream* stream = _inputFormatContext->streams[_videoStreamIndex];_videoFrameRate = (double)stream->avg_frame_rate.num / stream->avg_frame_rate.den;stream = _inputFormatContext->streams[_audioStreamIndex];_audioSampleRate = stream->codecpar->sample_rate;// 创建视频解码器上下文_videoDeCodecContext = CreateAvCodecContext(dec, _videoStreamIndex);// 创建音频上下文_audioDeCodecContext = CreateAvCodecContext(audioCodec, _audioStreamIndex);}

2. 输出文件初始化 (OpenOutputFile)

 private unsafe void OpenOutputFile(){// 创建输出格式上下文fixed (AVFormatContext** formatContextPtr = &_outputFormatContext){ffmpeg.avformat_alloc_output_context2(formatContextPtr, null, null, _outputPath).ThrowExceptionIfError();}// 添加视频流(如果存在)if (_videoStreamIndex != -1){_videoCodecContext = CreateAVCodecContext(_videoStreamIndex);}// 添加音频流(如果存在)if (_audioStreamIndex != -1){_audioCodecContext = CreateAVCodecContext(_audioStreamIndex);}// 打开输出文件if ((_outputFormatContext->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0){ffmpeg.avio_open(&_outputFormatContext->pb, _outputPath, ffmpeg.AVIO_FLAG_WRITE).ThrowExceptionIfError();}// 写入文件头ffmpeg.avformat_write_header(_outputFormatContext, null).ThrowExceptionIfError();}

3. 视频滤镜链构建 (InitVideoFilterGraph)

 private unsafe void InitVideoFilterGraph(){int ret = 0;if (_videoStreamIndex == -1) return;// 创建滤镜图_videoFilterGraph = ffmpeg.avfilter_graph_alloc();if (_videoFilterGraph == null) throw new Exception("无法创建视频滤镜图");// 获取输入流AVStream* stream = _inputFormatContext->streams[_videoStreamIndex];AVCodecParameters* codecpar = stream->codecpar;AVRational timeBase = stream->time_base;// 创建 buffer sourcestring bufferSrcArgs = $"video_size={codecpar->width}x{codecpar->height}:pix_fmt={codecpar->format}:time_base={timeBase.num}/{timeBase.den}";AVFilter* bufferSrc = ffmpeg.avfilter_get_by_name("buffer");fixed (AVFilterContext** fc = &_videoBufferSrcContext){ret = ffmpeg.avfilter_graph_create_filter(fc, bufferSrc, "in", bufferSrcArgs, null, _videoFilterGraph).ThrowExceptionIfError();}// 创建 buffer sinkAVFilter* bufferSink = ffmpeg.avfilter_get_by_name("buffersink");fixed (AVFilterContext** fc = &_videoBufferSinkContext){ret = ffmpeg.avfilter_graph_create_filter(fc, bufferSink, "out", null, null, _videoFilterGraph).ThrowExceptionIfError();}// 构建视频滤镜链string filterSpec = BuildVideoFilterSpec();//System.Diagnostics.Debug.WriteLine(filterSpec);AVFilterInOut* outputs = ffmpeg.avfilter_inout_alloc();AVFilterInOut* inputs = ffmpeg.avfilter_inout_alloc();outputs->name = ffmpeg.av_strdup("in");outputs->filter_ctx = _videoBufferSrcContext;outputs->pad_idx = 0;outputs->next = null;inputs->name = ffmpeg.av_strdup("out");inputs->filter_ctx = _videoBufferSinkContext;inputs->pad_idx = 0;inputs->next = null;ret = ffmpeg.avfilter_graph_parse_ptr(_videoFilterGraph, filterSpec, &inputs, &outputs, null);if (ret < 0){ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);throw new Exception($"无法解析视频滤镜链: {GetErrorString(ret)}");}ret = ffmpeg.avfilter_graph_config(_videoFilterGraph, null);if (ret < 0){ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);throw new Exception($"无法配置视频滤镜图: {GetErrorString(ret)}");}ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);}

4. 音频滤镜链构建 (InitAudioFilterGraph)

private unsafe void InitAudioFilterGraph()
{int ret = 0;if (_audioStreamIndex == -1) return;// 创建滤镜图_audioFilterGraph = ffmpeg.avfilter_graph_alloc();if (_audioFilterGraph == null) throw new Exception("无法创建音频滤镜图");// 获取输入流AVStream* stream = _inputFormatContext->streams[_audioStreamIndex];AVCodecParameters* codecpar = stream->codecpar;AVRational timeBase = stream->time_base;// 获取声道数string channelLayout = FFmpegHelper.GetChannelLayoutString(codecpar->ch_layout);// 创建 buffer sourcestring bufferSrcArgs = $"sample_rate={codecpar->sample_rate}" +$":sample_fmt={FFmpegHelper.GetSampleFormatName((AVSampleFormat)codecpar->format)}" +$":time_base={timeBase.num}/{timeBase.den}" +$":channel_layout={channelLayout}";System.Diagnostics.Debug.WriteLine(bufferSrcArgs);AVFilter* bufferSrc = ffmpeg.avfilter_get_by_name("abuffer");fixed (AVFilterContext** fc = &_audioBufferSrcContext){ret = ffmpeg.avfilter_graph_create_filter(fc, bufferSrc, "in", bufferSrcArgs, null, _audioFilterGraph).ThrowExceptionIfError();}// 创建 buffer sinkAVFilter* bufferSink = ffmpeg.avfilter_get_by_name("abuffersink");fixed (AVFilterContext** fc = &_audioBufferSinkContext){ret = ffmpeg.avfilter_graph_create_filter(fc, bufferSink, "out", null, null, _audioFilterGraph).ThrowExceptionIfError();}// 构建音频滤镜链string filterSpec = BuildAudioFilterSpec(stream);AVFilterInOut* outputs = ffmpeg.avfilter_inout_alloc();AVFilterInOut* inputs = ffmpeg.avfilter_inout_alloc();outputs->name = ffmpeg.av_strdup("in");outputs->filter_ctx = _audioBufferSrcContext;outputs->pad_idx = 0;outputs->next = null;inputs->name = ffmpeg.av_strdup("out");inputs->filter_ctx = _audioBufferSinkContext;inputs->pad_idx = 0;inputs->next = null;ret = ffmpeg.avfilter_graph_parse_ptr(_audioFilterGraph, filterSpec, &inputs, &outputs, null);if (ret < 0){ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);throw new Exception($"无法解析音频滤镜链: {GetErrorString(ret)}");}ret = ffmpeg.avfilter_graph_config(_audioFilterGraph, null);if (ret < 0){ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);throw new Exception($"无法配置音频滤镜图: {GetErrorString(ret)}");}ffmpeg.avfilter_inout_free(&inputs);ffmpeg.avfilter_inout_free(&outputs);
}

5. 帧处理逻辑 (ProcessFrames)

        private unsafe void ProcessFrames(){AVPacket* packet = ffmpeg.av_packet_alloc();AVFrame* videoFrame = ffmpeg.av_frame_alloc();AVFrame* audioFrame = ffmpeg.av_frame_alloc();AVFrame* filteredVideoFrame = ffmpeg.av_frame_alloc();AVFrame* filteredAudioFrame = ffmpeg.av_frame_alloc();try{while (true){int ret = ffmpeg.av_read_frame(_inputFormatContext, packet);if (ret < 0){if (ret == ffmpeg.AVERROR_EOF) break;throw new Exception($"读取帧错误: {GetErrorString(ret)}");}// 处理视频流if (packet->stream_index == _videoStreamIndex && _videoStreamIndex != -1){ret = ffmpeg.avcodec_send_packet(_videoDeCodecContext, packet).ThrowExceptionIfError();while (ret >= 0){ret = ffmpeg.avcodec_receive_frame(_videoDeCodecContext, videoFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"接收视频帧错误: {GetErrorString(ret)}");// 应用视频滤镜ret = ffmpeg.av_buffersrc_add_frame(_videoBufferSrcContext, videoFrame);if (ret < 0) throw new Exception($"添加到视频滤镜源错误: {GetErrorString(ret)}");while (true){ret = ffmpeg.av_buffersink_get_frame(_videoBufferSinkContext, filteredVideoFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"从视频滤镜接收帧错误: {GetErrorString(ret)}");// 编码并写入处理后的视频帧EncodeAndWriteFrame(filteredVideoFrame, _videoStreamIndex, _videoCodecContext);ffmpeg.av_frame_unref(filteredVideoFrame);}ffmpeg.av_frame_unref(videoFrame);}}// 处理音频流else if (packet->stream_index == _audioStreamIndex && _audioStreamIndex != -1){ret = ffmpeg.avcodec_send_packet(_audioDeCodecContext, packet).ThrowExceptionIfError();while (ret >= 0){ret = ffmpeg.avcodec_receive_frame(_audioDeCodecContext, audioFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"接收音频帧错误: {GetErrorString(ret)}");// 应用音频滤镜ret = ffmpeg.av_buffersrc_add_frame(_audioBufferSrcContext, audioFrame);if (ret < 0) throw new Exception($"添加到音频滤镜源错误: {GetErrorString(ret)}");while (true){ret = ffmpeg.av_buffersink_get_frame(_audioBufferSinkContext, filteredAudioFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"从音频滤镜接收帧错误: {GetErrorString(ret)}");// 编码并写入处理后的音频帧EncodeAndWriteFrame(filteredAudioFrame, _audioStreamIndex, _audioCodecContext);ffmpeg.av_frame_unref(filteredAudioFrame);}ffmpeg.av_frame_unref(audioFrame);}}ffmpeg.av_packet_unref(packet);}// 刷新视频滤镜图if (_videoStreamIndex != -1){ffmpeg.av_buffersrc_add_frame(_videoBufferSrcContext, null);while (true){int ret = ffmpeg.av_buffersink_get_frame(_videoBufferSinkContext, filteredVideoFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"从视频滤镜接收帧错误: {GetErrorString(ret)}");EncodeAndWriteFrame(filteredVideoFrame, _videoStreamIndex, _videoCodecContext);ffmpeg.av_frame_unref(filteredVideoFrame);}}// 刷新音频滤镜图if (_audioStreamIndex != -1){ffmpeg.av_buffersrc_add_frame(_audioBufferSrcContext, null);while (true){int ret = ffmpeg.av_buffersink_get_frame(_audioBufferSinkContext, filteredAudioFrame);if (ret == ffmpeg.AVERROR(ffmpeg.EAGAIN) || ret == ffmpeg.AVERROR_EOF)break;if (ret < 0) throw new Exception($"从音频滤镜接收帧错误: {GetErrorString(ret)}");EncodeAndWriteFrame(filteredAudioFrame, _audioStreamIndex, _audioCodecContext);ffmpeg.av_frame_unref(filteredAudioFrame);}}}finally{ffmpeg.av_frame_free(&videoFrame);ffmpeg.av_frame_free(&audioFrame);ffmpeg.av_frame_free(&filteredVideoFrame);ffmpeg.av_frame_free(&filteredAudioFrame);ffmpeg.av_packet_free(&packet);}}
五、滤镜字符串拼接

1.视频滤镜跟命令行时用的是一样的

private string BuildVideoFilterSpec(){// 格式化帧率(保留4位小数)string frameRateStr = _videoFrameRate.ToString("F4");// 构建完整的视频滤镜链表达式return $"select='not({_excludedTimeRangesString})',setpts=N/{frameRateStr}/TB";}

2.音频滤镜

音频滤镜要注意声道布局处理 : aformat=channel_layouts={channelLayout} 确保输出声道与输入一致。

private string BuildAudioFilterSpec(AVStream* stream){string channelLayout = FFmpegHelper.GetChannelLayoutString(stream->codecpar->ch_layout);return $"aselect='not({_excludedTimeRangesString})',asetpts=N/{_audioSampleRate}/TB,aformat=channel_layouts={channelLayout}";}

六.最后添加项目链接

视频多段删除项目

完整代码要点

  • 使用 ffmpeg.avfilter_graph_parse_ptr 解析滤镜表达式
  • 通过 av_buffersrc_add_frame 和 av_buffersink_get_frame 传递帧数据
  • 调用 avformat_write_header 和 av_write_trailer 维护文件结构
http://www.dtcms.com/a/394955.html

相关文章:

  • C、C++、Java 和 Python:四大编程语言的对比分析
  • ESP iic驱动
  • Ai-Agent学习历程——大模型的概念
  • 5G NR-NTN协议学习系列:NR-NTN介绍(3)
  • 一场 MCP 生态的变革——详解 OpenTiny NEXT 逆向思维的技术创新
  • 【案例教程】从CNN到 Transformer:基于PyTorch的遥感影像、无人机影像的地物分类、目标检测、语义分割和点云分类
  • 私有证书不被edge浏览器认可的问题的解决-Debian13环境下
  • 团体程序设计天梯赛 L2-052 吉利矩阵 (DFS+剪枝)(Java实现)
  • 【LeetCode 每日一题】966. 元音拼写检查器
  • windows 服务器如何开启系统自带的ftp服务
  • 2025年面试经历
  • Linux 基础:目录结构
  • OpenLayers地图交互 -- 章节七:指针交互详解
  • Kafka实战案例一:阿里云Kafka智能设备数据实时处理系统
  • 回调函数与错误处理
  • 深入大模型-2-大模型微调之Windows10安装大语言模型Unsloth微调环境
  • openssl x509 -noout -text -in server.cert.pem输出字段详解
  • Linux 基础:Vi/Vim 编辑器
  • K8s和Service Mesh如何强化微服务治理能力
  • 知识图谱赋能自然语言处理的深层语义分析:技术、影响与前沿趋势
  • 论文笔记:How Can Recommender Systems Benefit from Large Language Models: A Survey
  • idea终端添加git-bash,支持linux的shell语法
  • MITRE ATLAS对抗威胁矩阵:守护LLM安全的中国实践指南
  • 常见的 Web 项目性能优化方法有哪些?​​也适用于首页
  • Qt QMainWindow类深度解析:主窗口框架的核心实现
  • 知识图谱对自然语言处理深层语义分析的革命性影响与启示
  • 内部标识符
  • 计算机网络2
  • 计算机视觉(opencv)实战三十二——CascadeClassifier 人脸微笑检测(摄像头)
  • MyBatis-Plus 全方位深度指南:从入门到精通