Bug 734546: Add DASH Decoders and Readers r=cpearce r=ted

This commit is contained in:
Steve Workman 2012-09-29 16:29:04 -07:00
parent b2049ebc9e
commit 1de10bcca3
25 changed files with 2300 additions and 60 deletions

View File

@ -4181,6 +4181,7 @@ MOZ_SAMPLE_TYPE_S16=
MOZ_MEDIA=
MOZ_OPUS=1
MOZ_WEBM=1
MOZ_DASH=
MOZ_WEBRTC=1
MOZ_SRTP=
MOZ_WEBRTC_SIGNALING=
@ -5271,6 +5272,24 @@ if test -n "$MOZ_WEBM"; then
MOZ_VP8=1
fi;
dnl ========================================================
dnl = Enable DASH-WebM support
dnl ========================================================
MOZ_ARG_ENABLE_BOOL(dash,
[ --enable-dash Enable support for DASH-WebM],
MOZ_DASH=1,
MOZ_DASH=)
if test -n "$MOZ_DASH"; then
if test -n "$MOZ_WEBM"; then
AC_DEFINE(MOZ_DASH)
else
dnl Fail if WebM is not enabled as well as DASH.
AC_MSG_ERROR([WebM is currently disabled and must be enabled for DASH
to work.])
fi
fi;
dnl ========================================================
dnl = Enable media plugin support
dnl ========================================================
@ -8523,6 +8542,7 @@ AC_SUBST(MOZ_VORBIS)
AC_SUBST(MOZ_TREMOR)
AC_SUBST(MOZ_OPUS)
AC_SUBST(MOZ_WEBM)
AC_SUBST(MOZ_DASH)
AC_SUBST(MOZ_MEDIA_PLUGINS)
AC_SUBST(MOZ_OMX_PLUGIN)
AC_SUBST(MOZ_VP8_ERROR_CONCEALMENT)

View File

