前言
视频播放在上位机开发中经常会遇到,基本上是两种常见的解决方案

1.采用厂家提供的sdk和前端控件进行展示,常见的海康/大华都提供了相关sdk及文档

2.开启相机onvif协议,捅过rtsp视频流进行播放,前端可以采用web方式,或者wpf中的视频控件进行展示。

项目需求,决定了最终采用开启相机onvif供能,wpf中播放的方式。

网络调研一阵子之后,基本都是推荐Vlc.DotNet或者libvlcsharp.wpf进行前端展示。

参考了很多代码,无论是官方文档,还是不同博客里的代码,很难做到用mvvm的方式对于逻辑解耦。

而且Vlc.DotNet已经不再更新了。

Libvlcasharp.wpf的设计有些反人类,可以参考这篇文章WPF中使用LibVLCSharp.WPF 播放rtsp - Naylor - 博客园 (cnblogs.com)

所以这部分逻辑写的很难受,需要寻找其他方案。

最近有空了,调研了几个其他开源项目,大家的思路都比较一致,相机打开onvif协议推送rtsp视频流,本地通过ffmpeg进行视频转流,然后推送到wpf前端控件上。

unosquare/ffmediaelement: FFME: The Advanced WPF MediaElement (based on FFmpeg) (github.com)

SuRGeoNix/Flyleaf: Media Player .NET Library for WinUI 3/ WPF/WinForms (based on FFmpeg/DirectX) (github.com)

网上有FFME的样例代码,我在本地搭建没有成功,应该是我的ffmpeg编译版本问题,可以参考这个项目。

DG-Wangtao/FFMEVideoPlayer: 使用FFmepg封装的WPF MideaElement,可以播放rtsp视频流。感谢 https://github.com/unosquare/ffmediaelement

最终选择了Flyleaf的方案,简单搭建了demo给大家参考。

Flyleaf官方项目地址SuRGeoNix/Flyleaf: Media Player .NET Library for WinUI 3/ WPF/WinForms (based on FFmpeg/DirectX) (github.com)

MVVM框架使用的是CommunityToolKit.MVVM

正文

Flyleaf的使用整体分成四步走,

1.App.xaml及App.xaml.cs中配置ffmpeg的dll文件地址;

1.1ffmpeg的dll文件,我才用的是Flyleaf官方sample中的文件,版本不是最新的。

1.2文件统一放在项目中的FFmpeg文件夹中

1.3生成操作(Build Action)配置为 无(None)

1.4复制到输出目录(Copy to Output Directory)配置为 如果较新则复制(Copy if newer)

1.5App.xaml中添加startup事件

    <Application x:Class="FlyleafDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FlyleafDemo"
StartupUri="MainWindow.xaml"
Startup="Application_Startup">
<Application.Resources> </Application.Resources>
</Application>

1.6App.xaml.cs中配置ffmpeg的dll路径,项目编译后会复制ffmpeg文件夹及dll。

    using FlyleafLib;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows; namespace FlyleafDemo
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Engine.Start(new EngineConfig()
{
FFmpegPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FFmpeg"),
FFmpegDevices = false, // Prevents loading avdevice/avfilter dll files. Enable it only if you plan to use dshow/gdigrab etc. #if RELEASE
FFmpegLogLevel = FFmpegLogLevel.Quiet,
LogLevel = LogLevel.Quiet, #else
FFmpegLogLevel = FFmpegLogLevel.Warning,
LogLevel = LogLevel.Debug,
LogOutput = ":debug",
//LogOutput = ":console",
//LogOutput = @"C:\Flyleaf\Logs\flyleaf.log",
#endif //PluginsPath = @"C:\Flyleaf\Plugins", UIRefresh = false, // Required for Activity, BufferedDuration, Stats in combination with Config.Player.Stats = true
UIRefreshInterval = 250, // How often (in ms) to notify the UI
UICurTimePerSecond = true, // Whether to notify UI for CurTime only when it's second changed or by UIRefreshInterval
});
}
}
}

