C# 實現IP視頻監控(攝像頭)畫面推送(簡單的不能再簡單的DEMO),
最近繼續在家休息,在完成上一個Python抓取某音樂網站爬蟲後,琢磨著實現一個基於HTTP推送的 IP視頻監控,比如外出的時候,在家裡
開啟一個監控端(攝像頭+服務端),可以看到實時畫面,如果再加上自動告警,就更好了。公網訪問需要在 路由器上設置 花生殼+端口轉發。
計劃在退休的安卓手機上實現這IP視頻監控軟件,雖然應用市場一大堆別人寫好的軟件,不過我覺得吧,既然是程序員,自己敲代碼實現的軟件會
更有成就感。考慮到需要先驗證下方案的可行性,我用比較熟悉的C# 控制台實現了一個DEMO。
設想的方案:
1.實現一個簡單HTTP服務器,用來接受請求並啟動一個線程處理圖片流的推送功能
2.開發一個實時抓取圖片的線程,並將圖片交給HTTP推送線程
3.HTTP的請求URL參數中 附帶推送頻率、圖片高度和寬度
4.使用一個IP攝像頭監控端(或者Firefox浏覽器),實時查看視頻畫面
5.循環錄制視頻(未實現)
6.對畫面進行監控告警(未實現)
核心技術點:
1.HttpListener (HTTP.SYS)
2.HTTP :multipart/x-mixed-replace;
3.線程同步、委托、事件
4.攝像頭驅動、圖片抓取(Andrew Kirillov 寫的)
5.圖片流解析,顯示(Andrew Kirillov 寫的,也可以直接在Firefox浏覽器打開直接顯示)
運行截圖:
1.視頻監控端 (Andrew Kirillov 寫的 視頻源支持N種,當前配置推送頻率50毫秒 w=240&h=120)

2.視頻服務端(我寫的 簡陋的DEMO 不過實現了功能 嘎嘎)

下面開始貼核心源碼(最近右胳膊有石膏,左手寫代碼 湊合看吧!):
1.建立HTTP服務:

![]()
1 using (HttpListener listerner = new HttpListener())
2 {
3 listerner.AuthenticationSchemes = AuthenticationSchemes.Anonymous;//指定身份驗證 Anonymous匿名訪問
4 listerner.Prefixes.Add("http://+:6666/");
5
6 //listerner.Prefixes.Add("http://+/");
7 //listerner.Prefixes.Add("http://+:8080/");
8 //listerner.Prefixes.Add("http://+:6666/");
9 //listerner.Prefixes.Add("http://+/video.cgi/");
10 //listerner.Prefixes.Add("http://+:8080/video.cgi/");
11
12 listerner.Start();
13 Console.WriteLine("WebServer Start Successed.......");
14 while (true)
15 {
16 try
17 {
18 //等待請求連接
19 //沒有請求則GetContext處於阻塞狀態
20 HttpListenerContext ctx = listerner.GetContext();
21
22 SendImgService oService = new SendImgService();
23 oService.Ctx = ctx;
24 localsev.NewFrame += new CameraEventHandler(oService.camera_NewFrame);
25 ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), oService);
26
27 //Thread osThread = new Thread(new ThreadStart(oService.ServiceRun));
28 //osThread.Start();
29 }
30 catch (Exception ex)
31 {
32 Console.WriteLine(ex);
33 }
34 }
35 listerner.Stop();
36 listerner.Close();
37 }
View Code
2.啟動本地視頻頭,並抓取圖片

![]()
1 public void ServiceRun()
2 {
3
4 FilterCollection filters = new FilterCollection(FilterCategory.VideoInputDevice);
5
6 if (filters.Count == 0)
7 throw new ApplicationException();
8
9 // add all devices to combo
10 foreach (Filter filter in filters)
11 {
12 Console.WriteLine(filter.Name + ":" + filter.MonikerString);
13 }
14 CaptureDevice localSource = new CaptureDevice();
15 localSource.VideoSource = filters[0].MonikerString;
16
17 // create camera
18 camera = new Camera(localSource);
19 // start camera
20 camera.Start();
21
22
23 // set event handlers
24 camera.NewFrame += new CameraEventHandler(camera_NewFrame);
25
26 }
27
28 // On new frame ready
29 private void camera_NewFrame(object sender, CameraEventArgs e)
30 {
31 if (seq == 999)
32 {
33 seq = 0;
34 }
35 // Console.WriteLine("LocalCamService get camera_NewFrame ==> {0}", ++seq);
36
37 // lock
38 Monitor.Enter(this);
39
40 if (camera != null)
41 {
42 camera.Lock();
43
44 // dispose old frame
45 if (lastFrame != null)
46 {
47 lastFrame.Dispose();
48 }
49 // draw frame
50 if (camera.LastFrame != null)
51 {
52 lastFrame = (Bitmap)camera.LastFrame.Clone();
53 // notify client
54 if (NewFrame != null)
55 NewFrame(this, new CameraEventArgs(lastFrame));
56 }
57
58
59 camera.Unlock();
60 }
61
62 // unlock
63 Monitor.Exit(this);
64 }
65 }
View Code
3.圖片推送

