In this article you will find an implementation of a stream player control.

Introduction

This article is a sort of continuation of my previous article, which shows an implementation of a web camera control. Recently I created another control and would like to share my experience with community. It is a FFmpeg-based stream player control, which can do the following:

  1. Play a RTSP/RTMP video stream or local video file
  2. Retrieve the current frame being displayed by the control

The control has no additional dependencies and a minimalistic interface.

Requirements

  1. The WinForms version of the control is implemented using .NET Framework 2.0
  2. The WPF version of the control is implemented using .NET Framework 4 Client Profile

The control supports both x86 and x64 platform targets.

Background

Streaming audio, video and data over the Internet is a very usual thing these days. However, when I tried to find a .NET control to play a video stream sent over the network, I found almost nothing. This project tries to fill up that gap.

Implementation details

If you are not interested in implementation details, then you can skip this section.

The implementation is divided into three layers.

  1. The bottom layer is implemented as a native DLL module, which forwards our calls to the FFmpeg framework.
  2. For distribution convenience, the native DLL module is embedded into the control’s assembly as a resource. On the runtime stage, the DLL module will be extracted to a temporary file on disk and used via late binding technique. Once the control is disposed, the temporary file will be deleted. In other words, the control is distributed as a single file. All those operations are implemented by the middle layer.
  3. The top layer implements the control class itself.

The following diagram shows a logical structure of the implementation.

Only the top layer is supposed to be used by clients.

The Bottom Layer

The bottom layer uses the facade pattern to provide a simplified interface to the FFmpeg framework. The facade consists of three classes: the StreamPlayer class, which implements a stream playback functionality

Hide    Shrink     Copy Code
/// <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();
};

the Stream class, which converts a video stream into series of frames

Hide    Copy Code
/// <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();
};

and the Frame class, which is a set of frame related utilities.

Hide    Shrink     Copy Code
/// <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();
};

These tree classes form a heart of the FFmpeg Facade DLL module.

The Middle Layer

The middle layer is implemented by the StreamPlayerProxy class, which serves as a proxy to the FFmpeg Facade DLL module.

First, what we should do is extract the FFmpeg Facade DLL module from the resources and save it to a temporary file.

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

Then we load our DLL module into the address space of the calling process.

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

And bind the DLL module functions to the class instance methods.

Hide    Copy Code
private delegate Int32 StopDelegate();
private StopDelegate _stop; // ... IntPtr procPtr = GetProcAddress(_hDll, "Stop");
_stop =
(StopDelegate)Marshal.GetDelegateForFunctionPointer(procPtr,
typeof(StopDelegate));

When the control is being disposed, we unload the DLL module and delete it.

Hide    Copy Code
private void Dispose()
{
if (_hDll != IntPtr.Zero)
{
FreeLibrary(_hDll);
_hDll = IntPtr.Zero;
} if (File.Exists(_dllFile))
{
File.Delete(_dllFile);
}
}

The Top Layer

The top layer is implemented by the StreamPlayerControl class with the following interface.

Hide    Shrink     Copy Code
/// <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;

Usage

First, we need to add the control to the Visual Studio Designer Toolbox, using a right-click and then the "Choose Items..." menu item. Then we place the control on a form at the desired location and with the desired size. The default name of the control instance variable will be streamPlayerControl1.

The following code asynchronously plays a stream using the supplied address.

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

To get a frame being played just call the GetCurrentFrame() method. The resolution and quality of the frame depend on the stream quality.

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

To stop the stream the Stop() method is used.

Hide    Copy Code
streamPlayerControl1.Stop();

You can always check the playing state using the following code.

Hide    Copy Code
if (streamPlayerControl1.IsPlaying)
{
streamPlayerControl1.Stop();
}

Also, the StreamStarted, StreamStopped and StreamFailed events can be used to monitor the playback state.

To report errors, exceptions are used, so do not forget to wrap your code in a try/catch block. That is all about using it. To see a complete example please check the demo application sources.

WPF version

The FFmpeg facade expects a WinAPI window handle (HWND) in order to use it as a render target. The issue is that in the WPF world windows do not have handles anymore. The VideoWindow class workarounds this issue.

Hide    Copy Code
<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>

GitHub

The project has a GitHub repository available on the following page.

https://github.com/jacobbo/WebEye/tree/master/StreamPlayerControl

Any questions, remarks, and comments are welcome.

Licensing

  1. The FFmpeg facade sources, the same as the FFmpegframework, are licensed under The LGPL license.
  2. The .NET controls' sources and demos' sources are licensed under The Code Project Open License (CPOL).

You can use the control in your commercial product, the only thing is that you should mention that your product uses the FFmpeg library, here are the details.

History

  • 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.
