C# MJPEG 客户端简单实现方法
mjpeg协议在此不在过多描述,这里主要介绍一下使用c#中的picturebox控件频繁刷新mjpeg传输过来的图片,高频率的图片刷新实现视频播放效果;
环境:
服务端
mjpeg服务器使用的是手机的droidcam,很方便的一个mjpeg服务器,端口4747,打开软件就能使用,并且还附带了web端展示。
客户端
mjpeg客户端使用c# http请求,并获取到响应mjpeg视频流,截取到图片数据部分,用picturebox展示图片内容。
整体流程:
1. c# 向mjpeg发送请求url,请求url是mjpeg服务器定的,例如droidcam,可以通过访问: {手机所在ip}:4747
图片中红框内容就是视频流的地址,使用get请求后,服务端就会一直往这个请求的响应内容中写照片信息,直到这个get请求断开为止(客户端、服务端其中一个主动退出)
ps: 如果使用droidcam当服务器,建议使用手机热点、或者手机通过数据线共享链接方式链接,因为mjpeg实际是把视频的每一帧截成一张图片发送过来的,非常的占带宽,并且网速不好还有图片数据不完整情况,需要手动处理跳过.手机开wifi热点电脑链接, 手机端ip:192.168.43.1:4747,手机数据线连接usb网络共享,手机端ip:192.168.43.129:4747;
2. c# 读响应头,找出视频流中每张图片的分隔符, 读取每张图片前content-length长度, 读图片;
3. 每读到一张图片,刷新一次picturebox控件;
具体实现
//创建一个http请求,只要请求不结束,mjpeg服务端会一直给请求的响应体中发送实时图片内容 httpwebrequest hwrequest = (system.net.httpwebrequest)webrequest.create("请求url地址"); hwrequest.method = "get"; httpwebresponse hwresponse = (httpwebresponse)hwrequest.getresponse(); //读boundary指定的每张图片分隔符,droidcam为:--dcmjpeg string contenttype = hwresponse.headers["content-type"]; string boundrykey = "boundary="; string boundary = contenttype.substring(contenttype.indexof(boundrykey) + boundrykey.length); //拿到响应体流 stream stream = hwresponse.getresponsestream(); string headername = "content-length:"; //临时存储字符串数据 stringbuilder sb = new stringbuilder(); int len = 1024; while (true) { //读取一行数据 while (true) { char c = (char)stream.readbyte(); //console.write(c); if (c == '\n') { break; } sb.append(c); } string line = sb.tostring(); sb.remove(0, sb.length); //当前行中是否包含content-length: int i = line.indexof(headername); if (i != -1) { //每张图片前有一段图片简单介绍(图片类型、长度),这里只关心长度(content-length:)后边的值,用于后续读取图片 int imagefilelength = convert.toint32(line.substring(i + headername.length).trim()); //content-length:xxx 完后会有一个/r/n的换行符,换行符后才是真正的图片数据(不知道是droidcam自己这样还是都这样...) //这里跳过/r/n stream.read(new byte[2], 0, 2); //开始读取图片数据,imagefilelength就是读到的content-length:后的长度 byte[] imagefilebytes = new byte[imagefilelength]; stream.read(imagefilebytes, 0, imagefilebytes.length); //jpeg的文件头是: ff d8 ff ,文件尾是: ff d9,非常重要,调试时最好打印一下,便于区分读入的数据是否正好时图片的所有内容 //console.writeline("文件头:" + imagefilebytes[0].tostring("x") + " " + imagefilebytes[1].tostring("x") + " " + imagefilebytes[2].tostring("x") + " " + imagefilebytes[3].tostring("x") + " " + imagefilebytes[4].tostring("x")); //console.writeline("文件尾:" + imagefilebytes[imagefilelength - 2].tostring("x") + " " + imagefilebytes[imagefilelength - 1].tostring("x")); //此处做了一个如果读入文件不全时处理,图片越大,程序循环读取速度越快,越有可能导致读取文件不全情况...,如果有好的办法解决希望前辈们指教,非常感谢! //文件尾是否是ff d9 if (imagefilebytes[imagefilelength - 2].tostring("x") != "ff" && imagefilebytes[imagefilelength - 1].tostring("x") != "d9") { //读入文件内容不全,跳过次文件,让流位置跳到下次图片开始位置 //console.writeline("开始矫正..."); char l = '0'; while (true) { char c = (char)stream.readbyte(); //这里只判断了--dcmjpeg中的前两个字符--,当读到的流中连续两个字符是--时,表示流已读到下次图片开始位置 if (l == boundary[0] && c == boundary[1]) { break; } l = c; } } else { //读取图片成功! //accessimagehandler是一个action,用于把图片实时写到picturebox控件中 accessimagehandler(imagefilebytes); } //这里适当睡几十毫秒,会降低点图片读入不全情况,还未找到图片随机读取不全情况原因... thread.sleep(sleep); } } stream.close(); hwresponse.close();
可以先试着读一张图片,通过filestream 写成文件,看看写成的文件是否能用windows图片查看器查看,如果不能并且机器上有ps的话,可以试着用ps打开一下,ps对图片支持的比较好,如果文件头多写两个其他字符它是可以过滤掉的。但是最后的效果还是需要windows图片查看器能看,只有查看器能看,picturebox才能正常显示内容,否则在打开图片时会报内存不足异常!
多调试几遍,查看一下请求头、请求尾是否正确。
如果有兴趣,可以看下我调试例子:链接: https://pan.baidu.com/s/1oihxe8ficncm4gcae9sqbg 提取码: atwh ,例子内容有点乱,并且很不完善,希望对你多少有些帮助!
补充使用ip摄像头app连接时有密码情况:
mjpeg协议中应该是没规定加密情况,这个加密(http auth)应该是ip摄像头app规定的。
在使用ip摄像头app读mjpeg流时发现需要密码,使用浏览器直接访问会弹出输入账号密码框,通过解析请求发现其实就是在请求头中添加了一个请求头authorization:
ywrtaw46ywrtaw4=是我在app中设置的 用户名(admin):密码(admin) 拼接起来后转成base64的字符串, admin:admin 转成base64为: ywrtaw46ywrtaw4=
所以在修改一下请求头就可以了:
hwrequest.headers.add("authorization", "basic " + convert.tobase64string(encoding.utf8.getbytes(user + ":" + pass)));
这里hwrequest就是httpwebrequest
user是用户名,pass 是密码
以上就是c# mjpeg 客户端简单实现方法的详细内容,更多关于c# mjpeg 客户端简单实现的资料请关注其它相关文章!