[C#][WinForm] IPCam/NVR/CGI MJPEG解碼(MJPEG Decode)
目前有個CGI會處理和傳送影像
在Chrome上看到是這樣
那要寫一隻WinForm來做到一樣的事情該怎麼做呢
先開新專案 命名就隨意吧
在裡面加入一個PictureBox 且Dock = DockStyle.Fill 如下圖
然後就開始寫Code吧
新增一個Class命名為 MJPEGDecoder
主要的做法就是用HttpWebRequest 附帶帳號密碼認證後去撈取串流
在分析串流內是否有JPEG的開頭{FF,D8}
而在wiki上的 M-JPEG over HTTP 有寫著一些簡介
A special mime-type content type multipart/x-mixed-replace;boundary=<boundary-name> informs the client to expect several parts (frames) as an answer delimited by <boundary-name>.
所以只要找出 <boundary-name> 就可以知道每個frame的界線在哪
[PS: 當然 JPEG也有結尾的位元 {FF,D8} 也可以用此來判斷是否為結尾]
並利用抓到完整圖檔時觸發事件來更改pictureBox1的圖片
這樣放進無窮迴圈中就可以當作影片來看了
public class MJPEGDecoder
{
public Bitmap bitmap { get; set; }
// magic 2 byte header for JPEG images
private byte[] JpegHeader = new byte[] { 0xff, 0xd8 };
// you can replace boundaryBytes with JpegEnd
//private byte[] JpegEnd = new byte[] { 0xff, 0xd9 };
// pull down 1024 bytes at a time
private const int ChunkSize = 1024;
// used to cancel reading the stream
private bool _streamActive;
// current encoded JPEG image
public byte[] CurrentFrame { get; private set; }
// used to marshal back to UI thread
private SynchronizationContext _context;
// event to get the buffer above handed to you
public event EventHandler<FrameReadyEventArgs> FrameReady;
public MJPEGDecoder()
{
_context = SynchronizationContext.Current;
}
public void ParseStream(Uri uri)
{
ParseStream(uri, null, null);
}
public void ParseStream(Uri uri, string username, string password)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
if (!string.IsNullOrEmpty(username) || !string.IsNullOrEmpty(password))
request.Credentials = new NetworkCredential(username, password);
// asynchronously get a response
request.BeginGetResponse(OnGetResponse, request);
}
public void StopStream()
{
_streamActive = false;
}
private void OnGetResponse(IAsyncResult asyncResult)
{
HttpWebResponse resp;
byte[] buff;
byte[] imageBuffer = new byte[1024 * 1024];
Stream s;
// get the response
HttpWebRequest req = (HttpWebRequest)asyncResult.AsyncState;
resp = (HttpWebResponse)req.EndGetResponse(asyncResult);
// find our magic boundary value
string contentType = resp.Headers["Content-Type"];
if (!string.IsNullOrEmpty(contentType) && !contentType.Contains("="))
throw new Exception("Invalid content-type header. The camera is likely not returning a proper MJPEG stream.");
string boundary = resp.Headers["Content-Type"].Split('=')[1].Replace("\"", "");
byte[] boundaryBytes = Encoding.UTF8.GetBytes(boundary.StartsWith("--") ? boundary : "--" + boundary);
s = resp.GetResponseStream();
BinaryReader br = new BinaryReader(s);
_streamActive = true;
buff = br.ReadBytes(ChunkSize);
while (_streamActive)
{
int size;
// find the JPEG header
int imageStart = GetArrayPosition(buff, JpegHeader);
if (imageStart != -1)
{
// copy the start of the JPEG image to the imageBuffer
size = buff.Length - imageStart;
Array.Copy(buff, imageStart, imageBuffer, 0, size);
while (true)
{
buff = br.ReadBytes(ChunkSize);
// find the boundary text
int imageEnd = GetArrayPosition(buff, boundaryBytes);
if (imageEnd != -1)
{
// copy the remainder of the JPEG to the imageBuffer
Array.Copy(buff, 0, imageBuffer, size, imageEnd);
size += imageEnd;
// create a single JPEG frame
CurrentFrame = new byte[size];
Array.Copy(imageBuffer, 0, CurrentFrame, 0, size);
ProcessFrame(CurrentFrame);
// copy the leftover data to the start
Array.Copy(buff, imageEnd, buff, 0, buff.Length - imageEnd);
// fill the remainder of the buffer with new data and start over
byte[] temp = br.ReadBytes(imageEnd);
Array.Copy(temp, 0, buff, buff.Length - imageEnd, temp.Length);
break;
}
// copy all of the data to the imageBuffer
Array.Copy(buff, 0, imageBuffer, size, buff.Length);
size += buff.Length;
}
}
}
resp.Close();
}
private void ProcessFrame(byte[] frameBuffer)
{
_context.Post(delegate
{
// create a simple GDI+ happy Bitmap
bitmap = new Bitmap(new MemoryStream(frameBuffer));
// tell whoever's listening that we have a frame to draw
FrameReady?.Invoke(this, new FrameReadyEventArgs { FrameBuffer = CurrentFrame, Bitmap = bitmap });
}, null);
}
private int GetArrayPosition(byte[] mbyte, byte[] content)
{
int position = -1;
for (int i = 0; i <= mbyte.Length - content.Length; i++)
{
if (mbyte[i] == content[0])
{
bool isRight = true;
for(int j = 1; j < content.Length; j++)
{
if (mbyte[i + j] != content[j])
{
isRight = false;
break;
}
}
if (isRight) return i;
}
}
return position;
}
}
為了觸發FrameReady事件 還要再加入一個EventArgs類別 如下
public class FrameReadyEventArgs : EventArgs
{
public byte[] FrameBuffer;
public Bitmap Bitmap;
}
到這裡已經萬事俱備了
去FormLoad來使用吧
private void Form1_Load(object sender, EventArgs e)
{
var mjpeg = new MJPEGDecoder();
mjpeg.FrameReady += Mjpeg_FrameReady;
mjpeg.ParseStream(new Uri("http://192.168.0.17/airvideo/media?channel=channel7&streamtype=mjpeg&width=1280&height=720"), "JR", "123");
}
private void Mjpeg_FrameReady(object sender, FrameReadyEventArgs e)
{
pictureBox1.Image = e.Bitmap;
}
結果如下
新手發文,有謬誤請告知,也請多多指教。