Chromium Code Reviews| 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/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 { |
| + |
| +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 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) |
| + 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) { |
| + env->CallStaticVoidMethod(camera_class.obj(), |
| + method_get_camera_info, |
| + 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 " |
| + << orientation; |
| + } |
| +} |
| + |
| +// static |
| +VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) { |
| + VideoCaptureDeviceAndroid* self = new VideoCaptureDeviceAndroid(device_name); |
| + if (!self || !self->Init()) |
| + 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; |
| + if (device_name_.device_name.find("camera") == std::string::npos) |
| + return false; |
| + |
| + base::StringToInt(device_name_.unique_id, &id); |
| + |
| + JNIEnv* env = AttachCurrentThread(); |
| + if (!env) |
| + return false; |
| + |
| + 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(); |
| + CHECK(env); |
| + |
| + jint ret = Java_VideoCapture_allocate(env, |
| + j_capture_.obj(), |
| + width, |
| + height, |
| + frame_rate); |
| + if (ret < 0) { |
| + SetErrorState("failed to allocate"); |
| + return; |
| + } |
| + |
| + // Store current width and height. |
| + current_settings_.color = VideoCaptureCapability::kYV12; |
| + current_settings_.width = |
|
qinmin
2013/01/27 05:36:32
remove 1 whitespace
wjia(left Chromium)
2013/01/27 20:25:33
Done.
|
| + 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()); |
| + 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 (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_ == kIdle || state_ == kAllocated) |
| + return; |
| + state_ = kAllocated; |
| + } |
| + |
| + JNIEnv* env = AttachCurrentThread(); |
| + CHECK(env); |
| + |
| + 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(); |
| + CHECK(env); |
| + |
| + 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; |
| + ResetBuffer(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); |
| + src = rotation_buffer_.get(); |
| + observer_->OnIncomingCapturedFrame(src, length, base::Time::Now()); |
| + |
| + env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); |
| +} |
| + |
| +void VideoCaptureDeviceAndroid::SetErrorState(const std::string& reason) { |
| + LOG(ERROR) << "VideoCaptureDeviceAndroid::SetErrorState: " << reason; |
| + state_ = kError; |
| + observer_->OnError(); |
| +} |
| + |
| +} // namespace media |