tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

EbmlComposer.cpp (7110B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "EbmlComposer.h"
      7 
      8 #include "libmkv/EbmlIDs.h"
      9 #include "libmkv/EbmlWriter.h"
     10 #include "libmkv/WebMElement.h"
     11 #include "limits.h"
     12 #include "mozilla/EndianUtils.h"
     13 #include "mozilla/UniquePtr.h"
     14 #include "prtime.h"
     15 
     16 namespace mozilla {
     17 
     18 // Timecode scale in nanoseconds
     19 constexpr unsigned long TIME_CODE_SCALE = 1000000;
     20 // The WebM header size without audio CodecPrivateData
     21 constexpr int32_t DEFAULT_HEADER_SIZE = 1024;
     22 // Number of milliseconds after which we flush audio-only clusters
     23 constexpr int32_t FLUSH_AUDIO_ONLY_AFTER_MS = 1000;
     24 
     25 void EbmlComposer::GenerateHeader() {
     26  MOZ_RELEASE_ASSERT(!mMetadataFinished);
     27  MOZ_RELEASE_ASSERT(mHasAudio || mHasVideo);
     28 
     29  // Write the EBML header.
     30  EbmlGlobal ebml;
     31  // The WEbM header default size usually smaller than 1k.
     32  auto buffer =
     33      MakeUnique<uint8_t[]>(DEFAULT_HEADER_SIZE + mCodecPrivateData.Length());
     34  ebml.buf = buffer.get();
     35  ebml.offset = 0;
     36  writeHeader(&ebml);
     37  {
     38    EbmlLoc segEbmlLoc, ebmlLocseg, ebmlLoc;
     39    Ebml_StartSubElement(&ebml, &segEbmlLoc, Segment);
     40    {
     41      Ebml_StartSubElement(&ebml, &ebmlLocseg, SeekHead);
     42      // Todo: We don't know the exact sizes of encoded data and
     43      // ignore this section.
     44      Ebml_EndSubElement(&ebml, &ebmlLocseg);
     45      writeSegmentInformation(&ebml, &ebmlLoc, TIME_CODE_SCALE, 0);
     46      {
     47        EbmlLoc trackLoc;
     48        Ebml_StartSubElement(&ebml, &trackLoc, Tracks);
     49        {
     50          // Video
     51          if (mWidth > 0 && mHeight > 0) {
     52            writeVideoTrack(&ebml, 0x1, 0, "V_VP8", mWidth, mHeight,
     53                            mDisplayWidth, mDisplayHeight);
     54          }
     55          // Audio
     56          if (mCodecPrivateData.Length() > 0) {
     57            // Extract the pre-skip from mCodecPrivateData
     58            // then convert it to nanoseconds.
     59            // For more details see
     60            // https://tools.ietf.org/html/rfc7845#section-4.2
     61            uint64_t codecDelay = (uint64_t)LittleEndian::readUint16(
     62                                      mCodecPrivateData.Elements() + 10) *
     63                                  PR_NSEC_PER_SEC / 48000;
     64            // Fixed 80ms, convert into nanoseconds.
     65            uint64_t seekPreRoll = 80 * PR_NSEC_PER_MSEC;
     66            writeAudioTrack(&ebml, 0x2, 0x0, "A_OPUS", mSampleFreq, mChannels,
     67                            codecDelay, seekPreRoll,
     68                            mCodecPrivateData.Elements(),
     69                            mCodecPrivateData.Length());
     70          }
     71        }
     72        Ebml_EndSubElement(&ebml, &trackLoc);
     73      }
     74    }
     75    // The Recording length is unknown and
     76    // ignore write the whole Segment element size
     77  }
     78  MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + mCodecPrivateData.Length(),
     79             "write more data > EBML_BUFFER_SIZE");
     80  auto block = mBuffer.AppendElement();
     81  block->SetLength(ebml.offset);
     82  memcpy(block->Elements(), ebml.buf, ebml.offset);
     83  mMetadataFinished = true;
     84 }
     85 
     86 nsresult EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) {
     87  MOZ_RELEASE_ASSERT(mMetadataFinished);
     88  auto frameType = aFrame->mFrameType;
     89  const bool isVP8IFrame = (frameType == EncodedFrame::FrameType::VP8_I_FRAME);
     90  const bool isVP8PFrame = (frameType == EncodedFrame::FrameType::VP8_P_FRAME);
     91  const bool isOpus = (frameType == EncodedFrame::FrameType::OPUS_AUDIO_FRAME);
     92 
     93  MOZ_ASSERT_IF(isVP8IFrame, mHasVideo);
     94  MOZ_ASSERT_IF(isVP8PFrame, mHasVideo);
     95  MOZ_ASSERT_IF(isOpus, mHasAudio);
     96 
     97  if (isVP8PFrame && !mHasWrittenCluster) {
     98    // We ensure there is a cluster header and an I-frame prior to any P-frame.
     99    return NS_ERROR_INVALID_ARG;
    100  }
    101 
    102  int64_t timeCode = aFrame->mTime.ToMicroseconds() / PR_USEC_PER_MSEC -
    103                     mCurrentClusterTimecode;
    104 
    105  const bool needClusterHeader =
    106      !mHasWrittenCluster ||
    107      (!mHasVideo && timeCode >= FLUSH_AUDIO_ONLY_AFTER_MS) || isVP8IFrame;
    108 
    109  auto block = mBuffer.AppendElement();
    110  block->SetLength(aFrame->mFrameData->Length() + DEFAULT_HEADER_SIZE);
    111 
    112  EbmlGlobal ebml;
    113  ebml.offset = 0;
    114  ebml.buf = block->Elements();
    115 
    116  if (needClusterHeader) {
    117    mHasWrittenCluster = true;
    118    EbmlLoc ebmlLoc;
    119    // This starts the Cluster element. Note that we never end this element
    120    // through Ebml_EndSubElement. What the ending would allow us to do is write
    121    // the full length of the cluster in the element header. That would also
    122    // force us to keep the entire cluster in memory until we know where it
    123    // ends. Now it instead ends through the start of the next cluster. This
    124    // allows us to stream the muxed data with much lower latency than if we
    125    // would have to wait for clusters to end.
    126    Ebml_StartSubElement(&ebml, &ebmlLoc, Cluster);
    127    // if timeCode didn't under/overflow before, it shouldn't after this
    128    mCurrentClusterTimecode = aFrame->mTime.ToMicroseconds() / PR_USEC_PER_MSEC;
    129    Ebml_SerializeUnsigned(&ebml, Timecode, mCurrentClusterTimecode);
    130 
    131    // Can't under-/overflow now
    132    timeCode = 0;
    133  }
    134 
    135  if (MOZ_UNLIKELY(timeCode < SHRT_MIN || timeCode > SHRT_MAX)) {
    136    MOZ_CRASH_UNSAFE_PRINTF(
    137        "Invalid cluster timecode! audio=%d, video=%d, timeCode=%" PRId64
    138        "ms, currentClusterTimecode=%" PRIu64 "ms",
    139        mHasAudio, mHasVideo, timeCode, mCurrentClusterTimecode);
    140  }
    141 
    142  writeSimpleBlock(&ebml, isOpus ? 0x2 : 0x1, static_cast<short>(timeCode),
    143                   isVP8IFrame, 0, 0,
    144                   (unsigned char*)aFrame->mFrameData->Elements(),
    145                   aFrame->mFrameData->Length());
    146  MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + aFrame->mFrameData->Length(),
    147             "write more data > EBML_BUFFER_SIZE");
    148  block->SetLength(ebml.offset);
    149 
    150  return NS_OK;
    151 }
    152 
    153 void EbmlComposer::SetVideoConfig(uint32_t aWidth, uint32_t aHeight,
    154                                  uint32_t aDisplayWidth,
    155                                  uint32_t aDisplayHeight) {
    156  MOZ_RELEASE_ASSERT(!mMetadataFinished);
    157  MOZ_ASSERT(aWidth > 0, "Width should > 0");
    158  MOZ_ASSERT(aHeight > 0, "Height should > 0");
    159  MOZ_ASSERT(aDisplayWidth > 0, "DisplayWidth should > 0");
    160  MOZ_ASSERT(aDisplayHeight > 0, "DisplayHeight should > 0");
    161  mWidth = aWidth;
    162  mHeight = aHeight;
    163  mDisplayWidth = aDisplayWidth;
    164  mDisplayHeight = aDisplayHeight;
    165  mHasVideo = true;
    166 }
    167 
    168 void EbmlComposer::SetAudioConfig(uint32_t aSampleFreq, uint32_t aChannels) {
    169  MOZ_RELEASE_ASSERT(!mMetadataFinished);
    170  MOZ_ASSERT(aSampleFreq > 0, "SampleFreq should > 0");
    171  MOZ_ASSERT(aChannels > 0, "Channels should > 0");
    172  mSampleFreq = aSampleFreq;
    173  mChannels = aChannels;
    174  mHasAudio = true;
    175 }
    176 
    177 void EbmlComposer::ExtractBuffer(nsTArray<nsTArray<uint8_t> >* aDestBufs,
    178                                 uint32_t aFlag) {
    179  if (!mMetadataFinished) {
    180    return;
    181  }
    182  aDestBufs->AppendElements(std::move(mBuffer));
    183  MOZ_ASSERT(mBuffer.IsEmpty());
    184 }
    185 
    186 }  // namespace mozilla