From b0b135e4c2937e30f9597d52eb6ccc73fb4f7c0b Mon Sep 17 00:00:00 2001 From: "fischman@webrtc.org" Date: Wed, 9 Apr 2014 01:18:32 +0000 Subject: [PATCH] VideoCaptureAndroid: support multiple frame-rates per resolution. Also enables running video_capture_tests_apk on the WebRTC/Chromium APK bots, assuming GYP_DEFINES includes include_tests=1 and include_internal_video_capture=1. This required running VideoCaptureAndroid's camera capture on a dedicated thread, matching other platform's video_capture impls. BUG=2974,3152 R=wu@webrtc.org Review URL: https://webrtc-codereview.appspot.com/11359004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5868 4adac7df-926f-26a2-2b94-8c16560cd09d --- webrtc/build/apk_tests.gyp | 31 ++++- webrtc/build/apk_tests_noop.gyp | 4 + .../android/device_info_android.cc | 70 ++++++---- .../android/device_info_android.h | 10 +- .../videoengine/VideoCaptureAndroid.java | 126 +++++++++++++++--- .../VideoCaptureDeviceInfoAndroid.java | 30 +++-- .../android/video_capture_android.cc | 4 +- .../video_capture/ensure_initialized.cc | 54 ++++++++ .../video_capture/ensure_initialized.h | 19 +++ .../video_capture/include/video_capture.h | 2 +- .../test/video_capture_unittest.cc | 57 ++++++-- .../modules/video_capture/video_capture.gypi | 11 +- 12 files changed, 351 insertions(+), 67 deletions(-) create mode 100644 webrtc/modules/video_capture/ensure_initialized.cc create mode 100644 webrtc/modules/video_capture/ensure_initialized.h diff --git a/webrtc/build/apk_tests.gyp b/webrtc/build/apk_tests.gyp index 1df046925f..4914886b9a 100644 --- a/webrtc/build/apk_tests.gyp +++ b/webrtc/build/apk_tests.gyp @@ -213,7 +213,36 @@ 'includes': [ '../../../build/apk_test.gypi', ], - } + }, + { + 'target_name': 'video_capture_tests_apk', + 'type': 'none', + 'variables': { + 'test_suite_name': 'video_capture_tests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)video_capture_tests<(SHARED_LIB_SUFFIX)', + }, + 'dependencies': [ + '<(webrtc_root)/modules/modules.gyp:video_capture_tests', + 'video_capture_java', + ], + 'includes': [ + '../../../build/apk_test.gypi', + ], + }, + { + # Used only by video_capture_tests_apk above, and impossible to use in the + # standalone build, which is why it's declared here instead of under + # modules/video_capture/ (to avoid the need for a forked _noop.gyp file + # like this file has; see comment at the top of this file). + 'target_name': 'video_capture_java', + 'type': 'none', + 'variables': { + 'java_in_dir': '<(webrtc_root)/modules/video_capture/android/java', + }, + 'includes': [ + '../../../build/java.gypi', + ], + }, ], } diff --git a/webrtc/build/apk_tests_noop.gyp b/webrtc/build/apk_tests_noop.gyp index ed64863d0d..e8d39d7889 100644 --- a/webrtc/build/apk_tests_noop.gyp +++ b/webrtc/build/apk_tests_noop.gyp @@ -65,5 +65,9 @@ 'target_name': 'audio_codec_speed_tests_apk', 'type': 'none', }, + { + 'target_name': 'video_capture_tests_apk', + 'type': 'none', + }, ], } diff --git a/webrtc/modules/video_capture/android/device_info_android.cc b/webrtc/modules/video_capture/android/device_info_android.cc index 10c277eeb7..144fcbd57e 100644 --- a/webrtc/modules/video_capture/android/device_info_android.cc +++ b/webrtc/modules/video_capture/android/device_info_android.cc @@ -21,36 +21,40 @@ #include "webrtc/system_wrappers/interface/ref_count.h" #include "webrtc/system_wrappers/interface/trace.h" -namespace webrtc -{ +namespace webrtc { -namespace videocapturemodule -{ +namespace videocapturemodule { -static std::string ResolutionsToString( - const std::vector >& pairs) { +// 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 << "x" << pairs[i].second << ")"; + stream << "(" << pairs[i].first << separator << pairs[i].second << ")"; } return stream.str(); } struct AndroidCameraInfo { std::string name; - int min_mfps, max_mfps; // FPS*1000. bool front_facing; int orientation; - std::vector > resolutions; // Pairs are: (width,height). + 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: [" << min_mfps << ":" << max_mfps + stream << "Name: [" << name << "], MFPS ranges: [" + << IntPairsToString(mfpsRanges, ':') << "], front_facing: " << front_facing << ", orientation: " << orientation << ", resolutions: [" - << ResolutionsToString(resolutions) << "]"; + << IntPairsToString(resolutions, 'x') << "]"; return stream.str(); } }; @@ -120,8 +124,6 @@ void DeviceInfoAndroid::Initialize(JNIEnv* jni) { const Json::Value& camera = cameras[i]; AndroidCameraInfo info; info.name = camera["name"].asString(); - info.min_mfps = camera["min_mfps"].asInt(); - info.max_mfps = camera["max_mfps"].asInt(); info.front_facing = camera["front_facing"].asBool(); info.orientation = camera["orientation"].asInt(); Json::Value sizes = camera["sizes"]; @@ -130,6 +132,12 @@ void DeviceInfoAndroid::Initialize(JNIEnv* jni) { info.resolutions.push_back(std::make_pair( size["width"].asInt(), size["height"].asInt())); } + Json::Value mfpsRanges = camera["mfpsRanges"]; + for (Json::ArrayIndex j = 0; j < mfpsRanges.size(); ++j) { + const Json::Value& mfpsRange = mfpsRanges[j]; + info.mfpsRanges.push_back(std::make_pair(mfpsRange["min_mfps"].asInt(), + mfpsRange["max_mfps"].asInt())); + } g_camera_info->push_back(info); } } @@ -187,14 +195,17 @@ int32_t DeviceInfoAndroid::CreateCapabilityMap( return -1; for (size_t i = 0; i < info->resolutions.size(); ++i) { - const std::pair& size = info->resolutions[i]; - VideoCaptureCapability cap; - cap.width = size.first; - cap.height = size.second; - cap.maxFPS = info->max_mfps / 1000; - cap.expectedCaptureDelay = kExpectedCaptureDelay; - cap.rawType = kVideoNV21; - _captureCapabilities.push_back(cap); + 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.expectedCaptureDelay = kExpectedCaptureDelay; + cap.rawType = kVideoNV21; + _captureCapabilities.push_back(cap); + } } return _captureCapabilities.size(); } @@ -210,13 +221,22 @@ int32_t DeviceInfoAndroid::GetOrientation( return 0; } -void DeviceInfoAndroid::GetFpsRange(const char* deviceUniqueIdUTF8, - int* min_mfps, int* max_mfps) { +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; - *min_mfps = info->min_mfps; - *max_mfps = info->max_mfps; + // Rely on CameraParameters.getSupportedPreviewFpsRange() to sort its return + // value (per its documentation) and return the first (most flexible) range + // whose high end is at least as high as that requested. + for (size_t i = 0; i < info->mfpsRanges.size(); ++i) { + if (info->mfpsRanges[i].second / 1000 >= max_fps_to_match) { + *min_mfps = info->mfpsRanges[i].first; + *max_mfps = info->mfpsRanges[i].second; + return; + } + } } } // namespace videocapturemodule diff --git a/webrtc/modules/video_capture/android/device_info_android.h b/webrtc/modules/video_capture/android/device_info_android.h index d277113e5e..b8d838c037 100644 --- a/webrtc/modules/video_capture/android/device_info_android.h +++ b/webrtc/modules/video_capture/android/device_info_android.h @@ -53,10 +53,12 @@ class DeviceInfoAndroid : public DeviceInfoImpl { virtual int32_t GetOrientation(const char* deviceUniqueIdUTF8, VideoCaptureRotation& orientation); - // Populate |min_mfps| and |max_mfps| with the supported range of the device. - void GetFpsRange(const char* deviceUniqueIdUTF8, - int* min_mfps, - int* max_mfps); + // Populate |min_mfps| and |max_mfps| with the closest supported range of the + // device to |max_fps_to_match|. + void GetMFpsRange(const char* deviceUniqueIdUTF8, + int max_fps_to_match, + int* min_mfps, + int* max_mfps); private: enum { kExpectedCaptureDelay = 190}; diff --git a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java index 6e308af15b..80f6f63233 100644 --- a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java +++ b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java @@ -12,6 +12,7 @@ package org.webrtc.videoengine; import java.io.IOException; import java.util.Locale; +import java.util.concurrent.Exchanger; import java.util.concurrent.locks.ReentrantLock; import android.graphics.ImageFormat; @@ -19,11 +20,13 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.graphics.YuvImage; -import android.hardware.Camera; import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera; +import android.os.Handler; +import android.os.Looper; import android.util.Log; -import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; +import android.view.SurfaceHolder; // Wrapper for android Camera, with support for direct local preview rendering. // Threading notes: this class is called from ViE C++ code, and from Camera & @@ -39,6 +42,8 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { private static SurfaceHolder localPreview; private Camera camera; // Only non-null while capturing. + private CameraThread cameraThread; + private Handler cameraThreadHandler; private final int id; private final Camera.CameraInfo info; private final long native_capturer; // |VideoCaptureAndroid*| in C++. @@ -61,15 +66,45 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { this.native_capturer = native_capturer; this.info = new Camera.CameraInfo(); Camera.getCameraInfo(id, info); + Exchanger handlerExchanger = new Exchanger(); + cameraThread = new CameraThread(handlerExchanger); + cameraThread.start(); + cameraThreadHandler = exchange(handlerExchanger, null); + } + + private class CameraThread extends Thread { + private Exchanger handlerExchanger; + public CameraThread(Exchanger handlerExchanger) { + this.handlerExchanger = handlerExchanger; + } + + @Override public void run() { + Looper.prepare(); + exchange(handlerExchanger, new Handler()); + Looper.loop(); + } } // Called by native code. Returns true if capturer is started. // - // Note that this actually opens the camera, which can be a slow operation and - // thus might be done on a background thread, but ViE API needs a - // synchronous success return value so we can't do that. + // Note that this actually opens the camera, and Camera callbacks run on the + // thread that calls open(), so this is done on the CameraThread. Since ViE + // API needs a synchronous success return value we wait for the result. private synchronized boolean startCapture( - int width, int height, int min_mfps, int max_mfps) { + final int width, final int height, + final int min_mfps, final int max_mfps) { + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + startCaptureOnCameraThread(width, height, min_mfps, max_mfps, result); + } + }); + return exchange(result, false); // |false| is a dummy value here. + } + + private void startCaptureOnCameraThread( + int width, int height, int min_mfps, int max_mfps, + Exchanger result) { Log.d(TAG, "startCapture: " + width + "x" + height + "@" + min_mfps + ":" + max_mfps); Throwable error = null; @@ -114,7 +149,8 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { } camera.setPreviewCallbackWithBuffer(this); camera.startPreview(); - return true; + exchange(result, true); + return; } catch (IOException e) { error = e; } catch (RuntimeException e) { @@ -122,13 +158,27 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { } Log.e(TAG, "startCapture failed", error); if (camera != null) { - stopCapture(); + Exchanger resultDropper = new Exchanger(); + stopCaptureOnCameraThread(resultDropper); + exchange(resultDropper, false); } - return false; + exchange(result, false); + return; } // Called by native code. Returns true when camera is known to be stopped. private synchronized boolean stopCapture() { + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + stopCaptureOnCameraThread(result); + } + }); + return exchange(result, false); // |false| is a dummy value here. + } + + private void stopCaptureOnCameraThread( + Exchanger result) { Log.d(TAG, "stopCapture"); if (camera == null) { throw new RuntimeException("Camera is already stopped!"); @@ -145,14 +195,16 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { } camera.release(); camera = null; - return true; + exchange(result, true); + return; } catch (IOException e) { error = e; } catch (RuntimeException e) { error = e; } Log.e(TAG, "Failed to stop camera", error); - return false; + exchange(result, false); + return; } private native void ProvideCameraFrame( @@ -172,7 +224,15 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { // Sets the rotation of the preview render window. // Does not affect the captured video image. // Called by native code. - private synchronized void setPreviewRotation(int rotation) { + private synchronized void setPreviewRotation(final int rotation) { + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewRotationOnCameraThread(rotation); + } + }); + } + + private void setPreviewRotationOnCameraThread(int rotation) { Log.v(TAG, "setPreviewRotation:" + rotation); if (camera == null) { @@ -197,14 +257,19 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { format + ": " + width + "x" + height); } - public synchronized void surfaceCreated(SurfaceHolder holder) { + public synchronized void surfaceCreated(final SurfaceHolder holder) { Log.d(TAG, "VideoCaptureAndroid::surfaceCreated"); if (camera == null) { return; } - try { - camera.setPreviewDisplay(holder); - } catch (IOException e) { + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewDisplayOnCameraThread(holder, result); + } + }); + IOException e = exchange(result, null); // |null| is a dummy value here. + if (e != null) { throw new RuntimeException(e); } } @@ -214,9 +279,36 @@ public class VideoCaptureAndroid implements PreviewCallback, Callback { if (camera == null) { return; } + final Exchanger result = new Exchanger(); + cameraThreadHandler.post(new Runnable() { + @Override public void run() { + setPreviewDisplayOnCameraThread(null, result); + } + }); + IOException e = exchange(result, null); // |null| is a dummy value here. + if (e != null) { + throw new RuntimeException(e); + } + } + + private void setPreviewDisplayOnCameraThread( + SurfaceHolder holder, Exchanger result) { try { - camera.setPreviewDisplay(null); + camera.setPreviewDisplay(holder); } catch (IOException e) { + exchange(result, e); + return; + } + exchange(result, null); + return; + } + + // Exchanges |value| with |exchanger|, converting InterruptedExceptions to + // RuntimeExceptions (since we expect never to see these). + private static T exchange(Exchanger exchanger, T value) { + try { + return exchanger.exchange(value); + } catch (InterruptedException e) { throw new RuntimeException(e); } } diff --git a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java index f23e9a8a40..1d681c0608 100644 --- a/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java +++ b/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java @@ -57,17 +57,22 @@ public class VideoCaptureDeviceInfoAndroid { devices.put(cameraDict); List supportedSizes; List supportedFpsRanges; + Camera camera = null; try { - Camera camera = Camera.open(i); + camera = Camera.open(i); Parameters parameters = camera.getParameters(); supportedSizes = parameters.getSupportedPreviewSizes(); supportedFpsRanges = parameters.getSupportedPreviewFpsRange(); - camera.release(); Log.d(TAG, uniqueName); } catch (RuntimeException e) { - Log.e(TAG, "Failed to open " + uniqueName + ", skipping"); + Log.e(TAG, "Failed to open " + uniqueName + ", skipping", e); continue; + } finally { + if (camera != null) { + camera.release(); + } } + JSONArray sizes = new JSONArray(); for (Size supportedSize : supportedSizes) { JSONObject size = new JSONObject(); @@ -75,16 +80,23 @@ public class VideoCaptureDeviceInfoAndroid { size.put("height", supportedSize.height); sizes.put(size); } - // Android SDK deals in integral "milliframes per second" - // (i.e. fps*1000, instead of floating-point frames-per-second) so we - // preserve that through the Java->C++->Java round-trip. - int[] mfps = supportedFpsRanges.get(supportedFpsRanges.size() - 1); + + JSONArray mfpsRanges = new JSONArray(); + for (int[] range : supportedFpsRanges) { + JSONObject mfpsRange = new JSONObject(); + // Android SDK deals in integral "milliframes per second" + // (i.e. fps*1000, instead of floating-point frames-per-second) so we + // preserve that through the Java->C++->Java round-trip. + mfpsRange.put("min_mfps", range[Parameters.PREVIEW_FPS_MIN_INDEX]); + mfpsRange.put("max_mfps", range[Parameters.PREVIEW_FPS_MAX_INDEX]); + mfpsRanges.put(mfpsRange); + } + cameraDict.put("name", uniqueName); cameraDict.put("front_facing", isFrontFacing(info)) .put("orientation", info.orientation) .put("sizes", sizes) - .put("min_mfps", mfps[Parameters.PREVIEW_FPS_MIN_INDEX]) - .put("max_mfps", mfps[Parameters.PREVIEW_FPS_MAX_INDEX]); + .put("mfpsRanges", mfpsRanges); } String ret = devices.toString(2); return ret; diff --git a/webrtc/modules/video_capture/android/video_capture_android.cc b/webrtc/modules/video_capture/android/video_capture_android.cc index 2b6d60644f..93c71922c5 100644 --- a/webrtc/modules/video_capture/android/video_capture_android.cc +++ b/webrtc/modules/video_capture/android/video_capture_android.cc @@ -39,6 +39,7 @@ void JNICALL ProvideCameraFrame( } int32_t SetCaptureAndroidVM(JavaVM* javaVM) { + assert(!g_jvm); g_jvm = javaVM; AttachThreadScoped ats(g_jvm); @@ -143,7 +144,8 @@ int32_t VideoCaptureAndroid::StartCapture( assert(j_start); int min_mfps = 0; int max_mfps = 0; - _deviceInfo.GetFpsRange(_deviceUniqueId, &min_mfps, &max_mfps); + _deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS, + &min_mfps, &max_mfps); bool started = env->CallBooleanMethod(_jCapturer, j_start, _captureCapability.width, _captureCapability.height, diff --git a/webrtc/modules/video_capture/ensure_initialized.cc b/webrtc/modules/video_capture/ensure_initialized.cc new file mode 100644 index 0000000000..69f22ede76 --- /dev/null +++ b/webrtc/modules/video_capture/ensure_initialized.cc @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014 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. + */ + +// Platform-specific initialization bits, if any, go here. + +#if !defined(ANDROID) || !defined(WEBRTC_CHROMIUM_BUILD) + +namespace webrtc { +namespace videocapturemodule { +void EnsureInitialized() {} +} // namespace videocapturemodule +} // namespace webrtc + +#else // !defined(ANDROID) || !defined(WEBRTC_CHROMIUM_BUILD) + +#include +#include + +#include "base/android/jni_android.h" + +namespace webrtc { + +// Declared in webrtc/modules/video_capture/include/video_capture.h. +int32_t SetCaptureAndroidVM(JavaVM* javaVM); + +namespace videocapturemodule { + +static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT; + +void EnsureInitializedOnce() { + JNIEnv* jni = ::base::android::AttachCurrentThread(); + JavaVM* jvm = NULL; + int status = jni->GetJavaVM(&jvm); + assert(status == 0); + status = webrtc::SetCaptureAndroidVM(jvm) == 0; + assert(status); +} + +void EnsureInitialized() { + int ret = pthread_once(&g_initialize_once, &EnsureInitializedOnce); + assert(ret == 0); +} + +} // namespace videocapturemodule +} // namespace webrtc + +#endif // ANDROID & WEBRTC_CHROMIUM_BUILD diff --git a/webrtc/modules/video_capture/ensure_initialized.h b/webrtc/modules/video_capture/ensure_initialized.h new file mode 100644 index 0000000000..429879537c --- /dev/null +++ b/webrtc/modules/video_capture/ensure_initialized.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 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. + */ + +namespace webrtc { +namespace videocapturemodule { + +// Ensure any necessary initialization of webrtc::videocapturemodule has +// completed. +void EnsureInitialized(); + +} // namespace videocapturemodule. +} // namespace webrtc. diff --git a/webrtc/modules/video_capture/include/video_capture.h b/webrtc/modules/video_capture/include/video_capture.h index 5340cb7311..6966c23812 100644 --- a/webrtc/modules/video_capture/include/video_capture.h +++ b/webrtc/modules/video_capture/include/video_capture.h @@ -20,7 +20,7 @@ namespace webrtc { -#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD) +#if defined(ANDROID) int32_t SetCaptureAndroidVM(JavaVM* javaVM); #endif diff --git a/webrtc/modules/video_capture/test/video_capture_unittest.cc b/webrtc/modules/video_capture/test/video_capture_unittest.cc index 98b6aa61a0..db0c81872f 100644 --- a/webrtc/modules/video_capture/test/video_capture_unittest.cc +++ b/webrtc/modules/video_capture/test/video_capture_unittest.cc @@ -10,10 +10,14 @@ #include +#include +#include + #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_video/interface/i420_video_frame.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/utility/interface/process_thread.h" +#include "webrtc/modules/video_capture/ensure_initialized.h" #include "webrtc/modules/video_capture/include/video_capture.h" #include "webrtc/modules/video_capture/include/video_capture_factory.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" @@ -231,7 +235,9 @@ class VideoCaptureTest : public testing::Test { VideoCaptureTest() : number_of_devices_(0) {} void SetUp() { - device_info_.reset(VideoCaptureFactory::CreateDeviceInfo(5)); + webrtc::videocapturemodule::EnsureInitialized(); + device_info_.reset(VideoCaptureFactory::CreateDeviceInfo(0)); + assert(device_info_.get()); number_of_devices_ = device_info_->NumberOfDevices(); ASSERT_GT(number_of_devices_, 0u); } @@ -258,7 +264,7 @@ class VideoCaptureTest : public testing::Test { void StartCapture(VideoCaptureModule* capture_module, VideoCaptureCapability capability) { - EXPECT_EQ(0, capture_module->StartCapture(capability)); + ASSERT_EQ(0, capture_module->StartCapture(capability)); EXPECT_TRUE(capture_module->CaptureStarted()); VideoCaptureCapability resulting_capability; @@ -289,7 +295,7 @@ TEST_F(VideoCaptureTest, CreateDelete) { capability.rawType = webrtc::kVideoUnknown; #endif capture_observer.SetExpectedCapability(capability); - StartCapture(module.get(), capability); + ASSERT_NO_FATAL_FAILURE(StartCapture(module.get(), capability)); // Less than 4s to start the camera. EXPECT_LE(TickTime::MillisecondTimestamp() - start_time, 4000); @@ -323,17 +329,50 @@ TEST_F(VideoCaptureTest, Capabilities) { int number_of_capabilities = device_info_->NumberOfCapabilities( module->CurrentDeviceName()); EXPECT_GT(number_of_capabilities, 0); + // Key is x, value is vector of maxFPS values at that + // resolution. + typedef std::map > FrameRatesByResolution; + FrameRatesByResolution frame_rates_by_resolution; for (int i = 0; i < number_of_capabilities; ++i) { VideoCaptureCapability capability; EXPECT_EQ(0, device_info_->GetCapability(module->CurrentDeviceName(), i, capability)); + std::ostringstream resolutionStream; + resolutionStream << capability.width << "x" << capability.height; + resolutionStream.flush(); + std::string resolution = resolutionStream.str(); + frame_rates_by_resolution[resolution].push_back(capability.maxFPS); + + // Since Android presents so many resolution/FPS combinations and the test + // runner imposes a timeout, we only actually start the capture and test + // that a frame was captured for 2 frame-rates at each resolution. + if (frame_rates_by_resolution[resolution].size() > 2) + continue; + capture_observer.SetExpectedCapability(capability); - StartCapture(module.get(), capability); - // Make sure 5 frames are captured. - EXPECT_TRUE_WAIT(capture_observer.incoming_frames() >= 5, kTimeOut); + ASSERT_NO_FATAL_FAILURE(StartCapture(module.get(), capability)); + // Make sure at least one frame is captured. + EXPECT_TRUE_WAIT(capture_observer.incoming_frames() >= 1, kTimeOut); EXPECT_EQ(0, module->StopCapture()); } + +#if ANDROID + // There's no reason for this to _necessarily_ be true, but in practice all + // Android devices this test runs on in fact do support multiple capture + // resolutions and multiple frame-rates per captured resolution, so we assert + // this fact here as a regression-test against the time that we only noticed a + // single frame-rate per resolution (bug 2974). If this test starts being run + // on devices for which this is untrue (e.g. Nexus4) then the following should + // probably be wrapped in a base::android::BuildInfo::model()/device() check. + EXPECT_GT(frame_rates_by_resolution.size(), 1U); + for (FrameRatesByResolution::const_iterator it = + frame_rates_by_resolution.begin(); + it != frame_rates_by_resolution.end(); + ++it) { + EXPECT_GT(it->second.size(), 1U) << it->first; + } +#endif // ANDROID } // NOTE: flaky, crashes sometimes. @@ -376,10 +415,12 @@ TEST_F(VideoCaptureTest, DISABLED_TestTwoCameras) { #endif capture_observer2.SetExpectedCapability(capability2); - StartCapture(module1.get(), capability1); - StartCapture(module2.get(), capability2); + ASSERT_NO_FATAL_FAILURE(StartCapture(module1.get(), capability1)); + ASSERT_NO_FATAL_FAILURE(StartCapture(module2.get(), capability2)); EXPECT_TRUE_WAIT(capture_observer1.incoming_frames() >= 5, kTimeOut); EXPECT_TRUE_WAIT(capture_observer2.incoming_frames() >= 5, kTimeOut); + EXPECT_EQ(0, module2->StopCapture()); + EXPECT_EQ(0, module1->StopCapture()); } // Test class for testing external capture and capture feedback information diff --git a/webrtc/modules/video_capture/video_capture.gypi b/webrtc/modules/video_capture/video_capture.gypi index 970b05846c..7496683fea 100644 --- a/webrtc/modules/video_capture/video_capture.gypi +++ b/webrtc/modules/video_capture/video_capture.gypi @@ -19,6 +19,8 @@ 'sources': [ 'device_info_impl.cc', 'device_info_impl.h', + 'ensure_initialized.cc', + 'ensure_initialized.h', 'include/video_capture.h', 'include/video_capture_defines.h', 'include/video_capture_factory.h', @@ -137,7 +139,7 @@ 'targets': [ { 'target_name': 'video_capture_tests', - 'type': 'executable', + 'type': '<(gtest_target_type)', 'dependencies': [ 'video_capture_module', 'webrtc_utility', @@ -164,6 +166,13 @@ '-lX11', ], }], + # TODO(henrike): remove build_with_chromium==1 when the bots are + # using Chromium's buildbots. + ['build_with_chromium==1 and OS=="android" and gtest_target_type=="shared_library"', { + 'dependencies': [ + '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', + ], + }], ['OS=="mac"', { 'dependencies': [ # Link with a special main for mac so we can use the webcam.