Bug 792404 - Add code to enable adaptive decoder and reader switching for DASH-WebM r=cpearce

This commit is contained in:
Steve Workman 2012-12-06 15:27:08 -08:00
parent 272d7ed20d
commit aa6a61c8ef
26 changed files with 1499 additions and 267 deletions

View File

@ -93,8 +93,8 @@ public:
// held.
virtual void UpdatePlaybackPosition(int64_t aTime) = 0;
// Called when the metadata from the media file has been read by the reader.
// Call on the decode thread only.
// May be called by the reader to notify this decoder that the metadata from
// the media file has been read. Call on the decode thread only.
virtual void OnReadMetadataCompleted() = 0;
// Stack based class to assist in notifying the frame statistics of

View File

@ -1719,6 +1719,50 @@ MediaCacheStream::NotifyDataReceived(int64_t aSize, const char* aData,
mon.NotifyAll();
}
void
MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
if (blockOffset > 0) {
LOG(PR_LOG_DEBUG,
("Stream %p writing partial block: [%d] bytes; "
"mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
"notifying: [%s]",
this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
aNotifyAll ? "yes" : "no"));
// Write back the partial block
memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
BLOCK_SIZE - blockOffset);
gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
if (aNotifyAll) {
// Wake up readers who may be waiting for this data
mon.NotifyAll();
}
}
}
void
MediaCacheStream::FlushPartialBlock()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
// Write the current partial block to memory.
// Note: This writes a full block, so if data is not at the end of the
// stream, the decoder must subsequently choose correct start and end offsets
// for reading/seeking.
FlushPartialBlockInternal(false);
gMediaCache->QueueUpdate();
}
void
MediaCacheStream::NotifyDataEnded(nsresult aStatus)
{
@ -1733,16 +1777,7 @@ MediaCacheStream::NotifyDataEnded(nsresult aStatus)
mResourceID = gMediaCache->AllocateResourceID();
}
int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
if (blockOffset > 0) {
// Write back the partial block
memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
BLOCK_SIZE - blockOffset);
gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
// Wake up readers who may be waiting for this data
mon.NotifyAll();
}
FlushPartialBlockInternal(true);
if (!mDidNotifyDataEnded) {
MediaCache::ResourceStreamIterator iter(mResourceID);

View File

@ -273,6 +273,9 @@ public:
// We pass in the principal that was used to load this data.
void NotifyDataReceived(int64_t aSize, const char* aData,
nsIPrincipal* aPrincipal);
// Notifies the cache that the current bytes should be written to disk.
// Called on the main thread.
void FlushPartialBlock();
// Notifies the cache that the channel has closed with the given status.
void NotifyDataEnded(nsresult aStatus);
@ -415,6 +418,11 @@ private:
// This method assumes that the cache monitor is held and can be called on
// any thread.
int64_t GetNextCachedDataInternal(int64_t aOffset);
// Writes |mPartialBlock| to disk.
// Used by |NotifyDataEnded| and |FlushPartialBlock|.
// If |aNotifyAll| is true, this function will wake up readers who may be
// waiting on the media cache monitor. Called on the main thread only.
void FlushPartialBlockInternal(bool aNotify);
// A helper function to do the work of closing the stream. Assumes
// that the cache monitor is held. Main thread only.
// aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor.

View File

@ -655,8 +655,8 @@ public:
// change. Call on the main thread only.
void ChangeState(PlayState aState);
// Called when the metadata from the media file has been read by the reader.
// Call on the decode thread only.
// May be called by the reader to notify this decoder that the metadata from
// the media file has been read. Call on the decode thread only.
void OnReadMetadataCompleted() MOZ_OVERRIDE { }
// Called when the metadata from the media file has been loaded by the

View File

@ -337,8 +337,8 @@ nsresult MediaDecoderReader::ResetDecode()
{
nsresult res = NS_OK;
mVideoQueue.Reset();
mAudioQueue.Reset();
VideoQueue().Reset();
AudioQueue().Reset();
return res;
}
@ -346,7 +346,7 @@ nsresult MediaDecoderReader::ResetDecode()
VideoData* MediaDecoderReader::DecodeToFirstVideoData()
{
bool eof = false;
while (!eof && mVideoQueue.GetSize() == 0) {
while (!eof && VideoQueue().GetSize() == 0) {
{
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
if (mDecoder->IsShutdown()) {
@ -357,13 +357,13 @@ VideoData* MediaDecoderReader::DecodeToFirstVideoData()
eof = !DecodeVideoFrame(keyframeSkip, 0);
}
VideoData* d = nullptr;
return (d = mVideoQueue.PeekFront()) ? d : nullptr;
return (d = VideoQueue().PeekFront()) ? d : nullptr;
}
AudioData* MediaDecoderReader::DecodeToFirstAudioData()
{
bool eof = false;
while (!eof && mAudioQueue.GetSize() == 0) {
while (!eof && AudioQueue().GetSize() == 0) {
{
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
if (mDecoder->IsShutdown()) {
@ -373,7 +373,7 @@ AudioData* MediaDecoderReader::DecodeToFirstAudioData()
eof = !DecodeAudioData();
}
AudioData* d = nullptr;
return (d = mAudioQueue.PeekFront()) ? d : nullptr;
return (d = AudioQueue().PeekFront()) ? d : nullptr;
}
VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime)
@ -416,7 +416,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
int64_t startTime = -1;
nsAutoPtr<VideoData> video;
while (HasVideo() && !eof) {
while (mVideoQueue.GetSize() == 0 && !eof) {
while (VideoQueue().GetSize() == 0 && !eof) {
bool skip = false;
eof = !DecodeVideoFrame(skip, 0);
{
@ -426,21 +426,21 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
}
}
}
if (mVideoQueue.GetSize() == 0) {
if (VideoQueue().GetSize() == 0) {
// Hit end of file, we want to display the last frame of the video.
if (video) {
mVideoQueue.PushFront(video.forget());
VideoQueue().PushFront(video.forget());
}
break;
}
video = mVideoQueue.PeekFront();
video = VideoQueue().PeekFront();
// If the frame end time is less than the seek target, we won't want
// to display this frame after the seek, so discard it.
if (video && video->mEndTime <= aTarget) {
if (startTime == -1) {
startTime = video->mTime;
}
mVideoQueue.PopFront();
VideoQueue().PopFront();
} else {
video.forget();
break;
@ -459,7 +459,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
// Decode audio forward to the seek target.
bool eof = false;
while (HasAudio() && !eof) {
while (!eof && mAudioQueue.GetSize() == 0) {
while (!eof && AudioQueue().GetSize() == 0) {
eof = !DecodeAudioData();
{
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
@ -468,7 +468,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
}
}
}
const AudioData* audio = mAudioQueue.PeekFront();
const AudioData* audio = AudioQueue().PeekFront();
if (!audio)
break;
CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudioRate);
@ -479,7 +479,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
if (startFrame.value() + audio->mFrames <= targetFrame.value()) {
// Our seek target lies after the frames in this AudioData. Pop it
// off the queue, and keep decoding forwards.
delete mAudioQueue.PopFront();
delete AudioQueue().PopFront();
audio = nullptr;
continue;
}
@ -526,8 +526,8 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
frames,
audioData.forget(),
channels));
delete mAudioQueue.PopFront();
mAudioQueue.PushFront(data.forget());
delete AudioQueue().PopFront();
AudioQueue().PushFront(data.forget());
break;
}
}

View File

@ -373,6 +373,9 @@ public:
// or an un-recoverable read error has occured.
virtual bool DecodeAudioData() = 0;
// Steps to carry out at the start of the |DecodeLoop|.
virtual void PrepareToDecode() { }
// Reads and decodes one video frame. Packets with a timestamp less
// than aTimeThreshold will be decoded (unless they're not keyframes
// and aKeyframeSkip is true), but will not be added to the queue.
@ -471,17 +474,6 @@ public:
AudioData* DecodeToFirstAudioData();
VideoData* DecodeToFirstVideoData();
// Sets range for initialization bytes; used by DASH.
virtual void SetInitByteRange(MediaByteRange &aByteRange) { }
// Sets range for index frame bytes; used by DASH.
virtual void SetIndexByteRange(MediaByteRange &aByteRange) { }
// Returns list of ranges for index frame start/end offsets. Used by DASH.
virtual nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
return NS_ERROR_NOT_AVAILABLE;
}
protected:
// Pumps the decode until we reach frames required to play at time aTarget
// (usecs).

View File

@ -793,6 +793,8 @@ void MediaDecoderStateMachine::DecodeLoop()
!mStopDecodeThread &&
(videoPlaying || audioPlaying))
{
mReader->PrepareToDecode();
// We don't want to consider skipping to the next keyframe if we've
// only just started up the decode loop, so wait until we've decoded
// some frames before enabling the keyframe skip logic on video.

View File

@ -234,7 +234,8 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
// accept ranges.
if (!acceptsRanges) {
CMLOG("Error! HTTP_PARTIAL_RESPONSE_CODE received but server says "
"range requests are not accepted! Channel[%p]", hc.get());
"range requests are not accepted! Channel[%p] decoder[%p]",
hc.get(), mDecoder);
mDecoder->NetworkError();
CloseChannel();
return NS_OK;
@ -248,7 +249,8 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
if (NS_FAILED(rv)) {
// Content-Range header text should be parse-able.
CMLOG("Error processing \'Content-Range' for "
"HTTP_PARTIAL_RESPONSE_CODE: rv[%x]channel [%p]", rv, hc.get());
"HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
rv, hc.get(), mDecoder);
mDecoder->NetworkError();
CloseChannel();
return NS_OK;
@ -389,8 +391,8 @@ ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
NS_ENSURE_SUCCESS(rv, rv);
}
CMLOG("Received bytes [%d] to [%d] of [%d]",
aRangeStart, aRangeEnd, aRangeTotal);
CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
return NS_OK;
}
@ -478,10 +480,23 @@ ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
// Keep track of where we're up to
// For byte range downloads controlled by |DASHDecoder|, there are cases in
// which the reader's offset is different enough from the channel offset that
// |MediaCache| requests a |CacheClientSeek| to the reader's offset. This
// can happen between calls to |CopySegmentToCache|. To avoid copying at
// incorrect offsets, ensure |MediaCache| copies to the location that
// |ChannelMediaResource| expects.
if (closure->mResource->mByteRangeDownloads) {
closure->mResource->mCacheStream.NotifyDataStarted(closure->mResource->mOffset);
}
// Keep track of where we're up to.
LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
"[%d] bytes for decoder[%p]",
closure->mResource, closure->mResource->mOffset, aCount,
closure->mResource->mDecoder);
closure->mResource->mOffset += aCount;
LOG("%p [ChannelMediaResource]: CopySegmentToCache new mOffset = %d",
closure->mResource, closure->mResource->mOffset);
closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
closure->mPrincipal);
*aWriteCount = aCount;
@ -760,6 +775,8 @@ nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
{
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
aOffset, mDecoder);
// Remember |aOffset|, because Media Cache may request a diff offset later.
if (mByteRangeDownloads) {
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
@ -971,7 +988,21 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
{
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
CloseChannel();
CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
aOffset, mDecoder);
// |CloseChannel| immediately for non-byte-range downloads.
if (!mByteRangeDownloads) {
CloseChannel();
} else if (mChannel) {
// Only close byte range channels if they are not in pending state.
bool isPending = false;
nsresult rv = mChannel->IsPending(&isPending);
NS_ENSURE_SUCCESS(rv, rv);
if (!isPending) {
CloseChannel();
}
}
if (aResume) {
NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
@ -1001,24 +1032,55 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
nsresult rv;
{
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
// Ensure that media cache can only request an equal or smaller offset;
// it may be trying to include the start of a cache block.
NS_ENSURE_TRUE(aOffset <= mSeekOffset, NS_ERROR_ILLEGAL_VALUE);
rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
mSeekOffset = -1;
// Only continue with seek request if a prior call to |Seek| was made.
// If |Seek| was not called previously, it means the media cache is
// seeking on its own.
// E.g. For those WebM files which are encoded with cues at the end of
// the file, when the cues are parsed, the reader and media cache
// automatically return to the first offset not downloaded, normally the
// first byte after init data. This results in |MediaCache| requesting
// |aOffset| = 0 (aligning to the start of the cache block. Ignore this
// and let |DASHDecoder| decide which bytes to download and when.
if (mSeekOffset >= 0) {
rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
// Cache may try to seek from the next uncached byte: this offset may
// be after the byte range being seeked, i.e. the range containing
// |mSeekOffset|, which is the offset actually requested by the reader.
// This case means that the seeked range is already cached. For byte
// range downloads, we do not permit the cache to request bytes outside
// the seeked range. Instead, the decoder is responsible for
// controlling the sequence of byte range downloads. As such, return
// silently, and do NOT request a new download.
if (NS_SUCCEEDED(rv) && !mByteRange.IsNull() &&
aOffset > mByteRange.mEnd) {
rv = NS_ERROR_NOT_AVAILABLE;
mByteRange.Clear();
}
mSeekOffset = -1;
} else {
LOG("MediaCache [%p] trying to seek independently to offset [%lld].",
&mCacheStream, aOffset);
rv = NS_ERROR_NOT_AVAILABLE;
}
}
if (rv == NS_ERROR_NOT_AVAILABLE) {
// Assume decoder will request correct bytes when range information
// becomes available. Return silently.
// Decoder will not make byte ranges available for non-active streams, or
// if range information is not yet available, or for metadata bytes if
// they have already been downloaded and read. In all cases, it is ok to
// return silently and assume that the decoder will request the correct
// byte range when range information becomes available.
CMLOG("Byte range not available for decoder [%p]; returning "
"silently.", mDecoder);
return NS_OK;
} else if (NS_FAILED(rv) || mByteRange.IsNull()) {
// Decoder reported an error we don't want to handle here; just return.
CMLOG("Error getting byte range: seek offset[%lld] cache offset[%lld] "
"decoder[%p]", mSeekOffset, aOffset, mDecoder);
mDecoder->NetworkError();
CloseChannel();
return rv;
}
// Media cache may decrease offset to start of cache data block.
// Adjust start of byte range accordingly.
// Adjust the byte range to start where the media cache requested.
mByteRange.mStart = mOffset = aOffset;
return OpenByteRange(nullptr, mByteRange);
}
@ -1042,6 +1104,26 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
return OpenChannel(nullptr);
}
void
ChannelMediaResource::FlushCache()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Ensure that data in the cache's partial block is written to disk.
mCacheStream.FlushPartialBlock();
}
void
ChannelMediaResource::NotifyLastByteRange()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Tell media cache that the last data has been downloaded.
// Note: subsequent seeks will require re-opening the channel etc.
mCacheStream.NotifyDataEnded(NS_OK);
}
nsresult
ChannelMediaResource::CacheClientSuspend()
{

View File

@ -304,6 +304,13 @@ public:
* aRanges is being used.
*/
virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) = 0;
// Ensure that the media cache writes any data held in its partial block.
// Called on the main thread only.
virtual void FlushCache() { }
// Notify that the last data byte range was loaded.
virtual void NotifyLastByteRange() { }
};
class BaseMediaResource : public MediaResource {
@ -388,6 +395,13 @@ public:
// Resume the current load since data is wanted again
nsresult CacheClientResume();
// Ensure that the media cache writes any data held in its partial block.
// Called on the main thread.
virtual void FlushCache() MOZ_OVERRIDE;
// Notify that the last data byte range was loaded.
virtual void NotifyLastByteRange() MOZ_OVERRIDE;
// Main thread
virtual nsresult Open(nsIStreamListener** aStreamListener);
virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,

View File

@ -70,6 +70,45 @@ private:
ReentrantMonitor* mReentrantMonitor;
};
/**
* ReentrantMonitorConditionallyEnter
*
* Enters the supplied monitor only if the conditional value |aEnter| is true.
* E.g. Used to allow unmonitored read access on the decode thread,
* and monitored access on all other threads.
*/
class NS_STACK_CLASS ReentrantMonitorConditionallyEnter
{
public:
ReentrantMonitorConditionallyEnter(bool aEnter,
ReentrantMonitor &aReentrantMonitor) :
mReentrantMonitor(nullptr)
{
MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter);
if (aEnter) {
mReentrantMonitor = &aReentrantMonitor;
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->Enter();
}
}
~ReentrantMonitorConditionallyEnter(void)
{
if (mReentrantMonitor) {
mReentrantMonitor->Exit();
}
MOZ_COUNT_DTOR(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*);
ReentrantMonitor* mReentrantMonitor;
};
// Shuts down a thread asynchronously.
class ShutdownThreadEvent : public nsRunnable
{

View File

@ -149,8 +149,13 @@ DASHDecoder::DASHDecoder() :
mMPDReaderThread(nullptr),
mPrincipal(nullptr),
mDASHReader(nullptr),
mAudioRepDecoder(nullptr),
mVideoRepDecoder(nullptr)
mVideoAdaptSetIdx(-1),
mAudioRepDecoderIdx(-1),
mVideoRepDecoderIdx(-1),
mAudioSubsegmentIdx(0),
mVideoSubsegmentIdx(0),
mAudioMetadataReadCount(0),
mVideoMetadataReadCount(0)
{
MOZ_COUNT_CTOR(DASHDecoder);
}
@ -297,6 +302,7 @@ DASHDecoder::OnReadMPDBufferCompleted()
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mShuttingDown) {
LOG1("Shutting down! Ignoring OnReadMPDBufferCompleted().");
return;
}
@ -385,6 +391,9 @@ DASHDecoder::CreateRepDecoders()
for (int i = 0; i < mMPDManager->GetNumAdaptationSets(); i++) {
IMPDManager::AdaptationSetType asType = mMPDManager->GetAdaptationSetType(i);
if (asType == IMPDManager::DASH_VIDEO_STREAM) {
mVideoAdaptSetIdx = i;
}
for (int j = 0; j < mMPDManager->GetNumRepresentations(i); j++) {
// Get URL string.
nsAutoString segmentUrl;
@ -422,8 +431,8 @@ DASHDecoder::CreateRepDecoders()
}
}
NS_ENSURE_TRUE(mVideoRepDecoder, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mAudioRepDecoder, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(VideoRepDecoder(), NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(AudioRepDecoder(), NS_ERROR_NOT_INITIALIZED);
return NS_OK;
}
@ -441,14 +450,16 @@ DASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
DASHRepDecoder* audioDecoder = new DASHRepDecoder(this);
NS_ENSURE_TRUE(audioDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
if (!mAudioRepDecoder) {
mAudioRepDecoder = audioDecoder;
// Set current decoder to the first one created.
if (mAudioRepDecoderIdx == -1) {
mAudioRepDecoderIdx = 0;
}
mAudioRepDecoders.AppendElement(audioDecoder);
// Create sub-reader; attach to DASH reader and sub-decoder.
WebMReader* audioReader = new WebMReader(audioDecoder);
if (mDASHReader) {
audioReader->SetMainReader(mDASHReader);
mDASHReader->AddAudioReader(audioReader);
}
audioDecoder->SetReader(audioReader);
@ -461,6 +472,8 @@ DASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
audioDecoder->SetResource(audioResource);
audioDecoder->SetMPDRepresentation(aRep);
LOG("Created audio DASHRepDecoder [%p]", audioDecoder);
return NS_OK;
}
@ -477,14 +490,16 @@ DASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
DASHRepDecoder* videoDecoder = new DASHRepDecoder(this);
NS_ENSURE_TRUE(videoDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
if (!mVideoRepDecoder) {
mVideoRepDecoder = videoDecoder;
// Set current decoder to the first one created.
if (mVideoRepDecoderIdx == -1) {
mVideoRepDecoderIdx = 0;
}
mVideoRepDecoders.AppendElement(videoDecoder);
// Create sub-reader; attach to DASH reader and sub-decoder.
WebMReader* videoReader = new WebMReader(videoDecoder);
if (mDASHReader) {
videoReader->SetMainReader(mDASHReader);
mDASHReader->AddVideoReader(videoReader);
}
videoDecoder->SetReader(videoReader);
@ -497,6 +512,8 @@ DASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
videoDecoder->SetResource(videoResource);
videoDecoder->SetMPDRepresentation(aRep);
LOG("Created video DASHRepDecoder [%p]", videoDecoder);
return NS_OK;
}
@ -595,38 +612,42 @@ DASHDecoder::LoadRepresentations()
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
// Load the decoders for each |Representation|'s media streams.
if (mAudioRepDecoder) {
rv = mAudioRepDecoder->Load();
// XXX Prob ok to load all audio decoders, since there should only be one
// created, but need to review the rest of the file.
if (AudioRepDecoder()) {
rv = AudioRepDecoder()->Load();
NS_ENSURE_SUCCESS(rv, rv);
mAudioMetadataReadCount++;
}
if (mVideoRepDecoder) {
rv = mVideoRepDecoder->Load();
// Load all video decoders.
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
rv = mVideoRepDecoders[i]->Load();
NS_ENSURE_SUCCESS(rv, rv);
mVideoMetadataReadCount++;
}
if (NS_FAILED(rv)) {
LOG("Failed to open stream! rv [%x].", rv);
return rv;
if (AudioRepDecoder()) {
AudioRepDecoder()->SetStateMachine(mDecoderStateMachine);
}
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
mVideoRepDecoders[i]->SetStateMachine(mDecoderStateMachine);
}
}
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
DASHDecoder::NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
nsresult aStatus,
MediaByteRange &aRange)
nsresult aStatus,
int32_t const aSubsegmentIdx)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mShuttingDown) {
LOG1("Shutting down! Ignoring NotifyDownloadEnded().");
return;
}
// MPD Manager must exist, indicating MPD has been downloaded and parsed.
if (!mMPDManager) {
LOG1("Network Error! MPD Manager must exist, indicating MPD has been "
@ -643,21 +664,92 @@ DASHDecoder::NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
}
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!",
LOG("Byte range downloaded: decoder [%p] subsegmentIdx [%d]",
aRepDecoder, aSubsegmentIdx);
if (aSubsegmentIdx < 0) {
LOG("Last subsegment for decoder [%p] was downloaded",
aRepDecoder);
return;
}
nsRefPtr<DASHRepDecoder> decoder = aRepDecoder;
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (!IsDecoderAllowedToDownloadSubsegment(aRepDecoder,
aSubsegmentIdx)) {
NS_WARNING("Decoder downloaded subsegment but it is not allowed!");
LOG("Error! Decoder [%p] downloaded subsegment [%d] but it is not "
"allowed!", aRepDecoder, aSubsegmentIdx);
return;
}
if (aRepDecoder == VideoRepDecoder() &&
mVideoSubsegmentIdx == aSubsegmentIdx) {
IncrementSubsegmentIndex(aRepDecoder);
} else if (aRepDecoder == AudioRepDecoder() &&
mAudioSubsegmentIdx == aSubsegmentIdx) {
IncrementSubsegmentIndex(aRepDecoder);
} else {
return;
}
// Do Stream Switching here before loading next bytes.
// Audio stream switching not supported.
if (aRepDecoder == VideoRepDecoder() &&
mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges()) {
nsresult rv = PossiblySwitchDecoder(aRepDecoder);
if (NS_FAILED(rv)) {
LOG("Failed possibly switching decoder rv[0x%x]", rv);
DecodeError();
return;
}
decoder = VideoRepDecoder();
}
}
// Before loading, note the index of the decoder which will downloaded the
// next video subsegment.
if (decoder == VideoRepDecoder()) {
if (mVideoSubsegmentLoads.IsEmpty() ||
(uint32_t)mVideoSubsegmentIdx >= mVideoSubsegmentLoads.Length()) {
LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
"[%d] before load; mVideoSubsegmentIdx[%d].",
mVideoRepDecoderIdx, VideoRepDecoder(),
mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
} else {
// Change an existing load, and keep subsequent entries to help
// determine if subsegments are cached already.
LOG("Setting decoder [%d] [%p] in mVideoSubsegmentLoads at index "
"[%d] before load; mVideoSubsegmentIdx[%d].",
mVideoRepDecoderIdx, VideoRepDecoder(),
mVideoSubsegmentIdx, mVideoSubsegmentIdx);
mVideoSubsegmentLoads[mVideoSubsegmentIdx] = mVideoRepDecoderIdx;
}
}
// Load the next range of data bytes. If the range is already cached,
// this function will be called again to adaptively download the next
// subsegment.
#ifdef PR_LOGGING
if (decoder.get() == AudioRepDecoder()) {
LOG("Requesting load for audio decoder [%p] subsegment [%d].",
decoder.get(), mAudioSubsegmentIdx);
} else if (decoder.get() == VideoRepDecoder()) {
LOG("Requesting load for video decoder [%p] subsegment [%d].",
decoder.get(), mVideoSubsegmentIdx);
}
#endif
if (!decoder || (decoder != AudioRepDecoder() &&
decoder != VideoRepDecoder())) {
LOG("Invalid decoder [%p]: video idx [%d] audio idx [%d]",
decoder.get(), AudioRepDecoder(), VideoRepDecoder());
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;
decoder->LoadNextByteRange();
} else if (aStatus == NS_BINDING_ABORTED) {
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
if (mOwner) {
@ -731,5 +823,176 @@ DASHDecoder::DecodeError()
}
}
void
DASHDecoder::OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder)
{
if (mShuttingDown) {
LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
return;
}
NS_ASSERTION(aRepDecoder, "aRepDecoder is null!");
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
LOG("Metadata loaded for decoder[%p]", aRepDecoder);
// Decrement audio|video metadata read counter and get ref to active decoder.
nsRefPtr<DASHRepDecoder> activeDecoder;
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
for (uint32_t i = 0; i < mAudioRepDecoders.Length(); i++) {
if (aRepDecoder == mAudioRepDecoders[i]) {
--mAudioMetadataReadCount;
break;
}
}
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
if (aRepDecoder == mVideoRepDecoders[i]) {
--mVideoMetadataReadCount;
break;
}
}
}
// Once all metadata is downloaded for audio|video decoders, start loading
// data for the active decoder.
if (mAudioMetadataReadCount == 0 && mVideoMetadataReadCount == 0) {
if (AudioRepDecoder()) {
LOG("Dispatching load event for audio decoder [%p]", AudioRepDecoder());
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(AudioRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching audio decoder [%p] load event to main thread: "
"rv[%x]", AudioRepDecoder(), rv);
DecodeError();
return;
}
}
if (VideoRepDecoder()) {
LOG("Dispatching load event for video decoder [%p]", VideoRepDecoder());
// Add decoder to subsegment load history.
NS_ASSERTION(mVideoSubsegmentLoads.IsEmpty(),
"No subsegment loads should be recorded at this stage!");
NS_ASSERTION(mVideoSubsegmentIdx == 0,
"Current subsegment should be 0 at this stage!");
LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
"[%d] before load; mVideoSubsegmentIdx[%d].",
mVideoRepDecoderIdx, VideoRepDecoder(),
(uint32_t)mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(VideoRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching video decoder [%p] load event to main thread: "
"rv[%x]", VideoRepDecoder(), rv);
DecodeError();
return;
}
}
}
}
nsresult
DASHDecoder::PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(aRepDecoder == VideoRepDecoder(), NS_ERROR_ILLEGAL_VALUE);
NS_ASSERTION((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
"Index for video decoder is out of bounds!");
NS_ASSERTION((uint32_t)mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges(),
"Can't switch to a byte range out of bounds.");
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
// Now, determine if and which decoder to switch to.
// XXX This download rate is averaged over time, and only refers to the bytes
// downloaded for the given decoder. A running average would be better, and
// something that includes all downloads. But this will do for now.
NS_ASSERTION(VideoRepDecoder(), "Video decoder should not be null.");
NS_ASSERTION(VideoRepDecoder()->GetResource(),
"Video resource should not be null");
bool reliable = false;
double downloadRate = VideoRepDecoder()->GetResource()->GetDownloadRate(&reliable);
uint32_t bestRepIdx = UINT32_MAX;
bool noRepAvailable = !mMPDManager->GetBestRepForBandwidth(mVideoAdaptSetIdx,
downloadRate,
bestRepIdx);
LOG("downloadRate [%f] reliable [%s] bestRepIdx [%d] noRepAvailable",
downloadRate, (reliable ? "yes" : "no"), bestRepIdx,
(noRepAvailable ? "yes" : "no"));
// If there is a higher bitrate stream that can be downloaded with the
// current estimated bandwidth, step up to the next stream, for a graceful
// increase in quality.
uint32_t toDecoderIdx = mVideoRepDecoderIdx;
if (bestRepIdx > toDecoderIdx) {
toDecoderIdx = NS_MIN(toDecoderIdx+1, mVideoRepDecoders.Length()-1);
} else if (toDecoderIdx < bestRepIdx) {
// If the bitrate is too much for the current bandwidth, just use that
// stream directly.
toDecoderIdx = bestRepIdx;
}
NS_ENSURE_TRUE(toDecoderIdx < mVideoRepDecoders.Length(),
NS_ERROR_ILLEGAL_VALUE);
// Notify reader and sub decoders and do the switch.
if (toDecoderIdx != mVideoRepDecoderIdx) {
LOG("*** Switching video decoder from [%d] [%p] to [%d] [%p] at "
"subsegment [%d]", mVideoRepDecoderIdx, VideoRepDecoder(),
toDecoderIdx, mVideoRepDecoders[toDecoderIdx].get(),
mVideoSubsegmentIdx);
// Tell main reader to switch subreaders at |subsegmentIdx| - equates to
// switching data source for reading.
mDASHReader->RequestVideoReaderSwitch(mVideoRepDecoderIdx, toDecoderIdx,
mVideoSubsegmentIdx);
// Notify decoder it is about to be switched.
mVideoRepDecoders[mVideoRepDecoderIdx]->PrepareForSwitch();
// Switch video decoders - equates to switching download source.
mVideoRepDecoderIdx = toDecoderIdx;
}
return NS_OK;
}
bool
DASHDecoder::IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder)
{
NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
// Only return true if |aRepDecoder| is active and metadata for all
// representations has been downloaded.
return ((aRepDecoder == AudioRepDecoder() && mAudioMetadataReadCount == 0) ||
(aRepDecoder == VideoRepDecoder() && mVideoMetadataReadCount == 0));
}
bool
DASHDecoder::IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
int32_t const aSubsegmentIdx)
{
NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
// Return false if there is still metadata to be downloaded.
if (mAudioMetadataReadCount != 0 || mVideoMetadataReadCount != 0) {
return false;
}
// No audio switching; allow the audio decoder to download all subsegments.
if (aRepDecoder == AudioRepDecoder()) {
return true;
}
int32_t videoDecoderIdx = GetRepIdxForVideoSubsegmentLoad(aSubsegmentIdx);
if (aRepDecoder == mVideoRepDecoders[videoDecoderIdx]) {
return true;
}
return false;
}
} // namespace mozilla