@ -33,6 +33,9 @@ typedef uint16_t nsMediaReadyState;
namespace mozilla {
class MediaResource;
}
#ifdef MOZ_DASH
class nsDASHDecoder;
#endif
class nsHTMLMediaElement : public nsGenericHTMLElement,
public nsIObserver
@ -46,6 +49,10 @@ public:
typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
#ifdef MOZ_DASH
friend class nsDASHDecoder;
#endif
enum CanPlayStatus {
CANPLAY_NO,
CANPLAY_MAYBE,
@ -319,6 +326,12 @@ public:
static bool IsMediaPluginsType(const nsACString& aType);
#endif
#ifdef MOZ_DASH
static bool IsDASHEnabled();
static bool IsDASHMPDType(const nsACString& aType);
static const char gDASHMPDTypes[1][21];
#endif
/**
* Get the mime type for this element.
*/

View File

@ -91,6 +91,9 @@
#ifdef MOZ_WIDGET_GONK
#include "nsMediaOmxDecoder.h"
#endif
#ifdef MOZ_DASH
#include "nsDASHDecoder.h"
#endif
#ifdef PR_LOGGING
static PRLogModuleInfo* gMediaElementLog;
@ -2221,6 +2224,37 @@ nsHTMLMediaElement::IsMediaPluginsType(const nsACString& aType)
}
#endif
#ifdef MOZ_DASH
/* static */
const char nsHTMLMediaElement::gDASHMPDTypes[1][21] = {
"application/dash+xml"
};
/* static */
bool
nsHTMLMediaElement::IsDASHEnabled()
{
return Preferences::GetBool("media.dash.enabled");
}
/* static */
bool
nsHTMLMediaElement::IsDASHMPDType(const nsACString& aType)
{
if (!IsDASHEnabled()) {
return false;
}
for (uint32_t i = 0; i < ArrayLength(gDASHMPDTypes); ++i) {
if (aType.EqualsASCII(gDASHMPDTypes[i])) {
return true;
}
}
return false;
}
#endif
/* static */
nsHTMLMediaElement::CanPlayStatus
nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
@ -2250,6 +2284,13 @@ nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
return CANPLAY_YES;
}
#endif
#ifdef MOZ_DASH
if (IsDASHMPDType(nsDependentCString(aMIMEType))) {
// DASH manifest uses WebM codecs only.
*aCodecList = gWebMCodecs;
return CANPLAY_YES;
}
#endif
#ifdef MOZ_GSTREAMER
if (IsH264Type(nsDependentCString(aMIMEType))) {
@ -2439,6 +2480,15 @@ nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
}
#endif
#ifdef MOZ_DASH
if (IsDASHMPDType(aType)) {
nsRefPtr<nsDASHDecoder> decoder = new nsDASHDecoder();
if (decoder->Init(this)) {
return decoder.forget();
}
}
#endif
#ifdef MOZ_GSTREAMER
if (IsH264Type(aType)) {
nsRefPtr<nsGStreamerDecoder> decoder = new nsGStreamerDecoder();

View File

@ -84,6 +84,10 @@ ifdef MOZ_GSTREAMER
PARALLEL_DIRS += gstreamer
endif
ifdef MOZ_DASH
PARALLEL_DIRS += dash
endif
ifdef MOZ_MEDIA_PLUGINS
PARALLEL_DIRS += plugins
endif

View File

@ -981,7 +981,6 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
if (mByteRangeDownloads) {
// Query decoder for chunk containing desired offset.
// XXX Implement |nsDASHRepDecoder|::|GetByteRange| in future patch.
nsresult rv;
{
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);

View File

@ -0,0 +1,44 @@
# -*- Mode: makefile; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- #
# vim: set ts=2 et sw=2 tw=80: #
#
# 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/.
#
# Contributor(s):
# Steve Workman <sworkman@mozilla.com>
DEPTH := @DEPTH@
topsrcdir := @top_srcdir@
srcdir := @srcdir@
VPATH := @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE := content
LIBRARY_NAME := gkcondash_s
LIBXUL_LIBRARY := 1
EXPORTS := \
nsDASHDecoder.h \
nsDASHRepDecoder.h \
nsDASHReader.h \
$(NULL)
CPPSRCS := \
nsDASHDecoder.cpp \
nsDASHRepDecoder.cpp \
nsDASHReader.cpp \
$(NULL)
FORCE_STATIC_LIB := 1
include $(topsrcdir)/config/rules.mk
LOCAL_INCLUDES := \
-I$(topsrcdir)/netwerk/dash/mpd \
-I$(srcdir)/../webm \
-I$(srcdir)/../../base/src \
-I$(srcdir)/../../html/content/src \
$(MOZ_LIBVPX_INCLUDES) \
$(NULL)

View File

@ -0,0 +1,725 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP.
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* Interaction with nsBuiltinDecoderStateMachine, nsHTMLMediaElement,
* ChannelMediaResource and sub-decoders (nsWebMDecoder).
*
*
* nsBuiltinDecoderStateMachine nsHTMLMediaElement
* 1 / \ 1 / 1
* / \ /
* 1 / \ 1 / 1
* nsDASHReader ------ nsDASHDecoder ------------ ChannelMediaResource
* |1 1 1 |1 \1 (for MPD Manifest)
* | | ------------
* |* |* \*
* nsWebMReader ------- nsDASHRepDecoder ------- ChannelMediaResource
* 1 1 1 1 (for media streams)
*
* One decoder and state-machine, as with current, non-DASH decoders.
*
* DASH adds multiple readers, decoders and resources, in order to manage
* download and decode of the MPD manifest and individual media streams.
*
* Rep/|Representation| is for an individual media stream, e.g. audio
* nsDASHRepDecoder is the decoder for a rep/|Representation|.
*
* FLOW
*
* 1 - Download and parse the MPD (DASH XML-based manifest).
*
* Media element creates new |nsDASHDecoder| object:
* member var initialization to default values, including a/v sub-decoders.
* nsBuiltinDecoder and nsMediaDecoder constructors are called.
* nsBuiltinDecoder::Init() is called.
*
* Media element creates new |ChannelMediaResource|:
* used to download MPD manifest.
*
* Media element calls |nsDASHDecoder|->Load() to download the MPD file:
* creates an |nsDASHReader| object to forward calls to multiple
* nsWebMReaders (corresponding to MPD |Representation|s i.e. streams).
* Note: 1 |nsDASHReader| per DASH/WebM MPD.
*
* also calls |ChannelMediaResource|::Open().
* uses nsHttpChannel to download MPD; notifies nsDASHDecoder.
*
* Meanwhile, back in |nsDASHDecoder|->Load():
* nsBuiltinDecoderStateMachine is created.
* has ref to |nsDASHReader| object.
* state machine is scheduled.
*
* Media element finishes decoder setup:
* element added to media URI table etc.
*
* -- At this point, objects are waiting on HTTP returning MPD data.
*
* MPD Download (Async |ChannelMediaResource| channel callbacks):
* calls nsDASHDecoder::|NotifyDownloadEnded|().
* nsDASHDecoder parses MPD XML to DOM to MPD classes.
* gets |Segment| URLs from MPD for audio and video streams.
* creates |nsIChannel|s, |ChannelMediaResource|s.
* stores resources as member vars (to forward function calls later).
* creates |nsWebMReader|s and |nsDASHRepDecoder|s.
* DASHreader creates |nsWebMReader|s.
* |Representation| decoders are connected to the |ChannelMediaResource|s.
*
* |nsDASHDecoder|->|LoadRepresentations|() starts download and decode.
*
*
* 2 - Media Stream, Byte Range downloads.
*
* -- At this point the Segment media stream downloads are managed by
* individual |ChannelMediaResource|s and |nsWebMReader|s.
* A single |nsDASHDecoder| and |nsBuiltinDecoderStateMachine| manage them
* and communicate to |nsHTMLMediaElement|.
*
* Each |nsDASHRepDecoder| gets init range and index range from its MPD
* |Representation|. |nsDASHRepDecoder| uses ChannelMediaResource to start the
* byte range downloads, calling |OpenByteRange| with a |MediaByteRange|
* object.
* Once the init and index segments have been downloaded and |ReadMetadata| has
* completed, each |nsWebMReader| notifies it's peer |nsDASHRepDecoder|.
* Note: the decoder must wait until index data is parsed because it needs to
* get the offsets of the subsegments (WebM clusters) from the media file
* itself.
* Since byte ranges for subsegments are obtained, |nsDASHRepdecoder| continues
* downloading the files in byte range chunks.
*
* XXX Note that this implementation of nsDASHRepDecoder is focused on DASH
* WebM On Demand profile: on the todo list is an action item to make this
* more abstract.
*
* Note on |Seek|: Currently, |nsMediaCache| requires that seeking start at the
* beginning of the block in which the desired offset would be
* found. As such, when |ChannelMediaResource| does a seek
* using DASH WebM subsegments (clusters), it requests a start
* offset that corresponds to the beginning of the block, not
* the start offset of the cluster. For DASH Webm, which has
* media encoded in single files, this is fine. Future work on
* other profiles will require this to be re-examined.
*/
#include <limits>
#include <prdtoa.h>
#include "nsIURI.h"
#include "nsIFileURL.h"
#include "nsNetUtil.h"
#include "VideoUtils.h"
#include "nsThreadUtils.h"
#include "nsContentUtils.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsICachingChannel.h"
#include "nsBuiltinDecoderStateMachine.h"
#include "nsWebMDecoder.h"
#include "nsWebMReader.h"
#include "nsDASHReader.h"
#include "nsDASHMPDParser.h"
#include "nsDASHRepDecoder.h"
#include "nsDASHDecoder.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gBuiltinDecoderLog;
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHDecoder] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHDecoder] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
nsDASHDecoder::nsDASHDecoder() :
nsBuiltinDecoder(),
mNotifiedLoadAborted(false),
mBuffer(nullptr),
mBufferLength(0),
mMPDReaderThread(nullptr),
mPrincipal(nullptr),
mDASHReader(nullptr),
mAudioRepDecoder(nullptr),
mVideoRepDecoder(nullptr)
{
MOZ_COUNT_CTOR(nsDASHDecoder);
}
nsDASHDecoder::~nsDASHDecoder()
{
MOZ_COUNT_DTOR(nsDASHDecoder);
}
nsDecoderStateMachine*
nsDASHDecoder::CreateStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
return new nsBuiltinDecoderStateMachine(this, mDASHReader);
}
void
nsDASHDecoder::ReleaseStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
// Since state machine owns mDASHReader, remove reference to it.
mDASHReader = nullptr;
nsBuiltinDecoder::ReleaseStateMachine();
for (uint i = 0; i < mAudioRepDecoders.Length(); i++) {
mAudioRepDecoders[i]->ReleaseStateMachine();
}
for (uint i = 0; i < mVideoRepDecoders.Length(); i++) {
mVideoRepDecoders[i]->ReleaseStateMachine();
}
}
nsresult
nsDASHDecoder::Load(MediaResource* aResource,
nsIStreamListener** aStreamListener,
nsMediaDecoder* aCloneDonor)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mDASHReader = new nsDASHReader(this);
nsresult rv = OpenResource(aResource, aStreamListener);
NS_ENSURE_SUCCESS(rv, rv);
mDecoderStateMachine = CreateStateMachine();
if (!mDecoderStateMachine) {
LOG1("Failed to create state machine!");
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void
nsDASHDecoder::NotifyDownloadEnded(nsresult aStatus)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Should be no download ended notification if MPD Manager exists.
if (mMPDManager) {
LOG("Network Error! Repeated MPD download notification but MPD Manager "
"[%p] already exists!", mMPDManager.get());
NetworkError();
return;
}
if (NS_SUCCEEDED(aStatus)) {
LOG1("MPD downloaded.");
// mPrincipal must be set on main thread before dispatch to parser thread.
mPrincipal = GetCurrentPrincipal();
// Create reader thread for |ChannelMediaResource|::|Read|.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsDASHDecoder::ReadMPDBuffer);
NS_ENSURE_TRUE(event, );
nsresult rv = NS_NewNamedThread("DASH MPD Reader",
getter_AddRefs(mMPDReaderThread),
event,
MEDIA_THREAD_STACK_SIZE);
if (NS_FAILED(rv) || !mMPDReaderThread) {
LOG("Error creating MPD reader thread: rv[%x] thread [%p].",
rv, mMPDReaderThread.get());
DecodeError();
return;
}
} else if (aStatus == NS_BINDING_ABORTED) {
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
if (mElement) {
mElement->LoadAborted();
}
return;
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
NetworkError();
}
}
void
nsDASHDecoder::ReadMPDBuffer()
{
NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
LOG1("Started reading from the MPD buffer.");
int64_t length = mResource->GetLength();
if (length <= 0 || length > DASH_MAX_MPD_SIZE) {
LOG("MPD is larger than [%d]MB.", DASH_MAX_MPD_SIZE/(1024*1024));
DecodeError();
return;
}
mBuffer = new char[length];
uint32_t count = 0;
nsresult rv = mResource->Read(mBuffer, length, &count);
// By this point, all bytes should be available for reading.
if (NS_FAILED(rv) || count != length) {
LOG("Error reading MPD buffer: rv [%x] count [%d] length [%d].",
rv, count, length);
DecodeError();
return;
}
// Store buffer length for processing on main thread.
mBufferLength = static_cast<uint32_t>(length);
LOG1("Finished reading MPD buffer; back to main thread for parsing.");
// Dispatch event to Main thread to parse MPD buffer.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsDASHDecoder::OnReadMPDBufferCompleted);
rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
DecodeError();
return;
}
}
void
nsDASHDecoder::OnReadMPDBufferCompleted()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mShuttingDown) {
return;
}
// Shutdown the thread.
if (!mMPDReaderThread) {
LOG1("Error: MPD reader thread does not exist!");
DecodeError();
return;
}
nsresult rv = mMPDReaderThread->Shutdown();
if (NS_FAILED(rv)) {
LOG("MPD reader thread did not shutdown correctly! rv [%x]", rv);
DecodeError();
return;
}
mMPDReaderThread = nullptr;
// Close the MPD resource.
rv = mResource ? mResource->Close() : NS_ERROR_NULL_POINTER;
if (NS_FAILED(rv)) {
LOG("Media Resource did not close correctly! rv [%x]", rv);
NetworkError();
return;
}
// Start parsing the MPD data and loading the media.
rv = ParseMPDBuffer();
if (NS_FAILED(rv)) {
LOG("Error parsing MPD buffer! rv [%x]", rv);
DecodeError();
return;
}
rv = CreateRepDecoders();
if (NS_FAILED(rv)) {
LOG("Error creating decoders for Representations! rv [%x]", rv);
DecodeError();
return;
}
rv = LoadRepresentations();
if (NS_FAILED(rv)) {
LOG("Error loading Representations! rv [%x]", rv);
NetworkError();
return;
}
// Notify reader that it can start reading metadata. Sub-readers will still
// block until sub-resources have downloaded data into the media cache.
mDASHReader->ReadyToReadMetadata();
}
nsresult
nsDASHDecoder::ParseMPDBuffer()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(mBuffer, NS_ERROR_NULL_POINTER);
LOG1("Started parsing the MPD buffer.");
// Parse MPD buffer and get root DOM element.
nsAutoPtr<nsDASHMPDParser> parser;
parser = new nsDASHMPDParser(mBuffer.forget(), mBufferLength, mPrincipal,
mResource->URI());
mozilla::net::DASHMPDProfile profile;
parser->Parse(getter_Transfers(mMPDManager), &profile);
mBuffer = nullptr;
NS_ENSURE_TRUE(mMPDManager, NS_ERROR_NULL_POINTER);
LOG("Finished parsing the MPD buffer. Profile is [%d].", profile);
return NS_OK;
}
nsresult
nsDASHDecoder::CreateRepDecoders()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(mMPDManager, NS_ERROR_NULL_POINTER);
// Global settings for the presentation.
int64_t startTime = mMPDManager->GetStartTime();
mDuration = mMPDManager->GetDuration();
NS_ENSURE_TRUE(startTime >= 0 && mDuration > 0, NS_ERROR_ILLEGAL_VALUE);
// For each audio/video stream, create a |ChannelMediaResource| object.
for (int i = 0; i < mMPDManager->GetNumAdaptationSets(); i++) {
IMPDManager::AdaptationSetType asType = mMPDManager->GetAdaptationSetType(i);
for (int j = 0; j < mMPDManager->GetNumRepresentations(i); j++) {
// Get URL string.
nsAutoString segmentUrl;
nsresult rv = mMPDManager->GetFirstSegmentUrl(i, j, segmentUrl);
NS_ENSURE_SUCCESS(rv, rv);
// Get segment |nsIURI|; use MPD's base URI in case of relative paths.
nsCOMPtr<nsIURI> url;
rv = NS_NewURI(getter_AddRefs(url), segmentUrl, nullptr, mResource->URI());
NS_ENSURE_SUCCESS(rv, rv);
#ifdef PR_LOGGING
nsAutoCString newUrl;
rv = url->GetSpec(newUrl);
NS_ENSURE_SUCCESS(rv, rv);
LOG("Using URL=\"%s\" for AdaptationSet [%d] Representation [%d]",
newUrl.get(), i, j);
#endif
// 'file://' URLs are not supported.
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url);
NS_ENSURE_FALSE(fileURL, NS_ERROR_ILLEGAL_VALUE);
// Create |nsDASHRepDecoder| objects for each representation.
if (asType == IMPDManager::DASH_VIDEO_STREAM) {
Representation const * rep = mMPDManager->GetRepresentation(i, j);
NS_ENSURE_TRUE(rep, NS_ERROR_NULL_POINTER);
rv = CreateVideoRepDecoder(url, rep);
NS_ENSURE_SUCCESS(rv, rv);
} else if (asType == IMPDManager::DASH_AUDIO_STREAM) {
Representation const * rep = mMPDManager->GetRepresentation(i, j);
NS_ENSURE_TRUE(rep, NS_ERROR_NULL_POINTER);
rv = CreateAudioRepDecoder(url, rep);
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
NS_ENSURE_TRUE(mVideoRepDecoder, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mAudioRepDecoder, NS_ERROR_NOT_INITIALIZED);
return NS_OK;
}
nsresult
nsDASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
mozilla::net::Representation const * aRep)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_ARG(aUrl);
NS_ENSURE_ARG(aRep);
NS_ENSURE_TRUE(mElement, NS_ERROR_NOT_INITIALIZED);
// Create subdecoder and init with media element.
nsDASHRepDecoder* audioDecoder = new nsDASHRepDecoder(this);
NS_ENSURE_TRUE(audioDecoder->Init(mElement), NS_ERROR_NOT_INITIALIZED);
if (!mAudioRepDecoder) {
mAudioRepDecoder = audioDecoder;
}
mAudioRepDecoders.AppendElement(audioDecoder);
// Create sub-reader; attach to DASH reader and sub-decoder.
nsWebMReader* audioReader = new nsWebMReader(audioDecoder);
if (mDASHReader) {
mDASHReader->AddAudioReader(audioReader);
}
audioDecoder->SetReader(audioReader);
// Create media resource with URL and connect to sub-decoder.
MediaResource* audioResource
= CreateAudioSubResource(aUrl, static_cast<nsMediaDecoder*>(audioDecoder));
NS_ENSURE_TRUE(audioResource, NS_ERROR_NOT_INITIALIZED);
audioDecoder->SetResource(audioResource);
audioDecoder->SetMPDRepresentation(aRep);
return NS_OK;
}
nsresult
nsDASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
mozilla::net::Representation const * aRep)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_ARG(aUrl);
NS_ENSURE_ARG(aRep);
NS_ENSURE_TRUE(mElement, NS_ERROR_NOT_INITIALIZED);
// Create subdecoder and init with media element.
nsDASHRepDecoder* videoDecoder = new nsDASHRepDecoder(this);
NS_ENSURE_TRUE(videoDecoder->Init(mElement), NS_ERROR_NOT_INITIALIZED);
if (!mVideoRepDecoder) {
mVideoRepDecoder = videoDecoder;
}
mVideoRepDecoders.AppendElement(videoDecoder);
// Create sub-reader; attach to DASH reader and sub-decoder.
nsWebMReader* videoReader = new nsWebMReader(videoDecoder);
if (mDASHReader) {
mDASHReader->AddVideoReader(videoReader);
}
videoDecoder->SetReader(videoReader);
// Create media resource with URL and connect to sub-decoder.
MediaResource* videoResource
= CreateVideoSubResource(aUrl, static_cast<nsMediaDecoder*>(videoDecoder));
NS_ENSURE_TRUE(videoResource, NS_ERROR_NOT_INITIALIZED);
videoDecoder->SetResource(videoResource);
videoDecoder->SetMPDRepresentation(aRep);
return NS_OK;
}
mozilla::MediaResource*
nsDASHDecoder::CreateAudioSubResource(nsIURI* aUrl,
nsMediaDecoder* aAudioDecoder)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aUrl, nullptr);
NS_ENSURE_TRUE(aAudioDecoder, nullptr);
// Create channel for representation.
nsCOMPtr<nsIChannel> channel;
nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, nullptr);
// Create resource for representation.
MediaResource* audioResource
= MediaResource::Create(aAudioDecoder, channel);
NS_ENSURE_TRUE(audioResource, nullptr);
return audioResource;
}
mozilla::MediaResource*
nsDASHDecoder::CreateVideoSubResource(nsIURI* aUrl,
nsMediaDecoder* aVideoDecoder)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aUrl, nullptr);
NS_ENSURE_TRUE(aVideoDecoder, nullptr);
// Create channel for representation.
nsCOMPtr<nsIChannel> channel;
nsresult rv = CreateSubChannel(aUrl, getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, nullptr);
// Create resource for representation.
MediaResource* videoResource
= MediaResource::Create(aVideoDecoder, channel);
NS_ENSURE_TRUE(videoResource, nullptr);
return videoResource;
}
nsresult
nsDASHDecoder::CreateSubChannel(nsIURI* aUrl, nsIChannel** aChannel)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_ARG(aUrl);
nsCOMPtr<nsILoadGroup> loadGroup = mElement->GetDocumentLoadGroup();
NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
// Check for a Content Security Policy to pass down to the channel
// created to load the media content.
nsCOMPtr<nsIChannelPolicy> channelPolicy;
nsCOMPtr<nsIContentSecurityPolicy> csp;
nsresult rv = mElement->NodePrincipal()->GetCsp(getter_AddRefs(csp));
NS_ENSURE_SUCCESS(rv,rv);
if (csp) {
channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
channelPolicy->SetContentSecurityPolicy(csp);
channelPolicy->SetLoadType(nsIContentPolicy::TYPE_MEDIA);
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannel(getter_AddRefs(channel),
aUrl,
nullptr,
loadGroup,
nullptr,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY,
channelPolicy);
NS_ENSURE_SUCCESS(rv,rv);
NS_ENSURE_TRUE(channel, NS_ERROR_NULL_POINTER);
NS_ADDREF(*aChannel = channel);
return NS_OK;
}
nsresult
nsDASHDecoder::LoadRepresentations()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
nsresult rv;
{
// Hold the lock while we do this to set proper lock ordering
// expectations for dynamic deadlock detectors: decoder lock(s)
// should be grabbed before the cache lock.
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
// Load the decoders for each |Representation|'s media streams.
if (mAudioRepDecoder) {
rv = mAudioRepDecoder->Load();
NS_ENSURE_SUCCESS(rv, rv);
}
if (mVideoRepDecoder) {
rv = mVideoRepDecoder->Load();
NS_ENSURE_SUCCESS(rv, rv);
}
if (NS_FAILED(rv)) {
LOG("Failed to open stream! rv [%x].", rv);
return rv;
}
}
if (mAudioRepDecoder) {
mAudioRepDecoder->SetStateMachine(mDecoderStateMachine);
}
if (mVideoRepDecoder) {
mVideoRepDecoder->SetStateMachine(mDecoderStateMachine);
}
// Now that subreaders are init'd, it's ok to init state machine.
return InitializeStateMachine(nullptr);
}
void
nsDASHDecoder::NotifyDownloadEnded(nsDASHRepDecoder* aRepDecoder,
nsresult aStatus,
MediaByteRange &aRange)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// MPD Manager must exist, indicating MPD has been downloaded and parsed.
if (!mMPDManager) {
LOG1("Network Error! MPD Manager must exist, indicating MPD has been "
"downloaded and parsed");
NetworkError();
return;
}
// Decoder for the media |Representation| must not be null.
if (!aRepDecoder) {
LOG1("Decoder for Representation is reported as null.");
DecodeError();
return;
}
if (NS_SUCCEEDED(aStatus)) {
// Return error if |aRepDecoder| does not match current audio/video decoder.
if (aRepDecoder != mAudioRepDecoder && aRepDecoder != mVideoRepDecoder) {
LOG("Error! Decoder [%p] does not match current sub-decoders!",
aRepDecoder);
DecodeError();
return;
}
LOG("Byte range downloaded: decoder [%p] range requested [%d - %d]",
aRepDecoder, aRange.mStart, aRange.mEnd);
// XXX Do Stream Switching here before loading next bytes, e.g.
// decoder = PossiblySwitchDecoder(aRepDecoder);
// decoder->LoadNextByteRange();
aRepDecoder->LoadNextByteRange();
return;
} else if (aStatus == NS_BINDING_ABORTED) {
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
if (mElement) {
mElement->LoadAborted();
}
return;
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
NetworkError();
}
}
void
nsDASHDecoder::LoadAborted()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (!mNotifiedLoadAborted && mElement) {
mElement->LoadAborted();
mNotifiedLoadAborted = true;
LOG1("Load Aborted! Notifying media element.");
}
}
void
nsDASHDecoder::Shutdown()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Notify reader of shutdown first.
if (mDASHReader) {
mDASHReader->NotifyDecoderShuttingDown();
}
// Call parent class shutdown.
nsBuiltinDecoder::Shutdown();
NS_ENSURE_TRUE(mShuttingDown, );
// Shutdown reader thread if not already done.
if (mMPDReaderThread) {
nsresult rv = mMPDReaderThread->Shutdown();
NS_ENSURE_SUCCESS(rv, );
mMPDReaderThread = nullptr;
}
// Forward to sub-decoders.
for (uint i = 0; i < mAudioRepDecoders.Length(); i++) {
if (mAudioRepDecoders[i]) {
mAudioRepDecoders[i]->Shutdown();
}
}
for (uint i = 0; i < mVideoRepDecoders.Length(); i++) {
if (mVideoRepDecoders[i]) {
mVideoRepDecoders[i]->Shutdown();
}
}
}
void
nsDASHDecoder::DecodeError()
{
if (NS_IsMainThread()) {
nsBuiltinDecoder::DecodeError();
} else {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsBuiltinDecoder::DecodeError);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching DecodeError event to main thread: rv[%x]", rv);
}
}
}

