一個小時開發的直播推拉流軟件來了

1、簡介nginx

目前市面上直播推流的軟件有不少,拉流也很常見。近期由於業務須要,須要搭建一整套服務端推流,客戶端拉流的程序。隨即進行了展開研究,花了一個小時作了個基於winfrom桌面版的推拉流軟件。另外稍微囉嗦兩句,主要怕大家翻不到最下面。目前軟件仍是一個簡化版的,但已足夠平常使用,好比搭建一套餐館的監控,據我瞭解,小餐館裝個監控通常3000—5000,若是本身稍微懂點軟件知識,幾百元買幾個攝像頭+一臺電腦,搭建的監控不足千元,甚至一兩百元足夠搞定了。這是我研究這套軟件的另一個想法。git

2、使用的技術棧:github

一、nginx web

二、ffmpeg shell

三、asp.net framework4.5 winfrom c#

四、開發工具vs2019 服務器

五、開發語言c#app

關於以上技術大致作下說明,使用nginx作爲代理節點服務器,基於ffmpeg作推流,asp.net framework4.5 winfrom 作爲桌面應用。不少人比較陌生的多是ffmpeg,把它理解爲視頻處理最經常使用的開源軟件。關於它的更多詳細文章能夠去看阮一峯對它的介紹。「FFmpeg 視頻處理入門教程」。asp.net

5.1啓動nginx的核心代碼ide

using MnNiuVideoApp.Common;
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

namespace MnNiuVideoApp
{
    public class NginxProcess
    {
        //nginx的進程名
        public string _nginxFileName = "nginx";
        public string _stop = "stop.bat";
        public string _start = "start.bat";
        //nginx的文件路徑名
        public string _nginxFilePath = string.Empty;
        //nginx的啓動參數
        public string _arguments = string.Empty;
        //nginx的工做目錄
        public string _workingDirectory = string.Empty;
        public int _processId = 0;
        public NginxProcess()
        {
            string basePath = FileHelper.LoadNginxPath();
            string nginxPath = $@"{basePath}\nginx.exe";
            _nginxFilePath = Path.GetFullPath(nginxPath);
            _workingDirectory = Path.GetDirectoryName(_nginxFilePath);
            _arguments = @" -c \conf\nginx-win.conf";
        }
        //關掉全部nginx的進程,格式必須這樣,有空格存在  taskkill /IM  nginx.exe  /F

        /// <summary>
        /// 啓動服務
        /// </summary>
        /// <returns></returns>
        public void StartService()
        {
            try
            {
                if (ProcessesHelper.IsCheckProcesses(_nginxFileName))
                {
                    LogHelper.WriteLog("nginx進程已經啓動過了");
                }
                else
                {
                    var sinfo = new ProcessStartInfo
                    {
                        FileName = _nginxFilePath,
                        Verb = "runas",
                        WorkingDirectory = _workingDirectory,
                        Arguments = _arguments
                    };
#if DEBUG
                    sinfo.UseShellExecute = true;
                    sinfo.CreateNoWindow = false;
#else
                sinfo.UseShellExecute = false;
#endif
                    using (var process = Process.Start(sinfo))
                    {
                        //process?.WaitForExit();
                        _processId = process.Id;
                    }
                }
            }
            catch (Exception e)
            {
                LogHelper.WriteLog(e.Message);
                MessageBox.Show(e.Message);
            }

        }

        /// <summary>
        /// 關閉nginx全部進程
        /// </summary>
        /// <returns></returns>
        public void StopService()
        {
            ProcessesHelper.KillProcesses(_nginxFileName);
        }