View File

@ -66,7 +66,95 @@ public:
// Called on the main thread only.
void NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
nsresult aStatus,
MediaByteRange &aRange);
int32_t const aSubsegmentIdx);
// Notification from an |MediaDecoderReader| class that metadata has been
// read. Declared here to allow overloading.
void OnReadMetadataCompleted() MOZ_OVERRIDE { }
// Notification from |DASHRepDecoder| that a metadata has been read.
// |DASHDecoder| will initiate load of data bytes for active audio/video
// decoders. Called on the decode thread.
void OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder);
// Refers to downloading data bytes, i.e. non metadata.
// Returns true if |aRepDecoder| is an active audio or video sub decoder AND
// if metadata for all audio or video decoders has been read.
// Could be called from any thread; enters decoder monitor.
bool IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder);
// Refers to downloading data bytes during SEEKING.
// Returns true if |aRepDecoder| is the active audio sub decoder, OR if
// it is a video decoder and is allowed to download this subsegment.
// Returns false if there is still some metadata to download.
// Could be called from any thread; enters decoder monitor.
bool IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
int32_t const aSubsegmentIdx);
// Determines if rep/sub decoders should be switched, and if so switches
// them. Notifies |DASHReader| if and when it should switch readers.
// Returns a pointer to the new active decoder.
// Called on the main thread.
nsresult PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder);
// Sets the byte range index for audio|video downloads. Will only increment
// for current active decoders. Could be called from any thread.
// Requires monitor because of write to |mAudioSubsegmentIdx| or
// |mVideoSubsegmentIdx|.
void SetSubsegmentIndex(DASHRepDecoder* aRepDecoder,
uint32_t aSubsegmentIdx)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (aRepDecoder == AudioRepDecoder()) {
mAudioSubsegmentIdx = aSubsegmentIdx;
} else if (aRepDecoder == VideoRepDecoder()) {
mVideoSubsegmentIdx = aSubsegmentIdx;
}
}
private:
// Increments the byte range index for audio|video downloads. Will only
// increment for current active decoders. Could be called from any thread.
// Requires monitor because of write to |mAudioSubsegmentIdx| or
// |mVideoSubsegmentIdx|.
void IncrementSubsegmentIndex(DASHRepDecoder* aRepDecoder)
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
if (aRepDecoder == AudioRepDecoder()) {
mAudioSubsegmentIdx++;
} else if (aRepDecoder == VideoRepDecoder()) {
mVideoSubsegmentIdx++;
}
}
public:
// Gets the byte range index for audio|video downloads. Will only increment
// for current active decoders. Could be called from any thread. Will enter
// monitor for read access off the decode thread.
int32_t GetSubsegmentIndex(DASHRepDecoder* aRepDecoder)
{
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
GetReentrantMonitor());
if (aRepDecoder == AudioRepDecoder()) {
return mAudioSubsegmentIdx;
} else if (aRepDecoder == VideoRepDecoder()) {
return mVideoSubsegmentIdx;
}
return (-1);
}
// Returns the index of the rep decoder used to load a subsegment. Will enter
// monitor for read access off the decode thread.
int32_t GetRepIdxForVideoSubsegmentLoad(int32_t aSubsegmentIdx)
{
NS_ASSERTION(0 < aSubsegmentIdx, "Subsegment index should not be negative.");
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
GetReentrantMonitor());
if ((uint32_t)aSubsegmentIdx < mVideoSubsegmentLoads.Length()) {
return mVideoSubsegmentLoads[aSubsegmentIdx];
} else {
// If it hasn't been downloaded yet, use the lowest bitrate decoder.
return 0;
}
}
// Drop reference to state machine and tell sub-decoders to do the same.
// Only called during shutdown dance, on main thread only.
@ -107,6 +195,38 @@ private:
nsresult CreateAudioRepDecoder(nsIURI* aUrl, Representation const * aRep);
nsresult CreateVideoRepDecoder(nsIURI* aUrl, Representation const * aRep);
// Get audio sub-decoder for current audio |Representation|. Will return
// nullptr for out of range indexes.
// Enters monitor for read access off the decode thread.
// XXX Note: Although an array of audio decoders is provided, audio stream
// switching is not yet supported.
DASHRepDecoder* AudioRepDecoder() {
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
GetReentrantMonitor());
NS_ENSURE_TRUE((uint32_t)mAudioRepDecoderIdx < mAudioRepDecoders.Length(),
nullptr);
if (mAudioRepDecoderIdx < 0) {
return nullptr;
} else {
return mAudioRepDecoders[mAudioRepDecoderIdx];
}
}
// Get video sub-decoder for current video |Representation|. Will return
// nullptr for out of range indexes.
// Enters monitor for read access off the decode thread.
DASHRepDecoder* VideoRepDecoder() {
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
GetReentrantMonitor());
NS_ENSURE_TRUE((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
nullptr);
if (mVideoRepDecoderIdx < 0) {
return nullptr;
} else {
return mVideoRepDecoders[mVideoRepDecoderIdx];
}
}
// Creates audio/video resources for individual |Representation|s.
// On the main thread.
MediaResource* CreateAudioSubResource(nsIURI* aUrl,
@ -141,15 +261,37 @@ private:
// state machine; destroyed in state machine's destructor.
DASHReader* mDASHReader;
// Sub-decoder for current audio |Representation|.
nsRefPtr<DASHRepDecoder> mAudioRepDecoder;
// Array of pointers for the |Representation|s in the audio |AdaptationSet|.
nsTArray<nsRefPtr<DASHRepDecoder> > mAudioRepDecoders;
// Sub-decoder vars. Note: For all following members, the decode monitor
// should be held for write access on decode thread, and all read/write off
// the decode thread.
// Sub-decoder for current video |Representation|.
nsRefPtr<DASHRepDecoder> mVideoRepDecoder;
// Array of pointers for the |Representation|s in the video |AdaptationSet|.
// Index of the video |AdaptationSet|.
int32_t mVideoAdaptSetIdx;
// Indexes for the current audio and video decoders.
int32_t mAudioRepDecoderIdx;
int32_t mVideoRepDecoderIdx;
// Array of pointers for the |Representation|s in the audio/video
// |AdaptationSet|.
nsTArray<nsRefPtr<DASHRepDecoder> > mAudioRepDecoders;
nsTArray<nsRefPtr<DASHRepDecoder> > mVideoRepDecoders;
// Current index of subsegments downloaded for audio/video decoder.
int32_t mAudioSubsegmentIdx;
int32_t mVideoSubsegmentIdx;
// Count for the number of readers which have called |OnReadMetadataCompleted|.
// Initialised to 0; incremented for every decoder which has |Load| called;
// and decremented for every call to |OnReadMetadataCompleted|. When it is
// zero again, all metadata has been read for audio or video, and data bytes
// can be downloaded.
uint32_t mAudioMetadataReadCount;
uint32_t mVideoMetadataReadCount;
// Array records the index of the decoder/Representation which loaded each
// subsegment.
nsTArray<int32_t> mVideoSubsegmentLoads;
};
} // namespace mozilla

