Bug 1064128 - Implement support for timestampOffset in segments mode. r=k17e,r=cajbir
authorBobby Holley <bobbyholley@gmail.com>
Wed, 07 Jan 2015 15:58:55 -0800
changeset 248369 20ab622b36c1ce9d9904d8daaf22562a07e820c4
parent 248368 a909e2281ed1d54d44236a405e61b8aca735a636
child 248370 6c8f62d7fd1cb82189e58ba5591b9e1769a187de
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersk17e, cajbir
bugs1064128
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1064128 - Implement support for timestampOffset in segments mode. r=k17e,r=cajbir
dom/media/AbstractMediaDecoder.h
dom/media/fmp4/MP4Reader.cpp
dom/media/gtest/TestMP4Demuxer.cpp
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/MediaSourceDecoder.cpp
dom/media/mediasource/MediaSourceDecoder.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/mediasource/SourceBuffer.cpp
dom/media/mediasource/SourceBufferDecoder.cpp
dom/media/mediasource/SourceBufferDecoder.h
dom/media/mediasource/TrackBuffer.cpp
dom/media/mediasource/TrackBuffer.h
media/libstagefright/binding/DecoderData.cpp
media/libstagefright/binding/Index.cpp
media/libstagefright/binding/MoofParser.cpp
media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
media/libstagefright/binding/include/mp4_demuxer/Index.h
media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
media/libstagefright/binding/include/mp4_demuxer/mp4_demuxer.h
media/libstagefright/binding/mp4_demuxer.cpp
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -60,16 +60,19 @@ public:
   // Called by the decode thread to keep track of the number of bytes read
   // from the resource.
   virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) = 0;
 
   // Increments the parsed and decoded frame counters by the passed in counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) = 0;
 
+  // For decoders with a notion of timestamp offset, returns the value in microseconds.
+  virtual int64_t GetTimestampOffset() const { return 0; }
+
   // Return the duration of the media in microseconds.
   virtual int64_t GetMediaDuration() = 0;
 
   // Set the duration of the media in microseconds.
   virtual void SetMediaDuration(int64_t aDuration) = 0;
 
   // Sets the duration of the media in microseconds. The MediaDecoder
   // fires a durationchange event to its owner (e.g., an HTML audio
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -152,17 +152,17 @@ MP4Reader::InitLayersBackendType()
 
 static bool sIsEMEEnabled = false;
 
 nsresult
 MP4Reader::Init(MediaDecoderReader* aCloneDonor)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
   PlatformDecoderModule::Init();
-  mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource(), &mDemuxerMonitor), &mDemuxerMonitor);
+  mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource(), &mDemuxerMonitor), GetDecoder()->GetTimestampOffset(), &mDemuxerMonitor);
 
   InitLayersBackendType();
 
   mAudio.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
   NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE);
 
   mVideo.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
   NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE);
--- a/dom/media/gtest/TestMP4Demuxer.cpp
+++ b/dom/media/gtest/TestMP4Demuxer.cpp
@@ -19,17 +19,17 @@ public:
 
   nsRefPtr<MockMediaResource> resource;
   Monitor mMonitor;
   nsAutoPtr<MP4Demuxer> demuxer;
 
   explicit MP4DemuxerBinding(const char* aFileName = "dash_dashinit.mp4")
     : resource(new MockMediaResource(aFileName))
     , mMonitor("TestMP4Demuxer monitor")
