|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
|
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 "MP4Reader.h" |
|
8 #include "MediaResource.h" |
|
9 #include "mp4_demuxer/mp4_demuxer.h" |
|
10 #include "mp4_demuxer/Streams.h" |
|
11 #include "nsSize.h" |
|
12 #include "VideoUtils.h" |
|
13 #include "mozilla/dom/HTMLMediaElement.h" |
|
14 #include "ImageContainer.h" |
|
15 #include "Layers.h" |
|
16 #include "SharedThreadPool.h" |
|
17 #include "mozilla/Preferences.h" |
|
18 |
|
19 using mozilla::layers::Image; |
|
20 using mozilla::layers::LayerManager; |
|
21 using mozilla::layers::LayersBackend; |
|
22 |
|
23 #ifdef PR_LOGGING |
|
24 PRLogModuleInfo* GetDemuxerLog() { |
|
25 static PRLogModuleInfo* log = nullptr; |
|
26 if (!log) { |
|
27 log = PR_NewLogModule("MP4Demuxer"); |
|
28 } |
|
29 return log; |
|
30 } |
|
31 #define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) |
|
32 #else |
|
33 #define LOG(...) |
|
34 #endif |
|
35 |
|
36 using namespace mp4_demuxer; |
|
37 |
|
38 namespace mozilla { |
|
39 |
|
40 // Uncomment to enable verbose per-sample logging. |
|
41 //#define LOG_SAMPLE_DECODE 1 |
|
42 |
|
43 class MP4Stream : public mp4_demuxer::Stream { |
|
44 public: |
|
45 |
|
46 MP4Stream(MediaResource* aResource) |
|
47 : mResource(aResource) |
|
48 { |
|
49 MOZ_COUNT_CTOR(MP4Stream); |
|
50 MOZ_ASSERT(aResource); |
|
51 } |
|
52 virtual ~MP4Stream() { |
|
53 MOZ_COUNT_DTOR(MP4Stream); |
|
54 } |
|
55 |
|
56 virtual bool ReadAt(int64_t aOffset, |
|
57 uint8_t* aBuffer, |
|
58 uint32_t aCount, |
|
59 uint32_t* aBytesRead) MOZ_OVERRIDE { |
|
60 uint32_t sum = 0; |
|
61 do { |
|
62 uint32_t offset = aOffset + sum; |
|
63 char* buffer = reinterpret_cast<char*>(aBuffer + sum); |
|
64 uint32_t toRead = aCount - sum; |
|
65 uint32_t bytesRead = 0; |
|
66 nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead); |
|
67 if (NS_FAILED(rv)) { |
|
68 return false; |
|
69 } |
|
70 sum += bytesRead; |
|
71 } while (sum < aCount); |
|
72 *aBytesRead = sum; |
|
73 return true; |
|
74 } |
|
75 |
|
76 virtual int64_t Length() const MOZ_OVERRIDE { |
|
77 return mResource->GetLength(); |
|
78 } |
|
79 |
|
80 private: |
|
81 RefPtr<MediaResource> mResource; |
|
82 }; |
|
83 |
|
84 MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder) |
|
85 : MediaDecoderReader(aDecoder) |
|
86 , mAudio("MP4 audio decoder data", Preferences::GetUint("media.mp4-audio-decode-ahead", 2)) |
|
87 , mVideo("MP4 video decoder data", Preferences::GetUint("media.mp4-video-decode-ahead", 2)) |
|
88 , mLastReportedNumDecodedFrames(0) |
|
89 , mLayersBackendType(layers::LayersBackend::LAYERS_NONE) |
|
90 { |
|
91 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); |
|
92 MOZ_COUNT_CTOR(MP4Reader); |
|
93 } |
|
94 |
|
95 MP4Reader::~MP4Reader() |
|
96 { |
|
97 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); |
|
98 MOZ_COUNT_DTOR(MP4Reader); |
|
99 Shutdown(); |
|
100 } |
|
101 |
|
102 void |
|
103 MP4Reader::Shutdown() |
|
104 { |
|
105 if (mAudio.mDecoder) { |
|
106 Flush(kAudio); |
|
107 mAudio.mDecoder->Shutdown(); |
|
108 mAudio.mDecoder = nullptr; |
|
109 } |
|
110 if (mAudio.mTaskQueue) { |
|
111 mAudio.mTaskQueue->Shutdown(); |
|
112 mAudio.mTaskQueue = nullptr; |
|
113 } |
|
114 if (mVideo.mDecoder) { |
|
115 Flush(kVideo); |
|
116 mVideo.mDecoder->Shutdown(); |
|
117 mVideo.mDecoder = nullptr; |
|
118 } |
|
119 if (mVideo.mTaskQueue) { |
|
120 mVideo.mTaskQueue->Shutdown(); |
|
121 mVideo.mTaskQueue = nullptr; |
|
122 } |
|
123 } |
|
124 |
|
125 void |
|
126 MP4Reader::InitLayersBackendType() |
|
127 { |
|
128 if (!IsVideoContentType(mDecoder->GetResource()->GetContentType())) { |
|
129 // Not playing video, we don't care about the layers backend type. |
|
130 return; |
|
131 } |
|
132 // Extract the layer manager backend type so that platform decoders |
|
133 // can determine whether it's worthwhile using hardware accelerated |
|
134 // video decoding. |
|
135 MediaDecoderOwner* owner = mDecoder->GetOwner(); |
|
136 if (!owner) { |
|
137 NS_WARNING("MP4Reader without a decoder owner, can't get HWAccel"); |
|
138 return; |
|
139 } |
|
140 |
|
141 dom::HTMLMediaElement* element = owner->GetMediaElement(); |
|
142 NS_ENSURE_TRUE_VOID(element); |
|
143 |
|
144 nsRefPtr<LayerManager> layerManager = |
|
145 nsContentUtils::LayerManagerForDocument(element->OwnerDoc()); |
|
146 NS_ENSURE_TRUE_VOID(layerManager); |
|
147 |
|
148 mLayersBackendType = layerManager->GetCompositorBackendType(); |
|
149 } |
|
150 |
|
151 nsresult |
|
152 MP4Reader::Init(MediaDecoderReader* aCloneDonor) |
|
153 { |
|
154 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); |
|
155 PlatformDecoderModule::Init(); |
|
156 mMP4Stream = new MP4Stream(mDecoder->GetResource()); |
|
157 mDemuxer = new MP4Demuxer(mMP4Stream); |
|
158 |
|
159 InitLayersBackendType(); |
|
160 |
|
161 mAudio.mTaskQueue = new MediaTaskQueue( |
|
162 SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Audio Decode"))); |
|
163 NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE); |
|
164 |
|
165 mVideo.mTaskQueue = new MediaTaskQueue( |
|
166 SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Video Decode"))); |
|
167 NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE); |
|
168 |
|
169 return NS_OK; |
|
170 } |
|
171 |
|
172 nsresult |
|
173 MP4Reader::ReadMetadata(MediaInfo* aInfo, |
|
174 MetadataTags** aTags) |
|
175 { |
|
176 bool ok = mDemuxer->Init(); |
|
177 NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); |
|
178 |
|
179 const AudioDecoderConfig& audio = mDemuxer->AudioConfig(); |
|
180 mInfo.mAudio.mHasAudio = mAudio.mActive = mDemuxer->HasAudio() && |
|
181 audio.IsValidConfig(); |
|
182 // If we have audio, we *only* allow AAC to be decoded. |
|
183 if (HasAudio() && audio.codec() != kCodecAAC) { |
|
184 return NS_ERROR_FAILURE; |
|
185 } |
|
186 |
|
187 const VideoDecoderConfig& video = mDemuxer->VideoConfig(); |
|
188 mInfo.mVideo.mHasVideo = mVideo.mActive = mDemuxer->HasVideo() && |
|
189 video.IsValidConfig(); |
|
190 // If we have video, we *only* allow H.264 to be decoded. |
|
191 if (HasVideo() && video.codec() != kCodecH264) { |
|
192 return NS_ERROR_FAILURE; |
|
193 } |
|
194 |
|
195 mPlatform = PlatformDecoderModule::Create(); |
|
196 NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE); |
|
197 |
|
198 if (HasAudio()) { |
|
199 mInfo.mAudio.mRate = audio.samples_per_second(); |
|
200 mInfo.mAudio.mChannels = ChannelLayoutToChannelCount(audio.channel_layout()); |
|
201 mAudio.mCallback = new DecoderCallback(this, kAudio); |
|
202 mAudio.mDecoder = mPlatform->CreateAACDecoder(audio, |
|
203 mAudio.mTaskQueue, |
|
204 mAudio.mCallback); |
|
205 NS_ENSURE_TRUE(mAudio.mDecoder != nullptr, NS_ERROR_FAILURE); |
|
206 nsresult rv = mAudio.mDecoder->Init(); |
|
207 NS_ENSURE_SUCCESS(rv, rv); |
|
208 } |
|
209 |
|
210 if (HasVideo()) { |
|
211 IntSize sz = video.natural_size(); |
|
212 mInfo.mVideo.mDisplay = nsIntSize(sz.width(), sz.height()); |
|
213 mVideo.mCallback = new DecoderCallback(this, kVideo); |
|
214 mVideo.mDecoder = mPlatform->CreateH264Decoder(video, |
|
215 mLayersBackendType, |
|
216 mDecoder->GetImageContainer(), |
|
217 mVideo.mTaskQueue, |
|
218 mVideo.mCallback); |
|
219 NS_ENSURE_TRUE(mVideo.mDecoder != nullptr, NS_ERROR_FAILURE); |
|
220 nsresult rv = mVideo.mDecoder->Init(); |
|
221 NS_ENSURE_SUCCESS(rv, rv); |
|
222 } |
|
223 |
|
224 // Get the duration, and report it to the decoder if we have it. |
|
225 Microseconds duration = mDemuxer->Duration(); |
|
226 if (duration != -1) { |
|
227 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
|
228 mDecoder->SetMediaDuration(duration); |
|
229 } |
|
230 // We can seek if we get a duration *and* the reader reports that it's |
|
231 // seekable. |
|
232 if (!mDemuxer->CanSeek()) { |
|
233 mDecoder->SetMediaSeekable(false); |
|
234 } |
|
235 |
|
236 *aInfo = mInfo; |
|
237 *aTags = nullptr; |
|
238 |
|
239 return NS_OK; |
|
240 } |
|
241 |
|
242 bool |
|
243 MP4Reader::HasAudio() |
|
244 { |
|
245 return mAudio.mActive; |
|
246 } |
|
247 |
|
248 bool |
|
249 MP4Reader::HasVideo() |
|
250 { |
|
251 return mVideo.mActive; |
|
252 } |
|
253 |
|
254 MP4Reader::DecoderData& |
|
255 MP4Reader::GetDecoderData(TrackType aTrack) |
|
256 { |
|
257 MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo); |
|
258 return (aTrack == kAudio) ? mAudio : mVideo; |
|
259 } |
|
260 |
|
261 MP4SampleQueue& |
|
262 MP4Reader::SampleQueue(TrackType aTrack) |
|
263 { |
|
264 return GetDecoderData(aTrack).mDemuxedSamples; |
|
265 } |
|
266 |
|
267 MediaDataDecoder* |
|
268 MP4Reader::Decoder(mp4_demuxer::TrackType aTrack) |
|
269 { |
|
270 return GetDecoderData(aTrack).mDecoder; |
|
271 } |
|
272 |
|
273 MP4Sample* |
|
274 MP4Reader::PopSample(TrackType aTrack) |
|
275 { |
|
276 // Unfortunately the demuxer outputs in the order samples appear in the |
|
277 // media, not on a per stream basis. We cache the samples we get from |
|
278 // streams other than the one we want. |
|
279 MP4SampleQueue& sampleQueue = SampleQueue(aTrack); |
|
280 while (sampleQueue.empty()) { |
|
281 nsAutoPtr<MP4Sample> sample; |
|
282 bool eos = false; |
|
283 bool ok = mDemuxer->Demux(&sample, &eos); |
|
284 if (!ok || eos) { |
|
285 MOZ_ASSERT(!sample); |
|
286 return nullptr; |
|
287 } |
|
288 MOZ_ASSERT(sample); |
|
289 MP4Sample* s = sample.forget(); |
|
290 SampleQueue(s->type).push_back(s); |
|
291 } |
|
292 MOZ_ASSERT(!sampleQueue.empty()); |
|
293 MP4Sample* sample = sampleQueue.front(); |
|
294 sampleQueue.pop_front(); |
|
295 return sample; |
|
296 } |
|
297 |
|
298 // How async decoding works: |
|
299 // |
|
300 // When MP4Reader::Decode() is called: |
|
301 // * Lock the DecoderData. We assume the state machine wants |
|
302 // output from the decoder (in future, we'll assume decoder wants input |
|
303 // when the output MediaQueue isn't "full"). |
|
304 // * Cache the value of mNumSamplesOutput, as prevFramesOutput. |
|
305 // * While we've not output data (mNumSamplesOutput != prevNumFramesOutput) |
|
306 // and while we still require input, we demux and input data in the reader. |
|
307 // We assume we require input if |
|
308 // ((mNumSamplesInput - mNumSamplesOutput) < sDecodeAheadMargin) or |
|
309 // mInputExhausted is true. Before we send input, we reset mInputExhausted |
|
310 // and increment mNumFrameInput, and drop the lock on DecoderData. |
|
311 // * Once we no longer require input, we wait on the DecoderData |
|
312 // lock for output, or for the input exhausted callback. If we receive the |
|
313 // input exhausted callback, we go back and input more data. |
|
314 // * When our output callback is called, we take the DecoderData lock and |
|
315 // increment mNumSamplesOutput. We notify the DecoderData lock. This will |
|
316 // awaken the Decode thread, and unblock it, and it will return. |
|
317 bool |
|
318 MP4Reader::Decode(TrackType aTrack) |
|
319 { |
|
320 DecoderData& data = GetDecoderData(aTrack); |
|
321 MOZ_ASSERT(data.mDecoder); |
|
322 |
|
323 data.mMonitor.Lock(); |
|
324 uint64_t prevNumFramesOutput = data.mNumSamplesOutput; |
|
325 while (prevNumFramesOutput == data.mNumSamplesOutput) { |
|
326 data.mMonitor.AssertCurrentThreadOwns(); |
|
327 if (data.mError) { |
|
328 // Decode error! |
|
329 data.mMonitor.Unlock(); |
|
330 return false; |
|
331 } |
|
332 // Send input to the decoder, if we need to. We assume the decoder |
|
333 // needs input if it's told us it's out of input, or we're beneath the |
|
334 // "low water mark" for the amount of input we've sent it vs the amount |
|
335 // out output we've received. We always try to keep the decoder busy if |
|
336 // possible, so we try to maintain at least a few input samples ahead, |
|
337 // if we need output. |
|
338 while (prevNumFramesOutput == data.mNumSamplesOutput && |
|
339 (data.mInputExhausted || |
|
340 (data.mNumSamplesInput - data.mNumSamplesOutput) < data.mDecodeAhead)) { |
|
341 data.mMonitor.AssertCurrentThreadOwns(); |
|
342 data.mMonitor.Unlock(); |
|
343 nsAutoPtr<MP4Sample> compressed(PopSample(aTrack)); |
|
344 if (!compressed) { |
|
345 // EOS, or error. Let the state machine know there are no more |
|
346 // frames coming. |
|
347 return false; |
|
348 } |
|
349 data.mMonitor.Lock(); |
|
350 data.mInputExhausted = false; |
|
351 data.mNumSamplesInput++; |
|
352 data.mMonitor.Unlock(); |
|
353 if (NS_FAILED(data.mDecoder->Input(compressed))) { |
|
354 return false; |
|
355 } |
|
356 // If Input() failed, we let the auto pointer delete |compressed|. |
|
357 // Otherwise, we assume the decoder will delete it when it's finished |
|
358 // with it. |
|
359 compressed.forget(); |
|
360 data.mMonitor.Lock(); |
|
361 } |
|
362 data.mMonitor.AssertCurrentThreadOwns(); |
|
363 while (!data.mError && |
|
364 prevNumFramesOutput == data.mNumSamplesOutput && |
|
365 !data.mInputExhausted ) { |
|
366 data.mMonitor.Wait(); |
|
367 } |
|
368 } |
|
369 data.mMonitor.AssertCurrentThreadOwns(); |
|
370 data.mMonitor.Unlock(); |
|
371 return true; |
|
372 } |
|
373 |
|
374 #ifdef LOG_SAMPLE_DECODE |
|
375 static const char* |
|
376 TrackTypeToStr(TrackType aTrack) |
|
377 { |
|
378 MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo); |
|
379 switch (aTrack) { |
|
380 case kAudio: return "Audio"; |
|
381 case kVideo: return "Video"; |
|
382 default: return "Unknown"; |
|
383 } |
|
384 } |
|
385 #endif |
|
386 |
|
387 void |
|
388 MP4Reader::Output(mp4_demuxer::TrackType aTrack, MediaData* aSample) |
|
389 { |
|
390 #ifdef LOG_SAMPLE_DECODE |
|
391 LOG("Decoded %s sample time=%lld dur=%lld", |
|
392 TrackTypeToStr(aTrack), aSample->mTime, aSample->mDuration); |
|
393 #endif |
|
394 |
|
395 DecoderData& data = GetDecoderData(aTrack); |
|
396 // Don't accept output while we're flushing. |
|
397 MonitorAutoLock mon(data.mMonitor); |
|
398 if (data.mIsFlushing) { |
|
399 mon.NotifyAll(); |
|
400 return; |
|
401 } |
|
402 |
|
403 switch (aTrack) { |
|
404 case kAudio: { |
|
405 MOZ_ASSERT(aSample->mType == MediaData::AUDIO_SAMPLES); |
|
406 AudioQueue().Push(static_cast<AudioData*>(aSample)); |
|
407 break; |
|
408 } |
|
409 case kVideo: { |
|
410 MOZ_ASSERT(aSample->mType == MediaData::VIDEO_FRAME); |
|
411 VideoQueue().Push(static_cast<VideoData*>(aSample)); |
|
412 break; |
|
413 } |
|
414 default: |
|
415 break; |
|
416 } |
|
417 |
|
418 data.mNumSamplesOutput++; |
|
419 mon.NotifyAll(); |
|
420 } |
|
421 |
|
422 void |
|
423 MP4Reader::InputExhausted(mp4_demuxer::TrackType aTrack) |
|
424 { |
|
425 DecoderData& data = GetDecoderData(aTrack); |
|
426 MonitorAutoLock mon(data.mMonitor); |
|
427 data.mInputExhausted = true; |
|
428 mon.NotifyAll(); |
|
429 } |
|
430 |
|
431 void |
|
432 MP4Reader::Error(mp4_demuxer::TrackType aTrack) |
|
433 { |
|
434 DecoderData& data = GetDecoderData(aTrack); |
|
435 MonitorAutoLock mon(data.mMonitor); |
|
436 data.mError = true; |
|
437 mon.NotifyAll(); |
|
438 } |
|
439 |
|
440 bool |
|
441 MP4Reader::DecodeAudioData() |
|
442 { |
|
443 MOZ_ASSERT(HasAudio() && mPlatform && mAudio.mDecoder); |
|
444 return Decode(kAudio); |
|
445 } |
|
446 |
|
447 void |
|
448 MP4Reader::Flush(mp4_demuxer::TrackType aTrack) |
|
449 { |
|
450 DecoderData& data = GetDecoderData(aTrack); |
|
451 if (!data.mDecoder) { |
|
452 return; |
|
453 } |
|
454 // Purge the current decoder's state. |
|
455 // Set a flag so that we ignore all output while we call |
|
456 // MediaDataDecoder::Flush(). |
|
457 { |
|
458 data.mIsFlushing = true; |
|
459 MonitorAutoLock mon(data.mMonitor); |
|
460 } |
|
461 data.mDecoder->Flush(); |
|
462 { |
|
463 data.mIsFlushing = false; |
|
464 MonitorAutoLock mon(data.mMonitor); |
|
465 } |
|
466 } |
|
467 |
|
468 bool |
|
469 MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed) |
|
470 { |
|
471 MOZ_ASSERT(mVideo.mDecoder); |
|
472 |
|
473 Flush(kVideo); |
|
474 |
|
475 // Loop until we reach the next keyframe after the threshold. |
|
476 while (true) { |
|
477 nsAutoPtr<MP4Sample> compressed(PopSample(kVideo)); |
|
478 if (!compressed) { |
|
479 // EOS, or error. Let the state machine know. |
|
480 return false; |
|
481 } |
|
482 parsed++; |
|
483 if (!compressed->is_sync_point || |
|
484 compressed->composition_timestamp < aTimeThreshold) { |
|
485 continue; |
|
486 } |
|
487 mVideo.mDemuxedSamples.push_front(compressed.forget()); |
|
488 break; |
|
489 } |
|
490 |
|
491 return true; |
|
492 } |
|
493 |
|
494 bool |
|
495 MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip, |
|
496 int64_t aTimeThreshold) |
|
497 { |
|
498 // Record number of frames decoded and parsed. Automatically update the |
|
499 // stats counters using the AutoNotifyDecoded stack-based class. |
|
500 uint32_t parsed = 0, decoded = 0; |
|
501 AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); |
|
502 |
|
503 MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder); |
|
504 |
|
505 if (aKeyframeSkip) { |
|
506 bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed); |
|
507 if (!ok) { |
|
508 NS_WARNING("Failed to skip demux up to next keyframe"); |
|
509 return false; |
|
510 } |
|
511 aKeyframeSkip = false; |
|
512 nsresult rv = mVideo.mDecoder->Flush(); |
|
513 NS_ENSURE_SUCCESS(rv, false); |
|
514 } |
|
515 |
|
516 bool rv = Decode(kVideo); |
|
517 { |
|
518 // Report the number of "decoded" frames as the difference in the |
|
519 // mNumSamplesOutput field since the last time we were called. |
|
520 MonitorAutoLock mon(mVideo.mMonitor); |
|
521 uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames; |
|
522 decoded = static_cast<uint32_t>(delta); |
|
523 mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput; |
|
524 } |
|
525 return rv; |
|
526 } |
|
527 |
|
528 nsresult |
|
529 MP4Reader::Seek(int64_t aTime, |
|
530 int64_t aStartTime, |
|
531 int64_t aEndTime, |
|
532 int64_t aCurrentTime) |
|
533 { |
|
534 if (!mDemuxer->CanSeek()) { |
|
535 return NS_ERROR_FAILURE; |
|
536 } |
|
537 return NS_ERROR_NOT_IMPLEMENTED; |
|
538 } |
|
539 |
|
540 } // namespace mozilla |