天天看點

Stream Player Control-流播放器控件

原文位址: https://www.codeproject.com/articles/885869/stream-player-control

在本文中,您将會找到一個流播放器控件的實作。

  • Download WPF demo - 12.9 MB
  • Download WinForms demo - 12.6 MB
  • Download WPF source - 10.2 KB
  • Download WinForms source - 11.7 KB
  • Download FFmpeg Facade source - 17.8 KB
Stream Player Control-流播放器控件

介紹

這篇文章是我前一篇文章的一個延續,它展示了一個網絡攝像頭控件的實作。 最近我建立了另一個控件,并希望與社群分享我的經驗。 這是一個基于FFmpeg的流播放器控件,可以執行以下操作:

  1. 播放 RTSP/RTMP視訊流或本地視訊檔案
  2. 檢索控件顯示的目前幀

該控件沒有額外的依賴和一個簡約的界面。

必要條件

  1. 該控件的WinForms版本是使用.NET Framework 2.0實作的
  2. 該控件的WPF版本是使用.NET Framework 4 Client Profile實作的

該控件支援x86和x64平台目标。

背景

現在通過網際網路流式傳輸音頻,視訊和資料是非常平常的事情。 但是,當我試圖找到一個.NET控件來播放通過網絡發送的視訊流時,我幾乎找不到任何東西。 這個項目試圖填補這個空白。

實作細節

如果你對實作細節不感興趣,那麼你可以跳過這一節。

實施分為三層。

  1. 底層被實作為本地DLL子產品,它将我們的調用轉發給FFmpeg架構。
  2. 為了便于分發,原生DLL子產品作為資源嵌入到控件的程式集中。 在運作時階段,DLL子產品将被提取到磁盤上的臨時檔案,并通過後期綁定技術使用。 一旦控制權被處置,臨時檔案将被删除。 換句話說,該控件是作為一個單獨的檔案分發的。 所有這些操作都是由中間層來實作的。
  3. 頂層實作控制類本身。

下圖顯示了實作的邏輯結構。

Stream Player Control-流播放器控件

隻有頂層應該被客戶使用。

底層

底層使用facade 模式來為FFmpeg架構提供一個簡化的接口。 門面由三個類組成:StreamPlayer類,它實作了流播放功能

/// <summary>
/// The StreamPlayer class implements a stream playback functionality.
/// </summary>
class StreamPlayer : private boost::noncopyable
{
public:

    /// <summary>
    /// Initializes a new instance of the StreamPlayer class.
    /// </summary>
    StreamPlayer();

    /// <summary>
    /// Initializes the player.
    /// </summary>
    /// <param name="playerParams">The StreamPlayerParams object that contains the information that is used to initialize the player.</param>
    void Initialize(StreamPlayerParams playerParams);

    /// <summary>
    /// Asynchronously plays a stream.
    /// </summary>
    /// <param name="streamUrl">The url of a stream to play.</param>
    void StartPlay(std::string const& streamUrl);
    
    /// <summary>
    /// Retrieves the current frame being displayed by the player.
    /// </summary>
    /// <param name="bmpPtr">Address of a pointer to a byte that will receive the DIB.</param>
    void GetCurrentFrame(uint8_t **bmpPtr);

    /// <summary>
    /// Retrieves the unstretched frame size, in pixels.
    /// </summary>
    /// <param name="widthPtr">A pointer to an int that will receive the width.</param>
    /// <param name="heightPtr">A pointer to an int that will receive the height.</param>
    void GetFrameSize(uint32_t *widthPtr, uint32_t *heightPtr);

    /// <summary>
    /// Uninitializes the player.
    /// </summary>
    void Uninitialize();
};      

Stream類将視訊流轉換為一系列幀

/// <summary>
/// A Stream class converts a stream into series of frames. 
/// </summary>
class Stream : private boost::noncopyable
{
public:
    /// <summary>
    /// Initializes a new instance of the Stream class.
    /// </summary>
    /// <param name="streamUrl">The url of a stream to decode.</param>
    Stream(std::string const& streamUrl);

