| /* |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "TvInputHal" |
| |
| //#define LOG_NDEBUG 0 |
| |
| #include "android_os_MessageQueue.h" |
| #include "android_runtime/AndroidRuntime.h" |
| #include "android_runtime/android_view_Surface.h" |
| #include "JNIHelp.h" |
| #include "jni.h" |
| |
| #include <gui/Surface.h> |
| #include <utils/Errors.h> |
| #include <utils/KeyedVector.h> |
| #include <utils/Log.h> |
| #include <utils/Looper.h> |
| #include <utils/NativeHandle.h> |
| #include <hardware/tv_input.h> |
| |
| namespace android { |
| |
| static struct { |
| jmethodID deviceAvailable; |
| jmethodID deviceUnavailable; |
| jmethodID streamConfigsChanged; |
| jmethodID firstFrameCaptured; |
| } gTvInputHalClassInfo; |
| |
| static struct { |
| jclass clazz; |
| } gTvStreamConfigClassInfo; |
| |
| static struct { |
| jclass clazz; |
| |
| jmethodID constructor; |
| jmethodID streamId; |
| jmethodID type; |
| jmethodID maxWidth; |
| jmethodID maxHeight; |
| jmethodID generation; |
| jmethodID flags; |
| jmethodID build; |
| } gTvStreamConfigBuilderClassInfo; |
| |
| static struct { |
| jclass clazz; |
| |
| jmethodID constructor; |
| jmethodID deviceId; |
| jmethodID type; |
| jmethodID hdmiPortId; |
| jmethodID audioType; |
| jmethodID audioAddress; |
| jmethodID build; |
| } gTvInputHardwareInfoBuilderClassInfo; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| class BufferProducerThread : public Thread { |
| public: |
| BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream); |
| |
| virtual status_t readyToRun(); |
| |
| void setSurface(const sp<Surface>& surface); |
| void onCaptured(uint32_t seq, bool succeeded); |
| void shutdown(); |
| |
| private: |
| Mutex mLock; |
| Condition mCondition; |
| sp<Surface> mSurface; |
| tv_input_device_t* mDevice; |
| int mDeviceId; |
| tv_stream_t mStream; |
| sp<ANativeWindowBuffer_t> mBuffer; |
| enum { |
| CAPTURING, |
| CAPTURED, |
| RELEASED, |
| } mBufferState; |
| uint32_t mSeq; |
| bool mShutdown; |
| |
| virtual bool threadLoop(); |
| |
| void setSurfaceLocked(const sp<Surface>& surface); |
| }; |
| |
| BufferProducerThread::BufferProducerThread( |
| tv_input_device_t* device, int deviceId, const tv_stream_t* stream) |
| : Thread(false), |
| mDevice(device), |
| mDeviceId(deviceId), |
| mBuffer(NULL), |
| mBufferState(RELEASED), |
| mSeq(0u), |
| mShutdown(false) { |
| memcpy(&mStream, stream, sizeof(mStream)); |
| } |
| |
| status_t BufferProducerThread::readyToRun() { |
| sp<ANativeWindow> anw(mSurface); |
| status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage); |
| if (err != NO_ERROR) { |
| return err; |
| } |
| err = native_window_set_buffers_dimensions( |
| anw.get(), mStream.buffer_producer.width, mStream.buffer_producer.height); |
| if (err != NO_ERROR) { |
| return err; |
| } |
| err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format); |
| if (err != NO_ERROR) { |
| return err; |
| } |
| return NO_ERROR; |
| } |
| |
| void BufferProducerThread::setSurface(const sp<Surface>& surface) { |
| Mutex::Autolock autoLock(&mLock); |
| setSurfaceLocked(surface); |
| } |
| |
| void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) { |
| if (surface == mSurface) { |
| return; |
| } |
| |
| if (mBufferState == CAPTURING) { |
| mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq); |
| } |
| while (mBufferState == CAPTURING) { |
| status_t err = mCondition.waitRelative(mLock, s2ns(1)); |
| if (err != NO_ERROR) { |
| ALOGE("error %d while wating for buffer state to change.", err); |
| break; |
| } |
| } |
| mBuffer.clear(); |
| mBufferState = RELEASED; |
| |
| mSurface = surface; |
| mCondition.broadcast(); |
| } |
| |
| void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) { |
| Mutex::Autolock autoLock(&mLock); |
| if (seq != mSeq) { |
| ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq); |
| } |
| if (mBufferState != CAPTURING) { |
| ALOGW("mBufferState != CAPTURING : instead %d", mBufferState); |
| } |
| if (succeeded) { |
| mBufferState = CAPTURED; |
| } else { |
| mBuffer.clear(); |
| mBufferState = RELEASED; |
| } |
| mCondition.broadcast(); |
| } |
| |
| void BufferProducerThread::shutdown() { |
| Mutex::Autolock autoLock(&mLock); |
| mShutdown = true; |
| setSurfaceLocked(NULL); |
| requestExitAndWait(); |
| } |
| |
| bool BufferProducerThread::threadLoop() { |
| Mutex::Autolock autoLock(&mLock); |
| |
| status_t err = NO_ERROR; |
| if (mSurface == NULL) { |
| err = mCondition.waitRelative(mLock, s2ns(1)); |
| // It's OK to time out here. |
| if (err != NO_ERROR && err != TIMED_OUT) { |
| ALOGE("error %d while wating for non-null surface to be set", err); |
| return false; |
| } |
| return true; |
| } |
| sp<ANativeWindow> anw(mSurface); |
| while (mBufferState == CAPTURING) { |
| err = mCondition.waitRelative(mLock, s2ns(1)); |
| if (err != NO_ERROR) { |
| ALOGE("error %d while wating for buffer state to change.", err); |
| return false; |
| } |
| } |
| if (mBufferState == CAPTURED && anw != NULL) { |
| err = anw->queueBuffer(anw.get(), mBuffer.get(), -1); |
| if (err != NO_ERROR) { |
| ALOGE("error %d while queueing buffer to surface", err); |
| return false; |
| } |
| mBuffer.clear(); |
| mBufferState = RELEASED; |
| } |
| if (mBuffer == NULL && !mShutdown && anw != NULL) { |
| ANativeWindowBuffer_t* buffer = NULL; |
| err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer); |
| if (err != NO_ERROR) { |
| ALOGE("error %d while dequeueing buffer to surface", err); |
| return false; |
| } |
| mBuffer = buffer; |
| mBufferState = CAPTURING; |
| mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id, |
| buffer->handle, ++mSeq); |
| } |
| |
| return true; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| class JTvInputHal { |
| public: |
| ~JTvInputHal(); |
| |
| static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper); |
| |
| int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface); |
| int removeStream(int deviceId, int streamId); |
| const tv_stream_config_ext_t* getStreamConfigs(int deviceId, int* numConfigs); |
| |
| void onDeviceAvailable(const tv_input_device_info_t& info); |
| void onDeviceUnavailable(int deviceId); |
| void onStreamConfigurationsChanged(int deviceId); |
| void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded); |
| |
| private: |
| // Connection between a surface and a stream. |
| class Connection { |
| public: |
| Connection() {} |
| |
| sp<Surface> mSurface; |
| tv_stream_type_t mStreamType; |
| |
| // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE |
| sp<NativeHandle> mSourceHandle; |
| // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER |
| sp<BufferProducerThread> mThread; |
| }; |
| |
| class NotifyHandler : public MessageHandler { |
| public: |
| NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event); |
| ~NotifyHandler(); |
| |
| virtual void handleMessage(const Message& message); |
| |
| private: |
| tv_input_event_t mEvent; |
| JTvInputHal* mHal; |
| }; |
| |
| JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev, const sp<Looper>& looper); |
| |
| static void notify( |
| tv_input_device_t* dev, tv_input_event_t* event, void* data); |
| |
| static void cloneTvInputEvent( |
| tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent); |
| |
| Mutex mLock; |
| jweak mThiz; |
| tv_input_device_t* mDevice; |
| tv_input_callback_ops_t mCallback; |
| sp<Looper> mLooper; |
| |
| KeyedVector<int, KeyedVector<int, Connection> > mConnections; |
| |
| tv_stream_config_ext_t* mConfigBuffer; |
| int mConfigBufferSize; |
| }; |
| |
| JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device, |
| const sp<Looper>& looper) |
| : mConfigBuffer(NULL), |
| mConfigBufferSize(0) { |
| mThiz = env->NewWeakGlobalRef(thiz); |
| mDevice = device; |
| mCallback.notify = &JTvInputHal::notify; |
| mLooper = looper; |
| |
| mDevice->initialize(mDevice, &mCallback, this); |
| } |
| |
| JTvInputHal::~JTvInputHal() { |
| mDevice->common.close((hw_device_t*)mDevice); |
| |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| env->DeleteWeakGlobalRef(mThiz); |
| mThiz = NULL; |
| |
| if (mConfigBuffer != NULL) { |
| delete[] mConfigBuffer; |
| } |
| } |
| |
| JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) { |
| tv_input_module_t* module = NULL; |
| status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID, |
| (hw_module_t const**)&module); |
| if (err) { |
| ALOGE("Couldn't load %s module (%s)", |
| TV_INPUT_HARDWARE_MODULE_ID, strerror(-err)); |
| return 0; |
| } |
| |
| tv_input_device_t* device = NULL; |
| err = module->common.methods->open( |
| (hw_module_t*)module, |
| TV_INPUT_DEFAULT_DEVICE, |
| (hw_device_t**)&device); |
| if (err) { |
| ALOGE("Couldn't open %s device (%s)", |
| TV_INPUT_DEFAULT_DEVICE, strerror(-err)); |
| return 0; |
| } |
| |
| return new JTvInputHal(env, thiz, device, looper); |
| } |
| |
| int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) { |
| KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); |
| if (connections.indexOfKey(streamId) < 0) { |
| connections.add(streamId, Connection()); |
| } |
| Connection& connection = connections.editValueFor(streamId); |
| if (connection.mSurface == surface) { |
| // Nothing to do |
| return NO_ERROR; |
| } |
| // Clear the surface in the connection. |
| if (connection.mSurface != NULL) { |
| if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { |
| if (Surface::isValid(connection.mSurface)) { |
| connection.mSurface->setSidebandStream(NULL); |
| } |
| } |
| connection.mSurface.clear(); |
| } |
| if (connection.mSourceHandle == NULL && connection.mThread == NULL) { |
| // Need to configure stream |
| int numConfigs = 0; |
| const tv_stream_config_ext_t* configs = getStreamConfigs(deviceId, &numConfigs); |
| if (configs == NULL) { |
| ALOGE("Couldn't get stream configs"); |
| return UNKNOWN_ERROR; |
| } |
| int configIndex = -1; |
| for (int i = 0; i < numConfigs; ++i) { |
| if (configs[i].config.stream_id == streamId) { |
| configIndex = i; |
| break; |
| } |
| } |
| if (configIndex == -1) { |
| ALOGE("Cannot find a config with given stream ID: %d", streamId); |
| return BAD_VALUE; |
| } |
| connection.mStreamType = configs[configIndex].config.type; |
| |
| tv_stream_t stream; |
| stream.stream_id = configs[configIndex].config.stream_id; |
| if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { |
| stream.buffer_producer.width = configs[configIndex].config.max_video_width; |
| stream.buffer_producer.height = configs[configIndex].config.max_video_height; |
| } |
| if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) { |
| ALOGE("Couldn't add stream"); |
| return UNKNOWN_ERROR; |
| } |
| if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { |
| connection.mSourceHandle = NativeHandle::create( |
| stream.sideband_stream_source_handle, false); |
| } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { |
| if (connection.mThread != NULL) { |
| connection.mThread->shutdown(); |
| } |
| connection.mThread = new BufferProducerThread(mDevice, deviceId, &stream); |
| connection.mThread->run("BufferProducerThread"); |
| } |
| } |
| connection.mSurface = surface; |
| if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { |
| connection.mSurface->setSidebandStream(connection.mSourceHandle); |
| } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { |
| connection.mThread->setSurface(surface); |
| } |
| return NO_ERROR; |
| } |
| |
| int JTvInputHal::removeStream(int deviceId, int streamId) { |
| KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); |
| if (connections.indexOfKey(streamId) < 0) { |
| return BAD_VALUE; |
| } |
| Connection& connection = connections.editValueFor(streamId); |
| if (connection.mSurface == NULL) { |
| // Nothing to do |
| return NO_ERROR; |
| } |
| if (Surface::isValid(connection.mSurface)) { |
| connection.mSurface->setSidebandStream(NULL); |
| } |
| connection.mSurface.clear(); |
| if (connection.mThread != NULL) { |
| connection.mThread->shutdown(); |
| connection.mThread.clear(); |
| } |
| if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) { |
| ALOGE("Couldn't remove stream"); |
| return BAD_VALUE; |
| } |
| if (connection.mSourceHandle != NULL) { |
| connection.mSourceHandle.clear(); |
| } |
| return NO_ERROR; |
| } |
| |
| const tv_stream_config_ext_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { |
| const tv_stream_config_ext_t* configs = NULL; |
| if (mDevice->common.version >= TV_INPUT_DEVICE_API_VERSION_0_2) { |
| if (mDevice->get_stream_configurations_ext( |
| mDevice, deviceId, numConfigs, &configs) != 0) { |
| ALOGE("Couldn't get stream configs"); |
| return NULL; |
| } |
| } else { |
| const tv_stream_config_t* oldConfigs; |
| if (mDevice->get_stream_configurations( |
| mDevice, deviceId, numConfigs, &oldConfigs) != 0) { |
| ALOGE("Couldn't get stream configs"); |
| return NULL; |
| } |
| if (mConfigBufferSize < *numConfigs) { |
| mConfigBufferSize = (*numConfigs / 16 + 1) * 16; |
| if (mConfigBuffer != NULL) { |
| delete[] mConfigBuffer; |
| } |
| mConfigBuffer = new tv_stream_config_ext_t[mConfigBufferSize]; |
| } |
| for (int i = 0; i < *numConfigs; ++i) { |
| mConfigBuffer[i].config = oldConfigs[i]; |
| mConfigBuffer[i].flags = 0; |
| } |
| configs = mConfigBuffer; |
| } |
| return configs; |
| } |
| |
| // static |
| void JTvInputHal::notify( |
| tv_input_device_t* dev, tv_input_event_t* event, void* data) { |
| JTvInputHal* thiz = (JTvInputHal*)data; |
| thiz->mLooper->sendMessage(new NotifyHandler(thiz, event), event->type); |
| } |
| |
| // static |
| void JTvInputHal::cloneTvInputEvent( |
| tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent) { |
| memcpy(dstEvent, srcEvent, sizeof(tv_input_event_t)); |
| if ((srcEvent->type == TV_INPUT_EVENT_DEVICE_AVAILABLE || |
| srcEvent->type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE || |
| srcEvent->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) && |
| srcEvent->device_info.audio_address != NULL){ |
| char* audio_address = new char[strlen(srcEvent->device_info.audio_address) + 1]; |
| strcpy(audio_address, srcEvent->device_info.audio_address); |
| dstEvent->device_info.audio_address = audio_address; |
| } |
| } |
| |
| void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) { |
| { |
| Mutex::Autolock autoLock(&mLock); |
| mConnections.add(info.device_id, KeyedVector<int, Connection>()); |
| } |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| |
| jobject builder = env->NewObject( |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| gTvInputHardwareInfoBuilderClassInfo.constructor); |
| env->CallObjectMethod( |
| builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id); |
| env->CallObjectMethod( |
| builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type); |
| if (info.type == TV_INPUT_TYPE_HDMI) { |
| env->CallObjectMethod( |
| builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id); |
| } |
| env->CallObjectMethod( |
| builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type); |
| if (info.audio_type != AUDIO_DEVICE_NONE) { |
| jstring audioAddress = env->NewStringUTF(info.audio_address); |
| env->CallObjectMethod( |
| builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress); |
| env->DeleteLocalRef(audioAddress); |
| } |
| |
| jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build); |
| |
| env->CallVoidMethod( |
| mThiz, |
| gTvInputHalClassInfo.deviceAvailable, |
| infoObject); |
| |
| env->DeleteLocalRef(builder); |
| env->DeleteLocalRef(infoObject); |
| } |
| |
| void JTvInputHal::onDeviceUnavailable(int deviceId) { |
| { |
| Mutex::Autolock autoLock(&mLock); |
| KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); |
| for (size_t i = 0; i < connections.size(); ++i) { |
| removeStream(deviceId, connections.keyAt(i)); |
| } |
| connections.clear(); |
| mConnections.removeItem(deviceId); |
| } |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| env->CallVoidMethod( |
| mThiz, |
| gTvInputHalClassInfo.deviceUnavailable, |
| deviceId); |
| } |
| |
| void JTvInputHal::onStreamConfigurationsChanged(int deviceId) { |
| { |
| Mutex::Autolock autoLock(&mLock); |
| KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); |
| for (size_t i = 0; i < connections.size(); ++i) { |
| removeStream(deviceId, connections.keyAt(i)); |
| } |
| connections.clear(); |
| } |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| env->CallVoidMethod( |
| mThiz, |
| gTvInputHalClassInfo.streamConfigsChanged, |
| deviceId); |
| } |
| |
| void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { |
| sp<BufferProducerThread> thread; |
| { |
| Mutex::Autolock autoLock(&mLock); |
| KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); |
| Connection& connection = connections.editValueFor(streamId); |
| if (connection.mThread == NULL) { |
| ALOGE("capture thread not existing."); |
| return; |
| } |
| thread = connection.mThread; |
| } |
| thread->onCaptured(seq, succeeded); |
| if (seq == 0) { |
| JNIEnv* env = AndroidRuntime::getJNIEnv(); |
| env->CallVoidMethod( |
| mThiz, |
| gTvInputHalClassInfo.firstFrameCaptured, |
| deviceId, |
| streamId); |
| } |
| } |
| |
| JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event) { |
| mHal = hal; |
| cloneTvInputEvent(&mEvent, event); |
| } |
| |
| JTvInputHal::NotifyHandler::~NotifyHandler() { |
| if ((mEvent.type == TV_INPUT_EVENT_DEVICE_AVAILABLE || |
| mEvent.type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE || |
| mEvent.type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) && |
| mEvent.device_info.audio_address != NULL) { |
| delete mEvent.device_info.audio_address; |
| } |
| } |
| |
| void JTvInputHal::NotifyHandler::handleMessage(const Message& message) { |
| switch (mEvent.type) { |
| case TV_INPUT_EVENT_DEVICE_AVAILABLE: { |
| mHal->onDeviceAvailable(mEvent.device_info); |
| } break; |
| case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: { |
| mHal->onDeviceUnavailable(mEvent.device_info.device_id); |
| } break; |
| case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: { |
| mHal->onStreamConfigurationsChanged(mEvent.device_info.device_id); |
| } break; |
| case TV_INPUT_EVENT_CAPTURE_SUCCEEDED: { |
| mHal->onCaptured(mEvent.capture_result.device_id, |
| mEvent.capture_result.stream_id, |
| mEvent.capture_result.seq, |
| true /* succeeded */); |
| } break; |
| case TV_INPUT_EVENT_CAPTURE_FAILED: { |
| mHal->onCaptured(mEvent.capture_result.device_id, |
| mEvent.capture_result.stream_id, |
| mEvent.capture_result.seq, |
| false /* succeeded */); |
| } break; |
| default: |
| ALOGE("Unrecognizable event"); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) { |
| sp<MessageQueue> messageQueue = |
| android_os_MessageQueue_getMessageQueue(env, messageQueueObj); |
| return (jlong)JTvInputHal::createInstance(env, thiz, messageQueue->getLooper()); |
| } |
| |
| static int nativeAddOrUpdateStream(JNIEnv* env, jclass clazz, |
| jlong ptr, jint deviceId, jint streamId, jobject jsurface) { |
| JTvInputHal* tvInputHal = (JTvInputHal*)ptr; |
| if (!jsurface) { |
| return BAD_VALUE; |
| } |
| sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); |
| if (!Surface::isValid(surface)) { |
| return BAD_VALUE; |
| } |
| return tvInputHal->addOrUpdateStream(deviceId, streamId, surface); |
| } |
| |
| static int nativeRemoveStream(JNIEnv* env, jclass clazz, |
| jlong ptr, jint deviceId, jint streamId) { |
| JTvInputHal* tvInputHal = (JTvInputHal*)ptr; |
| return tvInputHal->removeStream(deviceId, streamId); |
| } |
| |
| static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, |
| jlong ptr, jint deviceId, jint generation) { |
| JTvInputHal* tvInputHal = (JTvInputHal*)ptr; |
| int numConfigs = 0; |
| const tv_stream_config_ext_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); |
| |
| jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL); |
| for (int i = 0; i < numConfigs; ++i) { |
| jobject builder = env->NewObject( |
| gTvStreamConfigBuilderClassInfo.clazz, |
| gTvStreamConfigBuilderClassInfo.constructor); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].config.stream_id); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.type, configs[i].config.type); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.maxWidth, |
| configs[i].config.max_video_width); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.maxHeight, |
| configs[i].config.max_video_height); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.generation, generation); |
| env->CallObjectMethod( |
| builder, gTvStreamConfigBuilderClassInfo.flags, |
| configs[i].flags); |
| |
| jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build); |
| |
| env->SetObjectArrayElement(result, i, config); |
| |
| env->DeleteLocalRef(config); |
| env->DeleteLocalRef(builder); |
| } |
| return result; |
| } |
| |
| static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { |
| JTvInputHal* tvInputHal = (JTvInputHal*)ptr; |
| delete tvInputHal; |
| } |
| |
| static const JNINativeMethod gTvInputHalMethods[] = { |
| /* name, signature, funcPtr */ |
| { "nativeOpen", "(Landroid/os/MessageQueue;)J", |
| (void*) nativeOpen }, |
| { "nativeAddOrUpdateStream", "(JIILandroid/view/Surface;)I", |
| (void*) nativeAddOrUpdateStream }, |
| { "nativeRemoveStream", "(JII)I", |
| (void*) nativeRemoveStream }, |
| { "nativeGetStreamConfigs", "(JII)[Landroid/media/tv/TvStreamConfig;", |
| (void*) nativeGetStreamConfigs }, |
| { "nativeClose", "(J)V", |
| (void*) nativeClose }, |
| }; |
| |
| #define FIND_CLASS(var, className) \ |
| var = env->FindClass(className); \ |
| LOG_FATAL_IF(! var, "Unable to find class " className) |
| |
| #define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ |
| var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ |
| LOG_FATAL_IF(! var, "Unable to find method" methodName) |
| |
| int register_android_server_tv_TvInputHal(JNIEnv* env) { |
| int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal", |
| gTvInputHalMethods, NELEM(gTvInputHalMethods)); |
| LOG_FATAL_IF(res < 0, "Unable to register native methods."); |
| (void)res; // Don't complain about unused variable in the LOG_NDEBUG case |
| |
| jclass clazz; |
| FIND_CLASS(clazz, "com/android/server/tv/TvInputHal"); |
| |
| GET_METHOD_ID( |
| gTvInputHalClassInfo.deviceAvailable, clazz, |
| "deviceAvailableFromNative", "(Landroid/media/tv/TvInputHardwareInfo;)V"); |
| GET_METHOD_ID( |
| gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V"); |
| GET_METHOD_ID( |
| gTvInputHalClassInfo.streamConfigsChanged, clazz, |
| "streamConfigsChangedFromNative", "(I)V"); |
| GET_METHOD_ID( |
| gTvInputHalClassInfo.firstFrameCaptured, clazz, |
| "firstFrameCapturedFromNative", "(II)V"); |
| |
| FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/media/tv/TvStreamConfig"); |
| gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz)); |
| |
| FIND_CLASS(gTvStreamConfigBuilderClassInfo.clazz, "android/media/tv/TvStreamConfig$Builder"); |
| gTvStreamConfigBuilderClassInfo.clazz = |
| jclass(env->NewGlobalRef(gTvStreamConfigBuilderClassInfo.clazz)); |
| |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.constructor, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "<init>", "()V"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.streamId, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "streamId", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.type, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "type", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.maxWidth, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "maxWidth", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.maxHeight, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "maxHeight", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.generation, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "generation", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.flags, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "flags", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); |
| GET_METHOD_ID( |
| gTvStreamConfigBuilderClassInfo.build, |
| gTvStreamConfigBuilderClassInfo.clazz, |
| "build", "()Landroid/media/tv/TvStreamConfig;"); |
| |
| FIND_CLASS(gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "android/media/tv/TvInputHardwareInfo$Builder"); |
| gTvInputHardwareInfoBuilderClassInfo.clazz = |
| jclass(env->NewGlobalRef(gTvInputHardwareInfoBuilderClassInfo.clazz)); |
| |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.constructor, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "<init>", "()V"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.deviceId, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "deviceId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.type, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "type", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "hdmiPortId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.audioType, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "audioType", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.audioAddress, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "audioAddress", "(Ljava/lang/String;)Landroid/media/tv/TvInputHardwareInfo$Builder;"); |
| GET_METHOD_ID( |
| gTvInputHardwareInfoBuilderClassInfo.build, |
| gTvInputHardwareInfoBuilderClassInfo.clazz, |
| "build", "()Landroid/media/tv/TvInputHardwareInfo;"); |
| |
| return 0; |
| } |
| |
| } /* namespace android */ |