Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "WidevineVideoDecoder.h"
7 :
8 : #include "WidevineUtils.h"
9 : #include "WidevineVideoFrame.h"
10 : #include "mozilla/Move.h"
11 : #include <inttypes.h>
12 :
13 : using namespace cdm;
14 :
15 : namespace mozilla {
16 :
17 0 : WidevineVideoDecoder::WidevineVideoDecoder(GMPVideoHost* aVideoHost,
18 0 : RefPtr<CDMWrapper> aCDMWrapper)
19 : : mVideoHost(aVideoHost)
20 0 : , mCDMWrapper(Move(aCDMWrapper))
21 : , mSentInput(false)
22 : , mCodecType(kGMPVideoCodecInvalid)
23 : , mReturnOutputCallDepth(0)
24 : , mDrainPending(false)
25 0 : , mResetInProgress(false)
26 : {
27 : // Expect to start with a CDM wrapper, will release it in DecodingComplete().
28 0 : MOZ_ASSERT(mCDMWrapper);
29 0 : CDM_LOG("WidevineVideoDecoder created this=%p", this);
30 :
31 : // Corresponding Release is in DecodingComplete().
32 0 : AddRef();
33 0 : }
34 :
35 0 : WidevineVideoDecoder::~WidevineVideoDecoder()
36 : {
37 0 : CDM_LOG("WidevineVideoDecoder destroyed this=%p", this);
38 0 : }
39 :
40 : static
41 : VideoDecoderConfig::VideoCodecProfile
42 0 : ToCDMH264Profile(uint8_t aProfile)
43 : {
44 0 : switch (aProfile) {
45 0 : case 66: return VideoDecoderConfig::kH264ProfileBaseline;
46 0 : case 77: return VideoDecoderConfig::kH264ProfileMain;
47 0 : case 88: return VideoDecoderConfig::kH264ProfileExtended;
48 0 : case 100: return VideoDecoderConfig::kH264ProfileHigh;
49 0 : case 110: return VideoDecoderConfig::kH264ProfileHigh10;
50 0 : case 122: return VideoDecoderConfig::kH264ProfileHigh422;
51 0 : case 144: return VideoDecoderConfig::kH264ProfileHigh444Predictive;
52 : }
53 0 : return VideoDecoderConfig::kUnknownVideoCodecProfile;
54 : }
55 :
56 : void
57 0 : WidevineVideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
58 : const uint8_t* aCodecSpecific,
59 : uint32_t aCodecSpecificLength,
60 : GMPVideoDecoderCallback* aCallback,
61 : int32_t aCoreCount)
62 : {
63 0 : mCallback = aCallback;
64 0 : VideoDecoderConfig config;
65 0 : mCodecType = aCodecSettings.mCodecType;
66 0 : if (mCodecType == kGMPVideoCodecH264) {
67 0 : config.codec = VideoDecoderConfig::kCodecH264;
68 0 : const GMPVideoCodecH264* h264 = (const GMPVideoCodecH264*)(aCodecSpecific);
69 0 : config.profile = ToCDMH264Profile(h264->mAVCC.mProfile);
70 0 : } else if (mCodecType == kGMPVideoCodecVP8) {
71 0 : config.codec = VideoDecoderConfig::kCodecVp8;
72 0 : config.profile = VideoDecoderConfig::kProfileNotNeeded;
73 0 : } else if (mCodecType == kGMPVideoCodecVP9) {
74 0 : config.codec = VideoDecoderConfig::kCodecVp9;
75 0 : config.profile = VideoDecoderConfig::kProfileNotNeeded;
76 : } else {
77 0 : mCallback->Error(GMPInvalidArgErr);
78 0 : return;
79 : }
80 0 : config.format = kYv12;
81 0 : config.coded_size = mCodedSize = Size(aCodecSettings.mWidth, aCodecSettings.mHeight);
82 0 : nsTArray<uint8_t> extraData;
83 0 : if (aCodecSpecificLength > 0) {
84 : // The first byte is the WebRTC packetization mode, which can be ignored.
85 0 : extraData.AppendElements(aCodecSpecific + 1, aCodecSpecificLength - 1);
86 0 : config.extra_data = extraData.Elements();
87 0 : config.extra_data_size = extraData.Length();
88 : }
89 0 : Status rv = CDM()->InitializeVideoDecoder(config);
90 0 : if (rv != kSuccess) {
91 0 : mCallback->Error(ToGMPErr(rv));
92 0 : return;
93 : }
94 0 : CDM_LOG("WidevineVideoDecoder::InitDecode() rv=%d", rv);
95 : }
96 :
97 : void
98 0 : WidevineVideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
99 : bool aMissingFrames,
100 : const uint8_t* aCodecSpecificInfo,
101 : uint32_t aCodecSpecificInfoLength,
102 : int64_t aRenderTimeMs)
103 : {
104 : // We should not be given new input if a drain has been initiated
105 0 : MOZ_ASSERT(!mDrainPending);
106 : // We may not get the same out of the CDM decoder as we put in, and there
107 : // may be some latency, i.e. we may need to input (say) 30 frames before
108 : // we receive output. So we need to store the durations of the frames input,
109 : // and retrieve them on output.
110 0 : mFrameDurations[aInputFrame->TimeStamp()] = aInputFrame->Duration();
111 :
112 0 : mSentInput = true;
113 0 : InputBuffer sample;
114 :
115 0 : const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
116 0 : nsTArray<SubsampleEntry> subsamples;
117 0 : InitInputBuffer(crypto, aInputFrame->TimeStamp(),
118 0 : aInputFrame->Buffer(), aInputFrame->Size(),
119 0 : sample, subsamples);
120 :
121 0 : WidevineVideoFrame frame;
122 0 : Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
123 0 : CDM_LOG("WidevineVideoDecoder::Decode(timestamp=%" PRId64 ") rv=%d",
124 : sample.timestamp, rv);
125 :
126 : // Destroy frame, so that the shmem is now free to be used to return
127 : // output to the Gecko process.
128 0 : aInputFrame->Destroy();
129 0 : aInputFrame = nullptr;
130 :
131 0 : if (rv == kSuccess || rv == kNoKey) {
132 0 : if (rv == kNoKey) {
133 0 : CDM_LOG("NoKey for sample at time=%" PRId64 "!", sample.timestamp);
134 : // Somehow our key became unusable. Typically this would happen when
135 : // a stream requires output protection, and the configuration changed
136 : // such that output protection is no longer available. For example, a
137 : // non-compliant monitor was attached. The JS player should notice the
138 : // key status changing to "output-restricted", and is supposed to switch
139 : // to a stream that doesn't require OP. In order to keep the playback
140 : // pipeline rolling, just output a black frame. See bug 1343140.
141 0 : if (!frame.InitToBlack(mCodedSize.width, mCodedSize.height,
142 : sample.timestamp)) {
143 0 : mCallback->Error(GMPDecodeErr);
144 0 : return;
145 : }
146 : }
147 0 : if (!ReturnOutput(frame)) {
148 0 : CDM_LOG("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
149 0 : mCallback->Error(GMPDecodeErr);
150 0 : return;
151 : }
152 : // A reset should only be started at most at level mReturnOutputCallDepth 1,
153 : // and if it's started it should be finished by that call by the time
154 : // the it returns, so it should always be false by this point.
155 0 : MOZ_ASSERT(!mResetInProgress);
156 : // Only request more data if we don't have pending samples.
157 0 : if (mFrameAllocationQueue.empty()) {
158 0 : MOZ_ASSERT(mCDMWrapper);
159 0 : mCallback->InputDataExhausted();
160 : }
161 0 : } else if (rv == kNeedMoreData) {
162 0 : MOZ_ASSERT(mCDMWrapper);
163 0 : mCallback->InputDataExhausted();
164 : } else {
165 0 : mCallback->Error(ToGMPErr(rv));
166 : }
167 : // Finish a drain if pending and we have no pending ReturnOutput calls on the
168 : // stack.
169 0 : if (mDrainPending && mReturnOutputCallDepth == 0) {
170 0 : Drain();
171 : }
172 : }
173 :
174 : // Util class to assist with counting mReturnOutputCallDepth.
175 : class CounterHelper {
176 : public:
177 : // RAII, increment counter
178 0 : explicit CounterHelper(int32_t& counter)
179 0 : : mCounter(counter)
180 : {
181 0 : mCounter++;
182 0 : }
183 :
184 : // RAII, decrement counter
185 0 : ~CounterHelper()
186 0 : {
187 0 : mCounter--;
188 0 : }
189 :
190 : private:
191 : int32_t& mCounter;
192 : };
193 :
194 : // Util class to make sure GMP frames are freed. Holds a GMPVideoi420Frame*
195 : // and will destroy it when the helper is destroyed unless the held frame
196 : // if forgotten with ForgetFrame.
197 : class FrameDestroyerHelper
198 : {
199 : public:
200 0 : explicit FrameDestroyerHelper(GMPVideoi420Frame*& frame) : frame(frame) { }
201 :
202 : // RAII, destroy frame if held.
203 0 : ~FrameDestroyerHelper()
204 0 : {
205 0 : if (frame) {
206 0 : frame->Destroy();
207 : }
208 0 : frame = nullptr;
209 0 : }
210 :
211 : // Forget the frame without destroying it.
212 0 : void ForgetFrame()
213 : {
214 0 : frame = nullptr;
215 0 : }
216 :
217 : private:
218 : GMPVideoi420Frame* frame;
219 : };
220 :
221 :
222 : // Special handing is needed around ReturnOutput as it spins the IPC message
223 : // queue when creating an empty frame and can end up with reentrant calls into
224 : // the class methods.
225 : bool
226 0 : WidevineVideoDecoder::ReturnOutput(WidevineVideoFrame& aCDMFrame)
227 : {
228 0 : MOZ_ASSERT(mReturnOutputCallDepth >= 0);
229 0 : CounterHelper counterHelper(mReturnOutputCallDepth);
230 0 : mFrameAllocationQueue.push_back(Move(aCDMFrame));
231 0 : if (mReturnOutputCallDepth > 1) {
232 : // In a reentrant call.
233 0 : return true;
234 : }
235 0 : while (!mFrameAllocationQueue.empty()) {
236 0 : MOZ_ASSERT(mReturnOutputCallDepth == 1);
237 : // If we're at call level 1 a reset should not have been started. A
238 : // reset may be received during CreateEmptyFrame below, but we should not
239 : // be in a reset at this stage -- this would indicate receiving decode
240 : // messages before completing our reset, which we should not.
241 0 : MOZ_ASSERT(!mResetInProgress);
242 0 : WidevineVideoFrame currentCDMFrame = Move(mFrameAllocationQueue.front());
243 0 : mFrameAllocationQueue.pop_front();
244 0 : GMPVideoFrame* f = nullptr;
245 0 : auto err = mVideoHost->CreateFrame(kGMPI420VideoFrame, &f);
246 0 : if (GMP_FAILED(err) || !f) {
247 0 : CDM_LOG("Failed to create i420 frame!\n");
248 0 : return false;
249 : }
250 0 : auto gmpFrame = static_cast<GMPVideoi420Frame*>(f);
251 0 : FrameDestroyerHelper frameDestroyerHelper(gmpFrame);
252 0 : Size size = currentCDMFrame.Size();
253 0 : const int32_t yStride = currentCDMFrame.Stride(VideoFrame::kYPlane);
254 0 : const int32_t uStride = currentCDMFrame.Stride(VideoFrame::kUPlane);
255 0 : const int32_t vStride = currentCDMFrame.Stride(VideoFrame::kVPlane);
256 0 : const int32_t halfHeight = size.height / 2;
257 : // This call can cause a shmem alloc, during this alloc other calls
258 : // may be made to this class and placed on the stack. ***WARNING***:
259 : // other IPC calls can happen during this call, resulting in calls
260 : // being made to the CDM. After this call state can have changed,
261 : // and should be reevaluated.
262 0 : err = gmpFrame->CreateEmptyFrame(size.width,
263 : size.height,
264 : yStride,
265 : uStride,
266 0 : vStride);
267 : // Assert possible reentrant calls or resets haven't altered level
268 : // unexpectedly.
269 0 : MOZ_ASSERT(mReturnOutputCallDepth == 1);
270 0 : ENSURE_GMP_SUCCESS(err, false);
271 :
272 : // If a reset started we need to dump the current frame and complete the
273 : // reset.
274 0 : if (mResetInProgress) {
275 0 : MOZ_ASSERT(mCDMWrapper);
276 0 : MOZ_ASSERT(mFrameAllocationQueue.empty());
277 0 : CompleteReset();
278 0 : return true;
279 : }
280 :
281 0 : err = gmpFrame->SetWidth(size.width);
282 0 : ENSURE_GMP_SUCCESS(err, false);
283 :
284 0 : err = gmpFrame->SetHeight(size.height);
285 0 : ENSURE_GMP_SUCCESS(err, false);
286 :
287 0 : Buffer* buffer = currentCDMFrame.FrameBuffer();
288 0 : uint8_t* outBuffer = gmpFrame->Buffer(kGMPYPlane);
289 0 : ENSURE_TRUE(outBuffer != nullptr, false);
290 0 : MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPYPlane) >= yStride*size.height);
291 0 : memcpy(outBuffer,
292 0 : buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kYPlane),
293 0 : yStride * size.height);
294 :
295 0 : outBuffer = gmpFrame->Buffer(kGMPUPlane);
296 0 : ENSURE_TRUE(outBuffer != nullptr, false);
297 0 : MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPUPlane) >= uStride * halfHeight);
298 0 : memcpy(outBuffer,
299 0 : buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kUPlane),
300 0 : uStride * halfHeight);
301 :
302 0 : outBuffer = gmpFrame->Buffer(kGMPVPlane);
303 0 : ENSURE_TRUE(outBuffer != nullptr, false);
304 0 : MOZ_ASSERT(gmpFrame->AllocatedSize(kGMPVPlane) >= vStride * halfHeight);
305 0 : memcpy(outBuffer,
306 0 : buffer->Data() + currentCDMFrame.PlaneOffset(VideoFrame::kVPlane),
307 0 : vStride * halfHeight);
308 :
309 0 : gmpFrame->SetTimestamp(currentCDMFrame.Timestamp());
310 :
311 0 : auto d = mFrameDurations.find(currentCDMFrame.Timestamp());
312 0 : if (d != mFrameDurations.end()) {
313 0 : gmpFrame->SetDuration(d->second);
314 0 : mFrameDurations.erase(d);
315 : }
316 :
317 : // Forget frame so it's not deleted, call back taking ownership.
318 0 : frameDestroyerHelper.ForgetFrame();
319 0 : mCallback->Decoded(gmpFrame);
320 : }
321 :
322 0 : return true;
323 : }
324 :
325 : void
326 0 : WidevineVideoDecoder::Reset()
327 : {
328 0 : CDM_LOG("WidevineVideoDecoder::Reset() mSentInput=%d", mSentInput);
329 : // We shouldn't reset if a drain is pending.
330 0 : MOZ_ASSERT(!mDrainPending);
331 0 : mResetInProgress = true;
332 0 : if (mSentInput) {
333 0 : CDM()->ResetDecoder(kStreamTypeVideo);
334 : }
335 : // Remove queued frames, but do not reset mReturnOutputCallDepth, let the
336 : // ReturnOutput calls unwind and decrement the counter as needed.
337 0 : mFrameAllocationQueue.clear();
338 0 : mFrameDurations.clear();
339 : // Only if no ReturnOutput calls are in progress can we complete, otherwise
340 : // ReturnOutput needs to finalize the reset.
341 0 : if (mReturnOutputCallDepth == 0) {
342 0 : CompleteReset();
343 : }
344 0 : }
345 :
346 : void
347 0 : WidevineVideoDecoder::CompleteReset()
348 : {
349 0 : mCallback->ResetComplete();
350 0 : mSentInput = false;
351 0 : mResetInProgress = false;
352 0 : }
353 :
354 : void
355 0 : WidevineVideoDecoder::Drain()
356 : {
357 0 : CDM_LOG("WidevineVideoDecoder::Drain()");
358 0 : if (mReturnOutputCallDepth > 0) {
359 0 : CDM_LOG("Drain call is reentrant, postponing drain");
360 0 : mDrainPending = true;
361 0 : return;
362 : }
363 :
364 0 : Status rv = kSuccess;
365 0 : while (rv == kSuccess) {
366 0 : WidevineVideoFrame frame;
367 0 : InputBuffer sample;
368 0 : Status rv = CDM()->DecryptAndDecodeFrame(sample, &frame);
369 0 : CDM_LOG("WidevineVideoDecoder::Drain(); DecryptAndDecodeFrame() rv=%d", rv);
370 0 : if (frame.Format() == kUnknownVideoFormat) {
371 0 : break;
372 : }
373 0 : if (rv == kSuccess) {
374 0 : if (!ReturnOutput(frame)) {
375 0 : CDM_LOG("WidevineVideoDecoder::Decode() Failed in ReturnOutput()");
376 : }
377 : }
378 : }
379 : // Shouldn't be reset while draining.
380 0 : MOZ_ASSERT(!mResetInProgress);
381 :
382 0 : CDM()->ResetDecoder(kStreamTypeVideo);
383 0 : mDrainPending = false;
384 0 : mCallback->DrainComplete();
385 : }
386 :
387 : void
388 0 : WidevineVideoDecoder::DecodingComplete()
389 : {
390 0 : CDM_LOG("WidevineVideoDecoder::DecodingComplete()");
391 :
392 0 : if (mCDMWrapper) {
393 : // mCallback will be null if the decoder has not been fully initialized.
394 0 : if (mCallback) {
395 0 : CDM()->DeinitializeDecoder(kStreamTypeVideo);
396 : } else {
397 0 : CDM_LOG("WideVineDecoder::DecodingComplete() Decoder was not fully initialized!");
398 : }
399 :
400 0 : mCDMWrapper = nullptr;
401 : }
402 :
403 : // Release that corresponds to AddRef() in constructor.
404 0 : Release();
405 0 : }
406 :
407 : } // namespace mozilla
|