![]()
1 public void ServiceRun()
2 {
3 remoteInfo = ctx.Request.RemoteEndPoint.ToString();
4 string intervalstr = ctx.Request.QueryString["i"];
5 string widthstr = ctx.Request.QueryString["w"];
6 string heightstr = ctx.Request.QueryString["h"];
7
8 if (!string.IsNullOrWhiteSpace(intervalstr))
9 {
10 interval = int.Parse(intervalstr);
11 }
12 if (!string.IsNullOrWhiteSpace(widthstr))
13 {
14 width = int.Parse(widthstr);
15 }
16 if (!string.IsNullOrWhiteSpace(heightstr))
17 {
18 height = int.Parse(heightstr);
19 }
20 Console.WriteLine("Accept one new request:{0},interval:[{1}]", remoteInfo, interval);
21
22
23 ctx.Response.StatusCode = 200;//設置返回給客服端http狀態代碼
24 ctx.Response.ContentType = "multipart/x-mixed-replace; boundary=--BoundaryString";
25
26 string rspheard = "--BoundaryString\r\nContent-type: image/jpg\r\nContent-Length: {0}\r\n\r\n";
27 string strrn = "\r\n";
28
29 using (Stream stream = ctx.Response.OutputStream)
30 {
31 while (true)
32 {
33 Thread.Sleep(interval);
34
35 try
36 {
37 // lock
38 Monitor.Enter(this);
39
40 if (newFrame == null)
41 {
42 continue;
43 }
44 //得到一個ms對象
45 byte[] imageBuffer;
46 using (MemoryStream ms = new MemoryStream())
47 {
48
49 //newFrame = (Bitmap)GetThumbnail(newFrame, width, height);
50 //將圖片保存至內存流
51 newFrame.Save(ms, ImageFormat.Jpeg);
52
53 rspheard = string.Format(rspheard, ms.Length);
54
55 byte[] heardbuff = Encoding.ASCII.GetBytes(rspheard);
56 stream.Write(heardbuff, 0, heardbuff.Length);
57
58 imageBuffer = new byte[512];
59 int c;
60 ms.Position = 0;
61 //通過內存流讀取到imageBytes
62 while ((c = ms.Read(imageBuffer, 0, 512)) > 0)
63 {
64 stream.Write(imageBuffer, 0, c);
65 }
66 byte[] rnbuff = Encoding.ASCII.GetBytes(strrn);
67 stream.Write(rnbuff, 0, rnbuff.Length);
68
69 Console.WriteLine("[{0}] : SendImgService send NewFrame", remoteInfo);
70
71 }
72
73 // stream.Flush();
74 }
75 catch (Exception ex)
76 {
77 Console.WriteLine(ex);
78
79 break;
80 }
81 finally
82 {
83 // unlock
84 Monitor.Exit(this);
85 }
86 }
87 }
88 Console.WriteLine("[{0}] : 線程結束...", remoteInfo);
89 }
View Code
附件:(剛會傳文件,還不知道怎麼插入鏈接,誰教我下?)
可運行程序:
http://files.cnblogs.com/files/ryhan/%E7%9B%91%E6%8E%A7%E5%8F%8A%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%8F%AF%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F.zip
監控端源碼:
http://files.cnblogs.com/files/ryhan/%E7%9B%91%E6%8E%A7%E7%AB%AF%E6%BA%90%E7%A0%81.zip
服務端源碼:
http://files.cnblogs.com/files/ryhan/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%BA%90%E7%A0%81%28%E5%8D%9A%E5%AE%A2%E4%B8%AD%E5%AE%9E%E7%8E%B0%29.zip
PS:
1.建議用VS2010打開
2.監控端cv_src目錄下cv3.sln為監控客戶端程序,用來看畫面,cameras.config配置視頻源
3.HttpImageStream是本次實現的圖片推送Demo 效率上估計有點問題。
4.運行HttpImageStream時,建議電腦上有攝像頭,不然估計會無法啟動。