WPF下播放Rtmp的解决方案

介绍

在实际的开发过程中,需要在应用内部内嵌播放器进行视频的播放。官方默认的MediaElement控件只能播放有限的视频格式,也不能播放网络流。比较流行的解决方式是vlc的库,但是在实际使用过程中发现有很多问题。这里给大家推荐另一个比较好的库。

使用

官网地址

安装库

Nuget下安装FFME.Windows

bash 复制代码
PM> Install-Package FFME.Windows

下载ffmpeg依赖

注:官网给的地址我在实际使用中发现,使用官方的代码没问题,使用给定的步骤使用就会抱错,如果你们跟我一样给大家推荐另一个ffmpeg包的地址
ffmpeg依赖下载地址

代码

  1. 指定ffmpeg库的地址
csharp 复制代码
Unosquare.FFME.Library.FFmpegDirectory = @"C:\ffmpeg\ffmpeg-4.4-windows-desktop-vs2022-gpl-lite\bin";
  1. xaml中插入控件
csharp 复制代码
<Window x:Class="WpfApp1.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1" 
        xmlns:ffme="clr-namespace:Unosquare.FFME;assembly=ffme.win"
        mc:Ignorable="d"
        Loaded="Window_Loaded"
        Closed="Window_Closed"
        Title="MainWindow" Height="300" Width="600">
    <Grid>
        <ffme:MediaElement x:Name="Media" Background="Gray" LoadedBehavior="Play" UnloadedBehavior="Manual" />
        <Button Content="Play" Click="PlayButton_Click" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="10"/>
        <Button Content="Stop" Click="StopButton_Click" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="100,10,0,0"/>
    </Grid>
</Window>
  1. 监听失败的事件
csharp 复制代码
Media.MediaFailed += OnMediaFailed;
  1. 创建一个处理流地址的类FileInputStream
csharp 复制代码
namespace Unosquare.FFME.Windows.Sample.Foundation;

using Common;
using FFmpeg.AutoGen;
using System;
using System.IO;
using System.Runtime.InteropServices;

/// <inheritdoc />
/// <summary>
/// Provides an example of a very simple custom input stream.
/// </summary>
/// <seealso cref="IMediaInputStream" />
public sealed unsafe class FileInputStream : IMediaInputStream
{
    private readonly FileStream BackingStream;
    private readonly object ReadLock = new();
    private readonly byte[] ReadBuffer;

    /// <summary>
    /// Initializes a new instance of the <see cref="FileInputStream"/> class.
    /// </summary>
    /// <param name="path">The path.</param>
    public FileInputStream(string path)
    {
        var fullPath = Path.GetFullPath(path);
        BackingStream = File.OpenRead(fullPath);
        var uri = new Uri(fullPath);
        StreamUri = new Uri(uri.ToString().ReplaceOrdinal("file://", Scheme));
        CanSeek = true;
        ReadBuffer = new byte[ReadBufferLength];
    }

    /// <summary>
    /// The custom file scheme (URL prefix) including the :// sequence.
    /// </summary>
    public static string Scheme => "customfile://";

    /// <inheritdoc />
    public Uri StreamUri { get; }

    /// <inheritdoc />
    public bool CanSeek { get; }

    /// <inheritdoc />
    public int ReadBufferLength => 1024 * 16;

    /// <inheritdoc />
    public InputStreamInitializing OnInitializing { get; }

    /// <inheritdoc />
    public InputStreamInitialized OnInitialized { get; }

    /// <inheritdoc />
    public void Dispose()
    {
        BackingStream?.Dispose();
    }

    /// <summary>
    /// Reads from the underlying stream and writes up to <paramref name="targetBufferLength" /> bytes
    /// to the <paramref name="targetBuffer" />. Returns the number of bytes that were written.
    /// </summary>
    /// <param name="opaque">The opaque.</param>
    /// <param name="targetBuffer">The target buffer.</param>
    /// <param name="targetBufferLength">Length of the target buffer.</param>
    /// <returns>
    /// The number of bytes that have been read.
    /// </returns>
    public int Read(void* opaque, byte* targetBuffer, int targetBufferLength)
    {
        lock (ReadLock)
        {
            try
            {
                var readCount = BackingStream.Read(ReadBuffer, 0, ReadBuffer.Length);
                if (readCount > 0)
                    Marshal.Copy(ReadBuffer, 0, (IntPtr)targetBuffer, readCount);
                else if (readCount == 0)
                    return ffmpeg.AVERROR_EOF;

                return readCount;
            }
            catch (Exception)
            {
                return ffmpeg.AVERROR_EOF;
            }
        }
    }

    /// <inheritdoc />
    public long Seek(void* opaque, long offset, int whence)
    {
        lock (ReadLock)
        {
            try
            {
                return whence == ffmpeg.AVSEEK_SIZE ?
                    BackingStream.Length : BackingStream.Seek(offset, SeekOrigin.Begin);
            }
            catch
            {
                return ffmpeg.AVERROR_EOF;
            }
        }
    }
}
  1. 播放视频
csharp 复制代码
//var target = new Uri(@"rtmp://127.0.0.1/live/test1231233");
var target = new Uri(@"D:\视频\泥坑.mp4");
if (target.ToString().StartsWith(FileInputStream.Scheme, StringComparison.OrdinalIgnoreCase))
    await Media.Open(new FileInputStream(target.LocalPath));
else
    await Media.Open(target);
相关推荐
玉面小君1 天前
从 WPF 到 Avalonia 的迁移系列实战篇6:Trigger、MultiTrigger、DataTrigger 的迁移
wpf·avalonia
招风的黑耳2 天前
Java生态圈核心组件深度解析:Spring技术栈与分布式系统实战
java·spring·wpf
lfw20192 天前
WPF 数据绑定模式详解(TwoWay、OneWay、OneTime、OneWayToSource、Default)
wpf
Magnum Lehar2 天前
3d wpf游戏引擎的导入文件功能c++的.h实现
3d·游戏引擎·wpf
FuckPatience3 天前
WPF Telerik.Windows.Controls.Data.PropertyGrid 自定义属性编辑器
wpf
almighty273 天前
C#WPF控制USB摄像头参数:曝光、白平衡等高级设置完全指南
开发语言·c#·wpf·usb相机·参数设置
军训猫猫头4 天前
12.NModbus4在C#上的部署与使用 C#例子 WPF例子
开发语言·c#·wpf
我要打打代码4 天前
在WPF项目中使用阿里图标库iconfont
wpf
拾忆,想起5 天前
Redisson 分布式锁的实现原理
java·开发语言·分布式·后端·性能优化·wpf
weixin_464078075 天前
wpf依赖注入驱动的 MVVM实现(含免费源代码demo)
wpf