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

Side by Side Diff: content/common/gpu/media/android_video_decode_accelerator.cc

Issue 11973010: AndroidVDA by using Android's MediaCodec API. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebasing and better NEOBB(). Created 7 years, 10 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 (c) 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.
4
5 #include "content/common/gpu/media/android_video_decode_accelerator.h"
6
7 #include <jni.h>
8
9 #include "base/android/jni_android.h"
10 #include "base/android/scoped_java_ref.h"
11 #include "base/bind.h"
12 #include "base/logging.h"
13 #include "base/message_loop.h"
14 #include "content/common/android/surface_callback.h"
15 #include "content/common/gpu/gpu_channel.h"
16 #include "content/common/gpu/media/gles2_external_texture_copier.h"
17 #include "media/base/bitstream_buffer.h"
18 #include "media/base/limits.h"
19 #include "media/video/picture.h"
20 #include "ui/gl/gl_bindings.h"
21
22 using base::android::MethodID;
23 using base::android::ScopedJavaLocalRef;
24
25 namespace content {
26
27 // XXX: drop the below before submitting.
28 #define LOG_LINE() LOG(INFO) << __FUNCTION__
29
30 // Helper macros for dealing with failure. If |result| evaluates false, emit
31 // |log| to ERROR, register |error| with the decoder, and return.
32 #define RETURN_ON_FAILURE(result, log, error) \
33 do { \
34 if (!(result)) { \
35 DLOG(ERROR) << log; \
36 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( \
37 &AndroidVideoDecodeAccelerator::NotifyError, \
38 base::AsWeakPtr(this), error)); \
39 state_ = ERROR; \
40 return; \
41 } \
42 } while (0)
43
44 // TODO(dwkang): We only need kMaxVideoFrames to pass media stack's prerolling
45 // phase, but 1 is added due to crbug.com/176036. This should be tuned when we
46 // have actual use case.
47 enum { kNumPictureBuffers = media::limits::kMaxVideoFrames + 1 };
48
49 // Max number of bitstreams notified to the client with
50 // NotifyEndOfBitstreamBuffer() before getting output from the bitstream.
51 enum { kMaxBitstreamsNotifiedInAdvance = 32 };
52
53 // static
54 const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay =
55 base::TimeDelta::FromMilliseconds(10);
56
57 AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator(
58 media::VideoDecodeAccelerator::Client* client,
59 const base::Callback<bool(void)>& make_context_current)
60 : client_(client),
61 make_context_current_(make_context_current),
62 codec_(media::MediaCodecBridge::VIDEO_H264),
63 state_(NO_ERROR),
64 surface_texture_id_(-1),
65 picturebuffers_requested_(false),
66 io_task_is_posted_(false),
67 decoder_met_eos_(false),
68 num_bytes_used_in_the_pending_buffer_(0) {
69 LOG_LINE();
70 }
71
72 AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() {
73 LOG_LINE();
74 DCHECK(thread_checker_.CalledOnValidThread());
75 }
76
77 bool AndroidVideoDecodeAccelerator::Initialize(
78 media::VideoCodecProfile profile) {
79 LOG_LINE();
80 DCHECK(!media_codec_);
81 DCHECK(thread_checker_.CalledOnValidThread());
82
83 if (profile == media::VP8PROFILE_MAIN) {
84 codec_ = media::MediaCodecBridge::VIDEO_VP8;
85 } else if (profile >= media::H264PROFILE_MIN &&
86 profile <= media::H264PROFILE_MAX) {
87 codec_ = media::MediaCodecBridge::VIDEO_H264;
88 } else {
89 LOG(ERROR) << "Unsupported profile: " << profile;
90 return false;
91 }
92
93 if (!make_context_current_.Run()) {
94 LOG(ERROR) << "Failed to make this decoder's GL context current.";
95 return false;
96 }
97
98 glGenTextures(1, &surface_texture_id_);
99 glActiveTexture(GL_TEXTURE0);
100 glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_);
101
102 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
103 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
104 glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
105 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
106 glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
107 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
108
109 surface_texture_ = new SurfaceTextureBridge(surface_texture_id_);
110
111 ConfigureMediaCodec();
112
113 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
114 &AndroidVideoDecodeAccelerator::NotifyInitializeDone,
115 base::AsWeakPtr(this)));
116 return true;
117 }
118
119 void AndroidVideoDecodeAccelerator::DoIOTask() {
120 io_task_is_posted_ = false;
121 if (state_ == ERROR) {
122 return;
123 }
124
125 DequeueOutput();
126 QueueInput();
127
128 if (!pending_bitstream_buffers_.empty() ||
129 !free_picture_ids_.empty()) {
130 io_task_is_posted_ = true;
131 MessageLoop::current()->PostDelayedTask(
132 FROM_HERE,
133 base::Bind(
134 &AndroidVideoDecodeAccelerator::DoIOTask, base::AsWeakPtr(this)),
135 kDecodePollDelay);
136 }
137 }
138
139 void AndroidVideoDecodeAccelerator::QueueInput() {
140 if (bitstreams_notified_in_advance_.size() > kMaxBitstreamsNotifiedInAdvance)
Ami GONE FROM CHROMIUM 2013/02/18 17:46:48 Why do you need this business? Isn't it best to sa
dwkang1 2013/02/19 02:14:55 Yes. To be specific, internal buffer lived in the
Ami GONE FROM CHROMIUM 2013/02/19 05:22:49 Hmm. That reading of the spec makes NEOBB pretty
141 return;
142 if (pending_bitstream_buffers_.empty())
143 return;
144
145 int input_buf_index = media_codec_->DequeueInputBuffer(
146 media::MediaCodecBridge::kTimeOutNoWait);
147 if (input_buf_index < 0) {
148 DCHECK_EQ(input_buf_index, media::MediaCodecBridge::INFO_TRY_AGAIN_LATER);
149 return;
150 }
151 media::BitstreamBuffer& bitstream_buffer =
152 pending_bitstream_buffers_.front();
153
154 if (bitstream_buffer.id() == -1) {
155 media_codec_->QueueEOS(input_buf_index);
156 pending_bitstream_buffers_.pop();
157 return;
158 }
159 // Abuse the presentation time argument to propagate the bitstream
160 // buffer ID to the output, so we can report it back to the client in
161 // PictureReady().
162 base::TimeDelta timestamp =
163 base::TimeDelta::FromMicroseconds(bitstream_buffer.id());
164
165 int bytes_written = 0;
166 scoped_ptr<base::SharedMemory> shm(
167 new base::SharedMemory(bitstream_buffer.handle(), true));
168
169 RETURN_ON_FAILURE(shm->Map(bitstream_buffer.size()),
170 "Failed to SharedMemory::Map()",
171 UNREADABLE_INPUT);
172
173 const size_t offset = num_bytes_used_in_the_pending_buffer_;
174 bytes_written = media_codec_->QueueInputBuffer(
175 input_buf_index,
176 static_cast<const uint8*>(shm->memory()) + offset,
177 bitstream_buffer.size() - offset, timestamp);
178 num_bytes_used_in_the_pending_buffer_ += bytes_written;
179 CHECK_LE(num_bytes_used_in_the_pending_buffer_, bitstream_buffer.size());
180
181 if (num_bytes_used_in_the_pending_buffer_ == bitstream_buffer.size()) {
182 num_bytes_used_in_the_pending_buffer_ = 0;
183 pending_bitstream_buffers_.pop();
184
185 // We should call NotifyEndOfBitstreamBuffer(), when no more decoded output
186 // will be returned from the bitstream buffer. However, MediaCodec API is
187 // not enough to guarantee it.
188 // So, here, we calls NotifyEndOfBitstreamBuffer() in advance in order to
189 // keep getting more bitstreams from the client, and throttle them by using
190 // |bitstreams_notified_in_advance_|.
191 // TODO(dwkang): check if there is a way to remove this workaround.
192 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
193 &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
194 base::AsWeakPtr(this), bitstream_buffer.id()));
195 bitstreams_notified_in_advance_.push_back(bitstream_buffer.id());
196 }
197 }
198
199 void AndroidVideoDecodeAccelerator::DequeueOutput() {
200 if (picturebuffers_requested_ && output_picture_buffers_.empty())
201 return;
202
203 if (!output_picture_buffers_.empty() && free_picture_ids_.empty()) {
204 // Don't have any picture buffer to send. Need to wait more.
205 return;
206 }
207
208 bool eos = false;
209 base::TimeDelta timestamp;
210 int32 buf_index = 0;
211 do {
212 int32 offset = 0;
213 int32 size = 0;
214 buf_index = media_codec_->DequeueOutputBuffer(
215 media::MediaCodecBridge::kTimeOutNoWait,
216 &offset, &size, &timestamp, &eos);
217 switch (buf_index) {
218 case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER:
219 return;
220
221 case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: {
222 int32 width, height;
223 media_codec_->GetOutputFormat(&width, &height);
224
225 if (!picturebuffers_requested_) {
226 picturebuffers_requested_ = true;
227 size_ = gfx::Size(width, height);
228 texture_copier_.reset(new Gles2ExternalTextureCopier());
229 texture_copier_->Init(width, height);
230 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
231 &AndroidVideoDecodeAccelerator::RequestPictureBuffers,
232 base::AsWeakPtr(this)));
233 } else {
234 // TODO(dwkang): support the dynamic resolution change.
235 // Currently, we assume that there is no resolution change in the
236 // input stream. So, INFO_OUTPUT_FORMAT_CHANGED should not happen
237 // more than once. However, we allows it if resolution is the same
238 // as the previous one because |media_codec_| can be reset in Reset().
239 RETURN_ON_FAILURE(size_ == gfx::Size(width, height),
240 "Dynamic resolution change is not supported.",
241 PLATFORM_FAILURE);
242 }
243 return;
244 }
245
246 case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED:
247 media_codec_->GetOutputBuffers();
248 break;
249 }
250 } while (buf_index < 0);
251
252 media_codec_->ReleaseOutputBuffer(buf_index, true);
253
254 if (eos) {
255 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
256 &AndroidVideoDecodeAccelerator::NotifyFlushDone,
257 base::AsWeakPtr(this)));
258 decoder_met_eos_ = true;
259 } else {
260 int64 bitstream_buffer_id = timestamp.InMicroseconds();
261 SendCurrentSurfaceToClient(static_cast<int32>(bitstream_buffer_id));
262
263 // Removes ids former or equal than the id from decoder. Note that
264 // |bitstreams_notified_in_advance_| does not mean bitstream ids in decoder
265 // because of frame reordering issue. We just maintain this roughly and use
266 // for the throttling purpose.
267 std::list<int32>::iterator it;
268 for (it = bitstreams_notified_in_advance_.begin();
269 it != bitstreams_notified_in_advance_.end();
270 ++it) {
271 if (*it == bitstream_buffer_id) {
272 bitstreams_notified_in_advance_.erase(
273 bitstreams_notified_in_advance_.begin(), ++it);
274 break;
275 }
276 }
277 }
278 }
279
280 void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient(
281 int32 bitstream_id) {
282 LOG_LINE();
283 DCHECK(thread_checker_.CalledOnValidThread());
284 DCHECK_NE(bitstream_id, -1);
285 DCHECK(!free_picture_ids_.empty());
286
287 RETURN_ON_FAILURE(make_context_current_.Run(),
288 "Failed to make this decoder's GL context current.",
289 PLATFORM_FAILURE);
290
291 int32 picture_buffer_id = free_picture_ids_.front();
292 free_picture_ids_.pop();
293
294 float transfrom_matrix[16];
295 surface_texture_->UpdateTexImage();
296 surface_texture_->GetTransformMatrix(transfrom_matrix);
297
298 OutputBufferMap::const_iterator i =
299 output_picture_buffers_.find(picture_buffer_id);
300 RETURN_ON_FAILURE(i != output_picture_buffers_.end(),
301 "Can't find a PictureBuffer for " << picture_buffer_id,
302 PLATFORM_FAILURE);
303 uint32 picture_buffer_texture_id = i->second.texture_id();
304
305 // Here, we copy |surface_texture_id_| to the picture buffer instead of
306 // setting new texture to |surface_texture_| by calling attachToGLContext()
307 // because:
308 // 1. Once we call detachFrameGLContext(), it deletes the texture previous
309 // attached.
310 // 2. SurfaceTexture requires us to apply a transform matrix when we show
311 // the texture.
312 texture_copier_->Copy(
313 surface_texture_id_, picture_buffer_texture_id, transfrom_matrix);
314
315 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
316 &AndroidVideoDecodeAccelerator::NotifyPictureReady,
317 base::AsWeakPtr(this), media::Picture(picture_buffer_id, bitstream_id)));
318 }
319
320 void AndroidVideoDecodeAccelerator::Decode(
321 const media::BitstreamBuffer& bitstream_buffer) {
322 LOG_LINE();
323 DCHECK(thread_checker_.CalledOnValidThread());
324 if (bitstream_buffer.id() != -1 && bitstream_buffer.size() == 0) {
Ami GONE FROM CHROMIUM 2013/02/18 17:46:48 why would id be -1 here?
dwkang1 2013/02/19 02:14:55 I added this test because Flush() calls this.
325 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
326 &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
327 base::AsWeakPtr(this), bitstream_buffer.id()));
328 return;
329 }
330
331 pending_bitstream_buffers_.push(bitstream_buffer);
332
333 if (!io_task_is_posted_)
334 DoIOTask();
335 }
336
337 void AndroidVideoDecodeAccelerator::AssignPictureBuffers(
338 const std::vector<media::PictureBuffer>& buffers) {
339 LOG_LINE();
340 DCHECK(thread_checker_.CalledOnValidThread());
341 DCHECK(output_picture_buffers_.empty());
342
343 for (size_t i = 0; i < buffers.size(); ++i) {
344 output_picture_buffers_.insert(std::make_pair(buffers[i].id(), buffers[i]));
345 free_picture_ids_.push(buffers[i].id());
346 }
347
348 RETURN_ON_FAILURE(output_picture_buffers_.size() == kNumPictureBuffers,
349 "Invalid picture buffers were passed.",
350 INVALID_ARGUMENT);
351
352 if (!io_task_is_posted_)
353 DoIOTask();
354 }
355
356 void AndroidVideoDecodeAccelerator::ReusePictureBuffer(
357 int32 picture_buffer_id) {
358 DCHECK(thread_checker_.CalledOnValidThread());
359 free_picture_ids_.push(picture_buffer_id);
360
361 if (!io_task_is_posted_)
362 DoIOTask();
363 }
364
365 void AndroidVideoDecodeAccelerator::Flush() {
366 LOG_LINE();
367 DCHECK(thread_checker_.CalledOnValidThread());
368
369 Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0));
370 }
371
372 void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() {
373 DCHECK(surface_texture_.get());
374
375 media_codec_.reset(new media::MediaCodecBridge(codec_));
376
377 JNIEnv* env = base::android::AttachCurrentThread();
378 CHECK(env);
379 ScopedJavaLocalRef<jclass> cls(
380 base::android::GetClass(env, "android/view/Surface"));
381 jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>(
382 env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V");
383 ScopedJavaLocalRef<jobject> j_surface(
384 env, env->NewObject(
385 cls.obj(), constructor,
386 surface_texture_->j_surface_texture().obj()));
387
388 // VDA does not pass the container indicated resolution in the initialization
389 // phase. Here, we set 720p by default.
390 // TODO(dwkang): find out a way to remove the following hard-coded value.
391 media_codec_->StartVideo(codec_, gfx::Size(1280, 720), j_surface.obj());
392 content::ReleaseSurface(j_surface.obj());
393 media_codec_->GetOutputBuffers();
394 }
395
396 void AndroidVideoDecodeAccelerator::Reset() {
397 LOG_LINE();
398 DCHECK(thread_checker_.CalledOnValidThread());
399
400 while(!pending_bitstream_buffers_.empty()) {
401 media::BitstreamBuffer& bitstream_buffer =
402 pending_bitstream_buffers_.front();
403 pending_bitstream_buffers_.pop();
404
405 if (bitstream_buffer.id() != -1) {
406 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
407 &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
408 base::AsWeakPtr(this), bitstream_buffer.id()));
409 }
410 }
411 bitstreams_notified_in_advance_.clear();
412
413 if (!decoder_met_eos_) {
414 media_codec_->Reset();
415 } else {
416 // MediaCodec should be usable after meeting EOS, but it is not on some
417 // devices. b/8125974 To avoid the case, we recreate a new one.
418 media_codec_->Stop();
419 ConfigureMediaCodec();
420 }
421 decoder_met_eos_ = false;
422 num_bytes_used_in_the_pending_buffer_ = 0;
423 state_ = NO_ERROR;
424
425 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
426 &AndroidVideoDecodeAccelerator::NotifyResetDone, base::AsWeakPtr(this)));
427 }
428
429 void AndroidVideoDecodeAccelerator::Destroy() {
430 LOG_LINE();
431 DCHECK(thread_checker_.CalledOnValidThread());
432
433 if (media_codec_) media_codec_->Stop();
434 delete this;
435 }
436
437 void AndroidVideoDecodeAccelerator::NotifyInitializeDone() {
438 client_->NotifyInitializeDone();
439 }
440
441 void AndroidVideoDecodeAccelerator::RequestPictureBuffers() {
442 client_->ProvidePictureBuffers(kNumPictureBuffers, size_, GL_TEXTURE_2D);
443 }
444
445 void AndroidVideoDecodeAccelerator::NotifyPictureReady(
446 const media::Picture& picture) {
447 client_->PictureReady(picture);
448 }
449
450 void AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer(
451 int input_buffer_id) {
452 client_->NotifyEndOfBitstreamBuffer(input_buffer_id);
453 }
454
455 void AndroidVideoDecodeAccelerator::NotifyFlushDone() {
456 client_->NotifyFlushDone();
457 }
458
459 void AndroidVideoDecodeAccelerator::NotifyResetDone() {
460 client_->NotifyResetDone();
461 }
462
463 void AndroidVideoDecodeAccelerator::NotifyError(
464 media::VideoDecodeAccelerator::Error error) {
465 client_->NotifyError(error);
466 }
467
468 } // namespace content
OLDNEW
« no previous file with comments | « content/common/gpu/media/android_video_decode_accelerator.h ('k') | content/common/gpu/media/gles2_external_texture_copier.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698