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

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: using flush() for reset(). 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/video/picture.h"
19 #include "third_party/angle/include/GLES2/gl2.h"
20 #include "third_party/angle/include/GLES2/gl2ext.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 #undef DCHECK
31 #define DCHECK CHECK
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Oh my! :) You might enjoy https://code.google.com/
dwkang1 2013/02/07 14:01:36 haha. It was part of XXX at L27. :) Thanks for let
32
33 // Helper macros for dealing with failure. If |result| evaluates false, emit
34 // |log| to ERROR, register |error| with the decoder, and return.
35 #define RETURN_ON_FAILURE(result, log, error) \
36 do { \
37 if (!(result)) { \
38 DLOG(ERROR) << log; \
39 MessageLoop::current()->PostTask(FROM_HERE, base::Bind( \
40 &AndroidVideoDecodeAccelerator::NotifyError, \
41 base::AsWeakPtr(this), error)); \
42 state_ = ERROR; \
43 return; \
44 } \
45 } while (0)
46
47 enum { kNumPictureBuffers = 4 };
48
49 // static
50 const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay =
51 base::TimeDelta::FromMilliseconds(10);
52
53 AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator(
54 media::VideoDecodeAccelerator::Client* client,
55 const base::Callback<bool(void)>& make_context_current)
56 : client_(client),
57 make_context_current_(make_context_current),
58 codec_(media::MediaCodecBridge::VIDEO_H264),
59 state_(NO_ERROR),
60 surface_texture_id_(-1),
61 picturebuffers_requested_(false),
62 io_task_is_posted_(false),
63 decoder_met_eos_(false),
64 num_bytes_used_in_the_pending_buffer_(0) {
65 LOG_LINE();
66 }
67
68 AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() {
69 LOG_LINE();
70 DCHECK(thread_checker_.CalledOnValidThread());
71 }
72
73 bool AndroidVideoDecodeAccelerator::Initialize(
74 media::VideoCodecProfile profile) {
75 LOG_LINE();
76 DCHECK(media_codec_ == NULL);
qinmin 2013/02/05 07:43:13 !media_codec_
dwkang1 2013/02/05 11:49:36 Done.
77 DCHECK(thread_checker_.CalledOnValidThread());
78
79 if (profile == media::VP8PROFILE_MAIN) {
80 codec_ = media::MediaCodecBridge::VIDEO_VP8;
81 } else if (profile >= media::H264PROFILE_MIN &&
82 profile <= media::H264PROFILE_MAX) {
83 codec_ = media::MediaCodecBridge::VIDEO_H264;
84 } else {
85 LOG(ERROR) << "Unsupported profile: " << profile;
86 return false;
87 }
88
89 if (!make_context_current_.Run()) {
90 LOG(ERROR) << "Failed to make this decoder's GL context current.";
91 return false;
92 }
93 // XXX: apply the scheme for GL access. http://crbug.com/169433
94 glGenTextures(1, &surface_texture_id_);
95 glActiveTexture(GL_TEXTURE0);
96 glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_);
97
98 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
99 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
100 glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
101 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
102 glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
103 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
104
105 surface_texture_ = new SurfaceTextureBridge(surface_texture_id_);
106
107 ConfigureMediaCodec();
108
109 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
110 &AndroidVideoDecodeAccelerator::NotifyInitializeDone,
111 base::AsWeakPtr(this)));
112 return true;
113 }
114
115 void AndroidVideoDecodeAccelerator::DoIOTask() {
116 io_task_is_posted_ = false;
117 if (state_ == ERROR) {
118 return;
119 }
120
121 DequeueOutput();
122 QueueInput();
123
124 if (!pending_bitstream_buffers_.empty() ||
125 !free_picture_ids_.empty()) {
126 io_task_is_posted_ = true;
127 MessageLoop::current()->PostDelayedTask(
128 FROM_HERE,
129 base::Bind(
130 &AndroidVideoDecodeAccelerator::DoIOTask, base::AsWeakPtr(this)),
131 kDecodePollDelay);
132 }
133 }
134
135 void AndroidVideoDecodeAccelerator::QueueInput() {
136 if (pending_bitstream_buffers_.empty()) {
qinmin 2013/02/05 07:43:13 no {
dwkang1 2013/02/05 08:08:21 afaik, it is okay to have { if it is consistent in
137 return;
138 }
139 int input_buf_index = media_codec_->DequeueInputBuffer(base::TimeDelta());
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 kNoWait or somesuch? Otherwise reading this calls
dwkang1 2013/02/07 14:01:36 Good idea.
140 if (input_buf_index < 0) {
141 DCHECK_EQ(input_buf_index, media::MediaCodecBridge::INFO_TRY_AGAIN_LATER);
142 return;
143 }
144 media::BitstreamBuffer& bitstream_buffer =
145 pending_bitstream_buffers_.front();
146
147 int flags = 0;
148 if (bitstream_buffer.id() == -1) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
149 flags |= media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM;
150 }
151 if (bitstream_buffer.size() > 0) {
152 scoped_ptr<base::SharedMemory> shm(
153 new base::SharedMemory(bitstream_buffer.handle(), true));
154
155 RETURN_ON_FAILURE(shm->Map(bitstream_buffer.size()),
156 "Failed to SharedMemory::Map()",
157 UNREADABLE_INPUT);
158
159
160 const size_t offset = num_bytes_used_in_the_pending_buffer_;
161 num_bytes_used_in_the_pending_buffer_ +=
162 media_codec_->PutToInputBuffer(
163 input_buf_index,
164 static_cast<const uint8*>(shm->memory()) + offset,
165 bitstream_buffer.size() - offset);
166 CHECK_LE(num_bytes_used_in_the_pending_buffer_, bitstream_buffer.size());
167 }
168
169 if (num_bytes_used_in_the_pending_buffer_ == bitstream_buffer.size()) {
170 num_bytes_used_in_the_pending_buffer_ = 0;
171 pending_bitstream_buffers_.pop();
172 }
173
174 // Abuse the presentation time argument to propagate the bitstream
175 // buffer ID to the output, so we can report it back to the client in
176 // PictureReady().
177 base::TimeDelta timestamp =
178 base::TimeDelta::FromMicroseconds(bitstream_buffer.id());
179 media_codec_->QueueInputBuffer(
180 input_buf_index, 0, bitstream_buffer.size(), timestamp, flags);
181
182 if (bitstream_buffer.id() != -1) {
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Yes.
183 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
184 &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 This is still wrong - https://chromiumcodereview.
dwkang1 2013/02/07 14:01:36 Actually, I implemented the scheme you mentioned (
Ami GONE FROM CHROMIUM 2013/02/07 19:40:16 The rationale was for knowing how long metadata ne
185 base::AsWeakPtr(this), bitstream_buffer.id()));
186 }
187 }
188
189 void AndroidVideoDecodeAccelerator::DequeueOutput() {
190 if (picturebuffers_requested_ && output_picture_buffers_.empty()) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
191 return;
192 }
193 if (!output_picture_buffers_.empty() && free_picture_ids_.empty()) {
194 // Don't have any picture buffer to send. Need to wait more.
195 return;
196 }
197
198 int32 flag = 0;
199 base::TimeDelta timestamp;
200 int32 buf_index = 0;
201 do {
202 int32 offset = 0;
203 int32 size = 0;
204 buf_index = media_codec_->DequeueOutputBuffer(
205 base::TimeDelta(), &offset, &size, timestamp, &flag);
206 switch (buf_index) {
207 case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER:
208 return;
209
210 case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: {
211 int32 unused_color_format, width, height;
212 media_codec_->GetOutputFormat(&unused_color_format, &width, &height);
213
214 if (!picturebuffers_requested_) {
215 picturebuffers_requested_ = true;
216 size_ = gfx::Size(width, height);
217 texture_copier_.reset(new Gles2ExternalTextureCopier());
218 texture_copier_->Init(width, height);
219 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
220 &AndroidVideoDecodeAccelerator::RequestPictureBuffers,
221 base::AsWeakPtr(this)));
222
223 } else {
224 // TODO(dwkang): support the dynamic resolution change.
225 RETURN_ON_FAILURE(size_ == gfx::Size(width, height),
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 So, what if this == is true? Ignore changes in co
dwkang1 2013/02/07 14:01:36 In the decoder case, only width and height matter.
226 "Dynamic resolution change is not supported.",
227 PLATFORM_FAILURE);
228 }
229 return;
230 }
231
232 case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED:
233 media_codec_->GetOutputBuffers();
234 break;
235 }
236 } while (buf_index < 0);
237
238 if (flag & media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM) {
239 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
240 &AndroidVideoDecodeAccelerator::NotifyFlushDone, base::AsWeakPtr(this))) ;
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 80-cols
dwkang1 2013/02/07 14:01:36 Done.
241 }
242
243 media_codec_->ReleaseOutputBuffer(buf_index, true);
244
245 int64 bitstream_buffer_id = timestamp.InMicroseconds();
246 if (bitstream_buffer_id != -1) {
qinmin 2013/02/05 07:43:13 ditto here and below
dwkang1 2013/02/05 11:49:36 Done.
247 SendCurrentSurfaceToClient(static_cast<int32>(bitstream_buffer_id));
248 } else {
249 decoder_met_eos_ = true;
250 }
251 }
252
253 void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient(
254 int32 bitstream_id) {
255 LOG_LINE();
256 DCHECK(thread_checker_.CalledOnValidThread());
257 DCHECK_NE(bitstream_id, -1);
258 DCHECK(!free_picture_ids_.empty());
259
260 int32 picture_buffer_id = free_picture_ids_.front();
261 free_picture_ids_.pop();
262
263 RETURN_ON_FAILURE(make_context_current_.Run(),
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Do this before the pop above?
dwkang1 2013/02/07 14:01:36 Done.
264 "Failed to make this decoder's GL context current.",
265 PLATFORM_FAILURE);
266
267 float transfrom_matrix[16];
268 surface_texture_->UpdateTexImage();
269 surface_texture_->GetTransformMatrix(transfrom_matrix);
270
271 OutputBufferMap::const_iterator i =
272 output_picture_buffers_.find(picture_buffer_id);
273 RETURN_ON_FAILURE(i != output_picture_buffers_.end(),
274 "Can't find a PictureBuffer for " << picture_buffer_id,
275 PLATFORM_FAILURE);
276 uint32 picture_buffer_texture_id = i->second.texture_id();
277
278 // Here, we copy |surface_texture_id_| to the picture buffer instead of
279 // setting new texture to |surface_texture_| by calling attachToGLContext()
280 // because:
281 // 1. Once we call detachFrameGLContext(), it deletes the texture previous
282 // attached.
283 // 2. SurfaceTexture requires us to apply a transform matrix when we show
284 // the texture.
285 texture_copier_->Copy(
286 surface_texture_id_, picture_buffer_texture_id, transfrom_matrix);
287
288 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
289 &AndroidVideoDecodeAccelerator::NotifyPictureReady,
290 base::AsWeakPtr(this), media::Picture(picture_buffer_id, bitstream_id)));
291 }
292
293 void AndroidVideoDecodeAccelerator::Decode(
294 const media::BitstreamBuffer& bitstream_buffer) {
295 LOG_LINE();
296 DCHECK(thread_checker_.CalledOnValidThread());
297 pending_bitstream_buffers_.push(bitstream_buffer);
298
299 if (!io_task_is_posted_) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
300 DoIOTask();
301 }
302 }
303
304 void AndroidVideoDecodeAccelerator::AssignPictureBuffers(
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Does this need to DoIOTask as well? (not sure, it
dwkang1 2013/02/07 14:01:36 Yes, it does.
305 const std::vector<media::PictureBuffer>& buffers) {
306 LOG_LINE();
307 DCHECK(thread_checker_.CalledOnValidThread());
308 DCHECK(output_picture_buffers_.empty());
309
310 for (size_t i = 0; i < buffers.size(); ++i) {
311 output_picture_buffers_.insert(std::make_pair(buffers[i].id(), buffers[i]));
312 free_picture_ids_.push(buffers[i].id());
313 }
314
315 RETURN_ON_FAILURE(output_picture_buffers_.size() == kNumPictureBuffers,
316 "Invalid picture buffers were passed.",
317 INVALID_ARGUMENT);
318 }
319
320 void AndroidVideoDecodeAccelerator::ReusePictureBuffer(
321 int32 picture_buffer_id) {
322 DCHECK(thread_checker_.CalledOnValidThread());
323 free_picture_ids_.push(picture_buffer_id);
324
325 if (!io_task_is_posted_) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
326 DoIOTask();
327 }
328 }
329
330 void AndroidVideoDecodeAccelerator::Flush() {
331 LOG_LINE();
332 DCHECK(thread_checker_.CalledOnValidThread());
333
334 Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0));
335 }
336
337 void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() {
338 DCHECK(surface_texture_.get());
339
340 media_codec_.reset(new media::MediaCodecBridge(codec_));
341
342 JNIEnv* env = base::android::AttachCurrentThread();
343 CHECK(env);
344 ScopedJavaLocalRef<jclass> cls(
345 base::android::GetClass(env, "android/view/Surface"));
346 jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>(
347 env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V");
348 ScopedJavaLocalRef<jobject> j_surface(
349 env, env->NewObject(
350 cls.obj(), constructor,
351 surface_texture_->j_surface_texture().obj()));
352
353 // VDA does not pass the container indicated resolution in the initialization
354 // phase. Here, we set 1080p by default.
355 // TODO(dwkang): find out a way to remove the following hard-coded value.
356 media_codec_->StartVideo(
357 codec_, gfx::Size(1920, 1080), j_surface.obj());
qinmin 2013/02/05 07:43:13 why 1080p? most android devices are 720p at most
dwkang1 2013/02/05 11:49:36 Changed to 720p.
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 to ensure we don't hide bugs, can we miminize this
dwkang1 2013/02/07 14:01:36 I tried from 16x16 ~ 256x256 because 16 ~ 128 show
Ami GONE FROM CHROMIUM 2013/02/07 19:40:16 THe only problem with container-specified resoluti
358 content::ReleaseSurface(j_surface.obj());
359
360 media_codec_->GetInputBuffers();
361 media_codec_->GetOutputBuffers();
362 }
363
364 void AndroidVideoDecodeAccelerator::Reset() {
365 LOG_LINE();
366 DCHECK(thread_checker_.CalledOnValidThread());
367
368 while(!pending_bitstream_buffers_.empty()) {
369 media::BitstreamBuffer& bitstream_buffer =
370 pending_bitstream_buffers_.front();
371 pending_bitstream_buffers_.pop();
372
373 if (bitstream_buffer.id() != -1) {
374 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
375 &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
376 base::AsWeakPtr(this), bitstream_buffer.id()));
377 }
378 }
379
380 if (!decoder_met_eos_) {
381 media_codec_->Flush();
382 } else {
383 // MediaCodec should be usable after meeting EOS, but it is not on some
384 // devices. To avoid the case, we recreate a new one.
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 This sort of comment should really have a pointer
dwkang1 2013/02/07 14:01:36 bug id is added.
385 media_codec_->Stop();
386 ConfigureMediaCodec();
387 }
388 decoder_met_eos_ = false;
389 num_bytes_used_in_the_pending_buffer_ = 0;
390 state_ = NO_ERROR;
391
392 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
393 &AndroidVideoDecodeAccelerator::NotifyResetDone, base::AsWeakPtr(this)));
394 }
395
396 void AndroidVideoDecodeAccelerator::Destroy() {
397 LOG_LINE();
398 DCHECK(thread_checker_.CalledOnValidThread());
399 delete this;
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Do you want to stop the codec first?
dwkang1 2013/02/07 14:01:36 Done.
400 }
401
402 void AndroidVideoDecodeAccelerator::NotifyInitializeDone() {
403 client_->NotifyInitializeDone();
404 }
405
406 void AndroidVideoDecodeAccelerator::RequestPictureBuffers() {
407 client_->ProvidePictureBuffers(kNumPictureBuffers, size_, GL_TEXTURE_2D);
408 }
409
410 void AndroidVideoDecodeAccelerator::NotifyPictureReady(
411 const media::Picture& picture) {
412 client_->PictureReady(picture);
413 }
414
415 void AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer(
416 int input_buffer_id) {
417 client_->NotifyEndOfBitstreamBuffer(input_buffer_id);
418 }
419
420 void AndroidVideoDecodeAccelerator::NotifyFlushDone() {
421 client_->NotifyFlushDone();
422 }
423
424 void AndroidVideoDecodeAccelerator::NotifyResetDone() {
425 client_->NotifyResetDone();
426 }
427
428 void AndroidVideoDecodeAccelerator::NotifyError(
429 media::VideoDecodeAccelerator::Error error) {
430 client_->NotifyError(error);
431 }
432
433 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698