-    , demuxer(new MP4Demuxer(new MP4Stream(resource, &mMonitor), &mMonitor))
+    , demuxer(new MP4Demuxer(new MP4Stream(resource, &mMonitor), 0, &mMonitor))
   {
     EXPECT_EQ(NS_OK, resource->Open(nullptr));
   }
 
 private:
   virtual ~MP4DemuxerBinding()
   {
   }
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -244,17 +244,21 @@ public:
   bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
                                   int64_t& aStart, int64_t& aEnd)
   {
     MonitorAutoLock mon(mMonitor); // We're not actually racing against anything,
                                    // but mParser requires us to hold a monitor.
     bool initSegment = IsInitSegmentPresent(aData, aLength);
     if (initSegment) {
       mStream = new mp4_demuxer::BufferStream();
-      mParser = new mp4_demuxer::MoofParser(mStream, 0, &mMonitor);
+      // We use a timestampOffset of 0 for ContainerParser, and require
+      // consumers of ParseStartAndEndTimestamps to add their timestamp offset
+      // manually. This allows the ContainerParser to be shared across different
+      // timestampOffsets.
+      mParser = new mp4_demuxer::MoofParser(mStream, 0, 0, &mMonitor);
     } else if (!mStream || !mParser) {
       return false;
     }
 
     mStream->AppendBytes(aData, aLength);
     nsTArray<MediaByteRange> byteRanges;
     byteRanges.AppendElement(mStream->GetByteRange());
     mParser->RebuildFragmentedIndex(byteRanges);
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -124,20 +124,20 @@ MediaSourceDecoder::AttachMediaSource(do
 void
 MediaSourceDecoder::DetachMediaSource()
 {
   MOZ_ASSERT(mMediaSource && NS_IsMainThread());
   mMediaSource = nullptr;
 }
 
 already_AddRefed<SourceBufferDecoder>
-MediaSourceDecoder::CreateSubDecoder(const nsACString& aType)
+MediaSourceDecoder::CreateSubDecoder(const nsACString& aType, int64_t aTimestampOffset)
 {
   MOZ_ASSERT(mReader);
-  return mReader->CreateSubDecoder(aType);
+  return mReader->CreateSubDecoder(aType, aTimestampOffset);
 }
 
 void
 MediaSourceDecoder::AddTrackBuffer(TrackBuffer* aTrackBuffer)
 {
   MOZ_ASSERT(mReader);
   mReader->AddTrackBuffer(aTrackBuffer);
 }
--- a/dom/media/mediasource/MediaSourceDecoder.h
+++ b/dom/media/mediasource/MediaSourceDecoder.h
@@ -41,17 +41,18 @@ public:
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
   static already_AddRefed<MediaResource> CreateResource(nsIPrincipal* aPrincipal = nullptr);
 
   void AttachMediaSource(dom::MediaSource* aMediaSource);
   void DetachMediaSource();
 
-  already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType);
+  already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType,
+                                                         int64_t aTimestampOffset /* microseconds */);
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
   void OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo);
 
   void Ended();
   bool IsExpectingMoreData() MOZ_OVERRIDE;
 
   void SetDecodedDuration(int64_t aDuration);
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -452,24 +452,24 @@ CreateReaderForType(const nsACString& aT
       MP4Decoder::IsEnabled()) {
     return new MP4Reader(aDecoder);
   }
 #endif
   return DecoderTraits::CreateReader(aType, aDecoder);
 }
 
 already_AddRefed<SourceBufferDecoder>
-MediaSourceReader::CreateSubDecoder(const nsACString& aType)
+MediaSourceReader::CreateSubDecoder(const nsACString& aType, int64_t aTimestampOffset)
 {
   if (IsShutdown()) {
     return nullptr;
   }
   MOZ_ASSERT(GetTaskQueue());
   nsRefPtr<SourceBufferDecoder> decoder =
-    new SourceBufferDecoder(new SourceBufferResource(aType), mDecoder);
+    new SourceBufferDecoder(new SourceBufferResource(aType), mDecoder, aTimestampOffset);
   nsRefPtr<MediaDecoderReader> reader(CreateReaderForType(aType, decoder));
   if (!reader) {
     return nullptr;
   }
 
   // MSE uses a start time of 0 everywhere. Set that immediately on the
   // subreader to make sure that it's always in a state where we can invoke
   // GetBuffered on it.
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -94,17 +94,18 @@ public:
   void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
   nsRefPtr<SeekPromise>
   Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime,
        int64_t aCurrentTime) MOZ_OVERRIDE;
 
   // Acquires the decoder monitor, and is thus callable on any thread.
   nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE;
 
-  already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType);
+  already_AddRefed<SourceBufferDecoder> CreateSubDecoder(const nsACString& aType,
+                                                         int64_t aTimestampOffset /* microseconds */);
 
   void AddTrackBuffer(TrackBuffer* aTrackBuffer);
   void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
   void OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo);
 
   nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
   virtual void BreakCycles();
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -330,17 +330,19 @@ void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   MSE_DEBUG("SourceBuffer(%p)::AppendData(aLength=%u)", this, aLength);
   if (!PrepareAppend(aRv)) {
     return;
   }
   StartUpdating();
 
