/MyBot/VideoProcessing/AForge/Video/MJPEGStream.cs

# · C# · 622 lines · 311 code · 58 blank · 253 comment · 68 complexity · 65646cf9faaa60d476a063ba53c752f4 MD5 · raw file

  1. // AForge Video Library
  2. // AForge.NET framework
  3. // http://www.aforgenet.com/framework/
  4. //
  5. // Copyright Š Andrew Kirillov, 2005-2010
  6. // andrew.kirillov@aforgenet.com
  7. //
  8. // Updated by Ivan.Farkas@FL4SaleLive.com, 02/01/2010
  9. // Fix related to AirLink cameras, which are not accurate with HTTP standard
  10. //
  11. namespace AForge.Video
  12. {
  13. using System;
  14. using System.Drawing;
  15. using System.IO;
  16. using System.Text;
  17. using System.Threading;
  18. using System.Net;
  19. /// <summary>
  20. /// MJPEG video source.
  21. /// </summary>
  22. ///
  23. /// <remarks><para>The video source downloads JPEG images from the specified URL, which represents
  24. /// MJPEG stream.</para>
  25. ///
  26. /// <para>Sample usage:</para>
  27. /// <code>
  28. /// // create MJPEG video source
  29. /// MJPEGStream stream = new MJPEGStream( "some url" );
  30. /// // set event handlers
  31. /// stream.NewFrame += new NewFrameEventHandler( video_NewFrame );
  32. /// // start the video source
  33. /// stream.Start( );
  34. /// // ...
  35. /// // signal to stop
  36. /// stream.SignalToStop( );
  37. /// </code>
  38. ///
  39. /// <para><note>Some cameras produce HTTP header, which does not conform strictly to
  40. /// standard, what leads to .NET exception. To avoid this exception the <b>useUnsafeHeaderParsing</b>
  41. /// configuration option of <b>httpWebRequest</b> should be set, what may be done using application
  42. /// configuration file.</note></para>
  43. /// <code>
  44. /// &lt;configuration&gt;
  45. /// &lt;system.net&gt;
  46. /// &lt;settings&gt;
  47. /// &lt;httpWebRequest useUnsafeHeaderParsing="true" /&gt;
  48. /// &lt;/settings&gt;
  49. /// &lt;/system.net&gt;
  50. /// &lt;/configuration&gt;
  51. /// </code>
  52. /// </remarks>
  53. ///
  54. public class MJPEGStream : IVideoSource
  55. {
  56. // URL for MJPEG stream
  57. private string source;
  58. // login and password for HTTP authentication
  59. private string login = null;
  60. private string password = null;
  61. // received frames count
  62. private int framesReceived;
  63. // recieved byte count
  64. private int bytesReceived;
  65. // use separate HTTP connection group or use default
  66. private bool useSeparateConnectionGroup = true;
  67. // timeout value for web request
  68. private int requestTimeout = 10000;
  69. // buffer size used to download MJPEG stream
  70. private const int bufSize = 512 * 1024;
  71. // size of portion to read at once
  72. private const int readSize = 1024;
  73. private Thread thread = null;
  74. private ManualResetEvent stopEvent = null;
  75. private ManualResetEvent reloadEvent = null;
  76. private string userAgent = "Mozilla/5.0";
  77. /// <summary>
  78. /// New frame event.
  79. /// </summary>
  80. ///
  81. /// <remarks><para>Notifies clients about new available frame from video source.</para>
  82. ///
  83. /// <para><note>Since video source may have multiple clients, each client is responsible for
  84. /// making a copy (cloning) of the passed video frame, because the video source disposes its
  85. /// own original copy after notifying of clients.</note></para>
  86. /// </remarks>
  87. ///
  88. public event NewFrameEventHandler NewFrame;
  89. /// <summary>
  90. /// Video source error event.
  91. /// </summary>
  92. ///
  93. /// <remarks>This event is used to notify clients about any type of errors occurred in
  94. /// video source object, for example internal exceptions.</remarks>
  95. ///
  96. public event VideoSourceErrorEventHandler VideoSourceError;
  97. /// <summary>
  98. /// Video playing finished event.
  99. /// </summary>
  100. ///
  101. /// <remarks><para>This event is used to notify clients that the video playing has finished.</para>
  102. /// </remarks>
  103. ///
  104. public event PlayingFinishedEventHandler PlayingFinished;
  105. /// <summary>
  106. /// Use or not separate connection group.
  107. /// </summary>
  108. ///
  109. /// <remarks>The property indicates to open web request in separate connection group.</remarks>
  110. ///
  111. public bool SeparateConnectionGroup
  112. {
  113. get { return useSeparateConnectionGroup; }
  114. set { useSeparateConnectionGroup = value; }
  115. }
  116. /// <summary>
  117. /// Video source.
  118. /// </summary>
  119. ///
  120. /// <remarks>URL, which provides MJPEG stream.</remarks>
  121. ///
  122. public string Source
  123. {
  124. get { return source; }
  125. set
  126. {
  127. source = value;
  128. // signal to reload
  129. if ( thread != null )
  130. reloadEvent.Set( );
  131. }
  132. }
  133. /// <summary>
  134. /// Login value.
  135. /// </summary>
  136. ///
  137. /// <remarks>Login required to access video source.</remarks>
  138. ///
  139. public string Login
  140. {
  141. get { return login; }
  142. set { login = value; }
  143. }
  144. /// <summary>
  145. /// Password value.
  146. /// </summary>
  147. ///
  148. /// <remarks>Password required to access video source.</remarks>
  149. ///
  150. public string Password
  151. {
  152. get { return password; }
  153. set { password = value; }
  154. }
  155. /// <summary>
  156. /// User agent to specify in HTTP request header.
  157. /// </summary>
  158. ///
  159. /// <remarks><para>Some IP cameras check what is the requesting user agent and depending
  160. /// on it they provide video in different formats or do not provide it at all. The property
  161. /// sets the value of user agent string, which is sent to camera in request header.
  162. /// </para>
  163. ///
  164. /// <para>Default value is set to "Mozilla/5.0". If the value is set to <see langword="null"/>,
  165. /// the user agent string is not sent in request header.</para>
  166. /// </remarks>
  167. ///
  168. public string HttpUserAgent
  169. {
  170. get { return userAgent; }
  171. set { userAgent = value; }
  172. }
  173. /// <summary>
  174. /// Received frames count.
  175. /// </summary>
  176. ///
  177. /// <remarks>Number of frames the video source provided from the moment of the last
  178. /// access to the property.
  179. /// </remarks>
  180. ///
  181. public int FramesReceived
  182. {
  183. get
  184. {
  185. int frames = framesReceived;
  186. framesReceived = 0;
  187. return frames;
  188. }
  189. }
  190. /// <summary>
  191. /// Received bytes count.
  192. /// </summary>
  193. ///
  194. /// <remarks>Number of bytes the video source provided from the moment of the last
  195. /// access to the property.
  196. /// </remarks>
  197. ///
  198. public int BytesReceived
  199. {
  200. get
  201. {
  202. int bytes = bytesReceived;
  203. bytesReceived = 0;
  204. return bytes;
  205. }
  206. }
  207. /// <summary>
  208. /// Request timeout value.
  209. /// </summary>
  210. ///
  211. /// <remarks>The property sets timeout value in milliseconds for web requests.
  212. /// Default value is 10000 milliseconds.</remarks>
  213. ///
  214. public int RequestTimeout
  215. {
  216. get { return requestTimeout; }
  217. set { requestTimeout = value; }
  218. }
  219. /// <summary>
  220. /// State of the video source.
  221. /// </summary>
  222. ///
  223. /// <remarks>Current state of video source object - running or not.</remarks>
  224. ///
  225. public bool IsRunning
  226. {
  227. get
  228. {
  229. if ( thread != null )
  230. {
  231. // check thread status
  232. if ( thread.Join( 0 ) == false )
  233. return true;
  234. // the thread is not running, so free resources
  235. Free( );
  236. }
  237. return false;
  238. }
  239. }
  240. /// <summary>
  241. /// Initializes a new instance of the <see cref="MJPEGStream"/> class.
  242. /// </summary>
  243. ///
  244. public MJPEGStream( ) { }
  245. /// <summary>
  246. /// Initializes a new instance of the <see cref="MJPEGStream"/> class.
  247. /// </summary>
  248. ///
  249. /// <param name="source">URL, which provides MJPEG stream.</param>
  250. ///
  251. public MJPEGStream( string source )
  252. {
  253. this.source = source;
  254. }
  255. /// <summary>
  256. /// Start video source.
  257. /// </summary>
  258. ///
  259. /// <remarks>Starts video source and return execution to caller. Video source
  260. /// object creates background thread and notifies about new frames with the
  261. /// help of <see cref="NewFrame"/> event.</remarks>
  262. ///
  263. /// <exception cref="ArgumentException">Video source is not specified.</exception>
  264. ///
  265. public void Start( )
  266. {
  267. if ( !IsRunning )
  268. {
  269. // check source
  270. if ( ( source == null ) || ( source == string.Empty ) )
  271. throw new ArgumentException( "Video source is not specified." );
  272. framesReceived = 0;
  273. bytesReceived = 0;
  274. // create events
  275. stopEvent = new ManualResetEvent( false );
  276. reloadEvent = new ManualResetEvent( false );
  277. // create and start new thread
  278. thread = new Thread( new ThreadStart( WorkerThread ) );
  279. thread.Name = source;
  280. thread.Start( );
  281. }
  282. }
  283. /// <summary>
  284. /// Signal video source to stop its work.
  285. /// </summary>
  286. ///
  287. /// <remarks>Signals video source to stop its background thread, stop to
  288. /// provide new frames and free resources.</remarks>
  289. ///
  290. public void SignalToStop( )
  291. {
  292. // stop thread
  293. if ( thread != null )
  294. {
  295. // signal to stop
  296. stopEvent.Set( );
  297. }
  298. }
  299. /// <summary>
  300. /// Wait for video source has stopped.
  301. /// </summary>
  302. ///
  303. /// <remarks>Waits for source stopping after it was signalled to stop using
  304. /// <see cref="SignalToStop"/> method.</remarks>
  305. ///
  306. public void WaitForStop( )
  307. {
  308. if ( thread != null )
  309. {
  310. // wait for thread stop
  311. thread.Join( );
  312. Free( );
  313. }
  314. }
  315. /// <summary>
  316. /// Stop video source.
  317. /// </summary>
  318. ///
  319. /// <remarks><para>Stops video source aborting its thread.</para>
  320. ///
  321. /// <para><note>Since the method aborts background thread, its usage is highly not preferred
  322. /// and should be done only if there are no other options. The correct way of stopping camera
  323. /// is <see cref="SignalToStop">signaling it stop</see> and then
  324. /// <see cref="WaitForStop">waiting</see> for background thread's completion.</note></para>
  325. /// </remarks>
  326. ///
  327. public void Stop( )
  328. {
  329. if ( this.IsRunning )
  330. {
  331. stopEvent.Set( );
  332. thread.Abort( );
  333. WaitForStop( );
  334. }
  335. }
  336. /// <summary>
  337. /// Free resource.
  338. /// </summary>
  339. ///
  340. private void Free( )
  341. {
  342. thread = null;
  343. // release events
  344. stopEvent.Close( );
  345. stopEvent = null;
  346. reloadEvent.Close( );
  347. reloadEvent = null;
  348. }
  349. // Worker thread
  350. private void WorkerThread( )
  351. {
  352. // buffer to read stream
  353. byte[] buffer = new byte[bufSize];
  354. // JPEG magic number
  355. byte[] jpegMagic = new byte[] { 0xFF, 0xD8, 0xFF };
  356. int jpegMagicLength = 3;
  357. while ( true )
  358. {
  359. // reset reload event
  360. reloadEvent.Reset( );
  361. // HTTP web request
  362. HttpWebRequest request = null;
  363. // web responce
  364. WebResponse response = null;
  365. // stream for MJPEG downloading
  366. Stream stream = null;
  367. // boundary betweeen images
  368. byte[] boundary = null;
  369. // length of boundary
  370. int boundaryLen;
  371. // read amounts and positions
  372. int read, todo = 0, total = 0, pos = 0, align = 1;
  373. int start = 0, stop = 0;
  374. // align
  375. // 1 = searching for image start
  376. // 2 = searching for image end
  377. try
  378. {
  379. // create request
  380. request = (HttpWebRequest) WebRequest.Create( source );
  381. // set user agent
  382. if ( userAgent != null )
  383. {
  384. request.UserAgent = userAgent;
  385. }
  386. // set timeout value for the request
  387. request.Timeout = requestTimeout;
  388. // set login and password
  389. if ( ( login != null ) && ( password != null ) && ( login != string.Empty ) )
  390. request.Credentials = new NetworkCredential( login, password );
  391. // set connection group name
  392. if ( useSeparateConnectionGroup )
  393. request.ConnectionGroupName = GetHashCode( ).ToString( );
  394. // get response
  395. response = request.GetResponse( );
  396. // check content type
  397. string contentType = response.ContentType;
  398. string[] contentTypeArray = contentType.Split( '/' );
  399. if ( ! ( ( contentTypeArray[0] == "multipart" ) && ( contentType.Contains( "mixed" ) ) ) )
  400. {
  401. // provide information to clients
  402. if ( VideoSourceError != null )
  403. {
  404. VideoSourceError( this, new VideoSourceErrorEventArgs( "Invalid content type" ) );
  405. }
  406. request.Abort( );
  407. request = null;
  408. response.Close( );
  409. response = null;
  410. // need to stop ?
  411. if ( stopEvent.WaitOne( 0, true ) )
  412. break;
  413. continue;
  414. }
  415. // get boundary
  416. ASCIIEncoding encoding = new ASCIIEncoding( );
  417. string boudaryStr = contentType.Substring( contentType.IndexOf( "boundary=", 0 ) + 9 );
  418. boundary = encoding.GetBytes( boudaryStr );
  419. boundaryLen = boundary.Length;
  420. bool boundaryIsChecked = false;
  421. // get response stream
  422. stream = response.GetResponseStream( );
  423. // loop
  424. while ( ( !stopEvent.WaitOne( 0, true ) ) && ( !reloadEvent.WaitOne( 0, true ) ) )
  425. {
  426. // check total read
  427. if ( total > bufSize - readSize )
  428. {
  429. total = pos = todo = 0;
  430. }
  431. // read next portion from stream
  432. if ( ( read = stream.Read( buffer, total, readSize ) ) == 0 )
  433. throw new ApplicationException( );
  434. total += read;
  435. todo += read;
  436. // increment received bytes counter
  437. bytesReceived += read;
  438. // do we need to check boundary ?
  439. if ( !boundaryIsChecked )
  440. {
  441. // some IP cameras, like AirLink, claim that boundary is "myboundary",
  442. // when it is really "--myboundary". this needs to be corrected.
  443. pos = ByteArrayUtils.Find( buffer, boundary, 0, todo );
  444. // continue reading if boudary was not found
  445. if ( pos == -1 )
  446. continue;
  447. for ( int i = pos - 1; i >= 0; i-- )
  448. {
  449. byte ch = buffer[i];
  450. if ( ( ch == (byte) '\n' ) || ( ch == (byte) '\r' ) )
  451. {
  452. break;
  453. }
  454. boudaryStr = (char) ch + boudaryStr;
  455. }
  456. boundary = encoding.GetBytes( boudaryStr );
  457. boundaryLen = boundary.Length;
  458. boundaryIsChecked = true;
  459. }
  460. // search for image start
  461. if ( align == 1 )
  462. {
  463. start = ByteArrayUtils.Find( buffer, jpegMagic, pos, todo );
  464. if ( start != -1 )
  465. {
  466. // found JPEG start
  467. pos = start;
  468. todo = total - pos;
  469. align = 2;
  470. }
  471. else
  472. {
  473. // delimiter not found
  474. todo = jpegMagicLength - 1;
  475. pos = total - todo;
  476. }
  477. }
  478. // search for image end
  479. while ( ( align == 2 ) && ( todo >= boundaryLen ) )
  480. {
  481. stop = ByteArrayUtils.Find( buffer, boundary, pos, todo );
  482. if ( stop != -1 )
  483. {
  484. pos = stop;
  485. todo = total - pos;
  486. // increment frames counter
  487. framesReceived ++;
  488. // image at stop
  489. if ( ( NewFrame != null ) && ( !stopEvent.WaitOne( 0, true ) ) )
  490. {
  491. Bitmap bitmap = (Bitmap) Bitmap.FromStream ( new MemoryStream( buffer, start, stop - start ) );
  492. // notify client
  493. NewFrame( this, new NewFrameEventArgs( bitmap ) );
  494. // release the image
  495. bitmap.Dispose( );
  496. bitmap = null;
  497. }
  498. // shift array
  499. pos = stop + boundaryLen;
  500. todo = total - pos;
  501. Array.Copy( buffer, pos, buffer, 0, todo );
  502. total = todo;
  503. pos = 0;
  504. align = 1;
  505. }
  506. else
  507. {
  508. // boundary not found
  509. todo = boundaryLen - 1;
  510. pos = total - todo;
  511. }
  512. }
  513. }
  514. }
  515. catch ( WebException exception )
  516. {
  517. // provide information to clients
  518. if ( VideoSourceError != null )
  519. {
  520. VideoSourceError( this, new VideoSourceErrorEventArgs( exception.Message ) );
  521. }
  522. // wait for a while before the next try
  523. Thread.Sleep( 250 );
  524. }
  525. catch ( ApplicationException )
  526. {
  527. // wait for a while before the next try
  528. Thread.Sleep( 250 );
  529. }
  530. catch ( Exception )
  531. {
  532. }
  533. finally
  534. {
  535. // abort request
  536. if ( request != null)
  537. {
  538. request.Abort( );
  539. request = null;
  540. }
  541. // close response stream
  542. if ( stream != null )
  543. {
  544. stream.Close( );
  545. stream = null;
  546. }
  547. // close response
  548. if ( response != null )
  549. {
  550. response.Close( );
  551. response = null;
  552. }
  553. }
  554. // need to stop ?
  555. if ( stopEvent.WaitOne( 0, true ) )
  556. break;
  557. }
  558. if ( PlayingFinished != null )
  559. {
  560. PlayingFinished( this, ReasonToFinishPlaying.StoppedByUser );
  561. }
  562. }
  563. }
  564. }