mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-26 23:23:33 +00:00
Bug 792404 - Add code to enable adaptive decoder and reader switching for DASH-WebM r=cpearce
This commit is contained in:
parent
272d7ed20d
commit
aa6a61c8ef
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
62
content/media/dash/DASHRepReader.h
Normal file
62
content/media/dash/DASHRepReader.h
Normal 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*/
|
@ -23,6 +23,7 @@ EXPORTS := \
|
||||
DASHDecoder.h \
|
||||
DASHRepDecoder.h \
|
||||
DASHReader.h \
|
||||
DASHRepReader.h \
|
||||
$(NULL)
|
||||
|
||||
CPPSRCS := \
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user