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