2.ViewModel中配置参数等信息;

    using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FlyleafLib.MediaPlayer;
using FlyleafLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media; namespace FlyleafDemo
{
public class MainViewModel:ObservableObject
{
private Player player; public Player Player
{
get => player;
set => SetProperty(ref player, value);
} private Config config; public Config Config
{
get => config;
set => SetProperty(ref config, value);
} private string uriString; public string UriString
{
get => uriString;
set => SetProperty(ref uriString, value);
} public IRelayCommand<string> PlayCommand { get; set; }
public MainViewModel()
{
Config = new Config();
Config.Video.BackgroundColor = Colors.Transparent;
// 设置播放延迟为100ms,可能我理解有误,具体可以在项目issues里查看
// Config.Player.MaxLatency = 100 * 10000; Player = new Player(Config);
PlayCommand = new RelayCommand<string>(PlayAction);
UriString = uri1;
} private string currentUri = string.Empty;
private string uri1 = "rtsp://192.168.20.2:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif";
private string uri2 = "rtsp://192.168.20.3:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif";
private void PlayAction(string uri)
{
if (!string.IsNullOrEmpty(uri))
{
if (currentUri == uri1)
{
//Player.Commands.Stop.Execute(null);
currentUri = uri2;
Player.Commands.Open.Execute(uri2);
}
else
{
//Player.Commands.Stop.Execute(null);
currentUri = uri1;
Player.Commands.Open.Execute(uri1);
}
}
}
}
}

3.View中配置布局等信息;

    <Window
x:Class="FlyleafDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fl="clr-namespace:FlyleafLib.Controls.WPF;assembly=FlyleafLib"
xmlns:local="clr-namespace:FlyleafDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<fl:FlyleafHost
AttachedDragMove="Both"
KeyBindings="Both"
Player="{Binding Player, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<Viewbox>
<TextBlock Foreground="DarkOrange" Text="Hello Flyleaf Overlay!" />
</Viewbox>
</fl:FlyleafHost>
<Button
Grid.Row="1"
Command="{Binding PlayCommand}"
CommandParameter="{Binding UriString, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</Grid>
</Window>

4.在xaml.cs中确定View和ViewModel的绑定关系

    using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace FlyleafDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}

总结

前端控件绑定比较方便,减少了在xaml.cs中的耦合逻辑
我尝试过三路视频同时播放,效果不错,系统资源消耗也不高
很多参数都可以在Config中配置,一些交互逻辑可以在Player中执行,比较清晰
但是,单视频控件切换视频流的时候,会有一定时间延迟,我尝试过使用
Player.Commands.Stop.Execute(null);
但效果不大。
感兴趣的可以深挖源码,我这里只是抛砖引玉。
Demo效果图如下

————————————————
版权声明:本文为CSDN博主「maoleigepu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/maoleigepu/article/details/133268837