-  if (!mTrackBuffer->AppendData(aData, aLength)) {
+  MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments,
+             "We don't handle timestampOffset for sequence mode yet");
+  if (!mTrackBuffer->AppendData(aData, aLength, mTimestampOffset * USECS_PER_S)) {
     Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
     ErrorResult dummy;
     mMediaSource->EndOfStream(decodeError, dummy);
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   if (mTrackBuffer->HasInitSegment()) {
--- a/dom/media/mediasource/SourceBufferDecoder.cpp
+++ b/dom/media/mediasource/SourceBufferDecoder.cpp
@@ -28,20 +28,22 @@ namespace layers {
 
 class ImageContainer;
 
 } // namespace layers
 
 NS_IMPL_ISUPPORTS0(SourceBufferDecoder)
 
 SourceBufferDecoder::SourceBufferDecoder(MediaResource* aResource,
-                                         AbstractMediaDecoder* aParentDecoder)
+                                         AbstractMediaDecoder* aParentDecoder,
+                                         int64_t aTimestampOffset)
   : mResource(aResource)
   , mParentDecoder(aParentDecoder)
   , mReader(nullptr)
+  , mTimestampOffset(aTimestampOffset)
   , mMediaDuration(-1)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(SourceBufferDecoder);
 }
 
 SourceBufferDecoder::~SourceBufferDecoder()
 {
--- a/dom/media/mediasource/SourceBufferDecoder.h
+++ b/dom/media/mediasource/SourceBufferDecoder.h
@@ -27,25 +27,27 @@ class TimeRanges;
 
 } // namespace dom
 
 class SourceBufferDecoder MOZ_FINAL : public AbstractMediaDecoder
 {
 public:
   // This class holds a weak pointer to MediaResource.  It's the responsibility
   // of the caller to manage the memory of the MediaResource object.
-  SourceBufferDecoder(MediaResource* aResource, AbstractMediaDecoder* aParentDecoder);
+  SourceBufferDecoder(MediaResource* aResource, AbstractMediaDecoder* aParentDecoder,
+                      int64_t aTimestampOffset /* microseconds */);
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
   virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE;
   virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
   virtual bool OnDecodeThread() const MOZ_FINAL MOZ_OVERRIDE;
   virtual bool OnStateMachineThread() const MOZ_FINAL MOZ_OVERRIDE;
+  virtual int64_t GetTimestampOffset() const MOZ_FINAL MOZ_OVERRIDE { return mTimestampOffset; }
   virtual int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE;
   virtual layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual MediaDecoderOwner* GetOwner() MOZ_FINAL MOZ_OVERRIDE;
   virtual SourceBufferResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE;
   virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
   virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
   virtual void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo, nsAutoPtr<MetadataTags> aTags) MOZ_FINAL MOZ_OVERRIDE;
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo) MOZ_FINAL MOZ_OVERRIDE;
@@ -124,16 +126,17 @@ private:
 
   // Our TrackBuffer's task queue, this is only non-null during initialization.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   nsRefPtr<MediaResource> mResource;
 
   AbstractMediaDecoder* mParentDecoder;
   nsRefPtr<MediaDecoderReader> mReader;
+  int64_t mTimestampOffset;
   int64_t mMediaDuration;
 
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 };
 
 } // namespace mozilla
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -34,16 +34,17 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #endif
 
 namespace mozilla {
 
 TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType)
   : mParentDecoder(aParentDecoder)
   , mType(aType)
   , mLastStartTimestamp(0)
+  , mLastTimestampOffset(0)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(TrackBuffer);
   mParser = ContainerParser::CreateForMIMEType(aType);
   mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool());
   aParentDecoder->AddTrackBuffer(this);
   mDecoderPerSegment = Preferences::GetBool("media.mediasource.decoder-per-segment", false);
   MSE_DEBUG("TrackBuffer(%p) created for parent decoder %p", this, aParentDecoder);
@@ -80,19 +81,19 @@ public:
 
   ~DecodersToInitialize()
   {
     for (size_t i = 0; i < mDecoders.Length(); i++) {
       mOwner->QueueInitializeDecoder(mDecoders[i]);
     }
   }
 
