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

Unified Diff: media/video/capture/android/video_capture_device_android.cc

Issue 11860002: Add video capture on Android. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: code review 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: media/video/capture/android/video_capture_device_android.cc
===================================================================
--- media/video/capture/android/video_capture_device_android.cc (revision 0)
+++ media/video/capture/android/video_capture_device_android.cc (revision 0)
@@ -0,0 +1,415 @@
+// 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 "media/video/capture/android/video_capture_device_android.h"
+
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "jni/Camera_jni.h"
+#include "jni/VideoCapture_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::CheckException;
+using base::android::GetClass;
+using base::android::MethodID;
+using base::android::JavaRef;
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+// TODO(wjia): add stride as part of buffer parameter.
+void ResetBufferI420(uint8* buffer, int width, int height) {
+ int y_size = width * height;
+ memset(buffer, 0, y_size);
+ buffer += y_size;
+ memset(buffer, 128, y_size / 2);
+}
+
+void RotatePlaneByPixels(
+ uint8* src,
+ uint8* dest,
+ int width,
+ int height,
+ int rotation,
+ bool flip_vert,
+ bool flip_horiz) {
+ // Consolidate cases. Only 0 and 90 are left.
+ if (rotation == 180 || rotation == 270) {
+ rotation -= 180;
+ flip_vert = !flip_vert;
+ flip_horiz = !flip_horiz;
+ }
+
+ int num_rows = height;
+ int num_cols = width;
+ int src_stride = width;
+ int dest_row_step = width;
+ int dest_col_step = 1;
+
+ if (rotation == 0) {
+ if (flip_horiz) {
+ // Use pixel copying.
+ dest_col_step = -1;
+ if (flip_vert) {
+ // Rotation 180.
+ dest_row_step = -width;
+ dest += height * width - 1;
+ } else {
+ dest += width - 1;
+ }
+ } else {
+ if (flip_vert) {
+ // Fast copy by rows.
+ dest += width * (height - 1);
+ for (int row = 0; row < height; ++row) {
+ memcpy(dest, src, width);
+ src += width;
+ dest -= width;
+ }
+ } else {
+ memcpy(dest, src, width * height);
+ }
+ return;
+ }
+ } else if (rotation == 90) {
+ int offset = (width - height) / 2;
+ if (width > height) {
+ src += offset;
+ num_rows = num_cols = height;
+ } else {
+ src += width * offset;
+ num_rows = num_cols = width;
+ offset = (height - width) / 2;
+ }
+
+ dest_col_step = (flip_vert ? -width : width);
+ if (flip_horiz) {
+ dest_row_step = 1;
+ if (flip_vert) {
+ dest += (width > height ? width * (height - 1) + offset :
+ width * (height - offset - 1));
+ } else {
+ dest += (width > height ? offset : width * offset);
+ }
+ } else {
+ dest_row_step = -1;
+ if (flip_vert) {
+ dest += (width > height ? width * height - offset - 1 :
+ width * (height - offset) - 1);
+ } else {
+ dest += (width > height ? width - offset - 1 :
+ width * (offset + 1) - 1);
+ }
+ }
+ }
+
+ // Copy pixels.
+ for (int row = 0; row < num_rows; ++row) {
+ uint8* src_ptr = src;
+ uint8* dest_ptr = dest;
+ for (int col = 0; col < num_cols; ++col) {
+ *dest_ptr = *src_ptr++;
+ dest_ptr += dest_col_step;
+ }
+ src += src_stride;
+ dest += dest_row_step;
+ }
+}
+
+int GetIntField(JNIEnv* env,
+ const JavaRef<jclass>& clazz,
+ const JavaRef<jobject>& instance,
+ const char* field_name) {
+ jfieldID field = GetFieldID(env, clazz, field_name, "I");
+ jint int_value = env->GetIntField(instance.obj(), field);
+ return int_value;
+}
+
+} // namespace
+
+namespace media {
+
+// static
+void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
+ device_names->clear();
+
+ JNIEnv* env = AttachCurrentThread();
+
+ int num_cameras = JNI_Camera::Java_Camera_getNumberOfCameras(env);
+ DVLOG(1) << "VideoCaptureDevice::GetDeviceNames: num_cameras=" << num_cameras;
+ if (num_cameras <= 0)
+ return;
+
+ // TODO(wjia): switch to using same approach as Camera when
+ // jar_file_jni_generator.gypi supports system inner classes.
+ std::string camera_info_string("android/hardware/Camera$CameraInfo");
+
+ ScopedJavaLocalRef<jclass> camera_info_class(
+ GetClass(env, camera_info_string.c_str()));
+ jmethodID constructor = MethodID::Get<MethodID::TYPE_INSTANCE>(
+ env, camera_info_class.obj(), "<init>", "()V");
+
+ ScopedJavaLocalRef<jobject> object_camera_info(
+ env, env->NewObject(camera_info_class.obj(), constructor));
+
+ jfieldID field_facing = GetFieldID(env, camera_info_class, "facing", "I");
+ jfieldID field_facing_front = GetStaticFieldID(
+ env, camera_info_class, "CAMERA_FACING_FRONT", "I");
+
+ for (int camera_id = num_cameras - 1; camera_id >= 0; --camera_id) {
+ JNI_Camera::Java_Camera_getCameraInfo(
+ env, camera_id, object_camera_info.obj());
+ CheckException(env);
+
+ Name name;
+ name.unique_id = StringPrintf("%d", camera_id);
+ std::string facing_string;
+ if (env->GetIntField(object_camera_info.obj(), field_facing) ==
+ env->GetStaticIntField(camera_info_class.obj(), field_facing_front)) {
+ facing_string = "front";
+ } else {
+ facing_string = "back";
+ }
+ name.device_name = StringPrintf(
+ "camera %d, facing %s", camera_id, facing_string.c_str());
+ device_names->push_back(name);
+ jfieldID field_orientation = GetFieldID(
+ env, camera_info_class, "orientation", "I");
+ jint orientation = env->GetIntField(object_camera_info.obj(),
+ field_orientation);
+ DVLOG(1) << "VideoCaptureDevice::GetDeviceNames: camera device_name="
+ << name.device_name
+ << ", unique_id="
+ << name.unique_id
+ << ", orientation "
+ << orientation;
+ }
+}
+
+// static
+VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
+ return VideoCaptureDeviceAndroid::Create(device_name);
+}
+
+// static
+VideoCaptureDevice* VideoCaptureDeviceAndroid::Create(const Name& device_name) {
+ scoped_ptr<VideoCaptureDeviceAndroid> ret(
+ new VideoCaptureDeviceAndroid(device_name));
+ if (ret->Init())
+ return ret.release();
+ return NULL;
+}
+
+// static
+bool VideoCaptureDeviceAndroid::RegisterVideoCaptureDevice(JNIEnv* env) {
+ return RegisterNativesImpl(env) && JNI_Camera::RegisterNativesImpl(env);
+}
+
+VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(const Name& device_name)
+ : state_(kIdle),
+ observer_(NULL),
+ device_name_(device_name),
+ current_settings_(),
+ rotation_(0) {
+}
+
+VideoCaptureDeviceAndroid::~VideoCaptureDeviceAndroid() {
+ DeAllocate();
+}
+
+bool VideoCaptureDeviceAndroid::Init() {
+ int id;
+ if (!base::StringToInt(device_name_.unique_id, &id))
+ return false;
+
+ JNIEnv* env = AttachCurrentThread();
+
+ j_capture_.Reset(Java_VideoCapture_createVideoCapture(
+ env, base::android::GetApplicationContext(), id,
+ reinterpret_cast<jlong>(this)));
+
+ return true;
+}
+
+const VideoCaptureDevice::Name& VideoCaptureDeviceAndroid::device_name() {
+ return device_name_;
+}
+
+void VideoCaptureDeviceAndroid::Allocate(
+ int width,
+ int height,
+ int frame_rate,
+ EventHandler* observer) {
+ {
+ base::AutoLock lock(lock_);
+ if (state_ != kIdle)
+ return;
+ observer_ = observer;
+ state_ = kAllocated;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+
+ jboolean ret = Java_VideoCapture_allocate(env,
+ j_capture_.obj(),
+ width,
+ height,
+ frame_rate);
+ if (!ret) {
+ SetErrorState("failed to allocate");
+ return;
+ }
+
+ // Store current width and height.
+ current_settings_.width =
+ Java_VideoCapture_queryWidth(env, j_capture_.obj());
+ current_settings_.height =
+ Java_VideoCapture_queryHeight(env, j_capture_.obj());
+ current_settings_.frame_rate =
+ Java_VideoCapture_queryFrameRate(env, j_capture_.obj());
+ current_settings_.color = VideoCaptureCapability::kYV12;
+ CHECK(current_settings_.width > 0 && !(current_settings_.width % 2));
Ami GONE FROM CHROMIUM 2013/02/06 04:10:46 It's strange to assert %2==0 when you really depen
wjia(left Chromium) 2013/02/06 17:33:35 Done.
+ CHECK(current_settings_.height > 0 && !(current_settings_.height % 2));
+
+ DVLOG(1) << "VideoCaptureDeviceAndroid::Allocate: queried width="
+ << current_settings_.width
+ << ", height="
+ << current_settings_.height
+ << ", frame_rate="
+ << current_settings_.frame_rate;
+ // Report the frame size to the observer.
+ observer_->OnFrameInfo(current_settings_);
+
+ int y_size = current_settings_.width * current_settings_.height;
+ rotation_buffer_.reset(new uint8[y_size * 3 / 2]);
+ ResetBufferI420(rotation_buffer_.get(),
+ current_settings_.width,
+ current_settings_.height);
+}
+
+void VideoCaptureDeviceAndroid::Start() {
+ DVLOG(1) << "VideoCaptureDeviceAndroid::Start";
+ {
+ base::AutoLock lock(lock_);
+ DCHECK_EQ(state_, kAllocated);
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+
+ jint ret = Java_VideoCapture_startCapture(env, j_capture_.obj());
+ if (ret < 0) {
+ SetErrorState("failed to start capture");
+ return;
+ }
+
+ {
+ base::AutoLock lock(lock_);
+ state_ = kCapturing;
+ }
+}
+
+void VideoCaptureDeviceAndroid::Stop() {
+ DVLOG(1) << "VideoCaptureDeviceAndroid::Stop";
+ {
+ base::AutoLock lock(lock_);
+ if (state_ != kCapturing && state_ != kError)
+ return;
+ if (state_ == kCapturing)
+ state_ = kAllocated;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+
+ jint ret = Java_VideoCapture_stopCapture(env, j_capture_.obj());
+ if (ret < 0) {
+ SetErrorState("failed to stop capture");
+ return;
+ }
+}
+
+void VideoCaptureDeviceAndroid::DeAllocate() {
+ DVLOG(1) << "VideoCaptureDeviceAndroid::DeAllocate";
+ {
+ base::AutoLock lock(lock_);
+ if (state_ == kIdle)
+ return;
+
+ if (state_ == kCapturing) {
+ base::AutoUnlock unlock(lock_);
+ Stop();
+ }
+
+ if (state_ == kAllocated)
+ state_ = kIdle;
+
+ observer_ = NULL;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+
+ Java_VideoCapture_deallocate(env, j_capture_.obj());
+}
+
+void VideoCaptureDeviceAndroid::OnFrameAvailable(
+ JNIEnv* env,
+ jobject obj,
+ jbyteArray data,
+ jint length,
+ jint rotation,
+ jboolean flip_vert,
+ jboolean flip_horiz) {
+ DVLOG(3) << "VideoCaptureDeviceAndroid::OnFrameAvailable: length =" << length;
+
+ base::AutoLock lock(lock_);
+ if (state_ != kCapturing || !observer_)
+ return;
+
+ jbyte* buffer = env->GetByteArrayElements(data, NULL);
+ if (!buffer) {
+ LOG(ERROR) << "VideoCaptureDeviceAndroid::OnFrameAvailable: "
+ "failed to GetByteArrayElements";
+ return;
+ }
+
+ // TODO(wjia): move rotation into VideoCaptureController to remove
+ // one buffer copying.
+ // Rotate the buffer when needed.
+ int width = current_settings_.width;
+ int height = current_settings_.height;
+ if (rotation_ != rotation) {
+ rotation_ = rotation;
+ ResetBufferI420(rotation_buffer_.get(), width, height);
+ }
+
+ uint8* src = reinterpret_cast<uint8*>(buffer);
+ uint8* dest = rotation_buffer_.get();
+
+ RotatePlaneByPixels(src, dest, width, height, rotation, flip_vert,
+ flip_horiz);
+ int y_size = width * height;
+ src += y_size;
+ dest += y_size;
+ RotatePlaneByPixels(src, dest, width/2, height/2, rotation, flip_vert,
+ flip_horiz);
+ src += y_size/4;
+ dest += y_size/4;
+ RotatePlaneByPixels(src, dest, width/2, height/2, rotation, flip_vert,
+ flip_horiz);
+ observer_->OnIncomingCapturedFrame(rotation_buffer_.get(), length,
+ base::Time::Now());
+
+ env->ReleaseByteArrayElements(data, buffer, JNI_ABORT);
+}
+
+void VideoCaptureDeviceAndroid::SetErrorState(const std::string& reason) {
+ LOG(ERROR) << "VideoCaptureDeviceAndroid::SetErrorState: " << reason;
+ state_ = kError;
Ami GONE FROM CHROMIUM 2013/02/06 04:10:46 Your logic that these don't need to be locked beca
wjia(left Chromium) 2013/02/06 17:33:35 I guess I was not clear about using lock. Just to
Ami GONE FROM CHROMIUM 2013/02/06 17:39:14 So how can you SetErrorState from the Java thread
wjia(left Chromium) 2013/02/06 17:52:29 SetErrorState is only called on VCM thread. I acci
+ observer_->OnError();
+}
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698