Invalid Space ID: a16Fl4k7Sr36NMeJe5afGb
root/RetroCASC/CASC-Lancelot/Pages/RoomPage/Streaming/VideoStream.cs
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;
}
}
} |