View File

@ -0,0 +1,156 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
#if !defined(nsDASHDecoder_h_)
#define nsDASHDecoder_h_
#include "nsTArray.h"
#include "nsIURI.h"
#include "nsITimer.h"
#include "nsThreadUtils.h"
#include "nsBuiltinDecoder.h"
#include "nsDASHReader.h"
class nsDASHRepDecoder;
namespace mozilla {
namespace net {
class IMPDManager;
class nsDASHMPDParser;
class Representation;
}// net
}// mozilla
class nsDASHDecoder : public nsBuiltinDecoder
{
public:
typedef class mozilla::net::IMPDManager IMPDManager;
typedef class mozilla::net::nsDASHMPDParser nsDASHMPDParser;
typedef class mozilla::net::Representation Representation;
// XXX Arbitrary max file size for MPD. 50MB seems generously large.
static const uint32_t DASH_MAX_MPD_SIZE = 50*1024*1024;
nsDASHDecoder();
~nsDASHDecoder();
// Clone not supported; just return nullptr.
nsMediaDecoder* Clone() { return nullptr; }
// Creates a single state machine for all stream decoders.
// Called from Load on the main thread only.
nsDecoderStateMachine* CreateStateMachine();
// Loads the MPD from the network and subsequently loads the media streams.
// Called from the main thread only.
nsresult Load(MediaResource* aResource,
nsIStreamListener** aListener,
nsMediaDecoder* aCloneDonor);
// Notifies download of MPD file has ended.
// Called on the main thread only.
void NotifyDownloadEnded(nsresult aStatus);
// Notifies that a byte range download has ended. As per the DASH spec, this
// allows for stream switching at the boundaries of the byte ranges.
// Called on the main thread only.
void NotifyDownloadEnded(nsDASHRepDecoder* aRepDecoder,
nsresult aStatus,
MediaByteRange &aRange);
// Drop reference to state machine and tell sub-decoders to do the same.
// Only called during shutdown dance, on main thread only.
void ReleaseStateMachine();
// Overridden to forward |Shutdown| to sub-decoders.
// Called on the main thread only.
void Shutdown();
// Called by sub-decoders when load has been aborted. Will notify media
// element only once. Called on the main thread only.
void LoadAborted();
// Notifies the element that decoding has failed. On main thread, call is
// forwarded to |nsBuiltinDecoder|::|Error| immediately. On other threads,
// a call is dispatched for execution on the main thread.
void DecodeError();
private:
// Reads the MPD data from resource to a byte stream.
// Called on the MPD reader thread.
void ReadMPDBuffer();
// Called when MPD data is completely read.
// On the main thread.
void OnReadMPDBufferCompleted();
// Parses the copied MPD byte stream.
// On the main thread: DOM APIs complain when off the main thread.
nsresult ParseMPDBuffer();
// Creates the sub-decoders for a |Representation|, i.e. media streams.
// On the main thread.
nsresult CreateRepDecoders();
// Creates audio/video decoders for individual |Representation|s.
// On the main thread.
nsresult CreateAudioRepDecoder(nsIURI* aUrl, Representation const * aRep);
nsresult CreateVideoRepDecoder(nsIURI* aUrl, Representation const * aRep);
// Creates audio/video resources for individual |Representation|s.
// On the main thread.
MediaResource* CreateAudioSubResource(nsIURI* aUrl,
nsMediaDecoder* aAudioDecoder);
MediaResource* CreateVideoSubResource(nsIURI* aUrl,
nsMediaDecoder* aVideoDecoder);
// Creates an http channel for a |Representation|.
// On the main thread.
nsresult CreateSubChannel(nsIURI* aUrl, nsIChannel** aChannel);
// Loads the media |Representations|, i.e. the media streams.
// On the main thread.
nsresult LoadRepresentations();
// True when media element has already been notified of an aborted load.
bool mNotifiedLoadAborted;
// Ptr for the MPD data.
nsAutoArrayPtr<char> mBuffer;
// Length of the MPD data.
uint32_t mBufferLength;
// Ptr to the MPD Reader thread.
nsCOMPtr<nsIThread> mMPDReaderThread;
// Document Principal.
nsCOMPtr<nsIPrincipal> mPrincipal;
// MPD Manager provides access to the MPD information.
nsAutoPtr<IMPDManager> mMPDManager;
// Main reader object; manages all sub-readers for |Representation|s. Owned by
// state machine; destroyed in state machine's destructor.
nsDASHReader* mDASHReader;
// Sub-decoder for current audio |Representation|.
nsRefPtr<nsDASHRepDecoder> mAudioRepDecoder;
// Array of pointers for the |Representation|s in the audio |AdaptationSet|.
nsTArray<nsRefPtr<nsDASHRepDecoder> > mAudioRepDecoders;
// Sub-decoder for current video |Representation|.
nsRefPtr<nsDASHRepDecoder> mVideoRepDecoder;
// Array of pointers for the |Representation|s in the video |AdaptationSet|.
nsTArray<nsRefPtr<nsDASHRepDecoder> > mVideoRepDecoders;
};
#endif