View File

@ -16,20 +16,46 @@
#include "VideoFrameContainer.h"
#include "AbstractMediaDecoder.h"
#include "DASHReader.h"
#include "DASHDecoder.h"
namespace mozilla {
#ifdef PR_LOGGING
extern PRLogModuleInfo* gMediaDecoderLog;
#define LOG(msg, ...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
PRLogModuleInfo* gDASHReaderLog;
#define LOG(msg, ...) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
("%p [DASHReader] " msg, this, __VA_ARGS__))
#define LOG1(msg) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
#define LOG1(msg) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
("%p [DASHReader] " msg, this))
#else
#define LOG(msg, ...)
#define LOG1(msg)
#endif
DASHReader::DASHReader(AbstractMediaDecoder* aDecoder) :
MediaDecoderReader(aDecoder),
mReadMetadataMonitor("media.dashreader.readmetadata"),
mReadyToReadMetadata(false),
mDecoderIsShuttingDown(false),
mAudioReader(this),
mVideoReader(this),
mAudioReaders(this),
mVideoReaders(this),
mSwitchVideoReaders(false),
mSwitchCount(-1)
{
MOZ_COUNT_CTOR(DASHReader);
#ifdef PR_LOGGING
if (!gDASHReaderLog) {
gDASHReaderLog = PR_NewLogModule("DASHReader");
}
#endif
}
DASHReader::~DASHReader()
{
MOZ_COUNT_DTOR(DASHReader);
}
nsresult
DASHReader::Init(MediaDecoderReader* aCloneDonor)
{
@ -52,7 +78,7 @@ DASHReader::Init(MediaDecoderReader* aCloneDonor)
}
void
DASHReader::AddAudioReader(MediaDecoderReader* aAudioReader)
DASHReader::AddAudioReader(DASHRepReader* aAudioReader)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aAudioReader, );
@ -66,7 +92,7 @@ DASHReader::AddAudioReader(MediaDecoderReader* aAudioReader)
}
void
DASHReader::AddVideoReader(MediaDecoderReader* aVideoReader)
DASHReader::AddVideoReader(DASHRepReader* aVideoReader)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ENSURE_TRUE(aVideoReader, );
@ -79,12 +105,26 @@ DASHReader::AddVideoReader(MediaDecoderReader* aVideoReader)
mVideoReader = aVideoReader;
}
bool
DASHReader::HasAudio()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
return mAudioReader ? mAudioReader->HasAudio() : false;
}
bool
DASHReader::HasVideo()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
return mVideoReader ? mVideoReader->HasVideo() : false;
}
int64_t
DASHReader::VideoQueueMemoryInUse()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
return (mVideoReader ? mVideoReader->VideoQueueMemoryInUse() : 0);
return VideoQueueMemoryInUse();
}
int64_t
@ -92,7 +132,7 @@ DASHReader::AudioQueueMemoryInUse()
{
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
return (mAudioReader ? mAudioReader->AudioQueueMemoryInUse() : 0);
return AudioQueueMemoryInUse();
}
bool
@ -116,7 +156,7 @@ DASHReader::DecodeAudioData()
nsresult
DASHReader::ReadMetadata(VideoInfo* aInfo,
MetadataTags** aTags)
MetadataTags** aTags)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
@ -130,15 +170,27 @@ DASHReader::ReadMetadata(VideoInfo* aInfo,
// Verify no other errors before continuing.
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(aTags, "Called with null MetadataTags**.");
*aTags = nullptr;
// Get metadata from child readers.
VideoInfo audioInfo, videoInfo;
if (mVideoReader) {
rv = mVideoReader->ReadMetadata(&videoInfo, aTags);
// Read metadata for all video streams.
for (uint i = 0; i < mVideoReaders.Length(); i++) {
// Use an nsAutoPtr here to ensure |tags| memory does not leak.
nsAutoPtr<nsHTMLMediaElement::MetadataTags> tags;
rv = mVideoReaders[i]->ReadMetadata(&videoInfo, getter_Transfers(tags));
NS_ENSURE_SUCCESS(rv, rv);
mInfo.mHasVideo = videoInfo.mHasVideo;
mInfo.mDisplay = videoInfo.mDisplay;
// Use metadata from current video sub reader to populate aInfo.
if (mVideoReaders[i] == mVideoReader) {
mInfo.mHasVideo = videoInfo.mHasVideo;
mInfo.mDisplay = videoInfo.mDisplay;
}
}
// Read metadata for audio stream.
// Note: Getting metadata tags from audio reader only for now.
// XXX Audio stream switching not yet supported.
if (mAudioReader) {
rv = mAudioReader->ReadMetadata(&audioInfo, aTags);
NS_ENSURE_SUCCESS(rv, rv);
@ -303,7 +355,7 @@ DASHReader::AudioQueue()
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
NS_ASSERTION(mAudioReader, "mAudioReader is NULL!");
return mAudioReader->AudioQueue();
return mAudioQueue;
}
MediaQueue<VideoData>&
@ -312,7 +364,7 @@ DASHReader::VideoQueue()
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
mDecoder->GetReentrantMonitor());
NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
return mVideoReader->VideoQueue();
return mVideoQueue;
}
bool
@ -326,5 +378,121 @@ DASHReader::IsSeekableInBufferedRanges()
(mAudioReader && !mAudioReader->IsSeekableInBufferedRanges()));
}
void
DASHReader::RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
uint32_t aToReaderIdx,
uint32_t aSubsegmentIdx)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ASSERTION(aFromReaderIdx < mVideoReaders.Length(),
"From index is greater than number of video readers!");
NS_ASSERTION(aToReaderIdx < mVideoReaders.Length(),
"To index is greater than number of video readers!");
NS_ASSERTION(aToReaderIdx != aFromReaderIdx,
"Don't request switches to same reader!");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
if (mSwitchCount < 0) {
mSwitchCount = 0;
}
DASHRepReader* fromReader = mVideoReaders[aFromReaderIdx];
DASHRepReader* toReader = mVideoReaders[aToReaderIdx];
LOG("Switch requested from reader [%d] [%p] to reader [%d] [%p] "
"at subsegment[%d].",
aFromReaderIdx, fromReader, aToReaderIdx, toReader, aSubsegmentIdx);
// Append the subsegment index to the list of pending switches.
mSwitchToVideoSubsegmentIndexes.AppendElement(aSubsegmentIdx);
// Tell the SWITCH FROM reader when it should stop reading.
fromReader->RequestSwitchAtSubsegment(aSubsegmentIdx, toReader);
// Tell the SWITCH TO reader to seek to the correct offset.
toReader->RequestSeekToSubsegment(aSubsegmentIdx);
mSwitchVideoReaders = true;
}
void
DASHReader::PossiblySwitchVideoReaders()
{
NS_ASSERTION(mDecoder, "Decoder should not be null");
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
// Flag to switch streams is set in |RequestVideoReaderSwitch|.
if (!mSwitchVideoReaders) {
return;
}
// Only switch if we reached a switch access point.
NS_ENSURE_TRUE(0 <= mSwitchCount, );
NS_ENSURE_TRUE((uint32_t)mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length(), );
uint32_t switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
if (!mVideoReader->HasReachedSubsegment(switchIdx)) {
return;
}
// Get Representation index to switch to.
DASHDecoder* dashDecoder = static_cast<DASHDecoder*>(mDecoder);
int32_t toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
NS_ENSURE_TRUE(0 <= toReaderIdx, );
NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
DASHRepReader* fromReader = mVideoReader;
DASHRepReader* toReader = mVideoReaders[toReaderIdx];
NS_ENSURE_TRUE(fromReader != toReader, );
LOG("Switching video readers now from [%p] to [%p] at subsegment [%d]: "
"mSwitchCount [%d].",
fromReader, toReader, switchIdx, mSwitchCount);
// Switch readers while in the monitor.
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mVideoReader = toReader;
// Prep readers for next switch, also while in monitor.
if ((uint32_t)++mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length()) {
// Get the subsegment at which to switch.
switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
// Update from and to reader ptrs for next switch.
fromReader = toReader;
toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
toReader = mVideoReaders[toReaderIdx];
NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
NS_ENSURE_TRUE(fromReader != toReader, );
// Tell the SWITCH FROM reader when it should stop reading.
fromReader->RequestSwitchAtSubsegment(switchIdx, toReader);
// Tell the SWITCH TO reader to seek to the correct offset.
toReader->RequestSeekToSubsegment(switchIdx);
} else {
// If there are no more pending switches, unset the switch readers flag.
mSwitchVideoReaders = false;
}
}
void
DASHReader::PrepareToDecode()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
// Flag to switch streams is set by |DASHDecoder|.
if (!mSwitchVideoReaders) {
return;
}
PossiblySwitchVideoReaders();
// Prepare each sub reader for decoding: includes seeking to the correct
// offset if a seek was previously requested.
for (uint32_t i = 0; i < mVideoReaders.Length(); i++) {
mVideoReaders[i]->PrepareToDecode();
}
}
} // namespace mozilla

