Remove the video capture module on Android.
Video capture for android is now implemented in talk/app/webrtc/androidvideocapturer.h BUG=webrtc:4475 Review URL: https://codereview.webrtc.org/1347083003 Cr-Commit-Position: refs/heads/master@{#9995}
This commit is contained in:
parent
8902433a43
commit
35d1767cc3
@ -227,7 +227,6 @@
|
||||
'app/webrtc/java/android',
|
||||
'<(webrtc_base_dir)/java/src',
|
||||
'<(webrtc_modules_dir)/audio_device/android/java/src',
|
||||
'<(webrtc_modules_dir)/video_capture/android/java/src',
|
||||
'<(webrtc_modules_dir)/video_render/android/java/src',
|
||||
],
|
||||
},
|
||||
|
||||
@ -215,36 +215,6 @@
|
||||
'../../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',
|
||||
'additional_src_dirs': [ '<(webrtc_root)/base/java/src', ],
|
||||
},
|
||||
'includes': [
|
||||
'../../build/java.gypi',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'audio_device_java',
|
||||
'type': 'none',
|
||||
|
||||
@ -65,9 +65,5 @@
|
||||
'target_name': 'audio_codec_speed_tests_apk',
|
||||
'type': 'none',
|
||||
},
|
||||
{
|
||||
'target_name': 'video_capture_tests_apk',
|
||||
'type': 'none',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@ -1,338 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013 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.
|
||||
*/
|
||||
|
||||
package org.webrtc.webrtcdemo;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaExtractor;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
|
||||
class MediaCodecVideoDecoder {
|
||||
public static final int DECODE = 0;
|
||||
private enum CodecName { ON2_VP8, GOOGLE_VPX, EXYNOX_VP8 }
|
||||
|
||||
private void check(boolean value, String message) {
|
||||
if (value) {
|
||||
return;
|
||||
}
|
||||
Log.e("WEBRTC-CHECK", message);
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
|
||||
alertDialog.setTitle("WebRTC Error");
|
||||
alertDialog.setMessage(message);
|
||||
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE,
|
||||
"OK",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
class Frame {
|
||||
public ByteBuffer buffer;
|
||||
public long timestampUs;
|
||||
|
||||
Frame(ByteBuffer buffer, long timestampUs) {
|
||||
this.buffer = buffer;
|
||||
this.timestampUs = timestampUs;
|
||||
}
|
||||
}
|
||||
|
||||
// This class enables decoding being run on a separate thread.
|
||||
class DecodeHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
// TODO(dwkang): figure out exceptions just make this thread finish.
|
||||
try {
|
||||
switch (msg.what) {
|
||||
case DECODE:
|
||||
decodePendingBuffers();
|
||||
long delayMillis = 5; // Don't busy wait.
|
||||
handler.sendMessageDelayed(
|
||||
handler.obtainMessage(DECODE), delayMillis);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String TAG;
|
||||
private Context context;
|
||||
private SurfaceView surfaceView;
|
||||
|
||||
private DecodeHandler handler;
|
||||
private Thread looperThread;
|
||||
|
||||
MediaCodec codec;
|
||||
MediaFormat format;
|
||||
|
||||
// Buffers supplied by MediaCodec for pushing encoded data to and pulling
|
||||
// decoded data from.
|
||||
private ByteBuffer[] codecInputBuffers;
|
||||
private ByteBuffer[] codecOutputBuffers;
|
||||
|
||||
// Frames from the native layer.
|
||||
private LinkedList<Frame> frameQueue;
|
||||
// Indexes to MediaCodec buffers
|
||||
private LinkedList<Integer> availableInputBufferIndices;
|
||||
private LinkedList<Integer> availableOutputBufferIndices;
|
||||
private LinkedList<MediaCodec.BufferInfo> availableOutputBufferInfos;
|
||||
|
||||
// Offset between system time and media time.
|
||||
private long deltaTimeUs;
|
||||
|
||||
public MediaCodecVideoDecoder(Context context) {
|
||||
TAG = context.getString(R.string.tag);
|
||||
this.context = context;
|
||||
surfaceView = new SurfaceView(context);
|
||||
frameQueue = new LinkedList<Frame>();
|
||||
availableInputBufferIndices = new LinkedList<Integer>();
|
||||
availableOutputBufferIndices = new LinkedList<Integer>();
|
||||
availableOutputBufferInfos = new LinkedList<MediaCodec.BufferInfo>();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
codec.stop();
|
||||
codec.release();
|
||||
}
|
||||
|
||||
// Return view that is written to by MediaCodec.
|
||||
public SurfaceView getView() { return surfaceView; }
|
||||
|
||||
// Entry point from the native layer. Called when the class should be ready
|
||||
// to start receiving raw frames.
|
||||
private boolean start(int width, int height) {
|
||||
deltaTimeUs = -1;
|
||||
if (!setCodecState(width, height, CodecName.ON2_VP8)) {
|
||||
return false;
|
||||
}
|
||||
startLooperThread();
|
||||
// The decoding must happen on |looperThread| thread.
|
||||
handler.sendMessage(handler.obtainMessage(DECODE));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean setCodecState(int width, int height, CodecName codecName) {
|
||||
// TODO(henrike): enable more than ON2_VP8 codec.
|
||||
format = new MediaFormat();
|
||||
format.setInteger(MediaFormat.KEY_WIDTH, width);
|
||||
format.setInteger(MediaFormat.KEY_HEIGHT, height);
|
||||
try {
|
||||
switch (codecName) {
|
||||
case ON2_VP8:
|
||||
format.setString(MediaFormat.KEY_MIME, "video/x-vnd.on2.vp8");
|
||||
codec = MediaCodec.createDecoderByType("video/x-vnd.on2.vp8");
|
||||
break;
|
||||
case GOOGLE_VPX:
|
||||
// SW VP8 decoder
|
||||
codec = MediaCodec.createByCodecName("OMX.google.vpx.decoder");
|
||||
break;
|
||||
case EXYNOX_VP8:
|
||||
// Nexus10 HW VP8 decoder
|
||||
codec = MediaCodec.createByCodecName("OMX.Exynos.VP8.Decoder");
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO(dwkang): replace this instanceof/throw with a narrower catch
|
||||
// clause once the SDK advances.
|
||||
if (e instanceof IOException) {
|
||||
Log.e(TAG, "Failed to create MediaCodec for VP8.", e);
|
||||
return false;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Surface surface = surfaceView.getHolder().getSurface();
|
||||
MediaCrypto crypto = null; // No crypto.
|
||||
int flags = 0; // Decoder (1 for encoder)
|
||||
codec.configure(format, surface, crypto, flags);
|
||||
codec.start();
|
||||
codecInputBuffers = codec.getInputBuffers();
|
||||
codecOutputBuffers = codec.getOutputBuffers();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startLooperThread() {
|
||||
looperThread = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
// Handler that is run by this thread.
|
||||
handler = new DecodeHandler();
|
||||
// Notify that the thread has created a handler.
|
||||
synchronized(MediaCodecVideoDecoder.this) {
|
||||
MediaCodecVideoDecoder.this.notify();
|
||||
}
|
||||
Looper.loop();
|
||||
}
|
||||
};
|
||||
looperThread.start();
|
||||
// Wait for thread to notify that Handler has been set up.
|
||||
synchronized(this) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point from the native layer. It pushes the raw buffer to this class.
|
||||
private void pushBuffer(ByteBuffer buffer, long renderTimeMs) {
|
||||
// TODO(dwkang): figure out why exceptions just make this thread finish.
|
||||
try {
|
||||
final long renderTimeUs = renderTimeMs * 1000;
|
||||
synchronized(frameQueue) {
|
||||
frameQueue.add(new Frame(buffer, renderTimeUs));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasFrame() {
|
||||
synchronized(frameQueue) {
|
||||
return !frameQueue.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private Frame dequeueFrame() {
|
||||
synchronized(frameQueue) {
|
||||
return frameQueue.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private void flush() {
|
||||
availableInputBufferIndices.clear();
|
||||
availableOutputBufferIndices.clear();
|
||||
availableOutputBufferInfos.clear();
|
||||
|
||||
codec.flush();
|
||||
}
|
||||
|
||||
// Media time is relative to previous frame.
|
||||
private long mediaTimeToSystemTime(long mediaTimeUs) {
|
||||
if (deltaTimeUs == -1) {
|
||||
long nowUs = System.currentTimeMillis() * 1000;
|
||||
deltaTimeUs = nowUs - mediaTimeUs;
|
||||
}
|
||||
return deltaTimeUs + mediaTimeUs;
|
||||
}
|
||||
|
||||
private void decodePendingBuffers() {
|
||||
int timeoutUs = 0; // Don't block on dequeuing input buffer.
|
||||
|
||||
int index = codec.dequeueInputBuffer(timeoutUs);
|
||||
if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||
availableInputBufferIndices.add(index);
|
||||
}
|
||||
while (feedInputBuffer()) {}
|
||||
|
||||
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
|
||||
index = codec.dequeueOutputBuffer(info, timeoutUs);
|
||||
if (index > 0) {
|
||||
availableOutputBufferIndices.add(index);
|
||||
availableOutputBufferInfos.add(info);
|
||||
}
|
||||
if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
||||
codecOutputBuffers = codec.getOutputBuffers();
|
||||
}
|
||||
|
||||
while (drainOutputBuffer()) {}
|
||||
}
|
||||
|
||||
// Returns true if MediaCodec is ready for more data and there was data
|
||||
// available from the native layer.
|
||||
private boolean feedInputBuffer() {
|
||||
if (availableInputBufferIndices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!hasFrame()) {
|
||||
return false;
|
||||
}
|
||||
Frame frame = dequeueFrame();
|
||||
ByteBuffer buffer = frame.buffer;
|
||||
|
||||
int index = availableInputBufferIndices.pollFirst();
|
||||
ByteBuffer codecData = codecInputBuffers[index];
|
||||
check(codecData.capacity() >= buffer.capacity(),
|
||||
"Buffer is too small to copy a frame.");
|
||||
buffer.rewind();
|
||||
codecData.rewind();
|
||||
codecData.put(buffer);
|
||||
|
||||
try {
|
||||
int offset = 0;
|
||||
int flags = 0;
|
||||
codec.queueInputBuffer(index, offset, buffer.capacity(),
|
||||
frame.timestampUs, flags);
|
||||
} catch (MediaCodec.CryptoException e) {
|
||||
check(false, "CryptoException w/ errorCode " + e.getErrorCode() +
|
||||
", '" + e.getMessage() + "'");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if more output data could be drained.MediaCodec has more data
|
||||
// to deliver.
|
||||
private boolean drainOutputBuffer() {
|
||||
if (availableOutputBufferIndices.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = availableOutputBufferIndices.peekFirst();
|
||||
MediaCodec.BufferInfo info = availableOutputBufferInfos.peekFirst();
|
||||
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
|
||||
// End of stream is unexpected with streamed video.
|
||||
check(false, "Saw output end of stream.");
|
||||
return false;
|
||||
}
|
||||
long realTimeUs = mediaTimeToSystemTime(info.presentationTimeUs);
|
||||
long nowUs = System.currentTimeMillis() * 1000;
|
||||
long lateUs = nowUs - realTimeUs;
|
||||
if (lateUs < -10000) {
|
||||
// Frame should not be presented yet.
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(dwkang): For some extreme cases, just not doing rendering is not
|
||||
// enough. Need to seek to the next key frame.
|
||||
boolean render = lateUs <= 30000;
|
||||
if (!render) {
|
||||
Log.d(TAG, "video late by " + lateUs + " us. Skipping...");
|
||||
}
|
||||
// Decode and render to surface if desired.
|
||||
codec.releaseOutputBuffer(index, render);
|
||||
availableOutputBufferIndices.removeFirst();
|
||||
availableOutputBufferInfos.removeFirst();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -18,18 +18,6 @@
|
||||
|
||||
'includes': [ '../../build/java.gypi' ],
|
||||
}, # audio_device_module_java
|
||||
{
|
||||
'target_name': 'video_capture_module_java',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'video_render_module_java',
|
||||
],
|
||||
'variables': {
|
||||
'java_in_dir': 'video_capture/android/java',
|
||||
'additional_src_dirs': [ '../base/java/src', ],
|
||||
},
|
||||
'includes': [ '../../build/java.gypi' ],
|
||||
}, # video_capture_module_java
|
||||
{
|
||||
'target_name': 'video_render_module_java',
|
||||
'type': 'none',
|
||||
|
||||
@ -16,17 +16,6 @@
|
||||
},
|
||||
'includes': [ '../../../build/java.gypi' ],
|
||||
}, # audio_device_module_java
|
||||
{
|
||||
'target_name': 'video_capture_module_java',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'video_render_module_java',
|
||||
],
|
||||
'variables': {
|
||||
'java_in_dir': 'video_capture/android/java',
|
||||
},
|
||||
'includes': [ '../../../build/java.gypi' ],
|
||||
}, # video_capture_module_java
|
||||
{
|
||||
'target_name': 'video_render_module_java',
|
||||
'type': 'none',
|
||||
|
||||
@ -130,21 +130,6 @@ if (!build_with_chromium) {
|
||||
|
||||
deps += [ "//third_party/winsdk_samples" ]
|
||||
}
|
||||
if (is_android) {
|
||||
sources = [
|
||||
"android/device_info_android.cc",
|
||||
"android/device_info_android.h",
|
||||
"android/video_capture_android.cc",
|
||||
"android/video_capture_android.h",
|
||||
]
|
||||
|
||||
if (rtc_build_json) {
|
||||
deps += [ "//third_party/jsoncpp" ]
|
||||
}
|
||||
if (rtc_build_icu) {
|
||||
deps += [ "//third_party/icu:icuuc" ]
|
||||
}
|
||||
}
|
||||
if (is_ios) {
|
||||
sources = [
|
||||
"ios/device_info_ios.h",
|
||||
|
||||
@ -1,256 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/video_capture/android/device_info_android.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "unicode/unistr.h"
|
||||
#include "webrtc/base/json.h"
|
||||
#include "webrtc/modules/video_capture/android/video_capture_android.h"
|
||||
#include "webrtc/system_wrappers/interface/logging.h"
|
||||
#include "webrtc/system_wrappers/interface/ref_count.h"
|
||||
#include "webrtc/system_wrappers/interface/trace.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::Initialize() and immutable
|
||||
// thereafter.
|
||||
static std::vector<AndroidCameraInfo>* g_camera_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(JNIEnv* jni) {
|
||||
// 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_camera_info = new std::vector<AndroidCameraInfo>();
|
||||
jclass j_info_class =
|
||||
jni->FindClass("org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid");
|
||||
assert(j_info_class);
|
||||
jmethodID j_initialize = jni->GetStaticMethodID(
|
||||
j_info_class, "getDeviceInfo", "()Ljava/lang/String;");
|
||||
jstring j_json_info = static_cast<jstring>(
|
||||
jni->CallStaticObjectMethod(j_info_class, j_initialize));
|
||||
|
||||
const jchar* jchars = jni->GetStringChars(j_json_info, NULL);
|
||||
icu::UnicodeString ustr(jchars, jni->GetStringLength(j_json_info));
|
||||
jni->ReleaseStringChars(j_json_info, jchars);
|
||||
std::string json_info;
|
||||
ustr.toUTF8String(json_info);
|
||||
|
||||
Json::Value cameras;
|
||||
Json::Reader reader(Json::Features::strictMode());
|
||||
bool parsed = reader.parse(json_info, cameras);
|
||||
if (!parsed) {
|
||||
std::stringstream stream;
|
||||
stream << "Failed to parse configuration:\n"
|
||||
<< reader.getFormattedErrorMessages();
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
for (Json::ArrayIndex i = 0; i < cameras.size(); ++i) {
|
||||
const Json::Value& camera = cameras[i];
|
||||
AndroidCameraInfo info;
|
||||
info.name = camera["name"].asString();
|
||||
info.front_facing = camera["front_facing"].asBool();
|
||||
info.orientation = camera["orientation"].asInt();
|
||||
Json::Value sizes = camera["sizes"];
|
||||
for (Json::ArrayIndex j = 0; j < sizes.size(); ++j) {
|
||||
const Json::Value& size = sizes[j];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceInfoAndroid::DeInitialize() {
|
||||
if (g_camera_info) {
|
||||
delete g_camera_info;
|
||||
g_camera_info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
VideoCaptureModule::DeviceInfo* VideoCaptureImpl::CreateDeviceInfo(
|
||||
const int32_t id) {
|
||||
return new videocapturemodule::DeviceInfoAndroid(id);
|
||||
}
|
||||
|
||||
DeviceInfoAndroid::DeviceInfoAndroid(const int32_t id) :
|
||||
DeviceInfoImpl(id) {
|
||||
}
|
||||
|
||||
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() {
|
||||
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*/) {
|
||||
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.expectedCaptureDelay = kExpectedCaptureDelay;
|
||||
cap.rawType = kVideoNV21;
|
||||
_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;
|
||||
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;
|
||||
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
|
||||
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_DEVICE_INFO_ANDROID_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_DEVICE_INFO_ANDROID_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "webrtc/modules/video_capture/device_info_impl.h"
|
||||
#include "webrtc/modules/video_capture/video_capture_impl.h"
|
||||
|
||||
namespace webrtc
|
||||
{
|
||||
namespace videocapturemodule
|
||||
{
|
||||
|
||||
class DeviceInfoAndroid : public DeviceInfoImpl {
|
||||
public:
|
||||
static void Initialize(JNIEnv* env);
|
||||
static void DeInitialize();
|
||||
|
||||
DeviceInfoAndroid(int32_t id);
|
||||
virtual ~DeviceInfoAndroid();
|
||||
|
||||
// Set |*index| to the index of the camera matching |deviceUniqueIdUTF8|, or
|
||||
// return false if no match.
|
||||
bool FindCameraIndex(const char* deviceUniqueIdUTF8, size_t* index);
|
||||
|
||||
virtual int32_t Init();
|
||||
virtual uint32_t NumberOfDevices();
|
||||
virtual int32_t GetDeviceName(
|
||||
uint32_t deviceNumber,
|
||||
char* deviceNameUTF8,
|
||||
uint32_t deviceNameLength,
|
||||
char* deviceUniqueIdUTF8,
|
||||
uint32_t deviceUniqueIdUTF8Length,
|
||||
char* productUniqueIdUTF8 = 0,
|
||||
uint32_t productUniqueIdUTF8Length = 0);
|
||||
virtual int32_t CreateCapabilityMap(const char* deviceUniqueIdUTF8);
|
||||
|
||||
virtual int32_t DisplayCaptureSettingsDialogBox(
|
||||
const char* /*deviceUniqueIdUTF8*/,
|
||||
const char* /*dialogTitleUTF8*/,
|
||||
void* /*parentWindow*/,
|
||||
uint32_t /*positionX*/,
|
||||
uint32_t /*positionY*/) { return -1; }
|
||||
virtual int32_t GetOrientation(const char* deviceUniqueIdUTF8,
|
||||
VideoRotation& orientation);
|
||||
|
||||
// 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};
|
||||
};
|
||||
|
||||
} // namespace videocapturemodule
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_DEVICE_INFO_ANDROID_H_
|
||||
@ -1,463 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.webrtc.videoengine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Exchanger;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.hardware.Camera.PreviewCallback;
|
||||
import android.hardware.Camera;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder.Callback;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.webrtc.Logging;
|
||||
|
||||
// Wrapper for android Camera, with support for direct local preview rendering.
|
||||
// Threading notes: this class is called from ViE C++ code, and from Camera &
|
||||
// SurfaceHolder Java callbacks. Since these calls happen on different threads,
|
||||
// the entry points to this class are all synchronized. This shouldn't present
|
||||
// a performance bottleneck because only onPreviewFrame() is called more than
|
||||
// once (and is called serially on a single thread), so the lock should be
|
||||
// uncontended. Note that each of these synchronized methods must check
|
||||
// |camera| for null to account for having possibly waited for stopCapture() to
|
||||
// complete.
|
||||
public class VideoCaptureAndroid implements PreviewCallback, Callback {
|
||||
private final static String TAG = "WEBRTC-JC";
|
||||
|
||||
private static SurfaceHolder localPreview;
|
||||
private Camera camera; // Only non-null while capturing.
|
||||
private CameraThread cameraThread;
|
||||
private Handler cameraThreadHandler;
|
||||
private Context context;
|
||||
private final int id;
|
||||
private final Camera.CameraInfo info;
|
||||
private final long native_capturer; // |VideoCaptureAndroid*| in C++.
|
||||
private SurfaceTexture cameraSurfaceTexture;
|
||||
private int[] cameraGlTextures = null;
|
||||
// Arbitrary queue depth. Higher number means more memory allocated & held,
|
||||
// lower number means more sensitivity to processing time in the client (and
|
||||
// potentially stalling the capturer if it runs out of buffers to write to).
|
||||
private final int numCaptureBuffers = 3;
|
||||
private double averageDurationMs;
|
||||
private long lastCaptureTimeMs;
|
||||
private int frameCount;
|
||||
private int frameDropRatio;
|
||||
|
||||
// Requests future capturers to send their frames to |localPreview| directly.
|
||||
public static void setLocalPreview(SurfaceHolder localPreview) {
|
||||
// It is a gross hack that this is a class-static. Doing it right would
|
||||
// mean plumbing this through the C++ API and using it from
|
||||
// webrtc/examples/android/media_demo's MediaEngine class.
|
||||
VideoCaptureAndroid.localPreview = localPreview;
|
||||
}
|
||||
|
||||
public VideoCaptureAndroid(int id, long native_capturer) {
|
||||
this.id = id;
|
||||
this.native_capturer = native_capturer;
|
||||
this.context = GetContext();
|
||||
this.info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(id, info);
|
||||
}
|
||||
|
||||
// Return the global application context.
|
||||
private static native Context GetContext();
|
||||
|
||||
private class CameraThread extends Thread {
|
||||
private Exchanger<Handler> handlerExchanger;
|
||||
public CameraThread(Exchanger<Handler> 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, 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(
|
||||
final int width, final int height,
|
||||
final int min_mfps, final int max_mfps) {
|
||||
Logging.d(TAG, "startCapture: " + width + "x" + height + "@" +
|
||||
min_mfps + ":" + max_mfps);
|
||||
if (cameraThread != null || cameraThreadHandler != null) {
|
||||
throw new RuntimeException("Camera thread already started!");
|
||||
}
|
||||
Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
|
||||
cameraThread = new CameraThread(handlerExchanger);
|
||||
cameraThread.start();
|
||||
cameraThreadHandler = exchange(handlerExchanger, null);
|
||||
|
||||
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
||||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
startCaptureOnCameraThread(width, height, min_mfps, max_mfps, result);
|
||||
}
|
||||
});
|
||||
boolean startResult = exchange(result, false); // |false| is a dummy value.
|
||||
return startResult;
|
||||
}
|
||||
|
||||
private void startCaptureOnCameraThread(
|
||||
int width, int height, int min_mfps, int max_mfps,
|
||||
Exchanger<Boolean> result) {
|
||||
Throwable error = null;
|
||||
try {
|
||||
camera = Camera.open(id);
|
||||
|
||||
if (localPreview != null) {
|
||||
localPreview.addCallback(this);
|
||||
if (localPreview.getSurface() != null &&
|
||||
localPreview.getSurface().isValid()) {
|
||||
camera.setPreviewDisplay(localPreview);
|
||||
}
|
||||
} else {
|
||||
// No local renderer (we only care about onPreviewFrame() buffers, not a
|
||||
// directly-displayed UI element). Camera won't capture without
|
||||
// setPreview{Texture,Display}, so we create a SurfaceTexture and hand
|
||||
// it over to Camera, but never listen for frame-ready callbacks,
|
||||
// and never call updateTexImage on it.
|
||||
try {
|
||||
cameraGlTextures = new int[1];
|
||||
// Generate one texture pointer and bind it as an external texture.
|
||||
GLES20.glGenTextures(1, cameraGlTextures, 0);
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
cameraGlTextures[0]);
|
||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
|
||||
cameraSurfaceTexture = new SurfaceTexture(cameraGlTextures[0]);
|
||||
cameraSurfaceTexture.setOnFrameAvailableListener(null);
|
||||
camera.setPreviewTexture(cameraSurfaceTexture);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Logging.d(TAG, "Camera orientation: " + info.orientation +
|
||||
" .Device orientation: " + getDeviceOrientation());
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
Logging.d(TAG, "isVideoStabilizationSupported: " +
|
||||
parameters.isVideoStabilizationSupported());
|
||||
if (parameters.isVideoStabilizationSupported()) {
|
||||
parameters.setVideoStabilization(true);
|
||||
}
|
||||
parameters.setPictureSize(width, height);
|
||||
parameters.setPreviewSize(width, height);
|
||||
|
||||
// Check if requested fps range is supported by camera,
|
||||
// otherwise calculate frame drop ratio.
|
||||
List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
|
||||
frameDropRatio = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < supportedFpsRanges.size(); i++) {
|
||||
int[] range = supportedFpsRanges.get(i);
|
||||
if (range[Parameters.PREVIEW_FPS_MIN_INDEX] == min_mfps &&
|
||||
range[Parameters.PREVIEW_FPS_MAX_INDEX] == max_mfps) {
|
||||
frameDropRatio = 1;
|
||||
break;
|
||||
}
|
||||
if (range[Parameters.PREVIEW_FPS_MIN_INDEX] % min_mfps == 0 &&
|
||||
range[Parameters.PREVIEW_FPS_MAX_INDEX] % max_mfps == 0) {
|
||||
int dropRatio = range[Parameters.PREVIEW_FPS_MAX_INDEX] / max_mfps;
|
||||
frameDropRatio = Math.min(dropRatio, frameDropRatio);
|
||||
}
|
||||
}
|
||||
if (frameDropRatio == Integer.MAX_VALUE) {
|
||||
Logging.e(TAG, "Can not find camera fps range");
|
||||
error = new RuntimeException("Can not find camera fps range");
|
||||
exchange(result, false);
|
||||
return;
|
||||
}
|
||||
if (frameDropRatio > 1) {
|
||||
Logging.d(TAG, "Frame dropper is enabled. Ratio: " + frameDropRatio);
|
||||
}
|
||||
min_mfps *= frameDropRatio;
|
||||
max_mfps *= frameDropRatio;
|
||||
Logging.d(TAG, "Camera preview mfps range: " + min_mfps + " - " + max_mfps);
|
||||
parameters.setPreviewFpsRange(min_mfps, max_mfps);
|
||||
|
||||
int format = ImageFormat.NV21;
|
||||
parameters.setPreviewFormat(format);
|
||||
camera.setParameters(parameters);
|
||||
int bufSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
|
||||
for (int i = 0; i < numCaptureBuffers; i++) {
|
||||
camera.addCallbackBuffer(new byte[bufSize]);
|
||||
}
|
||||
camera.setPreviewCallbackWithBuffer(this);
|
||||
frameCount = 0;
|
||||
averageDurationMs = 1000000.0f / (max_mfps / frameDropRatio);
|
||||
camera.startPreview();
|
||||
exchange(result, true);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
error = e;
|
||||
} catch (RuntimeException e) {
|
||||
error = e;
|
||||
}
|
||||
Logging.e(TAG, "startCapture failed", error);
|
||||
if (camera != null) {
|
||||
Exchanger<Boolean> resultDropper = new Exchanger<Boolean>();
|
||||
stopCaptureOnCameraThread(resultDropper);
|
||||
exchange(resultDropper, false);
|
||||
}
|
||||
exchange(result, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Called by native code. Returns true when camera is known to be stopped.
|
||||
private synchronized boolean stopCapture() {
|
||||
Logging.d(TAG, "stopCapture");
|
||||
final Exchanger<Boolean> result = new Exchanger<Boolean>();
|
||||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
stopCaptureOnCameraThread(result);
|
||||
}
|
||||
});
|
||||
boolean status = exchange(result, false); // |false| is a dummy value here.
|
||||
try {
|
||||
cameraThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
cameraThreadHandler = null;
|
||||
cameraThread = null;
|
||||
Logging.d(TAG, "stopCapture done");
|
||||
return status;
|
||||
}
|
||||
|
||||
private void stopCaptureOnCameraThread(
|
||||
Exchanger<Boolean> result) {
|
||||
if (camera == null) {
|
||||
throw new RuntimeException("Camera is already stopped!");
|
||||
}
|
||||
Throwable error = null;
|
||||
try {
|
||||
camera.stopPreview();
|
||||
camera.setPreviewCallbackWithBuffer(null);
|
||||
if (localPreview != null) {
|
||||
localPreview.removeCallback(this);
|
||||
camera.setPreviewDisplay(null);
|
||||
} else {
|
||||
camera.setPreviewTexture(null);
|
||||
cameraSurfaceTexture = null;
|
||||
if (cameraGlTextures != null) {
|
||||
GLES20.glDeleteTextures(1, cameraGlTextures, 0);
|
||||
cameraGlTextures = null;
|
||||
}
|
||||
}
|
||||
camera.release();
|
||||
camera = null;
|
||||
exchange(result, true);
|
||||
Looper.myLooper().quit();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
error = e;
|
||||
} catch (RuntimeException e) {
|
||||
error = e;
|
||||
}
|
||||
Logging.e(TAG, "Failed to stop camera", error);
|
||||
exchange(result, false);
|
||||
Looper.myLooper().quit();
|
||||
return;
|
||||
}
|
||||
|
||||
private int getDeviceOrientation() {
|
||||
int orientation = 0;
|
||||
if (context != null) {
|
||||
WindowManager wm = (WindowManager) context.getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
switch(wm.getDefaultDisplay().getRotation()) {
|
||||
case Surface.ROTATION_90:
|
||||
orientation = 90;
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
orientation = 180;
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
orientation = 270;
|
||||
break;
|
||||
case Surface.ROTATION_0:
|
||||
default:
|
||||
orientation = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
|
||||
private native void ProvideCameraFrame(
|
||||
byte[] data, int length, int rotation, long timeStamp, long captureObject);
|
||||
|
||||
// Called on cameraThread so must not "synchronized".
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, Camera callbackCamera) {
|
||||
if (Thread.currentThread() != cameraThread) {
|
||||
throw new RuntimeException("Camera callback not on camera thread?!?");
|
||||
}
|
||||
if (camera == null) {
|
||||
return;
|
||||
}
|
||||
if (camera != callbackCamera) {
|
||||
throw new RuntimeException("Unexpected camera in callback!");
|
||||
}
|
||||
frameCount++;
|
||||
// Check if frame needs to be dropped.
|
||||
if ((frameDropRatio > 1) && (frameCount % frameDropRatio) > 0) {
|
||||
camera.addCallbackBuffer(data);
|
||||
return;
|
||||
}
|
||||
long captureTimeMs = SystemClock.elapsedRealtime();
|
||||
if (frameCount > frameDropRatio) {
|
||||
double durationMs = captureTimeMs - lastCaptureTimeMs;
|
||||
averageDurationMs = 0.9 * averageDurationMs + 0.1 * durationMs;
|
||||
if ((frameCount % 30) == 0) {
|
||||
Logging.d(TAG, "Camera TS " + captureTimeMs +
|
||||
". Duration: " + (int)durationMs + " ms. FPS: " +
|
||||
(int) (1000 / averageDurationMs + 0.5));
|
||||
}
|
||||
}
|
||||
lastCaptureTimeMs = captureTimeMs;
|
||||
|
||||
int rotation = getDeviceOrientation();
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
||||
rotation = 360 - rotation;
|
||||
}
|
||||
rotation = (info.orientation + rotation) % 360;
|
||||
|
||||
ProvideCameraFrame(data, data.length, rotation,
|
||||
captureTimeMs, native_capturer);
|
||||
camera.addCallbackBuffer(data);
|
||||
}
|
||||
|
||||
// Sets the rotation of the preview render window.
|
||||
// Does not affect the captured video image.
|
||||
// Called by native code.
|
||||
private synchronized void setPreviewRotation(final int rotation) {
|
||||
if (camera == null || cameraThreadHandler == null) {
|
||||
return;
|
||||
}
|
||||
final Exchanger<IOException> result = new Exchanger<IOException>();
|
||||
cameraThreadHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
setPreviewRotationOnCameraThread(rotation, result);
|
||||
}
|
||||
});
|
||||
// Use the exchanger below to block this function until
|
||||
// setPreviewRotationOnCameraThread() completes, holding the synchronized
|
||||
// lock for the duration. The exchanged value itself is ignored.
|
||||
exchange(result, null);
|
||||
}
|
||||
|
||||
private void setPreviewRotationOnCameraThread(
|
||||
int rotation, Exchanger<IOException> result) {
|
||||
Logging.v(TAG, "setPreviewRotation:" + rotation);
|
||||
|
||||
int resultRotation = 0;
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
// This is a front facing camera. SetDisplayOrientation will flip
|
||||
// the image horizontally before doing the rotation.
|
||||
resultRotation = ( 360 - rotation ) % 360; // Compensate for the mirror.
|
||||
} else {
|
||||
// Back-facing camera.
|
||||
resultRotation = rotation;
|
||||
}
|
||||
camera.setDisplayOrientation(resultRotation);
|
||||
exchange(result, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void surfaceChanged(
|
||||
SurfaceHolder holder, int format, int width, int height) {
|
||||
Logging.d(TAG, "VideoCaptureAndroid::surfaceChanged ignored: " +
|
||||
format + ": " + width + "x" + height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void surfaceCreated(final SurfaceHolder holder) {
|
||||
Logging.d(TAG, "VideoCaptureAndroid::surfaceCreated");
|
||||
if (camera == null || cameraThreadHandler == null) {
|
||||
return;
|
||||
}
|
||||
final Exchanger<IOException> result = new Exchanger<IOException>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Logging.d(TAG, "VideoCaptureAndroid::surfaceDestroyed");
|
||||
if (camera == null || cameraThreadHandler == null) {
|
||||
return;
|
||||
}
|
||||
final Exchanger<IOException> result = new Exchanger<IOException>();
|
||||
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<IOException> result) {
|
||||
try {
|
||||
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> T exchange(Exchanger<T> exchanger, T value) {
|
||||
try {
|
||||
return exchanger.exchange(value);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,133 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.webrtc.videoengine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.hardware.Camera.Size;
|
||||
import android.hardware.Camera;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.webrtc.Logging;
|
||||
|
||||
public class VideoCaptureDeviceInfoAndroid {
|
||||
private final static String TAG = "WEBRTC-JC";
|
||||
|
||||
private static boolean isFrontFacing(CameraInfo info) {
|
||||
return info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
}
|
||||
|
||||
private static String deviceUniqueName(int index, CameraInfo info) {
|
||||
return "Camera " + index +", Facing " +
|
||||
(isFrontFacing(info) ? "front" : "back") +
|
||||
", Orientation "+ info.orientation;
|
||||
}
|
||||
|
||||
// Returns information about all cameras on the device as a serialized JSON
|
||||
// array of dictionaries encoding information about a single device. Since
|
||||
// this reflects static information about the hardware present, there is no
|
||||
// need to call this function more than once in a single process. It is
|
||||
// marked "private" as it is only called by native code.
|
||||
private static String getDeviceInfo() {
|
||||
try {
|
||||
JSONArray devices = new JSONArray();
|
||||
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
|
||||
CameraInfo info = new CameraInfo();
|
||||
Camera.getCameraInfo(i, info);
|
||||
String uniqueName = deviceUniqueName(i, info);
|
||||
JSONObject cameraDict = new JSONObject();
|
||||
devices.put(cameraDict);
|
||||
List<Size> supportedSizes;
|
||||
List<int[]> supportedFpsRanges;
|
||||
Camera camera = null;
|
||||
try {
|
||||
camera = Camera.open(i);
|
||||
Parameters parameters = camera.getParameters();
|
||||
supportedSizes = parameters.getSupportedPreviewSizes();
|
||||
supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
|
||||
Logging.d(TAG, uniqueName);
|
||||
} catch (RuntimeException e) {
|
||||
Logging.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();
|
||||
size.put("width", supportedSize.width);
|
||||
size.put("height", supportedSize.height);
|
||||
sizes.put(size);
|
||||
}
|
||||
|
||||
boolean is30fpsRange = false;
|
||||
boolean is15fpsRange = false;
|
||||
// If there is constant 30 fps mode, but no 15 fps - add 15 fps
|
||||
// mode to the list of supported ranges. Frame drop will be done
|
||||
// in software.
|
||||
for (int[] range : supportedFpsRanges) {
|
||||
if (range[Parameters.PREVIEW_FPS_MIN_INDEX] == 30000 &&
|
||||
range[Parameters.PREVIEW_FPS_MAX_INDEX] == 30000) {
|
||||
is30fpsRange = true;
|
||||
}
|
||||
if (range[Parameters.PREVIEW_FPS_MIN_INDEX] == 15000 &&
|
||||
range[Parameters.PREVIEW_FPS_MAX_INDEX] == 15000) {
|
||||
is15fpsRange = true;
|
||||
}
|
||||
}
|
||||
if (is30fpsRange && !is15fpsRange) {
|
||||
Logging.d(TAG, "Adding 15 fps support");
|
||||
int[] newRange = new int [Parameters.PREVIEW_FPS_MAX_INDEX + 1];
|
||||
newRange[Parameters.PREVIEW_FPS_MIN_INDEX] = 15000;
|
||||
newRange[Parameters.PREVIEW_FPS_MAX_INDEX] = 15000;
|
||||
for (int j = 0; j < supportedFpsRanges.size(); j++ ) {
|
||||
int[] range = supportedFpsRanges.get(j);
|
||||
if (range[Parameters.PREVIEW_FPS_MAX_INDEX] >
|
||||
newRange[Parameters.PREVIEW_FPS_MAX_INDEX]) {
|
||||
supportedFpsRanges.add(j, newRange);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("mfpsRanges", mfpsRanges);
|
||||
}
|
||||
String ret = devices.toString(2);
|
||||
Logging.d(TAG, ret);
|
||||
return ret;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,255 +0,0 @@
|
||||
/*
|
||||
* 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 "webrtc/modules/video_capture/android/video_capture_android.h"
|
||||
|
||||
#include "webrtc/base/common.h"
|
||||
#include "webrtc/modules/utility/interface/helpers_android.h"
|
||||
#include "webrtc/modules/video_capture/android/device_info_android.h"
|
||||
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
|
||||
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
|
||||
#include "webrtc/system_wrappers/interface/logging.h"
|
||||
#include "webrtc/system_wrappers/interface/ref_count.h"
|
||||
#include "webrtc/system_wrappers/interface/trace.h"
|
||||
|
||||
static JavaVM* g_jvm = NULL;
|
||||
static jclass g_java_capturer_class = NULL; // VideoCaptureAndroid.class.
|
||||
static jobject g_context = NULL; // Owned android.content.Context.
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
// Called by Java to get the global application context.
|
||||
jobject JNICALL GetContext(JNIEnv* env, jclass) {
|
||||
assert(g_context);
|
||||
return g_context;
|
||||
}
|
||||
|
||||
// Called by Java when the camera has a new frame to deliver.
|
||||
void JNICALL ProvideCameraFrame(
|
||||
JNIEnv* env,
|
||||
jobject,
|
||||
jbyteArray javaCameraFrame,
|
||||
jint length,
|
||||
jint rotation,
|
||||
jlong timeStamp,
|
||||
jlong context) {
|
||||
webrtc::videocapturemodule::VideoCaptureAndroid* captureModule =
|
||||
reinterpret_cast<webrtc::videocapturemodule::VideoCaptureAndroid*>(
|
||||
context);
|
||||
jbyte* cameraFrame = env->GetByteArrayElements(javaCameraFrame, NULL);
|
||||
captureModule->OnIncomingFrame(
|
||||
reinterpret_cast<uint8_t*>(cameraFrame), length, rotation, 0);
|
||||
env->ReleaseByteArrayElements(javaCameraFrame, cameraFrame, JNI_ABORT);
|
||||
}
|
||||
|
||||
int32_t SetCaptureAndroidVM(JavaVM* javaVM, jobject context) {
|
||||
if (javaVM) {
|
||||
assert(!g_jvm);
|
||||
g_jvm = javaVM;
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
g_context = ats.env()->NewGlobalRef(context);
|
||||
|
||||
videocapturemodule::DeviceInfoAndroid::Initialize(ats.env());
|
||||
|
||||
jclass j_capture_class =
|
||||
ats.env()->FindClass("org/webrtc/videoengine/VideoCaptureAndroid");
|
||||
assert(j_capture_class);
|
||||
g_java_capturer_class =
|
||||
reinterpret_cast<jclass>(ats.env()->NewGlobalRef(j_capture_class));
|
||||
assert(g_java_capturer_class);
|
||||
|
||||
JNINativeMethod native_methods[] = {
|
||||
{"GetContext",
|
||||
"()Landroid/content/Context;",
|
||||
reinterpret_cast<void*>(&GetContext)},
|
||||
{"ProvideCameraFrame",
|
||||
"([BIIJJ)V",
|
||||
reinterpret_cast<void*>(&ProvideCameraFrame)}};
|
||||
if (ats.env()->RegisterNatives(g_java_capturer_class,
|
||||
native_methods, 2) != 0)
|
||||
assert(false);
|
||||
} else {
|
||||
if (g_jvm) {
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
ats.env()->UnregisterNatives(g_java_capturer_class);
|
||||
ats.env()->DeleteGlobalRef(g_java_capturer_class);
|
||||
g_java_capturer_class = NULL;
|
||||
ats.env()->DeleteGlobalRef(g_context);
|
||||
g_context = NULL;
|
||||
videocapturemodule::DeviceInfoAndroid::DeInitialize();
|
||||
g_jvm = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace videocapturemodule {
|
||||
|
||||
VideoCaptureModule* VideoCaptureImpl::Create(
|
||||
const int32_t id,
|
||||
const char* deviceUniqueIdUTF8) {
|
||||
RefCountImpl<videocapturemodule::VideoCaptureAndroid>* implementation =
|
||||
new RefCountImpl<videocapturemodule::VideoCaptureAndroid>(id);
|
||||
if (implementation->Init(id, deviceUniqueIdUTF8) != 0) {
|
||||
delete implementation;
|
||||
implementation = NULL;
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::OnIncomingFrame(uint8_t* videoFrame,
|
||||
size_t videoFrameLength,
|
||||
int32_t degrees,
|
||||
int64_t captureTime) {
|
||||
if (!_captureStarted)
|
||||
return 0;
|
||||
VideoRotation current_rotation =
|
||||
(degrees <= 45 || degrees > 315) ? kVideoRotation_0 :
|
||||
(degrees > 45 && degrees <= 135) ? kVideoRotation_90 :
|
||||
(degrees > 135 && degrees <= 225) ? kVideoRotation_180 :
|
||||
(degrees > 225 && degrees <= 315) ? kVideoRotation_270 :
|
||||
kVideoRotation_0; // Impossible.
|
||||
if (_rotation != current_rotation) {
|
||||
LOG(LS_INFO) << "New camera rotation: " << degrees;
|
||||
_rotation = current_rotation;
|
||||
int32_t status = VideoCaptureImpl::SetCaptureRotation(_rotation);
|
||||
if (status != 0)
|
||||
return status;
|
||||
}
|
||||
return IncomingFrame(
|
||||
videoFrame, videoFrameLength, _captureCapability, captureTime);
|
||||
}
|
||||
|
||||
VideoCaptureAndroid::VideoCaptureAndroid(const int32_t id)
|
||||
: VideoCaptureImpl(id),
|
||||
_deviceInfo(id),
|
||||
_jCapturer(NULL),
|
||||
_captureStarted(false) {
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::Init(const int32_t id,
|
||||
const char* deviceUniqueIdUTF8) {
|
||||
const int nameLength = strlen(deviceUniqueIdUTF8);
|
||||
if (nameLength >= kVideoCaptureUniqueNameLength)
|
||||
return -1;
|
||||
|
||||
// Store the device name
|
||||
LOG(LS_INFO) << "VideoCaptureAndroid::Init: " << deviceUniqueIdUTF8;
|
||||
size_t camera_id = 0;
|
||||
if (!_deviceInfo.FindCameraIndex(deviceUniqueIdUTF8, &camera_id))
|
||||
return -1;
|
||||
_deviceUniqueId = new char[nameLength + 1];
|
||||
memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1);
|
||||
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
JNIEnv* env = ats.env();
|
||||
jmethodID ctor = env->GetMethodID(g_java_capturer_class, "<init>", "(IJ)V");
|
||||
assert(ctor);
|
||||
jlong j_this = reinterpret_cast<intptr_t>(this);
|
||||
_jCapturer = env->NewGlobalRef(
|
||||
env->NewObject(g_java_capturer_class, ctor, camera_id, j_this));
|
||||
assert(_jCapturer);
|
||||
_rotation = kVideoRotation_0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
VideoCaptureAndroid::~VideoCaptureAndroid() {
|
||||
// Ensure Java camera is released even if our caller didn't explicitly Stop.
|
||||
if (_captureStarted)
|
||||
StopCapture();
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
ats.env()->DeleteGlobalRef(_jCapturer);
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::StartCapture(
|
||||
const VideoCaptureCapability& capability) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
JNIEnv* env = ats.env();
|
||||
|
||||
if (_deviceInfo.GetBestMatchedCapability(
|
||||
_deviceUniqueId, capability, _captureCapability) < 0) {
|
||||
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, -1,
|
||||
"%s: GetBestMatchedCapability failed: %dx%d",
|
||||
__FUNCTION__, capability.width, capability.height);
|
||||
return -1;
|
||||
}
|
||||
|
||||
_captureDelay = _captureCapability.expectedCaptureDelay;
|
||||
|
||||
jmethodID j_start =
|
||||
env->GetMethodID(g_java_capturer_class, "startCapture", "(IIII)Z");
|
||||
assert(j_start);
|
||||
int min_mfps = 0;
|
||||
int max_mfps = 0;
|
||||
_deviceInfo.GetMFpsRange(_deviceUniqueId, _captureCapability.maxFPS,
|
||||
&min_mfps, &max_mfps);
|
||||
bool started = env->CallBooleanMethod(_jCapturer, j_start,
|
||||
_captureCapability.width,
|
||||
_captureCapability.height,
|
||||
min_mfps, max_mfps);
|
||||
if (started) {
|
||||
_requestedCapability = capability;
|
||||
_captureStarted = true;
|
||||
}
|
||||
return started ? 0 : -1;
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::StopCapture() {
|
||||
_apiCs.Enter();
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
JNIEnv* env = ats.env();
|
||||
|
||||
memset(&_requestedCapability, 0, sizeof(_requestedCapability));
|
||||
memset(&_captureCapability, 0, sizeof(_captureCapability));
|
||||
_captureStarted = false;
|
||||
// Exit critical section to avoid blocking camera thread inside
|
||||
// onIncomingFrame() call.
|
||||
_apiCs.Leave();
|
||||
|
||||
jmethodID j_stop =
|
||||
env->GetMethodID(g_java_capturer_class, "stopCapture", "()Z");
|
||||
return env->CallBooleanMethod(_jCapturer, j_stop) ? 0 : -1;
|
||||
}
|
||||
|
||||
bool VideoCaptureAndroid::CaptureStarted() {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
return _captureStarted;
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::CaptureSettings(
|
||||
VideoCaptureCapability& settings) {
|
||||
CriticalSectionScoped cs(&_apiCs);
|
||||
settings = _requestedCapability;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t VideoCaptureAndroid::SetCaptureRotation(VideoRotation rotation) {
|
||||
int32_t status = VideoCaptureImpl::SetCaptureRotation(rotation);
|
||||
if (status != 0)
|
||||
return status;
|
||||
|
||||
AttachThreadScoped ats(g_jvm);
|
||||
JNIEnv* env = ats.env();
|
||||
|
||||
jmethodID j_spr =
|
||||
env->GetMethodID(g_java_capturer_class, "setPreviewRotation", "(I)V");
|
||||
assert(j_spr);
|
||||
int rotation_degrees;
|
||||
if (RotationInDegrees(rotation, &rotation_degrees) != 0) {
|
||||
assert(false);
|
||||
}
|
||||
env->CallVoidMethod(_jCapturer, j_spr, rotation_degrees);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace videocapturemodule
|
||||
} // namespace webrtc
|
||||
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_VIDEO_CAPTURE_ANDROID_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_VIDEO_CAPTURE_ANDROID_H_
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "webrtc/modules/video_capture/android/device_info_android.h"
|
||||
#include "webrtc/modules/video_capture/video_capture_impl.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace videocapturemodule {
|
||||
|
||||
class VideoCaptureAndroid : public VideoCaptureImpl {
|
||||
public:
|
||||
VideoCaptureAndroid(const int32_t id);
|
||||
virtual int32_t Init(const int32_t id, const char* deviceUniqueIdUTF8);
|
||||
|
||||
virtual int32_t StartCapture(const VideoCaptureCapability& capability);
|
||||
virtual int32_t StopCapture();
|
||||
virtual bool CaptureStarted();
|
||||
virtual int32_t CaptureSettings(VideoCaptureCapability& settings);
|
||||
virtual int32_t SetCaptureRotation(VideoRotation rotation);
|
||||
|
||||
int32_t OnIncomingFrame(uint8_t* videoFrame,
|
||||
size_t videoFrameLength,
|
||||
int32_t degrees,
|
||||
int64_t captureTime = 0);
|
||||
|
||||
protected:
|
||||
virtual ~VideoCaptureAndroid();
|
||||
|
||||
DeviceInfoAndroid _deviceInfo;
|
||||
jobject _jCapturer; // Global ref to Java VideoCaptureAndroid object.
|
||||
VideoCaptureCapability _captureCapability;
|
||||
VideoRotation _rotation;
|
||||
bool _captureStarted;
|
||||
};
|
||||
|
||||
} // namespace videocapturemodule
|
||||
} // namespace webrtc
|
||||
#endif // WEBRTC_MODULES_VIDEO_CAPTURE_MAIN_SOURCE_ANDROID_VIDEO_CAPTURE_ANDROID_H_
|
||||
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
|
||||
#ifndef ANDROID
|
||||
|
||||
namespace webrtc {
|
||||
namespace videocapturemodule {
|
||||
void EnsureInitialized() {}
|
||||
} // namespace videocapturemodule
|
||||
} // namespace webrtc
|
||||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
// Note: this dependency is dangerous since it reaches into Chromium's base.
|
||||
// There's a risk of e.g. macro clashes. This file may only be used in tests.
|
||||
#include "base/android/jni_android.h"
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/modules/video_capture/video_capture_internal.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace videocapturemodule {
|
||||
|
||||
static pthread_once_t g_initialize_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
void EnsureInitializedOnce() {
|
||||
JNIEnv* jni = ::base::android::AttachCurrentThread();
|
||||
jobject context = ::base::android::GetApplicationContext();
|
||||
JavaVM* jvm = NULL;
|
||||
RTC_CHECK_EQ(0, jni->GetJavaVM(&jvm));
|
||||
RTC_CHECK_EQ(0, webrtc::SetCaptureAndroidVM(jvm, context));
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
RTC_CHECK_EQ(0, pthread_once(&g_initialize_once, &EnsureInitializedOnce));
|
||||
}
|
||||
|
||||
} // namespace videocapturemodule
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // !ANDROID
|
||||
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
@ -18,7 +18,6 @@
|
||||
#include "webrtc/base/scoped_ref_ptr.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"
|
||||
@ -235,7 +234,6 @@ class VideoCaptureTest : public testing::Test {
|
||||
VideoCaptureTest() : number_of_devices_(0) {}
|
||||
|
||||
void SetUp() {
|
||||
webrtc::videocapturemodule::EnsureInitialized();
|
||||
device_info_.reset(VideoCaptureFactory::CreateDeviceInfo(0));
|
||||
assert(device_info_.get());
|
||||
number_of_devices_ = device_info_->NumberOfDevices();
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'webrtc_utility',
|
||||
'<(webrtc_root)/common.gyp:webrtc_common',
|
||||
'<(webrtc_root)/common_video/common_video.gyp:common_video',
|
||||
'<(webrtc_root)/system_wrappers/system_wrappers.gyp:system_wrappers',
|
||||
],
|
||||
@ -54,11 +53,13 @@
|
||||
{
|
||||
'target_name': 'video_capture_module_internal_impl',
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'video_capture_module',
|
||||
'<(webrtc_root)/common.gyp:webrtc_common',
|
||||
],
|
||||
'conditions': [
|
||||
['OS!="android"', {
|
||||
'dependencies': [
|
||||
'video_capture_module',
|
||||
'<(webrtc_root)/common.gyp:webrtc_common',
|
||||
],
|
||||
}],
|
||||
['OS=="linux"', {
|
||||
'sources': [
|
||||
'linux/device_info_linux.cc',
|
||||
@ -115,26 +116,6 @@
|
||||
],
|
||||
},
|
||||
}], # win
|
||||
['OS=="android"', {
|
||||
'sources': [
|
||||
'android/device_info_android.cc',
|
||||
'android/device_info_android.h',
|
||||
'android/video_capture_android.cc',
|
||||
'android/video_capture_android.h',
|
||||
],
|
||||
'conditions': [
|
||||
['build_json==1', {
|
||||
'dependencies': [
|
||||
'<(DEPTH)/third_party/jsoncpp/jsoncpp.gyp:jsoncpp',
|
||||
],
|
||||
}],
|
||||
['build_icu==1', {
|
||||
'dependencies': [
|
||||
'<(DEPTH)/third_party/icu/icu.gyp:icuuc',
|
||||
],
|
||||
}],
|
||||
],
|
||||
}], # android
|
||||
['OS=="ios"', {
|
||||
'sources': [
|
||||
'ios/device_info_ios.h',
|
||||
@ -164,7 +145,7 @@
|
||||
},
|
||||
],
|
||||
}], # build_with_chromium==0
|
||||
['include_tests==1', {
|
||||
['include_tests==1 and OS!="android"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'video_capture_tests',
|
||||
@ -177,8 +158,6 @@
|
||||
'<(DEPTH)/testing/gtest.gyp:gtest',
|
||||
],
|
||||
'sources': [
|
||||
'ensure_initialized.cc',
|
||||
'ensure_initialized.h',
|
||||
'test/video_capture_unittest.cc',
|
||||
'test/video_capture_main_mac.mm',
|
||||
],
|
||||
@ -198,18 +177,6 @@
|
||||
'-lX11',
|
||||
],
|
||||
}],
|
||||
['OS=="android"', {
|
||||
'dependencies': [
|
||||
'<(DEPTH)/testing/android/native_test.gyp:native_test_native_code',
|
||||
],
|
||||
# Need to disable error due to the line in
|
||||
# base/android/jni_android.h triggering it:
|
||||
# const BASE_EXPORT jobject GetApplicationContext()
|
||||
# error: type qualifiers ignored on function return type
|
||||
'cflags': [
|
||||
'-Wno-ignored-qualifiers',
|
||||
],
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'dependencies': [
|
||||
# Link with a special main for mac so we can use the webcam.
|
||||
@ -231,36 +198,6 @@
|
||||
] # conditions
|
||||
},
|
||||
], # targets
|
||||
'conditions': [
|
||||
['OS=="android"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'video_capture_tests_apk_target',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'<(apk_tests_path):video_capture_tests_apk',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
['test_isolation_mode != "noop"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'video_capture_tests_run',
|
||||
'type': 'none',
|
||||
'dependencies': [
|
||||
'video_capture_tests',
|
||||
],
|
||||
'includes': [
|
||||
'../../build/isolate.gypi',
|
||||
],
|
||||
'sources': [
|
||||
'video_capture_tests.isolate',
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
||||
|
||||
@ -17,7 +17,11 @@ namespace webrtc
|
||||
|
||||
VideoCaptureModule* VideoCaptureFactory::Create(const int32_t id,
|
||||
const char* deviceUniqueIdUTF8) {
|
||||
#if defined(ANDROID)
|
||||
return nullptr;
|
||||
#else
|
||||
return videocapturemodule::VideoCaptureImpl::Create(id, deviceUniqueIdUTF8);
|
||||
#endif
|
||||
}
|
||||
|
||||
VideoCaptureModule* VideoCaptureFactory::Create(const int32_t id,
|
||||
@ -27,7 +31,11 @@ VideoCaptureModule* VideoCaptureFactory::Create(const int32_t id,
|
||||
|
||||
VideoCaptureModule::DeviceInfo* VideoCaptureFactory::CreateDeviceInfo(
|
||||
const int32_t id) {
|
||||
#if defined(ANDROID)
|
||||
return nullptr;
|
||||
#else
|
||||
return videocapturemodule::VideoCaptureImpl::CreateDeviceInfo(id);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user