Bug 1159027: Part3. Add MP4Demuxer object. r=cpearce
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 11 May 2015 20:57:20 +1000
changeset 243355 4d58f20f7252be597a200d51df3ebdb2208d8960
parent 243354 66c9b5468e3101d1e382d776812fa75d06457d01
child 243356 456b71b199def9dedeb9485a19b58c2213d9965b
push id28738
push usercbook@mozilla.com
push dateTue, 12 May 2015 14:11:31 +0000
treeherdermozilla-central@bedce1b405a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1159027
milestone40.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 1159027: Part3. Add MP4Demuxer object. r=cpearce
dom/media/fmp4/MP4Demuxer.cpp
dom/media/fmp4/MP4Demuxer.h
dom/media/fmp4/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -0,0 +1,323 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <limits>
+#include <stdint.h>
+
+#include "MP4Demuxer.h"
+#include "mp4_demuxer/Index.h"
+#include "mp4_demuxer/MoofParser.h"
+#include "mp4_demuxer/MP4Metadata.h"
+#include "mp4_demuxer/ResourceStream.h"
+#include "mp4_demuxer/BufferStream.h"
+
+namespace mozilla {
+
+MP4Demuxer::MP4Demuxer(MediaResource* aResource)
+  : mResource(aResource)
+  , mStream(new mp4_demuxer::ResourceStream(aResource))
+  , mInitData(new MediaLargeByteBuffer)
+{
+}
+
+nsRefPtr<MP4Demuxer::InitPromise>
+MP4Demuxer::Init()
+{
+  AutoPinned<mp4_demuxer::ResourceStream> stream(mStream);
+
+  // Check that we have enough data to read the metadata.
+  MediaByteRange br = mp4_demuxer::MP4Metadata::MetadataRange(stream);
+  if (br.IsNull()) {
+    return InitPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, __func__);
+  }
+
+  if (!mInitData->SetLength(br.Length())) {
+    // OOM
+    return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+
+  size_t size;
+  mStream->ReadAt(br.mStart, mInitData->Elements(), br.Length(), &size);
+  if (size != size_t(br.Length())) {
+    return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+
+  nsRefPtr<mp4_demuxer::BufferStream> bufferstream =
+    new mp4_demuxer::BufferStream(mInitData);
+
+  mMetadata = MakeUnique<mp4_demuxer::MP4Metadata>(bufferstream);
+
+  if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) &&
+      !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) {
+    return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+
+  return InitPromise::CreateAndResolve(NS_OK, __func__);
+}
+
+already_AddRefed<MediaDataDemuxer>
+MP4Demuxer::Clone() const
+{
+  nsRefPtr<MP4Demuxer> demuxer = new MP4Demuxer(mResource);
+  demuxer->mInitData = mInitData;
+  nsRefPtr<mp4_demuxer::BufferStream> bufferstream =
+    new mp4_demuxer::BufferStream(mInitData);
+  demuxer->mMetadata = MakeUnique<mp4_demuxer::MP4Metadata>(bufferstream);
+  if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) &&
+      !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) {
+    NS_WARNING("Couldn't recreate MP4Demuxer");
+    return nullptr;
+  }
+  return demuxer.forget();
+}
+
+bool
+MP4Demuxer::HasTrackType(TrackInfo::TrackType aType) const
+{
+  return !!GetNumberTracks(aType);
+}
+
+uint32_t
+MP4Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const
+{
+  return mMetadata->GetNumberTracks(aType);
+}
+
+already_AddRefed<MediaTrackDemuxer>
+MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
+{
+  if (mMetadata->GetNumberTracks(aType) <= aTrackNumber) {
+    return nullptr;
+  }
+  nsRefPtr<MediaTrackDemuxer> e =
+    new MP4TrackDemuxer(this, aType, aTrackNumber);
+  return e.forget();
+}
+
+bool
+MP4Demuxer::IsSeekable() const
+{
+  return mMetadata->CanSeek();
+}
+
+void
+MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset)
+{
+  // TODO. May not be required for our use
+}
+
+UniquePtr<EncryptionInfo>
+MP4Demuxer::GetCrypto()
+{
+  const mp4_demuxer::CryptoFile& cryptoFile = mMetadata->Crypto();
+  if (!cryptoFile.valid) {
+    return nullptr;
+  }
+
+  const nsTArray<mp4_demuxer::PsshInfo>& psshs = cryptoFile.pssh;
+  nsTArray<uint8_t> initData;
+  for (uint32_t i = 0; i < psshs.Length(); i++) {
+    initData.AppendElements(psshs[i].data);
+  }
+
+  if (initData.IsEmpty()) {
+    return nullptr;
+  }
+
+  auto crypto = MakeUnique<EncryptionInfo>();
+  crypto->AddInitData(NS_LITERAL_STRING("cenc"), Move(initData));
+
+  return crypto;
+}
+
+MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
+                                 TrackInfo::TrackType aType,
+                                 uint32_t aTrackNumber)
+  : mParent(aParent)
+  , mStream(new mp4_demuxer::ResourceStream(mParent->mResource))
+  , mMonitor("MP4TrackDemuxer")
+{
+  mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber);
+
+  MOZ_ASSERT(mInfo);
+
+  nsTArray<mp4_demuxer::Index::Indice> indices;
+  if (!mParent->mMetadata->ReadTrackIndex(indices, mInfo->mTrackId)) {
+    MOZ_ASSERT(false);
+  }
+  mIndex = new mp4_demuxer::Index(indices,
+                                  mStream,
+                                  mInfo->mTrackId,
+                                  mInfo->IsAudio(),
+                                  &mMonitor);
+  mIterator = MakeUnique<mp4_demuxer::SampleIterator>(mIndex);
+}
+
+UniquePtr<TrackInfo>
+MP4TrackDemuxer::GetInfo() const
+{
+  return mInfo->Clone();
+}
+
+nsRefPtr<MP4TrackDemuxer::SeekPromise>
+MP4TrackDemuxer::Seek(media::TimeUnit aTime)
+{
+  int64_t seekTime = aTime.ToMicroseconds();
+  mQueuedSample = nullptr;
+
+  MonitorAutoLock mon(mMonitor);
+  mIterator->Seek(seekTime);
+
+  // Check what time we actually seeked to.
+  mQueuedSample = mIterator->GetNext();
+  if (mQueuedSample) {
+    seekTime = mQueuedSample->mTime;
+  }
+
+  return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__);
+}
+
+nsRefPtr<MP4TrackDemuxer::SamplesPromise>
+MP4TrackDemuxer::GetSamples(int32_t aNumSamples)
+{
+  nsRefPtr<SamplesHolder> samples = new SamplesHolder;
+  if (!aNumSamples) {
+    return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+
+  if (mQueuedSample) {
+    samples->mSamples.AppendElement(mQueuedSample);
+    mQueuedSample = nullptr;
+    aNumSamples--;
+  }
+  MonitorAutoLock mon(mMonitor);
+  nsRefPtr<MediaRawData> sample;
+  while (aNumSamples && (sample = mIterator->GetNext())) {
+    samples->mSamples.AppendElement(sample);
+    aNumSamples--;
+  }
+
+  if (samples->mSamples.IsEmpty()) {
+    return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__);
+  } else {
+    UpdateSamples(samples->mSamples);
+    return SamplesPromise::CreateAndResolve(samples, __func__);
+  }
+}
+
+void
+MP4TrackDemuxer::Reset()
+{
+  mQueuedSample = nullptr;
+  // TODO, Seek to first frame available, which isn't always 0.
+  MonitorAutoLock mon(mMonitor);
+  mIterator->Seek(0);
+}
+
+void
+MP4TrackDemuxer::UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples)
+{
+  for (size_t i = 0; i < aSamples.Length(); i++) {
+    MediaRawData* sample = aSamples[i];
+    if (sample->mCrypto.mValid) {
+      nsAutoPtr<MediaRawDataWriter> writer(sample->CreateWriter());
+      writer->mCrypto.mMode = mInfo->mCrypto.mMode;
+      writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize;
+      writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId);
+    }
+    if (mInfo->GetAsVideoInfo()) {
+      sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData;
+    }
+  }
+  if (mNextKeyframeTime.isNothing() ||
+      aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) {
+    mNextKeyframeTime.reset();
+    mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime();
+    if (frameTime != -1) {
+      mNextKeyframeTime.emplace(
+        media::TimeUnit::FromMicroseconds(frameTime));
+    }
+  }
+}
+
+nsresult
+MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
+{
+  if (mNextKeyframeTime.isNothing()) {
+    // There's no next key frame.
+    *aTime =
+      media::TimeUnit::FromMicroseconds(std::numeric_limits<int64_t>::max());
+  } else {
+    *aTime = mNextKeyframeTime.value();
+  }
+  return NS_OK;
+}
+
+nsRefPtr<MP4TrackDemuxer::SkipAccessPointPromise>
+MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
+{
+  MonitorAutoLock mon(mMonitor);
+  mQueuedSample = nullptr;
+  // Loop until we reach the next keyframe after the threshold.
+  uint32_t parsed = 0;
+  bool found = false;
+  nsRefPtr<MediaRawData> sample;
+  while (!found && (sample = mIterator->GetNext())) {
+    parsed++;
+    if (sample->mKeyframe && sample->mTime >= aTimeThreshold.ToMicroseconds()) {
+      found = true;
+      mQueuedSample = sample;
+    }
+  }
+  if (found) {
+    return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
+  } else {
+    SkipFailureHolder failure(DemuxerFailureReason::END_OF_STREAM, parsed);
+    return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__);
+  }
+}
+
+int64_t
+MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime)
+{
+  MonitorAutoLock mon(mMonitor);
+  return int64_t(mIndex->GetEvictionOffset(aTime.ToMicroseconds()));
+}
+
+media::TimeIntervals
+MP4TrackDemuxer::GetBuffered()
+{
+  AutoPinned<MediaResource> resource(mParent->mResource);
+  nsTArray<MediaByteRange> byteRanges;
+  nsresult rv = resource->GetCachedRanges(byteRanges);
+
+  if (NS_FAILED(rv)) {
+    return media::TimeIntervals();
+  }
+  nsTArray<mp4_demuxer::Interval<int64_t>> timeRanges;
+
+  MonitorAutoLock mon(mMonitor);
+  mIndex->UpdateMoofIndex(byteRanges);
+  int64_t endComposition =
+    mIndex->GetEndCompositionIfBuffered(byteRanges);
+
+  mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges);
+  if (endComposition) {
+    mp4_demuxer::Interval<int64_t>::SemiNormalAppend(
+      timeRanges, mp4_demuxer::Interval<int64_t>(endComposition, endComposition));
+  }
+  // convert timeRanges.
+  media::TimeIntervals ranges;
+  for (size_t i = 0; i < timeRanges.Length(); i++) {
+    ranges +=
+      media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start),
+                          media::TimeUnit::FromMicroseconds(timeRanges[i].end));
+  }
+  return ranges;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/MP4Demuxer.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MP4Demuxer_h_)
+#define MP4Demuxer_h_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Monitor.h"
+#include "MediaDataDemuxer.h"
+#include "MediaResource.h"
+
+namespace mp4_demuxer {
+class Index;
+class MP4Metadata;
+class ResourceStream;
+class SampleIterator;
+}
+
+namespace mozilla {
+
+class MP4TrackDemuxer;
+
+class MP4Demuxer : public MediaDataDemuxer
+{
+public:
+  explicit MP4Demuxer(MediaResource* aResource);
+
+  virtual nsRefPtr<InitPromise> Init() override;
+
+  virtual already_AddRefed<MediaDataDemuxer> Clone() const override;
+
+  virtual bool HasTrackType(TrackInfo::TrackType aType) const override;
+
+  virtual uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
+
+  virtual already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(TrackInfo::TrackType aType,
+                                                              uint32_t aTrackNumber) override;
+
+  virtual bool IsSeekable() const override;
+
+  virtual UniquePtr<EncryptionInfo> GetCrypto() override;
+
+  virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
+
+private:
+  friend class MP4TrackDemuxer;
+  nsRefPtr<MediaResource> mResource;
+  nsRefPtr<mp4_demuxer::ResourceStream> mStream;
+  nsRefPtr<MediaLargeByteBuffer> mInitData;
+  UniquePtr<mp4_demuxer::MP4Metadata> mMetadata;
+};
+
+class MP4TrackDemuxer : public MediaTrackDemuxer
+{
+public:
+  MP4TrackDemuxer(MP4Demuxer* aParent,
+                  TrackInfo::TrackType aType,
+                  uint32_t aTrackNumber);
+
+  virtual UniquePtr<TrackInfo> GetInfo() const override;
+
+  virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
+
+  virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
+
+  virtual void Reset() override;
+
+  virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
+
+  nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
+
+  virtual media::TimeIntervals GetBuffered() override;
+
+  virtual int64_t GetEvictionOffset(media::TimeUnit aTime) override;
+
+private:
+  void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
+  nsRefPtr<MP4Demuxer> mParent;
+  nsRefPtr<mp4_demuxer::Index> mIndex;
+  UniquePtr<mp4_demuxer::SampleIterator> mIterator;
+  UniquePtr<TrackInfo> mInfo;
+  nsRefPtr<mp4_demuxer::ResourceStream> mStream;
+  Maybe<media::TimeUnit> mNextKeyframeTime;
+  // Queued samples extracted by the demuxer, but not yet returned.
+  nsRefPtr<MediaRawData> mQueuedSample;
+
+  // We do not actually need a monitor, however MoofParser will assert
+  // if a monitor isn't held.
+  Monitor mMonitor;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/fmp4/moz.build
+++ b/dom/media/fmp4/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'MP4Decoder.h',
+    'MP4Demuxer.h',
     'MP4Reader.h',
     'MP4Stream.h',
     'PlatformDecoderModule.h',
     'SharedDecoderManager.h',
     'wrappers/H264Converter.h'
 ]
 
 UNIFIED_SOURCES += [
@@ -18,16 +19,17 @@ UNIFIED_SOURCES += [
     'MP4Decoder.cpp',
     'MP4Stream.cpp',
     'PlatformDecoderModule.cpp',
     'SharedDecoderManager.cpp',
     'wrappers/H264Converter.cpp'
 ]
 
 SOURCES += [
+    'MP4Demuxer.cpp',
     'MP4Reader.cpp',
 ]
 
 DIRS += ['gmp']
 
 if CONFIG['MOZ_WMF']:
     DIRS += [ 'wmf' ];