View File

@ -16,34 +16,24 @@
#if !defined(DASHReader_h_)
#define DASHReader_h_
#include "VideoUtils.h"
#include "MediaDecoderReader.h"
#include "DASHRepReader.h"
namespace mozilla {
class DASHRepReader;
class DASHReader : public MediaDecoderReader
{
public:
DASHReader(AbstractMediaDecoder* aDecoder) :
MediaDecoderReader(aDecoder),
mReadMetadataMonitor("media.dashreader.readmetadata"),
mReadyToReadMetadata(false),
mDecoderIsShuttingDown(false),
mAudioReader(this),
mVideoReader(this),
mAudioReaders(this),
mVideoReaders(this)
{
MOZ_COUNT_CTOR(DASHReader);
}
~DASHReader()
{
MOZ_COUNT_DTOR(DASHReader);
}
DASHReader(AbstractMediaDecoder* aDecoder);
~DASHReader();
// Adds a pointer to a audio/video reader for a media |Representation|.
// Called on the main thread only.
void AddAudioReader(MediaDecoderReader* aAudioReader);
void AddVideoReader(MediaDecoderReader* aVideoReader);
void AddAudioReader(DASHRepReader* aAudioReader);
void AddVideoReader(DASHRepReader* aVideoReader);
// Waits for metadata bytes to be downloaded, then reads and parses them.
// Called on the decode thread only.
@ -89,19 +79,13 @@ public:
// 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;
}
bool HasAudio();
bool HasVideo();
// Returns references to the audio/video queues of sub-readers. Called on
// decode, state machine and audio threads.
MediaQueue<AudioData>& AudioQueue();
MediaQueue<VideoData>& VideoQueue();
MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE;
MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE;
// Called from MediaDecoderStateMachine on the main thread.
nsresult Init(MediaDecoderReader* aCloneDonor);
@ -110,6 +94,11 @@ public:
int64_t VideoQueueMemoryInUse();
int64_t AudioQueueMemoryInUse();
// Called on the decode thread, at the start of the decode loop, before
// |DecodeVideoFrame|. Carries out video reader switch if previously
// requested, and tells sub-readers to |PrepareToDecode|.
void PrepareToDecode() MOZ_OVERRIDE;
// Called on the decode thread.
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
bool DecodeAudioData();
@ -129,45 +118,20 @@ public:
// 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(DASHReader::ReentrantMonitorConditionallyEnter);
if (aEnter) {
mReentrantMonitor = &aReentrantMonitor;
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->Enter();
}
}
~ReentrantMonitorConditionallyEnter(void)
{
if (mReentrantMonitor) {
mReentrantMonitor->Exit();
}
MOZ_COUNT_DTOR(DASHReader::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*);
// Prepares for an upcoming switch of video readers. Called by
// |DASHDecoder| when it has switched download streams. Sets the index of
// the reader to switch TO and the index of the subsegment to switch AT
// (start offset). (Note: Subsegment boundaries are switch access points for
// DASH-WebM). Called on the main thread. Must be in the decode monitor.
void RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
uint32_t aToReaderIdx,
uint32_t aSubsegmentIdx);
// Ptr to the |ReentrantMonitor| object. Null if |aEnter| in constructor
// was false.
ReentrantMonitor* mReentrantMonitor;
};
private:
// Switches video subreaders if a stream-switch flag has been set, and the
// current reader has read up to the switching subsegment (start offset).
// Called on the decode thread only.
void PossiblySwitchVideoReaders();
// Monitor and booleans used to wait for metadata bytes to be downloaded, and
// skip reading metadata if |DASHDecoder|'s shutdown is in progress.
@ -199,7 +163,7 @@ private:
// Override '=' to always assert thread is "in monitor" for writes/changes
// to |mSubReader|.
MonitoredSubReader& operator=(MediaDecoderReader* rhs)
MonitoredSubReader& operator=(DASHRepReader* rhs)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
@ -209,7 +173,7 @@ private:
// Override '*' to assert threads other than the decode thread are "in
// monitor" for ptr reads.
operator MediaDecoderReader*() const
operator DASHRepReader*() const
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
if (!mReader->GetDecoder()->OnDecodeThread()) {
@ -220,7 +184,7 @@ private:
// Override '->' to assert threads other than the decode thread are "in
// monitor" for |mSubReader| function calls.
MediaDecoderReader* operator->() const
DASHRepReader* operator->() const
{
return *this;
}
@ -228,7 +192,7 @@ private:
// Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
DASHReader* mReader;
// Ref ptr to the sub reader.
nsRefPtr<MediaDecoderReader> mSubReader;
nsRefPtr<DASHRepReader> mSubReader;
};
// Wrapped ref ptrs to current sub-readers of individual media
@ -277,7 +241,7 @@ private:
// 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<MediaDecoderReader>& operator[](uint32_t i)
nsRefPtr<DASHRepReader>& operator[](uint32_t i)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
if (!mReader->GetDecoder()->OnDecodeThread()) {
@ -289,7 +253,7 @@ private:
// Appends a reader to the end of |mSubReaderList|. Will always assert that
// the thread is "in monitor".
void
AppendElement(MediaDecoderReader* aReader)
AppendElement(DASHRepReader* aReader)
{
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
@ -299,7 +263,7 @@ private:
// Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
DASHReader* mReader;
// Ref ptrs to the sub readers.
nsTArray<nsRefPtr<MediaDecoderReader> > mSubReaderList;
nsTArray<nsRefPtr<DASHRepReader> > mSubReaderList;
};
// Ref ptrs to all sub-readers of individual media |Representation|s.
@ -308,6 +272,18 @@ private:
// decode thread does not need to be protected.
MonitoredSubReaderList mAudioReaders;
MonitoredSubReaderList mVideoReaders;
// When true, indicates that we should switch reader. Must be in the monitor
// for write access and read access off the decode thread.
bool mSwitchVideoReaders;
// Indicates the subsegment index at which the reader should switch. Must be
// in the monitor for write access and read access off the decode thread.
nsTArray<uint32_t> mSwitchToVideoSubsegmentIndexes;
// Counts the number of switches that have taken place. Must be in the
// monitor for write access and read access off the decode thread.
int32_t mSwitchCount;
};
} // namespace mozilla