&amp;amp;amp;lt;a href="https://pubads.g.doubleclick.net/gampad/jump?iu=/6839/lqm.codeproject.site/Multimedia/Audio-and-Video/Video&amp;amp;amp;amp;sz=300x250&amp;amp;amp;amp;c=774823"&amp;amp;amp;gt;&amp;amp;amp;lt;img src="https://pubads.g.doubleclick.net/gampad/jump?iu=/6839/lqm.codeproject.site/Multimedia/Audio-and-Video/Video&amp;amp;amp;amp;sz=300x250&amp;amp;amp;amp;c=774823" width="300px" height="250px" target="_blank"/&amp;amp;amp;gt;&amp;amp;amp;lt;/a&amp;amp;amp;gt;

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Stream Player control的更多相关文章

  1. [Winform]Media Player组件全屏播放的设置

    摘要 在设置程序开始运行时,让视频全屏播放时,直接设置 windowsMediaPlay.fullScreen = true; 会报错,代码如下 windowsMediaPlay.URL = _vid ...

  2. 关于 datasnap Stream的英文博客能容

    转载:http://blogs.embarcadero.com/jimtierney/2009/04/06/31461/ DataSnap Server Method Stream Parameter ...

  3. 使用Quicktime 实现视频直播(Live video using Quicktime) (转)

    Quicktime是一个跨浏览器的播放插件,可以实现RTSP视频直播,可用于电视直播或视频监控平台.本文主要讲了关于播放器如何实现直播.事件响应.播放器全屏.动态修改播放路径等问题. 需要准备的软件: ...

  4. Unity3D 第一人称控制器 C#脚本

    CharacterMotor.cs using UnityEngine; using System.Collections; /** * @Author : www.xuanyusong.com */ ...

  5. http2协议翻译(转)

    超文本传输协议版本 2 IETF HTTP2草案(draft-ietf-httpbis-http2-13) 摘要 本规范描述了一种优化的超文本传输协议(HTTP).HTTP/2通过引进报头字段压缩以及 ...

  6. Cisco IOS Debug Command Reference I through L

    debug iapp through debug ip ftp debug iapp : to begin debugging of IAPP operations(in privileged EXE ...

  7. The Brain as a Universal Learning Machine

    The Brain as a Universal Learning Machine This article presents an emerging architectural hypothesis ...

  8. PhysX

    [PhysX] 1.施加力: ))) { //施加一个力,X轴方向力度为1000,Y轴方向力度为1000 addFrceObj.rigidbody.AddForce (, , ); } ))) { / ...

  9. (史上最全的ios源码汇总)

    按钮类         按钮 Drop Down Control         http://www.apkbus.com/android-106661-1-1.html 按钮-Circular M ...

随机推荐

  1. Python自动化测试(1)-自动化测试及基本技术手段概述

    生产力概述 在如今以google为首的互联网时代,软件的开发和生产模式都已经发生了变化, 在<参与感>一书提到:某位从微软出来的工程师很困惑,微软在google还有facebook这些公司 ...

  2. Python黑客编程2 入门demo--zip暴力破解

    Python黑客编程2 入门demo--zip暴力破解 上一篇文章,我们在Kali Linux中搭建了基本的Python开发环境,本篇文章为了拉近Python和大家的距离,我们写一个暴力破解zip包密 ...

  3. WCF基础教程之异常处理:你的Try..Catch语句真的能捕获到异常吗?

    在上一篇WCF基础教程之开篇:创建.测试和调用WCF博客中,我们简单的介绍了如何创建一个WCF服务并调用这个服务.其实,上一篇博客主要是为了今天这篇博客做铺垫,考虑到网上大多数WCF教程都是从基础讲起 ...

  4. 获取IOS应用的子目录

    在开发IOS应用时,我们经常需要将素材分类,并放入相应地子目录中. 在开发代码时,需要访问这些素材时,就需要获取对应的子目录路径.那么如何获取呢? 获取应用路径 首先,要找到应用所在的路径. NSSt ...

  5. Effective Java 创建和销毁对象

    <Effective Java>阅读笔记,用适合自己理解的方式提炼该书内容.<Effective Java>是一本很实用的书,阅读方法应该是快速的领会,总结,然后应用.而非,一 ...

  6. Git Day03,GitHub 1st

    1st, SSH key: Add a pic @ Sep 18 2016 20:26 To note the configuration process on Linux: 2nd,github网站 ...

  7. axis

    http://www.cnblogs.com/liyanblog/archive/2011/11/29/2266942.html 报错: D:\ws\la\WSofSMNS\WebRoot\WEB-I ...

  8. sybase学习

    安装 下载安装包, 解压到/opt/sybase,注意必须目录是这样的/opt/sybase/ASE-15_0/install/ ,因为RUN_DBSVR里面写死了/opt/sybase/ASE-15 ...

  9. lua表排序

    对于lua的table排序问题,一般的按照value值来排序,使用table.sort( needSortTable , func)即可(可以根据自己的需要重写func,否则会根据默认来:默认的情形之 ...

  10. OutputCache属性详解(四)— SqlDependency

    目录 OutputCache概念学习 OutputCache属性详解(一) OutputCache属性详解(二) OutputCache属性详解(三) OutputCache属性详解(四)— SqlD ...