Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include "MediaDecoderReader.h" |
michael@0 | 8 | #include "AbstractMediaDecoder.h" |
michael@0 | 9 | #include "VideoUtils.h" |
michael@0 | 10 | #include "ImageContainer.h" |
michael@0 | 11 | |
michael@0 | 12 | #include "mozilla/mozalloc.h" |
michael@0 | 13 | #include <stdint.h> |
michael@0 | 14 | #include <algorithm> |
michael@0 | 15 | |
michael@0 | 16 | namespace mozilla { |
michael@0 | 17 | |
michael@0 | 18 | // Un-comment to enable logging of seek bisections. |
michael@0 | 19 | //#define SEEK_LOGGING |
michael@0 | 20 | |
michael@0 | 21 | #ifdef PR_LOGGING |
michael@0 | 22 | extern PRLogModuleInfo* gMediaDecoderLog; |
michael@0 | 23 | #define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) |
michael@0 | 24 | #ifdef SEEK_LOGGING |
michael@0 | 25 | #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) |
michael@0 | 26 | #else |
michael@0 | 27 | #define SEEK_LOG(type, msg) |
michael@0 | 28 | #endif |
michael@0 | 29 | #else |
michael@0 | 30 | #define DECODER_LOG(type, msg) |
michael@0 | 31 | #define SEEK_LOG(type, msg) |
michael@0 | 32 | #endif |
michael@0 | 33 | |
michael@0 | 34 | class VideoQueueMemoryFunctor : public nsDequeFunctor { |
michael@0 | 35 | public: |
michael@0 | 36 | VideoQueueMemoryFunctor() : mSize(0) {} |
michael@0 | 37 | |
michael@0 | 38 | MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); |
michael@0 | 39 | |
michael@0 | 40 | virtual void* operator()(void* aObject) { |
michael@0 | 41 | const VideoData* v = static_cast<const VideoData*>(aObject); |
michael@0 | 42 | mSize += v->SizeOfIncludingThis(MallocSizeOf); |
michael@0 | 43 | return nullptr; |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | size_t mSize; |
michael@0 | 47 | }; |
michael@0 | 48 | |
michael@0 | 49 | |
michael@0 | 50 | class AudioQueueMemoryFunctor : public nsDequeFunctor { |
michael@0 | 51 | public: |
michael@0 | 52 | AudioQueueMemoryFunctor() : mSize(0) {} |
michael@0 | 53 | |
michael@0 | 54 | MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); |
michael@0 | 55 | |
michael@0 | 56 | virtual void* operator()(void* aObject) { |
michael@0 | 57 | const AudioData* audioData = static_cast<const AudioData*>(aObject); |
michael@0 | 58 | mSize += audioData->SizeOfIncludingThis(MallocSizeOf); |
michael@0 | 59 | return nullptr; |
michael@0 | 60 | } |
michael@0 | 61 | |
michael@0 | 62 | size_t mSize; |
michael@0 | 63 | }; |
michael@0 | 64 | |
michael@0 | 65 | MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) |
michael@0 | 66 | : mAudioCompactor(mAudioQueue), |
michael@0 | 67 | mDecoder(aDecoder), |
michael@0 | 68 | mIgnoreAudioOutputFormat(false) |
michael@0 | 69 | { |
michael@0 | 70 | MOZ_COUNT_CTOR(MediaDecoderReader); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | MediaDecoderReader::~MediaDecoderReader() |
michael@0 | 74 | { |
michael@0 | 75 | ResetDecode(); |
michael@0 | 76 | MOZ_COUNT_DTOR(MediaDecoderReader); |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const |
michael@0 | 80 | { |
michael@0 | 81 | VideoQueueMemoryFunctor functor; |
michael@0 | 82 | mVideoQueue.LockedForEach(functor); |
michael@0 | 83 | return functor.mSize; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | size_t MediaDecoderReader::SizeOfAudioQueueInBytes() const |
michael@0 | 87 | { |
michael@0 | 88 | AudioQueueMemoryFunctor functor; |
michael@0 | 89 | mAudioQueue.LockedForEach(functor); |
michael@0 | 90 | return functor.mSize; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | nsresult MediaDecoderReader::ResetDecode() |
michael@0 | 94 | { |
michael@0 | 95 | nsresult res = NS_OK; |
michael@0 | 96 | |
michael@0 | 97 | VideoQueue().Reset(); |
michael@0 | 98 | AudioQueue().Reset(); |
michael@0 | 99 | |
michael@0 | 100 | return res; |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | VideoData* MediaDecoderReader::DecodeToFirstVideoData() |
michael@0 | 104 | { |
michael@0 | 105 | bool eof = false; |
michael@0 | 106 | while (!eof && VideoQueue().GetSize() == 0) { |
michael@0 | 107 | { |
michael@0 | 108 | ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 109 | if (mDecoder->IsShutdown()) { |
michael@0 | 110 | return nullptr; |
michael@0 | 111 | } |
michael@0 | 112 | } |
michael@0 | 113 | bool keyframeSkip = false; |
michael@0 | 114 | eof = !DecodeVideoFrame(keyframeSkip, 0); |
michael@0 | 115 | } |
michael@0 | 116 | if (eof) { |
michael@0 | 117 | VideoQueue().Finish(); |
michael@0 | 118 | } |
michael@0 | 119 | VideoData* d = nullptr; |
michael@0 | 120 | return (d = VideoQueue().PeekFront()) ? d : nullptr; |
michael@0 | 121 | } |
michael@0 | 122 | |
michael@0 | 123 | AudioData* MediaDecoderReader::DecodeToFirstAudioData() |
michael@0 | 124 | { |
michael@0 | 125 | bool eof = false; |
michael@0 | 126 | while (!eof && AudioQueue().GetSize() == 0) { |
michael@0 | 127 | { |
michael@0 | 128 | ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 129 | if (mDecoder->IsShutdown()) { |
michael@0 | 130 | return nullptr; |
michael@0 | 131 | } |
michael@0 | 132 | } |
michael@0 | 133 | eof = !DecodeAudioData(); |
michael@0 | 134 | } |
michael@0 | 135 | if (eof) { |
michael@0 | 136 | AudioQueue().Finish(); |
michael@0 | 137 | } |
michael@0 | 138 | AudioData* d = nullptr; |
michael@0 | 139 | return (d = AudioQueue().PeekFront()) ? d : nullptr; |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) |
michael@0 | 143 | { |
michael@0 | 144 | NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), |
michael@0 | 145 | "Should be on state machine or decode thread."); |
michael@0 | 146 | |
michael@0 | 147 | // Extract the start times of the bitstreams in order to calculate |
michael@0 | 148 | // the duration. |
michael@0 | 149 | int64_t videoStartTime = INT64_MAX; |
michael@0 | 150 | int64_t audioStartTime = INT64_MAX; |
michael@0 | 151 | VideoData* videoData = nullptr; |
michael@0 | 152 | |
michael@0 | 153 | if (HasVideo()) { |
michael@0 | 154 | videoData = DecodeToFirstVideoData(); |
michael@0 | 155 | if (videoData) { |
michael@0 | 156 | videoStartTime = videoData->mTime; |
michael@0 | 157 | DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() video=%lld", videoStartTime)); |
michael@0 | 158 | } |
michael@0 | 159 | } |
michael@0 | 160 | if (HasAudio()) { |
michael@0 | 161 | AudioData* audioData = DecodeToFirstAudioData(); |
michael@0 | 162 | if (audioData) { |
michael@0 | 163 | audioStartTime = audioData->mTime; |
michael@0 | 164 | DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::FindStartTime() audio=%lld", audioStartTime)); |
michael@0 | 165 | } |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | int64_t startTime = std::min(videoStartTime, audioStartTime); |
michael@0 | 169 | if (startTime != INT64_MAX) { |
michael@0 | 170 | aOutStartTime = startTime; |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | return videoData; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) |
michael@0 | 177 | { |
michael@0 | 178 | DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); |
michael@0 | 179 | |
michael@0 | 180 | // Decode forward to the target frame. Start with video, if we have it. |
michael@0 | 181 | if (HasVideo()) { |
michael@0 | 182 | // Note: when decoding hits the end of stream we must keep the last frame |
michael@0 | 183 | // in the video queue so that we'll have something to display after the |
michael@0 | 184 | // seek completes. This makes our logic a bit messy. |
michael@0 | 185 | bool eof = false; |
michael@0 | 186 | nsAutoPtr<VideoData> video; |
michael@0 | 187 | while (HasVideo() && !eof) { |
michael@0 | 188 | while (VideoQueue().GetSize() == 0 && !eof) { |
michael@0 | 189 | bool skip = false; |
michael@0 | 190 | eof = !DecodeVideoFrame(skip, 0); |
michael@0 | 191 | { |
michael@0 | 192 | ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 193 | if (mDecoder->IsShutdown()) { |
michael@0 | 194 | return NS_ERROR_FAILURE; |
michael@0 | 195 | } |
michael@0 | 196 | } |
michael@0 | 197 | } |
michael@0 | 198 | if (eof) { |
michael@0 | 199 | // Hit end of file, we want to display the last frame of the video. |
michael@0 | 200 | if (video) { |
michael@0 | 201 | DECODER_LOG(PR_LOG_DEBUG, |
michael@0 | 202 | ("MediaDecoderReader::DecodeToTarget(%lld) repushing video frame [%lld, %lld] at EOF", |
michael@0 | 203 | aTarget, video->mTime, video->GetEndTime())); |
michael@0 | 204 | VideoQueue().PushFront(video.forget()); |
michael@0 | 205 | } |
michael@0 | 206 | VideoQueue().Finish(); |
michael@0 | 207 | break; |
michael@0 | 208 | } |
michael@0 | 209 | video = VideoQueue().PeekFront(); |
michael@0 | 210 | // If the frame end time is less than the seek target, we won't want |
michael@0 | 211 | // to display this frame after the seek, so discard it. |
michael@0 | 212 | if (video && video->GetEndTime() <= aTarget) { |
michael@0 | 213 | DECODER_LOG(PR_LOG_DEBUG, |
michael@0 | 214 | ("MediaDecoderReader::DecodeToTarget(%lld) pop video frame [%lld, %lld]", |
michael@0 | 215 | aTarget, video->mTime, video->GetEndTime())); |
michael@0 | 216 | VideoQueue().PopFront(); |
michael@0 | 217 | } else { |
michael@0 | 218 | // Found a frame after or encompasing the seek target. |
michael@0 | 219 | if (aTarget >= video->mTime && video->GetEndTime() >= aTarget) { |
michael@0 | 220 | // The seek target lies inside this frame's time slice. Adjust the frame's |
michael@0 | 221 | // start time to match the seek target. We do this by replacing the |
michael@0 | 222 | // first frame with a shallow copy which has the new timestamp. |
michael@0 | 223 | VideoQueue().PopFront(); |
michael@0 | 224 | VideoData* temp = VideoData::ShallowCopyUpdateTimestamp(video, aTarget); |
michael@0 | 225 | video = temp; |
michael@0 | 226 | VideoQueue().PushFront(video); |
michael@0 | 227 | } |
michael@0 | 228 | DECODER_LOG(PR_LOG_DEBUG, |
michael@0 | 229 | ("MediaDecoderReader::DecodeToTarget(%lld) found target video frame [%lld,%lld]", |
michael@0 | 230 | aTarget, video->mTime, video->GetEndTime())); |
michael@0 | 231 | |
michael@0 | 232 | video.forget(); |
michael@0 | 233 | break; |
michael@0 | 234 | } |
michael@0 | 235 | } |
michael@0 | 236 | { |
michael@0 | 237 | ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 238 | if (mDecoder->IsShutdown()) { |
michael@0 | 239 | return NS_ERROR_FAILURE; |
michael@0 | 240 | } |
michael@0 | 241 | } |
michael@0 | 242 | #ifdef PR_LOGGING |
michael@0 | 243 | const VideoData* front = VideoQueue().PeekFront(); |
michael@0 | 244 | DECODER_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", |
michael@0 | 245 | front ? front->mTime : -1)); |
michael@0 | 246 | #endif |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | if (HasAudio()) { |
michael@0 | 250 | // Decode audio forward to the seek target. |
michael@0 | 251 | bool eof = false; |
michael@0 | 252 | while (HasAudio() && !eof) { |
michael@0 | 253 | while (!eof && AudioQueue().GetSize() == 0) { |
michael@0 | 254 | eof = !DecodeAudioData(); |
michael@0 | 255 | { |
michael@0 | 256 | ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 257 | if (mDecoder->IsShutdown()) { |
michael@0 | 258 | return NS_ERROR_FAILURE; |
michael@0 | 259 | } |
michael@0 | 260 | } |
michael@0 | 261 | } |
michael@0 | 262 | const AudioData* audio = AudioQueue().PeekFront(); |
michael@0 | 263 | if (!audio || eof) { |
michael@0 | 264 | AudioQueue().Finish(); |
michael@0 | 265 | break; |
michael@0 | 266 | } |
michael@0 | 267 | CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudio.mRate); |
michael@0 | 268 | CheckedInt64 targetFrame = UsecsToFrames(aTarget, mInfo.mAudio.mRate); |
michael@0 | 269 | if (!startFrame.isValid() || !targetFrame.isValid()) { |
michael@0 | 270 | return NS_ERROR_FAILURE; |
michael@0 | 271 | } |
michael@0 | 272 | if (startFrame.value() + audio->mFrames <= targetFrame.value()) { |
michael@0 | 273 | // Our seek target lies after the frames in this AudioData. Pop it |
michael@0 | 274 | // off the queue, and keep decoding forwards. |
michael@0 | 275 | delete AudioQueue().PopFront(); |
michael@0 | 276 | audio = nullptr; |
michael@0 | 277 | continue; |
michael@0 | 278 | } |
michael@0 | 279 | if (startFrame.value() > targetFrame.value()) { |
michael@0 | 280 | // The seek target doesn't lie in the audio block just after the last |
michael@0 | 281 | // audio frames we've seen which were before the seek target. This |
michael@0 | 282 | // could have been the first audio data we've seen after seek, i.e. the |
michael@0 | 283 | // seek terminated after the seek target in the audio stream. Just |
michael@0 | 284 | // abort the audio decode-to-target, the state machine will play |
michael@0 | 285 | // silence to cover the gap. Typically this happens in poorly muxed |
michael@0 | 286 | // files. |
michael@0 | 287 | NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?"); |
michael@0 | 288 | break; |
michael@0 | 289 | } |
michael@0 | 290 | |
michael@0 | 291 | // The seek target lies somewhere in this AudioData's frames, strip off |
michael@0 | 292 | // any frames which lie before the seek target, so we'll begin playback |
michael@0 | 293 | // exactly at the seek target. |
michael@0 | 294 | NS_ASSERTION(targetFrame.value() >= startFrame.value(), |
michael@0 | 295 | "Target must at or be after data start."); |
michael@0 | 296 | NS_ASSERTION(targetFrame.value() < startFrame.value() + audio->mFrames, |
michael@0 | 297 | "Data must end after target."); |
michael@0 | 298 | |
michael@0 | 299 | int64_t framesToPrune = targetFrame.value() - startFrame.value(); |
michael@0 | 300 | if (framesToPrune > audio->mFrames) { |
michael@0 | 301 | // We've messed up somehow. Don't try to trim frames, the |frames| |
michael@0 | 302 | // variable below will overflow. |
michael@0 | 303 | NS_WARNING("Can't prune more frames that we have!"); |
michael@0 | 304 | break; |
michael@0 | 305 | } |
michael@0 | 306 | uint32_t frames = audio->mFrames - static_cast<uint32_t>(framesToPrune); |
michael@0 | 307 | uint32_t channels = audio->mChannels; |
michael@0 | 308 | nsAutoArrayPtr<AudioDataValue> audioData(new AudioDataValue[frames * channels]); |
michael@0 | 309 | memcpy(audioData.get(), |
michael@0 | 310 | audio->mAudioData.get() + (framesToPrune * channels), |
michael@0 | 311 | frames * channels * sizeof(AudioDataValue)); |
michael@0 | 312 | CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate); |
michael@0 | 313 | if (!duration.isValid()) { |
michael@0 | 314 | return NS_ERROR_FAILURE; |
michael@0 | 315 | } |
michael@0 | 316 | nsAutoPtr<AudioData> data(new AudioData(audio->mOffset, |
michael@0 | 317 | aTarget, |
michael@0 | 318 | duration.value(), |
michael@0 | 319 | frames, |
michael@0 | 320 | audioData.forget(), |
michael@0 | 321 | channels)); |
michael@0 | 322 | delete AudioQueue().PopFront(); |
michael@0 | 323 | AudioQueue().PushFront(data.forget()); |
michael@0 | 324 | break; |
michael@0 | 325 | } |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | #ifdef PR_LOGGING |
michael@0 | 329 | const VideoData* v = VideoQueue().PeekFront(); |
michael@0 | 330 | const AudioData* a = AudioQueue().PeekFront(); |
michael@0 | 331 | DECODER_LOG(PR_LOG_DEBUG, |
michael@0 | 332 | ("MediaDecoderReader::DecodeToTarget(%lld) finished v=%lld a=%lld", |
michael@0 | 333 | aTarget, v ? v->mTime : -1, a ? a->mTime : -1)); |
michael@0 | 334 | #endif |
michael@0 | 335 | |
michael@0 | 336 | return NS_OK; |
michael@0 | 337 | } |
michael@0 | 338 | |
michael@0 | 339 | nsresult |
michael@0 | 340 | MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered, |
michael@0 | 341 | int64_t aStartTime) |
michael@0 | 342 | { |
michael@0 | 343 | MediaResource* stream = mDecoder->GetResource(); |
michael@0 | 344 | int64_t durationUs = 0; |
michael@0 | 345 | { |
michael@0 | 346 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 347 | durationUs = mDecoder->GetMediaDuration(); |
michael@0 | 348 | } |
michael@0 | 349 | GetEstimatedBufferedTimeRanges(stream, durationUs, aBuffered); |
michael@0 | 350 | return NS_OK; |
michael@0 | 351 | } |
michael@0 | 352 | |
michael@0 | 353 | } // namespace mozilla |