root/RetroCASC/CASC-Lancelot/Pages/RoomPage/Streaming/VideoStream.cs

Author: moorglade Revision: 2 («Previous Next» Latest)
Date: 3 months ago (2009/10/20 20:11 UTC)
Show/hide line numbers
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Windows.Media;
using System.Windows;

namespace CASC.Camlann.Lancelot {
	/// <summary>
	/// 	Video stream source.
	/// </summary>
	public class VideoStream : MediaStreamSource {
		/// <summary>
		/// 	Class holding the codec private data.
		/// </summary>
		private VideoPrivateData codecPrivateData = null;

		/// <summary>
		/// 	Stream description.
		/// </summary>
		private MediaStreamDescription description = null;

		/// <summary>
		/// 	Buffer size, in samples.
		/// </summary>
		private const int BufferSize = 30;

		/// <summary>
		/// 	Buffer holding samples.
		/// </summary>
		private Queue<Sample> buffer = new Queue<Sample>();

		/// <summary>
		/// 	Timeout after which a fake sample will be sent if no samples are available.
		/// </summary>
		private TimeSpan timeout = TimeSpan.FromSeconds( 30.0 );

		/// <summary>
		/// 	An empty dictionary, which, suprisingly, IS needed.
		/// </summary>
		private Dictionary<MediaSampleAttributeKeys, string> empty = new Dictionary<MediaSampleAttributeKeys, string>();

		/// <summary>
		/// 	Has the streaming really started.
		/// </summary>
		private bool started = false;

		/// <summary>
		/// 	When the stream was opened.
		/// </summary>
		private DateTime openedTime;
	
		/// <summary>
		/// 	The difference between "stream's clock" and "our clock".
		/// </summary>
		private double deltaTime;

		/// <summary>
		/// 	Creates a new VideoStream.
		/// </summary>
		/// <param name="codecPrivateData">The codec private data.</param>
		public VideoStream( byte[] codecPrivateData ) {
			this.codecPrivateData = new VideoPrivateData( codecPrivateData );
			return;
		}

		/// <summary>
		/// 	Opens the media stream.
		/// </summary>
		protected override void OpenMediaAsync() {
			// not yet started :]
			this.started = false;
			
			// set the stream attributes
			Dictionary<MediaStreamAttributeKeys, string> streamAttributes = new Dictionary<MediaStreamAttributeKeys, string>();
			streamAttributes[MediaStreamAttributeKeys.VideoFourCC] = "WMV3";
			streamAttributes[MediaStreamAttributeKeys.Width] = this.codecPrivateData.Width.ToString();
			streamAttributes[MediaStreamAttributeKeys.Height] = this.codecPrivateData.Height.ToString();
			streamAttributes[MediaStreamAttributeKeys.CodecPrivateData] = this.codecPrivateData.ToBase16();

			// create the description
			this.description = new MediaStreamDescription( MediaStreamType.Video, streamAttributes );

			// a list of streams
			List<MediaStreamDescription> streams = new List<MediaStreamDescription>();
			streams.Add( this.description );

			// set infinite duration, no seeking
			Dictionary<MediaSourceAttributesKeys, string> sourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>();
			sourceAttributes[MediaSourceAttributesKeys.Duration] = TimeSpan.FromSeconds( 0 ).Ticks.ToString( CultureInfo.InvariantCulture );
			sourceAttributes[MediaSourceAttributesKeys.CanSeek] = false.ToString();

			// set the time
			this.openedTime = DateTime.Now;

			// report success
			this.ReportOpenMediaCompleted( sourceAttributes, streams );
			return;
		}

		/// <summary>
		/// 	Gets a media sample.
		/// </summary>
		/// <param name="mediaStreamType">Sample type.</param>
		protected override void GetSampleAsync( MediaStreamType type ) {
			if ( type != MediaStreamType.Video ) {
				this.ErrorOccurred( "Wrong sample type demanded." );
				return;
			}

			// start a thread to get the sample
			Thread thread = new Thread( new ThreadStart( this.GetSampleThread ) );
			thread.Start();

			return;
		}

		/// <summary>
		/// 	Thread procedure for getting samples.
		/// </summary>
		private void GetSampleThread() {
			// get the sample
			Sample sample = null;

			lock ( this ) {
				// wait while the buffer is empty
				if ( this.buffer.Count == 0 ) {
					if ( !Monitor.Wait( this, this.timeout ) ) {
						MediaStreamSample fakeSample = new MediaStreamSample(
							this.description, new MemoryStream(), 0, 0, 0, this.empty );
						this.ReportGetSampleCompleted( fakeSample );
						return;
					}
				}

				// dequeue a sample
				sample = this.buffer.Dequeue();
			}

			// format the sample into sth usable
			MemoryStream stream = new MemoryStream();
			stream.Write( sample.Buffer, 0, sample.Buffer.Length );
			MediaStreamSample mediaSample = new MediaStreamSample(
					this.description,
					stream,
					0,
					sample.Buffer.Length,
					(long)( ( sample.Time - this.deltaTime ) * 1E7 ),
					this.empty );

			// report
			this.ReportGetSampleCompleted( mediaSample );
			return;
		}

		/// <summary>
		/// 	Adds a new sample to the stream.
		/// </summary>
		/// <param name="sample">The sample to add.</param>
		public void NewSample( Sample sample ) {
			lock ( this ) {
				// check if already started
				if ( !this.started ) {
					// we're waiting for a keyframe
					if ( !sample.IsKeyFrame  ) {
						return;
					}
					// so we start!
					this.started = true;

					// compute the time difference
					this.deltaTime = sample.Time - DateTime.Now.Subtract( this.openedTime ).TotalSeconds;
				}
				
				// if the buffer is full, kill one sample
				if ( this.buffer.Count == VideoStream.BufferSize ) {
					this.buffer.Dequeue();
				}

				// enqueue the sample
				this.buffer.Enqueue( sample );

				// eslup, eslup
				Monitor.Pulse( this );
			}

			return;
		}

		/// <summary>
		/// 	Closes the media stream.
		/// </summary>
		protected override void CloseMedia() {
			lock ( this ) {
				this.buffer.Clear();
			}
			return;
		}

		/// <summary>
		/// 	Seeks.
		/// </summary>
		/// <param name="seekToTime">Sometimes it's very scary here...</param>
		protected override void SeekAsync( long seekToTime ) {
			// btw, we DON'T support seeking :]
			this.ReportSeekCompleted( seekToTime );
			return;
		}

		/// <summary>
		/// 	Some weird stuff.
		/// </summary>
		protected override void GetDiagnosticAsync( MediaStreamSourceDiagnosticKind diagnosticKind ) {
			this.ReportGetDiagnosticCompleted( diagnosticKind, 0 );
			return;
		}

		/// <summary>
		/// 	Some other weird stuff.
		/// </summary>
		protected override void SwitchMediaStreamAsync( MediaStreamDescription mediaStreamDescription ) {
			this.ReportSwitchMediaStreamCompleted( mediaStreamDescription );
			return;
		}
	}
}