Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(231)

Side by Side Diff: media/base/android/media_codec_loop.cc

Issue 2016213003: Separate MediaCodecLoop from MediaCodecAudioDecoder (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cl feedback. Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/base/android/media_codec_loop.h"
6
7 #include "base/android/build_info.h"
8 #include "base/bind.h"
9 #include "base/callback_helpers.h"
10 #include "base/logging.h"
11 #include "base/threading/thread_task_runner_handle.h"
12 #include "media/base/android/sdk_media_codec_bridge.h"
13 #include "media/base/audio_buffer.h"
14 #include "media/base/audio_timestamp_helper.h"
15 #include "media/base/bind_to_current_loop.h"
16 #include "media/base/timestamp_constants.h"
17
18 namespace media {
19
20 namespace {
21
22 // TODO(liberato): the client should choose these.
DaleCurtis 2016/06/08 22:55:01 You should be able to replace these now with const
liberato (no reviews please) 2016/06/10 21:34:09 nifty trick.
23 inline const base::TimeDelta DecodePollDelay() {
24 return base::TimeDelta::FromMilliseconds(10);
25 }
26
27 inline const base::TimeDelta NoWaitTimeout() {
28 return base::TimeDelta::FromMicroseconds(0);
29 }
30
31 inline const base::TimeDelta IdleTimerTimeout() {
32 return base::TimeDelta::FromSeconds(1);
33 }
34
35 } // namespace (anonymous)
36
37 MediaCodecLoop::InputBufferData::InputBufferData()
38 : memory(nullptr), length(0), is_eos(false), is_encrypted(false) {}
39 // TODO(liberato): does anybody call this? if not, maybe they should.
DaleCurtis 2016/06/08 22:55:01 Delete until then?
liberato (no reviews please) 2016/06/10 21:34:09 Done.
40 MediaCodecLoop::InputBufferData::InputBufferData(const uint8_t* m,
41 size_t l,
42 const DecodeCB& cb)
43 : memory(m),
44 length(l),
45 completion_cb(cb),
46 is_eos(false),
47 is_encrypted(false) {}
48
49 MediaCodecLoop::InputBufferData::InputBufferData(const InputBufferData& other)
50 : memory(other.memory),
51 length(other.length),
52 key_id(other.key_id),
53 iv(other.iv),
54 subsamples(other.subsamples),
55 presentation_time(other.presentation_time),
56 completion_cb(other.completion_cb),
57 is_eos(other.is_eos),
58 is_encrypted(other.is_encrypted) {}
59
60 MediaCodecLoop::InputBufferData::~InputBufferData() {}
61
62 MediaCodecLoop::MediaCodecLoop(Client* client,
63 std::unique_ptr<MediaCodecBridge>&& media_codec)
64 : state_(STATE_READY),
65 client_(client),
66 media_codec_(std::move(media_codec)),
67 pending_input_buf_index_(kInvalidBufferIndex),
68 weak_factory_(this) {
69 DVLOG(1) << __FUNCTION__;
70 }
71
72 MediaCodecLoop::~MediaCodecLoop() {
73 DVLOG(1) << __FUNCTION__;
74
75 media_codec_.reset();
watk 2016/06/09 02:04:33 Any reason to do this explicitly?
liberato (no reviews please) 2016/06/10 21:34:09 nope, removed.
76 }
77
78 void MediaCodecLoop::OnKeyAdded() {
79 DVLOG(1) << __FUNCTION__;
80
81 if (state_ == STATE_WAITING_FOR_KEY)
82 SetState(STATE_READY);
83
84 DoIOTask();
85 }
86
87 bool MediaCodecLoop::TryFlush() {
88 DVLOG(1) << __FUNCTION__;
89
90 // We do not clear the input queue here. It depends on the caller.
91 // For decoder reset, then it is appropriate. Otherwise, the requests might
92 // simply be sent to us later, such as on a format change.
93
94 // Flush if we can, otherwise completely recreate and reconfigure the codec.
95 // Prior to JellyBean-MR2, flush() had several bugs (b/8125974, b/8347958) so
96 // we have to completely destroy and recreate the codec there.
97 bool success = false;
98 if (state_ != STATE_ERROR && state_ != STATE_DRAINED &&
99 base::android::BuildInfo::GetInstance()->sdk_int() >= 18) {
100 io_timer_.Stop();
DaleCurtis 2016/06/08 22:55:01 Step N+1 is to switch to a common timer like AVDA?
liberato (no reviews please) 2016/06/10 21:34:09 yup. the first step aims at no function changes.
101
102 // media_codec_->Reset() calls MediaCodec.flush().
103 success = (media_codec_->Reset() == MEDIA_CODEC_OK);
DaleCurtis 2016/06/08 22:55:01 Avoid unnecessary parens.
liberato (no reviews please) 2016/06/10 21:34:09 Done.
104
105 // Transition to the error state if the flush failed.
106 SetState(success ? STATE_READY : STATE_ERROR);
107 }
108
109 return success;
110 }
111
112 void MediaCodecLoop::DoIOTask() {
113 if (state_ == STATE_ERROR)
114 return;
115
116 const bool did_input = QueueInput();
DaleCurtis 2016/06/08 22:55:01 Should these be done in a while loop like AVDA? No
Tima Vaisburd 2016/06/08 23:24:13 Each one is a while loop.
DaleCurtis 2016/06/09 00:30:49 Yes, but I don't think each one should be. We know
Tima Vaisburd 2016/06/09 00:54:44 I am not aware of these cases (or forgot), could y
DaleCurtis 2016/06/09 00:56:48 Pulling output may allow new input to be queued; a
liberato (no reviews please) 2016/06/10 21:34:09 hrm, i was going to keep this identical until the
117 const bool did_output = DequeueOutput();
118
119 ManageTimer(did_input || did_output);
120 }
121
122 bool MediaCodecLoop::QueueInput() {
DaleCurtis 2016/06/08 22:55:01 I think the naming for these methods should be rec
liberato (no reviews please) 2016/06/10 21:34:09 Done.
123 DVLOG(2) << __FUNCTION__;
124
125 bool did_work = false;
126 while (QueueOneInputBuffer())
127 did_work = true;
128
129 return did_work;
130 }
131
132 bool MediaCodecLoop::QueueOneInputBuffer() {
133 DVLOG(2) << __FUNCTION__;
134
135 // We can only queue a buffer if there is input from the client, or if we
136 // tried previously but had to wait for a key. In the latter case, MediaCodec
137 // already has the data.
138 if (pending_input_buf_index_ == kInvalidBufferIndex &&
DaleCurtis 2016/06/08 22:55:00 Needs {}
liberato (no reviews please) 2016/06/10 21:34:09 Done.
139 !client_->IsAnyInputPending())
140 return false;
141
142 if (state_ == STATE_WAITING_FOR_KEY || state_ == STATE_DRAINING ||
DaleCurtis 2016/06/08 22:55:01 Ditto.
liberato (no reviews please) 2016/06/10 21:34:09 Done.
143 state_ == STATE_DRAINED)
144 return false;
145
146 // DequeueInputBuffer() may set STATE_ERROR.
147 InputBufferInfo input_info = DequeueInputBuffer();
148
149 if (input_info.buf_index == kInvalidBufferIndex)
150 return false;
151
152 // EnqueueInputBuffer() may set STATE_DRAINING, STATE_WAITING_FOR_KEY or
153 // STATE_ERROR.
154 EnqueueInputBuffer(input_info);
155 return state_ == STATE_READY;
156 }
157
158 MediaCodecLoop::InputBufferInfo MediaCodecLoop::DequeueInputBuffer() {
159 DVLOG(2) << __FUNCTION__;
160
161 // Do not dequeue a new input buffer if we failed with MEDIA_CODEC_NO_KEY.
162 // That status does not return the input buffer back to the pool of
163 // available input buffers. We have to reuse it in QueueSecureInputBuffer().
164 if (pending_input_buf_index_ != kInvalidBufferIndex) {
165 InputBufferInfo result(pending_input_buf_index_, true);
166 pending_input_buf_index_ = kInvalidBufferIndex;
167 return result;
168 }
169
170 int input_buf_index = kInvalidBufferIndex;
171
172 media::MediaCodecStatus status =
173 media_codec_->DequeueInputBuffer(NoWaitTimeout(), &input_buf_index);
174 switch (status) {
175 case media::MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
176 DVLOG(2) << __FUNCTION__ << ": MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER";
177 break;
178
179 case media::MEDIA_CODEC_ERROR:
180 DVLOG(1) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from DequeInputBuffer";
181 SetState(STATE_ERROR);
182 break;
183
184 case media::MEDIA_CODEC_OK:
185 break;
186
187 default:
188 NOTREACHED() << "Unknown DequeueInputBuffer status " << status;
189 SetState(STATE_ERROR);
190 break;
191 }
192
193 return InputBufferInfo(input_buf_index, false);
194 }
195
196 void MediaCodecLoop::EnqueueInputBuffer(const InputBufferInfo& input_info) {
197 DVLOG(2) << __FUNCTION__ << ": index:" << input_info.buf_index;
198
199 DCHECK_NE(input_info.buf_index, kInvalidBufferIndex);
200
201 InputBufferData input_data;
202
203 if (input_info.is_pending) {
204 // A pending buffer is already filled with data, no need to copy it again.
205 DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer (pending):"
206 << " index:" << input_info.buf_index
207 << " pts:" << pending_input_buf_data_.presentation_time
208 << " size:" << pending_input_buf_data_.length;
209
210 input_data = pending_input_buf_data_;
211 } else {
212 input_data = client_->ProvideInputData();
213 }
214
215 // Process this buffer.
216
217 if (input_data.is_eos) {
218 media_codec_->QueueEOS(input_info.buf_index);
219 SetState(STATE_DRAINING);
220
221 // For EOS, the completion callback is called when the EOS arrives at the
222 // output queue.
223 pending_eos_completion_cb_ = input_data.completion_cb;
224 return;
225 }
226
227 media::MediaCodecStatus status = MEDIA_CODEC_OK;
228
229 if (input_data.is_encrypted) {
230 // Note that input_data might not have a valid memory ptr if this is a
231 // re-send of a buffer that was sent before decryption keys arrived.
232
233 DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer:"
234 << " index:" << input_info.buf_index
235 << " pts:" << input_data.presentation_time
236 << " size:" << input_data.length;
237
238 status = media_codec_->QueueSecureInputBuffer(
239 input_info.buf_index, input_data.memory, input_data.length,
240 input_data.key_id, input_data.iv, input_data.subsamples,
241 input_data.presentation_time);
242
243 } else {
244 DVLOG(2) << __FUNCTION__ << ": QueueInputBuffer:"
245 << " index:" << input_info.buf_index
246 << " pts:" << input_data.presentation_time
247 << " size:" << input_data.length;
248
249 status = media_codec_->QueueInputBuffer(
250 input_info.buf_index, input_data.memory, input_data.length,
251 input_data.presentation_time);
252 }
253
254 switch (status) {
255 case MEDIA_CODEC_ERROR:
256 DVLOG(0) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from QueueInputBuffer";
257 input_data.completion_cb.Run(DecodeStatus::DECODE_ERROR);
258 // Transition to the error state after running the completion cb, to keep
259 // it in order if the client chooses to flush its queue.
260 SetState(STATE_ERROR);
261 break;
262
263 case MEDIA_CODEC_NO_KEY:
264 DVLOG(1) << "QueueSecureInputBuffer failed: MEDIA_CODEC_NO_KEY";
265 // Do not call the completion cb here. It will be called when we retry
266 // after getting the key.
267 pending_input_buf_index_ = input_info.buf_index;
268 pending_input_buf_data_ = input_data;
269 // MediaCodec has a copy of the data already. When we call again, be sure
270 // to send in nullptr for the source. Note that the client doesn't
271 // guarantee that the pointer will remain valid after we return anyway.
272 pending_input_buf_data_.memory = nullptr;
273 SetState(STATE_WAITING_FOR_KEY);
274 break;
275
276 case MEDIA_CODEC_OK:
277 input_data.completion_cb.Run(DecodeStatus::OK);
278 break;
279
280 default:
281 NOTREACHED() << "Unknown Queue(Secure)InputBuffer status " << status;
282 input_data.completion_cb.Run(DecodeStatus::DECODE_ERROR);
283 SetState(STATE_ERROR);
284 break;
285 }
286 }
287
288 bool MediaCodecLoop::DequeueOutput() {
289 DVLOG(2) << __FUNCTION__;
290
291 MediaCodecStatus status;
292 OutputBufferInfo out;
293 bool did_work = false;
DaleCurtis 2016/06/08 22:55:01 I'd move this out of here and into DoIOTask then f
liberato (no reviews please) 2016/06/10 21:34:08 Done.
294 do {
295 status = media_codec_->DequeueOutputBuffer(NoWaitTimeout(), &out.buf_index,
296 &out.offset, &out.size, &out.pts,
297 &out.is_eos, &out.is_key_frame);
298
299 switch (status) {
300 case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
301 // Output buffers are replaced in MediaCodecBridge, nothing to do.
302 DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED";
303 did_work = true;
304 break;
305
306 case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
307 DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_OUTPUT_FORMAT_CHANGED";
308 if (!client_->OnOutputFormatChanged())
309 SetState(STATE_ERROR);
310 did_work = (state_ != STATE_ERROR);
311 break;
312
313 case MEDIA_CODEC_OK:
314 // We got the decoded frame.
315 if (out.is_eos) {
316 // Set state STATE_DRAINED after we have received EOS frame at the
317 // output. media_decoder_job.cc says: once output EOS has occurred, we
318 // should not be asked to decode again.
319 DCHECK_EQ(state_, STATE_DRAINING);
320 SetState(STATE_DRAINED);
321
322 DCHECK_NE(out.buf_index, kInvalidBufferIndex);
323 DCHECK(media_codec_);
324
325 media_codec_->ReleaseOutputBuffer(out.buf_index, false);
326
327 // Run the EOS completion callback now, since we deferred it until
328 // the EOS was completely processed.
329 pending_eos_completion_cb_.Run(DecodeStatus::OK);
330 pending_eos_completion_cb_ = DecodeCB();
331
332 client_->OnDecodedEos(out);
333 } else {
334 if (!client_->OnDecodedFrame(out))
335 SetState(STATE_ERROR);
336 }
337
338 did_work = true;
339 break;
340
341 case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
342 // Nothing to do.
343 DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER";
344 break;
345
346 case MEDIA_CODEC_ERROR:
347 DVLOG(0) << __FUNCTION__
348 << ": MEDIA_CODEC_ERROR from DequeueOutputBuffer";
349
350 // Next Decode() will report the error to the pipeline.
351 SetState(STATE_ERROR);
352 break;
353
354 default:
355 NOTREACHED() << "Unknown DequeueOutputBuffer status " << status;
356 // Next Decode() will report the error to the pipeline.
357 SetState(STATE_ERROR);
358 break;
359 }
360 } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER &&
361 status != MEDIA_CODEC_ERROR && !out.is_eos);
362
363 return did_work;
364 }
365
366 void MediaCodecLoop::ManageTimer(bool did_work) {
DaleCurtis 2016/06/08 22:55:01 I think this should actually be refactored out fur
liberato (no reviews please) 2016/06/10 21:34:09 i think that should be part of the next CL, since
367 bool should_be_running = true;
368
369 base::TimeTicks now = base::TimeTicks::Now();
370 if (did_work || idle_time_begin_ == base::TimeTicks()) {
371 idle_time_begin_ = now;
372 } else {
373 // Make sure that we have done work recently enough, else stop the timer.
374 if (now - idle_time_begin_ > IdleTimerTimeout())
375 should_be_running = false;
376 }
377
378 if (should_be_running && !io_timer_.IsRunning()) {
379 io_timer_.Start(FROM_HERE, DecodePollDelay(), this,
380 &MediaCodecLoop::DoIOTask);
381 } else if (!should_be_running && io_timer_.IsRunning()) {
382 io_timer_.Stop();
383 }
384 }
385
386 void MediaCodecLoop::SetState(State new_state) {
387 DVLOG(1) << __FUNCTION__ << ": " << AsString(state_) << "->"
388 << AsString(new_state);
389 if (state_ != new_state && state_ == STATE_ERROR)
DaleCurtis 2016/06/08 22:55:01 This offers no room for deferring errors since it
liberato (no reviews please) 2016/06/10 21:34:09 i think the error deferring logic stays in the cli
390 client_->OnError();
391 state_ = new_state;
392 }
393
394 MediaCodecBridge* MediaCodecLoop::GetCodec() const {
395 return media_codec_.get();
396 }
397
398 #undef RETURN_STRING
DaleCurtis 2016/06/08 22:55:01 No macros, lets avoid this from the beginning :)
Tima Vaisburd 2016/06/08 23:14:20 Dale, what would be your suggestion to implement A
DaleCurtis 2016/06/09 00:30:49 Hmm, I thought there'd be a nice C++ solution to t
liberato (no reviews please) 2016/06/10 21:34:09 done, though i skipped #else since #error.
399 #define RETURN_STRING(x) \
400 case x: \
401 return #x;
402
403 // static
404 const char* MediaCodecLoop::AsString(State state) {
405 switch (state) {
406 RETURN_STRING(STATE_READY);
407 RETURN_STRING(STATE_WAITING_FOR_KEY);
408 RETURN_STRING(STATE_DRAINING);
409 RETURN_STRING(STATE_DRAINED);
410 RETURN_STRING(STATE_ERROR);
411 }
412 NOTREACHED() << "Unknown state " << state;
413 return nullptr;
414 }
415
416 #undef RETURN_STRING
417
418 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698