2015-05-18 05:40:32 +00:00
|
|
|
/* -*- 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/. */
|
|
|
|
|
2016-09-08 10:06:20 +00:00
|
|
|
#include "mozilla/CDMProxy.h"
|
2015-05-18 05:40:32 +00:00
|
|
|
#include "mozilla/dom/HTMLMediaElement.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
2016-04-13 21:02:00 +00:00
|
|
|
#include "mozilla/Telemetry.h"
|
2016-02-16 21:55:33 +00:00
|
|
|
#include "nsContentUtils.h"
|
2015-05-18 05:40:32 +00:00
|
|
|
#include "nsPrintfCString.h"
|
|
|
|
#include "nsSize.h"
|
|
|
|
#include "Layers.h"
|
|
|
|
#include "MediaData.h"
|
|
|
|
#include "MediaInfo.h"
|
|
|
|
#include "MediaFormatReader.h"
|
|
|
|
#include "MediaResource.h"
|
2015-08-03 18:44:10 +00:00
|
|
|
#include "mozilla/SharedThreadPool.h"
|
2015-05-18 05:40:32 +00:00
|
|
|
#include "VideoUtils.h"
|
2016-05-27 06:33:48 +00:00
|
|
|
#include "VideoFrameContainer.h"
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
2015-05-31 04:18:12 +00:00
|
|
|
using namespace mozilla::media;
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
using mozilla::layers::Image;
|
|
|
|
using mozilla::layers::LayerManager;
|
|
|
|
using mozilla::layers::LayersBackend;
|
|
|
|
|
2015-11-15 13:49:01 +00:00
|
|
|
static mozilla::LazyLogModule sFormatDecoderLog("MediaFormatReader");
|
2016-07-24 12:30:07 +00:00
|
|
|
mozilla::LazyLogModule gMediaDemuxerLog("MediaDemuxer");
|
2015-11-15 13:49:01 +00:00
|
|
|
|
|
|
|
#define LOG(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Debug, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
|
|
|
#define LOGV(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Verbose, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
|
|
|
static const char*
|
|
|
|
TrackTypeToStr(TrackInfo::TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
|
|
|
|
aTrack == TrackInfo::kVideoTrack ||
|
|
|
|
aTrack == TrackInfo::kTextTrack);
|
|
|
|
switch (aTrack) {
|
|
|
|
case TrackInfo::kAudioTrack:
|
|
|
|
return "Audio";
|
|
|
|
case TrackInfo::kVideoTrack:
|
|
|
|
return "Video";
|
|
|
|
case TrackInfo::kTextTrack:
|
|
|
|
return "Text";
|
|
|
|
default:
|
|
|
|
return "Unknown";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
|
2015-10-15 00:04:00 +00:00
|
|
|
MediaDataDemuxer* aDemuxer,
|
|
|
|
VideoFrameContainer* aVideoFrameContainer,
|
|
|
|
layers::LayersBackend aLayersBackend)
|
2015-10-09 01:25:23 +00:00
|
|
|
: MediaDecoderReader(aDecoder)
|
2016-09-01 09:25:54 +00:00
|
|
|
, mAudio(this, MediaData::AUDIO_DATA,
|
2016-05-30 16:24:00 +00:00
|
|
|
Preferences::GetUint("media.audio-max-decode-error", 3))
|
2016-09-01 09:25:54 +00:00
|
|
|
, mVideo(this, MediaData::VIDEO_DATA,
|
2016-05-30 16:24:00 +00:00
|
|
|
Preferences::GetUint("media.video-max-decode-error", 2))
|
2015-10-07 01:00:52 +00:00
|
|
|
, mDemuxer(aDemuxer)
|
|
|
|
, mDemuxerInitDone(false)
|
2015-05-18 05:40:32 +00:00
|
|
|
, mLastReportedNumDecodedFrames(0)
|
2016-07-25 23:36:55 +00:00
|
|
|
, mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
|
2015-10-15 00:04:00 +00:00
|
|
|
, mLayersBackendType(aLayersBackend)
|
2015-05-18 05:40:32 +00:00
|
|
|
, mInitDone(false)
|
2015-06-11 23:26:58 +00:00
|
|
|
, mTrackDemuxersMayBlock(false)
|
2015-10-20 09:33:00 +00:00
|
|
|
, mDemuxOnly(false)
|
2016-05-05 05:01:51 +00:00
|
|
|
, mSeekScheduled(false)
|
2015-10-15 00:04:00 +00:00
|
|
|
, mVideoFrameContainer(aVideoFrameContainer)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(aDemuxer);
|
|
|
|
MOZ_COUNT_CTOR(MediaFormatReader);
|
|
|
|
}
|
|
|
|
|
|
|
|
MediaFormatReader::~MediaFormatReader()
|
|
|
|
{
|
|
|
|
MOZ_COUNT_DTOR(MediaFormatReader);
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<ShutdownPromise>
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaFormatReader::Shutdown()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
|
|
|
mDemuxerInitRequest.DisconnectIfExists();
|
2016-09-10 09:56:50 +00:00
|
|
|
mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2016-09-10 06:48:53 +00:00
|
|
|
mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
mSkipRequest.DisconnectIfExists();
|
|
|
|
|
|
|
|
if (mAudio.mDecoder) {
|
2016-05-05 05:06:40 +00:00
|
|
|
Reset(TrackInfo::kAudioTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
if (mAudio.HasPromise()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2016-01-22 03:01:50 +00:00
|
|
|
mAudio.ShutdownDecoder();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
if (mAudio.mTrackDemuxer) {
|
2015-05-25 05:09:16 +00:00
|
|
|
mAudio.ResetDemuxer();
|
2015-05-18 05:40:32 +00:00
|
|
|
mAudio.mTrackDemuxer->BreakCycles();
|
|
|
|
mAudio.mTrackDemuxer = nullptr;
|
|
|
|
}
|
|
|
|
if (mAudio.mTaskQueue) {
|
|
|
|
mAudio.mTaskQueue->BeginShutdown();
|
|
|
|
mAudio.mTaskQueue->AwaitShutdownAndIdle();
|
|
|
|
mAudio.mTaskQueue = nullptr;
|
|
|
|
}
|
2016-05-05 05:11:21 +00:00
|
|
|
MOZ_ASSERT(!mAudio.HasPromise());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
if (mVideo.mDecoder) {
|
2016-05-05 05:06:40 +00:00
|
|
|
Reset(TrackInfo::kVideoTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
if (mVideo.HasPromise()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2016-01-22 03:01:50 +00:00
|
|
|
mVideo.ShutdownDecoder();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
if (mVideo.mTrackDemuxer) {
|
2015-05-25 05:09:16 +00:00
|
|
|
mVideo.ResetDemuxer();
|
2015-05-18 05:40:32 +00:00
|
|
|
mVideo.mTrackDemuxer->BreakCycles();
|
|
|
|
mVideo.mTrackDemuxer = nullptr;
|
|
|
|
}
|
|
|
|
if (mVideo.mTaskQueue) {
|
|
|
|
mVideo.mTaskQueue->BeginShutdown();
|
|
|
|
mVideo.mTaskQueue->AwaitShutdownAndIdle();
|
|
|
|
mVideo.mTaskQueue = nullptr;
|
|
|
|
}
|
2016-05-05 05:11:21 +00:00
|
|
|
MOZ_ASSERT(!mVideo.HasPromise());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
mDemuxer = nullptr;
|
|
|
|
mPlatform = nullptr;
|
2016-05-22 13:39:55 +00:00
|
|
|
mVideoFrameContainer = nullptr;
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
return MediaDecoderReader::Shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::InitLayersBackendType()
|
|
|
|
{
|
|
|
|
// Extract the layer manager backend type so that platform decoders
|
|
|
|
// can determine whether it's worthwhile using hardware accelerated
|
|
|
|
// video decoding.
|
2015-10-15 00:04:00 +00:00
|
|
|
if (!mDecoder) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaDecoderOwner* owner = mDecoder->GetOwner();
|
|
|
|
if (!owner) {
|
|
|
|
NS_WARNING("MediaFormatReader without a decoder owner, can't get HWAccel");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dom::HTMLMediaElement* element = owner->GetMediaElement();
|
|
|
|
NS_ENSURE_TRUE_VOID(element);
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<LayerManager> layerManager =
|
2015-05-18 05:40:32 +00:00
|
|
|
nsContentUtils::LayerManagerForDocument(element->OwnerDoc());
|
|
|
|
NS_ENSURE_TRUE_VOID(layerManager);
|
|
|
|
|
|
|
|
mLayersBackendType = layerManager->GetCompositorBackendType();
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2015-10-13 07:28:57 +00:00
|
|
|
MediaFormatReader::Init()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
|
|
|
|
|
|
|
|
InitLayersBackendType();
|
|
|
|
|
|
|
|
mAudio.mTaskQueue =
|
2016-05-24 08:48:26 +00:00
|
|
|
new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER));
|
2015-05-18 05:40:32 +00:00
|
|
|
mVideo.mTaskQueue =
|
2016-05-24 08:48:26 +00:00
|
|
|
new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER));
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-06-28 23:42:07 +00:00
|
|
|
// Note: GMPCrashHelper must be created on main thread, as it may use
|
|
|
|
// weak references, which aren't threadsafe.
|
|
|
|
mCrashHelper = mDecoder->GetCrashHelper();
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
2016-04-26 00:23:21 +00:00
|
|
|
class DispatchKeyNeededEvent : public Runnable {
|
2015-05-18 05:40:32 +00:00
|
|
|
public:
|
|
|
|
DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
|
|
|
|
nsTArray<uint8_t>& aInitData,
|
|
|
|
const nsString& aInitDataType)
|
|
|
|
: mDecoder(aDecoder)
|
|
|
|
, mInitData(aInitData)
|
|
|
|
, mInitDataType(aInitDataType)
|
|
|
|
{
|
|
|
|
}
|
2016-08-08 02:18:10 +00:00
|
|
|
NS_IMETHOD Run() override {
|
2015-05-18 05:40:32 +00:00
|
|
|
// Note: Null check the owner, as the decoder could have been shutdown
|
|
|
|
// since this event was dispatched.
|
|
|
|
MediaDecoderOwner* owner = mDecoder->GetOwner();
|
|
|
|
if (owner) {
|
|
|
|
owner->DispatchEncrypted(mInitData, mInitDataType);
|
|
|
|
}
|
|
|
|
mDecoder = nullptr;
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
private:
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<AbstractMediaDecoder> mDecoder;
|
2015-05-18 05:40:32 +00:00
|
|
|
nsTArray<uint8_t> mInitData;
|
|
|
|
nsString mInitDataType;
|
|
|
|
};
|
2015-09-27 10:40:03 +00:00
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::SetCDMProxy(CDMProxy* aProxy)
|
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<CDMProxy> proxy = aProxy;
|
|
|
|
RefPtr<MediaFormatReader> self = this;
|
2015-09-27 10:40:03 +00:00
|
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
|
2015-10-08 06:05:06 +00:00
|
|
|
MOZ_ASSERT(self->OnTaskQueue());
|
2015-09-27 10:40:03 +00:00
|
|
|
self->mCDMProxy = proxy;
|
|
|
|
});
|
2015-10-08 06:05:06 +00:00
|
|
|
OwnerThread()->Dispatch(r.forget());
|
2015-09-27 10:40:03 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2015-09-27 10:59:52 +00:00
|
|
|
bool
|
|
|
|
MediaFormatReader::IsWaitingOnCDMResource() {
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-09-27 10:40:03 +00:00
|
|
|
return IsEncrypted() && !mCDMProxy;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<MediaDecoderReader::MetadataPromise>
|
2015-10-21 02:59:56 +00:00
|
|
|
MediaFormatReader::AsyncReadMetadata()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
2015-10-21 01:28:04 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mMetadataPromise.IsEmpty());
|
2015-08-11 03:50:07 +00:00
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
if (mInitDone) {
|
|
|
|
// We are returning from dormant.
|
2015-10-21 01:28:04 +00:00
|
|
|
RefPtr<MetadataHolder> metadata = new MetadataHolder();
|
|
|
|
metadata->mInfo = mInfo;
|
|
|
|
metadata->mTags = nullptr;
|
|
|
|
return MetadataPromise::CreateAndResolve(metadata, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-10-21 01:28:04 +00:00
|
|
|
RefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
mDemuxerInitRequest.Begin(mDemuxer->Init()
|
2015-07-16 18:31:21 +00:00
|
|
|
->Then(OwnerThread(), __func__, this,
|
2015-05-22 19:28:20 +00:00
|
|
|
&MediaFormatReader::OnDemuxerInitDone,
|
|
|
|
&MediaFormatReader::OnDemuxerInitFailed));
|
2015-05-18 05:40:32 +00:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::OnDemuxerInitDone(nsresult)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
mDemuxerInitRequest.Complete();
|
|
|
|
|
2015-10-07 01:00:52 +00:00
|
|
|
mDemuxerInitDone = true;
|
|
|
|
|
2016-01-08 00:22:53 +00:00
|
|
|
UniquePtr<MetadataTags> tags(MakeUnique<MetadataTags>());
|
|
|
|
|
2016-05-12 02:00:21 +00:00
|
|
|
RefPtr<PDMFactory> platform;
|
|
|
|
if (!IsWaitingOnCDMResource()) {
|
|
|
|
platform = new PDMFactory();
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
// To decode, we need valid video and a place to put it.
|
|
|
|
bool videoActive = !!mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack) &&
|
2015-10-15 00:04:00 +00:00
|
|
|
GetImageContainer();
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
if (videoActive) {
|
|
|
|
// We currently only handle the first video track.
|
|
|
|
mVideo.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
|
2015-08-26 01:25:49 +00:00
|
|
|
if (!mVideo.mTrackDemuxer) {
|
2016-09-10 09:56:50 +00:00
|
|
|
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
2015-08-26 01:25:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-05-12 02:00:21 +00:00
|
|
|
|
2016-05-12 01:42:20 +00:00
|
|
|
UniquePtr<TrackInfo> videoInfo = mVideo.mTrackDemuxer->GetInfo();
|
|
|
|
videoActive = videoInfo && videoInfo->IsValid();
|
|
|
|
if (videoActive) {
|
2016-05-12 02:00:21 +00:00
|
|
|
if (platform && !platform->SupportsMimeType(videoInfo->mMimeType, nullptr)) {
|
|
|
|
// We have no decoder for this track. Error.
|
2016-09-10 09:56:50 +00:00
|
|
|
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
2016-05-12 02:00:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-05-12 01:42:20 +00:00
|
|
|
mInfo.mVideo = *videoInfo->GetAsVideoInfo();
|
|
|
|
for (const MetadataTag& tag : videoInfo->mTags) {
|
|
|
|
tags->Put(tag.mKey, tag.mValue);
|
|
|
|
}
|
|
|
|
mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
|
|
|
|
mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
|
|
|
|
mTrackDemuxersMayBlock |= mVideo.mTrackDemuxer->GetSamplesMayBlock();
|
|
|
|
} else {
|
|
|
|
mVideo.mTrackDemuxer->BreakCycles();
|
|
|
|
mVideo.mTrackDemuxer = nullptr;
|
2016-01-08 00:22:53 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
|
|
|
|
if (audioActive) {
|
|
|
|
mAudio.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
|
2015-08-26 01:25:49 +00:00
|
|
|
if (!mAudio.mTrackDemuxer) {
|
2016-09-10 09:56:50 +00:00
|
|
|
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
2015-08-26 01:25:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-05-12 02:00:21 +00:00
|
|
|
|
2016-04-27 07:35:58 +00:00
|
|
|
UniquePtr<TrackInfo> audioInfo = mAudio.mTrackDemuxer->GetInfo();
|
|
|
|
// We actively ignore audio tracks that we know we can't play.
|
2016-05-12 02:00:21 +00:00
|
|
|
audioActive = audioInfo && audioInfo->IsValid() &&
|
|
|
|
(!platform ||
|
|
|
|
platform->SupportsMimeType(audioInfo->mMimeType, nullptr));
|
|
|
|
|
2016-04-27 07:35:58 +00:00
|
|
|
if (audioActive) {
|
|
|
|
mInfo.mAudio = *audioInfo->GetAsAudioInfo();
|
|
|
|
for (const MetadataTag& tag : audioInfo->mTags) {
|
|
|
|
tags->Put(tag.mKey, tag.mValue);
|
|
|
|
}
|
|
|
|
mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
|
|
|
|
mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
|
|
|
|
mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
|
|
|
|
} else {
|
|
|
|
mAudio.mTrackDemuxer->BreakCycles();
|
|
|
|
mAudio.mTrackDemuxer = nullptr;
|
2016-01-08 00:22:53 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
|
2015-10-15 00:04:00 +00:00
|
|
|
if (mDecoder && crypto && crypto->IsEncrypted()) {
|
2015-05-18 05:40:32 +00:00
|
|
|
// Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
|
|
|
|
for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
|
|
|
|
NS_DispatchToMainThread(
|
2016-06-08 02:07:09 +00:00
|
|
|
new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData, crypto->mInitDatas[i].mType));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
mInfo.mCrypto = *crypto;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t videoDuration = HasVideo() ? mInfo.mVideo.mDuration : 0;
|
|
|
|
int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
|
|
|
|
|
|
|
|
int64_t duration = std::max(videoDuration, audioDuration);
|
|
|
|
if (duration != -1) {
|
2015-05-31 04:18:12 +00:00
|
|
|
mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-12-02 07:42:32 +00:00
|
|
|
mInfo.mMediaSeekable = mDemuxer->IsSeekable();
|
2016-02-04 04:31:21 +00:00
|
|
|
mInfo.mMediaSeekableOnlyInBufferedRanges =
|
|
|
|
mDemuxer->IsSeekableOnlyInBufferedRanges();
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2015-07-14 14:25:44 +00:00
|
|
|
if (!videoActive && !audioActive) {
|
2016-09-10 09:56:50 +00:00
|
|
|
mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
|
2015-07-14 14:25:44 +00:00
|
|
|
return;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-10-21 01:28:04 +00:00
|
|
|
mInitDone = true;
|
|
|
|
RefPtr<MetadataHolder> metadata = new MetadataHolder();
|
|
|
|
metadata->mInfo = mInfo;
|
2016-02-12 01:40:19 +00:00
|
|
|
metadata->mTags = tags->Count() ? tags.release() : nullptr;
|
2015-10-21 01:28:04 +00:00
|
|
|
mMetadataPromise.Resolve(metadata, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-09-28 00:34:57 +00:00
|
|
|
bool
|
|
|
|
MediaFormatReader::IsEncrypted() const
|
|
|
|
{
|
|
|
|
return (HasAudio() && mInfo.mAudio.mCrypto.mValid) ||
|
|
|
|
(HasVideo() && mInfo.mVideo.mCrypto.mValid);
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
2016-09-12 02:22:20 +00:00
|
|
|
MediaFormatReader::OnDemuxerInitFailed(const MediaResult& aError)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
mDemuxerInitRequest.Complete();
|
2016-09-12 02:22:20 +00:00
|
|
|
mMetadataPromise.Reject(aError, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-09-13 03:07:26 +00:00
|
|
|
MediaResult
|
2015-11-21 11:18:45 +00:00
|
|
|
MediaFormatReader::EnsureDecoderCreated(TrackType aTrack)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-08-16 01:38:29 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsSuspended());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2015-11-21 11:18:45 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
if (decoder.mDecoder) {
|
2016-09-13 03:07:26 +00:00
|
|
|
return NS_OK;
|
2015-11-21 11:18:45 +00:00
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
if (!mPlatform) {
|
2015-10-06 08:56:29 +00:00
|
|
|
mPlatform = new PDMFactory();
|
2015-05-18 05:40:32 +00:00
|
|
|
if (IsEncrypted()) {
|
2015-09-27 10:59:50 +00:00
|
|
|
MOZ_ASSERT(mCDMProxy);
|
2015-10-06 08:56:29 +00:00
|
|
|
mPlatform->SetCDMProxy(mCDMProxy);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-21 11:18:45 +00:00
|
|
|
decoder.mDecoderInitialized = false;
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-01-22 03:01:50 +00:00
|
|
|
MonitorAutoLock mon(decoder.mMonitor);
|
|
|
|
|
2015-11-21 11:18:45 +00:00
|
|
|
switch (aTrack) {
|
2016-06-28 05:56:55 +00:00
|
|
|
case TrackType::kAudioTrack: {
|
|
|
|
decoder.mDecoder = mPlatform->CreateDecoder({
|
|
|
|
decoder.mInfo ? *decoder.mInfo->GetAsAudioInfo() : mInfo.mAudio,
|
|
|
|
decoder.mTaskQueue,
|
2016-06-28 23:42:07 +00:00
|
|
|
decoder.mCallback.get(),
|
2016-07-29 06:51:18 +00:00
|
|
|
mCrashHelper,
|
|
|
|
decoder.mIsBlankDecode
|
2016-06-28 05:56:55 +00:00
|
|
|
});
|
2015-11-21 11:18:45 +00:00
|
|
|
break;
|
2016-06-28 05:56:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case TrackType::kVideoTrack: {
|
2015-11-21 11:18:45 +00:00
|
|
|
// Decoders use the layers backend to decide if they can use hardware decoding,
|
|
|
|
// so specify LAYERS_NONE if we want to forcibly disable it.
|
2016-06-28 05:56:55 +00:00
|
|
|
decoder.mDecoder = mPlatform->CreateDecoder({
|
|
|
|
mVideo.mInfo ? *mVideo.mInfo->GetAsVideoInfo() : mInfo.mVideo,
|
|
|
|
decoder.mTaskQueue,
|
|
|
|
decoder.mCallback.get(),
|
|
|
|
mLayersBackendType,
|
|
|
|
GetImageContainer(),
|
2016-07-29 06:51:18 +00:00
|
|
|
mCrashHelper,
|
|
|
|
decoder.mIsBlankDecode
|
2016-06-28 05:56:55 +00:00
|
|
|
});
|
2015-11-21 11:18:45 +00:00
|
|
|
break;
|
2016-06-28 05:56:55 +00:00
|
|
|
}
|
2015-11-21 11:18:45 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2016-01-22 03:01:50 +00:00
|
|
|
if (decoder.mDecoder ) {
|
|
|
|
decoder.mDescription = decoder.mDecoder->GetDescriptionName();
|
2016-09-13 03:07:26 +00:00
|
|
|
return NS_OK;
|
2016-01-22 03:01:50 +00:00
|
|
|
}
|
2016-09-13 03:07:26 +00:00
|
|
|
decoder.mDescription = "error creating decoder";
|
|
|
|
return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "error creating decoder");
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-09-30 04:35:11 +00:00
|
|
|
bool
|
|
|
|
MediaFormatReader::EnsureDecoderInitialized(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-08-16 01:38:29 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsSuspended());
|
2016-08-05 09:25:49 +00:00
|
|
|
|
2015-09-30 04:35:11 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
if (!decoder.mDecoder || decoder.mInitPromise.Exists()) {
|
|
|
|
MOZ_ASSERT(decoder.mDecoder);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (decoder.mDecoderInitialized) {
|
|
|
|
return true;
|
|
|
|
}
|
2016-07-26 15:38:42 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<MediaFormatReader> self = this;
|
2015-09-30 04:35:11 +00:00
|
|
|
decoder.mInitPromise.Begin(decoder.mDecoder->Init()
|
|
|
|
->Then(OwnerThread(), __func__,
|
|
|
|
[self] (TrackType aTrack) {
|
2016-08-16 01:38:29 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!self->IsSuspended());
|
2015-09-30 04:35:11 +00:00
|
|
|
auto& decoder = self->GetDecoderData(aTrack);
|
2016-08-16 01:38:29 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(decoder.mDecoder);
|
2015-09-30 04:35:11 +00:00
|
|
|
decoder.mInitPromise.Complete();
|
|
|
|
decoder.mDecoderInitialized = true;
|
2016-01-22 03:01:50 +00:00
|
|
|
MonitorAutoLock mon(decoder.mMonitor);
|
|
|
|
decoder.mDescription = decoder.mDecoder->GetDescriptionName();
|
2016-06-08 02:59:57 +00:00
|
|
|
self->SetVideoDecodeThreshold();
|
2015-09-30 04:35:11 +00:00
|
|
|
self->ScheduleUpdate(aTrack);
|
|
|
|
},
|
2016-09-13 03:06:18 +00:00
|
|
|
[self, aTrack] (MediaResult aError) {
|
2015-09-30 04:35:11 +00:00
|
|
|
auto& decoder = self->GetDecoderData(aTrack);
|
|
|
|
decoder.mInitPromise.Complete();
|
2016-01-22 03:01:50 +00:00
|
|
|
decoder.ShutdownDecoder();
|
2016-09-13 03:06:18 +00:00
|
|
|
self->NotifyError(aTrack, aError);
|
2015-09-30 04:35:11 +00:00
|
|
|
}));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::ReadUpdatedMetadata(MediaInfo* aInfo)
|
|
|
|
{
|
|
|
|
*aInfo = mInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
MediaFormatReader::DecoderData&
|
|
|
|
MediaFormatReader::GetDecoderData(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
|
|
|
|
aTrack == TrackInfo::kVideoTrack);
|
|
|
|
if (aTrack == TrackInfo::kAudioTrack) {
|
|
|
|
return mAudio;
|
|
|
|
}
|
|
|
|
return mVideo;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
MediaFormatReader::ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(HasVideo());
|
|
|
|
media::TimeUnit nextKeyframe;
|
|
|
|
nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return aSkipToNextKeyframe;
|
|
|
|
}
|
2016-05-06 05:54:49 +00:00
|
|
|
return (nextKeyframe < aTimeThreshold ||
|
|
|
|
(mVideo.mTimeThreshold &&
|
2016-05-18 03:48:05 +00:00
|
|
|
mVideo.mTimeThreshold.ref().EndTime() < aTimeThreshold)) &&
|
2016-05-17 14:26:34 +00:00
|
|
|
nextKeyframe.ToMicroseconds() >= 0 && !nextKeyframe.IsInfinite();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-04-22 06:16:49 +00:00
|
|
|
RefPtr<MediaDecoderReader::MediaDataPromise>
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
|
2015-10-20 09:33:00 +00:00
|
|
|
int64_t aTimeThreshold)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
|
2015-07-15 04:43:35 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
|
|
|
|
mVideo.mTimeThreshold.isSome());
|
2015-05-18 05:40:32 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
|
|
|
|
LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
|
|
|
|
|
|
|
|
if (!HasVideo()) {
|
|
|
|
LOG("called with no video track");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (IsSeeking()) {
|
|
|
|
LOG("called mid-seek. Rejecting.");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mShutdown) {
|
|
|
|
NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-06-08 12:28:24 +00:00
|
|
|
if (IsSuspended()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2016-06-08 12:28:24 +00:00
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
|
2016-05-05 04:52:58 +00:00
|
|
|
// Ensure we have no pending seek going as ShouldSkip could return out of date
|
|
|
|
// information.
|
|
|
|
if (!mVideo.HasInternalSeekPending() &&
|
|
|
|
ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
|
2016-05-05 05:06:40 +00:00
|
|
|
RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
SkipVideoDemuxToNextKeyFrame(timeThreshold);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2016-05-05 05:11:21 +00:00
|
|
|
RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(TrackInfo::kVideoTrack);
|
2015-08-24 02:50:27 +00:00
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-12 02:22:20 +00:00
|
|
|
MediaFormatReader::OnDemuxFailed(TrackType aTrack, const MediaResult& aError)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-09-12 02:22:20 +00:00
|
|
|
LOG("Failed to demux %s, failure:%u",
|
|
|
|
aTrack == TrackType::kVideoTrack ? "video" : "audio", aError.Code());
|
2015-05-18 05:40:32 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
decoder.mDemuxRequest.Complete();
|
2016-09-12 02:22:20 +00:00
|
|
|
switch (aError.Code()) {
|
|
|
|
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
|
2016-05-23 06:09:12 +00:00
|
|
|
if (!decoder.mWaitingForData) {
|
|
|
|
decoder.mNeedDraining = true;
|
|
|
|
}
|
2015-06-15 00:58:12 +00:00
|
|
|
NotifyEndOfStream(aTrack);
|
2015-05-18 05:40:32 +00:00
|
|
|
break;
|
2016-09-12 02:22:20 +00:00
|
|
|
case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
|
2015-12-08 19:30:25 +00:00
|
|
|
if (!decoder.mWaitingForData) {
|
|
|
|
decoder.mNeedDraining = true;
|
|
|
|
}
|
2015-05-18 05:42:01 +00:00
|
|
|
NotifyWaitingForData(aTrack);
|
2015-05-18 05:40:32 +00:00
|
|
|
break;
|
2016-09-12 02:22:20 +00:00
|
|
|
case NS_ERROR_DOM_MEDIA_CANCELED:
|
2015-05-18 05:40:32 +00:00
|
|
|
if (decoder.HasPromise()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2016-09-12 02:22:20 +00:00
|
|
|
NotifyError(aTrack, aError);
|
2015-05-18 05:40:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::DoDemuxVideo()
|
|
|
|
{
|
|
|
|
mVideo.mDemuxRequest.Begin(mVideo.mTrackDemuxer->GetSamples(1)
|
2015-07-16 18:31:21 +00:00
|
|
|
->Then(OwnerThread(), __func__, this,
|
2015-05-22 19:28:20 +00:00
|
|
|
&MediaFormatReader::OnVideoDemuxCompleted,
|
|
|
|
&MediaFormatReader::OnVideoDemuxFailed));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-10-18 05:24:48 +00:00
|
|
|
MediaFormatReader::OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2015-06-15 00:58:12 +00:00
|
|
|
LOGV("%d video samples demuxed (sid:%d)",
|
|
|
|
aSamples->mSamples.Length(),
|
|
|
|
aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
|
2015-05-18 05:40:32 +00:00
|
|
|
mVideo.mDemuxRequest.Complete();
|
|
|
|
mVideo.mQueuedSamples.AppendElements(aSamples->mSamples);
|
|
|
|
ScheduleUpdate(TrackInfo::kVideoTrack);
|
|
|
|
}
|
|
|
|
|
2016-04-22 06:16:49 +00:00
|
|
|
RefPtr<MediaDecoderReader::MediaDataPromise>
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaFormatReader::RequestAudioData()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise(), "No duplicate sample requests");
|
2016-05-16 08:15:45 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(IsVideoSeeking() || mSeekPromise.IsEmpty(),
|
|
|
|
"No sample requests allowed while seeking");
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(IsVideoSeeking() ||
|
|
|
|
!mAudio.mSeekRequest.Exists() ||
|
|
|
|
mAudio.mTimeThreshold.isSome());
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(IsVideoSeeking() || !IsSeeking(), "called mid-seek");
|
2015-05-18 05:40:32 +00:00
|
|
|
LOGV("");
|
|
|
|
|
|
|
|
if (!HasAudio()) {
|
|
|
|
LOG("called with no audio track");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-06-08 12:28:24 +00:00
|
|
|
if (IsSuspended()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2016-06-08 12:28:24 +00:00
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
if (IsSeeking()) {
|
|
|
|
LOG("called mid-seek. Rejecting.");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mShutdown) {
|
|
|
|
NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
|
2016-09-10 06:48:53 +00:00
|
|
|
return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-05-05 05:11:21 +00:00
|
|
|
RefPtr<MediaDataPromise> p = mAudio.EnsurePromise(__func__);
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(TrackInfo::kAudioTrack);
|
2015-08-11 03:50:07 +00:00
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::DoDemuxAudio()
|
|
|
|
{
|
|
|
|
mAudio.mDemuxRequest.Begin(mAudio.mTrackDemuxer->GetSamples(1)
|
2015-07-16 18:31:21 +00:00
|
|
|
->Then(OwnerThread(), __func__, this,
|
2015-05-22 19:28:20 +00:00
|
|
|
&MediaFormatReader::OnAudioDemuxCompleted,
|
|
|
|
&MediaFormatReader::OnAudioDemuxFailed));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-10-18 05:24:48 +00:00
|
|
|
MediaFormatReader::OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2015-06-15 00:58:12 +00:00
|
|
|
LOGV("%d audio samples demuxed (sid:%d)",
|
|
|
|
aSamples->mSamples.Length(),
|
|
|
|
aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
|
2015-05-18 05:40:32 +00:00
|
|
|
mAudio.mDemuxRequest.Complete();
|
|
|
|
mAudio.mQueuedSamples.AppendElements(aSamples->mSamples);
|
|
|
|
ScheduleUpdate(TrackInfo::kAudioTrack);
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:42:01 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::NotifyNewOutput(TrackType aTrack, MediaData* aSample)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-08-12 11:24:48 +00:00
|
|
|
LOGV("Received new %s sample time:%lld duration:%lld",
|
|
|
|
TrackTypeToStr(aTrack), aSample->mTime, aSample->mDuration);
|
2015-05-18 05:42:01 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
if (!decoder.mOutputRequested) {
|
|
|
|
LOG("MediaFormatReader produced output while flushing, discarding.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
decoder.mOutput.AppendElement(aSample);
|
|
|
|
decoder.mNumSamplesOutput++;
|
2016-05-30 16:24:00 +00:00
|
|
|
decoder.mNumOfConsecutiveError = 0;
|
2015-05-18 05:42:01 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::NotifyInputExhausted(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Decoder has requested more %s data", TrackTypeToStr(aTrack));
|
2015-05-18 05:42:01 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2016-09-01 09:25:54 +00:00
|
|
|
decoder.mDecodePending = false;
|
2015-05-18 05:42:01 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::NotifyDrainComplete(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2015-07-26 11:52:22 +00:00
|
|
|
LOG("%s", TrackTypeToStr(aTrack));
|
2015-05-18 05:42:01 +00:00
|
|
|
if (!decoder.mOutputRequested) {
|
|
|
|
LOG("MediaFormatReader called DrainComplete() before flushing, ignoring.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
decoder.mDrainComplete = true;
|
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-09 23:56:53 +00:00
|
|
|
MediaFormatReader::NotifyError(TrackType aTrack, const MediaResult& aError)
|
2015-05-18 05:42:01 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-09-20 05:59:42 +00:00
|
|
|
NS_WARNING(aError.Description().get());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("%s Decoding error", TrackTypeToStr(aTrack));
|
2015-05-18 05:42:01 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2016-05-30 16:24:00 +00:00
|
|
|
decoder.mError = decoder.HasFatalError() ? decoder.mError : Some(aError);
|
2015-05-18 05:42:01 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::NotifyWaitingForData(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
decoder.mWaitingForData = true;
|
2015-12-08 19:30:25 +00:00
|
|
|
if (decoder.mTimeThreshold) {
|
|
|
|
decoder.mTimeThreshold.ref().mWaiting = true;
|
|
|
|
}
|
2015-05-18 05:42:01 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
2015-06-15 00:58:12 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
decoder.mDemuxEOS = true;
|
|
|
|
ScheduleUpdate(aTrack);
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
bool
|
|
|
|
MediaFormatReader::NeedInput(DecoderData& aDecoder)
|
|
|
|
{
|
2016-09-01 09:25:54 +00:00
|
|
|
// To account for H.264 streams which may require a longer
|
|
|
|
// run of input than we input, decoders fire an "input exhausted" callback.
|
|
|
|
// The decoder will not be fed a new raw sample until InputExhausted
|
|
|
|
// has been called.
|
2015-05-18 05:40:32 +00:00
|
|
|
return
|
2016-09-01 09:25:54 +00:00
|
|
|
(aDecoder.HasPromise() || aDecoder.mTimeThreshold.isSome()) &&
|
2016-06-02 11:08:05 +00:00
|
|
|
!aDecoder.HasPendingDrain() &&
|
2016-05-30 16:24:00 +00:00
|
|
|
!aDecoder.HasFatalError() &&
|
2015-05-25 05:09:16 +00:00
|
|
|
!aDecoder.mDemuxRequest.Exists() &&
|
2016-09-01 09:25:54 +00:00
|
|
|
!aDecoder.mOutput.Length() &&
|
2016-05-05 04:52:58 +00:00
|
|
|
!aDecoder.HasInternalSeekPending() &&
|
2016-09-01 09:25:54 +00:00
|
|
|
!aDecoder.mDecodePending;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::ScheduleUpdate(TrackType aTrack)
|
|
|
|
{
|
2015-05-18 05:42:01 +00:00
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
if (mShutdown) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
if (decoder.mUpdateScheduled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
LOGV("SchedulingUpdate(%s)", TrackTypeToStr(aTrack));
|
|
|
|
decoder.mUpdateScheduled = true;
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsIRunnable> task(
|
2016-05-05 08:45:00 +00:00
|
|
|
NewRunnableMethod<TrackType>(this, &MediaFormatReader::Update, aTrack));
|
2015-07-16 18:31:21 +00:00
|
|
|
OwnerThread()->Dispatch(task.forget());
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
MediaFormatReader::UpdateReceivedNewData(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
if (!decoder.mReceivedNewData) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-08 19:30:25 +00:00
|
|
|
|
2015-06-17 21:22:10 +00:00
|
|
|
// Update our cached TimeRange.
|
|
|
|
decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
|
2015-12-08 19:30:25 +00:00
|
|
|
|
2016-05-05 04:52:58 +00:00
|
|
|
// We do not want to clear mWaitingForData while there are pending
|
|
|
|
// demuxing or seeking operations that could affect the value of this flag.
|
|
|
|
// This is in order to ensure that we will retry once they complete as we may
|
|
|
|
// now have new data that could potentially allow those operations to
|
|
|
|
// successfully complete if tried again.
|
|
|
|
if (decoder.mSeekRequest.Exists()) {
|
|
|
|
// Nothing more to do until this operation complete.
|
|
|
|
return true;
|
|
|
|
}
|
2016-05-21 15:02:43 +00:00
|
|
|
|
|
|
|
if (aTrack == TrackType::kVideoTrack && mSkipRequest.Exists()) {
|
|
|
|
LOGV("Skipping in progress, nothing more to do");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-05 04:52:58 +00:00
|
|
|
if (decoder.mDemuxRequest.Exists()) {
|
|
|
|
// We may have pending operations to process, so we want to continue
|
|
|
|
// after UpdateReceivedNewData returns.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-06-02 11:08:05 +00:00
|
|
|
if (decoder.HasPendingDrain()) {
|
2015-12-08 19:30:25 +00:00
|
|
|
// We do not want to clear mWaitingForData or mDemuxEOS while
|
|
|
|
// a drain is in progress in order to properly complete the operation.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hasLastEnd;
|
|
|
|
media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
|
|
|
|
if (hasLastEnd) {
|
2016-02-08 13:35:20 +00:00
|
|
|
if (decoder.mLastTimeRangesEnd && decoder.mLastTimeRangesEnd.ref() < lastEnd) {
|
2015-12-08 19:30:25 +00:00
|
|
|
// New data was added after our previous end, we can clear the EOS flag.
|
|
|
|
decoder.mDemuxEOS = false;
|
|
|
|
}
|
|
|
|
decoder.mLastTimeRangesEnd = Some(lastEnd);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-12-08 19:30:25 +00:00
|
|
|
decoder.mReceivedNewData = false;
|
|
|
|
if (decoder.mTimeThreshold) {
|
|
|
|
decoder.mTimeThreshold.ref().mWaiting = false;
|
|
|
|
}
|
|
|
|
decoder.mWaitingForData = false;
|
|
|
|
|
2016-05-30 16:24:00 +00:00
|
|
|
if (decoder.HasFatalError()) {
|
2015-05-25 05:09:16 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-05-05 04:52:58 +00:00
|
|
|
|
2016-05-16 08:15:45 +00:00
|
|
|
if (!mSeekPromise.IsEmpty() &&
|
|
|
|
(!IsVideoSeeking() || aTrack == TrackInfo::kVideoTrack)) {
|
2016-05-05 05:01:51 +00:00
|
|
|
MOZ_ASSERT(!decoder.HasPromise());
|
2016-05-16 08:15:45 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT((IsVideoSeeking() || !mAudio.mTimeThreshold) &&
|
|
|
|
!mVideo.mTimeThreshold,
|
2016-05-05 05:01:51 +00:00
|
|
|
"InternalSeek must have been aborted when Seek was first called");
|
2016-05-16 08:15:45 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT((IsVideoSeeking() || !mAudio.HasWaitingPromise()) &&
|
|
|
|
!mVideo.HasWaitingPromise(),
|
2016-05-05 05:01:51 +00:00
|
|
|
"Waiting promises must have been rejected when Seek was first called");
|
2016-05-16 08:15:45 +00:00
|
|
|
if (mVideo.mSeekRequest.Exists() ||
|
|
|
|
(!IsVideoSeeking() && mAudio.mSeekRequest.Exists())) {
|
2016-05-05 05:01:51 +00:00
|
|
|
// Already waiting for a seek to complete. Nothing more to do.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
LOG("Attempting Seek");
|
|
|
|
ScheduleSeek();
|
|
|
|
return true;
|
|
|
|
}
|
2016-05-05 04:52:58 +00:00
|
|
|
if (decoder.HasInternalSeekPending() || decoder.HasWaitingPromise()) {
|
|
|
|
if (decoder.HasInternalSeekPending()) {
|
|
|
|
LOG("Attempting Internal Seek");
|
|
|
|
InternalSeek(aTrack, decoder.mTimeThreshold.ref());
|
|
|
|
}
|
|
|
|
if (decoder.HasWaitingPromise()) {
|
|
|
|
MOZ_ASSERT(!decoder.HasPromise());
|
|
|
|
LOG("We have new data. Resolving WaitingPromise");
|
|
|
|
decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::RequestDemuxSamples(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2015-05-25 05:09:16 +00:00
|
|
|
MOZ_ASSERT(!decoder.mDemuxRequest.Exists());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
if (!decoder.mQueuedSamples.IsEmpty()) {
|
|
|
|
// No need to demux new samples.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (decoder.mDemuxEOS) {
|
|
|
|
// Nothing left to demux.
|
2015-12-08 19:30:25 +00:00
|
|
|
// We do not want to attempt to demux while in waiting for data mode
|
|
|
|
// as it would retrigger an unecessary drain.
|
2015-05-18 05:40:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-05-25 05:09:16 +00:00
|
|
|
|
|
|
|
LOGV("Requesting extra demux %s", TrackTypeToStr(aTrack));
|
2015-05-18 05:40:32 +00:00
|
|
|
if (aTrack == TrackInfo::kVideoTrack) {
|
|
|
|
DoDemuxVideo();
|
|
|
|
} else {
|
|
|
|
DoDemuxAudio();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-09 03:50:37 +00:00
|
|
|
void
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
2015-10-20 09:33:00 +00:00
|
|
|
MediaRawData* aSample)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2016-09-09 03:50:37 +00:00
|
|
|
decoder.mDecoder->Input(aSample);
|
2016-09-01 09:25:54 +00:00
|
|
|
decoder.mDecodePending = true;
|
2015-10-20 09:33:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::HandleDemuxedSamples(TrackType aTrack,
|
2015-05-18 05:40:32 +00:00
|
|
|
AbstractMediaDecoder::AutoNotifyDecoded& aA)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-06-08 12:28:24 +00:00
|
|
|
|
2016-08-05 09:25:49 +00:00
|
|
|
// Don't try to create or initialize decoders
|
|
|
|
// (which might allocate hardware resources) when suspended.
|
|
|
|
if (IsSuspended()) {
|
|
|
|
// Should've deleted decoders when suspended.
|
2016-08-16 01:38:29 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mAudio.mDecoder && !mVideo.mDecoder);
|
2016-08-05 09:25:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
if (decoder.mQueuedSamples.IsEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-15 00:58:12 +00:00
|
|
|
|
2016-09-13 03:07:26 +00:00
|
|
|
MediaResult rv = EnsureDecoderCreated(aTrack);
|
|
|
|
if (NS_FAILED(rv)) {
|
2015-08-11 03:50:07 +00:00
|
|
|
NS_WARNING("Error constructing decoders");
|
2016-09-13 03:07:26 +00:00
|
|
|
NotifyError(aTrack, rv);
|
2015-08-11 03:50:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-30 04:35:11 +00:00
|
|
|
if (!EnsureDecoderInitialized(aTrack)) {
|
2015-08-11 03:50:07 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-16 10:15:17 +00:00
|
|
|
if (!ForceZeroStartTime() && decoder.mFirstDemuxedSampleTime.isNothing()) {
|
|
|
|
decoder.mFirstDemuxedSampleTime.emplace(
|
|
|
|
media::TimeUnit::FromMicroseconds(decoder.mQueuedSamples[0]->mTime));
|
|
|
|
}
|
|
|
|
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Giving %s input to decoder", TrackTypeToStr(aTrack));
|
2015-05-18 05:42:01 +00:00
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
// Decode all our demuxed frames.
|
2015-06-15 00:58:12 +00:00
|
|
|
bool samplesPending = false;
|
|
|
|
while (decoder.mQueuedSamples.Length()) {
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<MediaRawData> sample = decoder.mQueuedSamples[0];
|
|
|
|
RefPtr<SharedTrackInfo> info = sample->mTrackInfo;
|
2015-06-15 00:58:12 +00:00
|
|
|
|
|
|
|
if (info && decoder.mLastStreamSourceID != info->GetID()) {
|
|
|
|
if (samplesPending) {
|
|
|
|
// Let existing samples complete their decoding. We'll resume later.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-13 06:25:04 +00:00
|
|
|
if (decoder.mNextStreamSourceID.isNothing() ||
|
|
|
|
decoder.mNextStreamSourceID.ref() != info->GetID()) {
|
|
|
|
LOG("%s stream id has changed from:%d to:%d, draining decoder.",
|
|
|
|
TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
|
|
|
|
info->GetID());
|
|
|
|
decoder.mNeedDraining = true;
|
|
|
|
decoder.mNextStreamSourceID = Some(info->GetID());
|
2015-08-12 11:24:48 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
2015-07-13 06:25:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-06-15 00:58:12 +00:00
|
|
|
LOG("%s stream id has changed from:%d to:%d, recreating decoder.",
|
|
|
|
TrackTypeToStr(aTrack), decoder.mLastStreamSourceID,
|
|
|
|
info->GetID());
|
|
|
|
decoder.mInfo = info;
|
|
|
|
decoder.mLastStreamSourceID = info->GetID();
|
2015-12-08 19:30:25 +00:00
|
|
|
decoder.mNextStreamSourceID.reset();
|
2016-05-05 05:06:40 +00:00
|
|
|
// Reset will clear our array of queued samples. So make a copy now.
|
2015-10-18 05:24:48 +00:00
|
|
|
nsTArray<RefPtr<MediaRawData>> samples{decoder.mQueuedSamples};
|
2016-05-05 05:06:40 +00:00
|
|
|
Reset(aTrack);
|
2016-01-22 03:01:50 +00:00
|
|
|
decoder.ShutdownDecoder();
|
2015-06-15 00:58:12 +00:00
|
|
|
if (sample->mKeyframe) {
|
2015-08-11 15:29:46 +00:00
|
|
|
decoder.mQueuedSamples.AppendElements(Move(samples));
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(aTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
} else {
|
2016-05-18 03:48:05 +00:00
|
|
|
TimeInterval time =
|
|
|
|
TimeInterval(TimeUnit::FromMicroseconds(sample->mTime),
|
|
|
|
TimeUnit::FromMicroseconds(sample->GetEndTime()));
|
2016-02-01 04:05:25 +00:00
|
|
|
InternalSeekTarget seekTarget =
|
2016-05-18 03:48:05 +00:00
|
|
|
decoder.mTimeThreshold.refOr(InternalSeekTarget(time, false));
|
2015-06-15 00:58:12 +00:00
|
|
|
LOG("Stream change occurred on a non-keyframe. Seeking to:%lld",
|
2016-05-18 03:48:05 +00:00
|
|
|
sample->mTime);
|
2015-12-08 19:30:25 +00:00
|
|
|
InternalSeek(aTrack, seekTarget);
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2015-08-11 03:50:07 +00:00
|
|
|
return;
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LOGV("Input:%lld (dts:%lld kf:%d)",
|
|
|
|
sample->mTime, sample->mTimecode, sample->mKeyframe);
|
|
|
|
decoder.mOutputRequested = true;
|
|
|
|
decoder.mNumSamplesInput++;
|
|
|
|
decoder.mSizeOfQueue++;
|
|
|
|
if (aTrack == TrackInfo::kVideoTrack) {
|
2016-07-18 00:41:40 +00:00
|
|
|
aA.mStats.mParsedFrames++;
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2015-10-20 09:33:00 +00:00
|
|
|
|
|
|
|
if (mDemuxOnly) {
|
|
|
|
ReturnOutput(sample, aTrack);
|
2016-09-09 03:50:37 +00:00
|
|
|
} else {
|
|
|
|
DecodeDemuxedSamples(aTrack, sample);
|
2015-08-16 01:18:11 +00:00
|
|
|
}
|
2015-10-20 09:33:00 +00:00
|
|
|
|
2015-06-15 00:58:12 +00:00
|
|
|
decoder.mQueuedSamples.RemoveElementAt(0);
|
2015-10-20 09:33:00 +00:00
|
|
|
if (mDemuxOnly) {
|
|
|
|
// If demuxed-only case, ReturnOutput will resolve with one demuxed data.
|
|
|
|
// Then we should stop doing the iteration.
|
|
|
|
return;
|
|
|
|
}
|
2015-06-15 00:58:12 +00:00
|
|
|
samplesPending = true;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-08 19:30:25 +00:00
|
|
|
void
|
2016-02-01 04:05:25 +00:00
|
|
|
MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget)
|
2015-12-08 19:30:25 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-05-05 04:52:58 +00:00
|
|
|
LOG("%s internal seek to %f",
|
2016-05-18 03:48:05 +00:00
|
|
|
TrackTypeToStr(aTrack), aTarget.Time().ToSeconds());
|
2016-05-05 04:52:58 +00:00
|
|
|
|
2015-12-08 19:30:25 +00:00
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2016-05-05 05:06:40 +00:00
|
|
|
decoder.Flush();
|
2015-12-08 19:30:25 +00:00
|
|
|
decoder.ResetDemuxer();
|
2016-05-05 04:52:58 +00:00
|
|
|
decoder.mTimeThreshold = Some(aTarget);
|
|
|
|
RefPtr<MediaFormatReader> self = this;
|
2016-05-18 03:48:05 +00:00
|
|
|
decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().Time())
|
2015-12-08 19:30:25 +00:00
|
|
|
->Then(OwnerThread(), __func__,
|
|
|
|
[self, aTrack] (media::TimeUnit aTime) {
|
|
|
|
auto& decoder = self->GetDecoderData(aTrack);
|
|
|
|
decoder.mSeekRequest.Complete();
|
2016-05-05 04:52:58 +00:00
|
|
|
MOZ_ASSERT(decoder.mTimeThreshold,
|
|
|
|
"Seek promise must be disconnected when timethreshold is reset");
|
|
|
|
decoder.mTimeThreshold.ref().mHasSeeked = true;
|
2016-06-08 02:59:57 +00:00
|
|
|
self->SetVideoDecodeThreshold();
|
2016-09-01 09:25:54 +00:00
|
|
|
self->ScheduleUpdate(aTrack);
|
2015-12-08 19:30:25 +00:00
|
|
|
},
|
2016-09-12 02:22:20 +00:00
|
|
|
[self, aTrack] (const MediaResult& aError) {
|
2015-12-08 19:30:25 +00:00
|
|
|
auto& decoder = self->GetDecoderData(aTrack);
|
|
|
|
decoder.mSeekRequest.Complete();
|
2016-09-12 02:22:20 +00:00
|
|
|
switch (aError.Code()) {
|
|
|
|
case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
|
2015-12-08 19:30:25 +00:00
|
|
|
self->NotifyWaitingForData(aTrack);
|
|
|
|
break;
|
2016-09-12 02:22:20 +00:00
|
|
|
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
|
2016-05-05 04:52:58 +00:00
|
|
|
decoder.mTimeThreshold.reset();
|
2015-12-08 19:30:25 +00:00
|
|
|
self->NotifyEndOfStream(aTrack);
|
|
|
|
break;
|
2016-09-12 02:22:20 +00:00
|
|
|
case NS_ERROR_DOM_MEDIA_CANCELED:
|
2016-05-05 04:52:58 +00:00
|
|
|
decoder.mTimeThreshold.reset();
|
2015-12-08 19:30:25 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-05-05 04:52:58 +00:00
|
|
|
decoder.mTimeThreshold.reset();
|
2016-09-12 02:22:20 +00:00
|
|
|
self->NotifyError(aTrack, aError);
|
2015-12-08 19:30:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2015-07-13 06:21:04 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::DrainDecoder(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2015-07-15 04:43:35 +00:00
|
|
|
if (!decoder.mNeedDraining || decoder.mDraining) {
|
2015-07-13 06:21:04 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-07-16 05:47:51 +00:00
|
|
|
decoder.mNeedDraining = false;
|
2015-07-28 04:09:43 +00:00
|
|
|
// mOutputRequest must be set, otherwise NotifyDrainComplete()
|
|
|
|
// may reject the drain if a Flush recently occurred.
|
2015-07-28 06:36:10 +00:00
|
|
|
decoder.mOutputRequested = true;
|
2015-07-28 04:09:43 +00:00
|
|
|
if (!decoder.mDecoder ||
|
|
|
|
decoder.mNumSamplesInput == decoder.mNumSamplesOutput) {
|
2015-07-26 11:52:22 +00:00
|
|
|
// No frames to drain.
|
|
|
|
NotifyDrainComplete(aTrack);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-13 06:21:04 +00:00
|
|
|
decoder.mDecoder->Drain();
|
2015-07-15 04:43:35 +00:00
|
|
|
decoder.mDraining = true;
|
2015-07-13 06:21:04 +00:00
|
|
|
LOG("Requesting %s decoder to drain", TrackTypeToStr(aTrack));
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::Update(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
2015-10-19 18:11:35 +00:00
|
|
|
if (mShutdown) {
|
2015-05-18 05:40:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Processing update for %s", TrackTypeToStr(aTrack));
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
bool needOutput = false;
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2015-05-18 05:42:01 +00:00
|
|
|
decoder.mUpdateScheduled = false;
|
|
|
|
|
2015-10-19 18:11:35 +00:00
|
|
|
if (!mInitDone) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-26 03:59:46 +00:00
|
|
|
if (aTrack == TrackType::kVideoTrack && mSkipRequest.Exists()) {
|
|
|
|
LOGV("Skipping in progress, nothing more to do");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:42:01 +00:00
|
|
|
if (UpdateReceivedNewData(aTrack)) {
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Nothing more to do");
|
2015-05-18 05:42:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-05-05 04:52:58 +00:00
|
|
|
if (decoder.mSeekRequest.Exists()) {
|
|
|
|
LOGV("Seeking hasn't completed, nothing more to do");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!decoder.HasInternalSeekPending() ||
|
|
|
|
(!decoder.mOutput.Length() &&
|
|
|
|
!decoder.mQueuedSamples.Length()),
|
|
|
|
"No frames can be demuxed or decoded while an internal seek is pending");
|
|
|
|
|
2015-05-25 05:09:16 +00:00
|
|
|
// Record number of frames decoded and parsed. Automatically update the
|
|
|
|
// stats counters using the AutoNotifyDecoded stack-based class.
|
|
|
|
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
|
|
|
|
|
2015-12-17 22:37:03 +00:00
|
|
|
// Drop any frames found prior our internal seek target.
|
|
|
|
while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
|
|
|
|
RefPtr<MediaData>& output = decoder.mOutput[0];
|
2016-02-01 04:05:25 +00:00
|
|
|
InternalSeekTarget target = decoder.mTimeThreshold.ref();
|
2015-12-17 22:37:03 +00:00
|
|
|
media::TimeUnit time = media::TimeUnit::FromMicroseconds(output->mTime);
|
2016-05-18 03:48:05 +00:00
|
|
|
if (time >= target.Time()) {
|
2015-12-17 22:37:03 +00:00
|
|
|
// We have reached our internal seek target.
|
|
|
|
decoder.mTimeThreshold.reset();
|
2016-07-25 23:36:55 +00:00
|
|
|
// We might have dropped some keyframes.
|
|
|
|
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
|
2015-12-17 22:37:03 +00:00
|
|
|
}
|
2016-05-18 03:48:05 +00:00
|
|
|
if (time < target.Time() || (target.mDropTarget && target.Contains(time))) {
|
2015-12-17 22:37:03 +00:00
|
|
|
LOGV("Internal Seeking: Dropping %s frame time:%f wanted:%f (kf:%d)",
|
|
|
|
TrackTypeToStr(aTrack),
|
|
|
|
media::TimeUnit::FromMicroseconds(output->mTime).ToSeconds(),
|
2016-05-18 03:48:05 +00:00
|
|
|
target.Time().ToSeconds(),
|
2015-12-17 22:37:03 +00:00
|
|
|
output->mKeyframe);
|
|
|
|
decoder.mOutput.RemoveElementAt(0);
|
2016-05-19 09:03:39 +00:00
|
|
|
decoder.mSizeOfQueue -= 1;
|
2015-12-17 22:37:03 +00:00
|
|
|
}
|
2015-05-18 05:42:01 +00:00
|
|
|
}
|
2015-07-13 06:21:04 +00:00
|
|
|
|
2016-06-08 02:59:57 +00:00
|
|
|
while (decoder.mOutput.Length() && decoder.mOutput[0]->mType == MediaData::NULL_DATA) {
|
|
|
|
LOGV("Dropping null data. Time: %lld", decoder.mOutput[0]->mTime);
|
|
|
|
decoder.mOutput.RemoveElementAt(0);
|
|
|
|
decoder.mSizeOfQueue -= 1;
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:42:01 +00:00
|
|
|
if (decoder.HasPromise()) {
|
|
|
|
needOutput = true;
|
2015-12-17 22:37:03 +00:00
|
|
|
if (decoder.mOutput.Length()) {
|
2016-05-19 09:03:39 +00:00
|
|
|
RefPtr<MediaData> output = decoder.mOutput[0];
|
|
|
|
decoder.mOutput.RemoveElementAt(0);
|
|
|
|
decoder.mSizeOfQueue -= 1;
|
|
|
|
decoder.mLastSampleTime =
|
|
|
|
Some(TimeInterval(TimeUnit::FromMicroseconds(output->mTime),
|
|
|
|
TimeUnit::FromMicroseconds(output->GetEndTime())));
|
|
|
|
decoder.mNumSamplesOutputTotal++;
|
|
|
|
ReturnOutput(output, aTrack);
|
2015-05-18 05:42:01 +00:00
|
|
|
// We have a decoded sample ready to be returned.
|
2015-07-20 19:14:37 +00:00
|
|
|
if (aTrack == TrackType::kVideoTrack) {
|
2015-12-17 22:37:03 +00:00
|
|
|
uint64_t delta =
|
|
|
|
decoder.mNumSamplesOutputTotal - mLastReportedNumDecodedFrames;
|
2016-07-18 00:41:40 +00:00
|
|
|
a.mStats.mDecodedFrames = static_cast<uint32_t>(delta);
|
2015-12-17 22:37:03 +00:00
|
|
|
mLastReportedNumDecodedFrames = decoder.mNumSamplesOutputTotal;
|
2016-07-25 23:36:55 +00:00
|
|
|
if (output->mKeyframe) {
|
|
|
|
if (mPreviousDecodedKeyframeTime_us < output->mTime) {
|
|
|
|
// There is a previous keyframe -> Record inter-keyframe stats.
|
|
|
|
uint64_t segment_us = output->mTime - mPreviousDecodedKeyframeTime_us;
|
|
|
|
a.mStats.mInterKeyframeSum_us += segment_us;
|
|
|
|
a.mStats.mInterKeyframeCount += 1;
|
|
|
|
if (a.mStats.mInterKeyFrameMax_us < segment_us) {
|
|
|
|
a.mStats.mInterKeyFrameMax_us = segment_us;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mPreviousDecodedKeyframeTime_us = output->mTime;
|
|
|
|
}
|
2015-07-13 20:12:23 +00:00
|
|
|
nsCString error;
|
2015-07-20 19:14:37 +00:00
|
|
|
mVideo.mIsHardwareAccelerated =
|
2015-07-13 20:12:23 +00:00
|
|
|
mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
|
2015-07-20 19:14:37 +00:00
|
|
|
}
|
2016-05-30 16:24:00 +00:00
|
|
|
} else if (decoder.HasFatalError()) {
|
2015-12-17 22:37:03 +00:00
|
|
|
LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
|
2016-09-10 06:48:53 +00:00
|
|
|
decoder.RejectPromise(decoder.mError.ref(), __func__);
|
2015-12-08 19:30:25 +00:00
|
|
|
return;
|
2015-12-17 22:37:03 +00:00
|
|
|
} else if (decoder.mDrainComplete) {
|
2015-12-08 19:30:25 +00:00
|
|
|
bool wasDraining = decoder.mDraining;
|
2015-05-18 05:42:01 +00:00
|
|
|
decoder.mDrainComplete = false;
|
2015-07-15 04:43:35 +00:00
|
|
|
decoder.mDraining = false;
|
2015-12-08 19:30:25 +00:00
|
|
|
if (decoder.mDemuxEOS) {
|
2015-12-17 22:37:03 +00:00
|
|
|
LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
|
2016-09-10 06:48:53 +00:00
|
|
|
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
|
2015-12-08 19:30:25 +00:00
|
|
|
} else if (decoder.mWaitingForData) {
|
|
|
|
if (wasDraining && decoder.mLastSampleTime &&
|
|
|
|
!decoder.mNextStreamSourceID) {
|
|
|
|
// We have completed draining the decoder following WaitingForData.
|
|
|
|
// Set up the internal seek machinery to be able to resume from the
|
|
|
|
// last sample decoded.
|
|
|
|
LOG("Seeking to last sample time: %lld",
|
2016-05-18 03:48:05 +00:00
|
|
|
decoder.mLastSampleTime.ref().mStart.ToMicroseconds());
|
2016-02-01 04:05:25 +00:00
|
|
|
InternalSeek(aTrack, InternalSeekTarget(decoder.mLastSampleTime.ref(), true));
|
2015-12-08 19:30:25 +00:00
|
|
|
}
|
2016-02-08 13:36:47 +00:00
|
|
|
if (!decoder.mReceivedNewData) {
|
|
|
|
LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
|
2016-09-10 06:48:53 +00:00
|
|
|
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
|
2016-02-08 13:36:47 +00:00
|
|
|
}
|
2015-12-08 19:30:25 +00:00
|
|
|
}
|
|
|
|
// Now that draining has completed, we check if we have received
|
|
|
|
// new data again as the result may now be different from the earlier
|
|
|
|
// run.
|
2016-05-05 04:52:58 +00:00
|
|
|
if (UpdateReceivedNewData(aTrack) || decoder.mSeekRequest.Exists()) {
|
2015-12-08 19:30:25 +00:00
|
|
|
LOGV("Nothing more to do");
|
|
|
|
return;
|
2015-07-13 06:21:04 +00:00
|
|
|
}
|
2016-05-24 09:58:36 +00:00
|
|
|
} else if (decoder.mDemuxEOS && !decoder.mNeedDraining &&
|
2016-06-02 11:08:05 +00:00
|
|
|
!decoder.HasPendingDrain() && decoder.mQueuedSamples.IsEmpty()) {
|
2016-05-24 09:58:36 +00:00
|
|
|
// It is possible to transition from WAITING_FOR_DATA directly to EOS
|
|
|
|
// state during the internal seek; in which case no draining would occur.
|
|
|
|
// There is no more samples left to be decoded and we are already in
|
|
|
|
// EOS state. We can immediately reject the data promise.
|
|
|
|
LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
|
2016-09-10 06:48:53 +00:00
|
|
|
decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-12 11:24:48 +00:00
|
|
|
if (decoder.mNeedDraining) {
|
2015-07-13 06:21:04 +00:00
|
|
|
DrainDecoder(aTrack);
|
2015-05-25 05:09:16 +00:00
|
|
|
return;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-09-10 00:49:54 +00:00
|
|
|
if (decoder.mError && !decoder.HasFatalError()) {
|
2016-09-01 09:25:54 +00:00
|
|
|
decoder.mDecodePending = false;
|
2016-05-30 16:24:00 +00:00
|
|
|
if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
|
2016-09-13 03:03:21 +00:00
|
|
|
NotifyError(aTrack, decoder.mError.ref());
|
2016-05-30 16:24:00 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-09-13 03:03:21 +00:00
|
|
|
decoder.mError.reset();
|
2016-05-30 16:24:00 +00:00
|
|
|
LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
|
|
|
|
decoder.mNumOfConsecutiveError);
|
|
|
|
media::TimeUnit nextKeyframe;
|
|
|
|
if (aTrack == TrackType::kVideoTrack && !decoder.HasInternalSeekPending() &&
|
|
|
|
NS_SUCCEEDED(decoder.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe))) {
|
|
|
|
SkipVideoDemuxToNextKeyFrame(decoder.mLastSampleTime.refOr(TimeInterval()).Length());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-08 19:30:25 +00:00
|
|
|
bool needInput = NeedInput(decoder);
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-09-01 09:25:54 +00:00
|
|
|
LOGV("Update(%s) ni=%d no=%d ie=%d, in:%llu out:%llu qs=%u pending:%u waiting:%d promise:%d sid:%u",
|
|
|
|
TrackTypeToStr(aTrack), needInput, needOutput, decoder.mDecodePending,
|
2015-05-25 05:09:16 +00:00
|
|
|
decoder.mNumSamplesInput, decoder.mNumSamplesOutput,
|
2015-08-24 02:50:27 +00:00
|
|
|
uint32_t(size_t(decoder.mSizeOfQueue)), uint32_t(decoder.mOutput.Length()),
|
2016-09-01 09:25:54 +00:00
|
|
|
decoder.mWaitingForData, decoder.HasPromise(), decoder.mLastStreamSourceID);
|
2015-12-08 19:30:25 +00:00
|
|
|
|
|
|
|
if (decoder.mWaitingForData &&
|
|
|
|
(!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting)) {
|
|
|
|
// Nothing more we can do at present.
|
|
|
|
LOGV("Still waiting for data.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!needInput) {
|
|
|
|
LOGV("No need for additional input (pending:%u)",
|
|
|
|
uint32_t(decoder.mOutput.Length()));
|
|
|
|
return;
|
|
|
|
}
|
2015-05-25 05:09:16 +00:00
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
// Demux samples if we don't have some.
|
|
|
|
RequestDemuxSamples(aTrack);
|
2015-10-20 09:33:00 +00:00
|
|
|
|
|
|
|
HandleDemuxedSamples(aTrack, a);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::ReturnOutput(MediaData* aData, TrackType aTrack)
|
|
|
|
{
|
2016-08-25 10:18:22 +00:00
|
|
|
MOZ_ASSERT(GetDecoderData(aTrack).HasPromise());
|
2016-06-08 02:59:57 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aData->mType != MediaData::NULL_DATA);
|
2016-05-06 05:56:47 +00:00
|
|
|
LOG("Resolved data promise for %s [%lld, %lld]", TrackTypeToStr(aTrack),
|
|
|
|
aData->mTime, aData->GetEndTime());
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
if (aTrack == TrackInfo::kAudioTrack) {
|
2015-10-20 09:33:00 +00:00
|
|
|
if (aData->mType != MediaData::RAW_DATA) {
|
|
|
|
AudioData* audioData = static_cast<AudioData*>(aData);
|
|
|
|
|
|
|
|
if (audioData->mChannels != mInfo.mAudio.mChannels ||
|
|
|
|
audioData->mRate != mInfo.mAudio.mRate) {
|
|
|
|
LOG("change of audio format (rate:%d->%d). "
|
|
|
|
"This is an unsupported configuration",
|
|
|
|
mInfo.mAudio.mRate, audioData->mRate);
|
|
|
|
mInfo.mAudio.mRate = audioData->mRate;
|
|
|
|
mInfo.mAudio.mChannels = audioData->mChannels;
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
2016-05-05 05:11:21 +00:00
|
|
|
mAudio.ResolvePromise(aData, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
} else if (aTrack == TrackInfo::kVideoTrack) {
|
2016-01-12 10:03:13 +00:00
|
|
|
if (aData->mType != MediaData::RAW_DATA) {
|
|
|
|
VideoData* videoData = static_cast<VideoData*>(aData);
|
|
|
|
|
|
|
|
if (videoData->mDisplay != mInfo.mVideo.mDisplay) {
|
|
|
|
LOG("change of video display size (%dx%d->%dx%d)",
|
|
|
|
mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height,
|
|
|
|
videoData->mDisplay.width, videoData->mDisplay.height);
|
|
|
|
mInfo.mVideo.mDisplay = videoData->mDisplay;
|
|
|
|
}
|
|
|
|
}
|
2016-05-05 05:11:21 +00:00
|
|
|
mVideo.ResolvePromise(aData, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
MediaFormatReader::SizeOfVideoQueueInFrames()
|
|
|
|
{
|
|
|
|
return SizeOfQueue(TrackInfo::kVideoTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
MediaFormatReader::SizeOfAudioQueueInFrames()
|
|
|
|
{
|
|
|
|
return SizeOfQueue(TrackInfo::kAudioTrack);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t
|
|
|
|
MediaFormatReader::SizeOfQueue(TrackType aTrack)
|
|
|
|
{
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
2015-05-18 05:42:01 +00:00
|
|
|
return decoder.mSizeOfQueue;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<MediaDecoderReader::WaitForDataPromise>
|
2015-05-18 05:40:32 +00:00
|
|
|
MediaFormatReader::WaitForData(MediaData::Type aType)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
TrackType trackType = aType == MediaData::VIDEO_DATA ?
|
|
|
|
TrackType::kVideoTrack : TrackType::kAudioTrack;
|
|
|
|
auto& decoder = GetDecoderData(trackType);
|
2015-07-20 06:05:16 +00:00
|
|
|
if (!decoder.mWaitingForData) {
|
|
|
|
// We aren't waiting for data any longer.
|
|
|
|
return WaitForDataPromise::CreateAndResolve(decoder.mType, __func__);
|
|
|
|
}
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<WaitForDataPromise> p = decoder.mWaitingPromise.Ensure(__func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
ScheduleUpdate(trackType);
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
2016-05-31 04:32:37 +00:00
|
|
|
MediaFormatReader::ResetDecode(TrackSet aTracks)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("");
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
mSeekPromise.RejectIfExists(NS_OK, __func__);
|
|
|
|
mSkipRequest.DisconnectIfExists();
|
|
|
|
|
|
|
|
// Do the same for any data wait promises.
|
2016-05-31 04:32:37 +00:00
|
|
|
if (aTracks.contains(TrackInfo::kAudioTrack)) {
|
|
|
|
mAudio.mWaitingPromise.RejectIfExists(
|
|
|
|
WaitForDataRejectValue(MediaData::AUDIO_DATA,
|
|
|
|
WaitForDataRejectValue::CANCELED), __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (aTracks.contains(TrackInfo::kVideoTrack)) {
|
|
|
|
mVideo.mWaitingPromise.RejectIfExists(
|
|
|
|
WaitForDataRejectValue(MediaData::VIDEO_DATA,
|
|
|
|
WaitForDataRejectValue::CANCELED), __func__);
|
2016-05-16 06:50:04 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
// Reset miscellaneous seeking state.
|
|
|
|
mPendingSeekTime.reset();
|
|
|
|
|
2016-05-31 04:32:37 +00:00
|
|
|
if (HasVideo() && aTracks.contains(TrackInfo::kVideoTrack)) {
|
2016-05-13 01:14:12 +00:00
|
|
|
mVideo.ResetDemuxer();
|
2016-05-05 05:06:40 +00:00
|
|
|
Reset(TrackInfo::kVideoTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
if (mVideo.HasPromise()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
2016-04-18 06:33:52 +00:00
|
|
|
|
2016-05-31 04:32:37 +00:00
|
|
|
if (HasAudio() && aTracks.contains(TrackInfo::kAudioTrack)) {
|
2016-05-13 01:14:12 +00:00
|
|
|
mAudio.ResetDemuxer();
|
2016-05-05 05:06:40 +00:00
|
|
|
Reset(TrackInfo::kAudioTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
if (mAudio.HasPromise()) {
|
2016-09-10 06:48:53 +00:00
|
|
|
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
|
2015-06-15 00:58:12 +00:00
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
2016-05-31 04:32:37 +00:00
|
|
|
|
|
|
|
return MediaDecoderReader::ResetDecode(aTracks);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
|
|
|
|
{
|
|
|
|
if (!aSample) {
|
|
|
|
NS_WARNING("MediaFormatReader::Output() passed a null sample");
|
2016-09-09 23:56:53 +00:00
|
|
|
Error(aTrack, MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
|
2015-05-18 05:40:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-23 06:12:17 +00:00
|
|
|
LOGV("Decoded %s sample time=%lld timecode=%lld kf=%d dur=%lld",
|
|
|
|
TrackTypeToStr(aTrack), aSample->mTime, aSample->mTimecode,
|
|
|
|
aSample->mKeyframe, aSample->mDuration);
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsIRunnable> task =
|
2016-05-05 08:45:00 +00:00
|
|
|
NewRunnableMethod<TrackType, MediaData*>(
|
2015-05-18 05:42:01 +00:00
|
|
|
this, &MediaFormatReader::NotifyNewOutput, aTrack, aSample);
|
2015-07-16 18:31:21 +00:00
|
|
|
OwnerThread()->Dispatch(task.forget());
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::DrainComplete(TrackType aTrack)
|
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsIRunnable> task =
|
2016-05-05 08:45:00 +00:00
|
|
|
NewRunnableMethod<TrackType>(
|
2015-05-18 05:42:01 +00:00
|
|
|
this, &MediaFormatReader::NotifyDrainComplete, aTrack);
|
2015-07-16 18:31:21 +00:00
|
|
|
OwnerThread()->Dispatch(task.forget());
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::InputExhausted(TrackType aTrack)
|
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsIRunnable> task =
|
2016-05-05 08:45:00 +00:00
|
|
|
NewRunnableMethod<TrackType>(
|
2015-05-18 05:42:01 +00:00
|
|
|
this, &MediaFormatReader::NotifyInputExhausted, aTrack);
|
2015-07-16 18:31:21 +00:00
|
|
|
OwnerThread()->Dispatch(task.forget());
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-09 23:56:53 +00:00
|
|
|
MediaFormatReader::Error(TrackType aTrack, const MediaResult& aError)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<nsIRunnable> task =
|
2016-09-09 23:56:53 +00:00
|
|
|
NewRunnableMethod<TrackType, MediaResult>(
|
2016-05-30 16:24:00 +00:00
|
|
|
this, &MediaFormatReader::NotifyError, aTrack, aError);
|
2015-07-16 18:31:21 +00:00
|
|
|
OwnerThread()->Dispatch(task.forget());
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-05-05 05:06:40 +00:00
|
|
|
MediaFormatReader::Reset(TrackType aTrack)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-05-05 05:06:40 +00:00
|
|
|
LOG("Reset(%s) BEGIN", TrackTypeToStr(aTrack));
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
decoder.ResetState();
|
2016-05-05 05:06:40 +00:00
|
|
|
decoder.Flush();
|
|
|
|
|
|
|
|
LOG("Reset(%s) END", TrackTypeToStr(aTrack));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-05-26 04:58:48 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::DropDecodedSamples(TrackType aTrack)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
size_t lengthDecodedQueue = decoder.mOutput.Length();
|
|
|
|
if (lengthDecodedQueue && decoder.mTimeThreshold.isSome()) {
|
|
|
|
TimeUnit time =
|
|
|
|
TimeUnit::FromMicroseconds(decoder.mOutput.LastElement()->mTime);
|
|
|
|
if (time >= decoder.mTimeThreshold.ref().Time()) {
|
|
|
|
// We would have reached our internal seek target.
|
|
|
|
decoder.mTimeThreshold.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
decoder.mOutput.Clear();
|
|
|
|
decoder.mSizeOfQueue -= lengthDecodedQueue;
|
|
|
|
if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
|
2016-07-18 00:41:40 +00:00
|
|
|
mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
|
2016-05-26 04:58:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-21 20:51:01 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::WaitingForKey(TrackType aTrack)
|
|
|
|
{
|
|
|
|
if (mDecoder) {
|
|
|
|
mDecoder->NotifyWaitingForKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::SkipVideoDemuxToNextKeyFrame(media::TimeUnit aTimeThreshold)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
LOG("Skipping up to %lld", aTimeThreshold.ToMicroseconds());
|
|
|
|
|
2016-05-26 04:58:48 +00:00
|
|
|
// We've reached SkipVideoDemuxToNextKeyFrame when our decoding is late.
|
|
|
|
// As such we can drop all already decoded samples and discard all pending
|
|
|
|
// samples.
|
|
|
|
// TODO: Ideally we should set mOutputRequested to false so that all pending
|
|
|
|
// frames are dropped too. However, we can't do such thing as the code assumes
|
|
|
|
// that the decoder just got flushed. Once bug 1257107 land, we could set the
|
|
|
|
// decoder threshold to the value of currentTime.
|
|
|
|
DropDecodedSamples(TrackInfo::kVideoTrack);
|
|
|
|
|
2016-05-19 07:02:43 +00:00
|
|
|
mSkipRequest.Begin(mVideo.mTrackDemuxer->SkipToNextRandomAccessPoint(aTimeThreshold)
|
|
|
|
->Then(OwnerThread(), __func__, this,
|
|
|
|
&MediaFormatReader::OnVideoSkipCompleted,
|
|
|
|
&MediaFormatReader::OnVideoSkipFailed));
|
|
|
|
return;
|
|
|
|
}
|
2016-05-06 05:58:51 +00:00
|
|
|
|
2016-05-19 07:02:43 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::VideoSkipReset(uint32_t aSkipped)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-05-26 04:58:48 +00:00
|
|
|
|
|
|
|
// Some frames may have been output by the decoder since we initiated the
|
|
|
|
// videoskip process and we know they would be late.
|
|
|
|
DropDecodedSamples(TrackInfo::kVideoTrack);
|
|
|
|
// Report the pending frames as dropped.
|
2016-05-19 07:02:43 +00:00
|
|
|
if (mDecoder) {
|
2016-07-18 00:41:40 +00:00
|
|
|
mDecoder->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-05-19 07:02:43 +00:00
|
|
|
// Cancel any pending demux request and pending demuxed samples.
|
|
|
|
mVideo.mDemuxRequest.DisconnectIfExists();
|
|
|
|
Reset(TrackType::kVideoTrack);
|
|
|
|
|
|
|
|
if (mDecoder) {
|
2016-07-18 00:41:40 +00:00
|
|
|
mDecoder->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
|
2016-05-07 01:23:32 +00:00
|
|
|
}
|
|
|
|
|
2016-05-19 07:02:43 +00:00
|
|
|
mVideo.mNumSamplesSkippedTotal += aSkipped;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::OnVideoSkipCompleted(uint32_t aSkipped)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
LOG("Skipping succeeded, skipped %u frames", aSkipped);
|
|
|
|
mSkipRequest.Complete();
|
2016-05-19 07:02:43 +00:00
|
|
|
|
|
|
|
VideoSkipReset(aSkipped);
|
|
|
|
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(TrackInfo::kVideoTrack);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::OnVideoSkipFailed(MediaTrackDemuxer::SkipFailureHolder aFailure)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
LOG("Skipping failed, skipped %u frames", aFailure.mSkipped);
|
|
|
|
mSkipRequest.Complete();
|
2016-05-19 07:02:43 +00:00
|
|
|
|
2016-09-12 02:22:20 +00:00
|
|
|
switch (aFailure.mFailure.Code()) {
|
|
|
|
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
|
|
|
|
case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
|
2016-05-26 04:58:48 +00:00
|
|
|
// Some frames may have been output by the decoder since we initiated the
|
|
|
|
// videoskip process and we know they would be late.
|
|
|
|
DropDecodedSamples(TrackInfo::kVideoTrack);
|
2016-05-19 07:02:43 +00:00
|
|
|
// We can't complete the skip operation, will just service a video frame
|
|
|
|
// normally.
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(TrackInfo::kVideoTrack);
|
2015-06-15 00:58:12 +00:00
|
|
|
break;
|
2016-09-12 02:22:20 +00:00
|
|
|
case NS_ERROR_DOM_MEDIA_CANCELED:
|
2015-08-25 11:35:15 +00:00
|
|
|
if (mVideo.HasPromise()) {
|
2016-09-12 02:22:20 +00:00
|
|
|
mVideo.RejectPromise(aFailure.mFailure, __func__);
|
2015-08-25 11:35:15 +00:00
|
|
|
}
|
2015-06-15 00:58:12 +00:00
|
|
|
break;
|
|
|
|
default:
|
2016-09-12 02:22:20 +00:00
|
|
|
NotifyError(TrackType::kVideoTrack, aFailure.mFailure);
|
2015-06-15 00:58:12 +00:00
|
|
|
break;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<MediaDecoderReader::SeekPromise>
|
2016-02-01 04:05:25 +00:00
|
|
|
MediaFormatReader::Seek(SeekTarget aTarget, int64_t aUnused)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
2016-01-28 10:24:30 +00:00
|
|
|
LOG("aTarget=(%lld)", aTarget.GetTime().ToMicroseconds());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty());
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise());
|
2016-05-13 06:35:43 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aTarget.IsVideoOnly() || !mAudio.HasPromise());
|
2015-05-18 05:40:32 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mPendingSeekTime.isNothing());
|
2015-06-15 00:58:12 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mVideo.mTimeThreshold.isNothing());
|
2016-05-13 06:35:43 +00:00
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aTarget.IsVideoOnly() || mAudio.mTimeThreshold.isNothing());
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-02-04 04:31:21 +00:00
|
|
|
if (!mInfo.mMediaSeekable && !mInfo.mMediaSeekableOnlyInBufferedRanges) {
|
2015-05-18 05:40:32 +00:00
|
|
|
LOG("Seek() END (Unseekable)");
|
|
|
|
return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mShutdown) {
|
|
|
|
return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
|
|
|
}
|
|
|
|
|
2016-05-16 10:15:17 +00:00
|
|
|
SetSeekTarget(Move(aTarget));
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2015-10-18 05:24:48 +00:00
|
|
|
RefPtr<SeekPromise> p = mSeekPromise.Ensure(__func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-05-05 05:01:51 +00:00
|
|
|
ScheduleSeek();
|
2015-05-18 05:40:32 +00:00
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2016-05-16 10:15:17 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::SetSeekTarget(const SeekTarget& aTarget)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
|
|
|
SeekTarget target = aTarget;
|
|
|
|
|
|
|
|
// Transform the seek target time to the demuxer timeline.
|
|
|
|
if (!ForceZeroStartTime()) {
|
|
|
|
target.SetTime(aTarget.GetTime() - TimeUnit::FromMicroseconds(StartTime())
|
|
|
|
+ DemuxStartTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
mOriginalSeekTarget = target;
|
|
|
|
mFallbackSeekTime = mPendingSeekTime = Some(target.GetTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
TimeUnit
|
|
|
|
MediaFormatReader::DemuxStartTime()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
MOZ_ASSERT(!ForceZeroStartTime());
|
Bug 1290371 - handle files with audio and video tracks but no samples at all; r=jya
So, we have 4 boolean variables and here is the truth table.
Case 1, 2, 3, 4, 7, 8, 10 and 12 are not possible to happen.
Then, the remaining cases could be clustered into three categories:
(1) Case 5, 9 and 13: no sample is demuxed at all, return 0.
(2) Case 6, 11, 14 and 15: either audio or video is able to be demuxed, return the known value.
(3) Case 15: both audio and video are demuxed, return the minimum of the values.
For simplifying the logic, I will initialize the audioStartTime and videoStartTime
to be INFINITY if we don't have the first-demuxed sample, otherwise, initialize
them to be the real first-demuxed sample's time.
Then, the final calculation will be:
(1) Case 5, 9 and 13: the minimum of two INFINITY values is still INFINITY, return 0.
(2) Case 6, 11, 14 and 15: return the minimum of one real first-demuxed-time and the INFINITY.
(3) Case 15: return the minimum of two real first-demuxed-time values.
Case HasAudio HasVideo HasAudioSample HasVideoSample ExpectedResult
---------------------------------------------------------------------------------------------------
1 F F F F not possible
2 F F F T not possible
3 F F T F not possible
4 F F T T not possible
---------------------------------------------------------------------------------------------------
5 F T F F return 0
6 F T F T return video sample
7 F T T F not possible
8 F T T T not possible
---------------------------------------------------------------------------------------------------
9 T F F F return 0
10 T F F T not possible
11 T F T F return audio sample
12 T F T T not possible
---------------------------------------------------------------------------------------------------
13 T T F F return 0
14 T T F T return videoSample
15 T T T F return audioSample
16 T T T T return min(auidoSample, videoSample)
MozReview-Commit-ID: ANsYDth7slJ
--HG--
extra : transplant_source : %DAj%1A%EC%19%82%7B%B3%05%FE%21%04a%16%9A%9F%18x1%95
2016-08-03 08:51:29 +00:00
|
|
|
MOZ_ASSERT(HasAudio() || HasVideo());
|
|
|
|
|
|
|
|
const TimeUnit startTime =
|
|
|
|
std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
|
|
|
|
mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
|
|
|
|
|
|
|
|
return startTime.IsInfinite() ? TimeUnit::FromMicroseconds(0) : startTime;
|
2016-05-16 10:15:17 +00:00
|
|
|
}
|
|
|
|
|
2016-05-05 05:01:51 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::ScheduleSeek()
|
|
|
|
{
|
|
|
|
if (mSeekScheduled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mSeekScheduled = true;
|
|
|
|
OwnerThread()->Dispatch(NewRunnableMethod(this, &MediaFormatReader::AttemptSeek));
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::AttemptSeek()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-05-05 05:01:51 +00:00
|
|
|
|
|
|
|
mSeekScheduled = false;
|
|
|
|
|
2015-09-28 05:23:28 +00:00
|
|
|
if (mPendingSeekTime.isNothing()) {
|
|
|
|
return;
|
|
|
|
}
|
2016-05-13 01:14:12 +00:00
|
|
|
|
|
|
|
if (HasVideo()) {
|
|
|
|
mVideo.ResetDemuxer();
|
|
|
|
mVideo.ResetState();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't reset the audio demuxer not state when seeking video only
|
|
|
|
// as it will cause the audio to seek back to the beginning
|
|
|
|
// resulting in out-of-sync audio from video.
|
2016-05-16 06:47:57 +00:00
|
|
|
if (HasAudio() && !mOriginalSeekTarget.IsVideoOnly()) {
|
2016-05-13 01:14:12 +00:00
|
|
|
mAudio.ResetDemuxer();
|
|
|
|
mAudio.ResetState();
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
if (HasVideo()) {
|
|
|
|
DoVideoSeek();
|
|
|
|
} else if (HasAudio()) {
|
|
|
|
DoAudioSeek();
|
|
|
|
} else {
|
|
|
|
MOZ_CRASH();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-12 02:22:20 +00:00
|
|
|
MediaFormatReader::OnSeekFailed(TrackType aTrack, const MediaResult& aError)
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2016-09-12 02:22:20 +00:00
|
|
|
LOGV("%s failure:%u", TrackTypeToStr(aTrack), aError.Code());
|
2015-05-18 05:40:32 +00:00
|
|
|
if (aTrack == TrackType::kVideoTrack) {
|
2015-06-15 00:58:12 +00:00
|
|
|
mVideo.mSeekRequest.Complete();
|
2015-05-18 05:40:32 +00:00
|
|
|
} else {
|
2015-06-15 00:58:12 +00:00
|
|
|
mAudio.mSeekRequest.Complete();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-09-12 02:22:20 +00:00
|
|
|
if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
|
2015-08-11 04:15:19 +00:00
|
|
|
if (HasVideo() && aTrack == TrackType::kAudioTrack &&
|
2016-05-16 06:47:57 +00:00
|
|
|
mFallbackSeekTime.isSome() &&
|
|
|
|
mPendingSeekTime.ref() != mFallbackSeekTime.ref()) {
|
2015-08-11 04:15:19 +00:00
|
|
|
// We have failed to seek audio where video seeked to earlier.
|
|
|
|
// Attempt to seek instead to the closest point that we know we have in
|
|
|
|
// order to limit A/V sync discrepency.
|
|
|
|
|
|
|
|
// Ensure we have the most up to date buffered ranges.
|
|
|
|
UpdateReceivedNewData(TrackType::kAudioTrack);
|
|
|
|
Maybe<media::TimeUnit> nextSeekTime;
|
|
|
|
// Find closest buffered time found after video seeked time.
|
|
|
|
for (const auto& timeRange : mAudio.mTimeRanges) {
|
|
|
|
if (timeRange.mStart >= mPendingSeekTime.ref()) {
|
|
|
|
nextSeekTime.emplace(timeRange.mStart);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nextSeekTime.isNothing() ||
|
2016-05-16 06:47:57 +00:00
|
|
|
nextSeekTime.ref() > mFallbackSeekTime.ref()) {
|
|
|
|
nextSeekTime = Some(mFallbackSeekTime.ref());
|
2015-08-11 04:15:19 +00:00
|
|
|
LOG("Unable to seek audio to video seek time. A/V sync may be broken");
|
|
|
|
} else {
|
2016-05-16 06:47:57 +00:00
|
|
|
mFallbackSeekTime.reset();
|
2015-08-11 04:15:19 +00:00
|
|
|
}
|
|
|
|
mPendingSeekTime = nextSeekTime;
|
|
|
|
DoAudioSeek();
|
|
|
|
return;
|
|
|
|
}
|
2015-05-18 05:42:01 +00:00
|
|
|
NotifyWaitingForData(aTrack);
|
2015-05-18 05:40:32 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-06-15 00:58:12 +00:00
|
|
|
MOZ_ASSERT(!mVideo.mSeekRequest.Exists() && !mAudio.mSeekRequest.Exists());
|
2015-05-18 05:40:32 +00:00
|
|
|
mPendingSeekTime.reset();
|
2016-09-12 02:22:20 +00:00
|
|
|
mSeekPromise.Reject(aError, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::DoVideoSeek()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mPendingSeekTime.isSome());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Seeking video to %lld", mPendingSeekTime.ref().ToMicroseconds());
|
2015-05-18 05:40:32 +00:00
|
|
|
media::TimeUnit seekTime = mPendingSeekTime.ref();
|
2015-06-15 00:58:12 +00:00
|
|
|
mVideo.mSeekRequest.Begin(mVideo.mTrackDemuxer->Seek(seekTime)
|
2015-07-16 18:31:21 +00:00
|
|
|
->Then(OwnerThread(), __func__, this,
|
2015-05-22 19:28:20 +00:00
|
|
|
&MediaFormatReader::OnVideoSeekCompleted,
|
|
|
|
&MediaFormatReader::OnVideoSeekFailed));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::OnVideoSeekCompleted(media::TimeUnit aTime)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Video seeked to %lld", aTime.ToMicroseconds());
|
2015-06-15 00:58:12 +00:00
|
|
|
mVideo.mSeekRequest.Complete();
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2016-07-25 23:36:55 +00:00
|
|
|
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
|
|
|
|
|
2016-06-08 02:59:57 +00:00
|
|
|
SetVideoDecodeThreshold();
|
|
|
|
|
2016-05-16 06:47:57 +00:00
|
|
|
if (HasAudio() && !mOriginalSeekTarget.IsVideoOnly()) {
|
2016-04-18 06:33:52 +00:00
|
|
|
MOZ_ASSERT(mPendingSeekTime.isSome());
|
2016-05-16 06:47:57 +00:00
|
|
|
if (mOriginalSeekTarget.IsFast()) {
|
2016-01-28 12:25:15 +00:00
|
|
|
// We are performing a fast seek. We need to seek audio to where the
|
|
|
|
// video seeked to, to ensure proper A/V sync once playback resume.
|
|
|
|
mPendingSeekTime = Some(aTime);
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
DoAudioSeek();
|
|
|
|
} else {
|
|
|
|
mPendingSeekTime.reset();
|
2016-01-28 10:20:29 +00:00
|
|
|
mSeekPromise.Resolve(aTime, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 23:36:55 +00:00
|
|
|
void
|
2016-09-12 02:22:20 +00:00
|
|
|
MediaFormatReader::OnVideoSeekFailed(const MediaResult& aError)
|
2016-07-25 23:36:55 +00:00
|
|
|
{
|
|
|
|
mPreviousDecodedKeyframeTime_us = sNoPreviousDecodedKeyframe;
|
2016-09-12 02:22:20 +00:00
|
|
|
OnSeekFailed(TrackType::kVideoTrack, aError);
|
2016-07-25 23:36:55 +00:00
|
|
|
}
|
|
|
|
|
2016-06-08 02:59:57 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::SetVideoDecodeThreshold()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
|
|
|
if (!HasVideo() || !mVideo.mDecoder) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mVideo.mTimeThreshold && !IsSeeking()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
TimeUnit threshold;
|
|
|
|
if (mVideo.mTimeThreshold) {
|
|
|
|
// For internalSeek.
|
|
|
|
threshold = mVideo.mTimeThreshold.ref().Time();
|
|
|
|
} else if (IsSeeking()) {
|
|
|
|
// If IsSeeking() is true, then video seek must have completed already.
|
|
|
|
TimeUnit keyframe;
|
|
|
|
if (NS_FAILED(mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&keyframe))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the key frame is invalid/infinite, it means the target position is
|
|
|
|
// closing to end of stream. We don't want to skip any frame at this point.
|
|
|
|
if (!keyframe.IsValid() || keyframe.IsInfinite()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
threshold = mOriginalSeekTarget.GetTime();
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOG("Set seek threshold to %lld", threshold.ToMicroseconds());
|
|
|
|
mVideo.mDecoder->SetSeekThreshold(threshold);
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::DoAudioSeek()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mPendingSeekTime.isSome());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Seeking audio to %lld", mPendingSeekTime.ref().ToMicroseconds());
|
2015-05-18 05:40:32 +00:00
|
|
|
media::TimeUnit seekTime = mPendingSeekTime.ref();
|
2015-06-15 00:58:12 +00:00
|
|
|
mAudio.mSeekRequest.Begin(mAudio.mTrackDemuxer->Seek(seekTime)
|
2015-07-16 18:31:21 +00:00
|
|
|
->Then(OwnerThread(), __func__, this,
|
2015-05-22 19:28:20 +00:00
|
|
|
&MediaFormatReader::OnAudioSeekCompleted,
|
|
|
|
&MediaFormatReader::OnAudioSeekFailed));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::OnAudioSeekCompleted(media::TimeUnit aTime)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-05-25 05:09:16 +00:00
|
|
|
LOGV("Audio seeked to %lld", aTime.ToMicroseconds());
|
2015-06-15 00:58:12 +00:00
|
|
|
mAudio.mSeekRequest.Complete();
|
2015-05-18 05:40:32 +00:00
|
|
|
mPendingSeekTime.reset();
|
2016-01-28 10:20:29 +00:00
|
|
|
mSeekPromise.Resolve(aTime, __func__);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-07-25 23:36:55 +00:00
|
|
|
void
|
2016-09-12 02:22:20 +00:00
|
|
|
MediaFormatReader::OnAudioSeekFailed(const MediaResult& aError)
|
2016-07-25 23:36:55 +00:00
|
|
|
{
|
2016-09-12 02:22:20 +00:00
|
|
|
OnSeekFailed(TrackType::kAudioTrack, aError);
|
2016-07-25 23:36:55 +00:00
|
|
|
}
|
|
|
|
|
2015-05-18 06:15:47 +00:00
|
|
|
media::TimeIntervals
|
|
|
|
MediaFormatReader::GetBuffered()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2015-06-17 21:22:10 +00:00
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-05-18 05:40:32 +00:00
|
|
|
media::TimeIntervals videoti;
|
|
|
|
media::TimeIntervals audioti;
|
2015-05-25 09:27:21 +00:00
|
|
|
media::TimeIntervals intervals;
|
2015-05-18 05:40:32 +00:00
|
|
|
|
2015-05-25 05:09:16 +00:00
|
|
|
if (!mInitDone) {
|
2015-05-25 09:27:21 +00:00
|
|
|
return intervals;
|
|
|
|
}
|
2016-02-03 00:44:17 +00:00
|
|
|
int64_t startTime = 0;
|
2015-07-22 10:20:53 +00:00
|
|
|
if (!ForceZeroStartTime()) {
|
|
|
|
if (!HaveStartTime()) {
|
|
|
|
return intervals;
|
|
|
|
}
|
2015-07-01 23:28:47 +00:00
|
|
|
startTime = StartTime();
|
2015-05-25 05:09:16 +00:00
|
|
|
}
|
2015-06-17 21:22:10 +00:00
|
|
|
// Ensure we have up to date buffered time range.
|
|
|
|
if (HasVideo()) {
|
|
|
|
UpdateReceivedNewData(TrackType::kVideoTrack);
|
|
|
|
}
|
|
|
|
if (HasAudio()) {
|
|
|
|
UpdateReceivedNewData(TrackType::kAudioTrack);
|
|
|
|
}
|
|
|
|
if (HasVideo()) {
|
|
|
|
videoti = mVideo.mTimeRanges;
|
|
|
|
}
|
|
|
|
if (HasAudio()) {
|
|
|
|
audioti = mAudio.mTimeRanges;
|
|
|
|
}
|
|
|
|
if (HasAudio() && HasVideo()) {
|
|
|
|
intervals = media::Intersection(Move(videoti), Move(audioti));
|
|
|
|
} else if (HasAudio()) {
|
|
|
|
intervals = Move(audioti);
|
|
|
|
} else if (HasVideo()) {
|
|
|
|
intervals = Move(videoti);
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-02-03 00:44:17 +00:00
|
|
|
if (!intervals.Length() ||
|
|
|
|
intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
|
|
|
|
// IntervalSet already starts at 0 or is empty, nothing to shift.
|
|
|
|
return intervals;
|
|
|
|
}
|
2015-05-25 09:27:21 +00:00
|
|
|
return intervals.Shift(media::TimeUnit::FromMicroseconds(-startTime));
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2016-03-09 02:32:49 +00:00
|
|
|
// For the MediaFormatReader override we need to force an update to the
|
|
|
|
// buffered ranges, so we call NotifyDataArrive
|
|
|
|
RefPtr<MediaDecoderReader::BufferedUpdatePromise>
|
|
|
|
MediaFormatReader::UpdateBufferedWithPromise() {
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
// Call NotifyDataArrive to force a recalculation of the buffered
|
|
|
|
// ranges. UpdateBuffered alone will not force a recalculation, so we
|
|
|
|
// use NotifyDataArrived which sets flags to force this recalculation.
|
|
|
|
// See MediaFormatReader::UpdateReceivedNewData for an example of where
|
|
|
|
// the new data flag is used.
|
|
|
|
NotifyDataArrived();
|
|
|
|
return BufferedUpdatePromise::CreateAndResolve(true, __func__);
|
|
|
|
}
|
|
|
|
|
2016-08-17 07:03:30 +00:00
|
|
|
void MediaFormatReader::ReleaseResources()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2016-01-22 03:01:50 +00:00
|
|
|
mVideo.ShutdownDecoder();
|
2016-06-08 12:28:24 +00:00
|
|
|
mAudio.ShutdownDecoder();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
MediaFormatReader::VideoIsHardwareAccelerated() const
|
|
|
|
{
|
2015-07-20 19:14:37 +00:00
|
|
|
return mVideo.mIsHardwareAccelerated;
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-11-02 13:28:57 +00:00
|
|
|
MediaFormatReader::NotifyDemuxer()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
|
2015-10-07 01:00:52 +00:00
|
|
|
if (mShutdown || !mDemuxer ||
|
|
|
|
(!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
|
2015-05-18 05:40:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-06 05:56:47 +00:00
|
|
|
LOGV("");
|
|
|
|
|
2015-10-26 06:10:29 +00:00
|
|
|
mDemuxer->NotifyDataArrived();
|
|
|
|
|
2015-09-15 03:03:48 +00:00
|
|
|
if (!mInitDone) {
|
|
|
|
return;
|
|
|
|
}
|
2015-05-18 05:40:32 +00:00
|
|
|
if (HasVideo()) {
|
|
|
|
mVideo.mReceivedNewData = true;
|
|
|
|
ScheduleUpdate(TrackType::kVideoTrack);
|
|
|
|
}
|
|
|
|
if (HasAudio()) {
|
|
|
|
mAudio.mReceivedNewData = true;
|
|
|
|
ScheduleUpdate(TrackType::kAudioTrack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-11-02 13:28:57 +00:00
|
|
|
MediaFormatReader::NotifyDataArrivedInternal()
|
2015-05-18 05:40:32 +00:00
|
|
|
{
|
2015-06-16 22:37:48 +00:00
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
2015-11-02 13:28:57 +00:00
|
|
|
NotifyDemuxer();
|
2015-05-18 05:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-06-06 21:42:40 +00:00
|
|
|
bool
|
|
|
|
MediaFormatReader::ForceZeroStartTime() const
|
|
|
|
{
|
|
|
|
return !mDemuxer->ShouldComputeStartTime();
|
|
|
|
}
|
|
|
|
|
2015-10-15 00:04:00 +00:00
|
|
|
layers::ImageContainer*
|
|
|
|
MediaFormatReader::GetImageContainer()
|
|
|
|
{
|
|
|
|
return mVideoFrameContainer
|
|
|
|
? mVideoFrameContainer->GetImageContainer() : nullptr;
|
|
|
|
}
|
|
|
|
|
2016-01-17 23:21:59 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::GetMozDebugReaderData(nsAString& aString)
|
|
|
|
{
|
|
|
|
nsAutoCString result;
|
2016-01-22 03:01:50 +00:00
|
|
|
const char* audioName = "unavailable";
|
|
|
|
const char* videoName = audioName;
|
|
|
|
|
|
|
|
if (HasAudio()) {
|
|
|
|
MonitorAutoLock mon(mAudio.mMonitor);
|
|
|
|
audioName = mAudio.mDescription;
|
|
|
|
}
|
|
|
|
if (HasVideo()) {
|
|
|
|
MonitorAutoLock mon(mVideo.mMonitor);
|
|
|
|
videoName = mVideo.mDescription;
|
|
|
|
}
|
|
|
|
|
|
|
|
result += nsPrintfCString("audio decoder: %s\n", audioName);
|
|
|
|
result += nsPrintfCString("audio frames decoded: %lld\n",
|
|
|
|
mAudio.mNumSamplesOutputTotal);
|
2016-05-05 05:11:21 +00:00
|
|
|
if (HasAudio()) {
|
2016-09-01 09:25:54 +00:00
|
|
|
result += nsPrintfCString("audio state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
|
2016-05-05 05:11:21 +00:00
|
|
|
NeedInput(mAudio), mAudio.HasPromise(),
|
2016-09-01 09:25:54 +00:00
|
|
|
mAudio.mDecodePending,
|
2016-05-05 05:11:21 +00:00
|
|
|
mAudio.mDemuxRequest.Exists(),
|
|
|
|
int(mAudio.mQueuedSamples.Length()),
|
|
|
|
mAudio.mTimeThreshold
|
2016-05-18 03:48:05 +00:00
|
|
|
? mAudio.mTimeThreshold.ref().Time().ToSeconds()
|
2016-05-05 05:11:21 +00:00
|
|
|
: -1.0,
|
|
|
|
mAudio.mTimeThreshold
|
|
|
|
? mAudio.mTimeThreshold.ref().mHasSeeked
|
|
|
|
: -1,
|
|
|
|
mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
|
|
|
|
unsigned(size_t(mAudio.mSizeOfQueue)),
|
|
|
|
unsigned(mAudio.mOutput.Length()),
|
|
|
|
mAudio.mWaitingForData, mAudio.mLastStreamSourceID);
|
|
|
|
}
|
2016-01-22 03:01:50 +00:00
|
|
|
result += nsPrintfCString("video decoder: %s\n", videoName);
|
2016-01-17 23:21:59 +00:00
|
|
|
result += nsPrintfCString("hardware video decoding: %s\n",
|
|
|
|
VideoIsHardwareAccelerated() ? "enabled" : "disabled");
|
2016-01-22 03:01:50 +00:00
|
|
|
result += nsPrintfCString("video frames decoded: %lld (skipped:%lld)\n",
|
2016-01-17 23:21:59 +00:00
|
|
|
mVideo.mNumSamplesOutputTotal,
|
|
|
|
mVideo.mNumSamplesSkippedTotal);
|
2016-05-05 05:11:21 +00:00
|
|
|
if (HasVideo()) {
|
2016-09-01 09:25:54 +00:00
|
|
|
result += nsPrintfCString("video state: ni=%d no=%d ie=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%llu out:%llu qs=%u pending:%u waiting:%d sid:%u\n",
|
2016-05-05 05:11:21 +00:00
|
|
|
NeedInput(mVideo), mVideo.HasPromise(),
|
2016-09-01 09:25:54 +00:00
|
|
|
mVideo.mDecodePending,
|
2016-05-05 05:11:21 +00:00
|
|
|
mVideo.mDemuxRequest.Exists(),
|
|
|
|
int(mVideo.mQueuedSamples.Length()),
|
|
|
|
mVideo.mTimeThreshold
|
2016-05-18 03:48:05 +00:00
|
|
|
? mVideo.mTimeThreshold.ref().Time().ToSeconds()
|
2016-05-05 05:11:21 +00:00
|
|
|
: -1.0,
|
|
|
|
mVideo.mTimeThreshold
|
|
|
|
? mVideo.mTimeThreshold.ref().mHasSeeked
|
|
|
|
: -1,
|
|
|
|
mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
|
|
|
|
unsigned(size_t(mVideo.mSizeOfQueue)),
|
|
|
|
unsigned(mVideo.mOutput.Length()),
|
|
|
|
mVideo.mWaitingForData, mVideo.mLastStreamSourceID);
|
|
|
|
}
|
2016-01-17 23:21:59 +00:00
|
|
|
aString += NS_ConvertUTF8toUTF16(result);
|
|
|
|
}
|
|
|
|
|
2016-07-29 06:51:18 +00:00
|
|
|
void
|
|
|
|
MediaFormatReader::SetVideoBlankDecode(bool aIsBlankDecode)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
return SetBlankDecode(TrackType::kVideoTrack, aIsBlankDecode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaFormatReader::SetBlankDecode(TrackType aTrack, bool aIsBlankDecode)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(OnTaskQueue());
|
|
|
|
auto& decoder = GetDecoderData(aTrack);
|
|
|
|
|
|
|
|
LOG("%s, decoder.mIsBlankDecode = %d => aIsBlankDecode = %d",
|
|
|
|
TrackTypeToStr(aTrack), decoder.mIsBlankDecode, aIsBlankDecode);
|
|
|
|
|
|
|
|
if (decoder.mIsBlankDecode == aIsBlankDecode) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder.mIsBlankDecode = aIsBlankDecode;
|
|
|
|
decoder.Flush();
|
|
|
|
decoder.ShutdownDecoder();
|
2016-09-01 09:25:54 +00:00
|
|
|
ScheduleUpdate(TrackInfo::kVideoTrack);
|
2016-07-29 06:51:18 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-18 05:40:32 +00:00
|
|
|
} // namespace mozilla
|