blob: f398a5ae8868f572808fa25108d3b05797674355 [file] [log] [blame]
// 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/capture/video/android/video_capture_device_android.h"
#include <stdint.h>
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "jni/VideoCapture_jni.h"
#include "media/capture/video/android/photo_capabilities.h"
#include "media/capture/video/android/video_capture_device_factory_android.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/geometry/point_f.h"
using base::android::AttachCurrentThread;
using base::android::CheckException;
using base::android::GetClass;
using base::android::JavaParamRef;
using base::android::MethodID;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
namespace media {
// static
bool VideoCaptureDeviceAndroid::RegisterVideoCaptureDevice(JNIEnv* env) {
return RegisterNativesImpl(env);
}
VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(
const VideoCaptureDeviceDescriptor& device_descriptor)
: main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
state_(kIdle),
got_first_frame_(false),
device_descriptor_(device_descriptor),
weak_ptr_factory_(this) {}
VideoCaptureDeviceAndroid::~VideoCaptureDeviceAndroid() {
DCHECK(main_task_runner_->BelongsToCurrentThread());
StopAndDeAllocate();
}
bool VideoCaptureDeviceAndroid::Init() {
int id;
if (!base::StringToInt(device_descriptor_.device_id, &id))
return false;
j_capture_.Reset(VideoCaptureDeviceFactoryAndroid::createVideoCaptureAndroid(
id, reinterpret_cast<intptr_t>(this)));
return true;
}
void VideoCaptureDeviceAndroid::AllocateAndStart(
const VideoCaptureParams& params,
std::unique_ptr<Client> client) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
{
base::AutoLock lock(lock_);
if (state_ != kIdle)
return;
client_ = std::move(client);
got_first_frame_ = false;
}
JNIEnv* env = AttachCurrentThread();
jboolean ret = Java_VideoCapture_allocate(
env, j_capture_, params.requested_format.frame_size.width(),
params.requested_format.frame_size.height(),
params.requested_format.frame_rate);
if (!ret) {
SetErrorState(FROM_HERE, "failed to allocate");
return;
}
capture_format_.frame_size.SetSize(
Java_VideoCapture_queryWidth(env, j_capture_),
Java_VideoCapture_queryHeight(env, j_capture_));
capture_format_.frame_rate =
Java_VideoCapture_queryFrameRate(env, j_capture_);
capture_format_.pixel_format = GetColorspace();
DCHECK_NE(capture_format_.pixel_format, media::PIXEL_FORMAT_UNKNOWN);
CHECK(capture_format_.frame_size.GetArea() > 0);
CHECK(!(capture_format_.frame_size.width() % 2));
CHECK(!(capture_format_.frame_size.height() % 2));
if (capture_format_.frame_rate > 0) {
frame_interval_ = base::TimeDelta::FromMicroseconds(
(base::Time::kMicrosecondsPerSecond + capture_format_.frame_rate - 1) /
capture_format_.frame_rate);
}
DVLOG(1) << __FUNCTION__ << " requested ("
<< capture_format_.frame_size.ToString() << ")@ "
<< capture_format_.frame_rate << "fps";
ret = Java_VideoCapture_startCapture(env, j_capture_);
if (!ret) {
SetErrorState(FROM_HERE, "failed to start capture");
return;
}
{
base::AutoLock lock(lock_);
state_ = kConfigured;
}
}
void VideoCaptureDeviceAndroid::StopAndDeAllocate() {
DCHECK(main_task_runner_->BelongsToCurrentThread());
{
base::AutoLock lock(lock_);
if (state_ != kConfigured && state_ != kError)
return;
}
JNIEnv* env = AttachCurrentThread();
const jboolean ret = Java_VideoCapture_stopCapture(env, j_capture_);
if (!ret) {
SetErrorState(FROM_HERE, "failed to stop capture");
return;
}
{
base::AutoLock lock(lock_);
state_ = kIdle;
client_.reset();
}
Java_VideoCapture_deallocate(env, j_capture_);
}
void VideoCaptureDeviceAndroid::TakePhoto(TakePhotoCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
{
base::AutoLock lock(lock_);
if (state_ != kConfigured)
return;
if (!got_first_frame_) { // We have to wait until we get the first frame.
photo_requests_queue_.push_back(
base::Bind(&VideoCaptureDeviceAndroid::DoTakePhoto,
weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
return;
}
}
DoTakePhoto(std::move(callback));
}
void VideoCaptureDeviceAndroid::GetPhotoCapabilities(
GetPhotoCapabilitiesCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
{
base::AutoLock lock(lock_);
if (state_ != kConfigured)
return;
if (!got_first_frame_) { // We have to wait until we get the first frame.
photo_requests_queue_.push_back(
base::Bind(&VideoCaptureDeviceAndroid::DoGetPhotoCapabilities,
weak_ptr_factory_.GetWeakPtr(), base::Passed(&callback)));
return;
}
}
DoGetPhotoCapabilities(std::move(callback));
}
void VideoCaptureDeviceAndroid::SetPhotoOptions(
mojom::PhotoSettingsPtr settings,
SetPhotoOptionsCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
{
base::AutoLock lock(lock_);
if (state_ != kConfigured)
return;
if (!got_first_frame_) { // We have to wait until we get the first frame.
photo_requests_queue_.push_back(
base::Bind(&VideoCaptureDeviceAndroid::DoSetPhotoOptions,
weak_ptr_factory_.GetWeakPtr(), base::Passed(&settings),
base::Passed(&callback)));
return;
}
}
DoSetPhotoOptions(std::move(settings), std::move(callback));
}
void VideoCaptureDeviceAndroid::OnFrameAvailable(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jbyteArray>& data,
jint length,
jint rotation) {
{
base::AutoLock lock(lock_);
if (state_ != kConfigured || !client_)
return;
}
jbyte* buffer = env->GetByteArrayElements(data, NULL);
if (!buffer) {
LOG(ERROR) << "VideoCaptureDeviceAndroid::OnFrameAvailable: "
"failed to GetByteArrayElements";
return;
}
const base::TimeTicks current_time = base::TimeTicks::Now();
{
base::AutoLock lock(lock_);
if (!got_first_frame_) {
// Set aside one frame allowance for fluctuation.
expected_next_frame_time_ = current_time - frame_interval_;
first_ref_time_ = current_time;
got_first_frame_ = true;
for (const auto& request : photo_requests_queue_)
main_task_runner_->PostTask(FROM_HERE, request);
photo_requests_queue_.clear();
}
}
// Deliver the frame when it doesn't arrive too early.
if (expected_next_frame_time_ <= current_time) {
expected_next_frame_time_ += frame_interval_;
// TODO(qiangchen): Investigate how to get raw timestamp for Android,
// rather than using reference time to calculate timestamp.
base::AutoLock lock(lock_);
if (!client_)
return;
client_->OnIncomingCapturedData(reinterpret_cast<uint8_t*>(buffer), length,
capture_format_, rotation, current_time,
current_time - first_ref_time_);
}
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT);
}
void VideoCaptureDeviceAndroid::OnI420FrameAvailable(JNIEnv* env,
jobject obj,
jobject y_buffer,
jint y_stride,
jobject u_buffer,
jobject v_buffer,
jint uv_row_stride,
jint uv_pixel_stride,
jint width,
jint height,
jint rotation) {
{
base::AutoLock lock(lock_);
if (state_ != kConfigured || !client_)
return;
}
const base::TimeTicks current_time = base::TimeTicks::Now();
{
base::AutoLock lock(lock_);
if (!got_first_frame_) {
// Set aside one frame allowance for fluctuation.
expected_next_frame_time_ = current_time - frame_interval_;
first_ref_time_ = current_time;
got_first_frame_ = true;
for (const auto& request : photo_requests_queue_)
main_task_runner_->PostTask(FROM_HERE, request);
photo_requests_queue_.clear();
}
}
uint8_t* const y_src =
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(y_buffer));
CHECK(y_src);
uint8_t* const u_src =
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(u_buffer));
CHECK(u_src);
uint8_t* const v_src =
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(v_buffer));
CHECK(v_src);
const int y_plane_length = width * height;
const int uv_plane_length = y_plane_length / 4;
const int buffer_length = y_plane_length + uv_plane_length * 2;
std::unique_ptr<uint8_t> buffer(new uint8_t[buffer_length]);
libyuv::Android420ToI420(y_src, y_stride, u_src, uv_row_stride, v_src,
uv_row_stride, uv_pixel_stride, buffer.get(), width,
buffer.get() + y_plane_length, width / 2,
buffer.get() + y_plane_length + uv_plane_length,
width / 2, width, height);
// Deliver the frame when it doesn't arrive too early.
if (expected_next_frame_time_ <= current_time) {
expected_next_frame_time_ += frame_interval_;
// TODO(qiangchen): Investigate how to get raw timestamp for Android,
// rather than using reference time to calculate timestamp.
base::AutoLock lock(lock_);
if (!client_)
return;
client_->OnIncomingCapturedData(buffer.get(), buffer_length,
capture_format_, rotation, current_time,
current_time - first_ref_time_);
}
}
void VideoCaptureDeviceAndroid::OnError(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& message) {
SetErrorState(FROM_HERE,
base::android::ConvertJavaStringToUTF8(env, message));
}
void VideoCaptureDeviceAndroid::OnPhotoTaken(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jlong callback_id,
const base::android::JavaParamRef<jbyteArray>& data) {
DCHECK(callback_id);
base::AutoLock lock(photo_callbacks_lock_);
TakePhotoCallback* const cb =
reinterpret_cast<TakePhotoCallback*>(callback_id);
// Search for the pointer |cb| in the list of |photo_callbacks_|.
const auto reference_it =
std::find_if(photo_callbacks_.begin(), photo_callbacks_.end(),
[cb](const std::unique_ptr<TakePhotoCallback>& callback) {
return callback.get() == cb;
});
if (reference_it == photo_callbacks_.end()) {
NOTREACHED() << "|callback_id| not found.";
return;
}
mojom::BlobPtr blob = mojom::Blob::New();
base::android::JavaByteArrayToByteVector(env, data.obj(), &blob->data);
blob->mime_type = blob->data.empty() ? "" : "image/jpeg";
cb->Run(std::move(blob));
photo_callbacks_.erase(reference_it);
}
VideoPixelFormat VideoCaptureDeviceAndroid::GetColorspace() {
JNIEnv* env = AttachCurrentThread();
const int current_capture_colorspace =
Java_VideoCapture_getColorspace(env, j_capture_);
switch (current_capture_colorspace) {
case ANDROID_IMAGE_FORMAT_YV12:
return media::PIXEL_FORMAT_YV12;
case ANDROID_IMAGE_FORMAT_YUV_420_888:
return media::PIXEL_FORMAT_I420;
case ANDROID_IMAGE_FORMAT_NV21:
return media::PIXEL_FORMAT_NV21;
case ANDROID_IMAGE_FORMAT_UNKNOWN:
default:
return media::PIXEL_FORMAT_UNKNOWN;
}
}
void VideoCaptureDeviceAndroid::SetErrorState(
const tracked_objects::Location& from_here,
const std::string& reason) {
{
base::AutoLock lock(lock_);
state_ = kError;
if (!client_)
return;
client_->OnError(from_here, reason);
}
}
void VideoCaptureDeviceAndroid::DoTakePhoto(TakePhotoCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
#if DCHECK_IS_ON()
{
base::AutoLock lock(lock_);
DCHECK_EQ(kConfigured, state_);
DCHECK(got_first_frame_);
}
#endif
JNIEnv* env = AttachCurrentThread();
// Make copy on the heap so we can pass the pointer through JNI.
std::unique_ptr<TakePhotoCallback> heap_callback(
new TakePhotoCallback(std::move(callback)));
const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get());
if (!Java_VideoCapture_takePhoto(env, j_capture_, callback_id))
return;
{
base::AutoLock lock(photo_callbacks_lock_);
photo_callbacks_.push_back(std::move(heap_callback));
}
}
void VideoCaptureDeviceAndroid::DoGetPhotoCapabilities(
GetPhotoCapabilitiesCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
#if DCHECK_IS_ON()
{
base::AutoLock lock(lock_);
DCHECK_EQ(kConfigured, state_);
DCHECK(got_first_frame_);
}
#endif
JNIEnv* env = AttachCurrentThread();
PhotoCapabilities caps(
Java_VideoCapture_getPhotoCapabilities(env, j_capture_));
// TODO(mcasas): Manual member copying sucks, consider adding typemapping from
// PhotoCapabilities to mojom::PhotoCapabilitiesPtr, https://crbug.com/622002.
mojom::PhotoCapabilitiesPtr photo_capabilities =
mojom::PhotoCapabilities::New();
photo_capabilities->iso = mojom::Range::New();
photo_capabilities->iso->current = caps.getCurrentIso();
photo_capabilities->iso->max = caps.getMaxIso();
photo_capabilities->iso->min = caps.getMinIso();
photo_capabilities->height = mojom::Range::New();
photo_capabilities->height->current = caps.getCurrentHeight();
photo_capabilities->height->max = caps.getMaxHeight();
photo_capabilities->height->min = caps.getMinHeight();
photo_capabilities->width = mojom::Range::New();
photo_capabilities->width->current = caps.getCurrentWidth();
photo_capabilities->width->max = caps.getMaxWidth();
photo_capabilities->width->min = caps.getMinWidth();
photo_capabilities->zoom = mojom::Range::New();
photo_capabilities->zoom->current = caps.getCurrentZoom();
photo_capabilities->zoom->max = caps.getMaxZoom();
photo_capabilities->zoom->min = caps.getMinZoom();
switch (caps.getFocusMode()) {
case PhotoCapabilities::AndroidFocusMode::UNAVAILABLE:
photo_capabilities->focus_mode = mojom::FocusMode::UNAVAILABLE;
break;
case PhotoCapabilities::AndroidFocusMode::FIXED:
photo_capabilities->focus_mode = mojom::FocusMode::MANUAL;
break;
case PhotoCapabilities::AndroidFocusMode::SINGLE_SHOT:
photo_capabilities->focus_mode = mojom::FocusMode::SINGLE_SHOT;
break;
case PhotoCapabilities::AndroidFocusMode::CONTINUOUS:
photo_capabilities->focus_mode = mojom::FocusMode::CONTINUOUS;
break;
}
callback.Run(std::move(photo_capabilities));
}
void VideoCaptureDeviceAndroid::DoSetPhotoOptions(
mojom::PhotoSettingsPtr settings,
SetPhotoOptionsCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
#if DCHECK_IS_ON()
{
base::AutoLock lock(lock_);
DCHECK_EQ(kConfigured, state_);
DCHECK(got_first_frame_);
}
#endif
JNIEnv* env = AttachCurrentThread();
const int width = settings->has_width ? settings->width : 0;
const int height = settings->has_height ? settings->height : 0;
const int zoom = settings->has_zoom ? settings->zoom : 0;
PhotoCapabilities::AndroidFocusMode focus_mode =
PhotoCapabilities::AndroidFocusMode::UNAVAILABLE;
if (settings->has_focus_mode) {
switch (settings->focus_mode) {
case mojom::FocusMode::MANUAL:
focus_mode = PhotoCapabilities::AndroidFocusMode::FIXED;
break;
case mojom::FocusMode::SINGLE_SHOT:
focus_mode = PhotoCapabilities::AndroidFocusMode::SINGLE_SHOT;
break;
case mojom::FocusMode::CONTINUOUS:
focus_mode = PhotoCapabilities::AndroidFocusMode::CONTINUOUS;
break;
case mojom::FocusMode::UNAVAILABLE:
focus_mode = PhotoCapabilities::AndroidFocusMode::UNAVAILABLE;
break;
}
}
std::vector<float> points_of_interest_marshalled;
for (const auto& point : settings->points_of_interest) {
points_of_interest_marshalled.push_back(point->x);
points_of_interest_marshalled.push_back(point->y);
}
ScopedJavaLocalRef<jfloatArray> points_of_interest =
base::android::ToJavaFloatArray(env, points_of_interest_marshalled);
Java_VideoCapture_setPhotoOptions(env, j_capture_, zoom,
static_cast<int>(focus_mode), width, height,
points_of_interest);
callback.Run(true);
}
} // namespace media