-  bool NewDecoder()
+  bool NewDecoder(int64_t aTimestampOffset)
   {
-    nsRefPtr<SourceBufferDecoder> decoder = mOwner->NewDecoder();
+    nsRefPtr<SourceBufferDecoder> decoder = mOwner->NewDecoder(aTimestampOffset);
     if (!decoder) {
       return false;
     }
     mDecoders.AppendElement(decoder);
     return true;
   }
 
 private:
@@ -133,43 +134,46 @@ TrackBuffer::ContinueShutdown()
 
   mInitializedDecoders.Clear();
   mParentDecoder = nullptr;
 
   mShutdownPromise.Resolve(true, __func__);
 }
 
 bool
-TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength)
+TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength, int64_t aTimestampOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecodersToInitialize decoders(this);
   // TODO: Run more of the buffer append algorithm asynchronously.
   if (mParser->IsInitSegmentPresent(aData, aLength)) {
     MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this);
-    if (!decoders.NewDecoder()) {
+    if (!decoders.NewDecoder(aTimestampOffset)) {
       return false;
     }
   } else if (!mParser->HasInitData()) {
     MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
     return false;
   }
 
   int64_t start, end;
   if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
+    start += aTimestampOffset;
+    end += aTimestampOffset;
     if (mParser->IsMediaSegmentPresent(aData, aLength) &&
         mLastEndTimestamp &&
         (!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
+         mLastTimestampOffset != aTimestampOffset ||
          mDecoderPerSegment)) {
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
                 this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);
 
       // This data is earlier in the timeline than data we have already
       // processed, so we must create a new decoder to handle the decoding.
-      if (!decoders.NewDecoder()) {
+      if (!decoders.NewDecoder(aTimestampOffset)) {
         return false;
       }
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
       const nsTArray<uint8_t>& initData = mParser->InitData();
       AppendDataToCurrentResource(initData.Elements(), initData.Length());
       mLastStartTimestamp = start;
     } else {
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
@@ -326,33 +330,34 @@ TrackBuffer::Buffered(dom::TimeRanges* a
       aRanges->Union(r, double(mParser->GetRoundingError()) / USECS_PER_S);
     }
   }
 
   return highestEndTime;
 }
 
 already_AddRefed<SourceBufferDecoder>
-TrackBuffer::NewDecoder()
+TrackBuffer::NewDecoder(int64_t aTimestampOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mParentDecoder);
 
   DiscardDecoder();
 
-  nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType);
+  nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType, aTimestampOffset);
   if (!decoder) {
     return nullptr;
   }
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   mCurrentDecoder = decoder;
   mDecoders.AppendElement(decoder);
 
   mLastStartTimestamp = 0;
   mLastEndTimestamp.reset();
+  mLastTimestampOffset = aTimestampOffset;
 
   decoder->SetTaskQueue(mTaskQueue);
   return decoder.forget();
 }
 
 bool
 TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
 {
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -34,17 +34,17 @@ public:
 
   TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
 
   nsRefPtr<ShutdownPromise> Shutdown();
 
   // Append data to the current decoder.  Also responsible for calling
   // NotifyDataArrived on the decoder to keep buffered range computation up
   // to date.  Returns false if the append failed.
-  bool AppendData(const uint8_t* aData, uint32_t aLength);
+  bool AppendData(const uint8_t* aData, uint32_t aLength, int64_t aTimestampOffset /* microseconds */);
   bool EvictData(uint32_t aThreshold);
   void EvictBefore(double aTime);
 
   // Returns the highest end time of all of the buffered ranges in the
   // decoders managed by this TrackBuffer, and returns the union of the
   // decoders buffered ranges in aRanges. This may be called on any thread.
   double Buffered(dom::TimeRanges* aRanges);
 
@@ -87,17 +87,17 @@ private:
   friend class DecodersToInitialize;
   ~TrackBuffer();
 
   // Create a new decoder, set mCurrentDecoder to the new decoder and
   // returns it. The new decoder must be queued using QueueInitializeDecoder
   // for initialization.
   // The decoder is not considered initialized until it is added to
   // mInitializedDecoders.
-  already_AddRefed<SourceBufferDecoder> NewDecoder();
+  already_AddRefed<SourceBufferDecoder> NewDecoder(int64_t aTimestampOffset /* microseconds */);
 
   // Helper for AppendData, ensures NotifyDataArrived is called whenever
   // data is appended to the current decoder's SourceBufferResource.
   bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength);
 
   // Queue execution of InitializeDecoder on mTaskQueue.
   bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder);
 
