diff options
-rw-r--r-- | app/webrtc/java/jni/peerconnection_jni.cc | 721 | ||||
-rw-r--r-- | app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java | 271 | ||||
-rw-r--r-- | examples/android/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | examples/android/src/org/appspot/apprtc/AppRTCClient.java | 36 | ||||
-rw-r--r-- | examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java | 8 | ||||
-rwxr-xr-x | libjingle.gyp | 4 |
6 files changed, 1000 insertions, 41 deletions
diff --git a/app/webrtc/java/jni/peerconnection_jni.cc b/app/webrtc/java/jni/peerconnection_jni.cc index b68c6ae..54bcfb7 100644 --- a/app/webrtc/java/jni/peerconnection_jni.cc +++ b/app/webrtc/java/jni/peerconnection_jni.cc @@ -66,13 +66,18 @@ #include "talk/app/webrtc/mediaconstraintsinterface.h" #include "talk/app/webrtc/peerconnectioninterface.h" #include "talk/app/webrtc/videosourceinterface.h" +#include "talk/base/bind.h" #include "talk/base/logging.h" +#include "talk/base/messagequeue.h" #include "talk/base/ssladapter.h" #include "talk/media/base/videocapturer.h" #include "talk/media/base/videorenderer.h" #include "talk/media/devices/videorendererfactory.h" #include "talk/media/webrtc/webrtcvideocapturer.h" +#include "talk/media/webrtc/webrtcvideoencoderfactory.h" #include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/libyuv/include/libyuv/convert.h" +#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" #include "webrtc/system_wrappers/interface/compile_assert.h" #include "webrtc/system_wrappers/interface/trace.h" #include "webrtc/video_engine/include/vie_base.h" @@ -84,6 +89,10 @@ using webrtc::LogcatTraceContext; #endif using icu::UnicodeString; +using talk_base::Bind; +using talk_base::Thread; +using talk_base::ThreadManager; +using talk_base::scoped_ptr; using webrtc::AudioSourceInterface; using webrtc::AudioTrackInterface; using webrtc::AudioTrackVector; @@ -108,6 +117,7 @@ using webrtc::VideoRendererInterface; using webrtc::VideoSourceInterface; using webrtc::VideoTrackInterface; using webrtc::VideoTrackVector; +using webrtc::kVideoCodecVP8; // Abort the process if |x| is false, emitting |msg|. #define CHECK(x, msg) \ @@ -223,12 +233,16 @@ class ClassReferenceHolder { LoadClass(jni, "org/webrtc/DataChannel$Init"); LoadClass(jni, "org/webrtc/DataChannel$State"); LoadClass(jni, "org/webrtc/IceCandidate"); +#ifdef ANDROID + LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder"); + LoadClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo"); +#endif LoadClass(jni, "org/webrtc/MediaSource$State"); LoadClass(jni, "org/webrtc/MediaStream"); LoadClass(jni, "org/webrtc/MediaStreamTrack$State"); - LoadClass(jni, "org/webrtc/PeerConnection$SignalingState"); LoadClass(jni, "org/webrtc/PeerConnection$IceConnectionState"); LoadClass(jni, "org/webrtc/PeerConnection$IceGatheringState"); + LoadClass(jni, "org/webrtc/PeerConnection$SignalingState"); LoadClass(jni, "org/webrtc/SessionDescription"); LoadClass(jni, "org/webrtc/SessionDescription$Type"); LoadClass(jni, "org/webrtc/StatsReport"); @@ -302,6 +316,8 @@ jfieldID GetFieldID( return f; } +// Returns a global reference guaranteed to be valid for the lifetime of the +// process. jclass FindClass(JNIEnv* jni, const char* name) { return g_class_reference_holder->GetClass(name); } @@ -396,7 +412,7 @@ class ScopedLocalRefFrame { template<class T> // T is jclass, jobject, jintArray, etc. class ScopedGlobalRef { public: - explicit ScopedGlobalRef(JNIEnv* jni, T obj) + ScopedGlobalRef(JNIEnv* jni, T obj) : obj_(static_cast<T>(jni->NewGlobalRef(obj))) {} ~ScopedGlobalRef() { DeleteGlobalRef(AttachCurrentThreadIfNeeded(), obj_); @@ -408,6 +424,13 @@ class ScopedGlobalRef { T obj_; }; +// Java references to "null" can only be distinguished as such in C++ by +// creating a local reference, so this helper wraps that logic. +static bool IsNull(JNIEnv* jni, jobject obj) { + ScopedLocalRefFrame local_ref_frame(jni); + return jni->NewLocalRef(obj) == NULL; +} + // Return the (singleton) Java Enum object corresponding to |index|; // |state_class_fragment| is something like "MediaSource$State". jobject JavaEnumFromIndex( @@ -681,7 +704,7 @@ class PCOJava : public PeerConnectionObserver { const jmethodID j_data_channel_ctor_; typedef std::map<void*, jweak> NativeToJavaStreamsMap; NativeToJavaStreamsMap streams_; // C++ -> Java streams. - talk_base::scoped_ptr<ConstraintsWrapper> constraints_; + scoped_ptr<ConstraintsWrapper> constraints_; }; // Wrapper for a Java MediaConstraints object. Copies all needed data so when @@ -819,7 +842,7 @@ class SdpObserverWrapper : public T { } private: - talk_base::scoped_ptr<ConstraintsWrapper> constraints_; + scoped_ptr<ConstraintsWrapper> constraints_; const ScopedGlobalRef<jobject> j_observer_global_; const ScopedGlobalRef<jclass> j_observer_class_; }; @@ -1004,7 +1027,7 @@ class VideoRendererWrapper : public VideoRendererInterface { explicit VideoRendererWrapper(cricket::VideoRenderer* renderer) : renderer_(renderer) {} - talk_base::scoped_ptr<cricket::VideoRenderer> renderer_; + scoped_ptr<cricket::VideoRenderer> renderer_; }; // Wrapper dispatching webrtc::VideoRendererInterface to a Java VideoRenderer @@ -1078,8 +1101,604 @@ class JavaVideoRendererWrapper : public VideoRendererInterface { ScopedGlobalRef<jclass> j_byte_buffer_class_; }; -} // anonymous namespace +#ifdef ANDROID +// TODO(fischman): consider pulling MediaCodecVideoEncoder out of this file and +// into its own .h/.cc pair, if/when the JNI helper stuff above is extracted +// from this file. + +// Arbitrary interval to poll the codec for new outputs. +enum { kMediaCodecPollMs = 10 }; + +// MediaCodecVideoEncoder is a webrtc::VideoEncoder implementation that uses +// Android's MediaCodec SDK API behind the scenes to implement (hopefully) +// HW-backed video encode. This C++ class is implemented as a very thin shim, +// delegating all of the interesting work to org.webrtc.MediaCodecVideoEncoder. +// MediaCodecVideoEncoder is created, operated, and destroyed on a single +// thread, currently the libjingle Worker thread. +class MediaCodecVideoEncoder : public webrtc::VideoEncoder, + public talk_base::MessageHandler { + public: + virtual ~MediaCodecVideoEncoder(); + explicit MediaCodecVideoEncoder(JNIEnv* jni); + + // webrtc::VideoEncoder implementation. Everything trampolines to + // |codec_thread_| for execution. + virtual int32_t InitEncode(const webrtc::VideoCodec* codec_settings, + int32_t /* number_of_cores */, + uint32_t /* max_payload_size */) OVERRIDE; + virtual int32_t Encode( + const webrtc::I420VideoFrame& input_image, + const webrtc::CodecSpecificInfo* /* codec_specific_info */, + const std::vector<webrtc::VideoFrameType>* frame_types) OVERRIDE; + virtual int32_t RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback) OVERRIDE; + virtual int32_t Release() OVERRIDE; + virtual int32_t SetChannelParameters(uint32_t /* packet_loss */, + int /* rtt */) OVERRIDE; + virtual int32_t SetRates(uint32_t new_bit_rate, uint32_t frame_rate) OVERRIDE; + + // talk_base::MessageHandler implementation. + virtual void OnMessage(talk_base::Message* msg) OVERRIDE; + + private: + // CHECK-fail if not running on |codec_thread_|. + void CheckOnCodecThread(); + + // Release() and InitEncode() in an attempt to restore the codec to an + // operable state. Necessary after all manner of OMX-layer errors. + void ResetCodec(); + + // Implementation of webrtc::VideoEncoder methods above, all running on the + // codec thread exclusively. + // + // If width==0 then this is assumed to be a re-initialization and the + // previously-current values are reused instead of the passed parameters + // (makes it easier to reason about thread-safety). + int32_t InitEncodeOnCodecThread(int width, int height, int kbps); + int32_t EncodeOnCodecThread( + const webrtc::I420VideoFrame& input_image, + const std::vector<webrtc::VideoFrameType>* frame_types); + int32_t RegisterEncodeCompleteCallbackOnCodecThread( + webrtc::EncodedImageCallback* callback); + int32_t ReleaseOnCodecThread(); + int32_t SetRatesOnCodecThread(uint32_t new_bit_rate, uint32_t frame_rate); + + // Reset parameters valid between InitEncode() & Release() (see below). + void ResetParameters(JNIEnv* jni); + + // Helper accessors for MediaCodecVideoEncoder$OutputBufferInfo members. + int GetOutputBufferInfoIndex(JNIEnv* jni, jobject j_output_buffer_info); + jobject GetOutputBufferInfoBuffer(JNIEnv* jni, jobject j_output_buffer_info); + bool GetOutputBufferInfoIsKeyFrame(JNIEnv* jni, jobject j_output_buffer_info); + jlong GetOutputBufferInfoPresentationTimestampUs( + JNIEnv* jni, + jobject j_output_buffer_info); + + // Deliver any outputs pending in the MediaCodec to our |callback_| and return + // true on success. + bool DeliverPendingOutputs(JNIEnv* jni); + + // Valid all the time since RegisterEncodeCompleteCallback() Invoke()s to + // |codec_thread_| synchronously. + webrtc::EncodedImageCallback* callback_; + + // State that is constant for the lifetime of this object once the ctor + // returns. + scoped_ptr<Thread> codec_thread_; // Thread on which to operate MediaCodec. + ScopedGlobalRef<jclass> j_media_codec_video_encoder_class_; + ScopedGlobalRef<jobject> j_media_codec_video_encoder_; + jmethodID j_init_encode_method_; + jmethodID j_dequeue_input_buffer_method_; + jmethodID j_encode_method_; + jmethodID j_release_method_; + jmethodID j_set_rates_method_; + jmethodID j_dequeue_output_buffer_method_; + jmethodID j_release_output_buffer_method_; + jfieldID j_info_index_field_; + jfieldID j_info_buffer_field_; + jfieldID j_info_is_key_frame_field_; + jfieldID j_info_presentation_timestamp_us_field_; + + // State that is valid only between InitEncode() and the next Release(). + // Touched only on codec_thread_ so no explicit synchronization necessary. + int width_; // Frame width in pixels. + int height_; // Frame height in pixels. + int last_set_bitrate_kbps_; // Last-requested bitrate in kbps. + // Frame size in bytes fed to MediaCodec (stride==width, sliceHeight==height). + int nv12_size_; + // True only when between a callback_->Encoded() call return a positive value + // and the next Encode() call being ignored. + bool drop_next_input_frame_; + // Global references; must be deleted in Release(). + std::vector<jobject> input_buffers_; +}; + +enum { MSG_SET_RATES, MSG_POLL_FOR_READY_OUTPUTS, }; + +MediaCodecVideoEncoder::~MediaCodecVideoEncoder() { + // We depend on ResetParameters() to ensure no more callbacks to us after we + // are deleted, so assert it here. + CHECK(width_ == 0, "Release() should have been called"); +} + +MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni) + : callback_(NULL), + codec_thread_(new Thread()), + j_media_codec_video_encoder_class_( + jni, + FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")), + j_media_codec_video_encoder_( + jni, + jni->NewObject(*j_media_codec_video_encoder_class_, + GetMethodID(jni, + *j_media_codec_video_encoder_class_, + "<init>", + "()V"))) { + ScopedLocalRefFrame local_ref_frame(jni); + // It would be nice to avoid spinning up a new thread per MediaCodec, and + // instead re-use e.g. the PeerConnectionFactory's |worker_thread_|, but bug + // 2732 means that deadlocks abound. This class synchronously trampolines + // to |codec_thread_|, so if anything else can be coming to _us_ from + // |codec_thread_|, or from any thread holding the |_sendCritSect| described + // in the bug, we have a problem. For now work around that with a dedicated + // thread. + codec_thread_->SetName("MediaCodecVideoEncoder", NULL); + CHECK(codec_thread_->Start(), "Failed to start MediaCodecVideoEncoder"); + + ResetParameters(jni); + + jclass j_output_buffer_info_class = + FindClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo"); + j_init_encode_method_ = GetMethodID(jni, + *j_media_codec_video_encoder_class_, + "initEncode", + "(III)[Ljava/nio/ByteBuffer;"); + j_dequeue_input_buffer_method_ = GetMethodID( + jni, *j_media_codec_video_encoder_class_, "dequeueInputBuffer", "()I"); + j_encode_method_ = GetMethodID( + jni, *j_media_codec_video_encoder_class_, "encode", "(ZIIJ)Z"); + j_release_method_ = + GetMethodID(jni, *j_media_codec_video_encoder_class_, "release", "()V"); + j_set_rates_method_ = GetMethodID( + jni, *j_media_codec_video_encoder_class_, "setRates", "(II)Z"); + j_dequeue_output_buffer_method_ = + GetMethodID(jni, + *j_media_codec_video_encoder_class_, + "dequeueOutputBuffer", + "()Lorg/webrtc/MediaCodecVideoEncoder$OutputBufferInfo;"); + j_release_output_buffer_method_ = GetMethodID( + jni, *j_media_codec_video_encoder_class_, "releaseOutputBuffer", "(I)Z"); + + j_info_index_field_ = + GetFieldID(jni, j_output_buffer_info_class, "index", "I"); + j_info_buffer_field_ = GetFieldID( + jni, j_output_buffer_info_class, "buffer", "Ljava/nio/ByteBuffer;"); + j_info_is_key_frame_field_ = + GetFieldID(jni, j_output_buffer_info_class, "isKeyFrame", "Z"); + j_info_presentation_timestamp_us_field_ = GetFieldID( + jni, j_output_buffer_info_class, "presentationTimestampUs", "J"); + CHECK_EXCEPTION(jni, "MediaCodecVideoEncoder ctor failed"); +} + +int32_t MediaCodecVideoEncoder::InitEncode( + const webrtc::VideoCodec* codec_settings, + int32_t /* number_of_cores */, + uint32_t /* max_payload_size */) { + // Factory should guard against other codecs being used with us. + CHECK(codec_settings->codecType == kVideoCodecVP8, "Unsupported codec"); + + return codec_thread_->Invoke<int32_t>( + Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread, + this, + codec_settings->width, + codec_settings->height, + codec_settings->startBitrate)); +} + +int32_t MediaCodecVideoEncoder::Encode( + const webrtc::I420VideoFrame& frame, + const webrtc::CodecSpecificInfo* /* codec_specific_info */, + const std::vector<webrtc::VideoFrameType>* frame_types) { + return codec_thread_->Invoke<int32_t>(Bind( + &MediaCodecVideoEncoder::EncodeOnCodecThread, this, frame, frame_types)); +} + +int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallback( + webrtc::EncodedImageCallback* callback) { + return codec_thread_->Invoke<int32_t>( + Bind(&MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread, + this, + callback)); +} + +int32_t MediaCodecVideoEncoder::Release() { + return codec_thread_->Invoke<int32_t>( + Bind(&MediaCodecVideoEncoder::ReleaseOnCodecThread, this)); +} + +int32_t MediaCodecVideoEncoder::SetChannelParameters(uint32_t /* packet_loss */, + int /* rtt */) { + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoEncoder::SetRates(uint32_t new_bit_rate, + uint32_t frame_rate) { + return codec_thread_->Invoke<int32_t>( + Bind(&MediaCodecVideoEncoder::SetRatesOnCodecThread, + this, + new_bit_rate, + frame_rate)); +} + +void MediaCodecVideoEncoder::OnMessage(talk_base::Message* msg) { + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + + // We only ever send one message to |this| directly (not through a Bind()'d + // functor), so expect no ID/data. + CHECK(!msg->message_id, "Unexpected message!"); + CHECK(!msg->pdata, "Unexpected message!"); + CheckOnCodecThread(); + + // It would be nice to recover from a failure here if one happened, but it's + // unclear how to signal such a failure to the app, so instead we stay silent + // about it and let the next app-called API method reveal the borkedness. + DeliverPendingOutputs(jni); + codec_thread_->PostDelayed(kMediaCodecPollMs, this); +} + +void MediaCodecVideoEncoder::CheckOnCodecThread() { + CHECK(codec_thread_ == ThreadManager::Instance()->CurrentThread(), + "Running on wrong thread!"); +} + +void MediaCodecVideoEncoder::ResetCodec() { + if (Release() != WEBRTC_VIDEO_CODEC_OK || + codec_thread_->Invoke<int32_t>(Bind( + &MediaCodecVideoEncoder::InitEncodeOnCodecThread, this, 0, 0, 0)) != + WEBRTC_VIDEO_CODEC_OK) { + // TODO(fischman): wouldn't it be nice if there was a way to gracefully + // degrade to a SW encoder at this point? There isn't one AFAICT :( + // https://code.google.com/p/webrtc/issues/detail?id=2920 + } +} + +int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( + int width, int height, int kbps) { + CheckOnCodecThread(); + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + + if (width == 0) { + width = width_; + height = height_; + kbps = last_set_bitrate_kbps_; + } + + width_ = width; + height_ = height; + last_set_bitrate_kbps_ = kbps; + nv12_size_ = width_ * height_ * 3 / 2; + // We enforce no extra stride/padding in the format creation step. + jobjectArray input_buffers = reinterpret_cast<jobjectArray>( + jni->CallObjectMethod(*j_media_codec_video_encoder_, + j_init_encode_method_, + width_, + height_, + kbps)); + CHECK_EXCEPTION(jni, ""); + if (IsNull(jni, input_buffers)) + return WEBRTC_VIDEO_CODEC_ERROR; + + size_t num_input_buffers = jni->GetArrayLength(input_buffers); + CHECK(input_buffers_.empty(), "Unexpected double InitEncode without Release"); + input_buffers_.resize(num_input_buffers); + for (size_t i = 0; i < num_input_buffers; ++i) { + input_buffers_[i] = + jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i)); + int64 nv12_buffer_capacity = + jni->GetDirectBufferCapacity(input_buffers_[i]); + CHECK_EXCEPTION(jni, ""); + CHECK(nv12_buffer_capacity >= nv12_size_, "Insufficient capacity"); + } + CHECK_EXCEPTION(jni, ""); + + codec_thread_->PostDelayed(kMediaCodecPollMs, this); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( + const webrtc::I420VideoFrame& frame, + const std::vector<webrtc::VideoFrameType>* frame_types) { + CheckOnCodecThread(); + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + + if (!DeliverPendingOutputs(jni)) { + ResetCodec(); + // Continue as if everything's fine. + } + + if (drop_next_input_frame_) { + drop_next_input_frame_ = false; + return WEBRTC_VIDEO_CODEC_OK; + } + + CHECK(frame_types->size() == 1, "Unexpected stream count"); + bool key_frame = frame_types->front() != webrtc::kDeltaFrame; + + CHECK(frame.width() == width_, "Unexpected resolution change"); + CHECK(frame.height() == height_, "Unexpected resolution change"); + + int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_, + j_dequeue_input_buffer_method_); + CHECK_EXCEPTION(jni, ""); + if (j_input_buffer_index == -1) + return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887. + if (j_input_buffer_index == -2) { + ResetCodec(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + jobject j_input_buffer = input_buffers_[j_input_buffer_index]; + uint8* nv12_buffer = + reinterpret_cast<uint8*>(jni->GetDirectBufferAddress(j_input_buffer)); + CHECK_EXCEPTION(jni, ""); + CHECK(nv12_buffer, "Indirect buffer??"); + CHECK(!libyuv::I420ToNV12( + frame.buffer(webrtc::kYPlane), + frame.stride(webrtc::kYPlane), + frame.buffer(webrtc::kUPlane), + frame.stride(webrtc::kUPlane), + frame.buffer(webrtc::kVPlane), + frame.stride(webrtc::kVPlane), + nv12_buffer, + frame.width(), + nv12_buffer + frame.stride(webrtc::kYPlane) * frame.height(), + frame.width(), + frame.width(), + frame.height()), + "I420ToNV12 failed"); + jlong timestamp_us = frame.render_time_ms() * 1000; + int64_t start = talk_base::Time(); + bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_, + j_encode_method_, + key_frame, + j_input_buffer_index, + nv12_size_, + timestamp_us); + CHECK_EXCEPTION(jni, ""); + if (!encode_status || !DeliverPendingOutputs(jni)) { + ResetCodec(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoEncoder::RegisterEncodeCompleteCallbackOnCodecThread( + webrtc::EncodedImageCallback* callback) { + CheckOnCodecThread(); + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { + CheckOnCodecThread(); + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + for (size_t i = 0; i < input_buffers_.size(); ++i) + jni->DeleteGlobalRef(input_buffers_[i]); + input_buffers_.clear(); + jni->CallVoidMethod(*j_media_codec_video_encoder_, j_release_method_); + ResetParameters(jni); + CHECK_EXCEPTION(jni, ""); + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, + uint32_t frame_rate) { + CheckOnCodecThread(); + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + last_set_bitrate_kbps_ = new_bit_rate; + bool ret = jni->CallBooleanMethod(*j_media_codec_video_encoder_, + j_set_rates_method_, + new_bit_rate, + frame_rate); + CHECK_EXCEPTION(jni, ""); + if (!ret) { + ResetCodec(); + return WEBRTC_VIDEO_CODEC_ERROR; + } + return WEBRTC_VIDEO_CODEC_OK; +} + +void MediaCodecVideoEncoder::ResetParameters(JNIEnv* jni) { + talk_base::MessageQueueManager::Clear(this); + width_ = 0; + height_ = 0; + nv12_size_ = 0; + drop_next_input_frame_ = false; + CHECK(input_buffers_.empty(), + "ResetParameters called while holding input_buffers_!"); +} + +int MediaCodecVideoEncoder::GetOutputBufferInfoIndex( + JNIEnv* jni, + jobject j_output_buffer_info) { + return GetIntField(jni, j_output_buffer_info, j_info_index_field_); +} + +jobject MediaCodecVideoEncoder::GetOutputBufferInfoBuffer( + JNIEnv* jni, + jobject j_output_buffer_info) { + return GetObjectField(jni, j_output_buffer_info, j_info_buffer_field_); +} + +bool MediaCodecVideoEncoder::GetOutputBufferInfoIsKeyFrame( + JNIEnv* jni, + jobject j_output_buffer_info) { + return GetBooleanField(jni, j_output_buffer_info, j_info_is_key_frame_field_); +} + +jlong MediaCodecVideoEncoder::GetOutputBufferInfoPresentationTimestampUs( + JNIEnv* jni, + jobject j_output_buffer_info) { + return GetLongField( + jni, j_output_buffer_info, j_info_presentation_timestamp_us_field_); +} + +bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { + while (true) { + jobject j_output_buffer_info = jni->CallObjectMethod( + *j_media_codec_video_encoder_, j_dequeue_output_buffer_method_); + CHECK_EXCEPTION(jni, ""); + if (IsNull(jni, j_output_buffer_info)) + break; + + int output_buffer_index = + GetOutputBufferInfoIndex(jni, j_output_buffer_info); + if (output_buffer_index == -1) { + ResetCodec(); + return false; + } + + jlong capture_time_ms = + GetOutputBufferInfoPresentationTimestampUs(jni, j_output_buffer_info) / + 1000; + + int32_t callback_status = 0; + if (callback_) { + jobject j_output_buffer = + GetOutputBufferInfoBuffer(jni, j_output_buffer_info); + bool key_frame = GetOutputBufferInfoIsKeyFrame(jni, j_output_buffer_info); + size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer); + uint8* payload = reinterpret_cast<uint8_t*>( + jni->GetDirectBufferAddress(j_output_buffer)); + CHECK_EXCEPTION(jni, ""); + scoped_ptr<webrtc::EncodedImage> image( + new webrtc::EncodedImage(payload, payload_size, payload_size)); + image->_encodedWidth = width_; + image->_encodedHeight = height_; + // Convert capture time to 90 kHz RTP timestamp. + image->_timeStamp = static_cast<uint32_t>(90 * capture_time_ms); + image->capture_time_ms_ = capture_time_ms; + image->_frameType = (key_frame ? webrtc::kKeyFrame : webrtc::kDeltaFrame); + image->_completeFrame = true; + + webrtc::CodecSpecificInfo info; + memset(&info, 0, sizeof(info)); + info.codecType = kVideoCodecVP8; + info.codecSpecific.VP8.pictureId = webrtc::kNoPictureId; + info.codecSpecific.VP8.tl0PicIdx = webrtc::kNoTl0PicIdx; + info.codecSpecific.VP8.keyIdx = webrtc::kNoKeyIdx; + + // Generate a header describing a single fragment. + webrtc::RTPFragmentationHeader header; + memset(&header, 0, sizeof(header)); + header.VerifyAndAllocateFragmentationHeader(1); + header.fragmentationOffset[0] = 0; + header.fragmentationLength[0] = image->_length; + header.fragmentationPlType[0] = 0; + header.fragmentationTimeDiff[0] = 0; + + callback_status = callback_->Encoded(*image, &info, &header); + } + + bool success = jni->CallBooleanMethod(*j_media_codec_video_encoder_, + j_release_output_buffer_method_, + output_buffer_index); + CHECK_EXCEPTION(jni, ""); + if (!success) { + ResetCodec(); + return false; + } + + if (callback_status > 0) + drop_next_input_frame_ = true; + // Theoretically could handle callback_status<0 here, but unclear what that + // would mean for us. + } + + return true; +} + +// Simplest-possible implementation of an encoder factory, churns out +// MediaCodecVideoEncoders on demand (or errors, if that's not possible). +class MediaCodecVideoEncoderFactory + : public cricket::WebRtcVideoEncoderFactory { + public: + MediaCodecVideoEncoderFactory(); + virtual ~MediaCodecVideoEncoderFactory(); + + // WebRtcVideoEncoderFactory implementation. + virtual webrtc::VideoEncoder* CreateVideoEncoder(webrtc::VideoCodecType type) + OVERRIDE; + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual const std::vector<VideoCodec>& codecs() const OVERRIDE; + virtual void DestroyVideoEncoder(webrtc::VideoEncoder* encoder) OVERRIDE; + + private: + // Empty if platform support is lacking, const after ctor returns. + std::vector<VideoCodec> supported_codecs_; +}; + +MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() { + JNIEnv* jni = AttachCurrentThreadIfNeeded(); + ScopedLocalRefFrame local_ref_frame(jni); + jclass j_encoder_class = FindClass(jni, "org/webrtc/MediaCodecVideoEncoder"); + bool is_platform_supported = jni->CallStaticBooleanMethod( + j_encoder_class, + GetStaticMethodID(jni, j_encoder_class, "isPlatformSupported", "()Z")); + CHECK_EXCEPTION(jni, ""); + if (!is_platform_supported) + return; + + if (true) { + // TODO(fischman): re-enable once + // https://code.google.com/p/webrtc/issues/detail?id=2899 is fixed. Until + // then the Android MediaCodec experience is too abysmal to turn on. + return; + } + + // Wouldn't it be nice if MediaCodec exposed the maximum capabilities of the + // encoder? Sure would be. Too bad it doesn't. So we hard-code some + // reasonable defaults. + supported_codecs_.push_back( + VideoCodec(kVideoCodecVP8, "VP8", 1920, 1088, 30)); +} +MediaCodecVideoEncoderFactory::~MediaCodecVideoEncoderFactory() {} + +webrtc::VideoEncoder* MediaCodecVideoEncoderFactory::CreateVideoEncoder( + webrtc::VideoCodecType type) { + if (type != kVideoCodecVP8 || supported_codecs_.empty()) + return NULL; + return new MediaCodecVideoEncoder(AttachCurrentThreadIfNeeded()); +} + +// Since the available codec list is never going to change, we ignore the +// Observer-related interface here. +void MediaCodecVideoEncoderFactory::AddObserver(Observer* observer) {} +void MediaCodecVideoEncoderFactory::RemoveObserver(Observer* observer) {} + +const std::vector<MediaCodecVideoEncoderFactory::VideoCodec>& +MediaCodecVideoEncoderFactory::codecs() const { + return supported_codecs_; +} + +void MediaCodecVideoEncoderFactory::DestroyVideoEncoder( + webrtc::VideoEncoder* encoder) { + delete encoder; +} + +#endif // ANDROID + +} // anonymous namespace // Convenience macro defining JNI-accessible methods in the org.webrtc package. // Eliminates unnecessary boilerplate and line-wraps, reducing visual clutter. @@ -1102,6 +1721,7 @@ extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { } 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(talk_base::CleanupSSL(), "Failed to CleanupSSL()"); @@ -1116,7 +1736,7 @@ static DataChannelInterface* ExtractNativeDC(JNIEnv* jni, jobject j_dc) { JOW(jlong, DataChannel_registerObserverNative)( JNIEnv* jni, jobject j_dc, jobject j_observer) { - talk_base::scoped_ptr<DataChannelObserverWrapper> observer( + scoped_ptr<DataChannelObserverWrapper> observer( new DataChannelObserverWrapper(jni, j_observer)); ExtractNativeDC(jni, j_dc)->RegisterObserver(observer.get()); return jlongFromPointer(observer.release()); @@ -1258,23 +1878,68 @@ JOW(jboolean, PeerConnectionFactory_initializeAndroidGlobals)( } #endif // ANDROID +// Helper struct for working around the fact that CreatePeerConnectionFactory() +// comes in two flavors: either entirely automagical (constructing its own +// threads and deleting them on teardown, but no external codec factory support) +// or entirely manual (requires caller to delete threads after factory +// teardown). This struct takes ownership of its ctor's arguments to present a +// single thing for Java to hold and eventually free. +class OwnedFactoryAndThreads { + public: + OwnedFactoryAndThreads(Thread* worker_thread, + Thread* signaling_thread, + PeerConnectionFactoryInterface* factory) + : worker_thread_(worker_thread), + signaling_thread_(signaling_thread), + factory_(factory) {} + + ~OwnedFactoryAndThreads() { CHECK_RELEASE(factory_); } + + PeerConnectionFactoryInterface* factory() { return factory_; } + + private: + const scoped_ptr<Thread> worker_thread_; + const scoped_ptr<Thread> signaling_thread_; + PeerConnectionFactoryInterface* factory_; // Const after ctor except dtor. +}; + JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnectionFactory)( JNIEnv* jni, jclass) { webrtc::Trace::CreateTrace(); + Thread* worker_thread = new Thread(); + worker_thread->SetName("worker_thread", NULL); + Thread* signaling_thread = new Thread(); + signaling_thread->SetName("signaling_thread", NULL); + CHECK(worker_thread->Start() && signaling_thread->Start(), + "Failed to start threads"); + scoped_ptr<cricket::WebRtcVideoEncoderFactory> encoder_factory; +#ifdef ANDROID + encoder_factory.reset(new MediaCodecVideoEncoderFactory()); +#endif talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory( - webrtc::CreatePeerConnectionFactory()); - return (jlong)factory.release(); + webrtc::CreatePeerConnectionFactory(worker_thread, + signaling_thread, + NULL, + encoder_factory.release(), + NULL)); + OwnedFactoryAndThreads* owned_factory = new OwnedFactoryAndThreads( + worker_thread, signaling_thread, factory.release()); + return jlongFromPointer(owned_factory); } JOW(void, PeerConnectionFactory_freeFactory)(JNIEnv*, jclass, jlong j_p) { - CHECK_RELEASE(reinterpret_cast<PeerConnectionFactoryInterface*>(j_p)); + delete reinterpret_cast<OwnedFactoryAndThreads*>(j_p); webrtc::Trace::ReturnTrace(); } +static PeerConnectionFactoryInterface* factoryFromJava(jlong j_p) { + return reinterpret_cast<OwnedFactoryAndThreads*>(j_p)->factory(); +} + JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)( JNIEnv* jni, jclass, jlong native_factory, jstring label) { talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory( - reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory)); + factoryFromJava(native_factory)); talk_base::scoped_refptr<MediaStreamInterface> stream( factory->CreateLocalMediaStream(JavaToStdString(jni, label))); return (jlong)stream.release(); @@ -1283,10 +1948,10 @@ JOW(jlong, PeerConnectionFactory_nativeCreateLocalMediaStream)( JOW(jlong, PeerConnectionFactory_nativeCreateVideoSource)( JNIEnv* jni, jclass, jlong native_factory, jlong native_capturer, jobject j_constraints) { - talk_base::scoped_ptr<ConstraintsWrapper> constraints( + scoped_ptr<ConstraintsWrapper> constraints( new ConstraintsWrapper(jni, j_constraints)); talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory( - reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory)); + factoryFromJava(native_factory)); talk_base::scoped_refptr<VideoSourceInterface> source( factory->CreateVideoSource( reinterpret_cast<cricket::VideoCapturer*>(native_capturer), @@ -1298,7 +1963,7 @@ JOW(jlong, PeerConnectionFactory_nativeCreateVideoTrack)( JNIEnv* jni, jclass, jlong native_factory, jstring id, jlong native_source) { talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory( - reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory)); + factoryFromJava(native_factory)); talk_base::scoped_refptr<VideoTrackInterface> track( factory->CreateVideoTrack( JavaToStdString(jni, id), @@ -1309,7 +1974,7 @@ JOW(jlong, PeerConnectionFactory_nativeCreateVideoTrack)( JOW(jlong, PeerConnectionFactory_nativeCreateAudioTrack)( JNIEnv* jni, jclass, jlong native_factory, jstring id) { talk_base::scoped_refptr<PeerConnectionFactoryInterface> factory( - reinterpret_cast<PeerConnectionFactoryInterface*>(native_factory)); + factoryFromJava(native_factory)); talk_base::scoped_refptr<AudioTrackInterface> track( factory->CreateAudioTrack(JavaToStdString(jni, id), NULL)); return (jlong)track.release(); @@ -1357,7 +2022,8 @@ JOW(jlong, PeerConnectionFactory_nativeCreatePeerConnection)( JNIEnv *jni, jclass, jlong factory, jobject j_ice_servers, jobject j_constraints, jlong observer_p) { talk_base::scoped_refptr<PeerConnectionFactoryInterface> f( - reinterpret_cast<PeerConnectionFactoryInterface*>(factory)); + reinterpret_cast<PeerConnectionFactoryInterface*>( + factoryFromJava(factory))); PeerConnectionInterface::IceServers servers; JavaIceServersToJsepIceServers(jni, j_ice_servers, &servers); PCOJava* observer = reinterpret_cast<PCOJava*>(observer_p); @@ -1479,7 +2145,7 @@ JOW(jboolean, PeerConnection_updateIce)( JNIEnv* jni, jobject j_pc, jobject j_ice_servers, jobject j_constraints) { PeerConnectionInterface::IceServers ice_servers; JavaIceServersToJsepIceServers(jni, j_ice_servers, &ice_servers); - talk_base::scoped_ptr<ConstraintsWrapper> constraints( + scoped_ptr<ConstraintsWrapper> constraints( new ConstraintsWrapper(jni, j_constraints)); return ExtractNativePC(jni, j_pc)->UpdateIce(ice_servers, constraints.get()); } @@ -1489,14 +2155,14 @@ JOW(jboolean, PeerConnection_nativeAddIceCandidate)( jint j_sdp_mline_index, jstring j_candidate_sdp) { std::string sdp_mid = JavaToStdString(jni, j_sdp_mid); std::string sdp = JavaToStdString(jni, j_candidate_sdp); - talk_base::scoped_ptr<IceCandidateInterface> candidate( + scoped_ptr<IceCandidateInterface> candidate( webrtc::CreateIceCandidate(sdp_mid, j_sdp_mline_index, sdp, NULL)); return ExtractNativePC(jni, j_pc)->AddIceCandidate(candidate.get()); } JOW(jboolean, PeerConnection_nativeAddLocalStream)( JNIEnv* jni, jobject j_pc, jlong native_stream, jobject j_constraints) { - talk_base::scoped_ptr<ConstraintsWrapper> constraints( + scoped_ptr<ConstraintsWrapper> constraints( new ConstraintsWrapper(jni, j_constraints)); return ExtractNativePC(jni, j_pc)->AddStream( reinterpret_cast<MediaStreamInterface*>(native_stream), @@ -1549,7 +2215,7 @@ JOW(jobject, MediaSource_nativeState)(JNIEnv* jni, jclass, jlong j_p) { JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)( JNIEnv* jni, jclass, jstring j_device_name) { std::string device_name = JavaToStdString(jni, j_device_name); - talk_base::scoped_ptr<cricket::DeviceManagerInterface> device_manager( + scoped_ptr<cricket::DeviceManagerInterface> device_manager( cricket::DeviceManagerFactory::Create()); CHECK(device_manager->Init(), "DeviceManager::Init() failed"); cricket::Device device; @@ -1557,22 +2223,21 @@ JOW(jlong, VideoCapturer_nativeCreateVideoCapturer)( LOG(LS_ERROR) << "GetVideoCaptureDevice failed for " << device_name; return 0; } - talk_base::scoped_ptr<cricket::VideoCapturer> capturer( + scoped_ptr<cricket::VideoCapturer> capturer( device_manager->CreateVideoCapturer(device)); return (jlong)capturer.release(); } JOW(jlong, VideoRenderer_nativeCreateGuiVideoRenderer)( JNIEnv* jni, jclass, int x, int y) { - talk_base::scoped_ptr<VideoRendererWrapper> renderer( - VideoRendererWrapper::Create( - cricket::VideoRendererFactory::CreateGuiVideoRenderer(x, y))); + scoped_ptr<VideoRendererWrapper> renderer(VideoRendererWrapper::Create( + cricket::VideoRendererFactory::CreateGuiVideoRenderer(x, y))); return (jlong)renderer.release(); } JOW(jlong, VideoRenderer_nativeWrapVideoRenderer)( JNIEnv* jni, jclass, jobject j_callbacks) { - talk_base::scoped_ptr<JavaVideoRendererWrapper> renderer( + scoped_ptr<JavaVideoRendererWrapper> renderer( new JavaVideoRendererWrapper(jni, j_callbacks)); return (jlong)renderer.release(); } @@ -1580,7 +2245,7 @@ JOW(jlong, VideoRenderer_nativeWrapVideoRenderer)( JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) { cricket::VideoCapturer* capturer = reinterpret_cast<VideoSourceInterface*>(j_p)->GetVideoCapturer(); - talk_base::scoped_ptr<cricket::VideoFormatPod> format( + scoped_ptr<cricket::VideoFormatPod> format( new cricket::VideoFormatPod(*capturer->GetCaptureFormat())); capturer->Stop(); return jlongFromPointer(format.release()); @@ -1588,7 +2253,9 @@ JOW(jlong, VideoSource_stop)(JNIEnv* jni, jclass, jlong j_p) { JOW(void, VideoSource_restart)( JNIEnv* jni, jclass, jlong j_p_source, jlong j_p_format) { - talk_base::scoped_ptr<cricket::VideoFormatPod> format( + CHECK(j_p_source, ""); + CHECK(j_p_format, ""); + scoped_ptr<cricket::VideoFormatPod> format( reinterpret_cast<cricket::VideoFormatPod*>(j_p_format)); reinterpret_cast<VideoSourceInterface*>(j_p_source)->GetVideoCapturer()-> StartCapturing(cricket::VideoFormat(*format)); diff --git a/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java b/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java new file mode 100644 index 0000000..dff334b --- /dev/null +++ b/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java @@ -0,0 +1,271 @@ +/* + * libjingle + * Copyright 2013, 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.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteBuffer; + +// Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. +// This class is an implementation detail of the Java PeerConnection API. +// MediaCodec is thread-hostile so this class must be operated on a single +// thread. +class MediaCodecVideoEncoder { + // This class is constructed, operated, and destroyed by its C++ incarnation, + // so the class and its methods have non-public visibility. The API this + // class exposes aims to mimic the webrtc::VideoEncoder API as closely as + // possibly to minimize the amount of translation work necessary. + + private static final String TAG = "MediaCodecVideoEncoder"; + + private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. + private MediaCodec mediaCodec; + private ByteBuffer[] outputBuffers; + private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; + private Thread mediaCodecThread; + + private MediaCodecVideoEncoder() {} + + private static boolean isPlatformSupported() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + return false; // MediaCodec.setParameters is missing. + + if (!Build.MODEL.equals("Nexus 5")) { + // TODO(fischman): Currently the N5 is the only >=KK device containing a + // HW VP8 encoder, so don't bother with any others. When this list grows, + // update the KEY_COLOR_FORMAT logic below. + return false; + } + + for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { + MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); + if (!info.isEncoder()) + continue; + String name = null; + for (String mimeType : info.getSupportedTypes()) { + if (mimeType.equals(VP8_MIME_TYPE)) { + name = info.getName(); + break; + } + } + if (name == null) + continue; // No VP8 support in this codec; try the next one. + if (name.startsWith("OMX.google.") || name.startsWith("OMX.SEC.")) { + // SW encoder is highest priority VP8 codec; unlikely we can get HW. + // "OMX.google." is known-software, while "OMX.SEC." is sometimes SW & + // sometimes HW, although not VP8 HW in any known device, so treat as SW + // here (b/9735008 #20). + return false; + } + return true; // Yay, passed the gauntlet of pre-requisites! + } + return false; // No VP8 encoder. + } + + private static int bitRate(int kbps) { + // webrtc "kilo" means 1000, not 1024. Apparently. + // (and the price for overshooting is frame-dropping as webrtc enforces its + // bandwidth estimation, which is unpleasant). + return kbps * 1000; + } + + private void checkOnMediaCodecThread() { + if (mediaCodecThread.getId() != Thread.currentThread().getId()) { + throw new RuntimeException( + "MediaCodecVideoEncoder previously operated on " + mediaCodecThread + + " but is now called on " + Thread.currentThread()); + } + } + + // Return the array of input buffers, or null on failure. + private ByteBuffer[] initEncode(int width, int height, int kbps) { + if (mediaCodecThread != null) { + throw new RuntimeException("Forgot to release()?"); + } + mediaCodecThread = Thread.currentThread(); + try { + MediaFormat format = + MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate(kbps)); + // Arbitrary choices. + format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 450); + // TODO(fischman): when there is more than just the N5 with a VP8 HW + // encoder, negotiate input colorformats with the codec. For now + // hard-code qcom's supported value. See isPlatformSupported above. + format.setInteger( + MediaFormat.KEY_COLOR_FORMAT, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); + mediaCodec = MediaCodec.createEncoderByType(VP8_MIME_TYPE); + if (mediaCodec == null) { + return null; + } + mediaCodec.configure( + format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mediaCodec.start(); + outputBuffers = mediaCodec.getOutputBuffers(); + return mediaCodec.getInputBuffers(); + } catch (IllegalStateException e) { + Log.e(TAG, "initEncode failed", e); + return null; + } + } + + private boolean encode( + boolean isKeyframe, int inputBuffer, int size, + long presentationTimestampUs) { + checkOnMediaCodecThread(); + try { + if (isKeyframe) { + // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could + // indicate this in queueInputBuffer() below and guarantee _this_ frame + // be encoded as a key frame, but sadly that flag is ignored. Instead, + // we request a key frame "soon". + Bundle b = new Bundle(); + b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); + mediaCodec.setParameters(b); + } + mediaCodec.queueInputBuffer( + inputBuffer, 0, size, presentationTimestampUs, 0); + return true; + } + catch (IllegalStateException e) { + Log.e(TAG, "encode failed", e); + return false; + } + } + + private void release() { + checkOnMediaCodecThread(); + try { + mediaCodec.stop(); + mediaCodec.release(); + } catch (IllegalStateException e) { + Log.e(TAG, "release failed", e); + } + mediaCodec = null; + mediaCodecThread = null; + } + + private boolean setRates(int kbps, int frameRateIgnored) { + checkOnMediaCodecThread(); + try { + Bundle params = new Bundle(); + params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitRate(kbps)); + mediaCodec.setParameters(params); + // Sure would be nice to honor the frameRate argument to this function, + // but MediaCodec doesn't expose that particular knob. b/12977358 + return true; + } catch (IllegalStateException e) { + Log.e(TAG, "setRates failed", e); + return false; + } + } + + // Dequeue an input buffer and return its index, -1 if no input buffer is + // available, or -2 if the codec is no longer operative. + private int dequeueInputBuffer() { + checkOnMediaCodecThread(); + try { + return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT); + } catch (IllegalStateException e) { + Log.e(TAG, "dequeueIntputBuffer failed", e); + return -2; + } + } + + // Helper struct for dequeueOutputBuffer() below. + private static class OutputBufferInfo { + public OutputBufferInfo( + int index, ByteBuffer buffer, boolean isKeyFrame, + long presentationTimestampUs) { + this.index = index; + this.buffer = buffer; + this.isKeyFrame = isKeyFrame; + this.presentationTimestampUs = presentationTimestampUs; + } + + private final int index; + private final ByteBuffer buffer; + private final boolean isKeyFrame; + private final long presentationTimestampUs; + } + + // Dequeue and return an output buffer, or null if no output is ready. Return + // a fake OutputBufferInfo with index -1 if the codec is no longer operable. + private OutputBufferInfo dequeueOutputBuffer() { + checkOnMediaCodecThread(); + try { + MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); + if (result >= 0) { + // MediaCodec doesn't care about Buffer position/remaining/etc so we can + // mess with them to get a slice and avoid having to pass extra + // (BufferInfo-related) parameters back to C++. + ByteBuffer outputBuffer = outputBuffers[result].duplicate(); + outputBuffer.position(info.offset); + outputBuffer.limit(info.offset + info.size); + boolean isKeyFrame = + (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + return new OutputBufferInfo( + result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs); + } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + outputBuffers = mediaCodec.getOutputBuffers(); + return dequeueOutputBuffer(); + } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + return dequeueOutputBuffer(); + } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) { + return null; + } + throw new RuntimeException("dequeueOutputBuffer: " + result); + } catch (IllegalStateException e) { + Log.e(TAG, "dequeueOutputBuffer failed", e); + return new OutputBufferInfo(-1, null, false, -1); + } + } + + // Release a dequeued output buffer back to the codec for re-use. Return + // false if the codec is no longer operable. + private boolean releaseOutputBuffer(int index) { + checkOnMediaCodecThread(); + try { + mediaCodec.releaseOutputBuffer(index, false); + return true; + } catch (IllegalStateException e) { + Log.e(TAG, "releaseOutputBuffer failed", e); + return false; + } + } +} diff --git a/examples/android/AndroidManifest.xml b/examples/android/AndroidManifest.xml index 59974f7..1fb60ad 100644 --- a/examples/android/AndroidManifest.xml +++ b/examples/android/AndroidManifest.xml @@ -17,6 +17,7 @@ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher" + android:debuggable="true" android:allowBackup="false"> <activity android:name="AppRTCDemoActivity" android:label="@string/app_name" diff --git a/examples/android/src/org/appspot/apprtc/AppRTCClient.java b/examples/android/src/org/appspot/apprtc/AppRTCClient.java index 3d369e0..2a4cd60 100644 --- a/examples/android/src/org/appspot/apprtc/AppRTCClient.java +++ b/examples/android/src/org/appspot/apprtc/AppRTCClient.java @@ -131,6 +131,10 @@ public class AppRTCClient { return appRTCSignalingParameters.videoConstraints; } + public MediaConstraints audioConstraints() { + return appRTCSignalingParameters.audioConstraints; + } + // Struct holding the signaling parameters of an AppRTC room. private class AppRTCSignalingParameters { public final List<PeerConnection.IceServer> iceServers; @@ -140,12 +144,13 @@ public class AppRTCClient { public final boolean initiator; public final MediaConstraints pcConstraints; public final MediaConstraints videoConstraints; + public final MediaConstraints audioConstraints; public AppRTCSignalingParameters( List<PeerConnection.IceServer> iceServers, String gaeBaseHref, String channelToken, String postMessageUrl, boolean initiator, MediaConstraints pcConstraints, - MediaConstraints videoConstraints) { + MediaConstraints videoConstraints, MediaConstraints audioConstraints) { this.iceServers = iceServers; this.gaeBaseHref = gaeBaseHref; this.channelToken = channelToken; @@ -153,6 +158,7 @@ public class AppRTCClient { this.initiator = initiator; this.pcConstraints = pcConstraints; this.videoConstraints = videoConstraints; + this.audioConstraints = audioConstraints; } } @@ -268,34 +274,40 @@ public class AppRTCClient { MediaConstraints pcConstraints = constraintsFromJSON( getVarValue(roomHtml, "pcConstraints", false)); Log.d(TAG, "pcConstraints: " + pcConstraints); - MediaConstraints videoConstraints = constraintsFromJSON( - getVideoConstraints( + getAVConstraints("video", getVarValue(roomHtml, "mediaConstraints", false))); - Log.d(TAG, "videoConstraints: " + videoConstraints); + MediaConstraints audioConstraints = constraintsFromJSON( + getAVConstraints("audio", + getVarValue(roomHtml, "mediaConstraints", false))); + Log.d(TAG, "audioConstraints: " + audioConstraints); return new AppRTCSignalingParameters( iceServers, gaeBaseHref, token, postMessageUrl, initiator, - pcConstraints, videoConstraints); + pcConstraints, videoConstraints, audioConstraints); } - private String getVideoConstraints(String mediaConstraintsString) { + // Return the constraints specified for |type| of "audio" or "video" in + // |mediaConstraintsString|. + private String getAVConstraints( + String type, String mediaConstraintsString) { try { JSONObject json = new JSONObject(mediaConstraintsString); // Tricksy handling of values that are allowed to be (boolean or // MediaTrackConstraints) by the getUserMedia() spec. There are three // cases below. - if (!json.has("video") || !json.optBoolean("video", true)) { - // Case 1: "video" is not present, or is an explicit "false" boolean. + if (!json.has(type) || !json.optBoolean(type, true)) { + // Case 1: "audio"/"video" is not present, or is an explicit "false" + // boolean. return null; } - if (json.optBoolean("video", false)) { - // Case 2: "video" is an explicit "true" boolean. + if (json.optBoolean(type, false)) { + // Case 2: "audio"/"video" is an explicit "true" boolean. return "{\"mandatory\": {}, \"optional\": []}"; } - // Case 3: "video" is an object. - return json.getJSONObject("video").toString(); + // Case 3: "audio"/"video" is an object. + return json.getJSONObject(type).toString(); } catch (JSONException e) { throw new RuntimeException(e); } diff --git a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java index e872355..1754994 100644 --- a/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java +++ b/examples/android/src/org/appspot/apprtc/AppRTCDemoActivity.java @@ -75,6 +75,7 @@ public class AppRTCDemoActivity extends Activity private static final String TAG = "AppRTCDemoActivity"; private PeerConnectionFactory factory; private VideoSource videoSource; + private boolean videoSourceStopped; private PeerConnection pc; private final PCObserver pcObserver = new PCObserver(); private final SDPObserver sdpObserver = new SDPObserver(); @@ -159,6 +160,7 @@ public class AppRTCDemoActivity extends Activity vsv.onPause(); if (videoSource != null) { videoSource.stop(); + videoSourceStopped = true; } } @@ -166,7 +168,7 @@ public class AppRTCDemoActivity extends Activity public void onResume() { super.onResume(); vsv.onResume(); - if (videoSource != null) { + if (videoSource != null && videoSourceStopped) { videoSource.restart(); } } @@ -239,7 +241,9 @@ public class AppRTCDemoActivity extends Activity vsv, VideoStreamsView.Endpoint.LOCAL))); lMS.addTrack(videoTrack); } - lMS.addTrack(factory.createAudioTrack("ARDAMSa0")); + if (appRtcClient.audioConstraints() != null) { + lMS.addTrack(factory.createAudioTrack("ARDAMSa0")); + } pc.addStream(lMS, new MediaConstraints()); } logAndToast("Waiting for ICE candidates..."); diff --git a/libjingle.gyp b/libjingle.gyp index 3904a0f..0ed5c64 100755 --- a/libjingle.gyp +++ b/libjingle.gyp @@ -53,6 +53,9 @@ 'sources': [ 'app/webrtc/java/jni/peerconnection_jni.cc' ], + 'include_dirs': [ + '<(DEPTH)/third_party/libyuv/include', + ], 'conditions': [ ['OS=="linux"', { 'defines': [ @@ -105,6 +108,7 @@ # included here, or better yet, build a proper .jar in webrtc # and include it here. 'android_java_files': [ + 'app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.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', |