Chromium Code Reviews| OLD | NEW |
|---|---|
| (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/debug/trace_event.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/stl_util.h" | |
| 15 #include "base/string_util.h" | |
| 16 #include "content/common/android/surface_callback.h" | |
| 17 #include "content/common/gpu/gpu_channel.h" | |
| 18 #include "content/common/gpu/media/gles2_external_texture_copier.h" | |
| 19 #include "media/base/android/media_codec_bridge.h" | |
| 20 #include "media/base/bitstream_buffer.h" | |
| 21 #include "media/video/picture.h" | |
| 22 #include "third_party/angle/include/GLES2/gl2.h" | |
| 23 #include "third_party/angle/include/GLES2/gl2ext.h" | |
| 24 | |
| 25 using base::android::MethodID; | |
| 26 using base::android::ScopedJavaLocalRef; | |
| 27 | |
| 28 namespace content { | |
| 29 | |
| 30 #define LOG_LINE() VLOG(1) << __FUNCTION__ | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
drop before submitting?
dwkang1
2013/01/28 14:54:30
Marked with XXX.
| |
| 31 | |
| 32 enum { kNumPictureBuffers = 4 }; | |
| 33 | |
| 34 // TODO(dwkang) : For now, we are using very short timeouts for dequeueing | |
| 35 // buffers in order to prevent dequeueing for input from blocking it for output, | |
| 36 // and vice versa. Decoupling input and output procedure and using a longer | |
| 37 // timeout value may improve performance. | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
See my comment on the bridge class; I hope you can
dwkang1
2013/01/28 14:54:30
Done.
| |
| 38 enum { kDequeueInputBufferTimeOutUs = 10 }; | |
| 39 enum { kDequeueOutputBufferTimeOutUs = 10 }; | |
| 40 | |
| 41 // static | |
| 42 const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay = | |
| 43 base::TimeDelta::FromMilliseconds(10); | |
| 44 | |
| 45 AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator( | |
| 46 media::VideoDecodeAccelerator::Client* client, | |
| 47 const base::Callback<bool(void)>& make_context_current) | |
| 48 : message_loop_(MessageLoop::current()), | |
| 49 client_(client), | |
| 50 make_context_current_(make_context_current), | |
| 51 codec_(UNKNOWN), | |
| 52 state_(NO_ERROR), | |
| 53 surface_texture_id_(0), | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
-1 to be more obvious?
dwkang1
2013/01/28 14:54:30
Done.
| |
| 54 picturebuffer_requested_(false), | |
| 55 color_format_(0), | |
| 56 width_(0), | |
| 57 height_(0), | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
should be able to drop once using gfx::Size (which
dwkang1
2013/01/28 14:54:30
Removed.
| |
| 58 current_bitstream_id_(-1) { | |
| 59 LOG_LINE(); | |
| 60 } | |
| 61 | |
| 62 AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() { | |
| 63 LOG_LINE(); | |
| 64 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 65 } | |
| 66 | |
| 67 bool AndroidVideoDecodeAccelerator::Initialize( | |
| 68 media::VideoCodecProfile profile) { | |
| 69 LOG_LINE(); | |
| 70 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 71 | |
| 72 if (profile == media::VP8PROFILE_MAIN) { | |
| 73 codec_ = VP8; | |
| 74 } else if (profile >= media::H264PROFILE_MIN | |
| 75 && profile <= media::H264PROFILE_MAX) { | |
| 76 codec_ = H264; | |
| 77 }else { | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
missing space
dwkang1
2013/01/28 14:54:30
Done.
| |
| 78 LOG(ERROR) << "Unsupported profile: " << profile; | |
| 79 return false; | |
| 80 } | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Once you pass profile to ConfigureMediaCodec below
dwkang1
2013/01/28 14:54:30
codec_ is used when re-configuring MediaCodec.
| |
| 81 | |
| 82 if (media_codec_ == NULL) { | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
How could this *not* be NULL?
dwkang1
2013/01/28 14:54:30
Changed to DCHECK.
| |
| 83 if (!make_context_current_.Run()) { | |
| 84 LOG(ERROR) << "Failed to make this decoder's GL context current."; | |
| 85 return false; | |
| 86 } | |
| 87 glGenTextures(1, &surface_texture_id_); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Please make sure this file follows the (brand-new)
dwkang1
2013/01/28 14:54:30
Let me address this issue once we finalize how we
| |
| 88 glActiveTexture(GL_TEXTURE0); | |
| 89 glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_); | |
| 90 | |
| 91 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
| 92 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
| 93 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
| 94 GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
| 95 glTexParameteri(GL_TEXTURE_EXTERNAL_OES, | |
| 96 GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
| 97 | |
| 98 surface_texture_ = new SurfaceTextureBridge(surface_texture_id_); | |
| 99 | |
| 100 ConfigureMediaCodec(); | |
| 101 } | |
| 102 | |
| 103 message_loop_->PostTask( | |
| 104 FROM_HERE, | |
| 105 base::Bind( | |
| 106 &AndroidVideoDecodeAccelerator::DoDecode, base::Unretained(this))); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Per below, drop.
dwkang1
2013/01/28 14:54:30
Done.
| |
| 107 | |
| 108 if (client_) | |
| 109 client_->NotifyInitializeDone(); | |
| 110 return true; | |
| 111 } | |
| 112 | |
| 113 void AndroidVideoDecodeAccelerator::DoDecode() { | |
| 114 if (state_ == NO_ERROR) { | |
| 115 QueueInput(); | |
| 116 DequeueOutput(); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Shouldn't you drain the output first, to free up d
dwkang1
2013/01/28 14:54:30
Makes sense. Changed.
| |
| 117 } | |
| 118 | |
| 119 message_loop_->PostDelayedTask( | |
| 120 FROM_HERE, | |
| 121 base::Bind( | |
| 122 &AndroidVideoDecodeAccelerator::DoDecode, base::Unretained(this)), | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Why is Unretained(this) safe here (and also at l.1
dwkang1
2013/01/28 14:54:30
You are right. Changed to base::AsWeakPtr().
| |
| 123 kDecodePollDelay); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Oh my goodness... I didn't realize MediaCodec re
dwkang1
2013/01/28 14:54:30
with 10ms, cost was about 3% of cpu rate.
| |
| 124 } | |
| 125 | |
| 126 void AndroidVideoDecodeAccelerator::QueueInput() { | |
| 127 if (!pending_bitstream_buffers_.empty()) { | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
if you reverse the test you can early-return and d
dwkang1
2013/01/28 14:54:30
Done.
| |
| 128 int input_buf_index = | |
| 129 media_codec_->DequeueInputBuffer(kDequeueInputBufferTimeOutUs); | |
| 130 if (input_buf_index < 0) { | |
| 131 return; | |
| 132 } | |
| 133 media::BitstreamBuffer& bitstream_buffer = | |
| 134 pending_bitstream_buffers_.front(); | |
| 135 pending_bitstream_buffers_.pop(); | |
| 136 | |
| 137 int flags = 0; | |
| 138 if (bitstream_buffer.id() == -1) { | |
| 139 flags |= media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM; | |
| 140 } | |
| 141 if (bitstream_buffer.size() > 0) { | |
| 142 scoped_ptr<base::SharedMemory> shm( | |
| 143 new base::SharedMemory(bitstream_buffer.handle(), true)); | |
| 144 if (!shm->Map(bitstream_buffer.size())) { | |
| 145 LOG(ERROR) << "Failed to SharedMemory::Map()"; | |
| 146 client_->NotifyError(UNREADABLE_INPUT); | |
| 147 state_ = ERROR; | |
| 148 return; | |
| 149 } | |
| 150 media_codec_->PutToInputBuffer( | |
| 151 input_buf_index, | |
| 152 static_cast<const uint8*>(shm->memory()), | |
| 153 bitstream_buffer.size()); | |
| 154 } | |
| 155 // Abuse the presentation time argument to propagate the bitstream | |
| 156 // buffer ID to the output, so we can report it back to the client in | |
| 157 // PictureReady(). | |
| 158 int64 timestamp = bitstream_buffer.id(); | |
| 159 media_codec_->QueueInputBuffer( | |
| 160 input_buf_index, 0, bitstream_buffer.size(), timestamp, flags); | |
| 161 | |
| 162 if (bitstream_buffer.id() != -1) { | |
| 163 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This is wrong; you should only NotifyEndOfBitstrea
dwkang1
2013/01/28 14:54:30
When I wrote this code, I referred OVDA code. In O
Ami GONE FROM CHROMIUM
2013/01/28 19:49:45
See my comment on the next patchset.
| |
| 164 } | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 void AndroidVideoDecodeAccelerator::DequeueOutput() { | |
| 169 if (picturebuffer_requested_ && picture_map_.empty()) { | |
| 170 DLOG(INFO) << "Picture buffers are not ready."; | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
drop
dwkang1
2013/01/28 14:54:30
Done.
| |
| 171 return; | |
| 172 } | |
| 173 if (!picture_map_.empty() && free_picture_ids_.empty()) { | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Drop the picture_map_.empty() clause?
SendCurrentS
dwkang1
2013/01/28 14:54:30
Before picturebuffers is requested, free_picture_i
| |
| 174 // Don't have any picture buffer to send. Need to wait more. | |
| 175 return; | |
| 176 } | |
| 177 | |
| 178 int32 output_offset = 0; | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
this and the other params below don't seem to need
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Declare vars at first use unless there's a reason
dwkang1
2013/01/28 14:54:30
Done.
dwkang1
2013/01/28 14:54:30
Done.
| |
| 179 int32 output_size = 0; | |
| 180 int32 output_flag = 0; | |
| 181 int64 timestamp = 0; | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
bitstream_buffer_id
dwkang1
2013/01/28 14:54:30
Done.
| |
| 182 int32 output_buf_index = 0; | |
| 183 do { | |
| 184 output_buf_index = media_codec_->DequeueOutputBuffer( | |
| 185 kDequeueOutputBufferTimeOutUs, &output_offset, &output_size, | |
| 186 ×tamp, &output_flag); | |
| 187 switch (output_buf_index) { | |
| 188 case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER: | |
| 189 return; | |
| 190 | |
| 191 case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: | |
| 192 media_codec_->GetOutputFormat(&color_format_, &width_, &height_); | |
| 193 DLOG(INFO) << "Output color format: " << color_format_; | |
| 194 DLOG(INFO) << "Output size: " << width_ << "x" << height_; | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Drop these two lines
dwkang1
2013/01/28 14:54:30
Done.
| |
| 195 if (!picturebuffer_requested_) { | |
| 196 picturebuffer_requested_ = true; | |
| 197 texture_copier_.reset(new Gles2ExternalTextureCopier()); | |
| 198 texture_copier_->Init(width_, height_); | |
| 199 client_->ProvidePictureBuffers( | |
| 200 kNumPictureBuffers, | |
| 201 gfx::Size(width_, height_), | |
| 202 GL_TEXTURE_2D); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
If you requested a different texture target could
dwkang1
2013/01/28 14:54:30
Actually, using SurfaceTexure.attachToGLContext()
| |
| 203 } | |
| 204 // TODO(dwkang): support the dynamic resolution change. | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
set state to ERROR and NotifyError?
dwkang1
2013/01/28 14:54:30
Done.
| |
| 205 return; | |
| 206 | |
| 207 case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED: | |
| 208 media_codec_->GetOutputBuffers(); | |
| 209 break; | |
| 210 } | |
| 211 } while (output_buf_index < 0); | |
| 212 | |
| 213 if (output_flag & media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM) { | |
| 214 if (client_) { | |
| 215 client_->NotifyFlushDone(); | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 media_codec_->ReleaseOutputBuffer(output_buf_index, true); | |
| 220 current_bitstream_id_ = static_cast<int32>(timestamp); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This doesn't need to be a class member; you can ju
dwkang1
2013/01/28 14:54:30
Done.
| |
| 221 if (current_bitstream_id_ != -1) { | |
| 222 SendCurrentSurfaceToClient(); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient() { | |
| 227 LOG_LINE(); | |
| 228 | |
| 229 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 230 DCHECK_NE(current_bitstream_id_, -1); | |
| 231 DCHECK(!free_picture_ids_.empty()); | |
| 232 | |
| 233 int32 picture_buffer_id = free_picture_ids_.front(); | |
| 234 free_picture_ids_.pop(); | |
| 235 | |
| 236 if (!make_context_current_.Run()) { | |
| 237 LOG(ERROR) << "Failed to make this decoder's GL context current."; | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Shouldn't this set the state to ERROR and also Not
dwkang1
2013/01/28 14:54:30
Done.
| |
| 238 return; | |
| 239 } | |
| 240 | |
| 241 float mtx[16]; | |
| 242 surface_texture_->UpdateTexImage(); | |
| 243 surface_texture_->GetTransformMatrix(mtx); | |
| 244 CopyCurrentFrameToPictureBuffer(picture_buffer_id, mtx); | |
| 245 | |
| 246 client_->PictureReady( | |
| 247 media::Picture(picture_buffer_id, current_bitstream_id_)); | |
| 248 current_bitstream_id_ = -1; | |
| 249 } | |
| 250 | |
| 251 void AndroidVideoDecodeAccelerator::CopyCurrentFrameToPictureBuffer( | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
single call-site + simple impl == inline into call
dwkang1
2013/01/28 14:54:30
I think it depends on how we will define "simple",
| |
| 252 int32 picture_buffer_id, float transfrom_matrix[16]) { | |
| 253 PictureMap::const_iterator i = picture_map_.find(picture_buffer_id); | |
| 254 if (i == picture_map_.end()) { | |
| 255 LOG(ERROR) << "Can't find a PuctureBuffer for " << picture_buffer_id; | |
| 256 return; | |
| 257 } | |
| 258 uint32 picture_buffer_texture_id = i->second.texture_id(); | |
| 259 texture_copier_->Copy(surface_texture_id_, GL_TEXTURE_EXTERNAL_OES, | |
| 260 transfrom_matrix, | |
| 261 picture_buffer_texture_id, GL_TEXTURE_2D); | |
| 262 } | |
| 263 | |
| 264 void AndroidVideoDecodeAccelerator::Decode( | |
| 265 const media::BitstreamBuffer& bitstream_buffer) { | |
| 266 LOG_LINE(); | |
| 267 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 268 if (!client_) { | |
| 269 return; | |
| 270 } | |
| 271 pending_bitstream_buffers_.push(bitstream_buffer); | |
| 272 } | |
| 273 | |
| 274 void AndroidVideoDecodeAccelerator::AssignPictureBuffers( | |
| 275 const std::vector<media::PictureBuffer>& buffers) { | |
| 276 LOG_LINE(); | |
| 277 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 278 DCHECK(picture_map_.empty()); | |
| 279 | |
| 280 for (size_t i = 0; i < buffers.size(); ++i) { | |
| 281 picture_map_.insert(std::make_pair(buffers[i].id(), buffers[i])); | |
| 282 free_picture_ids_.push(buffers[i].id()); | |
| 283 } | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Add:
if (picture_map_.size() != kNumPictureBuffers
dwkang1
2013/01/28 14:54:30
Done.
| |
| 284 } | |
| 285 | |
| 286 void AndroidVideoDecodeAccelerator::ReusePictureBuffer( | |
| 287 int32 picture_buffer_id) { | |
| 288 LOG_LINE(); | |
| 289 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 290 free_picture_ids_.push(picture_buffer_id); | |
| 291 } | |
| 292 | |
| 293 void AndroidVideoDecodeAccelerator::Flush() { | |
| 294 LOG_LINE(); | |
| 295 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 296 | |
| 297 Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0)); | |
| 298 } | |
| 299 | |
| 300 void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() { | |
| 301 DCHECK(surface_texture_.get()); | |
| 302 DCHECK(codec_ == H264 || codec_ == VP8); | |
| 303 | |
| 304 std::string mime; | |
| 305 if (codec_ == VP8) { | |
| 306 mime = "video/x-vnd.on2.vp8"; | |
| 307 } else if (codec_ == H264) { | |
| 308 mime = "video/avc"; | |
| 309 } else { | |
| 310 LOG(ERROR) << "Unsupported codec type " << codec_; | |
| 311 NOTREACHED(); | |
| 312 } | |
| 313 media_codec_.reset(new media::MediaCodecBridge(mime)); | |
| 314 | |
| 315 JNIEnv* env = base::android::AttachCurrentThread(); | |
| 316 CHECK(env); | |
| 317 ScopedJavaLocalRef<jclass> cls( | |
| 318 base::android::GetClass(env, "android/view/Surface")); | |
| 319 jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>( | |
| 320 env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V"); | |
| 321 ScopedJavaLocalRef<jobject> j_surface( | |
| 322 env, env->NewObject( | |
| 323 cls.obj(), constructor, | |
| 324 surface_texture_->j_surface_texture().obj())); | |
| 325 | |
| 326 // VDA does not pass the container indicated resolution in the initialization | |
| 327 // phase. Here, we set 1080p by default. | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This is a mistake that is likely to hide bugs, IMO
dwkang1
2013/01/28 14:54:30
Actually, that was my first try, but it lead to Ex
| |
| 328 media_codec_->ConfigureVideo( | |
| 329 mime, 1920, 1080, NULL, 0, NULL, 0, j_surface.obj()); | |
| 330 content::ReleaseSurface(j_surface.obj()); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
How does this work? Didn't you just ask MediaCode
dwkang1
2013/01/28 14:54:30
If Surface is not registered to the windows manage
| |
| 331 | |
| 332 media_codec_->Start(); | |
| 333 media_codec_->GetInputBuffers(); | |
| 334 media_codec_->GetOutputBuffers(); | |
| 335 } | |
| 336 | |
| 337 void AndroidVideoDecodeAccelerator::Reset() { | |
| 338 LOG_LINE(); | |
| 339 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 340 | |
| 341 while(!pending_bitstream_buffers_.empty()) { | |
| 342 media::BitstreamBuffer& bitstream_buffer = | |
| 343 pending_bitstream_buffers_.front(); | |
| 344 pending_bitstream_buffers_.pop(); | |
| 345 | |
| 346 if (bitstream_buffer.id() != -1) { | |
| 347 client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
I don't think the VDA interface requires this (and
dwkang1
2013/01/28 14:54:30
Actually, I referred OVDA code when I wrote this c
| |
| 348 } | |
| 349 } | |
| 350 media_codec_->Flush(); | |
| 351 media_codec_->Stop(); | |
| 352 media_codec_->Release(); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
Why the Stop/Release/ConfigureMediaCodec dance?
dwkang1
2013/01/28 14:54:30
It's a recommended way to reset MediaCodec. FWIW,
| |
| 353 ConfigureMediaCodec(); | |
| 354 state_ = NO_ERROR; | |
| 355 | |
| 356 if (client_) { | |
| 357 client_->NotifyResetDone(); | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
For complicated reasons, it's not OK to make clien
dwkang1
2013/01/28 14:54:30
Done.
| |
| 358 } | |
| 359 } | |
| 360 | |
| 361 void AndroidVideoDecodeAccelerator::Destroy() { | |
| 362 LOG_LINE(); | |
| 363 DCHECK_EQ(message_loop_, MessageLoop::current()); | |
| 364 } | |
|
Ami GONE FROM CHROMIUM
2013/01/23 01:32:32
This method needs to
delete this;
or else you hav
dwkang1
2013/01/28 14:54:30
Done.
| |
| 365 | |
| 366 } // namespace content | |
| OLD | NEW |