/MyBot/VideoProcessing/AForge/Video/MJPEGStream.cs
# · C# · 622 lines · 311 code · 58 blank · 253 comment · 68 complexity · 65646cf9faaa60d476a063ba53c752f4 MD5 · raw file
- // AForge Video Library
- // AForge.NET framework
- // http://www.aforgenet.com/framework/
- //
- // Copyright Š Andrew Kirillov, 2005-2010
- // andrew.kirillov@aforgenet.com
- //
- // Updated by Ivan.Farkas@FL4SaleLive.com, 02/01/2010
- // Fix related to AirLink cameras, which are not accurate with HTTP standard
- //
-
- namespace AForge.Video
- {
- using System;
- using System.Drawing;
- using System.IO;
- using System.Text;
- using System.Threading;
- using System.Net;
-
- /// <summary>
- /// MJPEG video source.
- /// </summary>
- ///
- /// <remarks><para>The video source downloads JPEG images from the specified URL, which represents
- /// MJPEG stream.</para>
- ///
- /// <para>Sample usage:</para>
- /// <code>
- /// // create MJPEG video source
- /// MJPEGStream stream = new MJPEGStream( "some url" );
- /// // set event handlers
- /// stream.NewFrame += new NewFrameEventHandler( video_NewFrame );
- /// // start the video source
- /// stream.Start( );
- /// // ...
- /// // signal to stop
- /// stream.SignalToStop( );
- /// </code>
- ///
- /// <para><note>Some cameras produce HTTP header, which does not conform strictly to
- /// standard, what leads to .NET exception. To avoid this exception the <b>useUnsafeHeaderParsing</b>
- /// configuration option of <b>httpWebRequest</b> should be set, what may be done using application
- /// configuration file.</note></para>
- /// <code>
- /// <configuration>
- /// <system.net>
- /// <settings>
- /// <httpWebRequest useUnsafeHeaderParsing="true" />
- /// </settings>
- /// </system.net>
- /// </configuration>
- /// </code>
- /// </remarks>
- ///
- public class MJPEGStream : IVideoSource
- {
- // URL for MJPEG stream
- private string source;
- // login and password for HTTP authentication
- private string login = null;
- private string password = null;
- // received frames count
- private int framesReceived;
- // recieved byte count
- private int bytesReceived;
- // use separate HTTP connection group or use default
- private bool useSeparateConnectionGroup = true;
- // timeout value for web request
- private int requestTimeout = 10000;
-
- // buffer size used to download MJPEG stream
- private const int bufSize = 512 * 1024;
- // size of portion to read at once
- private const int readSize = 1024;
-
- private Thread thread = null;
- private ManualResetEvent stopEvent = null;
- private ManualResetEvent reloadEvent = null;
-
- private string userAgent = "Mozilla/5.0";
-
- /// <summary>
- /// New frame event.
- /// </summary>
- ///
- /// <remarks><para>Notifies clients about new available frame from video source.</para>
- ///
- /// <para><note>Since video source may have multiple clients, each client is responsible for
- /// making a copy (cloning) of the passed video frame, because the video source disposes its
- /// own original copy after notifying of clients.</note></para>
- /// </remarks>
- ///
- public event NewFrameEventHandler NewFrame;
-
- /// <summary>
- /// Video source error event.
- /// </summary>
- ///
- /// <remarks>This event is used to notify clients about any type of errors occurred in
- /// video source object, for example internal exceptions.</remarks>
- ///
- public event VideoSourceErrorEventHandler VideoSourceError;
-
- /// <summary>
- /// Video playing finished event.
- /// </summary>
- ///
- /// <remarks><para>This event is used to notify clients that the video playing has finished.</para>
- /// </remarks>
- ///
- public event PlayingFinishedEventHandler PlayingFinished;
-
- /// <summary>
- /// Use or not separate connection group.
- /// </summary>
- ///
- /// <remarks>The property indicates to open web request in separate connection group.</remarks>
- ///
- public bool SeparateConnectionGroup
- {
- get { return useSeparateConnectionGroup; }
- set { useSeparateConnectionGroup = value; }
- }
-
- /// <summary>
- /// Video source.
- /// </summary>
- ///
- /// <remarks>URL, which provides MJPEG stream.</remarks>
- ///
- public string Source
- {
- get { return source; }
- set
- {
- source = value;
- // signal to reload
- if ( thread != null )
- reloadEvent.Set( );
- }
- }
-
- /// <summary>
- /// Login value.
- /// </summary>
- ///
- /// <remarks>Login required to access video source.</remarks>
- ///
- public string Login
- {
- get { return login; }
- set { login = value; }
- }
-
- /// <summary>
- /// Password value.
- /// </summary>
- ///
- /// <remarks>Password required to access video source.</remarks>
- ///
- public string Password
- {
- get { return password; }
- set { password = value; }
- }
-
- /// <summary>
- /// User agent to specify in HTTP request header.
- /// </summary>
- ///
- /// <remarks><para>Some IP cameras check what is the requesting user agent and depending
- /// on it they provide video in different formats or do not provide it at all. The property
- /// sets the value of user agent string, which is sent to camera in request header.
- /// </para>
- ///
- /// <para>Default value is set to "Mozilla/5.0". If the value is set to <see langword="null"/>,
- /// the user agent string is not sent in request header.</para>
- /// </remarks>
- ///
- public string HttpUserAgent
- {
- get { return userAgent; }
- set { userAgent = value; }
- }
-
- /// <summary>
- /// Received frames count.
- /// </summary>
- ///
- /// <remarks>Number of frames the video source provided from the moment of the last
- /// access to the property.
- /// </remarks>
- ///
- public int FramesReceived
- {
- get
- {
- int frames = framesReceived;
- framesReceived = 0;
- return frames;
- }
- }
-
- /// <summary>
- /// Received bytes count.
- /// </summary>
- ///
- /// <remarks>Number of bytes the video source provided from the moment of the last
- /// access to the property.
- /// </remarks>
- ///
- public int BytesReceived
- {
- get
- {
- int bytes = bytesReceived;
- bytesReceived = 0;
- return bytes;
- }
- }
-
- /// <summary>
- /// Request timeout value.
- /// </summary>
- ///
- /// <remarks>The property sets timeout value in milliseconds for web requests.
- /// Default value is 10000 milliseconds.</remarks>
- ///
- public int RequestTimeout
- {
- get { return requestTimeout; }
- set { requestTimeout = value; }
- }
-
- /// <summary>
- /// State of the video source.
- /// </summary>
- ///
- /// <remarks>Current state of video source object - running or not.</remarks>
- ///
- public bool IsRunning
- {
- get
- {
- if ( thread != null )
- {
- // check thread status
- if ( thread.Join( 0 ) == false )
- return true;
-
- // the thread is not running, so free resources
- Free( );
- }
- return false;
- }
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MJPEGStream"/> class.
- /// </summary>
- ///
- public MJPEGStream( ) { }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MJPEGStream"/> class.
- /// </summary>
- ///
- /// <param name="source">URL, which provides MJPEG stream.</param>
- ///
- public MJPEGStream( string source )
- {
- this.source = source;
- }
-
- /// <summary>
- /// Start video source.
- /// </summary>
- ///
- /// <remarks>Starts video source and return execution to caller. Video source
- /// object creates background thread and notifies about new frames with the
- /// help of <see cref="NewFrame"/> event.</remarks>
- ///
- /// <exception cref="ArgumentException">Video source is not specified.</exception>
- ///
- public void Start( )
- {
- if ( !IsRunning )
- {
- // check source
- if ( ( source == null ) || ( source == string.Empty ) )
- throw new ArgumentException( "Video source is not specified." );
-
- framesReceived = 0;
- bytesReceived = 0;
-
- // create events
- stopEvent = new ManualResetEvent( false );
- reloadEvent = new ManualResetEvent( false );
-
- // create and start new thread
- thread = new Thread( new ThreadStart( WorkerThread ) );
- thread.Name = source;
- thread.Start( );
- }
- }
-
- /// <summary>
- /// Signal video source to stop its work.
- /// </summary>
- ///
- /// <remarks>Signals video source to stop its background thread, stop to
- /// provide new frames and free resources.</remarks>
- ///
- public void SignalToStop( )
- {
- // stop thread
- if ( thread != null )
- {
- // signal to stop
- stopEvent.Set( );
- }
- }
-
- /// <summary>
- /// Wait for video source has stopped.
- /// </summary>
- ///
- /// <remarks>Waits for source stopping after it was signalled to stop using
- /// <see cref="SignalToStop"/> method.</remarks>
- ///
- public void WaitForStop( )
- {
- if ( thread != null )
- {
- // wait for thread stop
- thread.Join( );
-
- Free( );
- }
- }
-
- /// <summary>
- /// Stop video source.
- /// </summary>
- ///
- /// <remarks><para>Stops video source aborting its thread.</para>
- ///
- /// <para><note>Since the method aborts background thread, its usage is highly not preferred
- /// and should be done only if there are no other options. The correct way of stopping camera
- /// is <see cref="SignalToStop">signaling it stop</see> and then
- /// <see cref="WaitForStop">waiting</see> for background thread's completion.</note></para>
- /// </remarks>
- ///
- public void Stop( )
- {
- if ( this.IsRunning )
- {
- stopEvent.Set( );
- thread.Abort( );
- WaitForStop( );
- }
- }
-
- /// <summary>
- /// Free resource.
- /// </summary>
- ///
- private void Free( )
- {
- thread = null;
-
- // release events
- stopEvent.Close( );
- stopEvent = null;
- reloadEvent.Close( );
- reloadEvent = null;
- }
-
- // Worker thread
- private void WorkerThread( )
- {
- // buffer to read stream
- byte[] buffer = new byte[bufSize];
- // JPEG magic number
- byte[] jpegMagic = new byte[] { 0xFF, 0xD8, 0xFF };
- int jpegMagicLength = 3;
-
- while ( true )
- {
- // reset reload event
- reloadEvent.Reset( );
-
- // HTTP web request
- HttpWebRequest request = null;
- // web responce
- WebResponse response = null;
- // stream for MJPEG downloading
- Stream stream = null;
- // boundary betweeen images
- byte[] boundary = null;
- // length of boundary
- int boundaryLen;
- // read amounts and positions
- int read, todo = 0, total = 0, pos = 0, align = 1;
- int start = 0, stop = 0;
-
- // align
- // 1 = searching for image start
- // 2 = searching for image end
-
- try
- {
- // create request
- request = (HttpWebRequest) WebRequest.Create( source );
- // set user agent
- if ( userAgent != null )
- {
- request.UserAgent = userAgent;
- }
- // set timeout value for the request
- request.Timeout = requestTimeout;
- // set login and password
- if ( ( login != null ) && ( password != null ) && ( login != string.Empty ) )
- request.Credentials = new NetworkCredential( login, password );
- // set connection group name
- if ( useSeparateConnectionGroup )
- request.ConnectionGroupName = GetHashCode( ).ToString( );
- // get response
- response = request.GetResponse( );
-
- // check content type
- string contentType = response.ContentType;
- string[] contentTypeArray = contentType.Split( '/' );
- if ( ! ( ( contentTypeArray[0] == "multipart" ) && ( contentType.Contains( "mixed" ) ) ) )
- {
- // provide information to clients
- if ( VideoSourceError != null )
- {
- VideoSourceError( this, new VideoSourceErrorEventArgs( "Invalid content type" ) );
- }
-
- request.Abort( );
- request = null;
- response.Close( );
- response = null;
-
- // need to stop ?
- if ( stopEvent.WaitOne( 0, true ) )
- break;
- continue;
- }
-
- // get boundary
- ASCIIEncoding encoding = new ASCIIEncoding( );
- string boudaryStr = contentType.Substring( contentType.IndexOf( "boundary=", 0 ) + 9 );
- boundary = encoding.GetBytes( boudaryStr );
- boundaryLen = boundary.Length;
- bool boundaryIsChecked = false;
-
- // get response stream
- stream = response.GetResponseStream( );
-
- // loop
- while ( ( !stopEvent.WaitOne( 0, true ) ) && ( !reloadEvent.WaitOne( 0, true ) ) )
- {
- // check total read
- if ( total > bufSize - readSize )
- {
- total = pos = todo = 0;
- }
-
- // read next portion from stream
- if ( ( read = stream.Read( buffer, total, readSize ) ) == 0 )
- throw new ApplicationException( );
-
- total += read;
- todo += read;
-
- // increment received bytes counter
- bytesReceived += read;
-
- // do we need to check boundary ?
- if ( !boundaryIsChecked )
- {
- // some IP cameras, like AirLink, claim that boundary is "myboundary",
- // when it is really "--myboundary". this needs to be corrected.
-
- pos = ByteArrayUtils.Find( buffer, boundary, 0, todo );
- // continue reading if boudary was not found
- if ( pos == -1 )
- continue;
-
- for ( int i = pos - 1; i >= 0; i-- )
- {
- byte ch = buffer[i];
-
- if ( ( ch == (byte) '\n' ) || ( ch == (byte) '\r' ) )
- {
- break;
- }
-
- boudaryStr = (char) ch + boudaryStr;
- }
-
- boundary = encoding.GetBytes( boudaryStr );
- boundaryLen = boundary.Length;
- boundaryIsChecked = true;
- }
-
- // search for image start
- if ( align == 1 )
- {
- start = ByteArrayUtils.Find( buffer, jpegMagic, pos, todo );
- if ( start != -1 )
- {
- // found JPEG start
- pos = start;
- todo = total - pos;
- align = 2;
- }
- else
- {
- // delimiter not found
- todo = jpegMagicLength - 1;
- pos = total - todo;
- }
- }
-
- // search for image end
- while ( ( align == 2 ) && ( todo >= boundaryLen ) )
- {
- stop = ByteArrayUtils.Find( buffer, boundary, pos, todo );
- if ( stop != -1 )
- {
- pos = stop;
- todo = total - pos;
-
- // increment frames counter
- framesReceived ++;
-
- // image at stop
- if ( ( NewFrame != null ) && ( !stopEvent.WaitOne( 0, true ) ) )
- {
- Bitmap bitmap = (Bitmap) Bitmap.FromStream ( new MemoryStream( buffer, start, stop - start ) );
- // notify client
- NewFrame( this, new NewFrameEventArgs( bitmap ) );
- // release the image
- bitmap.Dispose( );
- bitmap = null;
- }
-
- // shift array
- pos = stop + boundaryLen;
- todo = total - pos;
- Array.Copy( buffer, pos, buffer, 0, todo );
-
- total = todo;
- pos = 0;
- align = 1;
- }
- else
- {
- // boundary not found
- todo = boundaryLen - 1;
- pos = total - todo;
- }
- }
- }
- }
- catch ( WebException exception )
- {
- // provide information to clients
- if ( VideoSourceError != null )
- {
- VideoSourceError( this, new VideoSourceErrorEventArgs( exception.Message ) );
- }
- // wait for a while before the next try
- Thread.Sleep( 250 );
- }
- catch ( ApplicationException )
- {
- // wait for a while before the next try
- Thread.Sleep( 250 );
- }
- catch ( Exception )
- {
- }
- finally
- {
- // abort request
- if ( request != null)
- {
- request.Abort( );
- request = null;
- }
- // close response stream
- if ( stream != null )
- {
- stream.Close( );
- stream = null;
- }
- // close response
- if ( response != null )
- {
- response.Close( );
- response = null;
- }
- }
-
- // need to stop ?
- if ( stopEvent.WaitOne( 0, true ) )
- break;
- }
-
- if ( PlayingFinished != null )
- {
- PlayingFinished( this, ReasonToFinishPlaying.StoppedByUser );
- }
- }
- }
- }