/// <summary> /// Write audio, video and presentation data for this segment. This can be a long-running process. /// It can be cancelled with the Stop method. /// </summary> /// <param name="noRecompression"></param> /// <returns>A message string indicates a serious problem. Null for normal termination.</returns> public String Encode() { if (cancel) { return(null); } if ((startTime == DateTime.MinValue) || (endTime == DateTime.MinValue)) { return("Invalid timespan."); } if (useSlideStream && norecompression) { return("A slide stream cannot be processed in 'no recompression' mode."); } progressTracker.EndValue = (int)((TimeSpan)(endTime - startTime)).TotalSeconds; if (useSlideStream) { slideStream = new SlideStreamMgr(job, segment, logMgr, 29.97, writer.FrameWidth, writer.FrameHeight); //Using slides in place of the video } else { videoStream = new StreamMgr(segment.VideoDescriptor.VideoCname, segment.VideoDescriptor.VideoName, startTime, endTime, norecompression, PayloadType.dynamicVideo); } audiorecompression = !norecompression; //In this case we actually do need to recompress just the audio: if ((norecompression) && (segment.AudioDescriptor.Length != 1)) { audiorecompression = true; } audioStreams = new StreamMgr[segment.AudioDescriptor.Length]; for (int i = 0; i < segment.AudioDescriptor.Length; i++) { audioStreams[i] = new StreamMgr(segment.AudioDescriptor[i].AudioCname, segment.AudioDescriptor[i].AudioName, startTime, endTime, !audiorecompression, PayloadType.dynamicAudio); } if (cancel) { return(null); } actualSegmentStart = 0; actualSegmentEnd = 0; if (norecompression) { if (useSlideStream) { //Not supported } else { // Warn and log an error if a problem is detected with the media type, but try to proceed anyway. videoStream.ValidateCompressedMT(profileData.VideoMediaType, logMgr); // Make the last MT available to the caller to pass to the next segment to facilitate the checking. //this.compressedVideoMediaType = videoStream.GetFinalCompressedVideoMediaType(); } if (audioStreams.Length == 1) { //There is truly no recompression in this case. ///as above, do the same check with the Audio MediaType. Log a warning if the MT changed, but attempt to proceed. audioStreams[0].ValidateCompressedMT(profileData.AudioMediaType, logMgr); //this.compressedAudioMediaType = audioStreams[0].GetFinalCompressedAudioMediaType(); progressTracker.AVStatusMessage = "Writing Raw AV"; } else { //In this case we have to recompress audio in order to do the mixing, but that should be relatively quick. //Note that the WMSDK docs say that SetInputProps must be set before BeginWriting. This implies that //alternating between writing compressed and uncompressed samples is not supported. Therefore we will //first recompress the mixed audio, then deliver compressed samples to the writer. for (int i = 0; i < audioStreams.Length; i++) { progressTracker.AVStatusMessage = "Reading Audio (" + (i + 1).ToString() + " of " + audioStreams.Length.ToString() + ")"; if (cancel) { return(null); } if (!audioStreams[i].ToRawWMFile(progressTracker)) { return("Failed to configure a raw audio profile."); } } progressTracker.AVStatusMessage = "Mixing Audio"; mixer = new AudioMixer(audioStreams, this.logMgr); /// PRI3: We could tell the mixer to recompress with the previous segment's MT (if any). /// For now we just use the /// mixer's voting mechanism to choose the 'dominant' input (uncompressed) format, /// and make the profile from one of the streams that uses that format. mixer.Recompress(progressTracker); progressTracker.AVStatusMessage = "Writing Raw AV"; } } else // Recompress both audio and video { //In order to recompress, we first need to convert each stream to a raw wmv/wma //A slide stream starts life uncompressed, so just initialize decks. if (!useSlideStream) { progressTracker.AVStatusMessage = "Reading Video"; if (!videoStream.ToRawWMFile(progressTracker)) { return("Failed to configure the raw video profile."); } } else { if (!slideStream.Init(progressTracker)) { return("Failed to prepare slide decks."); } } for (int i = 0; i < audioStreams.Length; i++) { progressTracker.AVStatusMessage = "Reading Audio (" + (i + 1).ToString() + " of " + audioStreams.Length.ToString() + ")"; if (cancel) { return(null); } if (!audioStreams[i].ToRawWMFile(progressTracker)) { return("Failed to configure a raw audio profile."); } } mixer = new AudioMixer(audioStreams, this.logMgr); writer.GetInputProps(); //The SDK allows us to reconfigure the MediaTypes on the fly if we are writing uncompressed samples. //We do this at the beginning of every segment, even though most of the time it is probably the same MT. writer.ConfigAudio(mixer.UncompressedAudioMediaType); if (useSlideStream) { writer.ConfigVideo(slideStream.UncompressedMediaType); //writer.DebugPrintVideoProps(); } else { writer.ConfigVideo(videoStream.GetUncompressedVideoMediaType()); } progressTracker.CurrentValue = 0; progressTracker.AVStatusMessage = "Transcoding AV"; } //Now all the config and prep is done, so write the segment. writeSegment(); //If there is a Presentation stream, process it here unless the slides were used for the video stream. if ((!useSlideStream) && (this.segment.PresentationDescriptor != null) && (this.segment.PresentationDescriptor.PresentationCname != null)) { progressTracker.CurrentValue = 0; progressTracker.ShowPercentComplete = false; progressTracker.AVStatusMessage = "Writing Presentation"; /// The offset for PresentationMgr is a timespan in ticks to be subtracted from each absolute timestamp /// to make a new absolute timestamp which has been adjusted for accumulted time skipped (or overlap) between /// segments, or time during which there is missing AV data at the beginning of the first segment. /// It is calculated as: actualSegmentStart - jobStart - offset /// where: /// actualSegmentStart: Time of the first AV write for the current segment /// JobStart: user requested start time for the first segment in this job (Presentation data reference time) /// offset: calculated actual duration of all segments previous to this one. /// /// Note that the presentation output will use the user-specified jobStart as the reference time. /// During playback, relative timings of presentation events will be calculated by subtracting the reference time. /// Also note that the reference time may not be the same as the actualSegmentStart for the first segment of the /// job in the case where there is missing data at the beginning. This often (always) happens if we /// begin processing an archive from the beginning, since it takes several seconds to get the first AV bits /// into the database after the ArchiveService joins a venue. //long thisSegmentOffset = this.actualSegmentStart - startTime.Ticks; //long tmpoffset = this.actualSegmentStart-jobStart-offset; //Debug.WriteLine ("this segment offset. actualSegmentStart = " + this.actualSegmentStart.ToString() + // " jobStart = " + jobStart.ToString() + " offset = " + offset.ToString() + // " offset to presenterMgr = " + tmpoffset.ToString()); long previousSegmentEndTime = 0; if (m_PreviousSegment != null) { previousSegmentEndTime = m_PreviousSegment.actualSegmentEnd; } presentationMgr = new PresentationMgr(this.segment.PresentationDescriptor, this.actualSegmentStart, this.actualSegmentEnd, this.actualSegmentStart - jobStart - offset, this.logMgr, previousSegmentEndTime, progressTracker); if (cancel) { return(null); } String errmsg = presentationMgr.Process(); if (errmsg != null) { this.logMgr.WriteLine(errmsg); this.logMgr.ErrorLevel = 5; } progressTracker.ShowPercentComplete = true; } if ((useSlideStream) && (slideStream != null)) { slideStream.Dispose(); } return(null); }
/// <summary> /// Recompress audio from mixer into a temp file using the native profile. This is used to implement mixing /// in the 'norecompression' scenario. /// </summary> /// <param name="progressTracker"></param> /// <returns></returns> public bool Recompress(ProgressTracker progressTracker) { cancel = false; if (audioMgr.Length == 0) { return(false); } //String useProfile; ProfileData profileData = null; if (this.compatibleStreamID >= 0) { profileData = ProfileUtility.StreamIdToProfileData(compatibleStreamID, MSR.LST.Net.Rtp.PayloadType.dynamicAudio); //Debug.WriteLine("Mixer.Recompress: using audio profile from streamID: " + compatibleStreamID.ToString()); } else { //Under what circumstances could we get here?? profileData = audioMgr[0].StreamProfileData; } WMWriter wmWriter = new WMWriter(); wmWriter.Init(); if (!wmWriter.ConfigProfile(profileData)) { return(false); } String tempFileName = Utility.GetTempFilePath("wma"); wmWriter.ConfigFile(tempFileName); wmWriter.GetInputProps(); wmWriter.ConfigAudio(audioMgr[0].GetUncompressedAudioMediaType()); wmWriter.Start(); //Write samples progressTracker.CurrentValue = 0; BufferChunk audioSample = null; long audioTime = long.MaxValue; long refTime = 0, endTime = 0; long lastWriteTime = 0; while (!cancel) { if (audioSample == null) { endTime = audioTime; if (!GetNextSample(out audioSample, out audioTime)) { break; } } if (audioSample != null) { //write audio if (refTime == 0) { refTime = audioTime; } //Debug.WriteLine("mixer.Recompress write audio: " + (audioTime-refTime).ToString() + ";length=" + audioSample.Length.ToString()); lastWriteTime = audioTime - refTime; wmWriter.WriteAudio((uint)audioSample.Length, audioSample, (ulong)(audioTime - refTime)); audioSample = null; } else { break; } progressTracker.CurrentValue = (int)(lastWriteTime / (Constants.TicksPerSec)); } wmWriter.Stop(); wmWriter.Cleanup(); wmWriter = null; //Prepare a filestreamPlayer to read back compressed samples. fileStreamPlayer = new FileStreamPlayer(tempFileName, refTime, endTime, true, -1); return(true); }