View File

@ -19,6 +19,7 @@
#include "DASHReader.h"
#include "MediaResource.h"
#include "DASHRepDecoder.h"
#include "WebMReader.h"
namespace mozilla {
@ -133,16 +134,24 @@ DASHRepDecoder::NotifyDownloadEnded(nsresult 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]",
LOG("Metadata chunk [%d] downloaded: range requested [%lld - %lld] "
"subsegmentIdx [%d]",
mMetadataChunkCount,
mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx);
mMetadataChunkCount--;
} else {
LOG("Byte range downloaded: status [%x] range requested [%lld - %lld] "
"subsegmentIdx [%d]",
aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd,
mSubsegmentIdx);
if ((uint32_t)mSubsegmentIdx == mByteRanges.Length()-1) {
mResource->NotifyLastByteRange();
}
// 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);
// Only notify IF this decoder is allowed to download data.
NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
"This decoder should not have downloaded data.");
mMainDecoder->NotifyDownloadEnded(this, aStatus, mSubsegmentIdx);
}
} else if (aStatus == NS_BINDING_ABORTED) {
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
@ -161,63 +170,94 @@ DASHRepDecoder::OnReadMetadataCompleted()
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
// If shutting down, just return silently.
if (mShuttingDown) {
LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
return;
}
LOG1("Metadata has been read.");
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &DASHRepDecoder::LoadNextByteRange);
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
// Metadata loaded and read for this stream; ok to populate byte ranges.
nsresult rv = PopulateByteRanges();
if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
LOG("Error populating byte ranges [%x]", rv);
DecodeError();
return;
}
mMainDecoder->OnReadMetadataCompleted(this);
}
nsresult
DASHRepDecoder::PopulateByteRanges()
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
// Should not be called during shutdown.
NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
if (!mByteRanges.IsEmpty()) {
return NS_OK;
}
NS_ENSURE_TRUE(mReader, NS_ERROR_NULL_POINTER);
LOG1("Populating byte range array.");
return mReader->GetSubsegmentByteRanges(mByteRanges);
}
void
DASHRepDecoder::LoadNextByteRange()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (!mResource) {
LOG1("Error: resource is reported as null!");
NS_ASSERTION(mResource, "Error: resource is reported as null!");
// Return silently if shutting down.
if (mShuttingDown) {
LOG1("Shutting down! Ignoring LoadNextByteRange().");
return;
}
NS_ASSERTION(mMainDecoder, "Error: main decoder is null!");
NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
"Should not be called on non-active decoders!");
// Cannot have empty byte ranges.
if (mByteRanges.IsEmpty()) {
LOG1("Error getting list of subsegment byte ranges.");
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];
int32_t subsegmentIdx = mMainDecoder->GetSubsegmentIndex(this);
NS_ASSERTION(0 <= subsegmentIdx,
"Subsegment index should be >= 0 for active decoders");
if (subsegmentIdx >= 0 && (uint32_t)subsegmentIdx < mByteRanges.Length()) {
mCurrentByteRange = mByteRanges[subsegmentIdx];
mSubsegmentIdx = subsegmentIdx;
} else {
mCurrentByteRange.Clear();
LOG("End of subsegments: index [%d] out of range.", mSubsegmentIdx);
mSubsegmentIdx = -1;
LOG("End of subsegments: index [%d] out of range.", subsegmentIdx);
return;
}
// Request a seek for the first reader. Required so that the reader is
// primed to start here, and will block subsequent subsegment seeks unless
// the subsegment has been read.
if (subsegmentIdx == 0) {
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
mReader->RequestSeekToSubsegment(0);
}
// Open byte range corresponding to subsegment.
rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
nsresult rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
if (NS_FAILED(rv)) {
LOG("Error opening byte range [%d - %d]: rv [%x].",
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, rv);
LOG("Error opening byte range [%lld - %lld]: subsegmentIdx [%d] rv [%x].",
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx, rv);
NetworkError();
return;
}
// Increment subsegment index for next load.
mSubsegmentIdx++;
}
nsresult
@ -226,41 +266,78 @@ DASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
{
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;
// Only check data ranges if they're available and if this decoder is active,
// i.e. inactive rep decoders should only load metadata.
bool canDownloadData = mMainDecoder->IsDecoderAllowedToDownloadData(this);
if (canDownloadData) {
for (int i = 0; i < mByteRanges.Length(); i++) {
NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
// Check if |aOffset| lies within the current data range.
if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
mCurrentByteRange = aByteRange = mByteRanges[i];
mSubsegmentIdx = i;
// XXX Hack: should be setting subsegment outside this function, but
// need to review seeking for multiple switches anyhow.
mMainDecoder->SetSubsegmentIndex(this, i);
LOG("Getting DATA range [%d] for seek offset [%lld]: "
"bytes [%lld] to [%lld]",
i, aOffset, aByteRange.mStart, aByteRange.mEnd);
return NS_OK;
}
}
} else {
LOG1("Restricting seekable byte ranges to metadata for this decoder.");
}
// Don't allow metadata downloads once they're loaded and byte ranges have
// been populated.
bool canDownloadMetadata = mByteRanges.IsEmpty();
if (canDownloadMetadata) {
// Check metadata ranges; init range.
if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
mCurrentByteRange = aByteRange = mInitByteRange;
mSubsegmentIdx = 0;
LOG("Getting INIT range for seek offset [%lld]: bytes [%lld] to "
"[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
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;
// ... index range.
if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
mCurrentByteRange = aByteRange = mIndexByteRange;
mSubsegmentIdx = 0;
LOG("Getting INDEXES range for seek offset [%lld]: bytes [%lld] to "
"[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
return NS_OK;
}
} else {
LOG1("Metadata should be read; inhibiting further metadata downloads.");
}
// If no byte range is found by this stage, clear the parameter and return.
aByteRange.Clear();
if (mByteRanges.IsEmpty()) {
if (mByteRanges.IsEmpty() || !canDownloadData || !canDownloadMetadata) {
// Assume mByteRanges will be populated after metadata is read.
LOG("Can't get range for offset [%d].", aOffset);
LOG("Data ranges not populated [%s]; data download restricted [%s]; "
"metadata download restricted [%s]: offset[%lld].",
(mByteRanges.IsEmpty() ? "yes" : "no"),
(canDownloadData ? "no" : "yes"),
(canDownloadMetadata ? "no" : "yes"), 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);
LOG("Error! Offset [%lld] is in an unknown range!", aOffset);
return NS_ERROR_ILLEGAL_VALUE;
}
}
void
DASHRepDecoder::PrepareForSwitch()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Ensure that the media cache writes any data held in its partial block.
mResource->FlushCache();
}
void
DASHRepDecoder::NetworkError()
{
@ -302,7 +379,7 @@ DASHRepDecoder::NotifyDataArrived(const char* aBuffer,
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
LOG("Data bytes [%d - %d] arrived via buffer [%p].",
LOG("Data bytes [%lld - %lld] arrived via buffer [%p].",
aOffset, aOffset+aLength, aBuffer);
// Notify reader directly, since call to |MediaDecoderStateMachine|::
// |NotifyDataArrived| will go to |DASHReader|::|NotifyDataArrived|, which

View File

@ -25,6 +25,7 @@
namespace mozilla {
class DASHDecoder;
class DASHRepReader;
class DASHRepDecoder : public MediaDecoder
{
@ -39,7 +40,7 @@ public:
mMPDRepresentation(nullptr),
mMetadataChunkCount(0),
mCurrentByteRange(),
mSubsegmentIdx(0),
mSubsegmentIdx(-1),
mReader(nullptr)
{
MOZ_COUNT_CTOR(DASHRepDecoder);
@ -128,6 +129,14 @@ public:
nsresult GetByteRangeForSeek(int64_t const aOffset,
MediaByteRange& aByteRange);
// Gets the number of data byte ranges (not inc. metadata).
uint32_t GetNumDataByteRanges() {
return mByteRanges.Length();
}
// Notify that a switch is about to happen. Called on the main thread.
void PrepareForSwitch();
// Returns true if the current thread is the state machine thread.
bool OnStateMachineThread() const;
@ -135,14 +144,14 @@ public:
bool OnDecodeThread() const;
// Returns main decoder's monitor for synchronised access.
ReentrantMonitor& GetReentrantMonitor();
ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE;
// Called on the decode thread from WebMReader.
ImageContainer* GetImageContainer();
// Called when Metadata has been read; notifies that index data is read.
// Called on the decode thread only.
void OnReadMetadataCompleted();
void OnReadMetadataCompleted() MOZ_OVERRIDE;
// Overridden to cleanup ref to |DASHDecoder|. Called on main thread only.
void Shutdown() {
@ -162,6 +171,10 @@ public:
void DecodeError();
private:
// Populates |mByteRanges| by calling |GetIndexByteRanges| from |mReader|.
// Called on the main thread only.
nsresult PopulateByteRanges();
// The main decoder.
nsRefPtr<DASHDecoder> mMainDecoder;
// This decoder's MPD |Representation| object.
@ -179,12 +192,12 @@ private:
// The current byte range being requested.
MediaByteRange mCurrentByteRange;
// Index of the current byte range.
uint64_t mSubsegmentIdx;
// Index of the current byte range. Initialized to -1.
int32_t mSubsegmentIdx;
// Ptr to the reader object for this |Representation|. Owned by state
// machine.
MediaDecoderReader* mReader;
DASHRepReader* mReader;
};
} // namespace mozilla

View File

@ -0,0 +1,62 @@
/* -*- 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 DASHDecoder.cpp for comments on DASH object interaction
*/
#if !defined(DASHRepReader_h_)
#define DASHRepReader_h_
#include "VideoUtils.h"
#include "MediaDecoderReader.h"
#include "DASHReader.h"
namespace mozilla {
class DASHReader;
class DASHRepReader : public MediaDecoderReader
{
public:
DASHRepReader(AbstractMediaDecoder* aDecoder)
: MediaDecoderReader(aDecoder) { }
virtual ~DASHRepReader() { }
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DASHRepReader)
virtual void SetMainReader(DASHReader *aMainReader) = 0;
// Sets range for initialization bytes; used by DASH.
virtual void SetInitByteRange(MediaByteRange &aByteRange) = 0;
// Sets range for index frame bytes; used by DASH.
virtual void SetIndexByteRange(MediaByteRange &aByteRange) = 0;
// Returns list of ranges for index frame start/end offsets. Used by DASH.
virtual nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges) = 0;
// Returns true if the reader has reached a DASH switch access point.
virtual bool HasReachedSubsegment(uint32_t aSubsegmentIndex) = 0;
// Requests a seek to the start of a particular DASH subsegment.
virtual void RequestSeekToSubsegment(uint32_t aIdx) = 0;
// Reader should stop reading at the start of the specified subsegment, and
// should prepare for the next reader to add data to the video queue.
// Should be implemented by a sub-reader, e.g. |nsDASHWebMReader|.
virtual void RequestSwitchAtSubsegment(int32_t aCluster,
MediaDecoderReader* aNextReader) = 0;
};
}// namespace mozilla
#endif /*DASHRepReader*/

View File

@ -23,6 +23,7 @@ EXPORTS := \
DASHDecoder.h \
DASHRepDecoder.h \
DASHReader.h \
DASHRepReader.h \
$(NULL)
CPPSRCS := \

View File

@ -103,7 +103,11 @@ static int64_t webm_tell(void *aUserData)
}
WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
#ifdef MOZ_DASH
: DASHRepReader(aDecoder),
#else
: MediaDecoderReader(aDecoder),
#endif
mContext(nullptr),
mPacketCount(0),
mChannels(0),
@ -113,6 +117,15 @@ WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
mAudioFrames(0),
mHasVideo(false),
mHasAudio(false)
#ifdef MOZ_DASH
, mMainReader(nullptr),
mSwitchingCluster(-1),
mNextReader(nullptr),
mSeekToCluster(-1),
mCurrentOffset(-1),
mPushVideoPacketToNextReader(false),
mReachedSwitchAccessPoint(false)
#endif
{
MOZ_COUNT_CTOR(WebMReader);
// Zero these member vars to avoid crashes in VP8 destroy and Vorbis clear
@ -204,7 +217,11 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
io.seek = webm_seek;
io.tell = webm_tell;
io.userdata = mDecoder;
#ifdef MOZ_DASH
int64_t maxOffset = mInitByteRange.IsNull() ? -1 : mInitByteRange.mEnd;
#else
int64_t maxOffset = -1;
#endif
int r = nestegg_init(&mContext, io, nullptr, maxOffset);
if (r == -1) {
return NS_ERROR_FAILURE;
@ -354,6 +371,7 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
}
}
#ifdef MOZ_DASH
// Byte range for cues has been specified; load them.
if (!mCuesByteRange.IsNull()) {
maxOffset = mCuesByteRange.mEnd;
@ -386,12 +404,15 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
}
} while (!done);
}
#endif
*aInfo = mInfo;
*aTags = nullptr;
#ifdef MOZ_DASH
mDecoder->OnReadMetadataCompleted();
#endif
return NS_OK;
}
@ -513,7 +534,7 @@ bool WebMReader::DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset)
};
total_frames += frames;
mAudioQueue.Push(new AudioData(aOffset,
AudioQueue().Push(new AudioData(aOffset,
time.value(),
duration.value(),
frames,
@ -530,6 +551,46 @@ bool WebMReader::DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset)
}
nsReturnRef<NesteggPacketHolder> WebMReader::NextPacket(TrackType aTrackType)
#ifdef MOZ_DASH
{
nsAutoRef<NesteggPacketHolder> holder;
// Get packet from next reader if we're at a switching point; most likely we
// did not download the next packet for this reader's stream, so we have to
// get it from the next one. Note: Switch to next reader only for video;
// audio switching is not supported in the DASH-WebM On Demand profile.
if (aTrackType == VIDEO &&
(uint32_t)mSwitchingCluster < mClusterByteRanges.Length() &&
mCurrentOffset == mClusterByteRanges[mSwitchingCluster].mStart) {
if (mVideoPackets.GetSize() > 0) {
holder = NextPacketInternal(VIDEO);
LOG(PR_LOG_DEBUG,
("WebMReader[%p] got packet from mVideoPackets @[%lld]",
this, holder->mOffset));
} else {
mReachedSwitchAccessPoint = true;
NS_ASSERTION(mNextReader,
"Stream switch has been requested but mNextReader is null");
holder = mNextReader->NextPacket(aTrackType);
mPushVideoPacketToNextReader = true;
// Reset for possible future switches.
mSwitchingCluster = -1;
LOG(PR_LOG_DEBUG,
("WebMReader[%p] got packet from mNextReader[%p] @[%lld]",
this, mNextReader.get(), (holder ? holder->mOffset : 0)));
}
} else {
holder = NextPacketInternal(aTrackType);
if (holder) {
mCurrentOffset = holder->mOffset;
}
}
return holder.out();
}
nsReturnRef<NesteggPacketHolder>
WebMReader::NextPacketInternal(TrackType aTrackType)
#endif
{
// The packet queue that packets will be pushed on if they
// are not the type we are interested in.
@ -598,7 +659,7 @@ bool WebMReader::DecodeAudioData()
nsAutoRef<NesteggPacketHolder> holder(NextPacket(AUDIO));
if (!holder) {
mAudioQueue.Finish();
AudioQueue().Finish();
return false;
}
@ -617,7 +678,7 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
nsAutoRef<NesteggPacketHolder> holder(NextPacket(VIDEO));
if (!holder) {
mVideoQueue.Finish();
VideoQueue().Finish();
return false;
}
@ -652,7 +713,7 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
if (r == -1) {
return false;
}
mVideoPackets.PushFront(next_holder.disown());
PushVideoPacket(next_holder.disown());
} else {
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
int64_t endTime = mDecoder->GetEndMediaTime();
@ -752,19 +813,37 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
decoded++;
NS_ASSERTION(decoded <= parsed,
"Expect only 1 frame per chunk per packet in WebM...");
mVideoQueue.Push(v);
VideoQueue().Push(v);
}
}
return true;
}
void
WebMReader::PushVideoPacket(NesteggPacketHolder* aItem)
{
#ifdef MOZ_DASH
if (mPushVideoPacketToNextReader) {
NS_ASSERTION(mNextReader,
"Stream switch has been requested but mNextReader is null");
mNextReader->mVideoPackets.PushFront(aItem);
mPushVideoPacketToNextReader = false;
} else {
#endif
mVideoPackets.PushFront(aItem);
#ifdef MOZ_DASH
}
#endif
}
nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
int64_t aCurrentTime)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
LOG(PR_LOG_DEBUG, ("%p About to seek to %fs", mDecoder, aTarget/1000000.0));
LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
this, mDecoder, aTarget/1000000.0));
if (NS_FAILED(ResetDecode())) {
return NS_ERROR_FAILURE;
}
@ -836,8 +915,9 @@ void WebMReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_
mBufferedState->NotifyDataArrived(aBuffer, aLength, aOffset);
}
#ifdef MOZ_DASH
nsresult
WebMReader::GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges)
WebMReader::GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
{
NS_ENSURE_TRUE(mContext, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE(aByteRanges.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
@ -849,6 +929,97 @@ WebMReader::GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges)
return NS_OK;
}
void
WebMReader::RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
MediaDecoderReader* aNextReader)
{
NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
"Should be on main thread or decode thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
// Only allow one switch at a time; ignore if one is already requested.
if (mSwitchingCluster != -1) {
return;
}
NS_ENSURE_TRUE((uint32_t)aSubsegmentIdx < mClusterByteRanges.Length(), );
mSwitchingCluster = aSubsegmentIdx;
NS_ENSURE_TRUE(aNextReader != this, );
mNextReader = static_cast<WebMReader*>(aNextReader);
}
void
WebMReader::RequestSeekToSubsegment(uint32_t aIdx)
{
NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
"Should be on main thread or decode thread.");
NS_ASSERTION(mDecoder, "decoder should not be null!");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
// Don't seek if we're about to switch to another reader.
if (mSwitchingCluster != -1) {
return;
}
// Only allow seeking if a request was not already made.
if (mSeekToCluster != -1) {
return;
}
NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
mSeekToCluster = aIdx;
// XXX Hack to get the resource to seek to the correct offset if the decode
// thread is in shutdown, e.g. if the video is not autoplay.
if (mDecoder->IsShutdown()) {
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mDecoder->GetResource()->Seek(PR_SEEK_SET,
mClusterByteRanges[mSeekToCluster].mStart);
}
}
void
WebMReader::PrepareToDecode()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
if (mSeekToCluster != -1) {
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
SeekToCluster(mSeekToCluster);
}
}
void
WebMReader::SeekToCluster(uint32_t aIdx)
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
NS_ASSERTION(0 <= mSeekToCluster, "mSeekToCluster should be set.");
NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: seeking to "
"subsegment [%lld] at offset [%lld]",
this, mDecoder, aIdx, mClusterByteRanges[aIdx].mStart));
int r = nestegg_offset_seek(mContext, mClusterByteRanges[aIdx].mStart);
NS_ENSURE_TRUE(r == 0, );
mSeekToCluster = -1;
}
bool
WebMReader::HasReachedSubsegment(uint32_t aSubsegmentIndex)
{
NS_ASSERTION(mDecoder, "Decoder is null.");
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
NS_ENSURE_TRUE(aSubsegmentIndex < mClusterByteRanges.Length(), false);
NS_ASSERTION(mDecoder->GetResource(), "Decoder has no media resource.");
if (mReachedSwitchAccessPoint) {
LOG(PR_LOG_DEBUG,
("Reader [%p] for Decoder [%p]: reached switching offset [%lld] = "
"mClusterByteRanges[%d].mStart[%lld]",
this, mDecoder, mCurrentOffset, aSubsegmentIndex,
mClusterByteRanges[aSubsegmentIndex].mStart));
mReachedSwitchAccessPoint = false;
return true;
}
return false;
}
#endif /* MOZ_DASH */
} // namespace mozilla

