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

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: ready for review Created 7 years, 11 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,426 @@
+// 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/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;
+
+void OnFrameAvailable(JNIEnv* env, jobject obj,
qinmin 2013/01/22 21:31:42 why this one does not fit into an empty namespace?
wjia(left Chromium) 2013/01/22 22:40:01 This is the callback from VideoCapture JAVA class
+ jbyteArray data, jint length, jlong native_capture,
+ jint rotation, jboolean flip_vert, jboolean flip_horiz) {
+ DVLOG(3) << "OnFrmaeAvailable: length =" << static_cast<int>(length);
+ media::VideoCaptureDeviceAndroid* capture =
+ reinterpret_cast<media::VideoCaptureDeviceAndroid*>(native_capture);
+ CHECK(capture);
+
+ jbyte* buffer = env->GetByteArrayElements(data, NULL);
+ if (!buffer) {
+ LOG(ERROR) << "OnFrmaeAvailable: failed to GetByteArrayElements";
+ return;
+ }
+
+ capture->OnFrameAvailable(reinterpret_cast<uint8*>(buffer),
+ static_cast<int>(length),
qinmin 2013/01/22 21:31:42 I think there is no need to cast jint to int
wjia(left Chromium) 2013/01/22 22:40:01 Done, and remove all unneeded static_cast.
+ base::Time::Now(),
+ static_cast<int>(rotation),
+ static_cast<bool>(flip_vert),
+ static_cast<bool>(flip_horiz));
+ env->ReleaseByteArrayElements(data, buffer, JNI_ABORT);
+}
+
+namespace {
+
+void ResetBuffer(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 static_cast<int>(int_value);
+}
+
+} // namespace
+
+namespace media {
+
+// static
+void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
+ device_names->clear();
+
+ JNIEnv* env = AttachCurrentThread();
+ CHECK(env);
+
+ std::string camera_string("android/hardware/Camera");
+ std::string camera_info_string = camera_string + "$CameraInfo";
+
+ ScopedJavaLocalRef<jclass> camera_class(GetClass(env, camera_string.c_str()));
+
+ jmethodID method_get_number_of_cameras = MethodID::Get<MethodID::TYPE_STATIC>(
+ env, camera_class.obj(), "getNumberOfCameras", "()I");
+
+ int num_cameras = env->CallStaticIntMethod(camera_class.obj(),
+ method_get_number_of_cameras);
+ DVLOG(1) << "VideoCaptureDevice::GetDeviceNames: num_cameras=" << num_cameras;
+ if (num_cameras <= 0) {
qinmin 2013/01/22 21:31:42 remove { for single line if statement
wjia(left Chromium) 2013/01/22 22:40:01 Done.
+ return;
+ }
+
+ // Signature of getCameraInfo: "(ILandroid/hardware/Camera$CameraInfo;)V"
+ std::string signature = "(IL" + camera_info_string + ";)V";
+ jmethodID method_get_camera_info = MethodID::Get<MethodID::TYPE_STATIC>(
+ env, camera_class.obj(), "getCameraInfo", signature.c_str());
+
+ 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) {
+ //for (int camera_id = 0; camera_id < num_cameras; ++camera_id) {
qinmin 2013/01/22 21:31:42 remove this line
wjia(left Chromium) 2013/01/22 22:40:01 Done.
+ env->CallStaticVoidMethod(camera_class.obj(),
+ method_get_camera_info,
+ static_cast<jint>(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.c_str()
+ << ", unique_id="
+ << name.unique_id.c_str()
+ << ", orientation "
+ << static_cast<int>(orientation);
+ }
+}
+
+// static
+VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
+ VideoCaptureDeviceAndroid* self = new VideoCaptureDeviceAndroid(device_name);
+ if (!self || !self->Init()) {
qinmin 2013/01/22 21:31:42 no {
wjia(left Chromium) 2013/01/22 22:40:01 Done.
+ return NULL;
+ }
+
+ return self;
+}
+
+// static
+bool VideoCaptureDeviceAndroid::RegisterVideoCaptureDevice(JNIEnv* env) {
+ bool ret = RegisterNativesImpl(env);
+ DCHECK(g_VideoCapture_clazz);
+ return ret;
+}
+
+VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(const Name& device_name)
+ : state_(kIdle),
+ observer_(NULL),
+ device_name_(device_name),
+ rotation_(0) {
+}
+
+VideoCaptureDeviceAndroid::~VideoCaptureDeviceAndroid() {
+ DeAllocate();
+}
+
+bool VideoCaptureDeviceAndroid::Init() {
+ int id;
+ base::StringToInt(device_name_.unique_id, &id);
+
+ JNIEnv* env = AttachCurrentThread();
+ if (!env) {
+ return false;
+ }
+
+ j_capture_.Reset(Java_VideoCapture_createVideoCapture(env,
+ base::android::GetApplicationContext(), static_cast<jint>(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_);
+ observer_ = observer;
+ state_ = kAllocated;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ CHECK(env);
+
+ jint ret = Java_VideoCapture_allocate(env,
+ j_capture_.obj(),
+ static_cast<jint>(width),
+ static_cast<jint>(height),
+ static_cast<jint>(frame_rate));
+ if (static_cast<int>(ret) < 0) {
+ SetErrorState("failed to allocate");
+ return;
+ }
+
+ // Store current width and height.
+ current_settings_.color = VideoCaptureCapability::kYV12;
+ current_settings_.width =
+ static_cast<int>(Java_VideoCapture_queryWidth(env, j_capture_.obj()));
+ current_settings_.height =
+ static_cast<int>(Java_VideoCapture_queryHeight(env, j_capture_.obj()));
+ current_settings_.frame_rate =
+ static_cast<int>(Java_VideoCapture_queryFrameRate(env, j_capture_.obj()));
+ DCHECK(current_settings_.width > 0 && !(current_settings_.width % 2));
+ DCHECK(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]);
+ ResetBuffer(rotation_buffer_.get(), current_settings_.width,
+ current_settings_.height);
+}
+
+void VideoCaptureDeviceAndroid::Start() {
+ DVLOG(1) << "VideoCaptureDeviceAndroid::Start";
+ {
+ base::AutoLock lock(lock_);
+ if (state_ != kAllocated)
+ return;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ CHECK(env);
+
+ jint ret = Java_VideoCapture_startCapture(env, j_capture_.obj());
+ if (static_cast<int>(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_);
+ state_ = kAllocated;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ CHECK(env);
+
+ jint ret = Java_VideoCapture_stopCapture(env, j_capture_.obj());
+ if (static_cast<int>(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();
+ CHECK(env);
+
+ Java_VideoCapture_deallocate(env, j_capture_.obj());
+}
+
+void VideoCaptureDeviceAndroid::OnFrameAvailable(
+ uint8* buffer, int length, base::Time timestamp,
+ int rotation, bool flip_vert, bool flip_horiz) {
+ base::AutoLock lock(lock_);
+ if (state_ != kCapturing || !observer_) {
qinmin 2013/01/22 21:31:42 no {
wjia(left Chromium) 2013/01/22 22:40:01 Done.
+ 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;
+ ResetBuffer(rotation_buffer_.get(), width, height);
+ }
+
+ uint8* src = 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);
+ buffer = rotation_buffer_.get();
+ observer_->OnIncomingCapturedFrame(buffer, length, timestamp);
+
+ DVLOG(3) << "VideoCaptureDeviceAndroid::OnFrameAvailable: length=" << length;
+}
+
+void VideoCaptureDeviceAndroid::SetErrorState(const std::string& reason) {
+ LOG(ERROR) << "VideoCaptureDeviceAndroid::SetErrorState: " << reason;
+ state_ = kError;
+ observer_->OnError();
+}
+
+} // namespace media
« media/media.gyp ('K') | « media/video/capture/android/video_capture_device_android.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698