View File

@ -0,0 +1,329 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
#include "nsTimeRanges.h"
#include "VideoFrameContainer.h"
#include "nsBuiltinDecoder.h"
#include "nsDASHReader.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gBuiltinDecoderLog;
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHReader] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHReader] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
nsresult
nsDASHReader::Init(nsBuiltinDecoderReader* aCloneDonor)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(mAudioReaders.Length() != 0 && mVideoReaders.Length() != 0,
"Audio and video readers should exist already.");
nsresult rv;
for (uint i = 0; i < mAudioReaders.Length(); i++) {
rv = mAudioReaders[i]->Init(nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
for (uint i = 0; i < mVideoReaders.Length(); i++) {
rv = mVideoReaders[i]->Init(nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
void
nsDASHReader::AddAudioReader(nsBuiltinDecoderReader* aAudioReader)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aAudioReader, );
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mAudioReaders.AppendElement(aAudioReader);
// XXX For now, just pick the first reader to be default.
if (!mAudioReader)
mAudioReader = aAudioReader;
}
void
nsDASHReader::AddVideoReader(nsBuiltinDecoderReader* aVideoReader)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aVideoReader, );
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mVideoReaders.AppendElement(aVideoReader);
// XXX For now, just pick the first reader to be default.
if (!mVideoReader)
mVideoReader = aVideoReader;
}
int64_t
nsDASHReader::VideoQueueMemoryInUse()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
return (mVideoReader ? mVideoReader->VideoQueueMemoryInUse() : 0);
}
int64_t
nsDASHReader::AudioQueueMemoryInUse()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
return (mAudioReader ? mAudioReader->AudioQueueMemoryInUse() : 0);
}
bool
nsDASHReader::DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
if (mVideoReader) {
return mVideoReader->DecodeVideoFrame(aKeyframeSkip, aTimeThreshold);
} else {
return false;
}
}
bool
nsDASHReader::DecodeAudioData()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
return (mAudioReader ? mAudioReader->DecodeAudioData() : false);
}
nsresult
nsDASHReader::ReadMetadata(nsVideoInfo* aInfo,
nsHTMLMediaElement::MetadataTags** aTags)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
// Wait for MPD to be parsed and child readers created.
LOG1("Waiting for metadata download.");
nsresult rv = WaitForMetadata();
// If we get an abort, return silently; the decoder is shutting down.
if (NS_ERROR_ABORT == rv) {
return NS_OK;
}
// Verify no other errors before continuing.
NS_ENSURE_SUCCESS(rv, rv);
// Get metadata from child readers.
nsVideoInfo audioInfo, videoInfo;
if (mVideoReader) {
rv = mVideoReader->ReadMetadata(&videoInfo, aTags);
NS_ENSURE_SUCCESS(rv, rv);
mInfo.mHasVideo = videoInfo.mHasVideo;
mInfo.mDisplay = videoInfo.mDisplay;
}
if (mAudioReader) {
rv = mAudioReader->ReadMetadata(&audioInfo, aTags);
NS_ENSURE_SUCCESS(rv, rv);
mInfo.mHasAudio = audioInfo.mHasAudio;
mInfo.mAudioRate = audioInfo.mAudioRate;
mInfo.mAudioChannels = audioInfo.mAudioChannels;
mInfo.mStereoMode = audioInfo.mStereoMode;
}
*aInfo = mInfo;
return NS_OK;
}
nsresult
nsDASHReader::Seek(int64_t aTime,
int64_t aStartTime,
int64_t aEndTime,
int64_t aCurrentTime)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
nsresult rv;
if (mAudioReader) {
rv = mAudioReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mVideoReader) {
rv = mVideoReader->Seek(aTime, aStartTime, aEndTime, aCurrentTime);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsDASHReader::GetBuffered(nsTimeRanges* aBuffered,
int64_t aStartTime)
{
NS_ENSURE_ARG(aBuffered);
MediaResource* resource = nullptr;
nsBuiltinDecoder* decoder = nullptr;
// Need to find intersect of |nsTimeRanges| for audio and video.
nsTimeRanges audioBuffered, videoBuffered;
uint32_t audioRangeCount, videoRangeCount;
nsresult rv = NS_OK;
// First, get buffered ranges for sub-readers.
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
if (mAudioReader) {
decoder = mAudioReader->GetDecoder();
NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER);
resource = decoder->GetResource();
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
resource->Pin();
rv = mAudioReader->GetBuffered(&audioBuffered, aStartTime);
NS_ENSURE_SUCCESS(rv, rv);
resource->Unpin();
rv = audioBuffered.GetLength(&audioRangeCount);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mVideoReader) {
decoder = mVideoReader->GetDecoder();
NS_ENSURE_TRUE(decoder, NS_ERROR_NULL_POINTER);
resource = decoder->GetResource();
NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER);
resource->Pin();
rv = mVideoReader->GetBuffered(&videoBuffered, aStartTime);
NS_ENSURE_SUCCESS(rv, rv);
resource->Unpin();
rv = videoBuffered.GetLength(&videoRangeCount);
NS_ENSURE_SUCCESS(rv, rv);
}
// Now determine buffered data for available sub-readers.
if (mAudioReader && mVideoReader) {
// Calculate intersecting ranges.
for (uint32_t i = 0; i < audioRangeCount; i++) {
// |A|udio, |V|ideo, |I|ntersect.
double startA, startV, startI;
double endA, endV, endI;
rv = audioBuffered.Start(i, &startA);
NS_ENSURE_SUCCESS(rv, rv);
rv = audioBuffered.End(i, &endA);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t j = 0; j < videoRangeCount; j++) {
rv = videoBuffered.Start(i, &startV);
NS_ENSURE_SUCCESS(rv, rv);
rv = videoBuffered.End(i, &endV);
NS_ENSURE_SUCCESS(rv, rv);
// If video block is before audio block, compare next video block.
if (startA > endV) {
continue;
// If video block is after audio block, all of them are; compare next
// audio block.
} else if (endA < startV) {
break;
}
// Calculate intersections of current audio and video blocks.
startI = (startA > startV) ? startA : startV;
endI = (endA > endV) ? endV : endA;
aBuffered->Add(startI, endI);
}
}
} else if (mAudioReader) {
*aBuffered = audioBuffered;
} else if (mVideoReader) {
*aBuffered = videoBuffered;
} else {
return NS_ERROR_NOT_INITIALIZED;
}
return NS_OK;
}
VideoData*
nsDASHReader::FindStartTime(int64_t& aOutStartTime)
{
NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
"Should be on state machine or decode thread.");
// Extract the start times of the bitstreams in order to calculate
// the duration.
int64_t videoStartTime = INT64_MAX;
int64_t audioStartTime = INT64_MAX;
VideoData* videoData = nullptr;
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
if (HasVideo()) {
// Forward to video reader.
videoData
= mVideoReader->DecodeToFirstData(&nsBuiltinDecoderReader::DecodeVideoFrame,
VideoQueue());
if (videoData) {
videoStartTime = videoData->mTime;
}
}
if (HasAudio()) {
// Forward to audio reader.
AudioData* audioData
= mAudioReader->DecodeToFirstData(&nsBuiltinDecoderReader::DecodeAudioData,
AudioQueue());
if (audioData) {
audioStartTime = audioData->mTime;
}
}
int64_t startTime = NS_MIN(videoStartTime, audioStartTime);
if (startTime != INT64_MAX) {
aOutStartTime = startTime;
}
return videoData;
}
MediaQueue<AudioData>&
nsDASHReader::AudioQueue()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
NS_ASSERTION(mAudioReader, "mAudioReader is NULL!");
return mAudioReader->AudioQueue();
}
MediaQueue<VideoData>&
nsDASHReader::VideoQueue()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
return mVideoReader->VideoQueue();
}
bool
nsDASHReader::IsSeekableInBufferedRanges()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
// At least one subreader must exist, and all subreaders must return true.
return (mVideoReader || mAudioReader) &&
!((mVideoReader && !mVideoReader->IsSeekableInBufferedRanges()) ||
(mAudioReader && !mAudioReader->IsSeekableInBufferedRanges()));
}

