// // If data is collected from ConferenceRecorder, or some other class, // add an overloaded AddInstanceForCollection method here. // // Note: You could, if you wanted, make AddInstanceForCollection accept an object, // but that would be less performant (and this is a *Performance*Counter... :) // /// <summary> /// Remove an instance from the set of classes on which data is collected. /// </summary> internal void RemoveInstanceForCollection(BufferPlayer ss) { lock (buffers) { if (buffers.Contains(ss)) { buffers.Remove(ss); } } }
/// <summary> /// Accepts an instance of a class on which data is collected for this performance counter category. /// </summary> internal void AddInstanceForCollection(BufferPlayer ss) { lock (buffers) { if (!buffers.Contains(ss)) { buffers.Add(ss); } } }
/// <summary> /// Skips this stream to a given time point, repopulating or adjusting buffers as necessary. /// </summary> public void JumpTo(long timeToJumpTo) { // We may need to "restart" after this jump, so don't pretend to be stopped: this.currentStreamEnded = false; // Find out if any of the buffers contain this time point int usefulBuffer = -1; for (int cnt = 0; cnt < buffers.Length; ++cnt) { BufferPlayer buffer = buffers[0]; if (!buffer.Populating) // If the buffer is populating, we can't read Ticks from it { if (buffer.FirstTick <= timeToJumpTo && timeToJumpTo <= buffer.LastTick) { usefulBuffer = cnt; break; } } } // Setup the active buffer if (usefulBuffer != -1) { this.activeBufferIndex = usefulBuffer; this.activeBuffer = buffers[usefulBuffer]; this.activeBuffer.JumpToPointInBuffer(timeToJumpTo); } else { // do this synchronously so that we can populate the next buffer this.activeBuffer.Populate(timeToJumpTo); } // See if we need to dispose our sender because it's not in use // Double the SenderCreationLeadTime, as we don't want to dispose a sender just to create a new one momentarily if (timeToJumpTo < this.firstStreamTicks - 2 * Constants.SenderCreationLeadTime) { DisposeSender(); } // Make sure we have at least one other buffer with good data in it int newBufferIndex = (activeBufferIndex + 1) % Constants.BuffersPerStream; BufferPlayer newBuffer = buffers[newBufferIndex]; newBuffer.EnablePopulation(activeBuffer.LastTick + 1, true); }
/// <summary> /// get details of the stream...raw data and start tiume etc /// set up the stream senders associated with us. we have n, one active and the rest acting as buffers /// </summary> public StreamPlayer(RtpSession session, int newStreamID, ConferencePlayerPC cppc) { int maxFrameSize, maxFrameCount, maxBufferSize; // This occasionally throws due to bad data. Let ConferencePlayer handle it. DBHelper.GetStreamStatistics(newStreamID, out this.firstStreamTicks, out maxFrameSize, out maxFrameCount, out maxBufferSize); streamID = newStreamID; totalFramesSent = 0; totalBytesSent = 0; totalLateness = 0; buffers = new BufferPlayer[Constants.BuffersPerStream]; perfCounter = cppc; string payload; DBHelper.GetStreamAndParticipantDetails(streamID, out name, out payload, out cname, out privExtns); streamPayload = (PayloadType)Enum.Parse(typeof(PayloadType), payload, true); // for delayed buffers (late joiners), we create the rtpSender later this.rtpSession = session; Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Playing back stream: {0}; payload: {1}; name: {2} : {3}", streamID, streamPayload, cname, name)); // buffer n buffers worth of data long startingTick = this.firstStreamTicks; for (int i = 0; i < buffers.Length; i++) { buffers[i] = new BufferPlayer(streamID, maxFrameSize, maxFrameCount, maxBufferSize); buffers[i].Populate(startingTick); startingTick = buffers[i].LastTick + 1; perfCounter.AddInstanceForCollection(buffers[i]); } // first stream is initially active activeBufferIndex = 0; activeBuffer = buffers[activeBufferIndex]; }
/// <summary> /// get details of the stream...raw data and start tiume etc /// set up the stream senders associated with us. we have n, one active and the rest acting as buffers /// </summary> public StreamPlayer( RtpSession session, int newStreamID, ConferencePlayerPC cppc) { int maxFrameSize, maxFrameCount, maxBufferSize; // This occasionally throws due to bad data. Let ConferencePlayer handle it. DBHelper.GetStreamStatistics( newStreamID, out this.firstStreamTicks, out maxFrameSize, out maxFrameCount, out maxBufferSize); streamID = newStreamID; totalFramesSent = 0; totalBytesSent = 0; totalLateness = 0; buffers = new BufferPlayer[Constants.BuffersPerStream]; perfCounter = cppc; string payload; DBHelper.GetStreamAndParticipantDetails( streamID, out name, out payload, out cname, out privExtns ); streamPayload = (PayloadType)Enum.Parse( typeof(PayloadType), payload, true ); // for delayed buffers (late joiners), we create the rtpSender later this.rtpSession = session; Trace.WriteLine("Playing back stream: "+streamID+"; payload: "+streamPayload+"; name: "+ (cname + " : " +name)); // buffer n buffers worth of data long startingTick = this.firstStreamTicks; for ( int i = 0; i < buffers.Length; i++) { buffers[i] = new BufferPlayer( streamID, maxFrameSize, maxFrameCount, maxBufferSize); buffers[i].Populate( startingTick ); startingTick = buffers[i].LastTick + 1; perfCounter.AddInstanceForCollection(buffers[i]); } // first stream is initially active activeBufferIndex = 0; activeBuffer = buffers[activeBufferIndex]; }
/// <summary> /// Sends all of the frames this stream needs to send by the given time boundary. /// Also calls populate on the underlying buffer as necessary /// </summary> /// <returns>the time of the next frame to be sent, in ticks</returns> public long OnWakeUp(long timeBoundary) { if( this.currentStreamEnded ) return long.MaxValue; #if TIMING long startTimer, takenTime; #endif // We must return the time until the next frame. Keep up with it via this variable. long timeUntilFrame = 0; // Allowing the rtpSender to be created late allows the playback to properly replicate the situation seen // during recording. "Late joiner" support, if you will. This also deals with the underlying 500ms delay // implanted into RtpSession.CreateRtpSender. if( rtpSender != null ) { #if TIMING startTimer = DateTime.Now.Ticks; #endif #region Send Frames if( !activeBuffer.Populating && !activeBuffer.Empty ) { if( this.outOfDataLoggedFlag ) this.outOfDataLoggedFlag = false; totalFramesSent += activeBuffer.SendFrames( rtpSender, timeBoundary, out timeUntilFrame, ref totalBytesSent, ref totalLateness ); } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if( takenTime > Constants.TicksSpent ) Trace.WriteLine("TIMING: TIME WASTED SENDING FRAMES: "+(takenTime / Constants.TicksPerMs)+" ms"); startTimer = DateTime.Now.Ticks; #endif #region Empty Buffer if( activeBuffer.Empty ) { if( !activeBuffer.EndOfStream ) // not the end of the stream, so we go get new data to play out { // guaranteed atomic BufferPlayer oldBuffer = activeBuffer; // Get the next buffer int newBufferIndex = ( activeBufferIndex+1 ) % Constants.BuffersPerStream; BufferPlayer newBuffer = buffers[newBufferIndex]; if ( !newBuffer.Populating ) // we can't enable population on the old buffer until we can get the 'LastTick' from the new buffer { if( !newBuffer.EndOfStream ) // if we're at the end of the stream, don't enable population on the old buffer { // Broken here so we only work with 2 buffers oldBuffer.EnablePopulation( newBuffer.LastTick + 1 ); } } else // we're in a performance rut and have run out of data { // the "outOfDataLoggedFlag" prevents us from event-logging too much if( !outOfDataLoggedFlag ) { // The database can't keep up; both buffers are empty! eventLog.WriteEntry("StreamPlayer::OnWakeUp reached and no data in buffers! ID: " + streamID, EventLogEntryType.Warning, ArchiveServiceEventLog.ID.EmptyBuffersInPlayback); this.emptyErrors++; this.outOfDataLoggedFlag = true; } } timeUntilFrame = 0; // we need to come back quick to see when the first frame is in the next buffer // save the "new" buffer as the "current" one activeBufferIndex = newBufferIndex; activeBuffer = buffers[newBufferIndex]; } else // end of stream { if( !currentStreamEnded ) { Trace.WriteLine("End of stream reached. Stopping sending this stream. ID: " + streamID); currentStreamEnded = true; DisposeSender(); ThreadPool.QueueUserWorkItem(new WaitCallback(FireEndOfStream), this); } timeUntilFrame = long.MaxValue; } } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if( takenTime > Constants.TicksSpent ) Trace.WriteLine("TIMING: TIME WASTED ON EMPTY BUFFER: "+(takenTime / Constants.TicksPerMs)+" ms"); #endif } else // Sender isn't created yet. Check if we need to. { #if TIMING startTimer = DateTime.Now.Ticks; #endif // Pri2: Change this to be compatable with use of the "playback speed" feature. TimeBoundary is speed-based... timeUntilFrame = (firstStreamTicks - timeBoundary); #region Sender creation if( timeUntilFrame <= Constants.SenderCreationLeadTime ) // <x> ms of "prep time" to get fired up { if( !createSenderFired ) { createSenderFired = true; Trace.WriteLine("RtpSender being created for stream: "+streamID); ThreadPool.QueueUserWorkItem(new WaitCallback(CreateSender)); } } else { timeUntilFrame -= Constants.SenderCreationLeadTime; } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if( takenTime > Constants.TicksSpent ) Trace.WriteLine("TIMING: TIME WASTED CREATING SENDER: "+(takenTime / Constants.TicksPerMs)+" ms"); #endif } return timeUntilFrame; }
/// <summary> /// Skips this stream to a given time point, repopulating or adjusting buffers as necessary. /// </summary> public void JumpTo(long timeToJumpTo) { // We may need to "restart" after this jump, so don't pretend to be stopped: this.currentStreamEnded = false; // Find out if any of the buffers contain this time point int usefulBuffer = -1; for( int cnt = 0; cnt < buffers.Length; ++cnt ) { BufferPlayer buffer = buffers[0]; if( !buffer.Populating ) // If the buffer is populating, we can't read Ticks from it { if( buffer.FirstTick <= timeToJumpTo && timeToJumpTo <= buffer.LastTick ) { usefulBuffer = cnt; break; } } } // Setup the active buffer if( usefulBuffer != -1 ) { this.activeBufferIndex = usefulBuffer; this.activeBuffer = buffers[usefulBuffer]; this.activeBuffer.JumpToPointInBuffer (timeToJumpTo); } else { // do this synchronously so that we can populate the next buffer this.activeBuffer.Populate (timeToJumpTo); } // See if we need to dispose our sender because it's not in use // Double the SenderCreationLeadTime, as we don't want to dispose a sender just to create a new one momentarily if( timeToJumpTo < this.firstStreamTicks - 2*Constants.SenderCreationLeadTime ) { DisposeSender(); } // Make sure we have at least one other buffer with good data in it int newBufferIndex = (activeBufferIndex+1) % Constants.BuffersPerStream; BufferPlayer newBuffer = buffers[newBufferIndex]; newBuffer.EnablePopulation (activeBuffer.LastTick + 1, true); }
// // If data is collected from ConferenceRecorder, or some other class, // add an overloaded AddInstanceForCollection method here. // // Note: You could, if you wanted, make AddInstanceForCollection accept an object, // but that would be less performant (and this is a *Performance*Counter... :) // /// <summary> /// Remove an instance from the set of classes on which data is collected. /// </summary> internal void RemoveInstanceForCollection(BufferPlayer ss) { lock(buffers) { if( buffers.Contains(ss) ) buffers.Remove(ss); } }
/// <summary> /// Accepts an instance of a class on which data is collected for this performance counter category. /// </summary> internal void AddInstanceForCollection(BufferPlayer ss) { lock(buffers) { if( !buffers.Contains(ss) ) buffers.Add(ss); } }
/// <summary> /// Sends all of the frames this stream needs to send by the given time boundary. /// Also calls populate on the underlying buffer as necessary /// </summary> /// <returns>the time of the next frame to be sent, in ticks</returns> public long OnWakeUp(long timeBoundary) { if (this.currentStreamEnded) { return(long.MaxValue); } #if TIMING long startTimer, takenTime; #endif // We must return the time until the next frame. Keep up with it via this variable. long timeUntilFrame = 0; // Allowing the rtpSender to be created late allows the playback to properly replicate the situation seen // during recording. "Late joiner" support, if you will. This also deals with the underlying 500ms delay // implanted into RtpSession.CreateRtpSender. if (rtpSender != null) { #if TIMING startTimer = DateTime.Now.Ticks; #endif #region Send Frames if (!activeBuffer.Populating && !activeBuffer.Empty) { if (this.outOfDataLoggedFlag) { this.outOfDataLoggedFlag = false; } totalFramesSent += activeBuffer.SendFrames(rtpSender, timeBoundary, out timeUntilFrame, ref totalBytesSent, ref totalLateness); } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if (takenTime > Constants.TicksSpent) { Trace.WriteLine("TIMING: TIME WASTED SENDING FRAMES: " + (takenTime / Constants.TicksPerMs) + " ms"); } startTimer = DateTime.Now.Ticks; #endif #region Empty Buffer if (activeBuffer.Empty) { if (!activeBuffer.EndOfStream) // not the end of the stream, so we go get new data to play out { // guaranteed atomic BufferPlayer oldBuffer = activeBuffer; // Get the next buffer int newBufferIndex = (activeBufferIndex + 1) % Constants.BuffersPerStream; BufferPlayer newBuffer = buffers[newBufferIndex]; if (!newBuffer.Populating) // we can't enable population on the old buffer until we can get the 'LastTick' from the new buffer { if (!newBuffer.EndOfStream) // if we're at the end of the stream, don't enable population on the old buffer { // Broken here so we only work with 2 buffers oldBuffer.EnablePopulation(newBuffer.LastTick + 1); } } else // we're in a performance rut and have run out of data { // the "outOfDataLoggedFlag" prevents us from event-logging too much if (!outOfDataLoggedFlag) { // The database can't keep up; both buffers are empty! eventLog.WriteEntry("StreamPlayer::OnWakeUp reached and no data in buffers! ID: " + streamID, EventLogEntryType.Warning, ArchiveServiceEventLog.ID.EmptyBuffersInPlayback); this.emptyErrors++; this.outOfDataLoggedFlag = true; } } timeUntilFrame = 0; // we need to come back quick to see when the first frame is in the next buffer // save the "new" buffer as the "current" one activeBufferIndex = newBufferIndex; activeBuffer = buffers[newBufferIndex]; } else // end of stream { if (!currentStreamEnded) { Trace.WriteLine("End of stream reached. Stopping sending this stream. ID: " + streamID); currentStreamEnded = true; DisposeSender(); ThreadPool.QueueUserWorkItem(new WaitCallback(FireEndOfStream), this); } timeUntilFrame = long.MaxValue; } } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if (takenTime > Constants.TicksSpent) { Trace.WriteLine("TIMING: TIME WASTED ON EMPTY BUFFER: " + (takenTime / Constants.TicksPerMs) + " ms"); } #endif } else // Sender isn't created yet. Check if we need to. { #if TIMING startTimer = DateTime.Now.Ticks; #endif // Pri2: Change this to be compatable with use of the "playback speed" feature. TimeBoundary is speed-based... timeUntilFrame = (firstStreamTicks - timeBoundary); #region Sender creation if (timeUntilFrame <= Constants.SenderCreationLeadTime) // <x> ms of "prep time" to get fired up { if (!createSenderFired) { createSenderFired = true; Trace.WriteLine("RtpSender being created for stream: " + streamID); ThreadPool.QueueUserWorkItem(new WaitCallback(CreateSender)); } } else { timeUntilFrame -= Constants.SenderCreationLeadTime; } #endregion #if TIMING takenTime = DateTime.Now.Ticks - startTimer; if (takenTime > Constants.TicksSpent) { Trace.WriteLine("TIMING: TIME WASTED CREATING SENDER: " + (takenTime / Constants.TicksPerMs) + " ms"); } #endif } return(timeUntilFrame); }