View File

@ -22,6 +22,10 @@
#include "vorbis/codec.h"
#endif
#ifdef MOZ_DASH
#include "DASHRepReader.h"
#endif
namespace mozilla {
class WebMBufferedState;
@ -97,7 +101,11 @@ class WebMPacketQueue : private nsDeque {
}
};
#ifdef MOZ_DASH
class WebMReader : public DASHRepReader
#else
class WebMReader : public MediaDecoderReader
#endif
{
public:
WebMReader(AbstractMediaDecoder* aDecoder);
@ -136,20 +144,75 @@ public:
virtual nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
#ifdef MOZ_DASH
virtual void SetMainReader(DASHReader *aMainReader) MOZ_OVERRIDE {
NS_ASSERTION(aMainReader, "aMainReader is null.");
mMainReader = aMainReader;
}
// Called by |DASHReader| on the decode thread so that this reader will
// start reading at the appropriate subsegment/cluster. If this is not the
// current reader and a switch was previously requested, then it will seek to
// starting offset of the subsegment at which it is supposed to switch.
// Called on the decode thread, enters the decode monitor.
void PrepareToDecode() MOZ_OVERRIDE;
// Returns a reference to the audio/video queue of the main reader.
// Allows for a single audio/video queue to be shared among multiple
// |WebMReader|s.
MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE {
if (mMainReader) {
return mMainReader->AudioQueue();
} else {
return MediaDecoderReader::AudioQueue();
}
}
MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE {
if (mMainReader) {
return mMainReader->VideoQueue();
} else {
return MediaDecoderReader::VideoQueue();
}
}
// Sets byte range for initialization (EBML); used by DASH.
void SetInitByteRange(MediaByteRange &aByteRange) {
void SetInitByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
mInitByteRange = aByteRange;
}
// Sets byte range for cue points, i.e. cluster offsets; used by DASH.
void SetIndexByteRange(MediaByteRange &aByteRange) {
void SetIndexByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
mCuesByteRange = aByteRange;
}
// Returns list of ranges for cluster start and end offsets.
nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges);
nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
MOZ_OVERRIDE;
private:
// Called by |DASHReader|::|PossiblySwitchVideoReaders| to check if this
// reader has reached a switch access point and it's ok to switch readers.
// Called on the decode thread.
bool HasReachedSubsegment(uint32_t aSubsegmentIndex) MOZ_OVERRIDE;
// Requests that this reader seek to the specified subsegment. Seek will
// happen when |PrepareDecodeVideoFrame| is called on the decode
// thread.
// Called on the main thread or decoder thread. Decode monitor must be held.
void RequestSeekToSubsegment(uint32_t aIdx) MOZ_OVERRIDE;
// Requests that this reader switch to |aNextReader| at the start of the
// specified subsegment. This is the reader to switch FROM.
// Called on the main thread or decoder thread. Decode monitor must be held.
void RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
MediaDecoderReader* aNextReader) MOZ_OVERRIDE;
// Seeks to the beginning of the specified cluster. Called on the decode
// thread.
void SeekToCluster(uint32_t aIdx);
#endif
protected:
// Value passed to NextPacket to determine if we are reading a video or an
// audio packet.
enum TrackType {
@ -160,8 +223,19 @@ private:
// Read a packet from the nestegg file. Returns NULL if all packets for
// the particular track have been read. Pass VIDEO or AUDIO to indicate the
// type of the packet we want to read.
#ifdef MOZ_DASH
nsReturnRef<NesteggPacketHolder> NextPacketInternal(TrackType aTrackType);
// Read a packet from the nestegg file. Returns NULL if all packets for
// the particular track have been read. Pass VIDEO or AUDIO to indicate the
// type of the packet we want to read. If the reader reaches a switch access
// point, this function will get a packet from |mNextReader|.
#endif
nsReturnRef<NesteggPacketHolder> NextPacket(TrackType aTrackType);
// Pushes a packet to the front of the video packet queue.
virtual void PushVideoPacket(NesteggPacketHolder* aItem);
// Returns an initialized ogg packet with data obtained from the WebM container.
ogg_packet InitOggPacket(unsigned char* aData,
size_t aLength,
@ -227,6 +301,7 @@ private:
bool mHasVideo;
bool mHasAudio;
#ifdef MOZ_DASH
// Byte range for initialisation data; e.g. specified in DASH manifest.
MediaByteRange mInitByteRange;
@ -235,6 +310,40 @@ private:
// Byte ranges for clusters; set internally, derived from cues.
nsTArray<MediaByteRange> mClusterByteRanges;
// Pointer to the main |DASHReader|. Set in the constructor.
DASHReader* mMainReader;
// Index of the cluster to switch to. Monitor must be entered for write
// access on all threads, read access off the decode thread.
int32_t mSwitchingCluster;
// Pointer to the next reader. Used in |NextPacket| and |PushVideoPacket| at
// switch access points. Monitor must be entered for write access on all
// threads, read access off the decode thread.
nsRefPtr<WebMReader> mNextReader;
// Index of the cluster to seek to for a DASH stream request. Monitor must be
// entered for write access on all threads, read access off the decode
// thread.
int32_t mSeekToCluster;
// Current end offset of the last packet read in |NextPacket|. Used to check
// if the reader reached the switch access point. Accessed on the decode
// thread only.
int64_t mCurrentOffset;
// Set in |NextPacket| if we read a packet from the next reader. If true in
// |PushVideoPacket|, we will push the packet onto the next reader's
// video packet queue (not video data queue!). Accessed on decode thread
// only.
bool mPushVideoPacketToNextReader;
// Indicates if the reader has reached a switch access point. Set in
// |NextPacket| and read in |HasReachedSubsegment|. Accessed on
// decode thread only.
bool mReachedSwitchAccessPoint;
#endif
};
} // namespace mozilla