        /// <summary>
        /// 須要以管理員身份調用才能起做用
        /// </summary>
        public void KillAll()
        {
            try
            {
                ProcessStartInfo sinfo = new ProcessStartInfo();
#if DEBUG
                sinfo.UseShellExecute = true;
                // sinfo.CreateNoWindow = true;
#else
                sinfo.UseShellExecute = false;
#endif
                sinfo.FileName = _nginxFilePath;
                sinfo.Verb = "runas";
                sinfo.WorkingDirectory = _workingDirectory;
                sinfo.Arguments = $@"{_workingDirectory}\taskkill /IM  nginx.exe  /F ";
                using (Process _process = Process.Start(sinfo))
                {
                    _processId = _process.Id;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
}
View Code

5.2啓動ffmpeg進程的核心代碼

using MnNiuVideoApp.Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace MnNiuVideoApp
{
    public class VideoProcess
    {
        private static string _ffmpegPath = string.Empty;
        static VideoProcess()
        {
            _ffmpegPath = FileHelper.LoadFfmpegPath();
        }
        /// <summary>
        /// 調用ffmpeg.exe 執行命令
        /// </summary>
        /// <param name="Parameters">命令參數</param>
        /// <returns>返回執行結果</returns>
        public static void Run(string parameters)
        {

            // 設置啓動參數
            ProcessStartInfo startInfo = new ProcessStartInfo();

            startInfo.Verb = "runas";
            startInfo.FileName = _ffmpegPath;
            startInfo.Arguments = parameters;
#if DEBUG
            startInfo.CreateNoWindow = false;
            startInfo.UseShellExecute = true;
            //將輸出信息重定向
            //startInfo.RedirectStandardOutput = true;
#else

            //設置不在新窗口中啓動新的進程
            startInfo.CreateNoWindow = true;
            //不使用操做系統使用的shell啓動進程
            startInfo.UseShellExecute = false;
#endif
            using (var proc = Process.Start(startInfo))
            {
                proc?.WaitForExit(3000);
            }
            //finally
            //{
            //    if (proc != null && !proc.HasExited)
            //    {
            //        //"即將殺掉視頻錄製進程,Pid:{0}", proc.Id));
            //        proc.Kill();
            //        proc.Dispose();
            //    }
            //}
        }
    }
}
View Code

5.3 窗體裏面事件的核心代碼

using MnNiuVideoApp;
using MnNiuVideoApp.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MnNiuVideo
{
    public partial class PlayerForm : Form
    {
        public PlayerForm()
        {
            InitializeComponent();
            new NginxProcess().StopService();
            //獲取本機全部相機
            var cameras = CameraUtils.ListCameras();
            if (toolStripComboBox1.ComboBox != null)
            {
                var list = new List<string>() { "--請選擇相機--" };
                foreach (var item in cameras)
                {
                    list.Add(item.FriendlyName);
                }
                toolStripComboBox1.ComboBox.DataSource = list;
            }
        }
        TstRtmp rtmp = new TstRtmp();
        Thread thPlayer;
        private void StartPlayStripMenuItem_Click(object sender, EventArgs e)
        {
            StartPlayStripMenuItem.Enabled = false;
            TaskScheduler uiContext = TaskScheduler.FromCurrentSynchronizationContext();
            Task t = Task.Factory.StartNew(() =>
            {
                if (thPlayer != null)
                {
                    rtmp.Stop();
                    thPlayer = null;
                }
                else
                {
                    string path = FileHelper.GetLoadPath();
                    pic.Image = Image.FromFile(path);
                    thPlayer = new Thread(DeCoding)
                    {
                        IsBackground = true
                    };
                    thPlayer.Start();

                    StartPlayStripMenuItem.Text = "中止播放";
                    //StartPlayStripMenuItem.Enabled = true;
                }
            }).ContinueWith(m =>
            {
                StartPlayStripMenuItem.Enabled = true;
                Console.WriteLine("任務結束");
            }, uiContext);

        }

        /// <summary>
        /// 播放線程執行方法
        /// </summary>
        private unsafe void DeCoding()
        {
            try
            {
                Console.WriteLine("DeCoding run...");
                Bitmap oldBmp = null;
                // 更新圖片顯示
                TstRtmp.ShowBitmap show = (bmp) =>
                {
                    this.Invoke(new MethodInvoker(() =>
                    {
                        if (this.pic.Image != null)
                        {
                            this.pic.Image = null;
                        }

                        if (bmp != null)
                        {
                            this.pic.Image = bmp;
                        }
                        if (oldBmp != null)
                        {
                            oldBmp.Dispose();
                        }
                        oldBmp = bmp;
                    }));
                };
                //線程間操做無效
                var url = string.Empty;
                this.Invoke(new Action(() =>
                {
                    url = PlayAddressComboBox.Text.Trim();
                }));

                if (string.IsNullOrEmpty(url))
                {
                    MessageBox.Show("播放地址爲空!");
                    return;
                }
                rtmp.Start(show, url);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                Console.WriteLine("DeCoding exit");
                rtmp?.Stop();
                thPlayer = null;
                this.Invoke(new MethodInvoker(() =>
                {
                    StartPlayStripMenuItem.Text = "開始播放";
                    StartPlayStripMenuItem.Enabled = true;
                }));
            }
        }




        private void DesktopRecordStripMenuItem_Click(object sender, EventArgs e)
        {
            var path = FileHelper.VideoRecordPath();
            if (string.IsNullOrEmpty(path))
            {
                MessageBox.Show("視頻存放文件路徑爲空");
            }
            string args = $"ffmpeg -f gdigrab -r 24 -offset_x 0 -offset_y 0 -video_size 1920x1080 -i desktop -f dshow -list_devices 0 -i video=\"Integrated Webcam\":audio=\"麥克風(Realtek Audio)\" -filter_complex \"[0:v] scale = 1920x1080[desktop];[1:v] scale = 192x108[webcam];[desktop][webcam] overlay = x = W - w - 50:y = H - h - 50\" -f flv \"rtmp://127.0.0.1:20050/myapp/test\" -map 0 {path}";
            VideoProcess.Run(args);
            StartLiveToolStripMenuItem.Text = "正在直播";
        }

        private void LiveRecordStripMenuItem_Click(object sender, EventArgs e)
        {
            var path = FileHelper.VideoRecordPath();
            if (string.IsNullOrEmpty(path))
            {
                MessageBox.Show("視頻存放文件路徑爲空");
            }
            var args = $" -f dshow -re -i  video=\"Integrated Webcam\" -tune zerolatency -vcodec libx264 -preset ultrafast -b:v 400k -s 704x576 -r 25 -acodec aac -b:a 64k -f flv \"rtmp://127.0.0.1:20050/myapp/test\" -map 0 {path}";
            VideoProcess.Run(args);
            StartLiveToolStripMenuItem.Text = "正在直播";
        }
        /// <summary>
        /// 開始直播(服務端開始推流)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void StartLiveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {

                if (toolStripComboBox1.ComboBox != null)
                {
                    string camera = toolStripComboBox1.ComboBox.SelectedText;
                    if (string.IsNullOrEmpty(camera))
                    {
                        MessageBox.Show("請選擇要使用的相機");
                        return;
                    }
                    var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Icon");
                    var imgPath = Path.Combine(path + "\\", "stop.jpg");
                    StartLiveToolStripMenuItem.Enabled = false;

                    StartLiveToolStripMenuItem.Image = Image.FromFile(imgPath);
                    string args = $" -f dshow -re -i  video=\"{camera}\" -tune zerolatency -vcodec libx264 -preset ultrafast -b:v 400k -s 704x576 -r 25 -acodec aac -b:a 64k -f flv \"rtmp://127.0.0.1:20050/myapp/test\"";
                    VideoProcess.Run(args);
                }

                StartLiveToolStripMenuItem.Text = "正在直播";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void PlayerForm_Load(object sender, EventArgs e)
        {
            // if (toolStripComboBox1.ComboBox != null) toolStripComboBox1.ComboBox.SelectedIndex = 0;
        }

        private void PlayerForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.Dispose();
            this.Close();
        }

        private void PlayerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            DialogResult dr = MessageBox.Show("您是否退出?", "提示:", MessageBoxButtons.OKCancel, MessageBoxIcon.Information);

            if (dr != DialogResult.OK)
            {
                if (dr == DialogResult.Cancel)
                {
                    e.Cancel = true; //不執行操做
                }
            }
            else
            {
                new NginxProcess().StopService();
                Application.Exit();
                e.Cancel = false; //關閉窗體
            }
        }
    }
}
View Code

六、界面展現:

3、目前實現的功能

