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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...