Abort MSP::OnPrefetchDone() if just after MSP::Release(). Let seek and config change...
[chromium-blink-merge.git] / media / base / android / media_decoder_job.cc
blob4558845f325f043a796408b0a341a2271a05199e
1 // Copyright 2013 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.
5 #include "media/base/android/media_decoder_job.h"
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/debug/trace_event.h"
10 #include "base/message_loop/message_loop.h"
11 #include "media/base/android/media_codec_bridge.h"
12 #include "media/base/bind_to_loop.h"
13 #include "media/base/buffers.h"
15 namespace media {
17 // Timeout value for media codec operations. Because the first
18 // DequeInputBuffer() can take about 150 milliseconds, use 250 milliseconds
19 // here. See http://b/9357571.
20 static const int kMediaCodecTimeoutInMilliseconds = 250;
22 MediaDecoderJob::MediaDecoderJob(
23 const scoped_refptr<base::MessageLoopProxy>& decoder_loop,
24 MediaCodecBridge* media_codec_bridge,
25 const base::Closure& request_data_cb)
26 : ui_loop_(base::MessageLoopProxy::current()),
27 decoder_loop_(decoder_loop),
28 media_codec_bridge_(media_codec_bridge),
29 needs_flush_(false),
30 input_eos_encountered_(false),
31 prerolling_(true),
32 weak_this_(this),
33 request_data_cb_(request_data_cb),
34 access_unit_index_(0),
35 input_buf_index_(-1),
36 stop_decode_pending_(false),
37 destroy_pending_(false) {
40 MediaDecoderJob::~MediaDecoderJob() {}
42 void MediaDecoderJob::OnDataReceived(const DemuxerData& data) {
43 DVLOG(1) << __FUNCTION__ << ": " << data.access_units.size() << " units";
44 DCHECK(ui_loop_->BelongsToCurrentThread());
45 DCHECK(!on_data_received_cb_.is_null());
47 TRACE_EVENT_ASYNC_END2(
48 "media", "MediaDecoderJob::RequestData", this,
49 "Data type", data.type == media::DemuxerStream::AUDIO ? "AUDIO" : "VIDEO",
50 "Units read", data.access_units.size());
52 base::Closure done_cb = base::ResetAndReturn(&on_data_received_cb_);
54 if (stop_decode_pending_) {
55 OnDecodeCompleted(MEDIA_CODEC_STOPPED, kNoTimestamp(), 0);
56 return;
59 access_unit_index_ = 0;
60 received_data_ = data;
61 done_cb.Run();
64 void MediaDecoderJob::Prefetch(const base::Closure& prefetch_cb) {
65 DCHECK(ui_loop_->BelongsToCurrentThread());
66 DCHECK(on_data_received_cb_.is_null());
67 DCHECK(decode_cb_.is_null());
69 if (HasData()) {
70 DVLOG(1) << __FUNCTION__ << " : using previously received data";
71 ui_loop_->PostTask(FROM_HERE, prefetch_cb);
72 return;
75 DVLOG(1) << __FUNCTION__ << " : requesting data";
76 RequestData(prefetch_cb);
79 bool MediaDecoderJob::Decode(
80 const base::TimeTicks& start_time_ticks,
81 const base::TimeDelta& start_presentation_timestamp,
82 const DecoderCallback& callback) {
83 DCHECK(decode_cb_.is_null());
84 DCHECK(on_data_received_cb_.is_null());
85 DCHECK(ui_loop_->BelongsToCurrentThread());
87 decode_cb_ = callback;
89 if (!HasData()) {
90 RequestData(base::Bind(&MediaDecoderJob::DecodeNextAccessUnit,
91 base::Unretained(this),
92 start_time_ticks,
93 start_presentation_timestamp));
94 return true;
97 if (DemuxerStream::kConfigChanged ==
98 received_data_.access_units[access_unit_index_].status) {
99 // Clear received data because we need to handle a config change.
100 decode_cb_.Reset();
101 received_data_ = DemuxerData();
102 access_unit_index_ = 0;
103 return false;
106 DecodeNextAccessUnit(start_time_ticks, start_presentation_timestamp);
107 return true;
110 void MediaDecoderJob::StopDecode() {
111 DCHECK(ui_loop_->BelongsToCurrentThread());
112 DCHECK(is_decoding());
113 stop_decode_pending_ = true;
116 void MediaDecoderJob::Flush() {
117 DCHECK(decode_cb_.is_null());
119 // Do nothing, flush when the next Decode() happens.
120 needs_flush_ = true;
121 received_data_ = DemuxerData();
122 input_eos_encountered_ = false;
123 access_unit_index_ = 0;
124 on_data_received_cb_.Reset();
127 void MediaDecoderJob::BeginPrerolling(
128 const base::TimeDelta& preroll_timestamp) {
129 DVLOG(1) << __FUNCTION__ << "(" << preroll_timestamp.InSecondsF() << ")";
130 DCHECK(ui_loop_->BelongsToCurrentThread());
131 DCHECK(!is_decoding());
133 preroll_timestamp_ = preroll_timestamp;
134 prerolling_ = true;
137 void MediaDecoderJob::Release() {
138 DCHECK(ui_loop_->BelongsToCurrentThread());
139 DVLOG(1) << __FUNCTION__;
141 // If the decoder job is not waiting for data, and is still decoding, we
142 // cannot delete the job immediately.
143 destroy_pending_ = on_data_received_cb_.is_null() && is_decoding();
145 request_data_cb_.Reset();
146 on_data_received_cb_.Reset();
147 decode_cb_.Reset();
149 if (destroy_pending_) {
150 DVLOG(1) << __FUNCTION__ << " : delete is pending decode completion";
151 return;
154 delete this;
157 MediaCodecStatus MediaDecoderJob::QueueInputBuffer(const AccessUnit& unit) {
158 DVLOG(1) << __FUNCTION__;
159 DCHECK(decoder_loop_->BelongsToCurrentThread());
160 TRACE_EVENT0("media", __FUNCTION__);
162 int input_buf_index = input_buf_index_;
163 input_buf_index_ = -1;
165 // TODO(xhwang): Hide DequeueInputBuffer() and the index in MediaCodecBridge.
166 if (input_buf_index == -1) {
167 base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(
168 kMediaCodecTimeoutInMilliseconds);
169 MediaCodecStatus status =
170 media_codec_bridge_->DequeueInputBuffer(timeout, &input_buf_index);
171 if (status != MEDIA_CODEC_OK) {
172 DVLOG(1) << "DequeueInputBuffer fails: " << status;
173 return status;
177 // TODO(qinmin): skip frames if video is falling far behind.
178 DCHECK_GE(input_buf_index, 0);
179 if (unit.end_of_stream || unit.data.empty()) {
180 media_codec_bridge_->QueueEOS(input_buf_index);
181 return MEDIA_CODEC_INPUT_END_OF_STREAM;
184 if (unit.key_id.empty() || unit.iv.empty()) {
185 DCHECK(unit.iv.empty() || !unit.key_id.empty());
186 return media_codec_bridge_->QueueInputBuffer(
187 input_buf_index, &unit.data[0], unit.data.size(), unit.timestamp);
190 MediaCodecStatus status = media_codec_bridge_->QueueSecureInputBuffer(
191 input_buf_index,
192 &unit.data[0], unit.data.size(),
193 reinterpret_cast<const uint8*>(&unit.key_id[0]), unit.key_id.size(),
194 reinterpret_cast<const uint8*>(&unit.iv[0]), unit.iv.size(),
195 unit.subsamples.empty() ? NULL : &unit.subsamples[0],
196 unit.subsamples.size(),
197 unit.timestamp);
199 // In case of MEDIA_CODEC_NO_KEY, we must reuse the |input_buf_index_|.
200 // Otherwise MediaDrm will report errors.
201 if (status == MEDIA_CODEC_NO_KEY)
202 input_buf_index_ = input_buf_index;
204 return status;
207 bool MediaDecoderJob::HasData() const {
208 DCHECK(ui_loop_->BelongsToCurrentThread());
209 // When |input_eos_encountered_| is set, |access_units| must not be empty and
210 // |access_unit_index_| must be pointing to an EOS unit. We'll reuse this
211 // unit to flush the decoder until we hit output EOS.
212 DCHECK(!input_eos_encountered_ ||
213 (received_data_.access_units.size() > 0 &&
214 access_unit_index_ < received_data_.access_units.size()))
215 << " (access_units.size(): " << received_data_.access_units.size()
216 << ", access_unit_index_: " << access_unit_index_ << ")";
217 return access_unit_index_ < received_data_.access_units.size() ||
218 input_eos_encountered_;
221 void MediaDecoderJob::RequestData(const base::Closure& done_cb) {
222 DVLOG(1) << __FUNCTION__;
223 DCHECK(ui_loop_->BelongsToCurrentThread());
224 DCHECK(on_data_received_cb_.is_null());
225 DCHECK(!input_eos_encountered_);
227 TRACE_EVENT_ASYNC_BEGIN0("media", "MediaDecoderJob::RequestData", this);
229 received_data_ = DemuxerData();
230 access_unit_index_ = 0;
231 on_data_received_cb_ = done_cb;
233 request_data_cb_.Run();
236 void MediaDecoderJob::DecodeNextAccessUnit(
237 const base::TimeTicks& start_time_ticks,
238 const base::TimeDelta& start_presentation_timestamp) {
239 DCHECK(ui_loop_->BelongsToCurrentThread());
240 DCHECK(!decode_cb_.is_null());
242 // If the first access unit is a config change, request the player to dequeue
243 // the input buffer again so that it can request config data.
244 if (received_data_.access_units[access_unit_index_].status ==
245 DemuxerStream::kConfigChanged) {
246 ui_loop_->PostTask(FROM_HERE,
247 base::Bind(&MediaDecoderJob::OnDecodeCompleted,
248 base::Unretained(this),
249 MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER,
250 kNoTimestamp(),
251 0));
252 return;
255 decoder_loop_->PostTask(FROM_HERE, base::Bind(
256 &MediaDecoderJob::DecodeInternal, base::Unretained(this),
257 received_data_.access_units[access_unit_index_],
258 start_time_ticks, start_presentation_timestamp, needs_flush_,
259 media::BindToLoop(ui_loop_, base::Bind(
260 &MediaDecoderJob::OnDecodeCompleted, base::Unretained(this)))));
261 needs_flush_ = false;
264 void MediaDecoderJob::DecodeInternal(
265 const AccessUnit& unit,
266 const base::TimeTicks& start_time_ticks,
267 const base::TimeDelta& start_presentation_timestamp,
268 bool needs_flush,
269 const MediaDecoderJob::DecoderCallback& callback) {
270 DVLOG(1) << __FUNCTION__;
271 DCHECK(decoder_loop_->BelongsToCurrentThread());
272 TRACE_EVENT0("media", __FUNCTION__);
274 if (needs_flush) {
275 DVLOG(1) << "DecodeInternal needs flush.";
276 input_eos_encountered_ = false;
277 MediaCodecStatus reset_status = media_codec_bridge_->Reset();
278 if (MEDIA_CODEC_OK != reset_status) {
279 callback.Run(reset_status, kNoTimestamp(), 0);
280 return;
284 // For aborted access unit, just skip it and inform the player.
285 if (unit.status == DemuxerStream::kAborted) {
286 // TODO(qinmin): use a new enum instead of MEDIA_CODEC_STOPPED.
287 callback.Run(MEDIA_CODEC_STOPPED, kNoTimestamp(), 0);
288 return;
291 MediaCodecStatus input_status = MEDIA_CODEC_INPUT_END_OF_STREAM;
292 if (!input_eos_encountered_) {
293 input_status = QueueInputBuffer(unit);
294 if (input_status == MEDIA_CODEC_INPUT_END_OF_STREAM) {
295 input_eos_encountered_ = true;
296 } else if (input_status != MEDIA_CODEC_OK) {
297 callback.Run(input_status, kNoTimestamp(), 0);
298 return;
302 int buffer_index = 0;
303 size_t offset = 0;
304 size_t size = 0;
305 base::TimeDelta presentation_timestamp;
306 bool output_eos_encountered = false;
308 base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(
309 kMediaCodecTimeoutInMilliseconds);
311 MediaCodecStatus status = media_codec_bridge_->DequeueOutputBuffer(
312 timeout, &buffer_index, &offset, &size, &presentation_timestamp,
313 &output_eos_encountered);
315 if (status != MEDIA_CODEC_OK) {
316 if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED &&
317 !media_codec_bridge_->GetOutputBuffers()) {
318 status = MEDIA_CODEC_ERROR;
320 callback.Run(status, kNoTimestamp(), 0);
321 return;
324 // TODO(xhwang/qinmin): This logic is correct but strange. Clean it up.
325 if (output_eos_encountered)
326 status = MEDIA_CODEC_OUTPUT_END_OF_STREAM;
327 else if (input_status == MEDIA_CODEC_INPUT_END_OF_STREAM)
328 status = MEDIA_CODEC_INPUT_END_OF_STREAM;
330 // Check whether we need to render the output.
331 // TODO(qinmin): comparing most recently queued input's |unit.timestamp| with
332 // |preroll_timestamp_| is not accurate due to data reordering and possible
333 // input queueing without immediate dequeue when |input_status| !=
334 // |MEDIA_CODEC_OK|. Need to use the |presentation_timestamp| for video, and
335 // use |size| to calculate the timestamp for audio. See
336 // http://crbug.com/310823 and b/11356652.
337 bool render_output = unit.timestamp >= preroll_timestamp_ &&
338 (status != MEDIA_CODEC_OUTPUT_END_OF_STREAM || size != 0u);
339 base::TimeDelta time_to_render;
340 DCHECK(!start_time_ticks.is_null());
341 if (render_output && ComputeTimeToRender()) {
342 time_to_render = presentation_timestamp - (base::TimeTicks::Now() -
343 start_time_ticks + start_presentation_timestamp);
346 if (time_to_render > base::TimeDelta()) {
347 decoder_loop_->PostDelayedTask(
348 FROM_HERE,
349 base::Bind(&MediaDecoderJob::ReleaseOutputBuffer,
350 weak_this_.GetWeakPtr(), buffer_index, size, render_output,
351 base::Bind(callback, status, presentation_timestamp)),
352 time_to_render);
353 return;
356 // TODO(qinmin): The codec is lagging behind, need to recalculate the
357 // |start_presentation_timestamp_| and |start_time_ticks_| in
358 // media_source_player.cc.
359 DVLOG(1) << "codec is lagging behind :" << time_to_render.InMicroseconds();
360 if (render_output) {
361 // The player won't expect a timestamp smaller than the
362 // |start_presentation_timestamp|. However, this could happen due to decoder
363 // errors.
364 presentation_timestamp = std::max(
365 presentation_timestamp, start_presentation_timestamp);
366 } else {
367 presentation_timestamp = kNoTimestamp();
369 ReleaseOutputCompletionCallback completion_callback = base::Bind(
370 callback, status, presentation_timestamp);
371 ReleaseOutputBuffer(buffer_index, size, render_output, completion_callback);
374 void MediaDecoderJob::OnDecodeCompleted(
375 MediaCodecStatus status, const base::TimeDelta& presentation_timestamp,
376 size_t audio_output_bytes) {
377 DCHECK(ui_loop_->BelongsToCurrentThread());
379 if (destroy_pending_) {
380 DVLOG(1) << __FUNCTION__ << " : completing pending deletion";
381 delete this;
382 return;
385 DCHECK(!decode_cb_.is_null());
387 // If output was queued for rendering, then we have completed prerolling.
388 if (presentation_timestamp != kNoTimestamp())
389 prerolling_ = false;
391 switch (status) {
392 case MEDIA_CODEC_OK:
393 case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
394 case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
395 case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
396 case MEDIA_CODEC_OUTPUT_END_OF_STREAM:
397 if (!input_eos_encountered_)
398 access_unit_index_++;
399 break;
401 case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
402 case MEDIA_CODEC_INPUT_END_OF_STREAM:
403 case MEDIA_CODEC_NO_KEY:
404 case MEDIA_CODEC_STOPPED:
405 case MEDIA_CODEC_ERROR:
406 // Do nothing.
407 break;
410 stop_decode_pending_ = false;
411 base::ResetAndReturn(&decode_cb_).Run(status, presentation_timestamp,
412 audio_output_bytes);
415 } // namespace media