View File

@ -96,7 +96,7 @@ AdaptationSet::AddRepresentation(Representation* aRep)
NS_ENSURE_TRUE(aRep,);
// Only add if it's not already in the array.
if (!mRepresentations.Contains(aRep)) {
mRepresentations.AppendElement(aRep);
mRepresentations.InsertElementSorted(aRep, CompareRepresentationBitrates());
}
}

View File

@ -90,6 +90,7 @@ public:
private:
// Array of media |Representations| to switch between.
// Ordered list, ascending in order of bitrates.
nsTArray<nsAutoPtr<Representation> > mRepresentations;
// @width, height and @mimetype of this media stream.

View File

@ -111,6 +111,13 @@ public:
// Returns the duration of the presentation in seconds.
virtual double GetDuration() const = 0;
// Gets index of the |Representation| with next highest bitrate to the
// estimated bandwidth passed in. Returns true if there is at least one
// |Representation| with a bitrate lower than |aBandwidth|; otherwise returns
// false. Depends on |mRepresentations| being an ordered list.
virtual bool GetBestRepForBandwidth(uint32_t aAdaptSetIdx,
uint64_t aBandwidth,
uint32_t &aRepIdx) const = 0;
public:
// Factory method.
static IMPDManager* Create(DASHMPDProfile Profile, nsIDOMElement* aRoot);

