diff --git a/all.gyp b/all.gyp index 99ca3abe1..27cc65003 100644 --- a/all.gyp +++ b/all.gyp @@ -18,6 +18,13 @@ 'talk/libjingle_examples.gyp:*', 'talk/libjingle_tests.gyp:*', ], + 'conditions': [ + ['OS=="android"', { + 'dependencies': [ + 'webrtc/webrtc_examples.gyp:*', + ], + }], + ], }, ], } diff --git a/webrtc/video_engine/test/android/OWNERS b/webrtc/examples/android/OWNERS similarity index 100% rename from webrtc/video_engine/test/android/OWNERS rename to webrtc/examples/android/OWNERS diff --git a/webrtc/video_engine/test/android/AndroidManifest.xml b/webrtc/examples/android/media_demo/AndroidManifest.xml similarity index 80% rename from webrtc/video_engine/test/android/AndroidManifest.xml rename to webrtc/examples/android/media_demo/AndroidManifest.xml index 39a3c515e..62bf46076 100644 --- a/webrtc/video_engine/test/android/AndroidManifest.xml +++ b/webrtc/examples/android/media_demo/AndroidManifest.xml @@ -1,11 +1,12 @@ + android:versionCode="1" package="org.webrtc.webrtcdemo" android:versionName="1.07"> + android:label="@string/appName" + android:debuggable="true"> @@ -25,4 +26,4 @@ - + \ No newline at end of file diff --git a/webrtc/examples/android/media_demo/README b/webrtc/examples/android/media_demo/README new file mode 100644 index 000000000..af8872151 --- /dev/null +++ b/webrtc/examples/android/media_demo/README @@ -0,0 +1,24 @@ +This directory contains a sample app for sending and receiving video and audio +on Android. It further lets you enable and disable some call quality +enhancements such as echo cancellation, noise suppression etc. + +Prerequisites: +- Make sure gclient is checking out tools necessary to target Android: your + .gclient file should contain a line like: + target_os = ['android'] + Make sure to re-run gclient sync after adding this to download the tools. +- Env vars need to be set up to target Android; easiest way to do this is to run + (from the libjingle trunk directory): + . ./build/android/envsetup.sh + Note that this clobbers any previously-set $GYP_DEFINES so it must be done + before the next item. +- Set up webrtc-related GYP variables: + export GYP_DEFINES="$GYP_DEFINES java_home=" +- Finally, run "gclient runhooks" to generate Android-targeting .ninja files. + +Example of building the app: +cd /trunk +ninja -C out/Debug WebRTCDemo + +It can then be installed and run on the device: +adb install -r out/Debug/WebRTCDemo-debug.apk \ No newline at end of file diff --git a/webrtc/examples/android/media_demo/build.xml b/webrtc/examples/android/media_demo/build.xml new file mode 100644 index 000000000..c8a51dd5f --- /dev/null +++ b/webrtc/examples/android/media_demo/build.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/webrtc/examples/android/media_demo/jni/jni_helpers.cc b/webrtc/examples/android/media_demo/jni/jni_helpers.cc new file mode 100644 index 000000000..d7e326766 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/jni_helpers.cc @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" + +#include + +#include "third_party/icu/source/common/unicode/unistr.h" + +using icu::UnicodeString; + +jmethodID GetMethodID(JNIEnv* jni, jclass c, const std::string& name, + const char* signature) { + jmethodID m = jni->GetMethodID(c, name.c_str(), signature); + CHECK_EXCEPTION(jni, "error during GetMethodID"); + return m; +} + +jlong jlongFromPointer(void* ptr) { + CHECK(sizeof(intptr_t) <= sizeof(jlong), "Time to rethink the use of jlongs"); + // Going through intptr_t to be obvious about the definedness of the + // conversion from pointer to integral type. intptr_t to jlong is a standard + // widening by the COMPILE_ASSERT above. + jlong ret = reinterpret_cast(ptr); + CHECK(reinterpret_cast(ret) == ptr, + "jlong does not convert back to pointer"); + return ret; +} + +// Given a (UTF-16) jstring return a new UTF-8 native string. +std::string JavaToStdString(JNIEnv* jni, const jstring& j_string) { + const jchar* jchars = jni->GetStringChars(j_string, NULL); + CHECK_EXCEPTION(jni, "Error during GetStringChars"); + UnicodeString ustr(jchars, jni->GetStringLength(j_string)); + CHECK_EXCEPTION(jni, "Error during GetStringLength"); + jni->ReleaseStringChars(j_string, jchars); + CHECK_EXCEPTION(jni, "Error during ReleaseStringChars"); + std::string ret; + return ustr.toUTF8String(ret); +} + +ClassReferenceHolder::ClassReferenceHolder(JNIEnv* jni, const char** classes, + int size) { + for (int i = 0; i < size; ++i) { + LoadClass(jni, classes[i]); + } +} +ClassReferenceHolder::~ClassReferenceHolder() { + CHECK(classes_.empty(), "Must call FreeReferences() before dtor!"); +} + +void ClassReferenceHolder::FreeReferences(JNIEnv* jni) { + for (std::map::const_iterator it = classes_.begin(); + it != classes_.end(); ++it) { + jni->DeleteGlobalRef(it->second); + } + classes_.clear(); +} + +jclass ClassReferenceHolder::GetClass(const std::string& name) { + std::map::iterator it = classes_.find(name); + CHECK(it != classes_.end(), "Could not find class"); + return it->second; +} + +void ClassReferenceHolder::LoadClass(JNIEnv* jni, const std::string& name) { + jclass localRef = jni->FindClass(name.c_str()); + CHECK_EXCEPTION(jni, "Could not load class"); + CHECK(localRef, name.c_str()); + jclass globalRef = reinterpret_cast(jni->NewGlobalRef(localRef)); + CHECK_EXCEPTION(jni, "error during NewGlobalRef"); + CHECK(globalRef, name.c_str()); + bool inserted = classes_.insert(std::make_pair(name, globalRef)).second; + CHECK(inserted, "Duplicate class name"); +} diff --git a/webrtc/examples/android/media_demo/jni/jni_helpers.h b/webrtc/examples/android/media_demo/jni/jni_helpers.h new file mode 100644 index 000000000..25706db4b --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/jni_helpers.h @@ -0,0 +1,81 @@ +/* + * 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. + */ + +#ifndef WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_JNI_HELPERS_H_ +#define WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_JNI_HELPERS_H_ + +// TODO(henrike): this file contains duplication with regards to +// talk/app/webrtc/java/jni/peerconnection_jni.cc. When/if code can be shared +// between trunk/talk and trunk/webrtc remove the duplication. + +#include +#include + +#include +#include + +#define TAG "WEBRTC-NATIVE" + +// Abort the process if |x| is false, emitting |msg| to logcat. +#define CHECK(x, msg) \ + if (x) { \ + } else { \ + __android_log_print(ANDROID_LOG_ERROR, TAG, "%s:%d: %s", __FILE__, \ + __LINE__, msg); \ + abort(); \ + } + +// Abort the process if |jni| has a Java exception pending, emitting |msg| to +// logcat. +#define CHECK_EXCEPTION(jni, msg) \ + if (0) { \ + } else { \ + if (jni->ExceptionCheck()) { \ + jni->ExceptionDescribe(); \ + jni->ExceptionClear(); \ + CHECK(0, msg); \ + } \ + } + +#define ARRAYSIZE(instance) \ + static_cast(sizeof(instance) / sizeof(instance[0])) + +// JNIEnv-helper methods that CHECK success: no Java exception thrown and found +// object/class/method/field is non-null. +jmethodID GetMethodID(JNIEnv* jni, jclass c, const std::string& name, + const char* signature); + +// Return a |jlong| that will automatically convert back to |ptr| when assigned +// to a |uint64| +jlong jlongFromPointer(void* ptr); + +// Given a (UTF-16) jstring return a new UTF-8 native string. +std::string JavaToStdString(JNIEnv* jni, const jstring& j_string); + +// Android's FindClass() is trickier than usual because the app-specific +// ClassLoader is not consulted when there is no app-specific frame on the +// stack. Consequently, we only look up classes once in JNI_OnLoad. +// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass +class ClassReferenceHolder { + public: + ClassReferenceHolder(JNIEnv* jni, const char** classes, int size); + ~ClassReferenceHolder(); + + void FreeReferences(JNIEnv* jni); + + jclass GetClass(const std::string& name); + + private: + void LoadClass(JNIEnv* jni, const std::string& name); + + std::map classes_; +}; + +#endif // WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_JNI_HELPERS_H_ diff --git a/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.cc b/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.cc new file mode 100644 index 000000000..15eb2d720 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.cc @@ -0,0 +1,81 @@ +/* + * 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/examples/android/media_demo/jni/media_codec_video_decoder.h" + +#include + +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" +#include "webrtc/modules/utility/interface/helpers_android.h" + +namespace webrtc { + +MediaCodecVideoDecoder::MediaCodecVideoDecoder(JavaVM* vm, jobject decoder) + : vm_(vm), decoder_(NULL), j_start_(NULL), j_push_buffer_(NULL) { + AttachThreadScoped ats(vm_); + JNIEnv* jni = ats.env(); + // Make sure that the decoder is not recycled. + decoder_ = jni->NewGlobalRef(decoder); + + // Get all function IDs. + jclass decoderClass = jni->GetObjectClass(decoder); + j_push_buffer_ = + jni->GetMethodID(decoderClass, "pushBuffer", "(Ljava/nio/ByteBuffer;J)V"); + j_start_ = jni->GetMethodID(decoderClass, "start", "(II)Z"); +} + +MediaCodecVideoDecoder::~MediaCodecVideoDecoder() { + AttachThreadScoped ats(vm_); + JNIEnv* jni = ats.env(); + jni->DeleteGlobalRef(decoder_); +} + +int32_t MediaCodecVideoDecoder::InitDecode(const VideoCodec* codecSettings, + int32_t numberOfCores) { + AttachThreadScoped ats(vm_); + JNIEnv* jni = ats.env(); + if (!jni->CallBooleanMethod(decoder_, j_start_, codecSettings->width, + codecSettings->height)) { + return WEBRTC_VIDEO_CODEC_ERROR; + } + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoDecoder::Decode( + const EncodedImage& inputImage, bool missingFrames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codecSpecificInfo, int64_t renderTimeMs) { + + AttachThreadScoped ats(vm_); + JNIEnv* jni = ats.env(); + jobject byteBuffer = + jni->NewDirectByteBuffer(inputImage._buffer, inputImage._length); + jni->CallVoidMethod(decoder_, j_push_buffer_, byteBuffer, renderTimeMs); + jni->DeleteLocalRef(byteBuffer); + return WEBRTC_VIDEO_CODEC_NO_OUTPUT; +} + +int32_t MediaCodecVideoDecoder::RegisterDecodeCompleteCallback( + DecodedImageCallback* callback) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoDecoder::Release() { + // TODO(hellner): this maps nicely to MediaCodecVideoDecoder::dispose(). + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoDecoder::Reset() { + // TODO(hellner): implement. MediaCodec::stop() followed by + // MediaCodec::start()? + return WEBRTC_VIDEO_CODEC_OK; +} + +} // namespace webrtc diff --git a/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.h b/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.h new file mode 100644 index 000000000..0aafc0bf1 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/media_codec_video_decoder.h @@ -0,0 +1,61 @@ +/* + * 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_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_MEDIA_CODEC_VIDEO_DECODER_H_ +#define WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_MEDIA_CODEC_VIDEO_DECODER_H_ + +#include + +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" +#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" + +namespace webrtc { + +class MediaCodecVideoDecoder : public VideoDecoder { + public: + MediaCodecVideoDecoder(JavaVM* vm, jobject decoder); + virtual ~MediaCodecVideoDecoder(); + + virtual int32_t InitDecode(const VideoCodec* codecSettings, + int32_t numberOfCores); + + virtual int32_t Decode(const EncodedImage& inputImage, bool missingFrames, + const RTPFragmentationHeader* fragmentation, + const CodecSpecificInfo* codecSpecificInfo, + int64_t renderTimeMs); + + virtual int32_t RegisterDecodeCompleteCallback( + DecodedImageCallback* callback); + + virtual int32_t Release(); + + virtual int32_t Reset(); + + virtual int32_t SetCodecConfigParameters(const uint8_t* /*buffer*/, + int32_t /*size*/) { + return WEBRTC_VIDEO_CODEC_ERROR; + } + + virtual VideoDecoder* Copy() { + CHECK(0, "Not implemented"); + return NULL; + } + + private: + JavaVM* vm_; + // Global reference to a (Java) MediaCodecVideoDecoder object. + jobject decoder_; + jmethodID j_start_; + jmethodID j_push_buffer_; +}; + +} // namespace webrtc + +#endif // WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_MEDIA_CODEC_VIDEO_DECODER_H_ diff --git a/webrtc/examples/android/media_demo/jni/on_load.cc b/webrtc/examples/android/media_demo/jni/on_load.cc new file mode 100644 index 000000000..27a2394b3 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/on_load.cc @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#include + +#include + +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" +#include "webrtc/examples/android/media_demo/jni/video_engine_jni.h" +#include "webrtc/examples/android/media_demo/jni/voice_engine_jni.h" +#include "webrtc/video_engine/include/vie_base.h" +#include "webrtc/voice_engine/include/voe_base.h" + +// Macro for native functions that can be found by way of jni-auto discovery. +// Note extern "C" is needed for "discovery" of native methods to work. +#define JOWW(rettype, name) \ + extern "C" rettype JNIEXPORT JNICALL Java_org_webrtc_webrtcdemo_##name + +static JavaVM* g_vm = NULL; + +extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { + // Only called once. + CHECK(!g_vm, "OnLoad called more than once"); + g_vm = vm; + return JNI_VERSION_1_4; +} + +JOWW(void, NativeWebRtcContextRegistry_register)( + JNIEnv* jni, + jclass, + jobject context) { + webrtc_examples::SetVoeDeviceObjects(g_vm); + webrtc_examples::SetVieDeviceObjects(g_vm); + CHECK(webrtc::VideoEngine::SetAndroidObjects(g_vm) == 0, + "Failed to register android objects to video engine"); + CHECK(webrtc::VoiceEngine::SetAndroidObjects(g_vm, jni, context) == 0, + "Failed to register android objects to voice engine"); +} + +JOWW(void, NativeWebRtcContextRegistry_unRegister)( + JNIEnv* jni, + jclass) { + CHECK(webrtc::VoiceEngine::SetAndroidObjects(NULL, NULL, NULL) == 0, + "Failed to unregister android objects from voice engine"); + webrtc_examples::ClearVieDeviceObjects(); + webrtc_examples::ClearVoeDeviceObjects(); +} diff --git a/webrtc/examples/android/media_demo/jni/video_engine_jni.cc b/webrtc/examples/android/media_demo/jni/video_engine_jni.cc new file mode 100644 index 000000000..712e17866 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/video_engine_jni.cc @@ -0,0 +1,711 @@ +/* + * 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. + */ + +// This file contains JNI for the video engine interfaces. +// The native functions are found using jni's auto discovery. + +#include "webrtc/examples/android/media_demo/jni/video_engine_jni.h" + +#include +#include + +#include "webrtc/common_types.h" +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" +#include "webrtc/examples/android/media_demo/jni/media_codec_video_decoder.h" +#include "webrtc/examples/android/media_demo/jni/voice_engine_jni.h" +#include "webrtc/modules/utility/interface/helpers_android.h" +#include "webrtc/test/channel_transport/include/channel_transport.h" +#include "webrtc/video_engine/include/vie_base.h" +#include "webrtc/video_engine/include/vie_capture.h" +#include "webrtc/video_engine/include/vie_codec.h" +#include "webrtc/video_engine/include/vie_external_codec.h" +#include "webrtc/video_engine/include/vie_network.h" +#include "webrtc/video_engine/include/vie_render.h" +#include "webrtc/video_engine/include/vie_rtp_rtcp.h" + +// Macro for native functions that can be found by way of jni-auto discovery. +// Note extern "C" is needed for "discovery" of native methods to work. +#define JOWW(rettype, name) \ + extern "C" rettype JNIEXPORT JNICALL Java_org_webrtc_webrtcdemo_##name + +namespace { + +static JavaVM* g_vm = NULL; +static ClassReferenceHolder* g_class_reference_holder = NULL; + +jclass GetClass(const char* name) { + CHECK(g_class_reference_holder, "Class reference holder NULL"); + return g_class_reference_holder->GetClass(name); +} + +// C(++) description of a camera. This class is created by Java native calls +// and associated with the CameraDesc Java class. The Java class is used in the +// Java code but it is just a thin wrapper of the C(++) class that contain the +// actual information. The information is stored in C(++) as it is used to +// call video engine APIs. +struct CameraDesc { + // The name and id corresponds to ViECapture's |device_nameUTF8| and + // |unique_idUTF8|. + char name[64]; + char unique_id[64]; +}; + +// C++ callback class that can be used to register for callbacks from the +// video engine. It further propagates the callbacks to +// VideoDecodeEncodeObserver.java interface. The memory associated with this +// class is managed globally by the VideoEngineData class when registering and +// unregistering VideoDecodeEncodeObserver.java to receive callbacks. +class VideoDecodeEncodeObserver : public webrtc::ViEDecoderObserver, + public webrtc::ViEEncoderObserver { + public: + explicit VideoDecodeEncodeObserver(jobject j_observer) + : j_observer_(j_observer) { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + jclass j_observer_class = jni->GetObjectClass(j_observer_); + incoming_rate_ = + GetMethodID(jni, j_observer_class, "incomingRate", "(III)V"); + incoming_codec_changed_ = + GetMethodID(jni, j_observer_class, "incomingCodecChanged", + "(ILorg/webrtc/webrtcdemo/VideoCodecInst;)V"); + request_new_keyframe_ = + GetMethodID(jni, j_observer_class, "requestNewKeyFrame", "(I)V"); + outgoing_rate_ = + GetMethodID(jni, j_observer_class, "outgoingRate", "(III)V"); + j_observer_ = jni->NewGlobalRef(j_observer_); + } + + ~VideoDecodeEncodeObserver() { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + jni->DeleteGlobalRef(j_observer_); + } + + virtual void IncomingRate(const int video_channel, + const unsigned int framerate, + const unsigned int bitrate) { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + jni->CallVoidMethod(j_observer_, incoming_rate_, video_channel, + static_cast(framerate), static_cast(bitrate)); + } + + virtual void DecoderTiming(int decode_ms, int max_decode_ms, + int current_delay_ms, int target_delay_ms, + int jitter_buffer_ms, int min_playout_delay_ms, + int render_delay_ms) { + // TODO(fischman): consider plumbing this through to Java. + } + + virtual void IncomingCodecChanged(const int video_channel, + const webrtc::VideoCodec& video_codec) { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + webrtc::VideoCodec* codec = new webrtc::VideoCodec(video_codec); + jclass j_codec_class = + GetClass("org/webrtc/webrtcdemo/VideoCodecInst"); + jmethodID j_codec_ctor = GetMethodID(jni, j_codec_class, "", "(J)V"); + jobject j_codec = + jni->NewObject(j_codec_class, j_codec_ctor, jlongFromPointer(codec)); + CHECK_EXCEPTION(jni, "error during NewObject"); + jni->CallVoidMethod(j_observer_, incoming_codec_changed_, video_channel, + j_codec); + } + + virtual void RequestNewKeyFrame(const int video_channel) { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + jni->CallVoidMethod(j_observer_, request_new_keyframe_, video_channel); + } + + virtual void OutgoingRate(const int video_channel, + const unsigned int framerate, + const unsigned int bitrate) { + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + jni->CallVoidMethod(j_observer_, outgoing_rate_, video_channel, + static_cast(framerate), static_cast(bitrate)); + } + + virtual void SuspendChange(int video_channel, bool is_suspended) {} + + private: + jobject j_observer_; + jmethodID incoming_rate_; + jmethodID incoming_codec_changed_; + jmethodID request_new_keyframe_; + jmethodID outgoing_rate_; +}; + +template +void ReleaseSubApi(T instance) { + CHECK(instance->Release() == 0, "failed to release instance") +} + +class VideoEngineData { + public: + VideoEngineData() + : vie(webrtc::VideoEngine::Create()), + base(webrtc::ViEBase::GetInterface(vie)), + codec(webrtc::ViECodec::GetInterface(vie)), + network(webrtc::ViENetwork::GetInterface(vie)), + rtp(webrtc::ViERTP_RTCP::GetInterface(vie)), + render(webrtc::ViERender::GetInterface(vie)), + capture(webrtc::ViECapture::GetInterface(vie)), + externalCodec(webrtc::ViEExternalCodec::GetInterface(vie)) { + CHECK(vie != NULL, "Video engine instance failed to be created"); + CHECK(base != NULL, "Failed to acquire base interface"); + CHECK(codec != NULL, "Failed to acquire codec interface"); + CHECK(network != NULL, "Failed to acquire network interface"); + CHECK(rtp != NULL, "Failed to acquire rtp interface"); + CHECK(render != NULL, "Failed to acquire render interface"); + CHECK(capture != NULL, "Failed to acquire capture interface"); + CHECK(externalCodec != NULL, "Failed to acquire externalCodec interface"); + } + + ~VideoEngineData() { + CHECK(channel_transports_.empty(), + "ViE transports must be deleted before terminating"); + CHECK(observers_.empty(), + "ViE observers must be deleted before terminating"); + CHECK(external_decoders_.empty(), + "ViE external decoders must be deleted before terminating"); + ReleaseSubApi(externalCodec); + ReleaseSubApi(capture); + ReleaseSubApi(render); + ReleaseSubApi(rtp); + ReleaseSubApi(network); + ReleaseSubApi(codec); + ReleaseSubApi(base); + webrtc::VideoEngine* vie_pointer = vie; + CHECK(webrtc::VideoEngine::Delete(vie_pointer), "ViE failed to be deleted"); + } + + int CreateChannel() { + int channel; + CHECK(base->CreateChannel(channel) == 0, "Failed to create channel"); + CreateTransport(channel); + return channel; + } + + int DeleteChannel(int channel) { + if (base->DeleteChannel(channel) != 0) { + return -1; + } + DeleteTransport(channel); + return 0; + } + + webrtc::test::VideoChannelTransport* GetTransport(int channel) { + ChannelTransports::iterator found = channel_transports_.find(channel); + if (found == channel_transports_.end()) { + return NULL; + } + return found->second; + } + + int RegisterObserver(int channel, jobject j_observer) { + CHECK(observers_.find(channel) == observers_.end(), + "Observer already created for channel, inconsistent state"); + observers_[channel] = new VideoDecodeEncodeObserver(j_observer); + int ret_val = codec->RegisterDecoderObserver(channel, *observers_[channel]); + ret_val |= codec->RegisterEncoderObserver(channel, *observers_[channel]); + return ret_val; + } + + int DeregisterObserver(int channel) { + Observers::iterator found = observers_.find(channel); + if (observers_.find(channel) == observers_.end()) { + return -1; + } + int ret_val = codec->DeregisterDecoderObserver(channel); + ret_val |= codec->DeregisterEncoderObserver(channel); + delete found->second; + observers_.erase(found); + return ret_val; + } + + int RegisterExternalReceiveCodec(jint channel, jint pl_type, jobject decoder, + bool internal_source) { + CHECK(external_decoders_.find(channel) == external_decoders_.end(), + "External decoder already created for channel, inconsistent state"); + external_decoders_[channel] = + new webrtc::MediaCodecVideoDecoder(g_vm, decoder); + return externalCodec->RegisterExternalReceiveCodec( + channel, pl_type, external_decoders_[channel], internal_source); + } + + int DeRegisterExternalReceiveCodec(jint channel, jint pl_type) { + ExternalDecoders::iterator found = external_decoders_.find(channel); + CHECK(found != external_decoders_.end(), + "ViE channel missing external decoder, inconsistent state"); + CHECK(externalCodec->DeRegisterExternalReceiveCodec(channel, pl_type) == 0, + "Failed to register external receive decoder"); + delete found->second; + external_decoders_.erase(found); + return 0; + } + + webrtc::VideoEngine* const vie; + webrtc::ViEBase* const base; + webrtc::ViECodec* const codec; + webrtc::ViENetwork* const network; + webrtc::ViERTP_RTCP* const rtp; + webrtc::ViERender* const render; + webrtc::ViECapture* const capture; + webrtc::ViEExternalCodec* const externalCodec; + + private: + // Video engine no longer provides a socket implementation. There is, + // however, a socket implementation in webrtc::test. + typedef std::map + ChannelTransports; + typedef std::map Observers; + typedef std::map ExternalDecoders; + + void CreateTransport(int channel) { + CHECK(GetTransport(channel) == NULL, + "Transport already created for ViE channel, inconsistent state"); + channel_transports_[channel] = + new webrtc::test::VideoChannelTransport(network, channel); + } + void DeleteTransport(int channel) { + CHECK(GetTransport(channel) != NULL, + "ViE channel missing transport, inconsistent state"); + delete channel_transports_[channel]; + channel_transports_.erase(channel); + } + + ChannelTransports channel_transports_; + Observers observers_; + ExternalDecoders external_decoders_; +}; + +webrtc::VideoCodec* GetCodecInst(JNIEnv* jni, jobject j_codec) { + jclass j_codec_class = jni->GetObjectClass(j_codec); + jfieldID native_codec_id = + jni->GetFieldID(j_codec_class, "nativeCodecInst", "J"); + jlong j_p = jni->GetLongField(j_codec, native_codec_id); + return reinterpret_cast(j_p); +} + +CameraDesc* GetCameraDesc(JNIEnv* jni, jobject j_camera) { + jclass j_camera_class = jni->GetObjectClass(j_camera); + jfieldID native_camera_id = + jni->GetFieldID(j_camera_class, "nativeCameraDesc", "J"); + jlong j_p = jni->GetLongField(j_camera, native_camera_id); + return reinterpret_cast(j_p); +} + +VideoEngineData* GetVideoEngineData(JNIEnv* jni, jobject j_vie) { + jclass j_vie_class = jni->GetObjectClass(j_vie); + jfieldID native_vie_id = + jni->GetFieldID(j_vie_class, "nativeVideoEngine", "J"); + jlong j_p = jni->GetLongField(j_vie, native_vie_id); + return reinterpret_cast(j_p); +} + +} // namespace + +namespace webrtc_examples { + +static const char* g_classes[] = { + "org/webrtc/webrtcdemo/CameraDesc", + "org/webrtc/webrtcdemo/RtcpStatistics", + "org/webrtc/webrtcdemo/VideoCodecInst", + "org/webrtc/webrtcdemo/VideoDecodeEncodeObserver", + "org/webrtc/webrtcdemo/MediaCodecVideoDecoder"}; + +void SetVieDeviceObjects(JavaVM* vm) { + CHECK(vm, "Trying to register NULL vm"); + CHECK(!g_vm, "Trying to re-register vm"); + g_vm = vm; + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + g_class_reference_holder = new ClassReferenceHolder( + jni, g_classes, ARRAYSIZE(g_classes)); +} + +void ClearVieDeviceObjects() { + CHECK(g_vm, "Clearing vm without it being set"); + { + webrtc::AttachThreadScoped ats(g_vm); + g_class_reference_holder->FreeReferences(ats.env()); + } + g_vm = NULL; + delete g_class_reference_holder; + g_class_reference_holder = NULL; +} + +} // namespace webrtc_examples + +JOWW(jlong, VideoEngine_create)(JNIEnv* jni, jclass) { + VideoEngineData* vie_data = new VideoEngineData(); + return jlongFromPointer(vie_data); +} + +JOWW(jint, VideoEngine_init)(JNIEnv* jni, jobject j_vie) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->Init(); +} + +JOWW(jint, VideoEngine_setVoiceEngine)(JNIEnv* jni, jobject j_vie, + jobject j_voe) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + webrtc::VoiceEngine* voe = GetVoiceEngine(jni, j_voe); + return vie_data->base->SetVoiceEngine(voe); +} + +JOWW(void, VideoEngine_dispose)(JNIEnv* jni, jobject j_vie) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + delete vie_data; +} + +JOWW(jint, VideoEngine_startSend)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->StartSend(channel); +} + +JOWW(jint, VideoEngine_stopRender)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->render->StopRender(channel); +} + +JOWW(jint, VideoEngine_stopSend)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->StopSend(channel); +} + +JOWW(jint, VideoEngine_startReceive)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->StartReceive(channel); +} + +JOWW(jint, VideoEngine_stopReceive)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->StopReceive(channel); +} + +JOWW(jint, VideoEngine_createChannel)(JNIEnv* jni, jobject j_vie) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->CreateChannel(); +} + +JOWW(jint, VideoEngine_deleteChannel)(JNIEnv* jni, jobject j_vie, + jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->DeleteChannel(channel); +} + +JOWW(jint, + VideoEngine_connectAudioChannel(JNIEnv* jni, jobject j_vie, + jint video_channel, jint audio_channel)) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->base->ConnectAudioChannel(video_channel, audio_channel); +} + +JOWW(jint, VideoEngine_setLocalReceiver)(JNIEnv* jni, jobject j_vie, + jint channel, jint port) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->GetTransport(channel)->SetLocalReceiver(port); +} + +JOWW(jint, VideoEngine_setSendDestination)(JNIEnv* jni, jobject j_vie, + jint channel, jint port, + jstring j_addr) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + std::string addr = JavaToStdString(jni, j_addr); + webrtc::test::VideoChannelTransport* transport = + vie_data->GetTransport(channel); + return transport->SetSendDestination(addr.c_str(), port); +} + +JOWW(jint, VideoEngine_setReceiveCodec)(JNIEnv* jni, jobject j_vie, + jint channel, jobject j_codec) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + webrtc::VideoCodec* codec = GetCodecInst(jni, j_codec); + return vie_data->codec->SetReceiveCodec(channel, *codec); +} + +JOWW(jint, VideoEngine_setSendCodec)(JNIEnv* jni, jobject j_vie, jint channel, + jobject j_codec) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + webrtc::VideoCodec* codec = GetCodecInst(jni, j_codec); + return vie_data->codec->SetSendCodec(channel, *codec); +} + +JOWW(jint, VideoEngine_numberOfCodecs)(JNIEnv* jni, jobject j_vie) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->codec->NumberOfCodecs(); +} + +JOWW(jobject, VideoEngine_getCodec)(JNIEnv* jni, jobject j_vie, jint index) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + webrtc::VideoCodec* codec = new webrtc::VideoCodec(); + CHECK(vie_data->codec->GetCodec(index, *codec) == 0, + "getCodec must be called with valid index"); + jclass j_codec_class = GetClass("org/webrtc/webrtcdemo/VideoCodecInst"); + jmethodID j_codec_ctor = GetMethodID(jni, j_codec_class, "", "(J)V"); + jobject j_codec = + jni->NewObject(j_codec_class, j_codec_ctor, jlongFromPointer(codec)); + CHECK_EXCEPTION(jni, "error during NewObject"); + return j_codec; +} + +JOWW(jint, VideoEngine_addRenderer)(JNIEnv* jni, jobject j_vie, jint channel, + jobject gl_surface, jint z_order, + jfloat left, jfloat top, jfloat right, + jfloat bottom) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->render->AddRenderer(channel, gl_surface, z_order, left, top, + right, bottom); +} + +JOWW(jint, VideoEngine_removeRenderer)(JNIEnv* jni, jobject j_vie, + jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->render->RemoveRenderer(channel); +} + +JOWW(jint, VideoEngine_registerExternalReceiveCodec)(JNIEnv* jni, jobject j_vie, + jint channel, jint pl_type, + jobject decoder, + bool internal_source) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->RegisterExternalReceiveCodec(channel, pl_type, decoder, + true); +} + +JOWW(jint, + VideoEngine_deRegisterExternalReceiveCodec)(JNIEnv* jni, jobject j_vie, + jint channel, jint pl_type) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->DeRegisterExternalReceiveCodec(channel, pl_type); +} + +JOWW(jint, VideoEngine_startRender)(JNIEnv* jni, jobject j_vie, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->render->StartRender(channel); +} + +JOWW(jint, VideoEngine_numberOfCaptureDevices)(JNIEnv* jni, jobject j_vie) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->NumberOfCaptureDevices(); +} + +JOWW(jobject, + VideoEngine_getCaptureDevice(JNIEnv* jni, jobject j_vie, jint index)) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + CameraDesc* camera_info = new CameraDesc(); + if (vie_data->capture->GetCaptureDevice( + index, camera_info->name, sizeof(camera_info->name), + camera_info->unique_id, sizeof(camera_info->unique_id)) != 0) { + delete camera_info; + return NULL; + } + jclass j_camera_class = GetClass("org/webrtc/webrtcdemo/CameraDesc"); + jmethodID j_camera_ctor = GetMethodID(jni, j_camera_class, "", "(J)V"); + jobject j_camera = jni->NewObject(j_camera_class, j_camera_ctor, + jlongFromPointer(camera_info)); + CHECK_EXCEPTION(jni, "error during NewObject"); + return j_camera; +} + +JOWW(jint, VideoEngine_allocateCaptureDevice)(JNIEnv* jni, jobject j_vie, + jobject j_camera) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + CameraDesc* camera_info = GetCameraDesc(jni, j_camera); + jint capture_id; + if (vie_data->capture->AllocateCaptureDevice(camera_info->unique_id, + sizeof(camera_info->unique_id), + capture_id) != 0) { + return -1; + } + return capture_id; +} + +JOWW(jint, VideoEngine_connectCaptureDevice)(JNIEnv* jni, jobject j_vie, + jint camera_num, jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->ConnectCaptureDevice(camera_num, channel); +} + +JOWW(jint, VideoEngine_startCapture)(JNIEnv* jni, jobject j_vie, + jint camera_num) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->StartCapture(camera_num); +} + +JOWW(jint, VideoEngine_stopCapture)(JNIEnv* jni, jobject j_vie, + jint camera_id) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->StopCapture(camera_id); +} + +JOWW(jint, VideoEngine_releaseCaptureDevice)(JNIEnv* jni, jobject j_vie, + jint camera_id) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->ReleaseCaptureDevice(camera_id); +} + +JOWW(jint, VideoEngine_getOrientation)(JNIEnv* jni, jobject j_vie, + jobject j_camera) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + CameraDesc* camera_info = GetCameraDesc(jni, j_camera); + webrtc::RotateCapturedFrame orientation; + if (vie_data->capture->GetOrientation(camera_info->unique_id, orientation) != + 0) { + return -1; + } + return static_cast(orientation); +} + +JOWW(jint, VideoEngine_setRotateCapturedFrames)(JNIEnv* jni, jobject j_vie, + jint capture_id, jint degrees) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->capture->SetRotateCapturedFrames( + capture_id, static_cast(degrees)); +} + +JOWW(jint, VideoEngine_setNackStatus)(JNIEnv* jni, jobject j_vie, jint channel, + jboolean enable) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->rtp->SetNACKStatus(channel, enable); +} + +JOWW(jint, VideoEngine_setKeyFrameRequestMethod)(JNIEnv* jni, jobject j_vie, + jint channel, + jint request_method) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->rtp->SetKeyFrameRequestMethod( + channel, static_cast(request_method)); +} + +JOWW(jobject, VideoEngine_getReceivedRtcpStatistics)(JNIEnv* jni, jobject j_vie, + jint channel) { + unsigned short fraction_lost; // NOLINT + unsigned int cumulative_lost; // NOLINT + unsigned int extended_max; // NOLINT + unsigned int jitter; // NOLINT + int rtt_ms; + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + if (vie_data->rtp->GetReceivedRTCPStatistics(channel, fraction_lost, + cumulative_lost, extended_max, + jitter, rtt_ms) != 0) { + return NULL; + } + jclass j_rtcp_statistics_class = + GetClass("org/webrtc/webrtcdemo/RtcpStatistics"); + jmethodID j_rtcp_statistics_ctor = + GetMethodID(jni, j_rtcp_statistics_class, "", "(IIIII)V"); + jobject j_rtcp_statistics = + jni->NewObject(j_rtcp_statistics_class, j_rtcp_statistics_ctor, + fraction_lost, cumulative_lost, extended_max, jitter, + rtt_ms); + CHECK_EXCEPTION(jni, "error during NewObject"); + return j_rtcp_statistics; +} + +JOWW(jint, VideoEngine_registerObserver)(JNIEnv* jni, jobject j_vie, + jint channel, jobject callback) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->RegisterObserver(channel, callback); +} + +JOWW(jint, VideoEngine_deregisterObserver)(JNIEnv* jni, jobject j_vie, + jint channel) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->DeregisterObserver(channel); +} + +JOWW(jint, VideoEngine_setTraceFile)(JNIEnv* jni, jobject, jstring j_filename, + jboolean file_counter) { + std::string filename = JavaToStdString(jni, j_filename); + return webrtc::VideoEngine::SetTraceFile(filename.c_str(), file_counter); +} + +JOWW(jint, VideoEngine_setTraceFilter)(JNIEnv* jni, jobject, jint filter) { + return webrtc::VideoEngine::SetTraceFilter(filter); +} + +JOWW(jint, VideoEngine_startRtpDump)(JNIEnv* jni, jobject j_vie, jint channel, + jstring j_filename, jint direction) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + std::string filename = JavaToStdString(jni, j_filename); + return vie_data->rtp->StartRTPDump( + channel, filename.c_str(), static_cast(direction)); +} + +JOWW(jint, VideoEngine_stopRtpDump)(JNIEnv* jni, jobject j_vie, jint channel, + jint direction) { + VideoEngineData* vie_data = GetVideoEngineData(jni, j_vie); + return vie_data->rtp->StopRTPDump( + channel, static_cast(direction)); +} + +JOWW(void, VideoCodecInst_dispose)(JNIEnv* jni, jobject j_codec) { + delete GetCodecInst(jni, j_codec); +} + +JOWW(jint, VideoCodecInst_plType)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->plType; +} + +JOWW(jstring, VideoCodecInst_name)(JNIEnv* jni, jobject j_codec) { + return jni->NewStringUTF(GetCodecInst(jni, j_codec)->plName); +} + +JOWW(jint, VideoCodecInst_width)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->width; +} + +JOWW(void, VideoCodecInst_setWidth)(JNIEnv* jni, jobject j_codec, jint width) { + GetCodecInst(jni, j_codec)->width = width; +} + +JOWW(jint, VideoCodecInst_height)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->height; +} + +JOWW(void, VideoCodecInst_setHeight)(JNIEnv* jni, jobject j_codec, + jint height) { + GetCodecInst(jni, j_codec)->height = height; +} + +JOWW(jint, VideoCodecInst_startBitRate)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->startBitrate; +} + +JOWW(void, VideoCodecInst_setStartBitRate)(JNIEnv* jni, jobject j_codec, + jint bitrate) { + GetCodecInst(jni, j_codec)->startBitrate = bitrate; +} + +JOWW(jint, VideoCodecInst_maxBitRate)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->maxBitrate; +} + +JOWW(void, VideoCodecInst_setMaxBitRate)(JNIEnv* jni, jobject j_codec, + jint bitrate) { + GetCodecInst(jni, j_codec)->maxBitrate = bitrate; +} + +JOWW(jint, VideoCodecInst_maxFrameRate)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->maxFramerate; +} + +JOWW(void, VideoCodecInst_setMaxFrameRate)(JNIEnv* jni, jobject j_codec, + jint framerate) { + GetCodecInst(jni, j_codec)->maxFramerate = framerate; +} + +JOWW(void, CameraDesc_dispose)(JNIEnv* jni, jobject j_camera) { + delete GetCameraDesc(jni, j_camera); +} diff --git a/webrtc/examples/android/media_demo/jni/video_engine_jni.h b/webrtc/examples/android/media_demo/jni/video_engine_jni.h new file mode 100644 index 000000000..5228f3bdd --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/video_engine_jni.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#ifndef WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VIDEO_ENGINE_H_ +#define WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VIDEO_ENGINE_H_ + +#include + +namespace webrtc_examples { + +void SetVieDeviceObjects(JavaVM* vm); +void ClearVieDeviceObjects(); + +} // namespace webrtc_examples + +#endif // WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VIDEO_ENGINE_H_ diff --git a/webrtc/examples/android/media_demo/jni/voice_engine_jni.cc b/webrtc/examples/android/media_demo/jni/voice_engine_jni.cc new file mode 100644 index 000000000..72df498f3 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/voice_engine_jni.cc @@ -0,0 +1,444 @@ +/* + * 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. + */ + +// This file contains JNI for the voice engine interfaces. +// The native functions are found using jni's auto discovery. + +#include "webrtc/examples/android/media_demo/jni/voice_engine_jni.h" + +#include +#include + +#include "webrtc/examples/android/media_demo/jni/jni_helpers.h" +#include "webrtc/modules/utility/interface/helpers_android.h" +#include "webrtc/test/channel_transport/include/channel_transport.h" +#include "webrtc/voice_engine/include/voe_audio_processing.h" +#include "webrtc/voice_engine/include/voe_base.h" +#include "webrtc/voice_engine/include/voe_codec.h" +#include "webrtc/voice_engine/include/voe_file.h" +#include "webrtc/voice_engine/include/voe_hardware.h" +#include "webrtc/voice_engine/include/voe_network.h" +#include "webrtc/voice_engine/include/voe_rtp_rtcp.h" +#include "webrtc/voice_engine/include/voe_volume_control.h" + +// Macro for native functions that can be found by way of jni-auto discovery. +// Note extern "C" is needed for "discovery" of native methods to work. +#define JOWW(rettype, name) \ + extern "C" rettype JNIEXPORT JNICALL Java_org_webrtc_webrtcdemo_##name + +namespace { + +static JavaVM* g_vm = NULL; +static ClassReferenceHolder* g_class_reference_holder = NULL; + +jclass GetClass(JNIEnv* jni, const char* name) { + CHECK(g_class_reference_holder, "Class reference holder NULL"); + return g_class_reference_holder->GetClass(name); +} + +static const char* g_classes[] = {"org/webrtc/webrtcdemo/CodecInst"}; + +template +void ReleaseSubApi(T instance) { + CHECK(instance->Release() >= 0, "failed to release instance") +} + +class VoiceEngineData { + public: + VoiceEngineData() + : ve(webrtc::VoiceEngine::Create()), + base(webrtc::VoEBase::GetInterface(ve)), + codec(webrtc::VoECodec::GetInterface(ve)), + file(webrtc::VoEFile::GetInterface(ve)), + netw(webrtc::VoENetwork::GetInterface(ve)), + apm(webrtc::VoEAudioProcessing::GetInterface(ve)), + volume(webrtc::VoEVolumeControl::GetInterface(ve)), + hardware(webrtc::VoEHardware::GetInterface(ve)), + rtp(webrtc::VoERTP_RTCP::GetInterface(ve)) { + CHECK(ve != NULL, "Voice engine instance failed to be created"); + CHECK(base != NULL, "Failed to acquire base interface"); + CHECK(codec != NULL, "Failed to acquire codec interface"); + CHECK(file != NULL, "Failed to acquire file interface"); + CHECK(netw != NULL, "Failed to acquire netw interface"); + CHECK(apm != NULL, "Failed to acquire apm interface"); + CHECK(volume != NULL, "Failed to acquire volume interface"); + CHECK(hardware != NULL, "Failed to acquire hardware interface"); + CHECK(rtp != NULL, "Failed to acquire rtp interface"); + } + + ~VoiceEngineData() { + CHECK(channel_transports_.empty(), + "VoE transports must be deleted before terminating"); + CHECK(base->Terminate() == 0, "VoE failed to terminate"); + ReleaseSubApi(base); + ReleaseSubApi(codec); + ReleaseSubApi(file); + ReleaseSubApi(netw); + ReleaseSubApi(apm); + ReleaseSubApi(volume); + ReleaseSubApi(hardware); + ReleaseSubApi(rtp); + webrtc::VoiceEngine* ve_instance = ve; + CHECK(webrtc::VoiceEngine::Delete(ve_instance), "VoE failed to be deleted"); + } + + int CreateChannel() { + int channel = base->CreateChannel(); + if (channel == -1) { + return -1; + } + CreateTransport(channel); + return channel; + } + + int DeleteChannel(int channel) { + if (base->DeleteChannel(channel) != 0) { + return -1; + } + DeleteTransport(channel); + return 0; + } + + webrtc::test::VoiceChannelTransport* GetTransport(int channel) { + ChannelTransports::iterator found = channel_transports_.find(channel); + if (found == channel_transports_.end()) { + return NULL; + } + return found->second; + } + + webrtc::VoiceEngine* const ve; + webrtc::VoEBase* const base; + webrtc::VoECodec* const codec; + webrtc::VoEFile* const file; + webrtc::VoENetwork* const netw; + webrtc::VoEAudioProcessing* const apm; + webrtc::VoEVolumeControl* const volume; + webrtc::VoEHardware* const hardware; + webrtc::VoERTP_RTCP* const rtp; + + private: + // Voice engine no longer provides a socket implementation. There is, + // however, a socket implementation in webrtc::test. + typedef std::map + ChannelTransports; + + void CreateTransport(int channel) { + CHECK(GetTransport(channel) == NULL, + "Transport already created for VoE channel, inconsistent state"); + channel_transports_[channel] = + new webrtc::test::VoiceChannelTransport(netw, channel); + } + void DeleteTransport(int channel) { + CHECK(GetTransport(channel) != NULL, + "VoE channel missing transport, inconsistent state"); + delete channel_transports_[channel]; + channel_transports_.erase(channel); + } + + ChannelTransports channel_transports_; +}; + +webrtc::CodecInst* GetCodecInst(JNIEnv* jni, jobject j_codec) { + jclass j_codec_class = jni->GetObjectClass(j_codec); + jfieldID native_codec_id = + jni->GetFieldID(j_codec_class, "nativeCodecInst", "J"); + jlong j_p = jni->GetLongField(j_codec, native_codec_id); + return reinterpret_cast(j_p); +} + +} // namespace + +namespace webrtc_examples { + +void SetVoeDeviceObjects(JavaVM* vm) { + CHECK(vm, "Trying to register NULL vm"); + g_vm = vm; + webrtc::AttachThreadScoped ats(g_vm); + JNIEnv* jni = ats.env(); + g_class_reference_holder = new ClassReferenceHolder( + jni, g_classes, ARRAYSIZE(g_classes)); +} + +void ClearVoeDeviceObjects() { + CHECK(g_vm, "Clearing vm without it being set"); + { + webrtc::AttachThreadScoped ats(g_vm); + g_class_reference_holder->FreeReferences(ats.env()); + } + g_vm = NULL; + delete g_class_reference_holder; + g_class_reference_holder = NULL; +} + +} // namespace webrtc_examples + +VoiceEngineData* GetVoiceEngineData(JNIEnv* jni, jobject j_voe) { + jclass j_voe_class = jni->GetObjectClass(j_voe); + jfieldID native_voe_id = + jni->GetFieldID(j_voe_class, "nativeVoiceEngine", "J"); + jlong j_p = jni->GetLongField(j_voe, native_voe_id); + return reinterpret_cast(j_p); +} + +webrtc::VoiceEngine* GetVoiceEngine(JNIEnv* jni, jobject j_voe) { + return GetVoiceEngineData(jni, j_voe)->ve; +} + +JOWW(jlong, VoiceEngine_create)(JNIEnv* jni, jclass) { + VoiceEngineData* voe_data = new VoiceEngineData(); + return jlongFromPointer(voe_data); +} + +JOWW(void, VoiceEngine_dispose)(JNIEnv* jni, jobject j_voe) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + delete voe_data; +} + +JOWW(jint, VoiceEngine_init)(JNIEnv* jni, jobject j_voe) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->Init(); +} + +JOWW(jint, VoiceEngine_createChannel)(JNIEnv* jni, jobject j_voe) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->CreateChannel(); +} + +JOWW(jint, VoiceEngine_deleteChannel)(JNIEnv* jni, jobject j_voe, + jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->DeleteChannel(channel); +} + +JOWW(jint, VoiceEngine_setLocalReceiver)(JNIEnv* jni, jobject j_voe, + jint channel, jint port) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + webrtc::test::VoiceChannelTransport* transport = + voe_data->GetTransport(channel); + return transport->SetLocalReceiver(port); +} + +JOWW(jint, VoiceEngine_setSendDestination)(JNIEnv* jni, jobject j_voe, + jint channel, jint port, + jstring j_addr) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + std::string addr = JavaToStdString(jni, j_addr); + webrtc::test::VoiceChannelTransport* transport = + voe_data->GetTransport(channel); + return transport->SetSendDestination(addr.c_str(), port); +} + +JOWW(jint, VoiceEngine_startListen)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StartReceive(channel); +} + +JOWW(jint, VoiceEngine_startPlayout)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StartPlayout(channel); +} + +JOWW(jint, VoiceEngine_startSend)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StartSend(channel); +} + +JOWW(jint, VoiceEngine_stopListen)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StartReceive(channel); +} + +JOWW(jint, VoiceEngine_stopPlayout)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StopPlayout(channel); +} + +JOWW(jint, VoiceEngine_stopSend)(JNIEnv* jni, jobject j_voe, jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->base->StopSend(channel); +} + +JOWW(jint, VoiceEngine_setSpeakerVolume)(JNIEnv* jni, jobject j_voe, + jint level) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->volume->SetSpeakerVolume(level); +} + +JOWW(jint, VoiceEngine_setLoudspeakerStatus)(JNIEnv* jni, jobject j_voe, + jboolean enable) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->hardware->SetLoudspeakerStatus(enable); +} + +JOWW(jint, VoiceEngine_startPlayingFileLocally)(JNIEnv* jni, jobject j_voe, + jint channel, + jstring j_filename, + jboolean loop) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + std::string filename = JavaToStdString(jni, j_filename); + return voe_data->file->StartPlayingFileLocally(channel, + filename.c_str(), + loop); +} + +JOWW(jint, VoiceEngine_stopPlayingFileLocally)(JNIEnv* jni, jobject j_voe, + jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->file->StopPlayingFileLocally(channel); +} + +JOWW(jint, VoiceEngine_startPlayingFileAsMicrophone)(JNIEnv* jni, jobject j_voe, + jint channel, + jstring j_filename, + jboolean loop) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + std::string filename = JavaToStdString(jni, j_filename); + return voe_data->file->StartPlayingFileAsMicrophone(channel, + filename.c_str(), + loop); +} + +JOWW(jint, VoiceEngine_stopPlayingFileAsMicrophone)(JNIEnv* jni, jobject j_voe, + jint channel) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->file->StopPlayingFileAsMicrophone(channel); +} + +JOWW(jint, VoiceEngine_numOfCodecs)(JNIEnv* jni, jobject j_voe) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->codec->NumOfCodecs(); +} + +JOWW(jobject, VoiceEngine_getCodec)(JNIEnv* jni, jobject j_voe, jint index) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + webrtc::CodecInst* codec = new webrtc::CodecInst(); + CHECK(voe_data->codec->GetCodec(index, *codec) == 0, + "getCodec must be called with valid index"); + jclass j_codec_class = GetClass(jni, "org/webrtc/webrtcdemo/CodecInst"); + jmethodID j_codec_ctor = GetMethodID(jni, j_codec_class, "", "(J)V"); + jobject j_codec = + jni->NewObject(j_codec_class, j_codec_ctor, jlongFromPointer(codec)); + CHECK_EXCEPTION(jni, "error during NewObject"); + return j_codec; +} + +JOWW(jint, VoiceEngine_setSendCodec)(JNIEnv* jni, jobject j_voe, jint channel, + jobject j_codec) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + webrtc::CodecInst* inst = GetCodecInst(jni, j_codec); + return voe_data->codec->SetSendCodec(channel, *inst); +} + +JOWW(jint, VoiceEngine_setEcStatus)(JNIEnv* jni, jobject j_voe, jboolean enable, + jint ec_mode) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->apm->SetEcStatus(enable, + static_cast(ec_mode)); +} + +JOWW(jint, VoiceEngine_setAecmMode)(JNIEnv* jni, jobject j_voe, jint aecm_mode, + jboolean cng) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->apm->SetAecmMode(static_cast(aecm_mode), + cng); +} + +JOWW(jint, VoiceEngine_setAgcStatus)(JNIEnv* jni, jobject j_voe, + jboolean enable, jint agc_mode) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->apm->SetAgcStatus(enable, + static_cast(agc_mode)); +} + +// Returns the native AgcConfig object associated with the Java object +// |j_codec|. +void GetNativeAgcConfig(JNIEnv* jni, jobject j_codec, + webrtc::AgcConfig* agc_config) { + jclass j_codec_class = jni->GetObjectClass(j_codec); + jfieldID dBOv_id = jni->GetFieldID(j_codec_class, "targetLevelDbOv", "I"); + agc_config->targetLeveldBOv = jni->GetIntField(j_codec, dBOv_id); + jfieldID gain_id = + jni->GetFieldID(j_codec_class, "digitalCompressionGaindB", "I"); + agc_config->digitalCompressionGaindB = jni->GetIntField(j_codec, gain_id); + jfieldID limiter_id = jni->GetFieldID(j_codec_class, "limiterEnable", "Z"); + agc_config->limiterEnable = jni->GetBooleanField(j_codec, limiter_id); +} + +JOWW(jint, VoiceEngine_setAgcConfig)(JNIEnv* jni, jobject j_voe, + jobject j_config) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + webrtc::AgcConfig config; + GetNativeAgcConfig(jni, j_config, &config); + return voe_data->apm->SetAgcConfig(config); +} + +JOWW(jint, VoiceEngine_setNsStatus)(JNIEnv* jni, jobject j_voe, jboolean enable, + jint ns_mode) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->apm->SetNsStatus(enable, + static_cast(ns_mode)); +} + +JOWW(jint, VoiceEngine_startDebugRecording)(JNIEnv* jni, jobject j_voe, + jstring j_filename) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + std::string filename = JavaToStdString(jni, j_filename); + return voe_data->apm->StartDebugRecording(filename.c_str()); +} + +JOWW(jint, VoiceEngine_stopDebugRecording)(JNIEnv* jni, jobject j_voe) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->apm->StopDebugRecording(); +} + +JOWW(jint, VoiceEngine_startRtpDump)(JNIEnv* jni, jobject j_voe, jint channel, + jstring j_filename, jint direction) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + std::string filename = JavaToStdString(jni, j_filename); + return voe_data->rtp->StartRTPDump( + channel, filename.c_str(), + static_cast(direction)); +} + +JOWW(jint, VoiceEngine_stopRtpDump)(JNIEnv* jni, jobject j_voe, jint channel, + jint direction) { + VoiceEngineData* voe_data = GetVoiceEngineData(jni, j_voe); + return voe_data->rtp->StopRTPDump( + channel, static_cast(direction)); +} + +JOWW(void, CodecInst_dispose)(JNIEnv* jni, jobject j_codec) { + delete GetCodecInst(jni, j_codec); +} + +JOWW(jint, CodecInst_plType)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->pltype; +} + +JOWW(jstring, CodecInst_name)(JNIEnv* jni, jobject j_codec) { + return jni->NewStringUTF(GetCodecInst(jni, j_codec)->plname); +} + +JOWW(jint, CodecInst_plFrequency)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->plfreq; +} + +JOWW(jint, CodecInst_pacSize)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->pacsize; +} + +JOWW(jint, CodecInst_channels)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->channels; +} + +JOWW(jint, CodecInst_rate)(JNIEnv* jni, jobject j_codec) { + return GetCodecInst(jni, j_codec)->rate; +} diff --git a/webrtc/examples/android/media_demo/jni/voice_engine_jni.h b/webrtc/examples/android/media_demo/jni/voice_engine_jni.h new file mode 100644 index 000000000..57ef50765 --- /dev/null +++ b/webrtc/examples/android/media_demo/jni/voice_engine_jni.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#ifndef WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VOICE_ENGINE_H_ +#define WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VOICE_ENGINE_H_ + +#include + +namespace webrtc { + +class VoiceEngine; + +} // namespace webrtc + +namespace webrtc_examples { + +void SetVoeDeviceObjects(JavaVM* vm); +void ClearVoeDeviceObjects(); + +} // namespace webrtc_examples + +webrtc::VoiceEngine* GetVoiceEngine(JNIEnv* jni, jobject j_voe); + +#endif // WEBRTC_EXAMPLES_ANDROID_MEDIA_DEMO_JNI_VOICE_ENGINE_H_ diff --git a/webrtc/video_engine/test/android/project.properties b/webrtc/examples/android/media_demo/project.properties similarity index 100% rename from webrtc/video_engine/test/android/project.properties rename to webrtc/examples/android/media_demo/project.properties diff --git a/webrtc/examples/android/media_demo/res/drawable/logo.png b/webrtc/examples/android/media_demo/res/drawable/logo.png new file mode 100644 index 000000000..1ff07d110 Binary files /dev/null and b/webrtc/examples/android/media_demo/res/drawable/logo.png differ diff --git a/webrtc/examples/android/media_demo/res/layout/audiomenu.xml b/webrtc/examples/android/media_demo/res/layout/audiomenu.xml new file mode 100644 index 000000000..f35547062 --- /dev/null +++ b/webrtc/examples/android/media_demo/res/layout/audiomenu.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webrtc/examples/android/media_demo/res/layout/dropdownitems.xml b/webrtc/examples/android/media_demo/res/layout/dropdownitems.xml new file mode 100644 index 000000000..101461200 --- /dev/null +++ b/webrtc/examples/android/media_demo/res/layout/dropdownitems.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/webrtc/examples/android/media_demo/res/layout/mainmenu.xml b/webrtc/examples/android/media_demo/res/layout/mainmenu.xml new file mode 100644 index 000000000..8a119dc01 --- /dev/null +++ b/webrtc/examples/android/media_demo/res/layout/mainmenu.xml @@ -0,0 +1,39 @@ + + + + + + +