  1. winfrom桌面播放(拉流)

  2. 推流(直播)

  3. (直播)推流錄屏

  4. ....想到再加上去

4、如何使用

  1. 克隆或下載程序後可使用vs打開解決方案 、而後選擇debug或relase方式進行編譯,建議relase,編譯後的軟件在Bin\debug|relase目錄下。

  2. 雙擊Bin\debug|relase目錄下 MnNiuVideo.exe 便可運行起來。

  3. 軟件打開後,選擇本機相機(若是本機有多個相機任意選一個)、點擊開始直播(推流),而後點擊開始播放(拉流)。

  4. 關於其餘問題或者詳細介紹建議直接看源碼。

5、最後

可能一眼看去UI比較醜,多年沒有使用過winfrom,其實winform自己控件開發的界面就比較醜,界面這塊不屬於核心,也可使用web端拉流,手機端拉流,都是可行的。所用技術略有差異。另外,代碼這塊目前也談不上多麼規範,請輕拍,後期抽時間部分代碼都會進行整合調整。後面想到的功能會按期更新,長期維護。軟件純綠色版,基於MIT協議開源,也可自行修改。

源碼地址

碼雲:https://gitee.com/shenniu_code_group/mn-niu-video

github:https://github.com/realyrare/MnNiuVideo

相關文章
相關標籤/搜索