C#寫客戶端接收mjpg-streamer視訊流
阿新 • • 發佈:2018-12-13
最近用wpf做一個樹莓派機器人的綜合控制端,需要解析機器人攝像頭的視訊流,樹莓派是用mjpg-streamer呼叫並搭建了視訊流服務。
客戶端解析mjpg-streamer視訊幀的原理是:建立Http長連線,每次接收1024長度的資料,資料流中包含資料頭資訊和緊跟在資料頭資訊後的影象幀資料,所以需要先定位資料頭資訊,頭資訊中包含影象幀資料的長度,然後從頭資訊的結尾開始解析影象幀資料,第一次讀取的資料中不一定包含完整的影象幀資料,需要從第二次讀取的資料中繼續解析未完整的資料。
以下為具體程式碼:
private static readonly string DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"; public void GetLivingVedio(string requestUrl, Action<byte[]> setFrame) { Task.Run(delegate { if (string.IsNullOrEmpty(requestUrl)) { MessageBox.Show("GetLivingVedio url 為空!"); return; } try { HttpWebRequest request = WebRequest.Create(requestUrl) as HttpWebRequest; ; //在使用curl做POST的時候, 當要POST的資料大於1024位元組的時候, curl並不會直接就發起POST請求, 而是會分為倆步, //1.傳送一個請求, 包含一個Expect: 100 -continue, 詢問Server使用願意接受資料 //2.接收到Server返回的100 - continue應答以後, 才把資料POST給Server //並不是所有的Server都會正確應答100 -continue, 比如lighttpd, 就會返回417 “Expectation Failed”, 則會造成邏輯出錯,, request.ServicePoint.Expect100Continue = false; //預設代理 request.UserAgent = DefaultUserAgent; //ContentType //request.ContentType = "application/json;charset=utf-8"; request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"; request.KeepAlive = true; request.Method = "Get"; request.Timeout = (int)(30 * 1000f); // to ensure no timeout using (Stream stream = request.GetResponse().GetResponseStream()) { BinaryReader reader = new BinaryReader(new BufferedStream(stream), new System.Text.ASCIIEncoding()); int headerStart = 0; int frameStart = 0; int frameLength = 0; byte[] frameBuffer = null; int remainDataInNext = 0; while (!StopLivingVedio) { try { byte[] buffer = reader.ReadBytes(1024); if (remainDataInNext > 0) { int locationBuffStart = frameBuffer.Length - remainDataInNext; if (buffer.Length > remainDataInNext) { Array.Copy(buffer, 0, frameBuffer, locationBuffStart, remainDataInNext); } else { Array.Copy(buffer, 0, frameBuffer, locationBuffStart, buffer.Length); } remainDataInNext = remainDataInNext - buffer.Length < 0 ? 0 : remainDataInNext - buffer.Length; if (remainDataInNext == 0) { //Console.WriteLine("frame is ok! {0}", DateTime.Now.ToString("HH:mm:ss.fff")); if (setFrame != null) setFrame(frameBuffer); } } else if (LocateHeader(buffer, ref headerStart, ref frameStart, ref frameLength)) { frameBuffer = new byte[frameLength]; Array.Copy(buffer, frameStart, frameBuffer, 0, buffer.Length - frameStart); remainDataInNext = frameLength - (buffer.Length - frameStart); } } catch (Exception ex) { Console.WriteLine("GetLivingVedio's error:{0}", ex); } } } } catch (Exception ex) { Console.WriteLine("GetLivingVedio's error:{0}", ex.ToString()); } Console.WriteLine("exit real time preview"); }); } /// <summary> /// 定位buffer中的頭資訊 /// </summary> /// <param name="buffer"></param> /// <param name="headerstart"></param> /// <param name="framestart"></param> /// <param name="framelength"></param> /// <returns></returns> public bool LocateHeader(byte[] buffer, ref int headerstart, ref int framestart, ref int framelength) { bool searchHeaderStartResult = false; bool searchHeaderEndResult = false; if (buffer != null && buffer.Length > 0) { for (int i = 0; i < buffer.Length; i++) { //比較訊息頭 for (int j = 0; j < this.HeaderStartBuffer.Length; j++) { //相等時繼續比較,不跳出迴圈 if ((i + j) < buffer.Length && buffer[i + j] == this.HeaderStartBuffer[j]) { if (j == 19) { headerstart = i; searchHeaderStartResult = true; i += 20; } } else { break; } } //比較訊息結尾 for (int j = 0; j < this.HeaderEndBuffer.Length; j++) { //相等時繼續比較,不跳出迴圈 if ((i + j) < buffer.Length && buffer[i + j] == this.HeaderEndBuffer[j]) { if (j == 3) { framestart = i + 4; searchHeaderEndResult = true; } } else { break; } } if (searchHeaderStartResult && searchHeaderEndResult) { if (headerstart < framestart) { byte[] headerBuffer = new byte[framestart - headerstart]; Array.Copy(buffer, headerstart, headerBuffer, 0, headerBuffer.Length); string headerString = Encoding.UTF8.GetString(headerBuffer); int lengthStartIndex = headerString.IndexOf("Content-Length:") + 15; int lengthEndIndex = headerString.IndexOf("X-Timestamp"); string lengthString = headerString.Substring(lengthStartIndex, lengthEndIndex - lengthStartIndex); framelength = int.Parse(lengthString.Trim()); } break; } } } return searchHeaderStartResult && searchHeaderEndResult; }