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 客戶端簡單實現的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C#調用HTTP POST請求上傳圖片的示例代碼
- C#編寫網遊客戶端的實現
- C# 利用VS編寫一個簡單的網遊客戶端
- C#記一次http協議multipart/form-data的boundary問題
- C#編寫一個網遊客戶端的完整步驟