@@ -151,16 +151,19 @@ private:
   nsRefPtr<MediaSourceDecoder> mParentDecoder;
   const nsCString mType;
 
   // The last start and end timestamps added to the TrackBuffer via
   // AppendData.  Accessed on the main thread only.
   int64_t mLastStartTimestamp;
   Maybe<int64_t> mLastEndTimestamp;
 
+  // The timestamp offset used by our current decoder, in microseconds.
+  int64_t mLastTimestampOffset;
+
   // Set when the first decoder used by this TrackBuffer is initialized.
   // Protected by mParentDecoder's monitor.
   MediaInfo mInfo;
 
   void ContinueShutdown();
   MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
   bool mDecoderPerSegment;
   bool mShutdown;
--- a/media/libstagefright/binding/DecoderData.cpp
+++ b/media/libstagefright/binding/DecoderData.cpp
@@ -228,21 +228,23 @@ MP4Sample::MP4Sample(const MP4Sample& co
 MP4Sample::~MP4Sample()
 {
   if (mMediaBuffer) {
     mMediaBuffer->release();
   }
 }
 
 void
-MP4Sample::Update(int64_t& aMediaTime)
+MP4Sample::Update(int64_t& aMediaTime, int64_t& aTimestampOffset)
 {
   sp<MetaData> m = mMediaBuffer->meta_data();
-  decode_timestamp = FindInt64(m, kKeyDecodingTime);
-  composition_timestamp = FindInt64(m, kKeyTime) - aMediaTime;
+  // XXXbholley - Why don't we adjust decode_timestamp for aMediaTime?
+  // According to k17e, this code path is no longer used - we should probably remove it.
+  decode_timestamp = FindInt64(m, kKeyDecodingTime) + aTimestampOffset;
+  composition_timestamp = FindInt64(m, kKeyTime) - aMediaTime + aTimestampOffset;
   duration = FindInt64(m, kKeyDuration);
   byte_offset = FindInt64(m, kKey64BitFileOffset);
   is_sync_point = FindInt32(m, kKeyIsSyncFrame);
   data = reinterpret_cast<uint8_t*>(mMediaBuffer->data());
   size = mMediaBuffer->range_length();
 
   crypto.Update(m);
 }
--- a/media/libstagefright/binding/Index.cpp
+++ b/media/libstagefright/binding/Index.cpp
@@ -182,22 +182,23 @@ void SampleIterator::Seek(Microseconds a
     }
     Next();
   }
   mCurrentMoof = syncMoof;
   mCurrentSample = syncSample;
 }
 
 Index::Index(const stagefright::Vector<MediaSource::Indice>& aIndex,
-             Stream* aSource, uint32_t aTrackId, Monitor* aMonitor)
+             Stream* aSource, uint32_t aTrackId, Microseconds aTimestampOffset,
+             Monitor* aMonitor)
   : mSource(aSource)
   , mMonitor(aMonitor)
 {
   if (aIndex.isEmpty()) {
-    mMoofParser = new MoofParser(aSource, aTrackId, aMonitor);
+    mMoofParser = new MoofParser(aSource, aTrackId, aTimestampOffset, aMonitor);
   } else {
     for (size_t i = 0; i < aIndex.size(); i++) {
       const MediaSource::Indice& indice = aIndex[i];
       Sample sample;
       sample.mByteRange = MediaByteRange(indice.start_offset,
                                          indice.end_offset);
       sample.mCompositionRange = Interval<Microseconds>(indice.start_composition,
                                                         indice.end_composition);
--- a/media/libstagefright/binding/MoofParser.cpp
+++ b/media/libstagefright/binding/MoofParser.cpp
@@ -23,17 +23,17 @@ MoofParser::RebuildFragmentedIndex(
 void
 MoofParser::RebuildFragmentedIndex(BoxContext& aContext)
 {
   for (Box box(&aContext, mOffset); box.IsAvailable(); box = box.Next()) {
     if (box.IsType("moov")) {
       mInitRange = MediaByteRange(0, box.Range().mEnd);
       ParseMoov(box);
     } else if (box.IsType("moof")) {
-      Moof moof(box, mTrex, mMdhd, mEdts);
+      Moof moof(box, mTrex, mMdhd, mEdts, mTimestampOffset);
 
       if (!mMoofs.IsEmpty()) {
         // Stitch time ranges together in the case of a (hopefully small) time
         // range gap between moofs.
         mMoofs.LastElement().FixRounding(moof);
       }
 
       mMoofs.AppendElement(moof);
@@ -159,18 +159,18 @@ MoofParser::ParseMvex(Box& aBox)
       Trex trex = Trex(box);
       if (!mTrex.mTrackId || trex.mTrackId == mTrex.mTrackId) {
         mTrex = trex;
       }
     }
   }
 }
 
-Moof::Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd, Edts& aEdts) :
-    mRange(aBox.Range()), mMaxRoundingError(0)
+Moof::Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd, Edts& aEdts, Microseconds aTimestampOffset) :
+    mRange(aBox.Range()), mTimestampOffset(aTimestampOffset), mMaxRoundingError(0)
 {
   for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
     if (box.IsType("traf")) {
       ParseTraf(box, aTrex, aMdhd, aEdts);
     }
   }
   ProcessCenc();
 }
@@ -322,20 +322,20 @@ Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, 
     if (flags & 0x800) {
       ctsOffset = reader->Read32();
     }
 
     Sample sample;
     sample.mByteRange = MediaByteRange(offset, offset + sampleSize);
     offset += sampleSize;
 
-    sample.mDecodeTime = aMdhd.ToMicroseconds(decodeTime);
+    sample.mDecodeTime = aMdhd.ToMicroseconds(decodeTime) + mTimestampOffset;
     sample.mCompositionRange = Interval<Microseconds>(
-      aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset - aEdts.mMediaStart),
-      aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset + sampleDuration - aEdts.mMediaStart));
+      aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset - aEdts.mMediaStart) + mTimestampOffset,
+      aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset + sampleDuration - aEdts.mMediaStart) + mTimestampOffset);
     decodeTime += sampleDuration;
 
     sample.mSync = !(sampleFlags & 0x1010000);
 
     mIndex.AppendElement(sample);
 
     mMdatRange = mMdatRange.Extents(sample.mByteRange);
   }
