C#开源实现MJPEG流传输
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。
许久以前写了篇文章《基于.NET打造IP智能网络视频监控系统》,记录和介绍了自己几年来积累和演练的一个系统。发现几个月过去了,没有任何进展。
目前已经实现了 UDP+RTP 方式在不同物理机之间的媒体流传输。当然,由于没有基于 .NET 的媒体流压缩实现,所以直接传输的裸图 Bitmap。不过要求不高,帧率低一些,机器性能强一些,看着也很流畅。

能在桌面客户端上看到视频图像的功能已经完成了。下面需要考虑,如何通过浏览器来查看视频。
在不考虑使用 Flash、ActiveX 的条件下,貌似只能选择 MJPEG 方式。目前还没有研究在 HTML5 下视频是如何处理的,以后有时间可以探索。
目录
什么是 MJPEG?
看这里:
- http://en.wikipedia.org/wiki/Motion_JPEG
- http://zh.wikipedia.org/wiki/Motion_JPEG
- http://baike.baidu.com/view/2098077.htm
当然,我主要关注 MJPEG over HTTP 这段。
M-JPEG over HTTP
HTTP streaming separates each image into individual HTTP replies on a specified marker. RTP streaming creates packets of a sequence of JPEG images that can be received by clients such as QuickTime or VLC.
In response to a GET request for a MJPEG file or stream, the server streams the sequence of JPEG frames over HTTP. A special mime-type content type multipart/x-mixed-replace;boundary=<boundary-name> informs the client to expect several parts (frames) as an answer delimited by <boundary-name>. This boundary name is expressly disclosed within the MIME-type declaration itself. The TCP connection is not closed as long as the client wants to receive new frames and the server wants to provide new frames. Two basic implementations of a M-JPEG streaming server are cambozola and MJPG-Streamer. The more robust ffmpeg-server also provides M-JPEG streaming support.
也就是说,建立 HTTP 连接后,服务端在 Response 消息中先发一个数据头 Header 告诉客户端,我后面的都是 JPEG 图片。图片之间使用 boundary-name 来区分,每个图片前都有自己的数据头来描述图片数据长度。
MJPEG数据头定义
/// <summary>
/// 流头部
/// </summary>
public string StreamHeader
{
get
{
return "HTTP/1.1 200 OK" +
"\r\n" +
"Content-Type: multipart/x-mixed-replace; boundary=" + this.Boundary +
"\r\n";
}
}
/// <summary>
/// 图片头部
/// </summary>
public string PayloadHeader
{
get
{
return "\r\n" +
this.Boundary +
"\r\n" +
"Content-Type: image/jpeg" +
"\r\n" +
"Content-Length: " + _contentLengthString +
"\r\n\r\n";
}
}
这里的 Boundary 可以是任意字符串,只要你觉得唯一并能区分即可,比如我可以设置为“--dennisgao”。
服务器端实现
Http 服务器其实就是个支持 Tcp 连接的服务器。
private AsyncTcpServer _server; _server = new AsyncTcpServer(Port);
_server.Encoding = Encoding.ASCII;
public void Start()
{
_server.Start();
_server.ClientConnected += new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
_server.ClientDisconnected += new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
} public void Stop()
{
_server.Stop();
_server.ClientConnected -= new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
_server.ClientDisconnected -= new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
} private void OnClientConnected(object sender, TcpClientConnectedEventArgs e)
{
_clients.AddOrUpdate(e.TcpClient.Client.RemoteEndPoint.ToString(), e.TcpClient, (n, o) => { return e.TcpClient; });
} private void OnClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
{
TcpClient clientToBeThrowAway;
_clients.TryRemove(e.TcpClient.Client.RemoteEndPoint.ToString(), out clientToBeThrowAway);
}
这里可以参考两篇文章中的实现。
发送图片数据
首先要保证,对一个HTTP连接只能发一次流头,因为后面是接连不断的图片数据。当然,发点别的数据客户端也不会解码。
private void WriteStreamHeader()
{
if (_clients.Count > )
{
foreach (var item in _clients)
{
Logger.Debug(string.Format(CultureInfo.InvariantCulture,
"Writing stream header, {0}, {1}{2}", item.Key, Environment.NewLine, StreamHeader)); _server.SyncSend(item.Value, StreamHeader); TcpClient clientToBeThrowAway;
_clients.TryRemove(item.Key, out clientToBeThrowAway);
}
}
}
发送图片数据时,要保证图片的前面是图片头和长度信息,数据尾部要有换行符。
private void WritePayload(byte[] payload)
{
string payloadHeader = this.PayloadHeader.Replace(_contentLengthString, payload.Length.ToString());
string payloadTail = "\r\n"; Logger.Debug(string.Format(CultureInfo.InvariantCulture,
"Writing payload header, {0}{1}", Environment.NewLine, payloadHeader)); byte[] payloadHeaderBytes = _server.Encoding.GetBytes(payloadHeader);
byte[] payloadTailBytes = _server.Encoding.GetBytes(payloadTail);
byte[] packet = new byte[payloadHeaderBytes.Length + payload.Length + payloadTail.Length];
Buffer.BlockCopy(payloadHeaderBytes, , packet, , payloadHeaderBytes.Length);
Buffer.BlockCopy(payload, , packet, payloadHeaderBytes.Length, payload.Length);
Buffer.BlockCopy(payloadTailBytes, , packet, payloadHeaderBytes.Length + payload.Length, payloadTailBytes.Length); _server.SendToAll(packet);
}
结果演示
在可以成功发送流信息和图片信息后,就可以在浏览器上查看视频了。当然,我用的 Google Chrome 。IE10 好奇葩,它会把流当成文件不停的下载,搞不懂。

