/* * 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 #include #include #include #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 IntPair; typedef std::vector 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* 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(); 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( 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(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( jni->GetObjectField(capabilityElement, widthField)); jintArray heightResArray = static_cast( 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