View File

@ -0,0 +1,314 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* see nsDASHDecoder.cpp for comments on DASH object interaction
*/
#if !defined(nsDASHReader_h_)
#define nsDASHReader_h_
#include "nsBuiltinDecoderReader.h"
class nsDASHReader : public nsBuiltinDecoderReader
{
public:
typedef mozilla::MediaResource MediaResource;
nsDASHReader(nsBuiltinDecoder* aDecoder) :
nsBuiltinDecoderReader(aDecoder),
mReadMetadataMonitor("media.dashreader.readmetadata"),
mReadyToReadMetadata(false),
mDecoderIsShuttingDown(false),
mAudioReader(this),
mVideoReader(this),
mAudioReaders(this),
mVideoReaders(this)
{
MOZ_COUNT_CTOR(nsDASHReader);
}
~nsDASHReader()
{
MOZ_COUNT_DTOR(nsDASHReader);
}
// Adds a pointer to a audio/video reader for a media |Representation|.
// Called on the main thread only.
void AddAudioReader(nsBuiltinDecoderReader* aAudioReader);
void AddVideoReader(nsBuiltinDecoderReader* aVideoReader);
// Waits for metadata bytes to be downloaded, then reads and parses them.
// Called on the decode thread only.
nsresult ReadMetadata(nsVideoInfo* aInfo,
nsHTMLMediaElement::MetadataTags** aTags);
// Waits for |ReadyToReadMetadata| or |NotifyDecoderShuttingDown|
// notification, whichever comes first. Ensures no attempt to read metadata
// during |nsDASHDecoder|::|Shutdown|. Called on decode thread only.
nsresult WaitForMetadata() {
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
ReentrantMonitorAutoEnter mon(mReadMetadataMonitor);
while (true) {
// Abort if the decoder has started shutting down.
if (mDecoderIsShuttingDown) {
return NS_ERROR_ABORT;
} else if (mReadyToReadMetadata) {
break;
}
mon.Wait();
}
return NS_OK;
}
// Called on the main thread by |nsDASHDecoder| to notify that metadata bytes
// have been downloaded.
void ReadyToReadMetadata() {
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter mon(mReadMetadataMonitor);
mReadyToReadMetadata = true;
mon.NotifyAll();
}
// Called on the main thread by |nsDASHDecoder| when it starts Shutdown. Will
// wake metadata monitor if waiting for a silent return from |ReadMetadata|.
void NotifyDecoderShuttingDown() {
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter metadataMon(mReadMetadataMonitor);
mDecoderIsShuttingDown = true;
// Notify |ReadMetadata| of the shutdown if it's waiting.
metadataMon.NotifyAll();
}
// Audio/video status are dependent on the presence of audio/video readers.
// Call on decode thread only.
bool HasAudio() {
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
return mAudioReader ? mAudioReader->HasAudio() : false;
}
bool HasVideo() {
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
return mVideoReader ? mVideoReader->HasVideo() : false;
}
// Returns references to the audio/video queues of sub-readers. Called on
// decode, state machine and audio threads.
MediaQueue<AudioData>& AudioQueue();
MediaQueue<VideoData>& VideoQueue();
// Called from nsBuiltinDecoderStateMachine on the main thread.
nsresult Init(nsBuiltinDecoderReader* aCloneDonor);
// Used by |MediaMemoryReporter|.
int64_t VideoQueueMemoryInUse();
int64_t AudioQueueMemoryInUse();
// Called on the decode thread.
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
bool DecodeAudioData();
// Converts seek time to byte offset. Called on the decode thread only.
nsresult Seek(int64_t aTime,
int64_t aStartTime,
int64_t aEndTime,
int64_t aCurrentTime);
// Called by state machine on multiple threads.
nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
// Called on the state machine or decode threads.
VideoData* FindStartTime(int64_t& aOutStartTime);
// Call by state machine on multiple threads.
bool IsSeekableInBufferedRanges();
private:
// Similar to |ReentrantMonitorAutoEnter|, this class enters the supplied
// monitor in its constructor, but only if the conditional value |aEnter| is
// true. Used here to allow read access on the sub-readers' owning thread,
// i.e. the decode thread, while locking write accesses from all threads,
// and read accesses from non-decode threads.
class ReentrantMonitorConditionallyEnter
{
public:
ReentrantMonitorConditionallyEnter(bool aEnter,
ReentrantMonitor &aReentrantMonitor) :
mReentrantMonitor(nullptr)
{
MOZ_COUNT_CTOR(nsDASHReader::ReentrantMonitorConditionallyEnter);
if (aEnter) {
mReentrantMonitor = &aReentrantMonitor;
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->Enter();
}
}
~ReentrantMonitorConditionallyEnter(void)
{
if (mReentrantMonitor) {
mReentrantMonitor->Exit();
}
MOZ_COUNT_DTOR(nsDASHReader::ReentrantMonitorConditionallyEnter);
}
private:
// Restrict to constructor and destructor defined above.
ReentrantMonitorConditionallyEnter();
ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
static void* operator new(size_t) CPP_THROW_NEW;
static void operator delete(void*);
// Ptr to the |ReentrantMonitor| object. Null if |aEnter| in constructor
// was false.
ReentrantMonitor* mReentrantMonitor;
};
// Monitor and booleans used to wait for metadata bytes to be downloaded, and
// skip reading metadata if |nsDASHDecoder|'s shutdown is in progress.
ReentrantMonitor mReadMetadataMonitor;
bool mReadyToReadMetadata;
bool mDecoderIsShuttingDown;
// Wrapper class protecting accesses to sub-readers. Asserts that the
// decoder monitor has been entered for write access on all threads and read
// access on all threads that are not the decode thread. Read access on the
// decode thread does not need to be protected.
class MonitoredSubReader
{
public:
// Main constructor takes a pointer to the owning |nsDASHReader| to verify
// correct entry into the decoder's |ReentrantMonitor|.
MonitoredSubReader(nsDASHReader* aReader) :
mReader(aReader),
mSubReader(nullptr)
{
MOZ_COUNT_CTOR(nsDASHReader::MonitoredSubReader);
NS_ASSERTION(mReader, "Reader is null!");
}
// Note: |mSubReader|'s refcount will be decremented in this destructor.
~MonitoredSubReader()
{
MOZ_COUNT_DTOR(nsDASHReader::MonitoredSubReader);
}
// Override '=' to always assert thread is "in monitor" for writes/changes
// to |mSubReader|.
MonitoredSubReader& operator=(nsBuiltinDecoderReader* rhs)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
mSubReader = rhs;
return *this;
}
// Override '*' to assert threads other than the decode thread are "in
// monitor" for ptr reads.
operator nsBuiltinDecoderReader*() const
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
if (!mReader->GetDecoder()->OnDecodeThread()) {
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
}
return mSubReader;
}
// Override '->' to assert threads other than the decode thread are "in
// monitor" for |mSubReader| function calls.
nsBuiltinDecoderReader* operator->() const
{
return *this;
}
private:
// Pointer to |nsDASHReader| object which owns this |MonitoredSubReader|.
nsDASHReader* mReader;
// Ref ptr to the sub reader.
nsRefPtr<nsBuiltinDecoderReader> mSubReader;
};
// Wrapped ref ptrs to current sub-readers of individual media
// |Representation|s. Decoder monitor must be entered for write access on all
// threads and read access on all threads that are not the decode thread.
// Read access on the decode thread does not need to be protected.
// Note: |MonitoredSubReader| class will assert correct monitor use.
MonitoredSubReader mAudioReader;
MonitoredSubReader mVideoReader;
// Wrapper class protecting accesses to sub-reader list. Asserts that the
// decoder monitor has been entered for write access on all threads and read
// access on all threads that are not the decode thread. Read access on the
// decode thread does not need to be protected.
// Note: Elems accessed via operator[] are not protected with monitor
// assertion checks once obtained.
class MonitoredSubReaderList
{
public:
// Main constructor takes a pointer to the owning |nsDASHReader| to verify
// correct entry into the decoder's |ReentrantMonitor|.
MonitoredSubReaderList(nsDASHReader* aReader) :
mReader(aReader)
{
MOZ_COUNT_CTOR(nsDASHReader::MonitoredSubReaderList);
NS_ASSERTION(mReader, "Reader is null!");
}
// Note: Elements in |mSubReaderList| will have their refcounts decremented
// in this destructor.
~MonitoredSubReaderList()
{
MOZ_COUNT_DTOR(nsDASHReader::MonitoredSubReaderList);
}
// Returns Length of |mSubReaderList| array. Will assert threads other than
// the decode thread are "in monitor".
uint32_t Length() const
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
if (!mReader->GetDecoder()->OnDecodeThread()) {
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
}
return mSubReaderList.Length();
}
// Override '[]' to assert threads other than the decode thread are "in
// monitor" for accessing individual elems. Note: elems returned do not
// have monitor assertions builtin like |MonitoredSubReader| objects.
nsRefPtr<nsBuiltinDecoderReader>& operator[](uint32_t i)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
if (!mReader->GetDecoder()->OnDecodeThread()) {
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
}
return mSubReaderList[i];
}
// Appends a reader to the end of |mSubReaderList|. Will always assert that
// the thread is "in monitor".
void
AppendElement(nsBuiltinDecoderReader* aReader)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
mSubReaderList.AppendElement(aReader);
}
private:
// Pointer to |nsDASHReader| object which owns this |MonitoredSubReader|.
nsDASHReader* mReader;
// Ref ptrs to the sub readers.
nsTArray<nsRefPtr<nsBuiltinDecoderReader> > mSubReaderList;
};
// Ref ptrs to all sub-readers of individual media |Representation|s.
// Decoder monitor must be entered for write access on all threads and read
// access on all threads that are not the decode thread. Read acces on the
// decode thread does not need to be protected.
MonitoredSubReaderList mAudioReaders;
MonitoredSubReaderList mVideoReaders;
};
#endif