    /// <summary>
    /// Gets the next frame in the stream.
    /// </summary>
    /// <returns>The next frame in the stream.</returns>
    std::unique_ptr<Frame> GetNextFrame();

    /// <summary>
    /// Gets an interframe delay, in milliseconds.
    /// </summary>
    int32_t InterframeDelayInMilliseconds() const;

    /// <summary>
    /// Releases all resources used by the stream.
    /// </summary>
    ~Stream();
};      

Frame類,它是一組架構相關的工具。

/// <summary>
/// The Frame class implements a set of frame-related utilities. 
/// </summary>
class Frame : private boost::noncopyable
{
public:
    /// <summary>
    /// Initializes a new instance of the Frame class.
    /// </summary>
    Frame(uint32_t width, uint32_t height, AVPicture &avPicture);

    /// <summary>
    /// Gets the width, in pixels, of the frame.
    /// </summary>
    uint32_t Width() const { return width_; }

    /// <summary>
    /// Gets the height, in pixels, of the frame.
    /// </summary>
    uint32_t Height() const { return height_; }

    /// <summary>
    /// Draws the frame.
    /// </summary>
    /// <param name="window">A container window that frame should be drawn on.</param>
    void Draw(HWND window);

    /// <summary>
    /// Converts the frame to a bitmap.
    /// </summary>
    /// <param name="bmpPtr">Address of a pointer to a byte that will receive the DIB.</param>
    void ToBmp(uint8_t **bmpPtr);

    /// <summary>
    /// Releases all resources used by the frame.
    /// </summary>
    ~Frame();
};      

這些樹類是FFmpeg Facade DLL子產品的核心。

中間層

中間層由StreamPlayerProxy類實作,該類用作FFmpeg Facade DLL子產品的代理。

首先,我們應該從資源中提取FFmpeg Facade DLL子產品并将其儲存到一個臨時檔案中。

_dllFile = Path.GetTempFileName();
using (FileStream stream = new FileStream(_dllFile, FileMode.Create, FileAccess.Write))
{
    using (BinaryWriter writer = new BinaryWriter(stream))
    {
        writer.Write(Resources.StreamPlayer);
    }
}      

然後我們将DLL子產品加載到調用程序的位址空間中。

_hDll = LoadLibrary(_dllFile);
if (_hDll == IntPtr.Zero)
{
    throw new Win32Exception(Marshal.GetLastWin32Error());
}      

并将DLL子產品函數綁定到類執行個體方法。

private delegate Int32 StopDelegate();
private StopDelegate _stop;

// ...

IntPtr procPtr = GetProcAddress(_hDll, "Stop");
_stop =
    (StopDelegate)Marshal.GetDelegateForFunctionPointer(procPtr, 
     typeof(StopDelegate));      

在處理控件時,我們解除安裝DLL子產品并将其删除。

private void Dispose()
{    
    if (_hDll != IntPtr.Zero)
    {
        FreeLibrary(_hDll);
        _hDll = IntPtr.Zero;
    }

    if (File.Exists(_dllFile))
    {
        File.Delete(_dllFile);
    }    
}      

頂層

頂層由具有以下接口的StreamPlayerControl類實作。

/// <summary>
/// Asynchronously plays a stream.
/// </summary>
/// <param name="uri">The url of a stream to play.</param>
/// <exception cref="ArgumentException">An invalid string is passed as an argument.</exception>
/// <exception cref="Win32Exception">Failed to load the FFmpeg facade dll.</exception>
/// <exception cref="StreamPlayerException">Failed to play the stream.</exception>
public void StartPlay(Uri uri)

/// <summary>
/// Retrieves the image being played.
/// </summary>
/// <returns>The current image.</returns>
/// <exception cref="InvalidOperationException">The control is not playing a video stream.</exception>
/// <exception cref="StreamPlayerException">Failed to get the current image.</exception>
public Bitmap GetCurrentFrame();

