WPF另類實現攝像頭錄像

来源:https://www.cnblogs.com/wdw984/archive/2020/02/09/12286414.html
-Advertisement-
Play Games

WPF中使用第三方控制項來直接進行錄像的控制項沒有找到(aforgenet好像不維護了?WPFMediaKit好像只能實現攝像頭拍照。收費的控制項沒有使用,不做評論。) 通過百度(感謝:https://www.cnblogs.com/giserlong88/p/11244779.html),確定了可以通過 ...


WPF中使用第三方控制項來直接進行錄像的控制項沒有找到(aforgenet好像不維護了?WPFMediaKit好像只能實現攝像頭拍照。收費的控制項沒有使用,不做評論。)

通過百度(感謝:https://www.cnblogs.com/giserlong88/p/11244779.html),確定了可以通過FFmpeg+Nginx+Vlc.DotNet.Wpf可以實現攝像頭的錄像保存、錄像預覽(有延時),實現方案是,通過FFmpeg來實現錄像並推送到Nginx搭建的rtmp流媒體伺服器,然後WPF通過Vlc.DotNet.Wpf來拉取rtmp流伺服器的內容來實現視頻預覽。

具體代碼如下:

首先去下載FFmpeg(http://ffmpeg.org/download.html),Nginx(http://nginx.org/en/download.html),Nuget上引用Vlc.DotNet.Wpf,下載其所需要的libvlc播放器

nginx-win-rtmp.conf配置文件內容如下:
#user  nobody;
# multiple workers works !
worker_processes  2;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;
#worker_rlimit_nofile 100000;   #更改worker進程的最大打開文件數限制
                                               #如果沒設置的話, 這個值為操作系統的限制.
                                               #設置後你的操作系統和Nginx可以處理比“ulimit -a”更多的文件
                                               #所以把這個值設高, 這樣nginx就不會有“too many open files”問題了


events {
    worker_connections  8192;#設置可由一個worker進程同時打開的最大連接數
                              #如果設置了上面提到的worker_rlimit_nofile, 我們可以將這個值設得很高
    # max value 32768, nginx recycling connections+registry optimization = 
    #   this.value * 20 = max concurrent connections currently tested with one worker
    #   C1000K should be possible depending there is enough ram/cpu power
    # multi_accept on;
}

rtmp {
    server {
        listen 1935;#監聽埠,若被占用,可以更改
        chunk_size 4000;#上傳flv文件塊兒的大小
        application live { #創建一個叫live的應用
             live on;#開啟live的應用
             allow publish 127.0.0.1;#
             allow play all;
        }
    }
}

http {
    #include      /nginx/conf/naxsi_core.rules;
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr:$remote_port - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

#     # loadbalancing PHP
#     upstream myLoadBalancer {
#         server 127.0.0.1:9001 weight=1 fail_timeout=5;
#         server 127.0.0.1:9002 weight=1 fail_timeout=5;
#         server 127.0.0.1:9003 weight=1 fail_timeout=5;
#         server 127.0.0.1:9004 weight=1 fail_timeout=5;
#         server 127.0.0.1:9005 weight=1 fail_timeout=5;
#         server 127.0.0.1:9006 weight=1 fail_timeout=5;
#         server 127.0.0.1:9007 weight=1 fail_timeout=5;
#         server 127.0.0.1:9008 weight=1 fail_timeout=5;
#         server 127.0.0.1:9009 weight=1 fail_timeout=5;
#         server 127.0.0.1:9010 weight=1 fail_timeout=5;
#         least_conn;
#     }

    sendfile        off;
    #tcp_nopush     on;

    server_names_hash_bucket_size 128;

## Start: Timeouts ##
    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;
## End: Timeouts ##

    #gzip  on;

    server {
        listen       8088;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        ## Caching Static Files, put before first location
        #location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        #    expires 14d;
        #    add_header Vary Accept-Encoding;
        #}

# For Naxsi remove the single # line for learn mode, or the ## lines for full WAF mode
        location / {
            #include    /nginx/conf/mysite.rules; # see also http block naxsi include line
            ##SecRulesEnabled;
              ##DeniedUrl "/RequestDenied";
              ##CheckRule "$SQL >= 8" BLOCK;
              ##CheckRule "$RFI >= 8" BLOCK;
              ##CheckRule "$TRAVERSAL >= 4" BLOCK;
              ##CheckRule "$XSS >= 8" BLOCK;
            root   html;
            index  index.html index.htm;
        }

# For Naxsi remove the ## lines for full WAF mode, redirect location block used by naxsi
        ##location /RequestDenied {
        ##    return 412;
        ##}

## Lua examples !
#         location /robots.txt {
#           rewrite_by_lua '
#             if ngx.var.http_host ~= "localhost" then
#               return ngx.exec("/robots_disallow.txt");
#             end
#           ';
#         }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000; # single backend process
        #    fastcgi_pass   myLoadBalancer; # or multiple, see example above
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl spdy;
    #    server_name  localhost;

    #    ssl                  on;
    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_timeout  5m;

    #    ssl_prefer_server_ciphers On;
    #    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    #    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!eNULL:!MD5:!DSS:!EXP:!ADH:!LOW:!MEDIUM;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

 

把下載的FFmpeg、Nginx和libvlc放到Debug目錄下。

目錄結構如下

Debug

    FFmpeg

         ffmpeg.exe

         ……

   Nginx

        nginx.exe

        ……

   libvlc

         win-x64

            ……

         win-x86

           ……

新建一個WPF項目,在MainWindow.xaml主要處理啟動Nginx和進行推送

<Window x:Class="VideTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <WrapPanel>
            <TextBox Name="SavePath" Text="D:\test.mp4" Width="94" HorizontalAlignment="Left"></TextBox>
            <TextBox Name="VideoName" Text="羅技高清網路攝像機 C930c" Width="94" HorizontalAlignment="Left"></TextBox>
            <TextBox Name="AudioName" Text="麥克風 (羅技高清網路攝像機 C930c)" Width="94" HorizontalAlignment="Left"></TextBox>
            <Button Content="1、啟動視頻監控" HorizontalAlignment="Left"  VerticalAlignment="Top" Width="107" Click="ButtonStart_OnClick"/>
            <Button Content="2、開始錄製" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="ButtonSase_OnClick"/>
            <TextBlock Text="開始錄製後大概5秒主界面就可以看到監控視頻"></TextBlock>
        </WrapPanel>
        <Border Grid.Row="1">
            <Image x:Name="img"></Image>
        </Border>
        
    </Grid>
</Window>

  

文本框中的攝像頭和麥克風,是使用下發的Load中的命令檢測到的。

後臺代碼:

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Vlc.DotNet.Core;
using Vlc.DotNet.Wpf;


namespace VideTest
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly string ffmpegPath = $"{AppDomain.CurrentDomain.BaseDirectory}FFmpeg/ffmpeg.exe";
        private readonly string nginxPath = @"nginx.exe -c conf\nginx-win-rtmp.conf";
        private VlcVideoSourceProvider sourceProvider;
        public MainWindow()
        {
            InitializeComponent();
        }
        private void MediaPlayer_Log(object sender, VlcMediaPlayerLogEventArgs e)
        {
            var message = "libVlc : " + e.Level + e.Message + e.Module;
            Debug.WriteLine(message);
        }

        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {  
            //var ffmpegPath = $"{AppDomain.CurrentDomain.BaseDirectory}FFmpeg/ffmpeg.exe";
            //// 顯示可用的音效設備
            //var ffmpegArgument = " -list_devices true -f dshow -i dummy";

            //var process = new System.Diagnostics.Process();
            //var startInfo = new System.Diagnostics.ProcessStartInfo();
            //startInfo.FileName = ffmpegPath;
            //startInfo.Arguments = ffmpegArgument;
            //startInfo.UseShellExecute = false;
            //startInfo.RedirectStandardOutput = true;
            //startInfo.RedirectStandardError = true;

            // 將 StandardErrorEncoding 改為 UTF-8後FFmpeg輸出不會中文亂碼
            //startInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;

            //process.EnableRaisingEvents = true;
            //process.StartInfo = startInfo;
            //process.Start();

            // 顯示FFMpeg輸出的內容,從中取出視頻和音頻設備名稱
            //string output = process.StandardError.ReadToEnd();
            //Debug.WriteLine(output);
            //process.WaitForExit();
        }

        private void ButtonSase_OnClick(object sender, RoutedEventArgs e)
        {
            var file=new FileInfo(SavePath.Text);
            if(file.Exists) file.Delete();
            var ffmpegArgument = $" -f dshow -i video=\"{VideoName.Text}\" -f dshow -i audio=\"{AudioName.Text}\" -vcodec libx264 -acodec aac -strict -2 \"{SavePath.Text}\" -f flv rtmp://127.0.0.1:1935/live/home";
            Task.Run(() =>
            {
                var process = new Process();
                var startInfo = new ProcessStartInfo
                {
                    FileName = ffmpegPath,
                    Arguments = ffmpegArgument,
                    UseShellExecute = true,
                    RedirectStandardOutput = false

                };
                process.StartInfo = startInfo;
                process.Start();
                process.WaitForExit();
            });
        }

        private void ButtonStart_OnClick(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                var process = new Process();
                var startInfo = new ProcessStartInfo("cmd.exe")
                {
                    WorkingDirectory= $@"{AppDomain.CurrentDomain.BaseDirectory}nginx",
                    UseShellExecute = false,
                    RedirectStandardInput = true
                };
                process.StartInfo = startInfo;
                
                process.Start();
                process.StandardInput.WriteLine(nginxPath);
                process.StandardInput.AutoFlush = true;
                process.WaitForExit();
            });
            Dispatcher?.Invoke(() =>
            {
                var currentAssembly = Assembly.GetEntryAssembly();
                var currentDirectory = new FileInfo(currentAssembly.Location).DirectoryName;

                var libDirectory = new DirectoryInfo(System.IO.Path.Combine(currentDirectory, "libvlc",
                    IntPtr.Size == 4 ? "win-x86" : "win-x64"));

                sourceProvider = new VlcVideoSourceProvider(Dispatcher);
                sourceProvider.CreatePlayer(libDirectory);
                sourceProvider.MediaPlayer.Play("rtmp://127.0.0.1:1935/live/home");
                sourceProvider.MediaPlayer.Log += MediaPlayer_Log;
                sourceProvider.MediaPlayer.Manager.SetFullScreen(sourceProvider.MediaPlayer.Manager.CreateMediaPlayer(),
                    true);
                var bing = new Binding {Source = sourceProvider, Path = new PropertyPath("VideoSource")};
                img.SetBinding(Image.SourceProperty, bing);
            });
            MessageBox.Show("啟動成功,請點擊開始錄製。");
        }
    }
}

 

這樣按順序點擊1和2的按鈕後,即可實現WPF的視頻錄製和預覽錄製的視頻內容。

同時我們在APP.cs中重寫退出事件,來在程式退出的時候結束Nginx進行。

 public partial class App : Application
    {
        protected override void OnExit(ExitEventArgs e)
        {
            var process = new Process();
            var startInfo = new ProcessStartInfo()
            {
                FileName = "taskkill",
                Arguments = " /f /im nginx.exe",
                UseShellExecute = false,
                RedirectStandardInput = true
            };
            process.StartInfo = startInfo;

            process.Start();
            process.WaitForExit();
        }
    }

  至此,我們就變現實現了WPF進行視頻錄製和預覽錄製的視頻內容的功能。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 構造方法用於對象的初始化!靜態初始化塊,用於類的初始化操作!在靜態初始化塊中不能直接訪問非static成員。 註意事項: 靜態初始化塊執行順序(學完繼承再看這裡): 1. 上溯到Object類,先執行Object的靜態初始化塊,再向下執行子類的靜態初始化塊,直到我們的類的靜態初始化塊為止。 2. 構 ...
  • 在類中,用static聲明的成員變數為靜態成員變數,也稱為類變數。 類變數的生命周期和類相同,在整個應用程式執行期間都有效。它有如下特點: 1. 為該類的公用變數,屬於類,被該類的所有實例共用,在類被載入時被顯式初始化。 2. 對於該類的所有對象來說,static成員變數只有一份。被該類的所有對象共 ...
  • 首先考慮沒有限制的情況 當硬幣被限制數量,需要加入對硬幣情況的考慮 所以設dp[][] 記錄湊齊x的種類 代碼如下 #include <iostream>using namespace std;int main(){ int dp[251][101]={0},ans[251]={0}; int ty ...
  • 類的派生、多態、抽象類、介面 1:派生-extends 派生就是繼承已有類非私有的欄位和方法等創建新的類,還可以添加、重寫欄位和方法; 在類的派生中,構造函數不可以被繼承; 派生源的類-父類/基類/超類;派生的類-子類/派生類; 2:super(…)調用超類的構造函數,在子類構造函數的開頭; sup ...
  • 第一、瞭解記憶體空間 本文章文字有點多,會有點枯燥,配合圖文一起看可以緩解枯燥,耐心閱讀哦!!! 先瞭解記憶體地址,才更好的理解指針! 我們可以把記憶體想象為成一列很長很長的貨運火車,有很多大小相同的車廂,而每個車廂正好相當於在記憶體中表示一個位元組。這些車廂裝著不同的貨物,就像我們的記憶體要存著各式各樣的數據 ...
  • WPF允許在代碼中以及在標記中的各個位置定義資源(和特定的控制項、視窗一起定義,或在整個應用程式中定義)。 資源具有許多重要的優點,如下所述: 高效。可以通過資源定義對象,併在標記中的多個地方使用。這會精簡代碼,使其更加高效。 可維護性。可通過資源使用低級的格式化細節(如字型大小),並將它們移到便於對其進 ...
  • 前言 之前的博客我已經在Linux上部署好了.NetCore站點且通過Supervisor對站點進行了進程守護,同時也安裝好了Nginx。Nginx的用處非常大,還是簡單說下,它最大的功能就是方便我們做後續的橫向拓展,當站點的流量越來越大時候可以進行負載均衡,反向代理最大用處也是如此。 反向代理反向 ...
  • 前面兩章介紹了命令的基本內容,可考慮一些更複雜的實現了。接下來介紹如何使用自己的命令,根據目標以不同方式處理相同的命令以及使用命令參數,還將討論如何支持基本的撤銷特性。 一、自定義命令 在5個命令類(ApplicationCommands、NavigationCommands、EditingComm ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...