View File

@ -0,0 +1,389 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
#include "prlog.h"
#include "VideoUtils.h"
#include "SegmentBase.h"
#include "nsBuiltinDecoderStateMachine.h"
#include "nsDASHReader.h"
#include "MediaResource.h"
#include "nsDASHRepDecoder.h"
#ifdef PR_LOGGING
extern PRLogModuleInfo* gBuiltinDecoderLog;
#define LOG(msg, ...) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHRepDecoder] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gBuiltinDecoderLog, PR_LOG_DEBUG, \
("%p [nsDASHRepDecoder] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
nsDecoderStateMachine*
nsDASHRepDecoder::CreateStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Do not create; just return current state machine.
return mDecoderStateMachine;
}
nsresult
nsDASHRepDecoder::SetStateMachine(nsDecoderStateMachine* aSM)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mDecoderStateMachine = aSM;
return NS_OK;
}
void
nsDASHRepDecoder::SetResource(MediaResource* aResource)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mResource = aResource;
}
void
nsDASHRepDecoder::SetMPDRepresentation(Representation const * aRep)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mMPDRepresentation = aRep;
}
void
nsDASHRepDecoder::SetReader(nsWebMReader* aReader)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
mReader = aReader;
}
nsresult
nsDASHRepDecoder::Load(MediaResource* aResource,
nsIStreamListener** aListener,
nsMediaDecoder* aCloneDonor)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(mMPDRepresentation, NS_ERROR_NOT_INITIALIZED);
// Get init range and index range from MPD.
SegmentBase const * segmentBase = mMPDRepresentation->GetSegmentBase();
NS_ENSURE_TRUE(segmentBase, NS_ERROR_NULL_POINTER);
// Get and set init range.
segmentBase->GetInitRange(&mInitByteRange.mStart, &mInitByteRange.mEnd);
NS_ENSURE_TRUE(!mInitByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
mReader->SetInitByteRange(mInitByteRange);
// Get and set index range.
segmentBase->GetIndexRange(&mIndexByteRange.mStart, &mIndexByteRange.mEnd);
NS_ENSURE_TRUE(!mIndexByteRange.IsNull(), NS_ERROR_NOT_INITIALIZED);
mReader->SetIndexByteRange(mIndexByteRange);
// Determine byte range to Open.
// For small deltas between init and index ranges, we need to bundle the byte
// range requests together in order to deal with |nsMediaCache|'s control of
// seeking (see |nsMediaCache|::|Update|). |nsMediaCache| will not initiate a
// |ChannelMediaResource|::|CacheClientSeek| for the INDEX byte range if the
// delta between it and the INIT byte ranges is less than
// |SEEK_VS_READ_THRESHOLD|. To get around this, request all metadata bytes
// now so |nsMediaCache| can assume the bytes are en route.
int64_t delta = NS_MAX(mIndexByteRange.mStart, mInitByteRange.mStart)
- NS_MIN(mIndexByteRange.mEnd, mInitByteRange.mEnd);
MediaByteRange byteRange;
if (delta <= SEEK_VS_READ_THRESHOLD) {
byteRange.mStart = NS_MIN(mIndexByteRange.mStart, mInitByteRange.mStart);
byteRange.mEnd = NS_MAX(mIndexByteRange.mEnd, mInitByteRange.mEnd);
// Loading everything in one chunk .
mMetadataChunkCount = 1;
} else {
byteRange = mInitByteRange;
// Loading in two chunks: init and index.
mMetadataChunkCount = 2;
}
mCurrentByteRange = byteRange;
return mResource->OpenByteRange(nullptr, byteRange);
}
void
nsDASHRepDecoder::NotifyDownloadEnded(nsresult aStatus)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (!mMainDecoder) {
LOG("Error! Main Decoder is reported as null: mMainDecoder [%p]",
mMainDecoder.get());
DecodeError();
return;
}
if (NS_SUCCEEDED(aStatus)) {
// Decrement counter as metadata chunks are downloaded.
// Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|.
if (mMetadataChunkCount > 0) {
LOG("Metadata chunk [%d] downloaded: range requested [%d - %d]",
mMetadataChunkCount,
mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
mMetadataChunkCount--;
} else {
// Notify main decoder that a DATA byte range is downloaded.
LOG("Byte range downloaded: status [%x] range requested [%d - %d]",
aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
mMainDecoder->NotifyDownloadEnded(this, aStatus,
mCurrentByteRange);
}
} else if (aStatus == NS_BINDING_ABORTED) {
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
if (mMainDecoder) {
mMainDecoder->LoadAborted();
}
return;
} else if (aStatus != NS_BASE_STREAM_CLOSED) {
LOG("Network error trying to download MPD: aStatus [%x].", aStatus);
NetworkError();
}
}
void
nsDASHRepDecoder::OnReadMetadataCompleted()
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
LOG1("Metadata has been read.");
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsDASHRepDecoder::LoadNextByteRange);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
DecodeError();
return;
}
}
void
nsDASHRepDecoder::LoadNextByteRange()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (!mResource) {
LOG1("Error: resource is reported as null!");
DecodeError();
return;
}
// Populate the array of subsegment byte ranges if it's empty.
nsresult rv;
if (mByteRanges.IsEmpty()) {
if (!mReader) {
LOG1("Error: mReader should not be null!");
DecodeError();
return;
}
rv = mReader->GetIndexByteRanges(mByteRanges);
// If empty, just fail.
if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
LOG1("Error getting list of subsegment byte ranges.");
DecodeError();
return;
}
}
// Get byte range for subsegment.
if (mSubsegmentIdx < mByteRanges.Length()) {
mCurrentByteRange = mByteRanges[mSubsegmentIdx];
} else {
mCurrentByteRange.Clear();
LOG("End of subsegments: index [%d] out of range.", mSubsegmentIdx);
return;
}
// Open byte range corresponding to subsegment.
rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
if (NS_FAILED(rv)) {
LOG("Error opening byte range [%d - %d]: rv [%x].",
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, rv);
NetworkError();
return;
}
// Increment subsegment index for next load.
mSubsegmentIdx++;
}
nsresult
nsDASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
MediaByteRange& aByteRange)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Check data ranges, if available.
for (int i = 0; i < mByteRanges.Length(); i++) {
NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
mCurrentByteRange = aByteRange = mByteRanges[i];
mSubsegmentIdx = i;
return NS_OK;
}
}
// Check metadata ranges; init range.
if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
mCurrentByteRange = aByteRange = mInitByteRange;
mSubsegmentIdx = 0;
return NS_OK;
}
// ... index range.
if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
mCurrentByteRange = aByteRange = mIndexByteRange;
mSubsegmentIdx = 0;
return NS_OK;
}
aByteRange.Clear();
if (mByteRanges.IsEmpty()) {
// Assume mByteRanges will be populated after metadata is read.
LOG("Can't get range for offset [%d].", aOffset);
return NS_ERROR_NOT_AVAILABLE;
} else {
// Cannot seek to an unknown offset.
// XXX Revisit this for dynamic MPD profiles if MPD is regularly updated.
LOG("Error! Offset [%d] is in an unknown range!", aOffset);
return NS_ERROR_ILLEGAL_VALUE;
}
}
void
nsDASHRepDecoder::NetworkError()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->NetworkError(); }
}
void
nsDASHRepDecoder::SetDuration(double aDuration)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->SetDuration(aDuration); }
}
void
nsDASHRepDecoder::SetInfinite(bool aInfinite)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->SetInfinite(aInfinite); }
}
void
nsDASHRepDecoder::SetSeekable(bool aSeekable)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->SetSeekable(aSeekable); }
}
void
nsDASHRepDecoder::Progress(bool aTimer)
{
if (mMainDecoder) { mMainDecoder->Progress(aTimer); }
}
void
nsDASHRepDecoder::NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
LOG("Data bytes [%d - %d] arrived via buffer [%p].",
aOffset, aOffset+aLength, aBuffer);
// Notify reader directly, since call to |nsBuiltinDecoderStateMachine|::
// |NotifyDataArrived| will go to |nsDASHReader|::|NotifyDataArrived|, which
// has no way to forward the notification to the correct sub-reader.
if (mReader) {
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
}
// Forward to main decoder which will notify state machine.
if (mMainDecoder) {
mMainDecoder->NotifyDataArrived(aBuffer, aLength, aOffset);
}
}
void
nsDASHRepDecoder::NotifyBytesDownloaded()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->NotifyBytesDownloaded(); }
}
void
nsDASHRepDecoder::NotifySuspendedStatusChanged()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mMainDecoder) { mMainDecoder->NotifySuspendedStatusChanged(); }
}
bool
nsDASHRepDecoder::OnStateMachineThread() const
{
return (mMainDecoder ? mMainDecoder->OnStateMachineThread() : false);
}
bool
nsDASHRepDecoder::OnDecodeThread() const
{
return (mMainDecoder ? mMainDecoder->OnDecodeThread() : false);
}
ReentrantMonitor&
nsDASHRepDecoder::GetReentrantMonitor()
{
return mMainDecoder->GetReentrantMonitor();
}
nsDecoderStateMachine::State
nsDASHRepDecoder::GetDecodeState()
{
// XXX SHUTDOWN might not be an appropriate error.
return (mMainDecoder ? mMainDecoder->GetDecodeState()
: nsDecoderStateMachine::DECODER_STATE_SHUTDOWN);
}
mozilla::layers::ImageContainer*
nsDASHRepDecoder::GetImageContainer()
{
NS_ASSERTION(mMainDecoder && mMainDecoder->OnDecodeThread(),
"Should be on decode thread.");
return (mMainDecoder ? mMainDecoder->GetImageContainer() : nullptr);
}
void
nsDASHRepDecoder::DecodeError()
{
if (NS_IsMainThread()) {
nsBuiltinDecoder::DecodeError();
} else {
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsBuiltinDecoder::DecodeError);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching DecodeError event to main thread: rv[%x]", rv);
}
}
}
void
nsDASHRepDecoder::ReleaseStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
// Since state machine owns mReader, remove reference to it.
mReader = nullptr;
nsBuiltinDecoder::ReleaseStateMachine();
}

