protected string _privateData; // Either "CodecPrivateData" or "WaveFormatEx" #endregion Fields #region Constructors /// <summary> /// Prepate a SMIL generator /// </summary> /// <param name="FileRoot"> /// Start of the media file name (used by IIS to create archive files). /// This will be appended with bitrate and dot-extension. /// </param> public SmilGenerator(string FileRoot, MediaStream SourceStream) { switch (SourceStream.FourCC) { case "mp4a": _fourCC = "mp4a"; Height = 0; Width = 0; break; case "mp3a": _fourCC = "mp3a"; Height = 0; Width = 0; break; case "avc1": case "H264": _fourCC = "H264"; // use the Microsoft form, as this is for sending to IIS & Silverlight. Height = SourceStream.Height; Width = SourceStream.Width; break; default: throw new ArgumentException("Stream type " + SourceStream.FourCC + " is not supported", "SourceStream"); } _channel = SourceStream.TrackId; _fileRoot = FileRoot; }
/// <summary> /// Remove any frames with zero length. /// Zero length frames kill the live stream. /// </summary> private void SanitiseStream(MediaStream stream) { lock (SyncRoot) { var src = new List<GenericMediaFrame>(stream.Frames); stream.Frames.Clear(); foreach (var frame in src) { if (frame.FrameDuration < 1) continue; stream.Frames.Add(frame); } } }
/// <summary> /// Pushes a set of frames to IIS. Will trigger a connect if needed. /// </summary> private void PushStream(MediaStream stream, FileRoot TargetMp4fFile) { if (stream == null || stream.Frames == null) return; // no frames. SanitiseStream(stream); if (stream.Frames.Count < 1) return; // no frames. if (!PushServer.IsConnected(stream.TrackId)) ConnectAndPushHeaders(stream, TargetMp4fFile); // set start-of-fragment time from PTS stream.Offset = stream.Frames[0].FramePresentationTime - stream.Frames[0].FrameDuration; // Push the fragment var fragment_handler = TargetMp4fFile.GenerateFragment(stream); PushServer.PushData(stream.TrackId, fragment_handler.MoofData()); PushServer.PushData(stream.TrackId, fragment_handler.MdatData()); }
/// <summary> /// Used once per connection, this opens a long-life HTTP stream /// and pushes the very basic MP4 parts needed to get IIS working. /// </summary> private void ConnectAndPushHeaders(MediaStream stream, FileRoot TargetMp4fFile) { SmilGenerator smil = new SmilGenerator("HCS Encoder by Iain Ballard.", stream); smil.ApproxBitrate = stream.Bitrate; MP4_Mangler.ExtraBoxes.SmoothSmil ssmil = new MP4_Mangler.ExtraBoxes.SmoothSmil(smil.Generate()); PushServer.Connect(stream.TrackId); // This pushes to the subpath: Streams({id}-stream{index}) // push headers (only done once per track) // each one needs it's own HTTP Chunk, so don't concat! PushServer.PushData(stream.TrackId, TargetMp4fFile.GenerateFileSpec()); PushServer.PushData(stream.TrackId, ssmil.deepData()); PushServer.PushData(stream.TrackId, TargetMp4fFile.GenerateHeaders()); }
/// <summary> /// Generate a pair of 'moof' and 'mdat' atoms for the given stream. /// The stream should contains a populated list of frame data, otherwise output will be empty. /// Keeps track of fragment index. Does not write to file. Does not remove frames. Does not adjust offset time. /// </summary> public FragmentBoxes.MediaFragmentHandler GenerateFragment(MediaStream Stream) { if (Stream.Frames == null || Stream.Frames.Count < 1) throw new Exception("Stream was empty"); FragmentBoxes.MediaFragmentHandler output = new MP4_Mangler.FragmentBoxes.MediaFragmentHandler((uint)Stream.FragmentNumber, Stream.Offset); Stream.FragmentNumber++; foreach (var frame in Stream.Frames) { output.AddFrame((uint)Stream.TrackId, frame); } Stream.Frames.Clear(); return output; }
/// <summary> /// Store the frames of a stream in the KnownStreams cache, adding a new entry if needed. /// If there is a file open, then new streams will be refused with an argument exception /// </summary> private MediaStream CacheStreamFrames(MediaStream SourceStream) { var dst_stream = KnownStreams.Where(a => a.TrackId == SourceStream.TrackId).FirstOrDefault(); if (dst_stream == null) { // not a known stream, so add it. if (ActiveFile != null) throw new ArgumentException("Can't add new streams to an open file. Try adding your streams first, then open the file once all streams are known", "SourceStream"); dst_stream = new MediaStream(); dst_stream.FourCC = SourceStream.FourCC; dst_stream.Frames = new List<GenericMediaFrame>(SourceStream.Frames); dst_stream.Height = SourceStream.Height; dst_stream.TrackId = SourceStream.TrackId; dst_stream.Width = SourceStream.Width; KnownStreams.Add(dst_stream); } else { dst_stream.Frames.AddRange(SourceStream.Frames); } return dst_stream; }
/// <summary> /// Write the frames of a stream to a file if one is open or to memory if not. /// </summary> public void WriteStream(MediaStream SourceStream) { var dst_stream = CacheStreamFrames(SourceStream); if (ActiveFile == null) return; // No file to write. // Write to file system (writes, flushes and re-closes) using (BinaryWriter wr = new BinaryWriter(ActiveFile.Open(FileMode.Append, FileAccess.Write, FileShare.Read))) { wr.Write(GenerateFragment(dst_stream).FormatData()); // Write any waiting frames, plus the newly added ones wr.Flush(); wr.Close(); // closes the file as well. } dst_stream.Frames.Clear(); // OK, we've handled these -- so dump the caches }