Example #1
0
        // Calls fileStream.Position and fileStream.Write only.
        //
        // Usually trackSamples has 2 elements: a video track and an audio track.
        //
        // Uses trackEntries and trackSamples only as a read-only argument, doesn't modify their contents.
        //
        // Starts with the initial cue points specified in cuePoints, and appends subsequent cue points in place.
        private static void WriteClustersAndCues(FileStream fileStream,
            ulong segmentOffset,
            int videoTrackIndex,
            IList<bool> isAmsCodecs,
            IMediaDataSource mediaDataSource,
            MuxStateWriter muxStateWriter,
            IList<CuePoint> cuePoints,
            ref ulong minStartTime,
            ulong timePosition,
            out ulong seekHeadOffsetMS,
            out ulong cuesOffsetMS)
        {
            int trackCount = mediaDataSource.GetTrackCount();
              if (isAmsCodecs.Count != trackCount) {
            throw new Exception("ASSERT: isAmsCodecs vs mediaDataSource length mismatch.");
              }
              if (trackCount > 13) {  // 13 is because a..m and n..z in MuxStateWriter checkpointing.
            throw new Exception("Too many tracks to mux.");
              }
              // For each track, contains the data bytes of a media sample ungot (i.e. pushed back) after reading.
              // Initializes items to null (good).
              MediaDataBlock[] ungetBlocks = new MediaDataBlock[trackCount];
              ulong minStartTime0 = minStartTime;
              if (timePosition == ulong.MaxValue) {
            timePosition = 0;
            ulong maxStartTime = ulong.MaxValue;
            for (int i = 0; i < trackCount; ++i) {
              if ((ungetBlocks[i] = mediaDataSource.PeekBlock(i)) != null) {
            if (maxStartTime == ulong.MaxValue || maxStartTime < ungetBlocks[i].StartTime) {
              maxStartTime = ungetBlocks[i].StartTime;
            }
            mediaDataSource.ConsumeBlock(i);  // Since it was moved to ungetBlocks[i].
              }
            }
            for (int i = 0; i < trackCount; ++i) {
              MediaDataBlock block = mediaDataSource.PeekBlock(i);
              while (block != null && block.StartTime <= maxStartTime) {
            ungetBlocks[i] = block;  // Takes ownership.
            mediaDataSource.ConsumeBlock(i);
              }
              // We'll start each track (in ungetMediaSample[i]) from the furthest sample within maxStartTime.
            }
            int trackIndex2;
            if ((trackIndex2 = GetNextTrackIndex(mediaDataSource, ungetBlocks)) < 0) {
              throw new Exception("ASSERT: Empty media file, no samples.");
            }
            minStartTime = minStartTime0 = ungetBlocks[trackIndex2] != null ? ungetBlocks[trackIndex2].StartTime :
                  mediaDataSource.PeekBlock(trackIndex2).StartTime;
              muxStateWriter.WriteUlong('A', minStartTime0);
              }
              List<ArraySegment<byte>> output = new List<ArraySegment<byte>>();
              ulong[] lastOutputStartTimes = new ulong[trackCount];  // Items initialized to zero.
              int trackIndex;
              // timePosition is the beginning StartTime of the last output block written by fileStream.Write.
              while ((trackIndex = GetNextTrackIndex(mediaDataSource, ungetBlocks)) >= 0) {
            ulong timeCode;  // Will be set below.
            bool isKeyFrame;  // Will be set below.
            MediaDataBlock block0;  // Will be set below.
            MediaDataBlock block1 = null;  // May be set below.
            int mediaDataBlockTotalSize;  // Will be set below.
            {
              if ((block0 = ungetBlocks[trackIndex]) == null &&
              (block0 = mediaDataSource.PeekBlock(trackIndex)) == null) {
            throw new Exception("ASSERT: Reading from a track already at EOF.");
              }
              // Some kind of time delta for this sample.
              timeCode = block0.StartTime - timePosition - minStartTime0;
              if (block0.StartTime < timePosition + minStartTime0) {
            throw new Exception("Bad start times: block0.StartTime=" + block0.StartTime +
                                " timePosition=" + timePosition + " minStartTime=" + minStartTime0);
              }
              isKeyFrame = block0.IsKeyFrame;
              mediaDataBlockTotalSize = block0.Bytes.Count;
              if (ungetBlocks[trackIndex] != null) {
            ungetBlocks[trackIndex] = null;
              } else {
            mediaDataSource.ConsumeBlock(trackIndex);
              }
            }
            if (timeCode > 327670000uL) {
              throw new Exception("timeCode too large: " + timeCode);  // Maybe that's not fatal?
            }
            if (isAmsCodecs[trackIndex]) {  // Copy one more MediaSample if available.
              // TODO: Test this.
              block1 = ungetBlocks[trackIndex];
              if (block1 != null) {
            mediaDataBlockTotalSize += block1.Bytes.Count;
            ungetBlocks[trackIndex] = null;
              } else if ((block1 = mediaDataSource.PeekBlock(trackIndex)) != null) {
            mediaDataBlockTotalSize += block1.Bytes.Count;
            mediaDataSource.ConsumeBlock(trackIndex);
              }
            }
            // TODO: How can be timeCode so large at this point?
            if ((output.Count != 0 && trackIndex == videoTrackIndex && isKeyFrame) || timeCode > 327670000uL) {
              ulong outputOffset = (ulong)fileStream.Position - segmentOffset;
              cuePoints.Add(new CuePoint(timePosition / 10000uL, (ulong)(videoTrackIndex + 1), outputOffset));
              muxStateWriter.WriteUlong('C', timePosition);
              muxStateWriter.WriteUlong('D', outputOffset);
              int totalSize = 0;
              for (int i = 0; i < output.Count; ++i) {
            totalSize += output[i].Count;
              }
              // We do a single copy of the media stream data bytes here. That copy is inevitable, because it's
              // faster to save to file that way.
              byte[] bytes = Utils.CombineByteArraysAndArraySegments(
              new byte[][]{GetDataSizeBytes((ulong)ID.Cluster), GetDataSizeBytes((ulong)totalSize)}, output);
              output.Clear();
              // The average bytes.Length is 286834 bytes here, that's large enough (>8 kB), and it doesn't warrant a
              // a buffered output stream for speedup.
              fileStream.Write(bytes, 0, bytes.Length);
              fileStream.Flush();
              for (int i = 0; i < trackCount; ++i) {
            muxStateWriter.WriteUlong((char)('a' + i), lastOutputStartTimes[i]);
              }
              muxStateWriter.WriteUlong('P', (ulong)bytes.Length);
              muxStateWriter.Flush();
            }
            if (output.Count == 0) {
              timePosition += timeCode;
              timeCode = 0uL;
              output.Add(new ArraySegment<byte>(
              GetEEBytes(ID.Timecode, GetVintBytes(timePosition / 10000uL))));
            }
            output.Add(new ArraySegment<byte>(GetSimpleBlockBytes(
            (ulong)(trackIndex + 1), (short)(timeCode / 10000uL), isKeyFrame, isAmsCodecs[trackIndex],
            mediaDataBlockTotalSize)));
            output.Add(block0.Bytes);
            if (block1 != null) output.Add(block1.Bytes);
            lastOutputStartTimes[trackIndex] = block1 != null ? block1.StartTime : block0.StartTime;
              }

              // Write remaining samples (from output to fileStream), and write cuePoints.
              {
            ulong outputOffset = (ulong)fileStream.Position - segmentOffset;
            cuePoints.Add(new CuePoint(timePosition / 10000uL, (ulong)(videoTrackIndex + 1), outputOffset));
            muxStateWriter.WriteUlong('C', timePosition);
            muxStateWriter.WriteUlong('D', outputOffset);
            if (output.Count == 0) {
              throw new Exception("ASSERT: Expecting non-empty output at end of mixing.");
            }
            int totalSize = 0;
            for (int i = 0; i < output.Count; ++i) {
              totalSize += output[i].Count;
            }
            byte[] bytes = Utils.CombineByteArraysAndArraySegments(
            new byte[][]{GetDataSizeBytes((ulong)ID.Cluster), GetDataSizeBytes((ulong)totalSize)}, output);
            output.Clear();  // Save memory.
            cuesOffsetMS = outputOffset + (ulong)bytes.Length;
            byte[] bytes2 = GetCueBytes(cuePoints);  // cues are about 1024 bytes per 2 minutes.
            seekHeadOffsetMS = cuesOffsetMS + (ulong)bytes2.Length;
            SeekBlock[] seekBlocks = new SeekBlock[cuePoints.Count];
            for (int i = 0; i < cuePoints.Count; ++i) {
              seekBlocks[i] = new SeekBlock(ID.Cluster, cuePoints[i].CueClusterPosition);
            }
            byte[] bytes3 = GetSeekBytes(seekBlocks, -1);
            bytes = Utils.CombineBytes(bytes, bytes2, bytes3);
            fileStream.Write(bytes, 0, bytes.Length);
              }
        }
Example #2
0
 // Returns trackIndex with the smallest StartTime, or -1.
 private static int GetNextTrackIndex(IMediaDataSource mediaDataSource, MediaDataBlock[] ungetBlocks)
 {
     int trackCount = ungetBlocks.Length;  // == mediaDataSource.GetTrackCount().
       ulong minUnconsumedStartTime = 0;  // No real need to initialize it here.
       int trackIndex = -1;
       for (int i = 0; i < trackCount; ++i) {
     MediaDataBlock block = ungetBlocks[i];
     if (block == null) block = mediaDataSource.PeekBlock(i);
     if (block != null && (trackIndex == -1 || minUnconsumedStartTime > block.StartTime)) {
       trackIndex = i;
       minUnconsumedStartTime = block.StartTime;
     }
       }
       return trackIndex;
 }