View File

@ -66,6 +66,10 @@ public:
MOZ_COUNT_DTOR(Representation);
}
bool operator<(const Representation &other) const {
return this->mBitrate < other.mBitrate;
}
// Gets/Sets @bitrate in kbps.
int64_t const GetBitrate() const;
void SetBitrate(int64_t const aBitrate);
@ -101,6 +105,31 @@ private:
nsAutoPtr<SegmentBase> mSegmentBase;
};
// Comparator allows comparing |Representation|s based on media stream bitrate.
class CompareRepresentationBitrates
{
public:
// Returns true if the elements are equals; false otherwise.
// Note: |Representation| is stored as an array of |nsAutoPtr| in
// |AdaptationSet|, but needs to be compared to regular pointers.
// Hence the left hand side of the function being an
// |nsAutoPtr| and the right being a regular pointer.
bool Equals(const nsAutoPtr<Representation>& a,
const Representation *b) const {
return a == b;
}
// Returns true if (a < b); false otherwise.
// Note: |Representation| is stored as an array of |nsAutoPtr| in
// |AdaptationSet|, but needs to be compared to regular pointers.
// Hence the left hand side of the function being an
// |nsAutoPtr| and the right being a regular pointer.
bool LessThan(const nsAutoPtr<Representation>& a,
const Representation *b) const {
return *a < *b;
}
};
}//namespace net
}//namespace mozilla

View File

@ -198,6 +198,39 @@ nsDASHWebMODManager::GetDuration() const
return current->GetDuration();
}
bool
nsDASHWebMODManager::GetBestRepForBandwidth(uint32_t aAdaptSetIdx,
uint64_t aBandwidth,
uint32_t &aRepIdx) const
{
NS_ENSURE_TRUE(aAdaptSetIdx < GetNumAdaptationSets(), false);
NS_ENSURE_TRUE(0 < GetNumRepresentations(aAdaptSetIdx), false);
// Return false if there isn't enough bandwidth for even the lowest bitrate.
// Let calling function decide what to do. Use 0.95 multiplier to deal with
// 5% variance in bandwidth.
// XXX Multiplier is a guess at present.
if (aBandwidth*0.95 < GetRepresentation(aAdaptSetIdx, 0)->GetBitrate()) {
aRepIdx = UINT32_MAX;
return false;
}
// Iterate until the current |Representation|'s bitrate is higher than the
// estimated available bandwidth. Use 0.95 multiplier to deal with 5%
// variance in bandwidth.
// XXX Multiplier is a guess at present.
for (uint32_t i = 1; i < GetNumRepresentations(aAdaptSetIdx); i++) {
NS_ENSURE_TRUE(GetRepresentation(aAdaptSetIdx, i), false);
if (aBandwidth*0.95 < GetRepresentation(aAdaptSetIdx, i)->GetBitrate()) {
// Pick the previous one, since this one's bitrate is too high.
aRepIdx = i-1;
return true;
}
}
// If we reach here, all of the |Representation|'s bitrates are lower than the
// available bandwidth. Just pick the highest, i.e. last in the array.
aRepIdx = GetNumRepresentations(aAdaptSetIdx)-1;
return true;
}
}//namespace net
}//namespace mozilla

View File

@ -80,6 +80,14 @@ public:
double GetStartTime() const;
double GetDuration() const;
// Gets index of the |Representation| with next highest bitrate to the
// estimated bandwidth passed in. Returns true if there is at least one
// |Representation| with a bitrate lower than |aBandwidth|; otherwise returns
// false. Depends on |mRepresentations| being an ordered list.
bool GetBestRepForBandwidth(uint32_t aAdaptSetIdx,
uint64_t aBandwidth,
uint32_t &aRepIdx) const MOZ_OVERRIDE;
private:
// Internal helper functions.
AdaptationSet const * GetAdaptationSet(uint32_t const aAdaptSetIdx) const;