1. 程式人生 > >C#寫客戶端接收mjpg-streamer視訊流

C#寫客戶端接收mjpg-streamer視訊流

最近用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;
        }