1 /*
2 * Copyright 2015, Mozilla Foundation and contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <cstdint>
18 #include <limits>
19
20 #include "AudioDecoder.h"
21 #include "ClearKeyDecryptionManager.h"
22 #include "ClearKeyUtils.h"
23 #include "gmp-task-utils.h"
24
25 using namespace wmf;
26
AudioDecoder(GMPAudioHost * aHostAPI)27 AudioDecoder::AudioDecoder(GMPAudioHost *aHostAPI)
28 : mHostAPI(aHostAPI)
29 , mCallback(nullptr)
30 , mWorkerThread(nullptr)
31 , mMutex(nullptr)
32 , mNumInputTasks(0)
33 , mHasShutdown(false)
34 {
35 // We drop the ref in DecodingComplete().
36 AddRef();
37 }
38
~AudioDecoder()39 AudioDecoder::~AudioDecoder()
40 {
41 if (mMutex) {
42 mMutex->Destroy();
43 }
44 }
45
46 void
InitDecode(const GMPAudioCodec & aConfig,GMPAudioDecoderCallback * aCallback)47 AudioDecoder::InitDecode(const GMPAudioCodec& aConfig,
48 GMPAudioDecoderCallback* aCallback)
49 {
50 mCallback = aCallback;
51 assert(mCallback);
52 mDecoder = new WMFAACDecoder();
53 HRESULT hr = mDecoder->Init(aConfig.mChannelCount,
54 aConfig.mSamplesPerSecond,
55 (BYTE*)aConfig.mExtraData,
56 aConfig.mExtraDataLen);
57 LOG("[%p] AudioDecoder::InitializeAudioDecoder() hr=0x%x\n", this, hr);
58 if (FAILED(hr)) {
59 mCallback->Error(GMPGenericErr);
60 return;
61 }
62 auto err = GetPlatform()->createmutex(&mMutex);
63 if (GMP_FAILED(err)) {
64 mCallback->Error(GMPGenericErr);
65 return;
66 }
67 }
68
69 void
EnsureWorker()70 AudioDecoder::EnsureWorker()
71 {
72 if (!mWorkerThread) {
73 GetPlatform()->createthread(&mWorkerThread);
74 if (!mWorkerThread) {
75 mCallback->Error(GMPAllocErr);
76 return;
77 }
78 }
79 }
80
81 void
Decode(GMPAudioSamples * aInput)82 AudioDecoder::Decode(GMPAudioSamples* aInput)
83 {
84 EnsureWorker();
85 {
86 AutoLock lock(mMutex);
87 mNumInputTasks++;
88 }
89 mWorkerThread->Post(WrapTaskRefCounted(this,
90 &AudioDecoder::DecodeTask,
91 aInput));
92 }
93
94 void
DecodeTask(GMPAudioSamples * aInput)95 AudioDecoder::DecodeTask(GMPAudioSamples* aInput)
96 {
97 HRESULT hr;
98
99 {
100 AutoLock lock(mMutex);
101 mNumInputTasks--;
102 assert(mNumInputTasks >= 0);
103 }
104
105 if (!aInput || !mHostAPI || !mDecoder) {
106 LOG("Decode job not set up correctly!");
107 return;
108 }
109
110 const uint8_t* inBuffer = aInput->Buffer();
111 if (!inBuffer) {
112 LOG("No buffer for encoded samples!\n");
113 return;
114 }
115
116 const GMPEncryptedBufferMetadata* crypto = aInput->GetDecryptionData();
117 std::vector<uint8_t> buffer(inBuffer, inBuffer + aInput->Size());
118 if (crypto) {
119 // Plugin host should have set up its decryptor/key sessions
120 // before trying to decode!
121 GMPErr rv =
122 ClearKeyDecryptionManager::Get()->Decrypt(buffer, CryptoMetaData(crypto));
123
124 if (GMP_FAILED(rv)) {
125 CK_LOGE("Failed to decrypt with key id %08x...", *(uint32_t*)crypto->KeyId());
126 MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Error, rv));
127 return;
128 }
129 }
130
131 hr = mDecoder->Input(&buffer[0],
132 buffer.size(),
133 aInput->TimeStamp());
134
135 // We must delete the input sample!
136 GetPlatform()->runonmainthread(WrapTask(aInput, &GMPAudioSamples::Destroy));
137
138 SAMPLE_LOG("AudioDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
139 if (FAILED(hr)) {
140 LOG("AudioDecoder::DecodeTask() decode failed ret=0x%x%s\n",
141 hr,
142 ((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
143 return;
144 }
145
146 while (hr == S_OK) {
147 CComPtr<IMFSample> output;
148 hr = mDecoder->Output(&output);
149 SAMPLE_LOG("AudioDecoder::DecodeTask() output ret=0x%x\n", hr);
150 if (hr == S_OK) {
151 ReturnOutput(output);
152 }
153 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
154 AutoLock lock(mMutex);
155 if (mNumInputTasks == 0) {
156 // We have run all input tasks. We *must* notify Gecko so that it will
157 // send us more data.
158 MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::InputDataExhausted));
159 }
160 } else if (FAILED(hr)) {
161 LOG("AudioDecoder::DecodeTask() output failed hr=0x%x\n", hr);
162 }
163 }
164 }
165
166 void
ReturnOutput(IMFSample * aSample)167 AudioDecoder::ReturnOutput(IMFSample* aSample)
168 {
169 SAMPLE_LOG("[%p] AudioDecoder::ReturnOutput()\n", this);
170 assert(aSample);
171
172 HRESULT hr;
173
174 GMPAudioSamples* samples = nullptr;
175 mHostAPI->CreateSamples(kGMPAudioIS16Samples, &samples);
176 if (!samples) {
177 LOG("Failed to create i420 frame!\n");
178 return;
179 }
180
181 hr = MFToGMPSample(aSample, samples);
182 if (FAILED(hr)) {
183 samples->Destroy();
184 LOG("Failed to prepare output sample!");
185 return;
186 }
187 ENSURE(SUCCEEDED(hr), /*void*/);
188
189 MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::Decoded, samples));
190 }
191
192 HRESULT
MFToGMPSample(IMFSample * aInput,GMPAudioSamples * aOutput)193 AudioDecoder::MFToGMPSample(IMFSample* aInput,
194 GMPAudioSamples* aOutput)
195 {
196 ENSURE(aInput != nullptr, E_POINTER);
197 ENSURE(aOutput != nullptr, E_POINTER);
198
199 HRESULT hr;
200 CComPtr<IMFMediaBuffer> mediaBuffer;
201
202 hr = aInput->ConvertToContiguousBuffer(&mediaBuffer);
203 ENSURE(SUCCEEDED(hr), hr);
204
205 BYTE* data = nullptr; // Note: *data will be owned by the IMFMediaBuffer, we don't need to free it.
206 DWORD maxLength = 0, currentLength = 0;
207 hr = mediaBuffer->Lock(&data, &maxLength, ¤tLength);
208 ENSURE(SUCCEEDED(hr), hr);
209
210 auto err = aOutput->SetBufferSize(currentLength);
211 ENSURE(GMP_SUCCEEDED(err), E_FAIL);
212
213 memcpy(aOutput->Buffer(), data, currentLength);
214
215 mediaBuffer->Unlock();
216
217 LONGLONG hns = 0;
218 hr = aInput->GetSampleTime(&hns);
219 ENSURE(SUCCEEDED(hr), hr);
220 aOutput->SetTimeStamp(HNsToUsecs(hns));
221 aOutput->SetChannels(mDecoder->Channels());
222 aOutput->SetRate(mDecoder->Rate());
223
224 return S_OK;
225 }
226
227 void
Reset()228 AudioDecoder::Reset()
229 {
230 if (mDecoder) {
231 mDecoder->Reset();
232 }
233 if (mCallback) {
234 mCallback->ResetComplete();
235 }
236 }
237
238 void
DrainTask()239 AudioDecoder::DrainTask()
240 {
241 mDecoder->Drain();
242
243 // Return any pending output.
244 HRESULT hr = S_OK;
245 while (hr == S_OK) {
246 CComPtr<IMFSample> output;
247 hr = mDecoder->Output(&output);
248 SAMPLE_LOG("AudioDecoder::DrainTask() output ret=0x%x\n", hr);
249 if (hr == S_OK) {
250 ReturnOutput(output);
251 }
252 }
253 MaybeRunOnMainThread(WrapTask(mCallback, &GMPAudioDecoderCallback::DrainComplete));
254 }
255
256 void
Drain()257 AudioDecoder::Drain()
258 {
259 if (!mDecoder) {
260 return;
261 }
262 EnsureWorker();
263 mWorkerThread->Post(WrapTaskRefCounted(this,
264 &AudioDecoder::DrainTask));
265 }
266
267 void
DecodingComplete()268 AudioDecoder::DecodingComplete()
269 {
270 if (mWorkerThread) {
271 mWorkerThread->Join();
272 }
273 mHasShutdown = true;
274
275 // Release the reference we added in the constructor. There may be
276 // WrapRefCounted tasks that also hold references to us, and keep
277 // us alive a little longer.
278 Release();
279 }
280
281 void
MaybeRunOnMainThread(GMPTask * aTask)282 AudioDecoder::MaybeRunOnMainThread(GMPTask* aTask)
283 {
284 class MaybeRunTask : public GMPTask
285 {
286 public:
287 MaybeRunTask(AudioDecoder* aDecoder, GMPTask* aTask)
288 : mDecoder(aDecoder), mTask(aTask)
289 { }
290
291 virtual void Run(void) {
292 if (mDecoder->HasShutdown()) {
293 CK_LOGD("Trying to dispatch to main thread after AudioDecoder has shut down");
294 return;
295 }
296
297 mTask->Run();
298 }
299
300 virtual void Destroy()
301 {
302 mTask->Destroy();
303 delete this;
304 }
305
306 private:
307 RefPtr<AudioDecoder> mDecoder;
308 GMPTask* mTask;
309 };
310
311 GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
312 }
313