--- a/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/DecoderData.h
@@ -152,17 +152,17 @@ public:
 typedef int64_t Microseconds;
 
 class MP4Sample
 {
 public:
   MP4Sample();
   MP4Sample(const MP4Sample& copy);
   virtual ~MP4Sample();
-  void Update(int64_t& aMediaTime);
+  void Update(int64_t& aMediaTime, int64_t& aTimestampOffset);
   void Pad(size_t aPaddingBytes);
 
   stagefright::MediaBuffer* mMediaBuffer;
 
   Microseconds decode_timestamp;
   Microseconds composition_timestamp;
   Microseconds duration;
   int64_t byte_offset;
--- a/media/libstagefright/binding/include/mp4_demuxer/Index.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/Index.h
@@ -32,17 +32,18 @@ private:
 };
 
 class Index
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Index)
 
   Index(const stagefright::Vector<stagefright::MediaSource::Indice>& aIndex,
-        Stream* aSource, uint32_t aTrackId, Monitor* aMonitor);
+        Stream* aSource, uint32_t aTrackId, Microseconds aTimestampOffset,
+        Monitor* aMonitor);
 
   void UpdateMoofIndex(const nsTArray<mozilla::MediaByteRange>& aByteRanges);
   Microseconds GetEndCompositionIfBuffered(
     const nsTArray<mozilla::MediaByteRange>& aByteRanges);
   void ConvertByteRangesToTimeRanges(
     const nsTArray<mozilla::MediaByteRange>& aByteRanges,
     nsTArray<Interval<Microseconds>>* aTimeRanges);
   uint64_t GetEvictionOffset(Microseconds aTime);