WPF中以MVVM方式,实现RTSP视频播放的更多相关文章

  1. WPF中使用MVVM模式进行简单的数据绑定

    计划慢慢整理自己在WPF学习和工作应用中的一些心得和想法,先从一个简单的用法说起 在WPF中,XAML标记语言中绑定数据,而数据源就是指定为ViewModel类,而非界面本身的逻辑代码类 这样一定程度 ...

  2. 简单的介绍下WPF中的MVVM框架

    最近在研究学习Swift,苹果希望它迅速取代复杂的Objective-C开发,引发了一大堆热潮去学它,放眼望去各个培训机构都已打着Swift开发0基础快速上手的招牌了.不过我觉得,等同于无C++基础上 ...

  3. WPF中资源引用方式汇总

    在WPF应用程序开发中,总是难以记住各种访问资源的方法,遂逐一记下. 先从资源是否编译到程序集分类 一.程序集资源 资源在编译的时候嵌入到程序集中.WPF中的XAML会被编译为BAML,图片等其他资源 ...

  4. WPF中在MVVM模式下,后台绑定ListCollectionView事件触发问题

    问题:WPF中MVVM模式下 ListView绑定ListCollectionView时,CurrentChanged无法触发 解决方案: 初期方案:利用ListView的SelectionChang ...

  5. 浅谈WPF中的MVVM框架--MVVMFoundation

    先科普一下:什么是WPF,请看下图 微软对于WPF技术的构想是很宏大的,可惜普及率不高,不过如果你要做Windows客户端开发的话WPF技术还是值得一学的. 什么是MVVM模式 简单来说它是一种高级的 ...

  6. WPF 中使用MVVM模式后,找回ListBox中的ListBoxItem元素

    ListBoxItem lstitem = this.list.ItemContainerGenerator.ContainerFromItem(m) as ListBoxItem; 其中this.l ...

  7. WPF 中的绑定方式

    1.元素间的绑定 xaml方式 <Slider Name="slider1" Value="20"/>        <TextBlock T ...

  8. WPF中使用MVVM进行multibinding

    背景描述:在Number1和Number2文本框中输入数字后,在Answer文本框中会按照下图所示显示. xaml代码: <Window.Resources> <local:MyVa ...

  9. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  10. WPF中使用LibVLCSharp.WPF 播放rtsp

    目录 LibVLCSharp.WPF简介 vlc:VideoView基本使用 安装LibVLC 播放rtsp 引入命名空间 xaml 代码 cs代码 截图 概述 代码示例 vlc:VideoView进 ...

随机推荐

  1. 10.1. Java性能调优

    Java性能调优是一个复杂且重要的主题,它涉及到了JVM.垃圾收集器.内存管理.多线程.代码优化等多个方面.在本节中,我们将对Java性能调优的基本概念和方法进行简要介绍. 10.1.1. 理解性能指 ...

  2. C#与WPF中相关字符串操作

    字符串指定字符查找 例如:输入一个邮箱地址,如果正确则显示success否则显示error(正确的邮箱地址包含@,以.com结尾) //接受输入进来的字符串 string s=this.txtEmsi ...

  3. Java 设计模式实战系列—工厂模式

    在 Java 开发中,对象的创建是一个常见的场景,如果对象的创建和使用都写在一起,代码的耦合度高,也不利于后期的维护.我们可以使用工厂模式来解决这个问题,工厂模式是一个创建型模式,将对象的创建和使用分 ...

  4. CAN转PROFINET协议网关 JM-PN-CAN

        1 产品功能 捷米特JM-PN-CAN 是自主研发的一款 PROFINET 从站功能的通讯网关.主要功能是将各种 CAN 设备接入到 PROFINET 网络中. 捷米特JM-PN-CAN连接到 ...

  5. Map集合_HashMap_TreeMap_等_小记

    Map是一种依照键值对数据存储元素的容器. Map中的元素是两个对象,一个对象作为键,一个对象作为值.一个键(key)和它对应的值构成map集合中的一个元素.Map集合的数据结构只跟键有关,键不可以重 ...

  6. react项目中使用plop

    第一步,安装依赖 npm install plop --dev //或者用yarn yarn add plop --dev 第二步,在package同级目录下新建plopfile.js 这是plop的 ...

  7. 利用shell脚本交互式运行jar任务

    如题,废话不多说,直接上代码: #!/bin/bash APP_PATH=/root/bigdata/neural_networks/width_control_model/predict/uploa ...

  8. Centos静默安装Oracle11G

    环境准备 Oracle 11gR2 64位 Linux版安装包 linux.x64_11gR2_database_1of2.zip linux.x64_11gR2_database_2of2.zip ...

  9. docker部署zabbix 6.0高可用集群实验

    0 实验环境 虚拟机,postgresql本地部署,zabbix server及nginx容器部署 1 postgresql 参看前作 <postgresql + timescaledb离线安装 ...

  10. 通过Maxwell同步mariadb数据至kafka

    实验环境 本地虚拟机 maraidb 10.8.8 kafka 2.12-3.3.1 maxwell由容器部署 1 mariadb 1.1 配置log_bin 配置文件中加入如下内容 server-i ...