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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: content/common/gpu/media/android_video_decode_accelerator.cc
diff --git a/content/common/gpu/media/android_video_decode_accelerator.cc b/content/common/gpu/media/android_video_decode_accelerator.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f23c26eb5041ffb09c7cdf4f33196dd48c3643e8
--- /dev/null
+++ b/content/common/gpu/media/android_video_decode_accelerator.cc
@@ -0,0 +1,433 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/common/gpu/media/android_video_decode_accelerator.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "content/common/android/surface_callback.h"
+#include "content/common/gpu/gpu_channel.h"
+#include "content/common/gpu/media/gles2_external_texture_copier.h"
+#include "media/base/bitstream_buffer.h"
+#include "media/video/picture.h"
+#include "third_party/angle/include/GLES2/gl2.h"
+#include "third_party/angle/include/GLES2/gl2ext.h"
+
+using base::android::MethodID;
+using base::android::ScopedJavaLocalRef;
+
+namespace content {
+
+// XXX: drop the below before submitting.
+#define LOG_LINE() LOG(INFO) << __FUNCTION__
+
+#undef DCHECK
+#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
+
+// Helper macros for dealing with failure. If |result| evaluates false, emit
+// |log| to ERROR, register |error| with the decoder, and return.
+#define RETURN_ON_FAILURE(result, log, error) \
+ do { \
+ if (!(result)) { \
+ DLOG(ERROR) << log; \
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind( \
+ &AndroidVideoDecodeAccelerator::NotifyError, \
+ base::AsWeakPtr(this), error)); \
+ state_ = ERROR; \
+ return; \
+ } \
+ } while (0)
+
+enum { kNumPictureBuffers = 4 };
+
+// static
+const base::TimeDelta AndroidVideoDecodeAccelerator::kDecodePollDelay =
+ base::TimeDelta::FromMilliseconds(10);
+
+AndroidVideoDecodeAccelerator::AndroidVideoDecodeAccelerator(
+ media::VideoDecodeAccelerator::Client* client,
+ const base::Callback<bool(void)>& make_context_current)
+ : client_(client),
+ make_context_current_(make_context_current),
+ codec_(media::MediaCodecBridge::VIDEO_H264),
+ state_(NO_ERROR),
+ surface_texture_id_(-1),
+ picturebuffers_requested_(false),
+ io_task_is_posted_(false),
+ decoder_met_eos_(false),
+ num_bytes_used_in_the_pending_buffer_(0) {
+ LOG_LINE();
+}
+
+AndroidVideoDecodeAccelerator::~AndroidVideoDecodeAccelerator() {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+bool AndroidVideoDecodeAccelerator::Initialize(
+ media::VideoCodecProfile profile) {
+ LOG_LINE();
+ DCHECK(media_codec_ == NULL);
qinmin 2013/02/05 07:43:13 !media_codec_
dwkang1 2013/02/05 11:49:36 Done.
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (profile == media::VP8PROFILE_MAIN) {
+ codec_ = media::MediaCodecBridge::VIDEO_VP8;
+ } else if (profile >= media::H264PROFILE_MIN &&
+ profile <= media::H264PROFILE_MAX) {
+ codec_ = media::MediaCodecBridge::VIDEO_H264;
+ } else {
+ LOG(ERROR) << "Unsupported profile: " << profile;
+ return false;
+ }
+
+ if (!make_context_current_.Run()) {
+ LOG(ERROR) << "Failed to make this decoder's GL context current.";
+ return false;
+ }
+ // XXX: apply the scheme for GL access. http://crbug.com/169433
+ glGenTextures(1, &surface_texture_id_);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, surface_texture_id_);
+
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
+ GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
+ GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ surface_texture_ = new SurfaceTextureBridge(surface_texture_id_);
+
+ ConfigureMediaCodec();
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &AndroidVideoDecodeAccelerator::NotifyInitializeDone,
+ base::AsWeakPtr(this)));
+ return true;
+}
+
+void AndroidVideoDecodeAccelerator::DoIOTask() {
+ io_task_is_posted_ = false;
+ if (state_ == ERROR) {
+ return;
+ }
+
+ DequeueOutput();
+ QueueInput();
+
+ if (!pending_bitstream_buffers_.empty() ||
+ !free_picture_ids_.empty()) {
+ io_task_is_posted_ = true;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(
+ &AndroidVideoDecodeAccelerator::DoIOTask, base::AsWeakPtr(this)),
+ kDecodePollDelay);
+ }
+}
+
+void AndroidVideoDecodeAccelerator::QueueInput() {
+ 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
+ return;
+ }
+ 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.
+ if (input_buf_index < 0) {
+ DCHECK_EQ(input_buf_index, media::MediaCodecBridge::INFO_TRY_AGAIN_LATER);
+ return;
+ }
+ media::BitstreamBuffer& bitstream_buffer =
+ pending_bitstream_buffers_.front();
+
+ int flags = 0;
+ if (bitstream_buffer.id() == -1) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
+ flags |= media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM;
+ }
+ if (bitstream_buffer.size() > 0) {
+ scoped_ptr<base::SharedMemory> shm(
+ new base::SharedMemory(bitstream_buffer.handle(), true));
+
+ RETURN_ON_FAILURE(shm->Map(bitstream_buffer.size()),
+ "Failed to SharedMemory::Map()",
+ UNREADABLE_INPUT);
+
+
+ const size_t offset = num_bytes_used_in_the_pending_buffer_;
+ num_bytes_used_in_the_pending_buffer_ +=
+ media_codec_->PutToInputBuffer(
+ input_buf_index,
+ static_cast<const uint8*>(shm->memory()) + offset,
+ bitstream_buffer.size() - offset);
+ CHECK_LE(num_bytes_used_in_the_pending_buffer_, bitstream_buffer.size());
+ }
+
+ if (num_bytes_used_in_the_pending_buffer_ == bitstream_buffer.size()) {
+ num_bytes_used_in_the_pending_buffer_ = 0;
+ pending_bitstream_buffers_.pop();
+ }
+
+ // Abuse the presentation time argument to propagate the bitstream
+ // buffer ID to the output, so we can report it back to the client in
+ // PictureReady().
+ base::TimeDelta timestamp =
+ base::TimeDelta::FromMicroseconds(bitstream_buffer.id());
+ media_codec_->QueueInputBuffer(
+ input_buf_index, 0, bitstream_buffer.size(), timestamp, flags);
+
+ if (bitstream_buffer.id() != -1) {
Ami GONE FROM CHROMIUM 2013/02/05 20:24:33 Yes.
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &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
+ base::AsWeakPtr(this), bitstream_buffer.id()));
+ }
+}
+
+void AndroidVideoDecodeAccelerator::DequeueOutput() {
+ if (picturebuffers_requested_ && output_picture_buffers_.empty()) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
+ return;
+ }
+ if (!output_picture_buffers_.empty() && free_picture_ids_.empty()) {
+ // Don't have any picture buffer to send. Need to wait more.
+ return;
+ }
+
+ int32 flag = 0;
+ base::TimeDelta timestamp;
+ int32 buf_index = 0;
+ do {
+ int32 offset = 0;
+ int32 size = 0;
+ buf_index = media_codec_->DequeueOutputBuffer(
+ base::TimeDelta(), &offset, &size, timestamp, &flag);
+ switch (buf_index) {
+ case media::MediaCodecBridge::INFO_TRY_AGAIN_LATER:
+ return;
+
+ case media::MediaCodecBridge::INFO_OUTPUT_FORMAT_CHANGED: {
+ int32 unused_color_format, width, height;
+ media_codec_->GetOutputFormat(&unused_color_format, &width, &height);
+
+ if (!picturebuffers_requested_) {
+ picturebuffers_requested_ = true;
+ size_ = gfx::Size(width, height);
+ texture_copier_.reset(new Gles2ExternalTextureCopier());
+ texture_copier_->Init(width, height);
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &AndroidVideoDecodeAccelerator::RequestPictureBuffers,
+ base::AsWeakPtr(this)));
+
+ } else {
+ // TODO(dwkang): support the dynamic resolution change.
+ 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.
+ "Dynamic resolution change is not supported.",
+ PLATFORM_FAILURE);
+ }
+ return;
+ }
+
+ case media::MediaCodecBridge::INFO_OUTPUT_BUFFERS_CHANGED:
+ media_codec_->GetOutputBuffers();
+ break;
+ }
+ } while (buf_index < 0);
+
+ if (flag & media::MediaCodecBridge::BUFFER_FLAG_END_OF_STREAM) {
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &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.
+ }
+
+ media_codec_->ReleaseOutputBuffer(buf_index, true);
+
+ int64 bitstream_buffer_id = timestamp.InMicroseconds();
+ if (bitstream_buffer_id != -1) {
qinmin 2013/02/05 07:43:13 ditto here and below
dwkang1 2013/02/05 11:49:36 Done.
+ SendCurrentSurfaceToClient(static_cast<int32>(bitstream_buffer_id));
+ } else {
+ decoder_met_eos_ = true;
+ }
+}
+
+void AndroidVideoDecodeAccelerator::SendCurrentSurfaceToClient(
+ int32 bitstream_id) {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(bitstream_id, -1);
+ DCHECK(!free_picture_ids_.empty());
+
+ int32 picture_buffer_id = free_picture_ids_.front();
+ free_picture_ids_.pop();
+
+ 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.
+ "Failed to make this decoder's GL context current.",
+ PLATFORM_FAILURE);
+
+ float transfrom_matrix[16];
+ surface_texture_->UpdateTexImage();
+ surface_texture_->GetTransformMatrix(transfrom_matrix);
+
+ OutputBufferMap::const_iterator i =
+ output_picture_buffers_.find(picture_buffer_id);
+ RETURN_ON_FAILURE(i != output_picture_buffers_.end(),
+ "Can't find a PictureBuffer for " << picture_buffer_id,
+ PLATFORM_FAILURE);
+ uint32 picture_buffer_texture_id = i->second.texture_id();
+
+ // Here, we copy |surface_texture_id_| to the picture buffer instead of
+ // setting new texture to |surface_texture_| by calling attachToGLContext()
+ // because:
+ // 1. Once we call detachFrameGLContext(), it deletes the texture previous
+ // attached.
+ // 2. SurfaceTexture requires us to apply a transform matrix when we show
+ // the texture.
+ texture_copier_->Copy(
+ surface_texture_id_, picture_buffer_texture_id, transfrom_matrix);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &AndroidVideoDecodeAccelerator::NotifyPictureReady,
+ base::AsWeakPtr(this), media::Picture(picture_buffer_id, bitstream_id)));
+}
+
+void AndroidVideoDecodeAccelerator::Decode(
+ const media::BitstreamBuffer& bitstream_buffer) {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+ pending_bitstream_buffers_.push(bitstream_buffer);
+
+ if (!io_task_is_posted_) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
+ DoIOTask();
+ }
+}
+
+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.
+ const std::vector<media::PictureBuffer>& buffers) {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(output_picture_buffers_.empty());
+
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ output_picture_buffers_.insert(std::make_pair(buffers[i].id(), buffers[i]));
+ free_picture_ids_.push(buffers[i].id());
+ }
+
+ RETURN_ON_FAILURE(output_picture_buffers_.size() == kNumPictureBuffers,
+ "Invalid picture buffers were passed.",
+ INVALID_ARGUMENT);
+}
+
+void AndroidVideoDecodeAccelerator::ReusePictureBuffer(
+ int32 picture_buffer_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ free_picture_ids_.push(picture_buffer_id);
+
+ if (!io_task_is_posted_) {
qinmin 2013/02/05 07:43:13 ditto
dwkang1 2013/02/05 11:49:36 Done.
+ DoIOTask();
+ }
+}
+
+void AndroidVideoDecodeAccelerator::Flush() {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ Decode(media::BitstreamBuffer(-1, base::SharedMemoryHandle(), 0));
+}
+
+void AndroidVideoDecodeAccelerator::ConfigureMediaCodec() {
+ DCHECK(surface_texture_.get());
+
+ media_codec_.reset(new media::MediaCodecBridge(codec_));
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ CHECK(env);
+ ScopedJavaLocalRef<jclass> cls(
+ base::android::GetClass(env, "android/view/Surface"));
+ jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>(
+ env, cls.obj(), "<init>", "(Landroid/graphics/SurfaceTexture;)V");
+ ScopedJavaLocalRef<jobject> j_surface(
+ env, env->NewObject(
+ cls.obj(), constructor,
+ surface_texture_->j_surface_texture().obj()));
+
+ // VDA does not pass the container indicated resolution in the initialization
+ // phase. Here, we set 1080p by default.
+ // TODO(dwkang): find out a way to remove the following hard-coded value.
+ media_codec_->StartVideo(
+ 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
+ content::ReleaseSurface(j_surface.obj());
+
+ media_codec_->GetInputBuffers();
+ media_codec_->GetOutputBuffers();
+}
+
+void AndroidVideoDecodeAccelerator::Reset() {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ while(!pending_bitstream_buffers_.empty()) {
+ media::BitstreamBuffer& bitstream_buffer =
+ pending_bitstream_buffers_.front();
+ pending_bitstream_buffers_.pop();
+
+ if (bitstream_buffer.id() != -1) {
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer,
+ base::AsWeakPtr(this), bitstream_buffer.id()));
+ }
+ }
+
+ if (!decoder_met_eos_) {
+ media_codec_->Flush();
+ } else {
+ // MediaCodec should be usable after meeting EOS, but it is not on some
+ // 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.
+ media_codec_->Stop();
+ ConfigureMediaCodec();
+ }
+ decoder_met_eos_ = false;
+ num_bytes_used_in_the_pending_buffer_ = 0;
+ state_ = NO_ERROR;
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &AndroidVideoDecodeAccelerator::NotifyResetDone, base::AsWeakPtr(this)));
+}
+
+void AndroidVideoDecodeAccelerator::Destroy() {
+ LOG_LINE();
+ DCHECK(thread_checker_.CalledOnValidThread());
+ 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.
+}
+
+void AndroidVideoDecodeAccelerator::NotifyInitializeDone() {
+ client_->NotifyInitializeDone();
+}
+
+void AndroidVideoDecodeAccelerator::RequestPictureBuffers() {
+ client_->ProvidePictureBuffers(kNumPictureBuffers, size_, GL_TEXTURE_2D);
+}
+
+void AndroidVideoDecodeAccelerator::NotifyPictureReady(
+ const media::Picture& picture) {
+ client_->PictureReady(picture);
+}
+
+void AndroidVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer(
+ int input_buffer_id) {
+ client_->NotifyEndOfBitstreamBuffer(input_buffer_id);
+}
+
+void AndroidVideoDecodeAccelerator::NotifyFlushDone() {
+ client_->NotifyFlushDone();
+}
+
+void AndroidVideoDecodeAccelerator::NotifyResetDone() {
+ client_->NotifyResetDone();
+}
+
+void AndroidVideoDecodeAccelerator::NotifyError(
+ media::VideoDecodeAccelerator::Error error) {
+ client_->NotifyError(error);
+}
+
+} // namespace content

Powered by Google App Engine
This is Rietveld 408576698