/// <summary>
/// Stops a stream.
/// </summary>
/// <exception cref="InvalidOperationException">The control is not playing a stream.</exception>
/// <exception cref="StreamPlayerException">Failed to stop a stream.</exception>
public void Stop();

/// <summary>
/// Gets a value indicating whether the control is playing a video stream.
/// </summary>
public Boolean IsPlaying { get; }

/// <summary>
/// Gets the unstretched frame size, in pixels.
/// </summary>
public Size VideoSize  { get; }

/// <summary>
/// Occurs when the first frame is read from a stream.
/// </summary>
public event EventHandler StreamStarted;

/// <summary>
/// Occurs when there are no more frames to read from a stream.
/// </summary>
public event EventHandler StreamStopped;

/// <summary>
/// Occurs when the player fails to play a stream.
/// </summary>
public event EventHandler StreamFailed;      

使用

打開程式包管理器控制台,并将Nuget程式包添加到您的項目中:

Install-Package WebEye.Controls.WinForms.StreamPlayerControl      

首先,我們需要将控件添加到Visual Studio Designer工具箱,使用右鍵單擊,然後選擇“選擇項目...”菜單項。 然後,我們将控件放置在所需的位置并具有所需的大小。 控件執行個體變量的預設名稱将是streamPlayerControl1。

以下代碼使用提供的位址異步播放流。

streamPlayerControl1.StartPlay(new Uri("rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov"));      

還有一個選項可以指定連接配接逾時和底層傳輸協定。

streamPlayerControl1.StartPlay(new Uri("rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov"),
    TimeSpan.FromSeconds(15), RtspTransport.UdpMulticast);      

要獲得正在播放的幀,隻需調用GetCurrentFrame()方法即可。 幀的分辨率和品質取決于流的品質。

using (Bitmap image = streamPlayerControl1.GetCurrentFrame())
{
    // image processing...
}      

要停止流,使用Stop()方法。

streamPlayerControl1.Stop();      

您可以随時使用以下代碼檢查播放狀态。

if (streamPlayerControl1.IsPlaying)
{
    streamPlayerControl1.Stop();
}      

此外,StreamStarted,StreamStopped和StreamFailed事件可用于監視播放狀态。

要報告錯誤,使用異常,是以不要忘記将代碼包裝在try / catch塊中。 這就是使用它。 要檢視完整的示例,請檢視示範應用程式來源。

WPF版本

FFmpeg外觀需要一個WinAPI視窗句柄(HWND)才能将其用作渲染目标。 問題是,在WPF世界的窗戶不再有處理。 VideoWindow類解決了這個問題。

<UserControl x:Class="WebEye.StreamPlayerControl"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

             mc:Ignorable="d" 

             d:DesignHeight="300" d:DesignWidth="300"

             xmlns:local="clr-namespace:WebEye">
    <local:VideoWindow x:Name="_videoWindow"

                       HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</UserControl>      

要将WPF版本的控件添加到項目中,請使用以下nuget指令:

Install-Package WebEye.Controls.Wpf.StreamPlayerControl      

Github

該項目在下一頁提供了一個GitHub倉庫。https://github.com/jacobbo/WebEye/tree/master/StreamPlayerControl

任何問題,評論和意見都是值得歡迎的。

外部許可

  1. FFmpegfacade源與FFmpeg架構相同,在LGPL許可下獲得許可。
  2. .NET控件的源代碼和示範源在“代碼項目開放許可證”(CPOL)下獲得許可。

您可以在您的商業産品中使用該控件,唯一的一點是您應該提及您的産品使用FFmpeg庫,這裡是詳細資訊。

曆史 

  • March 19th, 2015 - The initial version.
  • August 22nd, 2015 - Added the x64 platform support.
  • October 25th, 2015 - Added asyncronous stream start and stream status events.
  • November 8th, 2015 - Added support for local files playback.
  • November 30th, 2015 - Added stream connection timeout.
  • October 17th, 2017 - Use new FFmpeg decoding API.

本文許可

本文以及任何相關的源代碼和檔案均已獲得“ 代碼項目開放許可證”(CPOL)的許可。