--- a/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h
@@ -146,17 +146,17 @@ private:
   int64_t mMoofOffset;
   Saiz& mSaiz;
   Saio& mSaio;
 };
 
 class Moof
 {
 public:
-  Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd, Edts& aEdts);
+  Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd, Edts& aEdts, Microseconds aTimestampOffset);
   bool GetAuxInfo(AtomType aType, nsTArray<MediaByteRange>* aByteRanges);
   void FixRounding(const Moof& aMoof);
 
   mozilla::MediaByteRange mRange;
   mozilla::MediaByteRange mMdatRange;
   Interval<Microseconds> mTimeRange;
   nsTArray<Sample> mIndex;
 
@@ -164,24 +164,27 @@ public:
   nsTArray<Saio> mSaios;
 
 private:
   void ParseTraf(Box& aBox, Trex& aTrex, Mdhd& aMdhd, Edts& aEdts);
   void ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd, Edts& aEdts);
   void ParseSaiz(Box& aBox);
   void ParseSaio(Box& aBox);
   bool ProcessCenc();
+  Microseconds mTimestampOffset;
   uint64_t mMaxRoundingError;
 };
 
 class MoofParser
 {
 public:
-  MoofParser(Stream* aSource, uint32_t aTrackId, Monitor* aMonitor)
-    : mSource(aSource), mOffset(0), mTrex(aTrackId), mMonitor(aMonitor)
+  MoofParser(Stream* aSource, uint32_t aTrackId,
+             Microseconds aTimestampOffset, Monitor* aMonitor)
+    : mSource(aSource), mOffset(0), mTimestampOffset(aTimestampOffset),
+      mTrex(aTrackId), mMonitor(aMonitor)
   {
     // Setting the mTrex.mTrackId to 0 is a nasty work around for calculating
     // the composition range for MSE. We need an array of tracks.
   }
   void RebuildFragmentedIndex(
     const nsTArray<mozilla::MediaByteRange>& aByteRanges);
   void RebuildFragmentedIndex(BoxContext& aContext);
   Interval<Microseconds> GetCompositionRange(
@@ -192,16 +195,17 @@ public:
   void ParseMdia(Box& aBox, Tkhd& aTkhd);
   void ParseMvex(Box& aBox);
 
   bool BlockingReadNextMoof();
 
   mozilla::MediaByteRange mInitRange;
   nsRefPtr<Stream> mSource;
   uint64_t mOffset;
+  Microseconds mTimestampOffset;
   nsTArray<uint64_t> mMoofOffsets;
   Mdhd mMdhd;
   Trex mTrex;
   Tfdt mTfdt;
   Edts mEdts;
   Monitor* mMonitor;
   nsTArray<Moof>& Moofs() { mMonitor->AssertCurrentThreadOwns(); return mMoofs; }
 private:
--- a/media/libstagefright/binding/include/mp4_demuxer/mp4_demuxer.h
+++ b/media/libstagefright/binding/include/mp4_demuxer/mp4_demuxer.h
@@ -38,17 +38,17 @@ protected:
   virtual ~Stream() {}
 };
 
 enum TrackType { kVideo = 1, kAudio };
 
 class MP4Demuxer
 {
 public:
-  explicit MP4Demuxer(Stream* aSource, Monitor* aMonitor);
+  explicit MP4Demuxer(Stream* aSource, Microseconds aTimestampOffset, Monitor* aMonitor);
   ~MP4Demuxer();
 
   bool Init();
   Microseconds Duration();
   bool CanSeek();
 
   bool HasValidAudio();
   bool HasValidVideo();
@@ -77,14 +77,15 @@ private:
   AudioDecoderConfig mAudioConfig;
   VideoDecoderConfig mVideoConfig;
   CryptoFile mCrypto;
 
   nsAutoPtr<StageFrightPrivate> mPrivate;
   nsRefPtr<Stream> mSource;
   nsTArray<mozilla::MediaByteRange> mCachedByteRanges;
   nsTArray<Interval<Microseconds>> mCachedTimeRanges;
+  Microseconds mTimestampOffset;
   Monitor* mMonitor;
 };
 
 } // namespace mozilla
 
 #endif // MP4_DEMUXER_H_
