Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |