基于FFmpeg.AutoGen实现音频文件按时间段分割的示例请求
Great question! Using FFmpeg.AutoGen to split audio files by specific time ranges (and further into fixed-duration segments) is totally feasible—and efficient, since we can leverage stream copying to avoid re-encoding whenever possible. Below is a complete, commented implementation tailored to your needs, supporting MP3, OGG, WAV, and all other FFmpeg-compatible audio formats.
Prerequisites
- Install the FFmpeg.AutoGen package via your NuGet package manager (search for
FFmpeg.AutoGen). - Ensure FFmpeg binaries (like
ffmpeg.dll,avcodec.dll) are present in your application's output directory. You can include them manually or use theFFmpeg.AutoGen.BinaryNuGet package to bundle them automatically. - Initialize FFmpeg at the start of your application:
FFmpegBinariesHelper.RegisterFFmpegBinaries(); ffmpeg.av_register_all(); ffmpeg.avformat_network_init();
Core Implementation
This method will take an input audio file, target a specific time window, and split that window into smaller segments of your desired duration:
using System; using FFmpeg.AutoGen; public static void SplitAudioSegments( string inputPath, string outputPrefix, TimeSpan overallStartTime, TimeSpan overallEndTime, TimeSpan segmentDuration) { // Convert time values to FFmpeg's internal timestamp units (AV_TIME_BASE) long overallStartTs = (long)(overallStartTime.TotalSeconds * ffmpeg.AV_TIME_BASE); long overallEndTs = (long)(overallEndTime.TotalSeconds * ffmpeg.AV_TIME_BASE); long segmentDurationTs = (long)(segmentDuration.TotalSeconds * ffmpeg.AV_TIME_BASE); // Open input format context AVFormatContext* inputCtx = null; if (ffmpeg.avformat_open_input(&inputCtx, inputPath, null, null) != 0) throw new Exception("Failed to open input file"); try { // Retrieve stream information if (ffmpeg.avformat_find_stream_info(inputCtx, null) < 0) throw new Exception("Failed to retrieve stream info"); // Find the first audio stream int audioStreamIndex = -1; for (int i = 0; i < inputCtx->nb_streams; i++) { if (inputCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audioStreamIndex = i; break; } } if (audioStreamIndex == -1) throw new Exception("No audio stream found in input file"); AVStream* audioStream = inputCtx->streams[audioStreamIndex]; long currentSegmentStartTs = overallStartTs; int segmentNumber = 1; // Iterate to create each segment while (currentSegmentStartTs < overallEndTs) { long currentSegmentEndTs = Math.Min(currentSegmentStartTs + segmentDurationTs, overallEndTs); string outputPath = $"{outputPrefix}{segmentNumber:D3}.{GetFileExtension(inputCtx)}"; segmentNumber++; // Create output format context AVFormatContext* outputCtx = null; if (ffmpeg.avformat_alloc_output_context2(&outputCtx, null, null, outputPath) < 0) throw new Exception("Failed to create output context"); try { // Create output audio stream AVStream* outputStream = ffmpeg.avformat_new_stream(outputCtx, null); if (outputStream == null) throw new Exception("Failed to create output stream"); // Copy codec parameters from input to output if (ffmpeg.avcodec_parameters_copy(outputStream->codecpar, audioStream->codecpar) < 0) throw new Exception("Failed to copy codec parameters"); // Open output file if ((outputCtx->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0) { if (ffmpeg.avio_open(&outputCtx->pb, outputPath, ffmpeg.AVIO_FLAG_WRITE) < 0) throw new Exception($"Failed to open output file: {outputPath}"); } // Write output header if (ffmpeg.avformat_write_header(outputCtx, null) < 0) throw new Exception("Failed to write output header"); // Seek to the start of the current segment if (ffmpeg.av_seek_frame(inputCtx, audioStreamIndex, currentSegmentStartTs, ffmpeg.AVSEEK_FLAG_BACKWARD) < 0) throw new Exception("Failed to seek to segment start"); AVPacket packet = new AVPacket(); ffmpeg.av_init_packet(&packet); packet.data = null; packet.size = 0; try { while (ffmpeg.av_read_frame(inputCtx, &packet) == 0) { if (packet.stream_index != audioStreamIndex) { ffmpeg.av_packet_unref(&packet); continue; } // Convert packet timestamp to output stream time base long packetTs = ffmpeg.av_rescale_q(packet.pts, audioStream->time_base, ffmpeg.AV_TIME_BASE_Q); // Stop if we've passed the segment end time if (packetTs > currentSegmentEndTs) { ffmpeg.av_packet_unref(&packet); break; } // Only write packets that fall within the segment's time window if (packetTs >= currentSegmentStartTs) { // Rescale timestamps for output stream packet.pts = ffmpeg.av_rescale_q(packet.pts, audioStream->time_base, outputStream->time_base); packet.dts = ffmpeg.av_rescale_q(packet.dts, audioStream->time_base, outputStream->time_base); packet.duration = ffmpeg.av_rescale_q(packet.duration, audioStream->time_base, outputStream->time_base); packet.stream_index = 0; if (ffmpeg.av_interleaved_write_frame(outputCtx, &packet) < 0) throw new Exception("Failed to write packet to output"); } ffmpeg.av_packet_unref(&packet); } } finally { ffmpeg.av_packet_unref(&packet); } // Write output trailer ffmpeg.av_write_trailer(outputCtx); } finally { // Clean up output context if ((outputCtx->oformat->flags & ffmpeg.AVFMT_NOFILE) == 0 && outputCtx->pb != null) ffmpeg.avio_close(outputCtx->pb); ffmpeg.avformat_free_context(outputCtx); } currentSegmentStartTs = currentSegmentEndTs; } } finally { // Clean up input context ffmpeg.avformat_close_input(&inputCtx); } } // Helper method to get the output file extension matching the input format private static string GetFileExtension(AVFormatContext* ctx) { return ctx->iformat->name switch { "mp3" => "mp3", "ogg" => "ogg", "wav" => "wav", _ => "m4a" // Fallback format if unknown }; }
Example Usage
To split the audio from 25:00 to 31:12 into 2-minute segments:
SplitAudioSegments( inputPath: "your_audio_file.mp3", outputPrefix: "audio_segment_", overallStartTime: TimeSpan.FromMinutes(25), overallEndTime: TimeSpan.FromMinutes(31) + TimeSpan.FromSeconds(12), segmentDuration: TimeSpan.FromMinutes(2) );
This will generate files like audio_segment_001.mp3, audio_segment_002.mp3, etc., with the last segment being shorter if the remaining time is less than 2 minutes.
Key Notes
- Stream Copy: This implementation uses stream copying (no re-encoding) which is fast and preserves audio quality. It works for formats where the codec is compatible between input and output (which is true for your target formats).
- Accurate Seeking: The
AVSEEK_FLAG_BACKWARDflag ensures we seek to the closest keyframe before the segment start, then discard early packets until we hit the exact start time for precision. - Error Handling: The code includes basic error handling—you can extend it with more specific exception messages or logging as needed.
内容的提问来源于stack exchange,提问作者Sven

