72搭建网站网页,电商网站建设方面的毕业设计,旅游网站建设总结报告,温州 建网站一、功能概述
通过 FFmpeg.AutoGen 库实现视频剪辑功能#xff0c;支持删除多个指定时间段#xff08;如 [8,10] 和 [15,22]#xff09;#xff0c;并重新生成连续时间戳。核心逻辑如下#xff1a; ffmpeg -i input.mp4 -vf selectnot(between(t,{8},{10}) betwe…一、功能概述
通过 FFmpeg.AutoGen 库实现视频剪辑功能支持删除多个指定时间段如 [8,10] 和 [15,22]并重新生成连续时间戳。核心逻辑如下 ffmpeg -i input.mp4 -vf selectnot(between(t,{8},{10}) between(t,{15},{22})),setptsN/29/TB -af aselectnot(between(t,{8},{10}) between(t,{15},{22})),asetptsN/44100/TB -c:a aac -b:a 128k output.mp4 关键参数说明 setpts 中的 29视频帧率通过 ffprobe 获取asetpts 中的 44100音频采样率通过 ffprobe 获取 二、环境配置 FFmpeg 库 下载 FFmpeg 6.1.1 Shared Build将 DLL 文件放入项目 ffmpeg 目录并设置为“始终复制”。 安装 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 $selectnot({_excludedTimeRangesString}),setptsN/{frameRateStr}/TB;}2.音频滤镜
音频滤镜要注意声道布局处理 aformatchannel_layouts{channelLayout} 确保输出声道与输入一致。
private string BuildAudioFilterSpec(AVStream* stream){string channelLayout FFmpegHelper.GetChannelLayoutString(stream-codecpar-ch_layout);return $aselectnot({_excludedTimeRangesString}),asetptsN/{_audioSampleRate}/TB,aformatchannel_layouts{channelLayout};}六.最后添加项目链接
视频多段删除项目 完整代码要点 使用 ffmpeg.avfilter_graph_parse_ptr 解析滤镜表达式通过 av_buffersrc_add_frame 和 av_buffersink_get_frame 传递帧数据调用 avformat_write_header 和 av_write_trailer 维护文件结构