天天看點

【C#】使用ffmpeg image2pipe将圖檔儲存為mp4視訊

文章目錄

  • ​​需求​​
  • ​​實作​​

需求

在正式開始之前,先介紹下我的需求是怎麼樣的,基于此需求如何使用ffmpeg實作。僅供參考。

需求點:

  1. 将圖檔儲存為視訊
  2. 圖檔數量不是固定的,是由上遊的webrtc傳下的幀資料,轉成的bitmap。是以隻要webrtc開着,圖檔流就一直會有。
  3. 每幀圖像的間隔時間依賴于不同的網絡環境,是以不是固定的時間間隔。

實作

在使用原生ffmpeg之前,筆者使用了幾個第三方的nuget庫,如:FFmpeg.AutoGen、Xabe.FFmpeg、Accord.Video.FFMPEG。前兩個庫要麼隻支援将檔案夾裡現有的圖檔儲存為mp4,要麼不支援設定每幀的PTS,導緻生成的mp4播放速度太快。最後選用了Accord.Video.FFMPEG,這個庫能滿足上述的三個需求點。無奈此庫已長期不維護,當上遊的FPS>15時,​

​WriteVideoFrame​

​方法抛出異常的頻率會大大提升,導緻記憶體洩漏,而且目前幀也會被丢掉。

然後項目使用的是.net452,一時半會版本也更新不上去,這就過濾大多數的nuget庫。最後,隻能使用的原生的ffmpeg了。

ffmpeg隻是提供了一個exe,并沒有官方的API可供我們調用,隻提供了一大堆的參數說明,真是令人頭大。經過不斷的看文檔和搜尋調試之後,發現配置以下參數可以達到我們的需求。

-f image2pipe -use_wallclock_as_timestamps 1 -i - -c:v libx264 -pix_fmt yuv420p -vsync passthrough -maxrate 5000k  -an -y 123.mp4      
  • ​image2pipe​

    ​:使用圖檔管道,我們可以将圖檔資料一直往管道裡塞,ffmpeg會不斷将其添加到mp4檔案中。用來滿足需求1和2.
  • ​use_wallclock_as_timestamps 1​

    ​:開啟此選項,ffmpeg就會将接收此圖檔的時間作為該幀的timestamp。這樣生成的MP4播放速度才正常,滿足需求3.
  • ​pix_fmt yuv420p​

    ​:設定像素格式,解決生成的視訊無法使用windows media player播放的問題。
  • ​-vsync passthrough​

    ​:可以了解為動态幀率,根據上遊的幀率來決定輸出mp4的幀率。預設有以下幾個選項:
  • ​passthrough​

    ​ :使用幀原始的timestamp.
  • ​cfr (1)​

    ​:根據輸出幀率的配置,進行自動插幀(上遊幀率小于輸入幀率)或者丢幀(上遊幀率大于輸入幀率)。
  • ​vfr (2)​

    ​:類似于passthrough, 不過當兩幀具有timestamp時會丢棄其中一個。
  • ​drop​

    ​:類似于passthrough,隻不過會丢棄幀原始的timstamp,然後重新生成符合幀率要求的timestamp。
  • ​auto (-1)​

    ​:預設行為。在cfr和vfr之前自動選擇。
  • ​maxrate​

    ​:設定最大比特率
  • ​123.mp4​

    ​:儲存的檔案名或者路徑,注意裡面不要有空格。
public class FfmpegToVideoService 
{
        private bool _isRunning = false;
        private int _fps;
        private readonly Process _proc;

        /// <summary>
        /// Bitmap儲存為MP4
        /// </summary>
        /// <param name="filePath">mp4要儲存的路徑,如:D:\\a\b\123.mp4</param>
        /// <param name="maxBitRate">最大比特率,機關K</param>
        public FfmpegToVideoService(string filePath,int maxBitRate = 5000)
        {
            var formattedPath = Path.GetFullPath(filePath);
            _proc = new Process();
            //-pix_fmt yuv420p -movflags +faststart  -r {30}  -i pipe:.bmp -r {_fps} -timecode 00:00:00.000
            //-vsync cfr自動內插補點 vfr根據timestamp,重複的丢棄    passthrough根據timestamp重複的不丢  -vsync passthrough
            //-r 30 入幀出幀都是30
            _proc.StartInfo.FileName = @"ffmpeg.exe";
            _proc.StartInfo.Arguments = $"-f image2pipe -use_wallclock_as_timestamps 1 -i - -c:v libx264 -pix_fmt yuv420p -vsync passthrough -maxrate {maxBitRate}k  -an -y {formattedPath}";
            _proc.StartInfo.WorkingDirectory = CommonFunctions.BasePath;
            _proc.StartInfo.UseShellExecute = false;
            _proc.StartInfo.RedirectStandardInput = true;
            _proc.StartInfo.RedirectStandardOutput = true;
            _proc.Start();
        }

        // 将Bitmap資料寫入管道
        private void SendToPipe(Bitmap bitmap)
        {
            if (_proc.StartInfo.RedirectStandardInput)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    bitmap.Save(ms, ImageFormat.Png);
                    ms.WriteTo(_proc.StandardInput.BaseStream);
                }
            }
        }

        /// <summary>
        /// 異步線程啟動服務
        /// </summary>
        public override void StartAsync()
        {
            _isRunning = true;
        }

        /// <summary>
        /// 停止服務
        /// </summary>
        public override void Stop()
        {
            _isRunning = false;
            try
            {
                _proc.StartInfo.RedirectStandardInput = false;
                _proc.StartInfo.RedirectStandardOutput = false;
                _proc.StandardInput.Close();
                _proc.StandardOutput.Close();
                _proc.Close();
            }
            catch (Exception ex)
            {
                Log.Error(ex, "");
            }
        }

        /// <summary>
        /// 添加item
        /// </summary>
        /// <param name="item"></param>
        public override void Add(FrameInfo item)
        {
            if(_isRunning)
            {
                SendToPipe(item.Bitmap);
            }
        }
    }      
  1. ​​https://trac.ffmpeg.org/wiki/Slideshow​​
  2. ​​https://ffmpeg.org/ffmpeg.html#filter_005foption​​
  3. ​​https://stackoverflow.com/questions/60977555/adding-current-time-as-timestamp-in-h-264-raw-stream-with-few-frames​​