зеркало из https://github.com/mozilla/gecko-dev.git
317 строки
11 KiB
C++
317 строки
11 KiB
C++
/*
|
|
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "device_info_android.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "rtc_base/logging.h"
|
|
#include "modules/utility/include/helpers_android.h"
|
|
|
|
#include "mozilla/jni/Utils.h"
|
|
|
|
namespace webrtc {
|
|
|
|
namespace videocapturemodule {
|
|
|
|
// Helper for storing lists of pairs of ints. Used e.g. for resolutions & FPS
|
|
// ranges.
|
|
typedef std::pair<int, int> IntPair;
|
|
typedef std::vector<IntPair> IntPairs;
|
|
|
|
static std::string IntPairsToString(const IntPairs& pairs, char separator) {
|
|
std::stringstream stream;
|
|
for (size_t i = 0; i < pairs.size(); ++i) {
|
|
if (i > 0) {
|
|
stream << ", ";
|
|
}
|
|
stream << "(" << pairs[i].first << separator << pairs[i].second << ")";
|
|
}
|
|
return stream.str();
|
|
}
|
|
|
|
struct AndroidCameraInfo {
|
|
std::string name;
|
|
bool front_facing;
|
|
int orientation;
|
|
IntPairs resolutions; // Pairs are: (width,height).
|
|
// Pairs are (min,max) in units of FPS*1000 ("milli-frame-per-second").
|
|
IntPairs mfpsRanges;
|
|
|
|
std::string ToString() {
|
|
std::stringstream stream;
|
|
stream << "Name: [" << name << "], MFPS ranges: ["
|
|
<< IntPairsToString(mfpsRanges, ':')
|
|
<< "], front_facing: " << front_facing
|
|
<< ", orientation: " << orientation << ", resolutions: ["
|
|
<< IntPairsToString(resolutions, 'x') << "]";
|
|
return stream.str();
|
|
}
|
|
};
|
|
|
|
// Camera info; populated during DeviceInfoAndroid::Refresh()
|
|
static std::vector<AndroidCameraInfo>* g_camera_info = NULL;
|
|
|
|
static JavaVM* g_jvm_dev_info = NULL;
|
|
|
|
// Set |*index| to the index of |name| in g_camera_info or return false if no
|
|
// match found.
|
|
static bool FindCameraIndexByName(const std::string& name, size_t* index) {
|
|
for (size_t i = 0; i < g_camera_info->size(); ++i) {
|
|
if (g_camera_info->at(i).name == name) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns a pointer to the named member of g_camera_info, or NULL if no match
|
|
// is found.
|
|
static AndroidCameraInfo* FindCameraInfoByName(const std::string& name) {
|
|
size_t index = 0;
|
|
if (FindCameraIndexByName(name, &index)) {
|
|
return &g_camera_info->at(index);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// static
|
|
void DeviceInfoAndroid::Initialize(JavaVM* javaVM) {
|
|
// TODO(henrike): this "if" would make a lot more sense as an assert, but
|
|
// Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_GetVideoEngine() and
|
|
// Java_org_webrtc_videoengineapp_ViEAndroidJavaAPI_Terminate() conspire to
|
|
// prevent this. Once that code is made to only
|
|
// VideoEngine::SetAndroidObjects() once per process, this can turn into an
|
|
// assert.
|
|
if (g_camera_info) {
|
|
return;
|
|
}
|
|
|
|
g_jvm_dev_info = javaVM;
|
|
BuildDeviceList();
|
|
}
|
|
|
|
void DeviceInfoAndroid::BuildDeviceList() {
|
|
if (!g_jvm_dev_info) {
|
|
return;
|
|
}
|
|
|
|
AttachThreadScoped ats(g_jvm_dev_info);
|
|
JNIEnv* jni = ats.env();
|
|
|
|
g_camera_info = new std::vector<AndroidCameraInfo>();
|
|
jclass j_info_class = mozilla::jni::GetClassRef(
|
|
jni, "org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid");
|
|
jclass j_cap_class = mozilla::jni::GetClassRef(
|
|
jni, "org/webrtc/videoengine/CaptureCapabilityAndroid");
|
|
assert(j_info_class);
|
|
jmethodID j_get_device_info = jni->GetStaticMethodID(
|
|
j_info_class, "getDeviceInfo",
|
|
"()[Lorg/webrtc/videoengine/CaptureCapabilityAndroid;");
|
|
jarray j_camera_caps = static_cast<jarray>(
|
|
jni->CallStaticObjectMethod(j_info_class, j_get_device_info));
|
|
if (jni->ExceptionCheck()) {
|
|
jni->ExceptionClear();
|
|
RTC_LOG(LS_INFO) << __FUNCTION__ << ": Failed to get camera capabilities.";
|
|
return;
|
|
}
|
|
if (j_camera_caps == nullptr) {
|
|
RTC_LOG(LS_INFO) << __FUNCTION__ << ": Failed to get camera capabilities.";
|
|
return;
|
|
}
|
|
|
|
const jsize capLength = jni->GetArrayLength(j_camera_caps);
|
|
|
|
jfieldID widthField = jni->GetFieldID(j_cap_class, "width", "[I");
|
|
jfieldID heightField = jni->GetFieldID(j_cap_class, "height", "[I");
|
|
jfieldID maxFpsField = jni->GetFieldID(j_cap_class, "maxMilliFPS", "I");
|
|
jfieldID minFpsField = jni->GetFieldID(j_cap_class, "minMilliFPS", "I");
|
|
jfieldID orientationField = jni->GetFieldID(j_cap_class, "orientation", "I");
|
|
jfieldID frontFacingField = jni->GetFieldID(j_cap_class, "frontFacing", "Z");
|
|
jfieldID nameField =
|
|
jni->GetFieldID(j_cap_class, "name", "Ljava/lang/String;");
|
|
if (widthField == NULL || heightField == NULL || maxFpsField == NULL ||
|
|
minFpsField == NULL || orientationField == NULL ||
|
|
frontFacingField == NULL || nameField == NULL) {
|
|
RTC_LOG(LS_INFO) << __FUNCTION__ << ": Failed to get field Id.";
|
|
return;
|
|
}
|
|
|
|
for (jsize i = 0; i < capLength; i++) {
|
|
jobject capabilityElement =
|
|
jni->GetObjectArrayElement((jobjectArray)j_camera_caps, i);
|
|
|
|
AndroidCameraInfo info;
|
|
jstring camName =
|
|
static_cast<jstring>(jni->GetObjectField(capabilityElement, nameField));
|
|
const char* camChars = jni->GetStringUTFChars(camName, nullptr);
|
|
info.name = std::string(camChars);
|
|
jni->ReleaseStringUTFChars(camName, camChars);
|
|
|
|
info.orientation = jni->GetIntField(capabilityElement, orientationField);
|
|
info.front_facing =
|
|
jni->GetBooleanField(capabilityElement, frontFacingField);
|
|
jint min_mfps = jni->GetIntField(capabilityElement, minFpsField);
|
|
jint max_mfps = jni->GetIntField(capabilityElement, maxFpsField);
|
|
|
|
jintArray widthResArray = static_cast<jintArray>(
|
|
jni->GetObjectField(capabilityElement, widthField));
|
|
jintArray heightResArray = static_cast<jintArray>(
|
|
jni->GetObjectField(capabilityElement, heightField));
|
|
|
|
const jsize numRes = jni->GetArrayLength(widthResArray);
|
|
|
|
jint* widths = jni->GetIntArrayElements(widthResArray, nullptr);
|
|
jint* heights = jni->GetIntArrayElements(heightResArray, nullptr);
|
|
|
|
for (jsize j = 0; j < numRes; ++j) {
|
|
info.resolutions.push_back(std::make_pair(widths[j], heights[j]));
|
|
}
|
|
|
|
info.mfpsRanges.push_back(std::make_pair(min_mfps, max_mfps));
|
|
g_camera_info->push_back(info);
|
|
|
|
jni->ReleaseIntArrayElements(widthResArray, widths, JNI_ABORT);
|
|
jni->ReleaseIntArrayElements(heightResArray, heights, JNI_ABORT);
|
|
}
|
|
|
|
jni->DeleteLocalRef(j_info_class);
|
|
jni->DeleteLocalRef(j_cap_class);
|
|
}
|
|
|
|
void DeviceInfoAndroid::DeInitialize() {
|
|
if (g_camera_info) {
|
|
delete g_camera_info;
|
|
g_camera_info = NULL;
|
|
}
|
|
}
|
|
|
|
int32_t DeviceInfoAndroid::Refresh() {
|
|
if (!g_camera_info || g_camera_info->size() == 0) {
|
|
DeviceInfoAndroid::BuildDeviceList();
|
|
#ifdef DEBUG
|
|
int frontFacingIndex = -1;
|
|
for (uint32_t i = 0; i < g_camera_info->size(); i++) {
|
|
if (g_camera_info->at(i).front_facing) {
|
|
frontFacingIndex = i;
|
|
}
|
|
}
|
|
// Either there is a front-facing camera, and it's first in the list, or
|
|
// there is no front-facing camera.
|
|
MOZ_ASSERT(frontFacingIndex == 0 || frontFacingIndex == -1);
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo() {
|
|
return new videocapturemodule::DeviceInfoAndroid();
|
|
}
|
|
|
|
DeviceInfoAndroid::DeviceInfoAndroid() : DeviceInfoImpl() {}
|
|
|
|
DeviceInfoAndroid::~DeviceInfoAndroid() {}
|
|
|
|
bool DeviceInfoAndroid::FindCameraIndex(const char* deviceUniqueIdUTF8,
|
|
size_t* index) {
|
|
return FindCameraIndexByName(deviceUniqueIdUTF8, index);
|
|
}
|
|
|
|
int32_t DeviceInfoAndroid::Init() { return 0; }
|
|
|
|
uint32_t DeviceInfoAndroid::NumberOfDevices() {
|
|
Refresh();
|
|
return g_camera_info->size();
|
|
}
|
|
|
|
int32_t DeviceInfoAndroid::GetDeviceName(
|
|
uint32_t deviceNumber, char* deviceNameUTF8, uint32_t deviceNameLength,
|
|
char* deviceUniqueIdUTF8, uint32_t deviceUniqueIdUTF8Length,
|
|
char* /*productUniqueIdUTF8*/, uint32_t /*productUniqueIdUTF8Length*/,
|
|
pid_t* /*pid*/) {
|
|
if (deviceNumber >= g_camera_info->size()) {
|
|
return -1;
|
|
}
|
|
const AndroidCameraInfo& info = g_camera_info->at(deviceNumber);
|
|
if (info.name.length() + 1 > deviceNameLength ||
|
|
info.name.length() + 1 > deviceUniqueIdUTF8Length) {
|
|
return -1;
|
|
}
|
|
memcpy(deviceNameUTF8, info.name.c_str(), info.name.length() + 1);
|
|
memcpy(deviceUniqueIdUTF8, info.name.c_str(), info.name.length() + 1);
|
|
return 0;
|
|
}
|
|
|
|
int32_t DeviceInfoAndroid::CreateCapabilityMap(const char* deviceUniqueIdUTF8) {
|
|
_captureCapabilities.clear();
|
|
const AndroidCameraInfo* info = FindCameraInfoByName(deviceUniqueIdUTF8);
|
|
if (info == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
for (size_t i = 0; i < info->resolutions.size(); ++i) {
|
|
for (size_t j = 0; j < info->mfpsRanges.size(); ++j) {
|
|
const IntPair& size = info->resolutions[i];
|
|
const IntPair& mfpsRange = info->mfpsRanges[j];
|
|
VideoCaptureCapability cap;
|
|
cap.width = size.first;
|
|
cap.height = size.second;
|
|
cap.maxFPS = mfpsRange.second / 1000;
|
|
cap.videoType = VideoType::kNV21;
|
|
_captureCapabilities.push_back(cap);
|
|
}
|
|
}
|
|
return _captureCapabilities.size();
|
|
}
|
|
|
|
int32_t DeviceInfoAndroid::GetOrientation(const char* deviceUniqueIdUTF8,
|
|
VideoRotation& orientation) {
|
|
const AndroidCameraInfo* info = FindCameraInfoByName(deviceUniqueIdUTF8);
|
|
if (info == NULL || VideoCaptureImpl::RotationFromDegrees(
|
|
info->orientation, &orientation) != 0) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void DeviceInfoAndroid::GetMFpsRange(const char* deviceUniqueIdUTF8,
|
|
int max_fps_to_match, int* min_mfps,
|
|
int* max_mfps) {
|
|
const AndroidCameraInfo* info = FindCameraInfoByName(deviceUniqueIdUTF8);
|
|
if (info == NULL) {
|
|
return;
|
|
}
|
|
int desired_mfps = max_fps_to_match * 1000;
|
|
int best_diff_mfps = 0;
|
|
RTC_LOG(LS_INFO) << "Search for best target mfps " << desired_mfps;
|
|
// Search for best fps range with preference shifted to constant fps modes.
|
|
for (size_t i = 0; i < info->mfpsRanges.size(); ++i) {
|
|
int diff_mfps =
|
|
abs(info->mfpsRanges[i].first - desired_mfps) +
|
|
abs(info->mfpsRanges[i].second - desired_mfps) +
|
|
(info->mfpsRanges[i].second - info->mfpsRanges[i].first) / 2;
|
|
RTC_LOG(LS_INFO) << "Fps range " << info->mfpsRanges[i].first << ":"
|
|
<< info->mfpsRanges[i].second
|
|
<< ". Distance: " << diff_mfps;
|
|
if (i == 0 || diff_mfps < best_diff_mfps) {
|
|
best_diff_mfps = diff_mfps;
|
|
*min_mfps = info->mfpsRanges[i].first;
|
|
*max_mfps = info->mfpsRanges[i].second;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace videocapturemodule
|
|
} // namespace webrtc
|