diff -r 000000000000 -r 6474c204b198 content/media/MediaDecoderReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/media/MediaDecoderReader.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MediaDecoderReader.h" +#include "AbstractMediaDecoder.h" +#include "VideoUtils.h" +#include "ImageContainer.h" + +#include "mozilla/mozalloc.h" +#include +#include + +namespace mozilla { + +// Un-comment to enable logging of seek bisections. +//#define SEEK_LOGGING + +#ifdef PR_LOGGING +extern PRLogModuleInfo* gMediaDecoderLog; +#define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) +#ifdef SEEK_LOGGING +#define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) +#else +#define SEEK_LOG(type, msg) +#endif +#else +#define DECODER_LOG(type, msg) +#define SEEK_LOG(type, msg) +#endif + +class VideoQueueMemoryFunctor : public nsDequeFunctor { +public: + VideoQueueMemoryFunctor() : mSize(0) {} + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); + + virtual void* operator()(void* aObject) { + const VideoData* v = static_cast(aObject); + mSize += v->SizeOfIncludingThis(MallocSizeOf); + return nullptr; + } + + size_t mSize; +}; + + +class AudioQueueMemoryFunctor : public nsDequeFunctor { +public: + AudioQueueMemoryFunctor() : mSize(0) {} + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); + + virtual void* operator()(void* aObject) { + const AudioData* audioData = static_cast(aObject); + mSize += audioData->SizeOfIncludingThis(MallocSizeOf); + return nullptr; + } + + size_t mSize; +}; + +MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) + : mAudioCompactor(mAudioQueue), + mDecoder(aDecoder), + mIgnoreAudioOutputFormat(false) +{ + MOZ_COUNT_CTOR(MediaDecoderReader); +} + +MediaDecoderReader::~MediaDecoderReader() +{ + ResetDecode(); + MOZ_COUNT_DTOR(MediaDecoderReader); +} + +size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const +{ + VideoQueueMemoryFunctor functor; + mVideoQueue.LockedForEach(functor); + return functor.mSize; +} + +size_t MediaDecoderReader::SizeOfAudioQueueInBytes() const +{ + AudioQueueMemoryFunctor functor; + mAudioQueue.LockedForEach(functor); + return functor.mSize; +} + +nsresult MediaDecoderReader::ResetDecode() +{ + nsresult res = NS_OK; + + VideoQueue().Reset(); + AudioQueue().Reset(); + + return res; +} + +VideoData* MediaDecoderReader::DecodeToFirstVideoData() +{ + bool eof = false; + while (!eof && VideoQueue().GetSize() == 0) { + { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + if (mDecoder->IsShutdown()) { + return nullptr; + } + } + bool keyframeSkip = false; + eof = !DecodeVideoFrame(keyframeSkip, 0); + } + if (eof) { + VideoQueue().Finish(); + } + VideoData* d = nullptr; + return (d = VideoQueue().PeekFront()) ? d : nullptr; +} + +AudioData* MediaDecoderReader::DecodeToFirstAudioData() +{ + bool eof = false; + while (!eof && AudioQueue().GetSize() == 0) { + { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + if (mDecoder->IsShutdown()) { + return nullptr; + } + } + eof = !DecodeAudioData(); + } + if (eof) { + AudioQueue().Finish(); + } + AudioData* d = nullptr; + return (d = AudioQueue().PeekFront()) ? d : nullptr; +} + +VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) +{ + NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), + "Should be on state machine or decode thread."); + + // Extract the start times of the bitstreams in order to calculate + // the duration. + int64_t videoStartTime = INT64_MAX; + int64_t audioStartTime = INT64_MAX; + VideoData* videoData = nullptr; + + if (HasVideo()) { + videoData = DecodeToFirstVideoData(); + if (videoData) { + videoStartTime = videoData->mTime; + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() video=%lld", videoStartTime)); + } + } + if (HasAudio()) { + AudioData* audioData = DecodeToFirstAudioData(); + if (audioData) { + audioStartTime = audioData->mTime; + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() audio=%lld", audioStartTime)); + } + } + + int64_t startTime = std::min(videoStartTime, audioStartTime); + if (startTime != INT64_MAX) { + aOutStartTime = startTime; + } + + return videoData; +} + +nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) +{ + DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); + + // Decode forward to the target frame. Start with video, if we have it. + if (HasVideo()) { + // Note: when decoding hits the end of stream we must keep the last frame + // in the video queue so that we'll have something to display after the + // seek completes. This makes our logic a bit messy. + bool eof = false; + nsAutoPtr video; + while (HasVideo() && !eof) { + while (VideoQueue().GetSize() == 0 && !eof) { + bool skip = false; + eof = !DecodeVideoFrame(skip, 0); + { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + if (mDecoder->IsShutdown()) { + return NS_ERROR_FAILURE; + } + } + } + if (eof) { + // Hit end of file, we want to display the last frame of the video. + if (video) { + DECODER_LOG(PR_LOG_DEBUG, + ("MediaDecoderReader::DecodeToTarget(%lld) repushing video frame [%lld, %lld] at EOF", + aTarget, video->mTime, video->GetEndTime())); + VideoQueue().PushFront(video.forget()); + } + VideoQueue().Finish(); + break; + } + 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->GetEndTime() <= aTarget) { + DECODER_LOG(PR_LOG_DEBUG, + ("MediaDecoderReader::DecodeToTarget(%lld) pop video frame [%lld, %lld]", + aTarget, video->mTime, video->GetEndTime())); + VideoQueue().PopFront(); + } else { + // Found a frame after or encompasing the seek target. + if (aTarget >= video->mTime && video->GetEndTime() >= aTarget) { + // The seek target lies inside this frame's time slice. Adjust the frame's + // start time to match the seek target. We do this by replacing the + // first frame with a shallow copy which has the new timestamp. + VideoQueue().PopFront(); + VideoData* temp = VideoData::ShallowCopyUpdateTimestamp(video, aTarget); + video = temp; + VideoQueue().PushFront(video); + } + DECODER_LOG(PR_LOG_DEBUG, + ("MediaDecoderReader::DecodeToTarget(%lld) found target video frame [%lld,%lld]", + aTarget, video->mTime, video->GetEndTime())); + + video.forget(); + break; + } + } + { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + if (mDecoder->IsShutdown()) { + return NS_ERROR_FAILURE; + } + } +#ifdef PR_LOGGING + const VideoData* front = VideoQueue().PeekFront(); + DECODER_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", + front ? front->mTime : -1)); +#endif + } + + if (HasAudio()) { + // Decode audio forward to the seek target. + bool eof = false; + while (HasAudio() && !eof) { + while (!eof && AudioQueue().GetSize() == 0) { + eof = !DecodeAudioData(); + { + ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); + if (mDecoder->IsShutdown()) { + return NS_ERROR_FAILURE; + } + } + } + const AudioData* audio = AudioQueue().PeekFront(); + if (!audio || eof) { + AudioQueue().Finish(); + break; + } + CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudio.mRate); + CheckedInt64 targetFrame = UsecsToFrames(aTarget, mInfo.mAudio.mRate); + if (!startFrame.isValid() || !targetFrame.isValid()) { + return NS_ERROR_FAILURE; + } + 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 AudioQueue().PopFront(); + audio = nullptr; + continue; + } + if (startFrame.value() > targetFrame.value()) { + // The seek target doesn't lie in the audio block just after the last + // audio frames we've seen which were before the seek target. This + // could have been the first audio data we've seen after seek, i.e. the + // seek terminated after the seek target in the audio stream. Just + // abort the audio decode-to-target, the state machine will play + // silence to cover the gap. Typically this happens in poorly muxed + // files. + NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?"); + break; + } + + // The seek target lies somewhere in this AudioData's frames, strip off + // any frames which lie before the seek target, so we'll begin playback + // exactly at the seek target. + NS_ASSERTION(targetFrame.value() >= startFrame.value(), + "Target must at or be after data start."); + NS_ASSERTION(targetFrame.value() < startFrame.value() + audio->mFrames, + "Data must end after target."); + + int64_t framesToPrune = targetFrame.value() - startFrame.value(); + if (framesToPrune > audio->mFrames) { + // We've messed up somehow. Don't try to trim frames, the |frames| + // variable below will overflow. + NS_WARNING("Can't prune more frames that we have!"); + break; + } + uint32_t frames = audio->mFrames - static_cast(framesToPrune); + uint32_t channels = audio->mChannels; + nsAutoArrayPtr audioData(new AudioDataValue[frames * channels]); + memcpy(audioData.get(), + audio->mAudioData.get() + (framesToPrune * channels), + frames * channels * sizeof(AudioDataValue)); + CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate); + if (!duration.isValid()) { + return NS_ERROR_FAILURE; + } + nsAutoPtr data(new AudioData(audio->mOffset, + aTarget, + duration.value(), + frames, + audioData.forget(), + channels)); + delete AudioQueue().PopFront(); + AudioQueue().PushFront(data.forget()); + break; + } + } + +#ifdef PR_LOGGING + const VideoData* v = VideoQueue().PeekFront(); + const AudioData* a = AudioQueue().PeekFront(); + DECODER_LOG(PR_LOG_DEBUG, + ("MediaDecoderReader::DecodeToTarget(%lld) finished v=%lld a=%lld", + aTarget, v ? v->mTime : -1, a ? a->mTime : -1)); +#endif + + return NS_OK; +} + +nsresult +MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered, + int64_t aStartTime) +{ + MediaResource* stream = mDecoder->GetResource(); + int64_t durationUs = 0; + { + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + durationUs = mDecoder->GetMediaDuration(); + } + GetEstimatedBufferedTimeRanges(stream, durationUs, aBuffered); + return NS_OK; +} + +} // namespace mozilla