root/RetroCASC/CASC-Lancelot/Pages/RoomPage/Streaming/VideoStream.cs
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; } } } |
View as a web page
Download