/// <summary> /// Assume that all the StreamMgrs provided are configured with the correct time range. Do not assume /// contiguous data, or compatible media types. /// </summary> /// <param name="audioMgr"></param> /// <param name="log"></param> public AudioMixer(StreamMgr[] audioMgr, LogMgr log) { fileStreamPlayer = null; this.audioMgr = audioMgr; this.log = log; refTime = long.MinValue; inbufs = new ArrayList(); if (audioMgr.Length == 0) { return; } /// Examine the uncompressed MT's for each audioMgr, and implement a voting system so that the /// media type that is dominant for this mixer is the one we use, and other incompatible MT's /// are ignored in the mix. Log a warning at places where the MT changes. /// Remember that each audioMgr may itself have multiple FileStreamPlayers which have different uncompressed /// media types. /// Finally we need to make our uncompressed MT available to the caller for use in configuring the writer. /// Later on let's look at ways to convert any common uncompressed types so that they are compatible. AudioCompatibilityMgr audioCompatibilityMgr = new AudioCompatibilityMgr(); foreach (StreamMgr astream in audioMgr) { astream.CheckUncompressedAudioTypes(audioCompatibilityMgr); } this.uncompressedMediaType = audioCompatibilityMgr.GetDominantType(); String warning = audioCompatibilityMgr.GetWarningString(); if (warning != "") { log.WriteLine(warning); log.ErrorLevel = 5; } incompatibleGuids = audioCompatibilityMgr.GetIncompatibleGuids(); // Here we also want to collect a "native" (compressed) profile corresponding to one of the "compatible" // streams. This is useful in case we need to recompress. Note this profile can be created if we have // a stream ID. compatibleStreamID = audioCompatibilityMgr.GetCompatibleStreamID(); this.bitsPerSample = this.uncompressedMediaType.WaveFormatEx.BitsPerSample; this.bytesPerSample = bitsPerSample / 8; this.ticksPerSample = ((uint)Constants.TicksPerSec) / (this.uncompressedMediaType.WaveFormatEx.SamplesPerSec * this.uncompressedMediaType.WaveFormatEx.Channels); limit = (long)((ulong)1 << (int)bitsPerSample) / 2 - 1; //clip level buffers = new SampleBuffer[audioMgr.Length]; for (int i = 0; i < buffers.Length; i++) { buffers[i] = new SampleBuffer(audioMgr[i], ticksPerSample, incompatibleGuids, this.uncompressedMediaType.WaveFormatEx.Channels); } }
/// <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); }
/// <summary> /// Write each stream from DBStreamPlayer to a WM file, then create FileStreamPlayers for each. /// It is necessary to do this before reading uncompressed samples, or using any of the /// methods that return uncompressed MediaTypes. /// This can be a long-running process. It can be cancelled with the Stop method. /// </summary> /// <returns>False if we failed to configure the native profile</returns> public bool ToRawWMFile(ProgressTracker progressTracker) { if (cancel) { return(true); } String tmpfile = ""; fileStreamPlayers = new FileStreamPlayer[streamPlayers.Length]; for (int i = 0; i < streams.Length; i++) { streamProfileData = ProfileUtility.StreamIdToProfileData(streams[i], payload); if (payload == PayloadType.dynamicVideo) { tmpfile = Utility.GetTempFilePath("wmv"); //nativeProfile = ProfileUtility.MakeNativeVideoProfile(streams[i]); } else { tmpfile = Utility.GetTempFilePath("wma"); //nativeProfile = ProfileUtility.MakeNativeAudioProfile(streams[i]); } WMWriter wmWriter = new WMWriter(); wmWriter.Init(); //if (!wmWriter.ConfigProfile(nativeProfile,"",0)) if (!wmWriter.ConfigProfile(StreamProfileData)) { return(false); } wmWriter.ConfigFile(tmpfile); wmWriter.ConfigNullProps(); //wmWriter.SetCodecInfo(payload); wmWriter.Start(); long streamTime = long.MaxValue; long refTime = 0; long endTime = 0; long lastWriteTime = 0; BufferChunk frame; BufferChunk sample; bool keyframe; bool discontinuity; discontinuity = true; //Catch exceptions to work around the rare case of data corruption. //Oddly in one case where this occurred it did not occur if the segments were short enough while (streamPlayers[i].GetNextFrame(out frame, out streamTime)) { try { sample = ProfileUtility.FrameToSample(frame, out keyframe); } catch { DateTime dt = new DateTime(streamTime); Console.WriteLine("Possible data corruption in stream: " + this.payload + ";" + this.cname + " at " + dt.ToString() + " (" + streamTime.ToString() + ")"); continue; } if (refTime == 0) { refTime = streamTime; } lastWriteTime = streamTime - refTime; try { if (payload == PayloadType.dynamicVideo) { //Debug.WriteLine("Write video: " + (streamTime-refTime).ToString() + ";length=" + sample.Length.ToString()); wmWriter.WriteCompressedVideo((ulong)(streamTime - refTime), (uint)sample.Length, (byte[])sample, keyframe, discontinuity); } else { //Debug.WriteLine("Write audio: " + (streamTime-refTime).ToString() + ";length=" + sample.Length.ToString()); wmWriter.WriteCompressedAudio((ulong)(streamTime - refTime), (uint)sample.Length, (byte[])sample); } } catch { DateTime dt = new DateTime(streamTime); Console.WriteLine("Failed to write. Possible data corruption in stream: " + this.payload + ";" + this.cname + " at " + dt.ToString() + " (" + streamTime.ToString() + ")"); } if (discontinuity) { discontinuity = false; } endTime = streamTime; if (cancel) { break; } progressTracker.CurrentValue = (int)(lastWriteTime / Constants.TicksPerSec); //Debug.WriteLine("StreamMgr.ToRawWMFile: ProgressTracker currentValue=" + progressTracker.CurrentValue.ToString() + // ";streamTime=" + streamTime.ToString()); } wmWriter.Stop(); wmWriter.Cleanup(); wmWriter = null; fileStreamPlayers[i] = new FileStreamPlayer(tmpfile, refTime, endTime, false, streams[i]); if (cancel) { break; } } return(true); }