|
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/. */ |
|
6 |
|
7 #include "MediaDecoderReader.h" |
|
8 #include "AbstractMediaDecoder.h" |
|
9 #include "VideoUtils.h" |
|
10 #include "ImageContainer.h" |
|
11 |
|
12 #include "mozilla/mozalloc.h" |
|
13 #include <stdint.h> |
|
14 #include <algorithm> |
|
15 |
|
16 namespace mozilla { |
|
17 |
|
18 // Un-comment to enable logging of seek bisections. |
|
19 //#define SEEK_LOGGING |
|
20 |
|
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 |
|
33 |
|
34 class VideoQueueMemoryFunctor : public nsDequeFunctor { |
|
35 public: |
|
36 VideoQueueMemoryFunctor() : mSize(0) {} |
|
37 |
|
38 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); |
|
39 |
|
40 virtual void* operator()(void* aObject) { |
|
41 const VideoData* v = static_cast<const VideoData*>(aObject); |
|
42 mSize += v->SizeOfIncludingThis(MallocSizeOf); |
|
43 return nullptr; |
|
44 } |
|
45 |
|
46 size_t mSize; |
|
47 }; |
|
48 |
|
49 |
|
50 class AudioQueueMemoryFunctor : public nsDequeFunctor { |
|
51 public: |
|
52 AudioQueueMemoryFunctor() : mSize(0) {} |
|
53 |
|
54 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); |
|
55 |
|
56 virtual void* operator()(void* aObject) { |
|
57 const AudioData* audioData = static_cast<const AudioData*>(aObject); |
|
58 mSize += audioData->SizeOfIncludingThis(MallocSizeOf); |
|
59 return nullptr; |
|
60 } |
|
61 |
|
62 size_t mSize; |
|
63 }; |
|
64 |
|
65 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) |
|
66 : mAudioCompactor(mAudioQueue), |
|
67 mDecoder(aDecoder), |
|
68 mIgnoreAudioOutputFormat(false) |
|
69 { |
|
70 MOZ_COUNT_CTOR(MediaDecoderReader); |
|
71 } |
|
72 |
|
73 MediaDecoderReader::~MediaDecoderReader() |
|
74 { |
|
75 ResetDecode(); |
|
76 MOZ_COUNT_DTOR(MediaDecoderReader); |
|
77 } |
|
78 |
|
79 size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const |
|
80 { |
|
81 VideoQueueMemoryFunctor functor; |
|
82 mVideoQueue.LockedForEach(functor); |
|
83 return functor.mSize; |
|
84 } |
|
85 |
|
86 size_t MediaDecoderReader::SizeOfAudioQueueInBytes() const |
|
87 { |
|
88 AudioQueueMemoryFunctor functor; |
|
89 mAudioQueue.LockedForEach(functor); |
|
90 return functor.mSize; |
|
91 } |
|
92 |
|
93 nsresult MediaDecoderReader::ResetDecode() |
|
94 { |
|
95 nsresult res = NS_OK; |
|
96 |
|
97 VideoQueue().Reset(); |
|
98 AudioQueue().Reset(); |
|
99 |
|
100 return res; |
|
101 } |
|
102 |
|
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 } |
|
122 |
|
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 } |
|
141 |
|
142 VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) |
|
143 { |
|
144 NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), |
|
145 "Should be on state machine or decode thread."); |
|
146 |
|
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; |
|
152 |
|
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 } |
|
167 |
|
168 int64_t startTime = std::min(videoStartTime, audioStartTime); |
|
169 if (startTime != INT64_MAX) { |
|
170 aOutStartTime = startTime; |
|
171 } |
|
172 |
|
173 return videoData; |
|
174 } |
|
175 |
|
176 nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) |
|
177 { |
|
178 DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); |
|
179 |
|
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())); |
|
231 |
|
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 } |
|
248 |
|
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 } |
|
290 |
|
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."); |
|
298 |
|
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 } |
|
327 |
|
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 |
|
335 |
|
336 return NS_OK; |
|
337 } |
|
338 |
|
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 } |
|
352 |
|
353 } // namespace mozilla |