View File

@ -0,0 +1,192 @@
/* -*- 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/. */
/* DASH - Dynamic Adaptive Streaming over HTTP
*
* DASH is an adaptive bitrate streaming technology where a multimedia file is
* partitioned into one or more segments and delivered to a client using HTTP.
*
* see nsDASHDecoder.cpp for info on DASH interaction with the media engine.*/
#if !defined(nsDASHRepDecoder_h_)
#define nsDASHRepDecoder_h_
#include "Representation.h"
#include "ImageLayers.h"
#include "nsDASHDecoder.h"
#include "nsWebMDecoder.h"
#include "nsWebMReader.h"
#include "nsBuiltinDecoder.h"
class nsDASHDecoder;
class nsDASHRepDecoder : public nsBuiltinDecoder
{
public:
typedef mozilla::net::Representation Representation;
typedef mozilla::net::SegmentBase SegmentBase;
typedef mozilla::layers::ImageContainer ImageContainer;
// Constructor takes a ptr to the main decoder.
nsDASHRepDecoder(nsDASHDecoder* aMainDecoder) :
mMainDecoder(aMainDecoder),
mMPDRepresentation(nullptr),
mMetadataChunkCount(0),
mCurrentByteRange(),
mSubsegmentIdx(0),
mReader(nullptr)
{
MOZ_COUNT_CTOR(nsDASHRepDecoder);
}
~nsDASHRepDecoder()
{
MOZ_COUNT_DTOR(nsDASHRepDecoder);
}
// Clone not supported; just return nullptr.
virtual nsMediaDecoder* Clone() { return nullptr; }
// Called by the main decoder at creation time; points to the main state
// machine managed by the main decoder. Called on the main thread only.
nsresult SetStateMachine(nsDecoderStateMachine* aSM);
private:
// Overridden to return the ptr set by SetStateMachine. Called on the main
// thread only.
nsDecoderStateMachine* CreateStateMachine();
public:
// Called by nsDASHDecoder at creation time; points to the media resource
// for this decoder's |Representation|. Called on the main thread only.
void SetResource(MediaResource* aResource);
// Sets the |Representation| object for this decoder. Called on the main
// thread.
void SetMPDRepresentation(Representation const * aRep);
// Called from nsDASHDecoder on main thread; Starts media stream download.
nsresult Load(MediaResource* aResource = nullptr,
nsIStreamListener** aListener = nullptr,
nsMediaDecoder* aCloneDonor = nullptr);
// Loads the next byte range (or first one on first call). Called on the main
// thread only.
void LoadNextByteRange();
// Calls from nsDASHRepDecoder. Called on the main thread only.
void SetReader(nsWebMReader* aReader);
// Called if the media file encounters a network error. Call on the main
// thread only.
void NetworkError();
// Set the duration of the media resource in units of seconds.
// This is called via a channel listener if it can pick up the duration
// from a content header. Must be called from the main thread only.
virtual void SetDuration(double aDuration);
// Set media stream as infinite. Called on the main thread only.
void SetInfinite(bool aInfinite);
// Sets media stream as seekable. Called on main thread only.
void SetSeekable(bool aSeekable);
// Fire progress events if needed according to the time and byte
// constraints outlined in the specification. aTimer is true
// if the method is called as a result of the progress timer rather
// than the result of downloaded data.
void Progress(bool aTimer);
// Called as data arrives on the stream and is read into the cache. Called
// on the main thread only.
void NotifyDataArrived(const char* aBuffer,
uint32_t aLength,
int64_t aOffset);
// Called by MediaResource when some data has been received.
// Call on the main thread only.
void NotifyBytesDownloaded();
// Notify that a byte range request has been completed by the media resource.
// Called on the main thread only.
void NotifyDownloadEnded(nsresult aStatus);
// Called by MediaResource when the "cache suspended" status changes.
// If MediaResource::IsSuspendedByCache returns true, then the decoder
// should stop buffering or otherwise waiting for download progress and
// start consuming data, if possible, because the cache is full.
void NotifySuspendedStatusChanged();
// Gets a byte range containing the byte offset. Call on main thread only.
nsresult GetByteRangeForSeek(int64_t const aOffset,
MediaByteRange& aByteRange);
// Returns true if the current thread is the state machine thread.
bool OnStateMachineThread() const;
// Returns true if the current thread is the decode thread.
bool OnDecodeThread() const;
// Returns main decoder's monitor for synchronised access.
ReentrantMonitor& GetReentrantMonitor();
// Return the current decode state, according to the main decoder. The
// decoder monitor must be obtained before calling this.
nsDecoderStateMachine::State GetDecodeState();
// Called on the decode thread from nsWebMReader.
ImageContainer* GetImageContainer();
// Called when Metadata has been read; notifies that index data is read.
// Called on the decode thread only.
void OnReadMetadataCompleted();
// Overridden to cleanup ref to |nsDASHDecoder|. Called on main thread only.
void Shutdown() {
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Call parent class shutdown.
nsBuiltinDecoder::Shutdown();
NS_ENSURE_TRUE(mShuttingDown, );
// Cleanup ref to main decoder.
mMainDecoder = nullptr;
}
// Drop reference to state machine and mReader (owned by state machine).
// Only called during shutdown dance.
void ReleaseStateMachine();
// Notifies the element that decoding has failed.
void DecodeError();
private:
// The main decoder.
nsRefPtr<nsDASHDecoder> mMainDecoder;
// This decoder's MPD |Representation| object.
Representation const * mMPDRepresentation;
// Countdown var for loading metadata byte ranges.
uint16_t mMetadataChunkCount;
// All the byte ranges for this |Representation|.
nsTArray<MediaByteRange> mByteRanges;
// Byte range for the init and index bytes.
MediaByteRange mInitByteRange;
MediaByteRange mIndexByteRange;
// The current byte range being requested.
MediaByteRange mCurrentByteRange;
// Index of the current byte range.
uint64_t mSubsegmentIdx;
// Ptr to the reader object for this |Representation|. Owned by state
// machine.
nsBuiltinDecoderReader* mReader;
};
#endif //nsDASHRepDecoder_h_

View File

@ -711,7 +711,7 @@ public:
virtual void NotifyAudioAvailableListener();
// Notifies the element that decoding has failed.
void DecodeError();
virtual void DecodeError();
// Schedules the state machine to run one cycle on the shared state
// machine thread. Main thread only.

View File

@ -376,24 +376,6 @@ VideoData* nsBuiltinDecoderReader::FindStartTime(int64_t& aOutStartTime)
return videoData;
}
/*template<class Data>
Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
MediaQueue<Data>& aQueue)
{
bool eof = false;
while (!eof && aQueue.GetSize() == 0) {
{
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
if (mDecoder->GetDecodeState() == nsDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
return nullptr;
}
}
eof = !(this->*aDecodeFn)();
}
Data* d = nullptr;
return (d = aQueue.PeekFront()) ? d : nullptr;
}*/
nsresult nsBuiltinDecoderReader::DecodeToTarget(int64_t aTarget)
{
// Decode forward to the target frame. Start with video, if we have it.

View File

@ -530,7 +530,7 @@ public:
virtual void SetIndexByteRange(MediaByteRange &aByteRange) { }
// Returns list of ranges for index frame start/end offsets. Used by DASH.
nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
virtual nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
return NS_ERROR_NOT_AVAILABLE;
}

View File

@ -32,3 +32,9 @@ include $(topsrcdir)/config/rules.mk
LOCAL_INCLUDES = \
$(MOZ_LIBVPX_CFLAGS) \
$(NULL)
ifdef MOZ_DASH
LOCAL_INCLUDES += \
-I$(srcdir)/../dash \
$(NULL)
endif

View File

