blob: 2e5f448099e4324d6dfc480f16093258c1a73cf5 [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/bind.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "media/capture/mojom/image_capture_types.h"
#include "media/capture/video/android/capture_jni_headers/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 {
namespace {
mojom::MeteringMode ToMojomMeteringMode(
PhotoCapabilities::AndroidMeteringMode android_mode) {
switch (android_mode) {
case PhotoCapabilities::AndroidMeteringMode::FIXED:
return mojom::MeteringMode::MANUAL;
case PhotoCapabilities::AndroidMeteringMode::SINGLE_SHOT:
return mojom::MeteringMode::SINGLE_SHOT;
case PhotoCapabilities::AndroidMeteringMode::CONTINUOUS:
return mojom::MeteringMode::CONTINUOUS;
case PhotoCapabilities::AndroidMeteringMode::NONE:
return mojom::MeteringMode::NONE;
case PhotoCapabilities::AndroidMeteringMode::NOT_SET:
case PhotoCapabilities::AndroidMeteringMode::NUM_ENTRIES:
NOTREACHED();
}
return mojom::MeteringMode::NONE;
}
PhotoCapabilities::AndroidMeteringMode ToAndroidMeteringMode(
mojom::MeteringMode mojom_mode) {
switch (mojom_mode) {
case mojom::MeteringMode::MANUAL:
return PhotoCapabilities::AndroidMeteringMode::FIXED;
case mojom::MeteringMode::SINGLE_SHOT:
return PhotoCapabilities::AndroidMeteringMode::SINGLE_SHOT;
case mojom::MeteringMode::CONTINUOUS:
return PhotoCapabilities::AndroidMeteringMode::CONTINUOUS;
case mojom::MeteringMode::NONE:
return PhotoCapabilities::AndroidMeteringMode::NONE;
}
NOTREACHED();
return PhotoCapabilities::AndroidMeteringMode::NOT_SET;
}
mojom::FillLightMode ToMojomFillLightMode(
PhotoCapabilities::AndroidFillLightMode android_mode) {
switch (android_mode) {
case PhotoCapabilities::AndroidFillLightMode::FLASH:
return mojom::FillLightMode::FLASH;
case PhotoCapabilities::AndroidFillLightMode::AUTO:
return mojom::FillLightMode::AUTO;
case PhotoCapabilities::AndroidFillLightMode::OFF:
return mojom::FillLightMode::OFF;
case PhotoCapabilities::AndroidFillLightMode::NOT_SET:
case PhotoCapabilities::AndroidFillLightMode::NUM_ENTRIES:
NOTREACHED();
}
NOTREACHED();
return mojom::FillLightMode::OFF;
}
PhotoCapabilities::AndroidFillLightMode ToAndroidFillLightMode(
mojom::FillLightMode mojom_mode) {
switch (mojom_mode) {
case mojom::FillLightMode::FLASH:
return PhotoCapabilities::AndroidFillLightMode::FLASH;
case mojom::FillLightMode::AUTO:
return PhotoCapabilities::AndroidFillLightMode::AUTO;
case mojom::FillLightMode::OFF:
return PhotoCapabilities::AndroidFillLightMode::OFF;
}
NOTREACHED();
return PhotoCapabilities::AndroidFillLightMode::NOT_SET;
}
} // anonymous namespace
VideoCaptureDeviceAndroid::VideoCaptureDeviceAndroid(
const VideoCaptureDeviceDescriptor& device_descriptor)
: main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
device_descriptor_(device_descriptor) {}
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, params.enable_face_detection);
if (!ret) {
SetErrorState(media::VideoCaptureError::kAndroidFailedToAllocate, FROM_HERE,
"failed to allocate");
return;
}
// TODO(julien.isorce): Use Camera.SENSOR_COLOR_TRANSFORM2 to build a
// gfx::ColorSpace, and rename VideoCaptureDeviceAndroid::GetColorspace()
// to GetPixelFormat, see http://crbug.com/959901.
capture_color_space_ = gfx::ColorSpace();
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, 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::Microseconds(
(base::Time::kMicrosecondsPerSecond + capture_format_.frame_rate - 1) /
capture_format_.frame_rate);
}
DVLOG(1) << __func__ << " requested ("
<< capture_format_.frame_size.ToString() << ")@ "
<< capture_format_.frame_rate << "fps";
ret = Java_VideoCapture_startCaptureMaybeAsync(env, j_capture_);
if (!ret) {
SetErrorState(media::VideoCaptureError::kAndroidFailedToStartCapture,
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_stopCaptureAndBlockUntilStopped(env, j_capture_);
if (!ret) {
SetErrorState(media::VideoCaptureError::kAndroidFailedToStopCapture,
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());
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureDeviceAndroid::TakePhoto",
TRACE_EVENT_SCOPE_PROCESS);
{
base::AutoLock lock(lock_);
if (state_ != kConfigured)
return;
if (!got_first_frame_) { // We have to wait until we get the first frame.
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureDeviceAndroid::TakePhoto enqueuing to "
"wait for first frame",
TRACE_EVENT_SCOPE_PROCESS);
photo_requests_queue_.push_back(
base::BindOnce(&VideoCaptureDeviceAndroid::DoTakePhoto,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
return;
}
}
DoTakePhoto(std::move(callback));
}
void VideoCaptureDeviceAndroid::GetPhotoState(GetPhotoStateCallback 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::BindOnce(&VideoCaptureDeviceAndroid::DoGetPhotoState,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
return;
}
}
DoGetPhotoState(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::BindOnce(&VideoCaptureDeviceAndroid::DoSetPhotoOptions,
weak_ptr_factory_.GetWeakPtr(), std::move(settings),
std::move(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) {
if (!IsClientConfigured())
return;
const base::TimeTicks current_time = base::TimeTicks::Now();
ProcessFirstFrameAvailable(current_time);
// Using |expected_next_frame_time_| to estimate a proper capture timestamp
// since android.hardware.Camera API doesn't expose a better timestamp.
const base::TimeDelta capture_time =
expected_next_frame_time_ - base::TimeTicks();
// Deliver the frame when it doesn't arrive too early.
if (ThrottleFrame(current_time)) {
client_->OnFrameDropped(VideoCaptureFrameDropReason::kAndroidThrottling);
return;
}
jbyte* buffer = env->GetByteArrayElements(data, NULL);
if (!buffer) {
LOG(ERROR) << "VideoCaptureDeviceAndroid::OnFrameAvailable: "
"failed to GetByteArrayElements";
// In case of error, restore back the throttle control value.
expected_next_frame_time_ -= frame_interval_;
client_->OnFrameDropped(
VideoCaptureFrameDropReason::kAndroidGetByteArrayElementsFailed);
return;
}
// TODO(qiangchen): Investigate how to get raw timestamp for Android,
// rather than using reference time to calculate timestamp.
SendIncomingDataToClient(reinterpret_cast<uint8_t*>(buffer), length, rotation,
current_time, capture_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,
jlong timestamp) {
if (!IsClientConfigured())
return;
const int64_t absolute_micro =
timestamp / base::Time::kNanosecondsPerMicrosecond;
const base::TimeDelta capture_time = base::Microseconds(absolute_micro);
const base::TimeTicks current_time = base::TimeTicks::Now();
ProcessFirstFrameAvailable(current_time);
// Deliver the frame when it doesn't arrive too early.
if (ThrottleFrame(current_time)) {
client_->OnFrameDropped(VideoCaptureFrameDropReason::kAndroidThrottling);
return;
}
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);
SendIncomingDataToClient(buffer.get(), buffer_length, rotation, current_time,
capture_time);
}
void VideoCaptureDeviceAndroid::OnError(JNIEnv* env,
const JavaParamRef<jobject>& obj,
int android_video_capture_error,
const JavaParamRef<jstring>& message) {
SetErrorState(
static_cast<media::VideoCaptureError>(android_video_capture_error),
FROM_HERE, base::android::ConvertJavaStringToUTF8(env, message));
}
void VideoCaptureDeviceAndroid::OnFrameDropped(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
int android_video_capture_frame_drop_reason) {
base::AutoLock lock(lock_);
if (!client_)
return;
client_->OnFrameDropped(static_cast<media::VideoCaptureFrameDropReason>(
android_video_capture_frame_drop_reason));
}
void VideoCaptureDeviceAndroid::OnGetPhotoCapabilitiesReply(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jlong callback_id,
jobject result) {
base::AutoLock lock(photo_callbacks_lock_);
GetPhotoStateCallback* const cb =
reinterpret_cast<GetPhotoStateCallback*>(callback_id);
// Search for the pointer |cb| in the list of |take_photo_callbacks_|.
const auto reference_it = std::find_if(
get_photo_state_callbacks_.begin(), get_photo_state_callbacks_.end(),
[cb](const std::unique_ptr<GetPhotoStateCallback>& callback) {
return callback.get() == cb;
});
if (reference_it == get_photo_state_callbacks_.end()) {
NOTREACHED() << "|callback_id| not found.";
return;
}
if (result == nullptr) {
get_photo_state_callbacks_.erase(reference_it);
return;
}
base::android::ScopedJavaLocalRef<jobject> scoped_photo_capabilities(env,
result);
PhotoCapabilities caps(scoped_photo_capabilities);
// TODO(mcasas): Manual member copying sucks, consider adding typemapping from
// PhotoCapabilities to mojom::PhotoStatePtr, https://crbug.com/622002.
mojom::PhotoStatePtr photo_capabilities = mojo::CreateEmptyPhotoState();
const auto jni_white_balance_modes = caps.getMeteringModeArray(
PhotoCapabilities::MeteringModeType::WHITE_BALANCE);
std::vector<mojom::MeteringMode> white_balance_modes;
for (const auto& white_balance_mode : jni_white_balance_modes)
white_balance_modes.push_back(ToMojomMeteringMode(white_balance_mode));
photo_capabilities->supported_white_balance_modes = white_balance_modes;
photo_capabilities->current_white_balance_mode = ToMojomMeteringMode(
caps.getMeteringMode(PhotoCapabilities::MeteringModeType::WHITE_BALANCE));
const auto jni_exposure_modes =
caps.getMeteringModeArray(PhotoCapabilities::MeteringModeType::EXPOSURE);
std::vector<mojom::MeteringMode> exposure_modes;
for (const auto& exposure_mode : jni_exposure_modes)
exposure_modes.push_back(ToMojomMeteringMode(exposure_mode));
photo_capabilities->supported_exposure_modes = exposure_modes;
photo_capabilities->current_exposure_mode = ToMojomMeteringMode(
caps.getMeteringMode(PhotoCapabilities::MeteringModeType::EXPOSURE));
const auto jni_focus_modes =
caps.getMeteringModeArray(PhotoCapabilities::MeteringModeType::FOCUS);
std::vector<mojom::MeteringMode> focus_modes;
for (const auto& focus_mode : jni_focus_modes)
focus_modes.push_back(ToMojomMeteringMode(focus_mode));
photo_capabilities->supported_focus_modes = focus_modes;
photo_capabilities->current_focus_mode = ToMojomMeteringMode(
caps.getMeteringMode(PhotoCapabilities::MeteringModeType::FOCUS));
photo_capabilities->focus_distance = mojom::Range::New();
photo_capabilities->focus_distance->current = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::CURRENT_FOCUS_DISTANCE);
photo_capabilities->focus_distance->max = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MAX_FOCUS_DISTANCE);
photo_capabilities->focus_distance->min = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MIN_FOCUS_DISTANCE);
photo_capabilities->focus_distance->step = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::STEP_FOCUS_DISTANCE);
photo_capabilities->exposure_compensation = mojom::Range::New();
photo_capabilities->exposure_compensation->current = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::CURRENT_EXPOSURE_COMPENSATION);
photo_capabilities->exposure_compensation->max = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MAX_EXPOSURE_COMPENSATION);
photo_capabilities->exposure_compensation->min = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MIN_EXPOSURE_COMPENSATION);
photo_capabilities->exposure_compensation->step = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::STEP_EXPOSURE_COMPENSATION);
photo_capabilities->exposure_time = mojom::Range::New();
photo_capabilities->exposure_time->current = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::CURRENT_EXPOSURE_TIME);
photo_capabilities->exposure_time->max = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MAX_EXPOSURE_TIME);
photo_capabilities->exposure_time->min = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::MIN_EXPOSURE_TIME);
photo_capabilities->exposure_time->step = caps.getDouble(
PhotoCapabilities::PhotoCapabilityDouble::STEP_EXPOSURE_TIME);
photo_capabilities->color_temperature = mojom::Range::New();
photo_capabilities->color_temperature->current = caps.getInt(
PhotoCapabilities::PhotoCapabilityInt::CURRENT_COLOR_TEMPERATURE);
photo_capabilities->color_temperature->max =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MAX_COLOR_TEMPERATURE);
photo_capabilities->color_temperature->min =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MIN_COLOR_TEMPERATURE);
photo_capabilities->color_temperature->step = caps.getInt(
PhotoCapabilities::PhotoCapabilityInt::STEP_COLOR_TEMPERATURE);
photo_capabilities->iso = mojom::Range::New();
photo_capabilities->iso->current =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::CURRENT_ISO);
photo_capabilities->iso->max =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MAX_ISO);
photo_capabilities->iso->min =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MIN_ISO);
photo_capabilities->iso->step =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::STEP_ISO);
photo_capabilities->brightness = mojom::Range::New();
photo_capabilities->contrast = mojom::Range::New();
photo_capabilities->saturation = mojom::Range::New();
photo_capabilities->sharpness = mojom::Range::New();
photo_capabilities->zoom = mojom::Range::New();
photo_capabilities->zoom->current =
caps.getDouble(PhotoCapabilities::PhotoCapabilityDouble::CURRENT_ZOOM);
photo_capabilities->zoom->max =
caps.getDouble(PhotoCapabilities::PhotoCapabilityDouble::MAX_ZOOM);
photo_capabilities->zoom->min =
caps.getDouble(PhotoCapabilities::PhotoCapabilityDouble::MIN_ZOOM);
photo_capabilities->zoom->step =
caps.getDouble(PhotoCapabilities::PhotoCapabilityDouble::STEP_ZOOM);
photo_capabilities->supports_torch =
caps.getBool(PhotoCapabilities::PhotoCapabilityBool::SUPPORTS_TORCH);
photo_capabilities->torch =
caps.getBool(PhotoCapabilities::PhotoCapabilityBool::TORCH);
photo_capabilities->red_eye_reduction =
caps.getBool(PhotoCapabilities::PhotoCapabilityBool::RED_EYE_REDUCTION)
? mojom::RedEyeReduction::CONTROLLABLE
: mojom::RedEyeReduction::NEVER;
photo_capabilities->height = mojom::Range::New();
photo_capabilities->height->current =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::CURRENT_HEIGHT);
photo_capabilities->height->max =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MAX_HEIGHT);
photo_capabilities->height->min =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MIN_HEIGHT);
photo_capabilities->height->step =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::STEP_HEIGHT);
photo_capabilities->width = mojom::Range::New();
photo_capabilities->width->current =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::CURRENT_WIDTH);
photo_capabilities->width->max =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MAX_WIDTH);
photo_capabilities->width->min =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::MIN_WIDTH);
photo_capabilities->width->step =
caps.getInt(PhotoCapabilities::PhotoCapabilityInt::STEP_WIDTH);
const auto fill_light_modes = caps.getFillLightModeArray();
std::vector<mojom::FillLightMode> modes;
for (const auto& fill_light_mode : fill_light_modes)
modes.push_back(ToMojomFillLightMode(fill_light_mode));
photo_capabilities->fill_light_mode = modes;
std::move(*cb).Run(std::move(photo_capabilities));
get_photo_state_callbacks_.erase(reference_it);
}
void VideoCaptureDeviceAndroid::OnPhotoTaken(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jlong callback_id,
const base::android::JavaParamRef<jbyteArray>& data) {
DCHECK(callback_id);
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureDeviceAndroid::OnPhotoTaken",
TRACE_EVENT_SCOPE_PROCESS);
base::AutoLock lock(photo_callbacks_lock_);
TakePhotoCallback* const cb =
reinterpret_cast<TakePhotoCallback*>(callback_id);
// Search for the pointer |cb| in the list of |take_photo_callbacks_|.
const auto reference_it =
std::find_if(take_photo_callbacks_.begin(), take_photo_callbacks_.end(),
[cb](const std::unique_ptr<TakePhotoCallback>& callback) {
return callback.get() == cb;
});
if (reference_it == take_photo_callbacks_.end()) {
NOTREACHED() << "|callback_id| not found.";
return;
}
if (data != nullptr) {
mojom::BlobPtr blob = mojom::Blob::New();
base::android::JavaByteArrayToByteVector(env, data, &blob->data);
blob->mime_type = blob->data.empty() ? "" : "image/jpeg";
std::move(*cb).Run(std::move(blob));
}
take_photo_callbacks_.erase(reference_it);
}
void VideoCaptureDeviceAndroid::OnStarted(JNIEnv* env,
const JavaParamRef<jobject>& obj) {
if (client_)
client_->OnStarted();
}
void VideoCaptureDeviceAndroid::DCheckCurrentlyOnIncomingTaskRunner(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
}
void VideoCaptureDeviceAndroid::ConfigureForTesting() {
Java_VideoCapture_setTestMode(AttachCurrentThread(), j_capture_);
}
void VideoCaptureDeviceAndroid::ProcessFirstFrameAvailable(
base::TimeTicks current_time) {
base::AutoLock lock(lock_);
if (got_first_frame_)
return;
got_first_frame_ = true;
// Set aside one frame allowance for fluctuation.
expected_next_frame_time_ = current_time - frame_interval_;
for (auto& request : photo_requests_queue_)
main_task_runner_->PostTask(FROM_HERE, std::move(request));
photo_requests_queue_.clear();
}
bool VideoCaptureDeviceAndroid::IsClientConfigured() {
base::AutoLock lock(lock_);
return (state_ == kConfigured && client_);
}
bool VideoCaptureDeviceAndroid::ThrottleFrame(base::TimeTicks current_time) {
if (expected_next_frame_time_ > current_time)
return true;
expected_next_frame_time_ += frame_interval_;
return false;
}
void VideoCaptureDeviceAndroid::SendIncomingDataToClient(
const uint8_t* data,
int length,
int rotation,
base::TimeTicks reference_time,
base::TimeDelta timestamp) {
base::AutoLock lock(lock_);
if (!client_)
return;
client_->OnIncomingCapturedData(
data, length, capture_format_, capture_color_space_, rotation,
false /* flip_y */, reference_time, timestamp);
}
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 PIXEL_FORMAT_YV12;
case ANDROID_IMAGE_FORMAT_YUV_420_888:
return PIXEL_FORMAT_I420;
case ANDROID_IMAGE_FORMAT_NV21:
return PIXEL_FORMAT_NV21;
case ANDROID_IMAGE_FORMAT_UNKNOWN:
default:
return PIXEL_FORMAT_UNKNOWN;
}
}
void VideoCaptureDeviceAndroid::SetErrorState(media::VideoCaptureError error,
const base::Location& from_here,
const std::string& reason) {
{
base::AutoLock lock(lock_);
state_ = kError;
if (!client_)
return;
client_->OnError(error, from_here, reason);
}
}
void VideoCaptureDeviceAndroid::DoTakePhoto(TakePhotoCallback callback) {
DCHECK(main_task_runner_->BelongsToCurrentThread());
TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
"VideoCaptureDeviceAndroid::DoTakePhoto",
TRACE_EVENT_SCOPE_PROCESS);
#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());
{
base::AutoLock lock(photo_callbacks_lock_);
take_photo_callbacks_.push_back(std::move(heap_callback));
}
Java_VideoCapture_takePhotoAsync(env, j_capture_, callback_id);
}
void VideoCaptureDeviceAndroid::DoGetPhotoState(
GetPhotoStateCallback 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<GetPhotoStateCallback> heap_callback(
new GetPhotoStateCallback(std::move(callback)));
const intptr_t callback_id = reinterpret_cast<intptr_t>(heap_callback.get());
{
base::AutoLock lock(photo_callbacks_lock_);
get_photo_state_callbacks_.push_back(std::move(heap_callback));
}
Java_VideoCapture_getPhotoCapabilitiesAsync(env, j_capture_, callback_id);
}
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 double zoom = settings->has_zoom ? settings->zoom : 0.0;
const double focusDistance =
settings->has_focus_distance ? settings->focus_distance : 0.0;
const PhotoCapabilities::AndroidMeteringMode focus_mode =
settings->has_focus_mode
? ToAndroidMeteringMode(settings->focus_mode)
: PhotoCapabilities::AndroidMeteringMode::NOT_SET;
const PhotoCapabilities::AndroidMeteringMode exposure_mode =
settings->has_exposure_mode
? ToAndroidMeteringMode(settings->exposure_mode)
: PhotoCapabilities::AndroidMeteringMode::NOT_SET;
const double width = settings->has_width ? settings->width : 0.0;
const double height = settings->has_height ? settings->height : 0.0;
std::vector<double> 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<jdoubleArray> points_of_interest =
base::android::ToJavaDoubleArray(env, points_of_interest_marshalled);
const double exposure_compensation = settings->has_exposure_compensation
? settings->exposure_compensation
: 0.0;
const double exposure_time =
settings->has_exposure_time ? settings->exposure_time : 0.0;
const PhotoCapabilities::AndroidMeteringMode white_balance_mode =
settings->has_white_balance_mode
? ToAndroidMeteringMode(settings->white_balance_mode)
: PhotoCapabilities::AndroidMeteringMode::NOT_SET;
const double iso = settings->has_iso ? settings->iso : 0.0;
const PhotoCapabilities::AndroidFillLightMode fill_light_mode =
settings->has_fill_light_mode
? ToAndroidFillLightMode(settings->fill_light_mode)
: PhotoCapabilities::AndroidFillLightMode::NOT_SET;
const double color_temperature =
settings->has_color_temperature ? settings->color_temperature : 0.0;
Java_VideoCapture_setPhotoOptions(
env, j_capture_, zoom, static_cast<int>(focus_mode), focusDistance,
static_cast<int>(exposure_mode), width, height, points_of_interest,
settings->has_exposure_compensation, exposure_compensation, exposure_time,
static_cast<int>(white_balance_mode), iso,
settings->has_red_eye_reduction, settings->red_eye_reduction,
static_cast<int>(fill_light_mode), settings->has_torch, settings->torch,
color_temperature);
std::move(callback).Run(true);
}
} // namespace media