C# MJPEG 客戶端簡(jiǎn)單實(shí)現(xiàn)方法
MJPEG協(xié)議在此不在過多描述,這里主要介紹一下使用C#中的PictureBox控件頻繁刷新MJPEG傳輸過來的圖片,高頻率的圖片刷新實(shí)現(xiàn)視頻播放效果;
環(huán)境:
服務(wù)端
MJPEG服務(wù)器使用的是手機(jī)的DroidCam,很方便的一個(gè)MJPEG服務(wù)器,端口4747,打開軟件就能使用,并且還附帶了web端展示。
客戶端
MJPEG客戶端使用C# Http請(qǐng)求,并獲取到響應(yīng)MJPEG視頻流,截取到圖片數(shù)據(jù)部分,用PictureBox展示圖片內(nèi)容。
整體流程:
1. C# 向MJPEG發(fā)送請(qǐng)求URL,請(qǐng)求URL是MJPEG服務(wù)器定的,例如DroidCam,可以通過訪問: {手機(jī)所在IP}:4747
圖片中紅框內(nèi)容就是視頻流的地址,使用GET請(qǐng)求后,服務(wù)端就會(huì)一直往這個(gè)請(qǐng)求的響應(yīng)內(nèi)容中寫照片信息,直到這個(gè)GET請(qǐng)求斷開為止(客戶端、服務(wù)端其中一個(gè)主動(dòng)退出)
ps: 如果使用DroidCam當(dāng)服務(wù)器,建議使用手機(jī)熱點(diǎn)、或者手機(jī)通過數(shù)據(jù)線共享鏈接方式鏈接,因?yàn)镸JPEG實(shí)際是把視頻的每一幀截成一張圖片發(fā)送過來的,非常的占帶寬,并且網(wǎng)速不好還有圖片數(shù)據(jù)不完整情況,需要手動(dòng)處理跳過.手機(jī)開WiFi熱點(diǎn)電腦鏈接, 手機(jī)端IP:192.168.43.1:4747,手機(jī)數(shù)據(jù)線連接usb網(wǎng)絡(luò)共享,手機(jī)端IP:192.168.43.129:4747;
2. C# 讀響應(yīng)頭,找出視頻流中每張圖片的分隔符, 讀取每張圖片前Content-Length長(zhǎng)度, 讀圖片;
3. 每讀到一張圖片,刷新一次PictureBox控件;
具體實(shí)現(xiàn)
//創(chuàng)建一個(gè)HTTP請(qǐng)求,只要請(qǐng)求不結(jié)束,MJPEG服務(wù)端會(huì)一直給請(qǐng)求的響應(yīng)體中發(fā)送實(shí)時(shí)圖片內(nèi)容 HttpWebRequest hwRequest = (System.Net.HttpWebRequest)WebRequest.Create("請(qǐng)求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); //拿到響應(yīng)體流 Stream stream = hwResponse.GetResponseStream(); string headerName = "Content-Length:"; //臨時(shí)存儲(chǔ)字符串?dāng)?shù)據(jù) StringBuilder sb = new StringBuilder(); int len = 1024; while (true) { //讀取一行數(shù)據(jù) 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); //當(dāng)前行中是否包含Content-Length: int i = line.IndexOf(headerName); if (i != -1) { //每張圖片前有一段圖片簡(jiǎn)單介紹(圖片類型、長(zhǎng)度),這里只關(guān)心長(zhǎng)度(Content-Length:)后邊的值,用于后續(xù)讀取圖片 int imageFileLength = Convert.ToInt32(line.Substring(i + headerName.Length).Trim()); //Content-Length:xxx 完后會(huì)有一個(gè)/r/n的換行符,換行符后才是真正的圖片數(shù)據(jù)(不知道是DroidCam自己這樣還是都這樣...) //這里跳過/r/n stream.Read(new byte[2], 0, 2); //開始讀取圖片數(shù)據(jù),imageFileLength就是讀到的Content-Length:后的長(zhǎng)度 byte[] imageFileBytes = new byte[imageFileLength]; stream.Read(imageFileBytes, 0, imageFileBytes.Length); //JPEG的文件頭是: FF D8 FF ,文件尾是: FF D9,非常重要,調(diào)試時(shí)最好打印一下,便于區(qū)分讀入的數(shù)據(jù)是否正好時(shí)圖片的所有內(nèi)容 //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")); //此處做了一個(gè)如果讀入文件不全時(shí)處理,圖片越大,程序循環(huán)讀取速度越快,越有可能導(dǎo)致讀取文件不全情況...,如果有好的辦法解決希望前輩們指教,非常感謝! //文件尾是否是FF D9 if (imageFileBytes[imageFileLength - 2].ToString("X") != "FF" && imageFileBytes[imageFileLength - 1].ToString("X") != "D9") { //讀入文件內(nèi)容不全,跳過次文件,讓流位置跳到下次圖片開始位置 //Console.WriteLine("開始矯正..."); char l = '0'; while (true) { char c = (char)stream.ReadByte(); //這里只判斷了--dcmjpeg中的前兩個(gè)字符--,當(dāng)讀到的流中連續(xù)兩個(gè)字符是--時(shí),表示流已讀到下次圖片開始位置 if (l == boundary[0] && c == boundary[1]) { break; } l = c; } } else { //讀取圖片成功! //accessImageHandler是一個(gè)Action,用于把圖片實(shí)時(shí)寫到PictureBox控件中 accessImageHandler(imageFileBytes); } //這里適當(dāng)睡幾十毫秒,會(huì)降低點(diǎn)圖片讀入不全情況,還未找到圖片隨機(jī)讀取不全情況原因... Thread.Sleep(sleep); } } stream.Close(); hwResponse.Close();
可以先試著讀一張圖片,通過FileStream 寫成文件,看看寫成的文件是否能用Windows圖片查看器查看,如果不能并且機(jī)器上有PS的話,可以試著用PS打開一下,PS對(duì)圖片支持的比較好,如果文件頭多寫兩個(gè)其他字符它是可以過濾掉的。但是最后的效果還是需要Windows圖片查看器能看,只有查看器能看,PictureBox才能正常顯示內(nèi)容,否則在打開圖片時(shí)會(huì)報(bào)內(nèi)存不足異常!
多調(diào)試幾遍,查看一下請(qǐng)求頭、請(qǐng)求尾是否正確。
如果有興趣,可以看下我調(diào)試?yán)樱烘溄? https://pan.baidu.com/s/1oihxe8ficnCm4gcaE9SQBg 提取碼: atwh ,例子內(nèi)容有點(diǎn)亂,并且很不完善,希望對(duì)你多少有些幫助!
補(bǔ)充使用IP攝像頭APP連接時(shí)有密碼情況:
MJPEG協(xié)議中應(yīng)該是沒規(guī)定加密情況,這個(gè)加密(http auth)應(yīng)該是IP攝像頭APP規(guī)定的。
在使用IP攝像頭App讀MJPEG流時(shí)發(fā)現(xiàn)需要密碼,使用瀏覽器直接訪問會(huì)彈出輸入賬號(hào)密碼框,通過解析請(qǐng)求發(fā)現(xiàn)其實(shí)就是在請(qǐng)求頭中添加了一個(gè)請(qǐng)求頭Authorization:
YWRtaW46YWRtaW4=是我在APP中設(shè)置的 用戶名(admin):密碼(admin) 拼接起來后轉(zhuǎn)成Base64的字符串, admin:admin 轉(zhuǎn)成base64為: YWRtaW46YWRtaW4=
所以在修改一下請(qǐng)求頭就可以了:
hwRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(user + ":" + pass)));
這里hwRequest就是HttpWebRequest
user是用戶名,pass 是密碼
以上就是C# MJPEG 客戶端簡(jiǎn)單實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于C# MJPEG 客戶端簡(jiǎn)單實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C# 關(guān)于AppDomain的一些總結(jié)
這篇文章主要介紹了C# 關(guān)于AppDomain的一些總結(jié),幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2021-02-02C#探秘系列(四)——GetHashCode,ExpandoObject
這篇繼續(xù)分享下GetHashCode和ExpandoObject這兩個(gè)比較好玩的方法。2014-05-05c# 實(shí)現(xiàn)MD5,SHA1,SHA256,SHA512等常用加密算法源代碼
c# 如何實(shí)現(xiàn)MD5,SHA1,SHA256,SHA512等常用加密算法,需要的朋友可以參考下2012-12-12C#開發(fā)Windows UWP系列之布局面板RelativePanel
這篇文章介紹了C#開發(fā)Windows UWP系列之布局面板RelativePanel,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06