Add Android specific VideoCapturer.

The Java implementation of VideoCapturer is losely based on the the work in webrtc/modules/videocapturer.

The capturer is now started asyncronously.
The capturer supports easy camera switching.

BUG=
R=henrika@webrtc.org, magjed@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/30849004

Cr-Commit-Position: refs/heads/master@{#8329}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8329 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
perkj@webrtc.org 2015-02-11 11:26:56 +00:00
parent c18957e877
commit 83bc721c7e
8 changed files with 1354 additions and 151 deletions

View File

@ -0,0 +1,222 @@
/*
* libjingle
* Copyright 2015 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.webrtc;
import android.hardware.Camera;
import android.test.ActivityTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import org.webrtc.VideoCapturerAndroid.CaptureFormat;
import org.webrtc.VideoRenderer.I420Frame;
import java.util.ArrayList;
@SuppressWarnings("deprecation")
public class VideoCapturerAndroidTest extends ActivityTestCase {
static class RendererCallbacks implements VideoRenderer.Callbacks {
private int framesRendered = 0;
private Object frameLock = 0;
@Override
public void setSize(int width, int height) {
}
@Override
public void renderFrame(I420Frame frame) {
synchronized (frameLock) {
++framesRendered;
frameLock.notify();
}
}
public int WaitForNextFrameToRender() throws InterruptedException {
synchronized (frameLock) {
frameLock.wait();
return framesRendered;
}
}
}
static class FakeCapturerObserver implements
VideoCapturerAndroid.CapturerObserver {
private int framesCaptured = 0;
private int frameSize = 0;
private Object frameLock = 0;
private Object capturerStartLock = 0;
private boolean captureStartResult = false;
@Override
public void OnCapturerStarted(boolean success) {
synchronized (capturerStartLock) {
captureStartResult = success;
capturerStartLock.notify();
}
}
@Override
public void OnFrameCaptured(byte[] data, int rotation, long timeStamp) {
synchronized (frameLock) {
++framesCaptured;
frameSize = data.length;
frameLock.notify();
}
}
public boolean WaitForCapturerToStart() throws InterruptedException {
synchronized (capturerStartLock) {
capturerStartLock.wait();
return captureStartResult;
}
}
public int WaitForNextCapturedFrame() throws InterruptedException {
synchronized (frameLock) {
frameLock.wait();
return framesCaptured;
}
}
int frameSize() {
synchronized (frameLock) {
return frameSize;
}
}
}
// Return true if the device under test have at least two cameras.
@SuppressWarnings("deprecation")
boolean HaveTwoCameras() {
return (Camera.getNumberOfCameras() >= 2);
}
void starCapturerAndRender(String deviceName) throws InterruptedException {
PeerConnectionFactory factory = new PeerConnectionFactory();
VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName);
VideoSource source =
factory.createVideoSource(capturer, new MediaConstraints());
VideoTrack track = factory.createVideoTrack("dummy", source);
RendererCallbacks callbacks = new RendererCallbacks();
track.addRenderer(new VideoRenderer(callbacks));
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
}
@Override
protected void setUp() {
assertTrue(PeerConnectionFactory.initializeAndroidGlobals(
getInstrumentation().getContext(), true,
true, true, null));
}
@SmallTest
public void testCreateAndRelease() throws Exception {
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("");
assertNotNull(capturer);
capturer.dispose();
}
@SmallTest
public void testCreateNoneExistingCamera() throws Exception {
VideoCapturerAndroid capturer = VideoCapturerAndroid.create(
"none existing camera");
assertNull(capturer);
}
@SmallTest
// This test that the camera can be started and that the frames are forwarded
// to a Java video renderer using a "default" capturer.
// It tests both the Java and the C++ layer.
public void testStartVideoCapturer() throws Exception {
starCapturerAndRender("");
}
@SmallTest
// This test that the camera can be started and that the frames are forwarded
// to a Java video renderer using the front facing video capturer.
// It tests both the Java and the C++ layer.
public void testStartFrontFacingVideoCapturer() throws Exception {
starCapturerAndRender(VideoCapturerAndroid.getNameOfFrontFacingDevice());
}
@SmallTest
// This test that the camera can be started and that the frames are forwarded
// to a Java video renderer using the back facing video capturer.
// It tests both the Java and the C++ layer.
public void testStartBackFacingVideoCapturer() throws Exception {
if (!HaveTwoCameras()) {
return;
}
starCapturerAndRender(VideoCapturerAndroid.getNameOfBackFacingDevice());
}
@SmallTest
// This test that the default camera can be started and but the camera can
// later be switched to another camera.
// It tests both the Java and the C++ layer.
public void testSwitchVideoCapturer() throws Exception {
PeerConnectionFactory factory = new PeerConnectionFactory();
VideoCapturerAndroid capturer = VideoCapturerAndroid.create("");
VideoSource source =
factory.createVideoSource(capturer, new MediaConstraints());
VideoTrack track = factory.createVideoTrack("dummy", source);
if (HaveTwoCameras())
assertTrue(capturer.switchCamera());
else
assertFalse(capturer.switchCamera());
// Wait until the camera have been switched.
capturer.runCameraThreadUntilIdle();
// Ensure that frames are received.
RendererCallbacks callbacks = new RendererCallbacks();
track.addRenderer(new VideoRenderer(callbacks));
assertTrue(callbacks.WaitForNextFrameToRender() > 0);
}
@SmallTest
// This test that the camera can be started at different resolutions.
// It does not test or use the C++ layer.
public void testStartStopWithDifferentResolutions() throws Exception {
FakeCapturerObserver observer = new FakeCapturerObserver();
String deviceName = VideoCapturerAndroid.getDeviceName(0);
ArrayList<CaptureFormat> formats =
VideoCapturerAndroid.getSupportedFormats(0);
VideoCapturerAndroid capturer = VideoCapturerAndroid.create(deviceName);
for(int i = 0; i < 3 ; ++i) {
VideoCapturerAndroid.CaptureFormat format = formats.get(i);
capturer.startCapture(format.width, format.height, format.maxFramerate,
getInstrumentation().getContext(), observer);
assertTrue(observer.WaitForCapturerToStart());
observer.WaitForNextCapturedFrame();
// Check the frame size. NV21 is assumed.
assertEquals((format.width*format.height*3)/2, observer.frameSize());
assertTrue(capturer.stopCapture());
}
}
}

View File

@ -0,0 +1,206 @@
/*
* libjingle
* Copyright 2015 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "talk/app/webrtc/androidvideocapturer.h"
#include "talk/media/webrtc/webrtcvideoframe.h"
#include "webrtc/base/bind.h"
#include "webrtc/base/common.h"
#include "webrtc/base/json.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/base/thread.h"
namespace webrtc {
using cricket::WebRtcVideoFrame;
using rtc::scoped_ptr;
// An implementation of cricket::VideoFrameFactory for frames that are not
// guaranteed to outlive the created cricket::VideoFrame.
// A frame is injected using UpdateCapturedFrame, and converted into a
// cricket::VideoFrame with
// CreateAliasedFrame. UpdateCapturedFrame should be called before
// CreateAliasedFrame for every frame.
class AndroidVideoCapturer::FrameFactory : public cricket::VideoFrameFactory {
public:
FrameFactory(int width, int height) : start_time_(rtc::TimeNanos()) {
// Create a CapturedFrame that only contains header information, not the
// actual pixel data.
captured_frame_.width = width;
captured_frame_.height = height;
captured_frame_.pixel_height = 1;
captured_frame_.pixel_width = 1;
captured_frame_.rotation = 0;
captured_frame_.data = NULL;
captured_frame_.data_size = cricket::CapturedFrame::kUnknownDataSize;
captured_frame_.fourcc = static_cast<uint32>(cricket::FOURCC_ANY);
}
void UpdateCapturedFrame(signed char* frame_data,
int length,
int rotation,
int64 time_stamp_in_ms) {
captured_frame_.fourcc = static_cast<uint32>(cricket::FOURCC_NV21);
captured_frame_.data = frame_data;
captured_frame_.elapsed_time = rtc::TimeNanos() - start_time_;
captured_frame_.time_stamp =
time_stamp_in_ms * rtc::kNumNanosecsPerMillisec;
captured_frame_.rotation = rotation;
captured_frame_.data_size = length;
}
const cricket::CapturedFrame* GetCapturedFrame() const {
return &captured_frame_;
}
cricket::VideoFrame* CreateAliasedFrame(
const cricket::CapturedFrame* captured_frame,
int dst_width,
int dst_height) const override {
// This override of CreateAliasedFrame creates a copy of the frame since
// |captured_frame_.data| is only guaranteed to be valid during the scope
// of |AndroidVideoCapturer::OnIncomingFrame_w|.
// Check that captured_frame is actually our frame.
DCHECK(captured_frame == &captured_frame_);
scoped_ptr<WebRtcVideoFrame> frame(new WebRtcVideoFrame());
frame->Init(captured_frame, dst_width, dst_height);
return frame.release();
}
private:
uint64 start_time_;
cricket::CapturedFrame captured_frame_;
};
AndroidVideoCapturer::AndroidVideoCapturer(
rtc::scoped_ptr<AndroidVideoCapturerDelegate> delegate)
: running_(false),
delegate_(delegate.Pass()),
worker_thread_(NULL),
frame_factory_(NULL) {
std::string json_string = delegate_->GetSupportedFormats();
LOG(LS_INFO) << json_string;
Json::Value json_values;
Json::Reader reader(Json::Features::strictMode());
if (!reader.parse(json_string, json_values)) {
LOG(LS_ERROR) << "Failed to parse formats.";
}
std::vector<cricket::VideoFormat> formats;
for (Json::ArrayIndex i = 0; i < json_values.size(); ++i) {
const Json::Value& json_value = json_values[i];
DCHECK(!json_value["width"].isNull() && !json_value["height"].isNull() &&
!json_value["framerate"].isNull());
cricket::VideoFormat format(
json_value["width"].asInt(),
json_value["height"].asInt(),
cricket::VideoFormat::FpsToInterval(json_value["framerate"].asInt()),
cricket::FOURCC_NV21);
formats.push_back(format);
}
SetSupportedFormats(formats);
}
AndroidVideoCapturer::~AndroidVideoCapturer() {
DCHECK(!running_);
}
cricket::CaptureState AndroidVideoCapturer::Start(
const cricket::VideoFormat& capture_format) {
DCHECK(!running_);
DCHECK(worker_thread_ == nullptr);
// TODO(perkj): Better way to get a handle to the worker thread?
worker_thread_ = rtc::Thread::Current();
LOG(LS_INFO) << " AndroidVideoCapturer::Start w = " << capture_format.width
<< " h = " << capture_format.height;
frame_factory_ = new AndroidVideoCapturer::FrameFactory(
capture_format.width, capture_format.height);
set_frame_factory(frame_factory_);
running_ = true;
delegate_->Start(
capture_format.width, capture_format.height,
cricket::VideoFormat::IntervalToFps(capture_format.interval), this);
return cricket::CS_STARTING;
}
void AndroidVideoCapturer::Stop() {
DCHECK(worker_thread_->IsCurrent());
LOG(LS_INFO) << " AndroidVideoCapturer::Stop ";
DCHECK(running_);
running_ = false;
SetCaptureFormat(NULL);
delegate_->Stop();
SignalStateChange(this, cricket::CS_STOPPED);
}
bool AndroidVideoCapturer::IsRunning() {
return running_;
}
bool AndroidVideoCapturer::GetPreferredFourccs(std::vector<uint32>* fourccs) {
fourccs->push_back(cricket::FOURCC_NV21);
return true;
}
void AndroidVideoCapturer::OnCapturerStarted(bool success) {
// This method is called from a Java thread.
DCHECK(!worker_thread_->IsCurrent());
worker_thread_->Invoke<void>(
rtc::Bind(&AndroidVideoCapturer::OnCapturerStarted_w, this, success));
}
void AndroidVideoCapturer::OnCapturerStarted_w(bool success) {
DCHECK(worker_thread_->IsCurrent());
cricket::CaptureState new_state =
success ? cricket::CS_RUNNING : cricket::CS_FAILED;
SetCaptureState(new_state);
}
void AndroidVideoCapturer::OnIncomingFrame(signed char* videoFrame,
int length,
int rotation,
int64 time_stamp) {
// This method is called from a Java thread.
DCHECK(!worker_thread_->IsCurrent());
worker_thread_->Invoke<void>(
rtc::Bind(&AndroidVideoCapturer::OnIncomingFrame_w, this, videoFrame,
length, rotation, time_stamp));
}
void AndroidVideoCapturer::OnIncomingFrame_w(signed char* frame_data,
int length,
int rotation,
int64 time_stamp) {
DCHECK(worker_thread_->IsCurrent());
frame_factory_->UpdateCapturedFrame(frame_data, length, rotation, time_stamp);
SignalFrameCaptured(this, frame_factory_->GetCapturedFrame());
}
} // namespace webrtc

View File

@ -0,0 +1,109 @@
/*
* libjingle
* Copyright 2015 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TALK_APP_WEBRTC_ANDROIDVIDEOCAPTURER_H_
#define TALK_APP_WEBRTC_ANDROIDVIDEOCAPTURER_H_
#include <string>
#include <vector>
#include "talk/media/base/videocapturer.h"
namespace webrtc {
class AndroidVideoCapturer;
class AndroidVideoCapturerDelegate {
public:
virtual ~AndroidVideoCapturerDelegate() {}
// Start capturing. The implementation of the delegate must call
// AndroidVideoCapturer::OnCapturerStarted with the result of this request.
virtual void Start(int width, int height, int framerate,
AndroidVideoCapturer* capturer) = 0;
// Stops capturing. The implementation must synchronously stop the capturer.
// The delegate may not call into AndroidVideoCapturer after this call.
virtual bool Stop() = 0;
// Must returns a JSON string "{{width=xxx, height=xxx, framerate = xxx}}"
virtual std::string GetSupportedFormats() = 0;
};
// Android implementation of cricket::VideoCapturer for use with WebRtc
// PeerConnection.
class AndroidVideoCapturer : public cricket::VideoCapturer {
public:
explicit AndroidVideoCapturer(
rtc::scoped_ptr<AndroidVideoCapturerDelegate> delegate);
virtual ~AndroidVideoCapturer();
// Called from JNI when the capturer has been started. Called from a Java
// thread.
void OnCapturerStarted(bool success);
// Called from JNI when a new frame has been captured. Called from a Java
// thread.
void OnIncomingFrame(signed char* videoFrame,
int length,
int rotation,
int64 time_stamp);
AndroidVideoCapturerDelegate* delegate() { return delegate_.get(); }
private:
void OnCapturerStarted_w(bool success);
void OnIncomingFrame_w(signed char* frame_data,
int length,
int rotation,
int64 time_stamp);
// cricket::VideoCapturer implementation.
// Video frames will be delivered using
// cricket::VideoCapturer::SignalFrameCaptured on the thread that calls Start.
cricket::CaptureState Start(
const cricket::VideoFormat& capture_format) override;
void Stop() override;
bool IsRunning() override;
bool IsScreencast() const override { return false; }
bool GetPreferredFourccs(std::vector<uint32>* fourccs) override;
bool running_;
rtc::scoped_ptr<AndroidVideoCapturerDelegate> delegate_;
// |worker_thread_| is the thread that calls Start and is used for
// communication with the Java capturer.
// Video frames are delivered to cricket::VideoCapturer::SignalFrameCaptured
// on this thread.
rtc::Thread* worker_thread_;
class FrameFactory;
FrameFactory* frame_factory_; // Owned by cricket::VideoCapturer.
};
} // namespace webrtc
#endif // TALK_APP_WEBRTC_ANDROIDVIDEOCAPTURER_H_

View File

@ -91,7 +91,7 @@
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
#include <android/log.h>
#include "webrtc/modules/video_capture/video_capture_internal.h"
#include "talk/app/webrtc/androidvideocapturer.h"
#include "webrtc/modules/video_render/video_render_internal.h"
#include "webrtc/system_wrappers/interface/logcat_trace_context.h"
#include "webrtc/system_wrappers/interface/tick_util.h"
@ -279,6 +279,8 @@ class ClassReferenceHolder {
LoadClass(jni, "org/webrtc/IceCandidate");
#if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
LoadClass(jni, "android/graphics/SurfaceTexture");
LoadClass(jni, "org/webrtc/VideoCapturerAndroid");
LoadClass(jni, "org/webrtc/VideoCapturerAndroid$NativeFrameObserver");
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder");
LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo");
LoadClass(jni, "org/webrtc/MediaCodecVideoDecoder");
@ -304,6 +306,7 @@ class ClassReferenceHolder {
LoadClass(jni, "org/webrtc/StatsReport");
LoadClass(jni, "org/webrtc/StatsReport$Value");
LoadClass(jni, "org/webrtc/VideoRenderer$I420Frame");
LoadClass(jni, "org/webrtc/VideoCapturer");
LoadClass(jni, "org/webrtc/VideoTrack");
}
@ -425,6 +428,36 @@ void DeleteGlobalRef(JNIEnv* jni, jobject o) {
CHECK_EXCEPTION(jni) << "error during DeleteGlobalRef";
}
// Convenience macro defining JNI-accessible methods in the org.webrtc package.
// Eliminates unnecessary boilerplate and line-wraps, reducing visual clutter.
#define JOW(rettype, name) extern "C" rettype JNIEXPORT JNICALL \
Java_org_webrtc_##name
extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
CHECK(!g_jvm) << "JNI_OnLoad called more than once!";
g_jvm = jvm;
CHECK(g_jvm) << "JNI_OnLoad handed NULL?";
CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey)) << "pthread_once";
CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
JNIEnv* jni;
if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK)
return -1;
g_class_reference_holder = new ClassReferenceHolder(jni);
return JNI_VERSION_1_6;
}
extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM *jvm, void *reserved) {
g_class_reference_holder->FreeReferences(AttachCurrentThreadIfNeeded());
delete g_class_reference_holder;
g_class_reference_holder = NULL;
CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
g_jvm = NULL;
}
// Given a jweak reference, allocate a (strong) local reference scoped to the
// lifetime of this object if the weak reference is still valid, or NULL
// otherwise.
@ -2634,46 +2667,113 @@ webrtc::VideoDecoder* MediaCodecVideoDecoderFactory::CreateVideoDecoder(
return new MediaCodecVideoDecoder(AttachCurrentThreadIfNeeded());
}
void MediaCodecVideoDecoderFactory::DestroyVideoDecoder(
webrtc::VideoDecoder* decoder) {
delete decoder;
}
// AndroidVideoCapturerJni implements AndroidVideoCapturerDelegate.
// The purpose of the delegate is to hide the JNI specifics from the C++ only
// AndroidVideoCapturer.
// TODO(perkj): Refactor this to a separate file once the jni utility functions
// and classes have been moved.
class AndroidVideoCapturerJni : public webrtc::AndroidVideoCapturerDelegate {
public:
static int SetAndroidObjects(JNIEnv* jni, jobject appliction_context) {
if (application_context_) {
jni->DeleteGlobalRef(application_context_);
}
application_context_ = NewGlobalRef(jni, appliction_context);
return 0;
}
AndroidVideoCapturerJni(JNIEnv* jni, jobject j_video_capturer)
: j_capturer_global_(jni, j_video_capturer),
j_video_capturer_class_(
jni, FindClass(jni, "org/webrtc/VideoCapturerAndroid")),
j_frame_observer_class_(
jni,
FindClass(jni,
"org/webrtc/VideoCapturerAndroid$NativeFrameObserver")) {
}
void Start(int width, int height, int framerate,
webrtc::AndroidVideoCapturer* capturer) override {
j_frame_observer_ = NewGlobalRef(
jni(),
jni()->NewObject(*j_frame_observer_class_,
GetMethodID(jni(),
*j_frame_observer_class_,
"<init>",
"(J)V"),
jlongFromPointer(capturer)));
CHECK_EXCEPTION(jni()) << "error during NewObject";
jmethodID m = GetMethodID(
jni(), *j_video_capturer_class_, "startCapture",
"(IIILandroid/content/Context;"
"Lorg/webrtc/VideoCapturerAndroid$CapturerObserver;)V");
jni()->CallVoidMethod(*j_capturer_global_,
m, width, height,
framerate,
application_context_,
j_frame_observer_);
CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.startCapture";
}
bool Stop() override {
jmethodID m = GetMethodID(jni(), *j_video_capturer_class_,
"stopCapture", "()Z");
jboolean result = jni()->CallBooleanMethod(*j_capturer_global_, m);
CHECK_EXCEPTION(jni()) << "error during VideoCapturerAndroid.stopCapture";
DeleteGlobalRef(jni(), j_frame_observer_);
return result;
}
std::string GetSupportedFormats() override {
jmethodID m =
GetMethodID(jni(), *j_video_capturer_class_,
"getSupportedFormatsAsJson", "()Ljava/lang/String;");
jstring j_json_caps =
(jstring) jni()->CallObjectMethod(*j_capturer_global_, m);
CHECK_EXCEPTION(jni()) << "error during supportedFormatsAsJson";
return JavaToStdString(jni(), j_json_caps);
}
private:
JNIEnv* jni() { return AttachCurrentThreadIfNeeded(); }
const ScopedGlobalRef<jobject> j_capturer_global_;
const ScopedGlobalRef<jclass> j_video_capturer_class_;
const ScopedGlobalRef<jclass> j_frame_observer_class_;
jobject j_frame_observer_;
static jobject application_context_;
};
jobject AndroidVideoCapturerJni::application_context_ = nullptr;
JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeOnFrameCaptured)
(JNIEnv* jni, jclass, jlong j_capturer, jbyteArray j_frame,
jint rotation, jlong ts) {
jbyte* bytes = jni->GetByteArrayElements(j_frame, NULL);
reinterpret_cast<webrtc::AndroidVideoCapturer*>(
j_capturer)->OnIncomingFrame(bytes, jni->GetArrayLength(j_frame),
rotation, ts);
jni->ReleaseByteArrayElements(j_frame, bytes, JNI_ABORT);
}
JOW(void, VideoCapturerAndroid_00024NativeFrameObserver_nativeCapturerStarted)
(JNIEnv* jni, jclass, jlong j_capturer, jboolean j_success) {
reinterpret_cast<webrtc::AndroidVideoCapturer*>(
j_capturer)->OnCapturerStarted(j_success);
}
#endif // #if defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
} // anonymous namespace
// Convenience macro defining JNI-accessible methods in the org.webrtc package.
// Eliminates unnecessary boilerplate and line-wraps, reducing visual clutter.
#define JOW(rettype, name) extern "C" rettype JNIEXPORT JNICALL \
Java_org_webrtc_##name
extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
CHECK(!g_jvm) << "JNI_OnLoad called more than once!";
g_jvm = jvm;
CHECK(g_jvm) << "JNI_OnLoad handed NULL?";
CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey)) << "pthread_once";
CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
JNIEnv* jni;
if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK)
return -1;
g_class_reference_holder = new ClassReferenceHolder(jni);
return JNI_VERSION_1_6;
}
extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM *jvm, void *reserved) {
g_class_reference_holder->FreeReferences(AttachCurrentThreadIfNeeded());
delete g_class_reference_holder;
g_class_reference_holder = NULL;
CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
g_jvm = NULL;
}
static DataChannelInterface* ExtractNativeDC(JNIEnv* jni, jobject j_dc) {
jfieldID native_dc_id = GetFieldID(jni,
GetObjectClass(jni, j_dc), "nativeDataChannel", "J");
@ -2828,16 +2928,17 @@ JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)(
vp8_hw_acceleration_enabled = vp8_hw_acceleration;
if (!factory_static_initialized) {
if (initialize_video) {
failure |= webrtc::SetCaptureAndroidVM(g_jvm, context);
failure |= webrtc::SetRenderAndroidVM(g_jvm);
failure |= AndroidVideoCapturerJni::SetAndroidObjects(jni, context);
}
if (initialize_audio)
failure |= webrtc::VoiceEngine::SetAndroidObjects(g_jvm, jni, context);
factory_static_initialized = true;
}
if (initialize_video)
if (initialize_video) {
failure |= MediaCodecVideoDecoder::SetAndroidObjects(jni,
render_egl_context);
}
return !failure;
}
#endif // defined(ANDROID) && !defined(WEBRTC_CHROMIUM_BUILD)
@ -3218,8 +3319,32 @@ JOW(jobject, MediaSource_nativeState)(JNIEnv* jni, jclass, jlong j_p) {
return JavaEnumFromIndex(jni, "MediaSource$State", p->state());
}
JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)(
JOW(jobject, VideoCapturer_nativeCreateVideoCapturer)(
JNIEnv* jni, jclass, jstring j_device_name) {
// Since we can't create platform specific java implementations in Java, we
// defer the creation to C land.
#if defined(ANDROID)
jclass j_video_capturer_class(
FindClass(jni, "org/webrtc/VideoCapturerAndroid"));
const jmethodID j_videocapturer_ctor(GetMethodID(
jni, j_video_capturer_class, "<init>", "()V"));
jobject j_video_capturer = jni->NewObject(j_video_capturer_class,
j_videocapturer_ctor);
CHECK_EXCEPTION(jni) << "error during NewObject";
const jmethodID m(GetMethodID(
jni, j_video_capturer_class, "Init", "(Ljava/lang/String;)Z"));
if (!jni->CallBooleanMethod(j_video_capturer, m, j_device_name)) {
return nullptr;
}
CHECK_EXCEPTION(jni) << "error during CallVoidMethod";
rtc::scoped_ptr<webrtc::AndroidVideoCapturerDelegate> delegate(
new AndroidVideoCapturerJni(jni, j_video_capturer));
rtc::scoped_ptr<webrtc::AndroidVideoCapturer> capturer(
new webrtc::AndroidVideoCapturer(delegate.Pass()));
#else
std::string device_name = JavaToStdString(jni, j_device_name);
scoped_ptr<cricket::DeviceManagerInterface> device_manager(
cricket::DeviceManagerFactory::Create());
@ -3231,7 +3356,24 @@ JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)(
}
scoped_ptr<cricket::VideoCapturer> capturer(
device_manager->CreateVideoCapturer(device));
return (jlong)capturer.release();
jclass j_video_capturer_class(
FindClass(jni, "org/webrtc/VideoCapturer"));
const jmethodID j_videocapturer_ctor(GetMethodID(
jni, j_video_capturer_class, "<init>", "()V"));
jobject j_video_capturer =
jni->NewObject(j_video_capturer_class,
j_videocapturer_ctor);
CHECK_EXCEPTION(jni) << "error during creation of VideoCapturer";
#endif
const jmethodID j_videocapturer_set_native_capturer(GetMethodID(
jni, j_video_capturer_class, "setNativeCapturer", "(J)V"));
jni->CallVoidMethod(j_video_capturer,
j_videocapturer_set_native_capturer,
(jlong)capturer.release());
CHECK_EXCEPTION(jni) << "error during setNativeCapturer";
return j_video_capturer;
}
JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)(

View File

@ -31,16 +31,19 @@ package org.webrtc;
public class VideoCapturer {
private long nativeVideoCapturer;
private VideoCapturer(long nativeVideoCapturer) {
this.nativeVideoCapturer = nativeVideoCapturer;
protected VideoCapturer() {
}
public static VideoCapturer create(String deviceName) {
long nativeVideoCapturer = nativeCreateVideoCapturer(deviceName);
if (nativeVideoCapturer == 0) {
return null;
}
return new VideoCapturer(nativeVideoCapturer);
Object capturer = nativeCreateVideoCapturer(deviceName);
if (capturer != null)
return (VideoCapturer) (capturer);
return null;
}
// Sets |nativeCapturer| to be owned by VideoCapturer.
protected void setNativeCapturer(long nativeCapturer) {
this.nativeVideoCapturer = nativeCapturer;
}
// Package-visible for PeerConnectionFactory.
@ -61,7 +64,7 @@ public class VideoCapturer {
}
}
private static native long nativeCreateVideoCapturer(String deviceName);
private static native Object nativeCreateVideoCapturer(String deviceName);
private static native void free(long nativeVideoCapturer);
}

View File

@ -0,0 +1,605 @@
/*
* libjingle
* Copyright 2015 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.webrtc;
import static java.lang.Math.abs;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Exchanger;
// Android specific implementation of VideoCapturer.
// An instance of this class can be created by an application using
// VideoCapturerAndroid.create();
// This class extends VideoCapturer with a method to easily switch between the
// front and back camera. It also provides methods for enumerating valid device
// names.
//
// Threading notes: this class is called from C++ code, and from Camera
// 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.
@SuppressWarnings("deprecation")
public class VideoCapturerAndroid extends VideoCapturer implements PreviewCallback {
private final static String TAG = "VideoCapturerAndroid";
private Camera camera; // Only non-null while capturing.
private CameraThread cameraThread;
private Handler cameraThreadHandler;
private Context applicationContext;
private int id;
private Camera.CameraInfo info;
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 int width;
private int height;
private int framerate;
private CapturerObserver frameObserver = null;
// List of formats supported by all cameras. This list is filled once in order
// to be able to switch cameras.
private static ArrayList<CaptureFormat>[] supportedFormats;
// Returns device names that can be used to create a new VideoCapturerAndroid.
public static String[] getDeviceNames() {
String[] names = new String[Camera.getNumberOfCameras()];
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
names[i] = getDeviceName(i);
}
return names;
}
public static String getDeviceName(int index) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(index, info);
String facing =
(info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) ? "front" : "back";
return "Camera " + index + ", Facing " + facing
+ ", Orientation " + info.orientation;
}
public static String getNameOfFrontFacingDevice() {
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
return getDeviceName(i);
}
throw new RuntimeException("Front facing camera does not exist.");
}
public static String getNameOfBackFacingDevice() {
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
return getDeviceName(i);
}
throw new RuntimeException("Back facing camera does not exist.");
}
public static VideoCapturerAndroid create(String name) {
VideoCapturer capturer = VideoCapturer.create(name);
if (capturer != null)
return (VideoCapturerAndroid) capturer;
return null;
}
// Switch camera to the next valid camera id. This can only be called while
// the camera is running.
// Returns true on success. False if the next camera does not support the
// current resolution.
public synchronized boolean switchCamera() {
if (Camera.getNumberOfCameras() < 2 )
return false;
if (cameraThread == null) {
Log.e(TAG, "Camera has not been started");
return false;
}
id = ++id % Camera.getNumberOfCameras();
CaptureFormat formatToUse = null;
for (CaptureFormat format : supportedFormats[id]) {
if (format.width == width && format.height == height) {
formatToUse = format;
break;
}
}
if (formatToUse == null) {
Log.d(TAG, "No valid format found to switch camera.");
return false;
}
cameraThreadHandler.post(new Runnable() {
@Override public void run() {
switchCameraOnCameraThread();
}
});
return true;
}
private VideoCapturerAndroid() {
Log.d(TAG, "VideoCapturerAndroid");
}
// Called by native code.
// Enumerates resolution and frame rates for all cameras to be able to switch
// cameras. Initializes local variables for the camera named |deviceName|.
// If deviceName is empty, the first available device is used in order to be
// compatible with the generic VideoCapturer class.
boolean Init(String deviceName) {
Log.e(TAG, "Init " + deviceName);
if (!InitStatics())
return false;
if (deviceName.isEmpty()) {
this.id = 0;
return true;
}
Boolean foundDevice = false;
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
if (deviceName.equals(getDeviceName(i))) {
this.id = i;
foundDevice = true;
}
}
return foundDevice;
}
private static boolean InitStatics() {
if (supportedFormats != null)
return true;
try {
supportedFormats = new ArrayList[Camera.getNumberOfCameras()];
for (int i = 0; i < Camera.getNumberOfCameras(); ++i) {
supportedFormats[i] = getSupportedFormats(i);
}
return true;
} catch (Exception e) {
supportedFormats = null;
Log.e(TAG, "InitStatics failed",e);
}
return false;
}
String getSupportedFormatsAsJson() throws JSONException {
return getSupportedFormatsAsJson(id);
}
static class CaptureFormat {
public final int width;
public final int height;
public final int maxFramerate;
public final int minFramerate;
public CaptureFormat(int width, int height, int minFramerate,
int maxFramerate) {
this.width = width;
this.height = height;
this.minFramerate = minFramerate;
this.maxFramerate = maxFramerate;
}
}
private static String getSupportedFormatsAsJson(int id) throws JSONException {
ArrayList<CaptureFormat> formats = supportedFormats[id];
JSONArray json_formats = new JSONArray();
for (CaptureFormat format : formats) {
JSONObject json_format = new JSONObject();
json_format.put("width", format.width);
json_format.put("height", format.height);
json_format.put("framerate", (format.maxFramerate + 999) / 1000);
json_formats.put(json_format);
}
Log.d(TAG, "Supported formats: " + json_formats.toString(2));
return json_formats.toString();
}
// Returns a list of CaptureFormat for the camera with index id.
static ArrayList<CaptureFormat> getSupportedFormats(int id) {
Camera camera;
camera = Camera.open(id);
Camera.Parameters parameters;
parameters = camera.getParameters();
ArrayList<CaptureFormat> formatList = new ArrayList<CaptureFormat>();
// getSupportedPreviewFpsRange returns a sorted list.
List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
int[] range = {0, 0};
if (listFpsRange != null)
range = listFpsRange.get(listFpsRange.size() -1);
List<Camera.Size> supportedSizes =
parameters.getSupportedPreviewSizes();
for (Camera.Size size : supportedSizes) {
formatList.add(new CaptureFormat(size.width, size.height,
range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]));
}
camera.release();
return formatList;
}
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 the
// API needs a synchronous success return value we wait for the result.
synchronized void startCapture(
final int width, final int height, final int framerate,
final Context applicationContext, final CapturerObserver frameObserver) {
Log.d(TAG, "startCapture requested: " + width + "x" + height
+ "@" + framerate);
if (cameraThread != null || cameraThreadHandler != null) {
throw new RuntimeException("Camera thread already started!");
}
if (applicationContext == null) {
throw new RuntimeException("applicationContext not set.");
}
if (frameObserver == null) {
throw new RuntimeException("frameObserver not set.");
}
Exchanger<Handler> handlerExchanger = new Exchanger<Handler>();
cameraThread = new CameraThread(handlerExchanger);
cameraThread.start();
cameraThreadHandler = exchange(handlerExchanger, null);
cameraThreadHandler.post(new Runnable() {
@Override public void run() {
startCaptureOnCameraThread(width, height, framerate, frameObserver,
applicationContext);
}
});
}
private void startCaptureOnCameraThread(
int width, int height, int framerate, CapturerObserver frameObserver,
Context applicationContext) {
Throwable error = null;
this.applicationContext = applicationContext;
this.frameObserver = frameObserver;
this.width = width;
this.height = height;
this.framerate = framerate;
try {
this.camera = Camera.open(id);
this.info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
// 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 {
cameraSurfaceTexture = null;
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);
}
Log.d(TAG, "Camera orientation: " + info.orientation +
" .Device orientation: " + getDeviceOrientation());
Camera.Parameters parameters = camera.getParameters();
Log.d(TAG, "isVideoStabilizationSupported: " +
parameters.isVideoStabilizationSupported());
if (parameters.isVideoStabilizationSupported()) {
parameters.setVideoStabilization(true);
}
int androidFramerate = framerate * 1000;
int[] range = getFramerateRange(parameters, androidFramerate);
if (range != null) {
Log.d(TAG, "Start capturing: " + width + "x" + height + "@[" +
range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + ":" +
range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
parameters.setPreviewFpsRange(
range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
}
parameters.setPictureSize(width, height);
parameters.setPreviewSize(width, height);
int format = ImageFormat.NV21;
parameters.setPreviewFormat(format);
camera.setParameters(parameters);
// Note: setRecordingHint(true) actually decrease frame rate on N5.
// parameters.setRecordingHint(true);
int bufSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
for (int i = 0; i < numCaptureBuffers; i++) {
camera.addCallbackBuffer(new byte[bufSize]);
}
camera.setPreviewCallbackWithBuffer(this);
camera.startPreview();
frameObserver.OnCapturerStarted(true);
return;
} catch (RuntimeException e) {
error = e;
}
Log.e(TAG, "startCapture failed", error);
if (camera != null) {
Exchanger<Boolean> resultDropper = new Exchanger<Boolean>();
stopCaptureOnCameraThread(resultDropper);
frameObserver.OnCapturerStarted(false);
}
frameObserver.OnCapturerStarted(false);
return;
}
// Called by native code. Returns true when camera is known to be stopped.
synchronized boolean stopCapture() {
Log.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;
Log.d(TAG, "stopCapture done");
return status;
}
private void stopCaptureOnCameraThread(Exchanger<Boolean> result) {
Log.d(TAG, "stopCaptureOnCameraThread");
if (camera == null) {
throw new RuntimeException("Camera is already stopped!");
}
frameObserver = null;
doStopCaptureOnCamerathread();
exchange(result, true);
Looper.myLooper().quit();
return;
}
private void doStopCaptureOnCamerathread() {
try {
camera.stopPreview();
camera.setPreviewCallbackWithBuffer(null);
camera.setPreviewTexture(null);
cameraSurfaceTexture = null;
if (cameraGlTextures != null) {
GLES20.glDeleteTextures(1, cameraGlTextures, 0);
cameraGlTextures = null;
}
camera.release();
camera = null;
} catch (IOException e) {
Log.e(TAG, "Failed to stop camera", e);
}
}
private void switchCameraOnCameraThread() {
Log.d(TAG, "switchCameraOnCameraThread");
doStopCaptureOnCamerathread();
startCaptureOnCameraThread(width, height, framerate, frameObserver,
applicationContext);
}
private int getDeviceOrientation() {
int orientation = 0;
WindowManager wm = (WindowManager) applicationContext.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 static int[] getFramerateRange(Camera.Parameters parameters,
int framerate) {
List<int[]> listFpsRange = parameters.getSupportedPreviewFpsRange();
int[] bestRange = null;
int bestRangeDiff = Integer.MAX_VALUE;
for (int[] range : listFpsRange) {
int rangeDiff =
abs(framerate -range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX])
+ abs(range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] - framerate);
if (bestRangeDiff > rangeDiff) {
bestRange = range;
bestRangeDiff = rangeDiff;
}
}
return bestRange;
}
// 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!");
}
long captureTimeMs = SystemClock.elapsedRealtime();
int rotation = getDeviceOrientation();
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
rotation = 360 - rotation;
}
rotation = (info.orientation + rotation) % 360;
frameObserver.OnFrameCaptured(data, rotation, captureTimeMs);
camera.addCallbackBuffer(data);
}
// runCameraThreadUntilIdle make sure all posted messages to the cameraThread
// is processed before returning. It does that by itself posting a message to
// to the message queue and waits until is has been processed.
// It is used in tests.
void runCameraThreadUntilIdle() {
if (cameraThreadHandler == null)
return;
final Exchanger<Boolean> result = new Exchanger<Boolean>();
cameraThreadHandler.post(new Runnable() {
@Override public void run() {
exchange(result, true); // |true| is a dummy here.
}
});
exchange(result, false); // |false| is a dummy value here.
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);
}
}
// Interface used for providing callbacks to an observer.
interface CapturerObserver {
// Notify if the camera have beens started successfully or not.
// Called on a Java thread owned by VideoCapturerAndroid.
abstract void OnCapturerStarted(boolean success);
// Delivers a captured frame. Called on a Java thread owned by
// VideoCapturerAndroid.
abstract void OnFrameCaptured(byte[] data, int rotation, long timeStamp);
}
// An implementation of CapturerObserver that forwards all calls from
// Java to the C layer.
public static class NativeFrameObserver implements CapturerObserver {
private final long nativeCapturer;
public NativeFrameObserver(long nativeCapturer) {
this.nativeCapturer = nativeCapturer;
}
@Override
public void OnFrameCaptured(byte[] data, int rotation, long timeStamp) {
nativeOnFrameCaptured(nativeCapturer, data, rotation, timeStamp);
}
private native void nativeOnFrameCaptured(
long captureObject, byte[] data, int rotation, long timeStamp);
@Override
public void OnCapturerStarted(boolean success) {
nativeCapturerStarted(nativeCapturer, success);
}
private native void nativeCapturerStarted(long captureObject,
boolean success);
}
}

View File

@ -27,13 +27,12 @@
package org.appspot.apprtc;
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.appspot.apprtc.util.LooperExecutor;
import android.content.Context;
import android.opengl.EGLContext;
import android.util.Log;
import org.appspot.apprtc.AppRTCClient.SignalingParameters;
import org.appspot.apprtc.util.LooperExecutor;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaCodecVideoEncoder;
@ -47,7 +46,7 @@ import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.StatsObserver;
import org.webrtc.StatsReport;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoCapturerAndroid;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
@ -109,6 +108,8 @@ public class PeerConnectionClient {
private boolean useFrontFacingCamera = true;
private SessionDescription localSdp = null; // either offer or answer SDP
private MediaStream mediaStream = null;
private VideoCapturerAndroid videoCapturer = null;
private Context context = null;
// enableVideo is set to true if video should be rendered and sent.
private boolean renderVideo = true;
private VideoTrack localVideoTrack = null;
@ -282,6 +283,7 @@ public class PeerConnectionClient {
events.onPeerConnectionError("Failed to initializeAndroidGlobals");
}
factory = new PeerConnectionFactory();
this.context = context;
Log.d(TAG, "Peer connection factory created.");
}
@ -317,7 +319,9 @@ public class PeerConnectionClient {
mediaStream = factory.createLocalMediaStream("ARDAMS");
if (videoConstraints != null) {
mediaStream.addTrack(createVideoTrack(useFrontFacingCamera));
videoCapturer = VideoCapturerAndroid.create(
VideoCapturerAndroid.getNameOfFrontFacingDevice());
mediaStream.addTrack(createVideoTrack(videoCapturer));
}
if (signalingParameters.audioConstraints != null) {
@ -529,45 +533,12 @@ public class PeerConnectionClient {
});
}
// Cycle through likely device names for the camera and return the first
// capturer that works, or crash if none do.
private VideoCapturer getVideoCapturer(boolean useFrontFacing) {
String[] cameraFacing = { "front", "back" };
if (!useFrontFacing) {
cameraFacing[0] = "back";
cameraFacing[1] = "front";
}
for (String facing : cameraFacing) {
int[] cameraIndex = { 0, 1 };
int[] cameraOrientation = { 0, 90, 180, 270 };
for (int index : cameraIndex) {
for (int orientation : cameraOrientation) {
String name = "Camera " + index + ", Facing " + facing
+ ", Orientation " + orientation;
VideoCapturer capturer = VideoCapturer.create(name);
if (capturer != null) {
Log.d(TAG, "Using camera: " + name);
return capturer;
}
}
}
}
reportError("Failed to open capturer");
return null;
}
private VideoTrack createVideoTrack(boolean frontFacing) {
VideoCapturer capturer = getVideoCapturer(frontFacing);
if (videoSource != null) {
videoSource.stop();
videoSource.dispose();
}
videoSource = factory.createVideoSource(capturer, videoConstraints);
String trackExtension = frontFacing ? "frontFacing" : "backFacing";
private VideoTrack createVideoTrack(VideoCapturerAndroid capturer) {
videoSource = factory.createVideoSource(
capturer, signalingParameters.videoConstraints);
localVideoTrack =
factory.createVideoTrack(VIDEO_TRACK_ID + trackExtension, videoSource);
factory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
localVideoTrack.setEnabled(renderVideo);
localVideoTrack.addRenderer(new VideoRenderer(localRender));
return localVideoTrack;
@ -669,50 +640,8 @@ public class PeerConnectionClient {
if (videoConstraints == null) {
return; // No video is sent.
}
if (peerConnection.signalingState()
!= PeerConnection.SignalingState.STABLE) {
Log.e(TAG, "Switching camera during negotiation is not handled.");
return;
}
videoCapturer.switchCamera();
Log.d(TAG, "Switch camera");
peerConnection.removeStream(mediaStream);
VideoTrack currentTrack = mediaStream.videoTracks.get(0);
mediaStream.removeTrack(currentTrack);
String trackId = currentTrack.id();
// On Android, there can only be one camera open at the time and we
// need to release our implicit references to the videoSource before the
// PeerConnectionFactory is released. Since createVideoTrack creates a new
// videoSource and frees the old one, we need to release the track here.
currentTrack.dispose();
useFrontFacingCamera = !useFrontFacingCamera;
VideoTrack newTrack = createVideoTrack(useFrontFacingCamera);
mediaStream.addTrack(newTrack);
peerConnection.addStream(mediaStream);
SessionDescription remoteDesc = peerConnection.getRemoteDescription();
if (localSdp == null || remoteDesc == null) {
Log.d(TAG, "Switching camera before the negotiation started.");
return;
}
localSdp = new SessionDescription(localSdp.type,
localSdp.description.replaceAll(trackId, newTrack.id()));
if (isInitiator) {
peerConnection.setLocalDescription(
new SwitchCameraSdbObserver(), localSdp);
peerConnection.setRemoteDescription(
new SwitchCameraSdbObserver(), remoteDesc);
} else {
peerConnection.setRemoteDescription(
new SwitchCameraSdbObserver(), remoteDesc);
peerConnection.setLocalDescription(
new SwitchCameraSdbObserver(), localSdp);
}
Log.d(TAG, "Switch camera done");
}
public void switchCamera() {
@ -893,24 +822,4 @@ public class PeerConnectionClient {
reportError("setSDP error: " + error);
}
}
private class SwitchCameraSdbObserver implements SdpObserver {
@Override
public void onCreateSuccess(SessionDescription sdp) {
}
@Override
public void onSetSuccess() {
Log.d(TAG, "Camera switch SDP set succesfully");
}
@Override
public void onCreateFailure(final String error) {
}
@Override
public void onSetFailure(final String error) {
reportError("setSDP error while switching camera: " + error);
}
}
}

View File

@ -110,9 +110,8 @@
'app/webrtc/java/android/org/webrtc/VideoRendererGui.java',
'app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java',
'app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java',
'app/webrtc/java/src/org/webrtc/VideoCapturerAndroid.java',
'<(webrtc_modules_dir)/audio_device/android/java/src/org/webrtc/voiceengine/AudioManagerAndroid.java',
'<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java',
'<(webrtc_modules_dir)/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java',
'<(webrtc_modules_dir)/video_render/android/java/src/org/webrtc/videoengine/ViEAndroidGLES20.java',
'<(webrtc_modules_dir)/video_render/android/java/src/org/webrtc/videoengine/ViERenderer.java',
'<(webrtc_modules_dir)/video_render/android/java/src/org/webrtc/videoengine/ViESurfaceRenderer.java',
@ -694,6 +693,14 @@
'app/webrtc/webrtcsessiondescriptionfactory.cc',
'app/webrtc/webrtcsessiondescriptionfactory.h',
],
'conditions': [
['OS=="android" and build_with_chromium==0', {
'sources': [
'app/webrtc/androidvideocapturer.h',
'app/webrtc/androidvideocapturer.cc',
],
}],
],
}, # target libjingle_peerconnection
],
}