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. C#性能优化之Lazy<T> 实现延迟初始化

    在.NET4.0中,可以使用Lazy<T> 来实现对象的延迟初始化,从而优化系统的性能.延迟初始化就是将对象的初始化延迟到第一次使用该对象时.延迟初始化是我们在写程序时经常会遇到的情形,例 ...

  2. 分享一些Hadoop环境搭建所用到的软件

    本来想用土的掉渣的语言说说hadoop配置的,因为最近总有人问我,环境搭建老出莫名其妙的问题,可是写到一半,还是决定不写了,网上教程好多好多,而大家遇到问题有很多是软件版本不对应造成的,因此我就把大家 ...

  3. C#实现的等额本息法、按月付息到期还本法、一次性还本付息法

    你若懂行,那便有用,如下: void Main(){    var x = DengEBenXi.Compute(11111, 12, 3);    x.Dump();    var y = AnYu ...

  4. 用DirectX实现魔方(三)视角变换及缩放(附源码)

    在本系列第一篇介绍过鼠标按键的功能,如下. 左键拖拽 - 旋转魔方 右键拖拽 - 变换视角 滚轮 - 缩放魔方 今天研究一下如何实现后面两个功能,用到的技术主要是Arcball,Arcball是实现M ...

  5. 自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析

    自制Unity小游戏TankHero-2D(4)关卡+小地图图标+碰撞条件分析 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm ...

  6. 泛型实现中没有正确lock引用类型的一个隐藏bug分析

    最近看到这篇文章dotNetDR_的回复,让我想起一个真实发生的案例,下面就简单说说这个关于lock引用类型的一个不容易发现的隐藏缺陷. 某类库中的代码,封装了很简单的一个通用类,用于线程安全地执行某 ...

  7. AngularJS快速入门指南12:模块

    AngularJS模块定义了一个application. 模块是一个application中不同部分的容器. application中的所有控制器都应该属于一个模块. 带有一个控制器的模块 下面这个a ...

  8. Java基础之泛型

    泛型: (1)为什么会出现泛型? 因为集合存放的数据类型不固定,故往集合里面存放元素时,存在安全隐患, 如果在定义集合时,可以想定义数组一样指定数据类型,那么就可以解决该类安全问题. JDK1.5后出 ...

  9. Atitit 跨平台异常处理(2)--------异常转换 -----java c# js异常对象结构比较and转换

    Atitit 跨平台异常处理(2)--------异常转换 -----java c# js异常对象结构比较and转换 { "@type":"java.lang.Runti ...

  10. paip.python错误解决23

    paip.python错误解决 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.net/attilax ...