@ -191,6 +191,12 @@ SHARED_LIBRARY_LIBS += \
$(NULL)
endif
ifdef MOZ_DASH
SHARED_LIBRARY_LIBS += \
$(DEPTH)/content/media/dash/$(LIB_PREFIX)gkcondash_s.$(LIB_SUFFIX) \
$(NULL)
endif
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
INCLUDES += \
-I$(srcdir)/../../base/src \

View File

@ -165,6 +165,9 @@ pref("media.wave.enabled", true);
#ifdef MOZ_WEBM
pref("media.webm.enabled", true);
#endif
#ifdef MOZ_DASH
pref("media.dash.enabled", true);
#endif
#ifdef MOZ_GSTREAMER
pref("media.h264.enabled", true);
#endif

View File

@ -8,14 +8,14 @@
# Contributor(s):
# Steve Workman <sworkman@mozilla.com
DEPTH = ../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
DEPTH := @DEPTH@
topsrcdir := @top_srcdir@
srcdir := @srcdir@
VPATH := @srcdir@
include $(DEPTH)/config/autoconf.mk
PARALLEL_DIRS = \
PARALLEL_DIRS := \
mpd \
$(NULL)

View File

@ -6,23 +6,21 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Contributor(s):
# Steve Workman <sworkman@mozilla.com
# Steve Workman <sworkman@mozilla.com>
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
DEPTH := @DEPTH@
topsrcdir := @top_srcdir@
srcdir := @srcdir@
VPATH := @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = necko
LIBRARY_NAME = nkdashmpd_s
LIBXUL_LIBRARY = 1
XPIDL_MODULE = necko_dashmpd
GRE_MODULE = 1
FORCE_STATIC_LIB = 1
MODULE := necko
LIBRARY_NAME := nkdashmpd_s
LIBXUL_LIBRARY := 1
FORCE_STATIC_LIB := 1
CPPSRCS = \
CPPSRCS := \
nsDASHMPDParser.cpp \
IMPDManager.cpp \
nsDASHWebMODManager.cpp \
@ -34,22 +32,13 @@ CPPSRCS = \
SegmentBase.cpp \
$(NULL)
EXPORTS = \
IMPDManager.h \
nsDASHMPDParser.h \
$(NULL)
LOCAL_INCLUDES = \
-I$(srcdir)/../manager/ \
LOCAL_INCLUDES := \
-I$(topsrcdir)/content/base/src \
-I$(topsrcdir)/content/html/content/public \
-I$(topsrcdir)/content/html/content/src \
$(NULL)
include $(topsrcdir)/config/rules.mk
include $(topsrcdir)/ipc/chromium/chromium-config.mk
include $(topsrcdir)/config/rules.mk
DEFINES += -DIMPL_NS_NET

View File

@ -38,9 +38,12 @@
#if defined(PR_LOGGING)
static PRLogModuleInfo* gDASHMPDParserLog = nullptr;
#define LOG(msg, ...) PR_LOG(gDASHMPDParserLog, PR_LOG_DEBUG, \
("%p [nsDASHMPDParser] " msg, this, ##__VA_ARGS__))
("%p [nsDASHMPDParser] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gDASHMPDParserLog, PR_LOG_DEBUG, \
("%p [nsDASHMPDParser] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
namespace mozilla {
@ -83,7 +86,7 @@ nsDASHMPDParser::Parse(IMPDManager** aMPDManager,
nsAutoCString spec;
nsresult rv = mURI->GetSpec(spec);
if (NS_FAILED(rv)) {
LOG("Preparing to parse MPD: cannot get spec from URI");
LOG1("Preparing to parse MPD: cannot get spec from URI");
} else {
LOG("Preparing to parse MPD: mURI:\"%s\"", spec.get());
}
@ -103,7 +106,7 @@ nsDASHMPDParser::Parse(IMPDManager** aMPDManager,
getter_AddRefs(doc));
NS_ENSURE_SUCCESS(rv, rv);
if(!doc) {
LOG("ERROR! Document not parsed as XML!");
LOG1("ERROR! Document not parsed as XML!");
return NS_ERROR_NO_INTERFACE;
}
// Use root node to create MPD manager.

View File

@ -46,9 +46,12 @@ namespace net {
static PRLogModuleInfo* gnsDASHWebMODManagerLog = nullptr;
#define LOG(msg, ...) \
PR_LOG(gnsDASHWebMODManagerLog, PR_LOG_DEBUG, \
("%p [nsDASHWebMODManager] " msg, this, ##__VA_ARGS__))
("%p [nsDASHWebMODManager] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gDASHMPDParserLog, PR_LOG_DEBUG, \
("%p [nsDASHWebMODManager] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
nsDASHWebMODManager::nsDASHWebMODManager(MPD* aMpd)

View File

@ -43,9 +43,13 @@
static PRLogModuleInfo* gnsDASHWebMODParserLog = nullptr;
#define LOG(msg, ...) \
PR_LOG(gnsDASHWebMODParserLog, PR_LOG_DEBUG, \
("%p [nsDASHWebMODParser] " msg, this, ##__VA_ARGS__))
("%p [nsDASHWebMODParser] " msg, this, __VA_ARGS__))
#define LOG1(msg) \
PR_LOG(gnsDASHWebMODParserLog, PR_LOG_DEBUG, \
("%p [nsDASHWebMODParser] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
namespace mozilla {
@ -59,7 +63,7 @@ nsDASHWebMODParser::nsDASHWebMODParser(nsIDOMElement* aRoot) :
if(!gnsDASHWebMODParserLog)
gnsDASHWebMODParserLog = PR_NewLogModule("nsDASHWebMODParser");
#endif
LOG("Created nsDASHWebMODParser");
LOG1("Created nsDASHWebMODParser");
}
nsDASHWebMODParser::~nsDASHWebMODParser()
@ -70,7 +74,7 @@ nsDASHWebMODParser::~nsDASHWebMODParser()
MPD*
nsDASHWebMODParser::Parse()
{
LOG("Parsing DOM into MPD objects");
LOG1("Parsing DOM into MPD objects");
nsAutoPtr<MPD> mpd(new MPD());
nsresult rv = VerifyMPDAttributes();
@ -219,7 +223,7 @@ nsDASHWebMODParser::SetPeriods(MPD* aMpd)
// |Period| should be ignored if its child elems are invalid
if (bIgnoreThisPeriod) {
LOG("Ignoring period");
LOG1("Ignoring period");
} else {
aMpd->AddPeriod(period.forget());
LOG("Period #%d: added to MPD", i++);
@ -250,7 +254,7 @@ nsDASHWebMODParser::ValidateAdaptationSetAttributes(nsIDOMElement* aChild,
NS_ENSURE_SUCCESS(rv, rv);
bAttributesValid = !mimeType.IsEmpty();
if (!bAttributesValid)
LOG("mimeType not present!");
LOG1("mimeType not present!");
}
// Validate attributes for video.
if (bAttributesValid && mimeType.EqualsLiteral(VIDEO_WEBM)) {
@ -260,7 +264,7 @@ nsDASHWebMODParser::ValidateAdaptationSetAttributes(nsIDOMElement* aChild,
NS_ENSURE_SUCCESS(rv, rv);
bAttributesValid = (value.IsEmpty() || value.EqualsLiteral("true"));
if (!bAttributesValid)
LOG("segmentAlignment not present or invalid!");
LOG1("segmentAlignment not present or invalid!");
}
if (bAttributesValid) {
rv = GetAttribute(aChild, NS_LITERAL_STRING("subsegmentAlignment"),
@ -268,7 +272,7 @@ nsDASHWebMODParser::ValidateAdaptationSetAttributes(nsIDOMElement* aChild,
NS_ENSURE_SUCCESS(rv, rv);
bAttributesValid = (!value.IsEmpty() && value.EqualsLiteral("true"));
if (!bAttributesValid)
LOG("subsegmentAlignment not present or invalid!");
LOG1("subsegmentAlignment not present or invalid!");
}
if (bAttributesValid) {
rv = GetAttribute(aChild, NS_LITERAL_STRING("bitstreamSwitching"),
@ -276,7 +280,7 @@ nsDASHWebMODParser::ValidateAdaptationSetAttributes(nsIDOMElement* aChild,
NS_ENSURE_SUCCESS(rv, rv);
bAttributesValid = (!value.IsEmpty() && value.EqualsLiteral("true"));
if (!bAttributesValid)
LOG("bitstreamSwitching not present or invalid!");
LOG1("bitstreamSwitching not present or invalid!");
}
} else if (bAttributesValid && mimeType.EqualsLiteral(AUDIO_WEBM)) {
// Validate attributes for audio.

View File

@ -136,6 +136,9 @@
#define VIDEO_OGG "video/ogg"
#define VIDEO_WEBM "video/webm"
#define APPLICATION_OGG "application/ogg"
#ifdef MOZ_DASH
#define APPLICATION_DASH "application/dash+xml"
#endif
/* x-uuencode-apple-single. QuickMail made me do this. */
#define UUENCODE_APPLE_SINGLE "x-uuencode-apple-single"

View File

@ -402,6 +402,9 @@ static nsDefaultMimeTypeEntry defaultMimeEntries [] =
{ VIDEO_WEBM, "webm" },
{ AUDIO_WEBM, "webm" },
#endif
#ifdef MOZ_DASH
{ APPLICATION_DASH, "mpd" },
#endif
#ifdef MOZ_GSTREAMER
{ VIDEO_MP4, "mp4" },
#endif
@ -476,6 +479,9 @@ static nsExtraMimeTypeEntry extraMimeEntries [] =
{ AUDIO_OGG, "opus", "Opus Audio" },
{ VIDEO_WEBM, "webm", "Web Media Video" },
{ AUDIO_WEBM, "webm", "Web Media Audio" },
#ifdef MOZ_DASH
{ APPLICATION_DASH, "mpd", "DASH Media Presentation Description" },
#endif
#ifdef MOZ_MEDIA_PLUGINS
{ AUDIO_MP3, "mp3", "MPEG Audio" },
#endif