远程访问
局域网内的无线设备,只要浏览器支持 MJPEG ,均可以查看视频。我测试了 iPad 上的 Safari 是可以的,但 Chrome 却直接解析成乱码。

当然,如果在路由器上配置转发规则,就可以在外网访问了。

完整代码
public class MJpegStreamingServer
{
private static string _contentLengthString = "__PayloadHeaderContentLength__";
private AsyncTcpServer _server;
private ConcurrentDictionary<string, TcpClient> _clients; public MJpegStreamingServer(int listenPort)
: this(listenPort, "--dennisgao")
{
} public MJpegStreamingServer(int listenPort, string boundary)
{
Port = listenPort;
Boundary = boundary; _server = new AsyncTcpServer(Port);
_server.Encoding = Encoding.ASCII;
_clients = new ConcurrentDictionary<string, TcpClient>();
} /// <summary>
/// 监听的端口
/// </summary>
public int Port { get; private set; } /// <summary>
/// 分隔符
/// </summary>
public string Boundary { get; private set; } /// <summary>
/// 流头部
/// </summary>
public string StreamHeader
{
get
{
return "HTTP/1.1 200 OK" +
"\r\n" +
"Content-Type: multipart/x-mixed-replace; boundary=" + this.Boundary +
"\r\n";
}
} /// <summary>
/// 图片头部
/// </summary>
public string PayloadHeader
{
get
{
return "\r\n" +
this.Boundary +
"\r\n" +
"Content-Type: image/jpeg" +
"\r\n" +
"Content-Length: " + _contentLengthString +
"\r\n\r\n";
}
} public void Start()
{
_server.Start();
_server.ClientConnected += new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
_server.ClientDisconnected += new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
} public void Stop()
{
_server.Stop();
_server.ClientConnected -= new EventHandler<TcpClientConnectedEventArgs>(OnClientConnected);
_server.ClientDisconnected -= new EventHandler<TcpClientDisconnectedEventArgs>(OnClientDisconnected);
} private void OnClientConnected(object sender, TcpClientConnectedEventArgs e)
{
_clients.AddOrUpdate(e.TcpClient.Client.RemoteEndPoint.ToString(), e.TcpClient, (n, o) => { return e.TcpClient; });
} private void OnClientDisconnected(object sender, TcpClientDisconnectedEventArgs e)
{
TcpClient clientToBeThrowAway;
_clients.TryRemove(e.TcpClient.Client.RemoteEndPoint.ToString(), out clientToBeThrowAway);
} public void Write(Image image)
{
if (_server.IsRunning)
{
byte[] payload = BytesOf(image); WriteStreamHeader();
WritePayload(payload);
}
} private void WriteStreamHeader()
{
if (_clients.Count > )
{
foreach (var item in _clients)
{
Logger.Debug(string.Format(CultureInfo.InvariantCulture,
"Writing stream header, {0}, {1}{2}", item.Key, Environment.NewLine, StreamHeader)); _server.SyncSend(item.Value, StreamHeader); TcpClient clientToBeThrowAway;
_clients.TryRemove(item.Key, out clientToBeThrowAway);
}
}
} private void WritePayload(byte[] payload)
{
string payloadHeader = this.PayloadHeader.Replace(_contentLengthString, payload.Length.ToString());
string payloadTail = "\r\n"; Logger.Debug(string.Format(CultureInfo.InvariantCulture,
"Writing payload header, {0}{1}", Environment.NewLine, payloadHeader)); byte[] payloadHeaderBytes = _server.Encoding.GetBytes(payloadHeader);
byte[] payloadTailBytes = _server.Encoding.GetBytes(payloadTail);
byte[] packet = new byte[payloadHeaderBytes.Length + payload.Length + payloadTail.Length];
Buffer.BlockCopy(payloadHeaderBytes, , packet, , payloadHeaderBytes.Length);
Buffer.BlockCopy(payload, , packet, payloadHeaderBytes.Length, payload.Length);
Buffer.BlockCopy(payloadTailBytes, , packet, payloadHeaderBytes.Length + payload.Length, payloadTailBytes.Length); _server.SendToAll(packet);
} private byte[] BytesOf(Image image)
{
MemoryStream ms = new MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] payload = ms.ToArray(); return payload;
}
}
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载。
C#开源实现MJPEG流传输的更多相关文章
- openwrt,mjpeg流,wifi摄像头与APP联动,拍照、录像
最近公司好忙,自己主管的产品又忙着上线,好久都没更新博客了. 最近产品在做一款wifi摄像头,摄像头与手机同时连接在一个局域网内,即可实现摄像头图像在手机显示,并且拍照录像等功能 mjpeg是一张一张 ...
- RTSP RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议
RTSP 编辑 RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学.网景和RealNetwo ...
- Kafka 在行动:7步实现从RDBMS到Hadoop的实时流传输
原文:https://coyee.com/article/11095-kafka-in-action-7-steps-to-real-time-streaming-from-rdbms-to-hado ...
- EzHttp 流传输调用代码示例
EzHttp框架提供的内置接口,用于文件流等传输 流传输调用代码示例 内置接口: public interface IEzStreamHandler { Task<byte[]> GetD ...
- [转]开源实时视频码流分析软件:VideoEye
原文太长了,就直接贴上链接,以便大家学习. 引文链接:开源实时视频码流分析软件:VideoEye
- .net WebApi 批量文件进行压缩zip以二进制流传输至前端(Vue)下载
前言:最近接了个项目,需要进行将服务端生成的文件进行打包压缩供前端下载,百度查了下资料,决定采用SharpZipLib C#开园的压缩解压库进行服务器文件压缩,在实现过程,郁闷的是前端接收下载下来的压 ...
- 开源实时视频码流分析软件:VideoEye
本文介绍一个自己做的码流分析软件:VideoEye.为什么要起这个名字呢?感觉这个软件的主要功能就是对"视频"进行"分析".而分析是要用眼睛来看的,因此取了&q ...
- 基于TCP协议的项目架构之Socket流传输的实现
项目背景 某银行的影像平台由于使用时间长,服务器等配置原因,老影像系统满足不了现在日益增长的数据量的需求,所以急需要升级改造.传统的影像平台使用的是Oracle数据库和简单的架构来存储数据(视频.图 ...
- HttpUrlConnection流传输问题(正确传输包含中文的JSON字符串)
目前在写一个功能,主要是使用 HttpURLConnection 发送http请求调用外部接口.本来一切正常的,可是在发送post请求上传数据给服务端时,服务端返回错误信息:获取的JSON请求是乱码的 ...
随机推荐
- MVVM模式和在WPF中的实现(一)MVVM模式简介
MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
- javascript:逆波兰式表示法计算表达式结果
逆波兰式表示法,是由栈做基础的表达式,举个例子: 5 1 2 + 4 * + 3 - 等价于 5 + ((1 + 2) * 4) - 3 原理:依次将5 1 2 压入栈中, 这时遇到了运算符 + ...
- CSS 3 学习——transform 3D转换渲染
以下内容根据官方规范翻译,没有翻译关于SVG变换的内容和关于矩阵计算的内容. 一般情况下,元素在一个无景深无立体感的平面(flat plane)上渲染,这个平面就是其包含块所处的平面.同时,页面上的其 ...
- JDBC MySQL 多表关联查询查询
public static void main(String[] args) throws Exception{ Class.forName("com.mysql.jdbc.Driver&q ...
- arcgis api for js入门开发系列八聚合效果(含源代码)
上一篇实现了demo的图层控制模块,本篇新增聚合效果,截图如下(源代码见文章底部): 聚合效果实现的思路如下: 1.map.html引用聚合包,项目已经包含进来了的聚合文件夹: <script ...
- linux 如何对文件解压或打包压缩
tar命令用与对文件打包压缩或解压,格式: tar [选项] [文件] 打包并压缩文件: tar -czvf 压缩包名 .tar.gz 解压并展开压缩包: tar -xzvf 压缩包名 .tar. ...
- fedora上部署ASP.NET——(卡带式电脑跑.NET WEB服务器)
andrew,20130601,guilin 本文记录在树莓派(fedora)上部署ASP.NET MVC2 的过程. 本文共分为六部分,分别是前置条件,Apache的安装,Mysql的安装,安装mo ...
- 死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?
写在前面 阅读目录: 设计误区 数据库已死 枚举映射 关联映射 后记 在上一篇<一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?>博文中, ...
- ASP.NET MVC Model验证(一)
ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...