--- a/media/libstagefright/binding/mp4_demuxer.cpp
+++ b/media/libstagefright/binding/mp4_demuxer.cpp
@@ -68,18 +68,19 @@ public:
   virtual uint32_t flags() { return kWantsPrefetching | kIsHTTPBasedSource; }
 
   virtual status_t reconnectAtOffset(off64_t offset) { return NO_ERROR; }
 
 private:
   nsRefPtr<Stream> mSource;
 };
 
-MP4Demuxer::MP4Demuxer(Stream* source, Monitor* aMonitor)
-  : mPrivate(new StageFrightPrivate()), mSource(source), mMonitor(aMonitor)
+MP4Demuxer::MP4Demuxer(Stream* source, Microseconds aTimestampOffset, Monitor* aMonitor)
+  : mPrivate(new StageFrightPrivate()), mSource(source),
+    mTimestampOffset(aTimestampOffset), mMonitor(aMonitor)
 {
   mPrivate->mExtractor = new MPEG4Extractor(new DataSourceAdapter(source));
 }
 
 MP4Demuxer::~MP4Demuxer()
 {
   if (mPrivate->mAudio.get()) {
     mPrivate->mAudio->stop();
@@ -105,30 +106,32 @@ MP4Demuxer::Init()
     if (!mPrivate->mAudio.get() && !strncmp(mimeType, "audio/", 6)) {
       sp<MediaSource> track = e->getTrack(i);
       if (track->start() != OK) {
         return false;
       }
       mPrivate->mAudio = track;
       mAudioConfig.Update(metaData, mimeType);
       nsRefPtr<Index> index = new Index(mPrivate->mAudio->exportIndex(),
-                                        mSource, mAudioConfig.mTrackId, mMonitor);
+                                        mSource, mAudioConfig.mTrackId,
+                                        mTimestampOffset, mMonitor);
       mPrivate->mIndexes.AppendElement(index);
       if (index->IsFragmented() && !mAudioConfig.crypto.valid) {
         mPrivate->mAudioIterator = new SampleIterator(index);
       }
     } else if (!mPrivate->mVideo.get() && !strncmp(mimeType, "video/", 6)) {
       sp<MediaSource> track = e->getTrack(i);
       if (track->start() != OK) {
         return false;
       }
       mPrivate->mVideo = track;
       mVideoConfig.Update(metaData, mimeType);
       nsRefPtr<Index> index = new Index(mPrivate->mVideo->exportIndex(),
-                                        mSource, mVideoConfig.mTrackId, mMonitor);
+                                        mSource, mVideoConfig.mTrackId,
+                                        mTimestampOffset, mMonitor);
       mPrivate->mIndexes.AppendElement(index);
       if (index->IsFragmented() && !mVideoConfig.crypto.valid) {
         mPrivate->mVideoIterator = new SampleIterator(index);
       }
     }
   }
   sp<MetaData> metaData = e->getMetaData();
   mCrypto.Update(metaData);
@@ -208,17 +211,17 @@ MP4Demuxer::DemuxAudioSample()
   status_t status =
     mPrivate->mAudio->read(&sample->mMediaBuffer, &mPrivate->mAudioOptions);
   mPrivate->mAudioOptions.clearSeekTo();
 
   if (status < 0) {
     return nullptr;
   }
 
-  sample->Update(mAudioConfig.media_time);
+  sample->Update(mAudioConfig.media_time, mTimestampOffset);
 
   return sample.forget();
 }
 
 MP4Sample*
 MP4Demuxer::DemuxVideoSample()
 {
   mMonitor->AssertCurrentThreadOwns();
@@ -238,17 +241,17 @@ MP4Demuxer::DemuxVideoSample()
   status_t status =
     mPrivate->mVideo->read(&sample->mMediaBuffer, &mPrivate->mVideoOptions);
   mPrivate->mVideoOptions.clearSeekTo();
 
   if (status < 0) {
     return nullptr;
   }
 
-  sample->Update(mVideoConfig.media_time);
+  sample->Update(mVideoConfig.media_time, mTimestampOffset);
   sample->extra_data = mVideoConfig.extra_data;
 
   return sample.forget();
 }
 
 void
 MP4Demuxer::UpdateIndex(const nsTArray<mozilla::MediaByteRange>& aByteRanges)
 {