diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2042e36c7..8a4658d23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,11 +59,9 @@ jobs: - name: Build JNI (Mac and Linux) if: ${{ runner.os == 'macOS' || runner.os == 'linux' }} run: | - git clone https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp.git --single-branch -b ${{ env.BRANCH_NAME }} - cd amazon-kinesis-video-streams-producer-sdk-cpp mkdir build cd build - cmake .. -DBUILD_JNI_ONLY=ON + cmake .. make -j - name: Build JNI (Windows) @@ -72,42 +70,27 @@ jobs: run: | @echo on - :: Create a shorter directory to avoid long path issues - mkdir D:\work || exit /b 1 - git clone https://github.com/awslabs/amazon-kinesis-video-streams-producer-sdk-cpp.git D:\work\producer --single-branch -b ${{ env.BRANCH_NAME }} || exit /b 1 - - :: Ensure cloning succeeded - if not exist "D:\work\producer" ( - echo "Error: Clone failed, directory does not exist." - exit /b 1 - ) - - cd D:\work\producer || exit /b 1 - :: Create build directory mkdir build || exit /b 1 cd build || exit /b 1 :: Run CMake with the correct path - cmake .. -DBUILD_JNI_ONLY=ON -G "Visual Studio 17 2022" || exit /b 1 + cmake .. -G "Visual Studio 17 2022" || exit /b 1 cmake --build . --config Release --verbose || exit /b 1 :: Move the compiled library to the expected location :: Note: Using Visual Studio generator, the output file gets put in Release\KinesisVideoProducerJNI.dll :: rather than in the build folder directly - cd "%GITHUB_WORKSPACE%" || exit /b 1 - mkdir amazon-kinesis-video-streams-producer-sdk-cpp || exit /b 1 - mkdir amazon-kinesis-video-streams-producer-sdk-cpp\build || exit /b 1 - move D:\work\producer\build\Release\KinesisVideoProducerJNI.dll amazon-kinesis-video-streams-producer-sdk-cpp\build || exit /b 1 + move Release\KinesisVideoProducerJNI.dll . || exit /b 1 - name: Upload JNI Library uses: actions/upload-artifact@v4 with: name: jni-library-${{ matrix.platform.name }} path: | - amazon-kinesis-video-streams-producer-sdk-cpp/build/libKinesisVideoProducerJNI.so - amazon-kinesis-video-streams-producer-sdk-cpp/build/libKinesisVideoProducerJNI.dylib - amazon-kinesis-video-streams-producer-sdk-cpp/build/KinesisVideoProducerJNI.dll + build/libKinesisVideoProducerJNI.so + build/libKinesisVideoProducerJNI.dylib + build/KinesisVideoProducerJNI.dll retention-days: 1 run-unit-tests: diff --git a/.gitignore b/.gitignore index 6aa446dea..8a838b977 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ cm-file target logs hs_err_pid*.log - +dependency/ diff --git a/CMake/Dependencies/libkvspic-CMakeLists.txt b/CMake/Dependencies/libkvspic-CMakeLists.txt new file mode 100644 index 000000000..c8825ec99 --- /dev/null +++ b/CMake/Dependencies/libkvspic-CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.6.3) + +project(libkvspic-download LANGUAGES C) + +include(ExternalProject) + +ExternalProject_Add(libkvspic-download + GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-pic.git + GIT_TAG v1.2.0 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/kvspic-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/kvspic-build" + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_DEPENDENCIES=OFF + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/CMake/Utilities.cmake b/CMake/Utilities.cmake new file mode 100644 index 000000000..a6a011c4e --- /dev/null +++ b/CMake/Utilities.cmake @@ -0,0 +1,42 @@ +# only fetch target repo for add_subdirectory later +function(fetch_repo lib_name) + set(supported_libs + kvspic) + list(FIND supported_libs ${lib_name} index) + if(${index} EQUAL -1) + message(WARNING "${lib_name} is not supported for fetch_repo") + return() + endif() + + if (WIN32 OR NOT PARALLEL_BUILD) + set(PARALLEL_BUILD "") # No parallel build for Windows + else() + set(PARALLEL_BUILD "--parallel") # Enable parallel builds for Unix-like systems + endif() + + # build library + configure_file( + ./CMake/Dependencies/lib${lib_name}-CMakeLists.txt + ${DEPENDENCY_DOWNLOAD_PATH}/lib${lib_name}/CMakeLists.txt COPYONLY) + execute_process( + COMMAND ${CMAKE_COMMAND} + ${CMAKE_GENERATOR} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${DEPENDENCY_DOWNLOAD_PATH}/lib${lib_name}) + if(result) + message(FATAL_ERROR "CMake step for lib${lib_name} failed: ${result}") + endif() + execute_process( + COMMAND ${CMAKE_COMMAND} --build . ${PARALLEL_BUILD} + RESULT_VARIABLE result + WORKING_DIRECTORY ${DEPENDENCY_DOWNLOAD_PATH}/lib${lib_name}) + if(result) + message(FATAL_ERROR "CMake step for lib${lib_name} failed: ${result}") + endif() +endfunction() + +function(enableSanitizer SANITIZER) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fsanitize=${SANITIZER} -fno-omit-frame-pointer" PARENT_SCOPE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fsanitize=${SANITIZER} -fno-omit-frame-pointer -fno-optimize-sibling-calls" PARENT_SCOPE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${SANITIZER}" PARENT_SCOPE) +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..65d0dc430 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.6.3) + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}") +include(Utilities) + +project(KinesisVideoProducerJNI VERSION 3.4.2) + +set(CMAKE_CXX_STANDARD 11) +include(GNUInstallDirs) + +add_definitions(-DCPP_VERSION_STRING=\"${PROJECT_VERSION}\") + +set(CMAKE_MACOSX_RPATH TRUE) + +############# Check dependencies ############ + +find_package(Threads) + +find_package(JNI QUIET) +if(NOT JNI_FOUND) + message(FATAL_ERROR + "\nJava Development Kit (JDK) not found!" + "\nPlease ensure that:" + "\n 1. JDK is installed" + "\n 2. JAVA_HOME environment variable is set correctly" + "\n 3. Your JDK installation includes JNI headers" + ) +endif() +include_directories(${JNI_INCLUDE_DIRS}) + +############# Enable Sanitizers ############ +if(${CMAKE_C_COMPILER_ID} MATCHES "GNU|Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + + if(ADD_MUCLIBC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -muclibc") + endif() + + if(CODE_COVERAGE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + endif() + + if(ADDRESS_SANITIZER) + enableSanitizer("address") + endif() + if(MEMORY_SANITIZER) + enableSanitizer("memory") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-memory-track-origins") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-memory-track-origins") + endif() + if(THREAD_SANITIZER) + enableSanitizer("thread") + endif() + if(UNDEFINED_BEHAVIOR_SANITIZER) + enableSanitizer("undefined") + endif() +endif() + +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) +endif() + +############# Build Targets ############ + +# PIC +set(DEPENDENCY_DOWNLOAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dependency) +set(BUILD_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) +fetch_repo(kvspic ${BUILD_ARGS}) +add_subdirectory("${DEPENDENCY_DOWNLOAD_PATH}/libkvspic/kvspic-src") +file(GLOB PIC_HEADERS "${pic_project_SOURCE_DIR}/src/*/include") +include_directories("${PIC_HEADERS}") + +# JNI +file(GLOB_RECURSE JNI_SOURCE_FILES "jni/*.cpp") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/jni/include) + +add_library(KinesisVideoProducerJNI SHARED ${JNI_HEADERS} ${JNI_SOURCE_FILES}) +target_link_libraries(KinesisVideoProducerJNI kvspic) + +add_custom_target(clean-all + COMMENT "Removing all build and dependency directories" + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${DEPENDENCY_DOWNLOAD_PATH} + COMMENT "Removed the build and dependency directories" +) diff --git a/jni/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp b/jni/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp new file mode 100644 index 000000000..a8c491f8c --- /dev/null +++ b/jni/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.cpp @@ -0,0 +1,2707 @@ +/** + * Implementation of Kinesis Video Producer client wrapper + */ +#define LOG_CLASS "KinesisVideoClientWrapper" +#define MAX_LOG_MESSAGE_LENGTH 1024 * 10 + +#include "com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h" + +// initializing static members of the class +JavaVM* KinesisVideoClientWrapper::mJvm = NULL; +jobject KinesisVideoClientWrapper::mGlobalJniObjRef = NULL; +jmethodID KinesisVideoClientWrapper::mLogPrintMethodId = NULL; + + +KinesisVideoClientWrapper::KinesisVideoClientWrapper(JNIEnv* env, + jobject thiz, + jobject deviceInfo): mClientHandle(INVALID_CLIENT_HANDLE_VALUE) +{ + UINT32 retStatus; + + CHECK(env != NULL && thiz != NULL && deviceInfo != NULL); + + // Get and store the JVM so the callbacks can use it later + if (env->GetJavaVM(&mJvm) != 0) { + CHECK_EXT(FALSE, "Couldn't retrieve the JavaVM reference."); + } + + // Set the callbacks + if (!setCallbacks(env, thiz)) { + throwNativeException(env, EXCEPTION_NAME, "Failed to set the callbacks.", STATUS_INVALID_ARG); + return; + } + + // Extract the DeviceInfo structure + if (!setDeviceInfo(env, deviceInfo, &mDeviceInfo)) { + throwNativeException(env, EXCEPTION_NAME, "Failed to set the DeviceInfo structure.", STATUS_INVALID_ARG); + return; + } + + // Creating the client object might return an error as well so freeing potentially allocated tags right after the call. + retStatus = createKinesisVideoClient(&mDeviceInfo, &mClientCallbacks, &mClientHandle); + releaseTags(mDeviceInfo.tags); + if (STATUS_FAILED(retStatus)) { + throwNativeException(env, EXCEPTION_NAME, "Failed to create Kinesis Video client.", retStatus); + return; + } + + // Auxiliary initialization + mAuthInfo.version = AUTH_INFO_CURRENT_VERSION; + mAuthInfo.size = 0; + mAuthInfo.expiration = 0; + mAuthInfo.type = AUTH_INFO_UNDEFINED; +} + +KinesisVideoClientWrapper::~KinesisVideoClientWrapper() +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + + if (mJvm == NULL) { + return; + } + + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + if (STATUS_FAILED(retStatus = freeKinesisVideoClient(&mClientHandle))) { + DLOGE("Failed to free the producer client object"); + if (env != NULL) { + throwNativeException(env, EXCEPTION_NAME, "Failed to free the producer client object.", retStatus); + } + return; + } + } + + // You cannot log anything after this! + if (env != NULL && mGlobalJniObjRef != NULL) { + env->DeleteGlobalRef(mGlobalJniObjRef); + mGlobalJniObjRef = NULL; + } +} + +void KinesisVideoClientWrapper::stopKinesisVideoStreams() +{ + STATUS retStatus = STATUS_SUCCESS; + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::stopKinesisVideoStreams(mClientHandle))) + { + DLOGE("Failed to stop the streams with status code 0x%08x", retStatus); + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + throwNativeException(env, EXCEPTION_NAME, "Failed to stop the streams.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::stopKinesisVideoStream(jlong streamHandle) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::stopKinesisVideoStream(streamHandle))) + { + DLOGE("Failed to stop kinesis video stream with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to stop kinesis video stream.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::freeKinesisVideoStream(jlong streamHandle) +{ + STATUS retStatus = STATUS_SUCCESS; + STREAM_HANDLE handle = (STREAM_HANDLE) streamHandle; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::freeKinesisVideoStream(&handle))) + { + DLOGE("Failed to free kinesis video stream with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to free kinesis video stream.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::getKinesisVideoMetrics(jobject kinesisVideoMetrics) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (NULL == kinesisVideoMetrics) + { + DLOGE("KinesisVideoMetrics object is null"); + throwNativeException(env, EXCEPTION_NAME, "KinesisVideoMetrics object is null.", STATUS_NULL_ARG); + return; + } + + ClientMetrics metrics; + metrics.version = CLIENT_METRICS_CURRENT_VERSION; + + if (STATUS_FAILED(retStatus = ::getKinesisVideoMetrics(mClientHandle, &metrics))) + { + DLOGE("Failed to get KinesisVideoMetrics with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to get KinesisVideoMetrics.", retStatus); + return; + } + + //get the class + jclass metricsClass = env->GetObjectClass(kinesisVideoMetrics); + if (metricsClass == NULL){ + DLOGE("Failed to get metrics class object"); + throwNativeException(env, EXCEPTION_NAME, "Failed to get metrics class object.", STATUS_INVALID_OPERATION); + return; + } + + // Set the Java object + jmethodID setterMethodId = env->GetMethodID(metricsClass, "setMetrics", "(JJJJJJ)V"); + if (setterMethodId == NULL) + { + DLOGE("Failed to get the setter method id."); + throwNativeException(env, EXCEPTION_NAME, "Failed to get setter method id.", STATUS_INVALID_OPERATION); + return; + } + + // call the setter method + env->CallVoidMethod(kinesisVideoMetrics, + setterMethodId, + metrics.contentStoreSize, + metrics.contentStoreAllocatedSize, + metrics.contentStoreAvailableSize, + metrics.totalContentViewsSize, + metrics.totalFrameRate, + metrics.totalTransferRate); +} + +void KinesisVideoClientWrapper::getKinesisVideoStreamMetrics(jlong streamHandle, jobject kinesisVideoStreamMetrics) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64 , (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (NULL == kinesisVideoStreamMetrics) + { + DLOGE("KinesisVideoStreamMetrics object is null"); + throwNativeException(env, EXCEPTION_NAME, "KinesisVideoStreamMetrics object is null.", STATUS_NULL_ARG); + return; + } + + StreamMetrics metrics; + metrics.version = STREAM_METRICS_CURRENT_VERSION; + + if (STATUS_FAILED(retStatus = ::getKinesisVideoStreamMetrics(streamHandle, &metrics))) + { + DLOGE("Failed to get StreamMetrics with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to get StreamMetrics.", retStatus); + return; + } + + //get the class + jclass metricsClass = env->GetObjectClass(kinesisVideoStreamMetrics); + if (metricsClass == NULL){ + DLOGE("Failed to get metrics class object"); + throwNativeException(env, EXCEPTION_NAME, "Failed to get metrics class object.", STATUS_INVALID_OPERATION); + return; + } + + // Set the Java object + jmethodID setterMethodId = env->GetMethodID(metricsClass, "setMetrics", "(JJJJDJ)V"); + if (setterMethodId == NULL) + { + DLOGE("Failed to get the setter method id."); + throwNativeException(env, EXCEPTION_NAME, "Failed to get setter method id.", STATUS_INVALID_OPERATION); + return; + } + + // call the setter method + env->CallVoidMethod(kinesisVideoStreamMetrics, + setterMethodId, + metrics.overallViewSize, + metrics.currentViewSize, + metrics.overallViewDuration, + metrics.currentViewDuration, + metrics.currentFrameRate, + metrics.currentTransferRate); +} + +STREAM_HANDLE KinesisVideoClientWrapper::createKinesisVideoStream(jobject streamInfo) +{ + STATUS retStatus = STATUS_SUCCESS; + STREAM_HANDLE streamHandle = INVALID_STREAM_HANDLE_VALUE; + UINT32 i; + JNIEnv *env; + StreamInfo kinesisVideoStreamInfo; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Convert the StreamInfo object + MEMSET(&kinesisVideoStreamInfo, 0x00, SIZEOF(kinesisVideoStreamInfo)); + if (!setStreamInfo(env, streamInfo, &kinesisVideoStreamInfo)) + { + DLOGE("Failed converting stream info object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting stream info object.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + if (STATUS_FAILED(retStatus = ::createKinesisVideoStream(mClientHandle, &kinesisVideoStreamInfo, &streamHandle))) + { + DLOGE("Failed to create a stream with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to create a stream.", retStatus); + goto CleanUp; + } + +CleanUp: + + // Release the Segment UUID memory if any + if (kinesisVideoStreamInfo.streamCaps.segmentUuid != NULL) { + MEMFREE(kinesisVideoStreamInfo.streamCaps.segmentUuid); + kinesisVideoStreamInfo.streamCaps.segmentUuid = NULL; + } + + // Release the temporary memory allocated for cpd + for (i = 0; i < kinesisVideoStreamInfo.streamCaps.trackInfoCount; ++i) { + if (kinesisVideoStreamInfo.streamCaps.trackInfoList[i].codecPrivateData != NULL) { + MEMFREE(kinesisVideoStreamInfo.streamCaps.trackInfoList[i].codecPrivateData); + kinesisVideoStreamInfo.streamCaps.trackInfoList[i].codecPrivateData = NULL; + kinesisVideoStreamInfo.streamCaps.trackInfoList[i].codecPrivateDataSize = 0; + } + } + if (kinesisVideoStreamInfo.streamCaps.trackInfoList != NULL) { + MEMFREE(kinesisVideoStreamInfo.streamCaps.trackInfoList); + kinesisVideoStreamInfo.streamCaps.trackInfoList = NULL; + kinesisVideoStreamInfo.streamCaps.trackInfoCount = 0; + } + + releaseTags(kinesisVideoStreamInfo.tags); + + return streamHandle; +} + +SyncMutex& KinesisVideoClientWrapper::getSyncLock() +{ + return mSyncLock; +} + +void KinesisVideoClientWrapper::putKinesisVideoFrame(jlong streamHandle, jobject kinesisVideoFrame) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (kinesisVideoFrame == NULL) + { + DLOGE("Invalid kinesis video frame."); + throwNativeException(env, EXCEPTION_NAME, "Kinesis video frame is null.", STATUS_INVALID_OPERATION); + return; + } + + // Convert the KinesisVideoFrame object + Frame frame; + if (!setFrame(env, kinesisVideoFrame, &frame)) + { + DLOGE("Failed converting frame object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting frame object.", STATUS_INVALID_OPERATION); + return; + } + + PStreamInfo pStreamInfo; + UINT32 zeroCount = 0; + ::kinesisVideoStreamGetStreamInfo(streamHandle, &pStreamInfo); + + if ((pStreamInfo->streamCaps.nalAdaptationFlags & NAL_ADAPTATION_ANNEXB_NALS) != NAL_ADAPTATION_FLAG_NONE) { + // In some devices encoder would generate annexb frames with more than 3 trailing zeros + // which is not allowed by AnnexB specification + while (frame.size > zeroCount) { + if (frame.frameData[frame.size - 1 - zeroCount] != 0) { + break; + } else { + zeroCount++; + } + } + + // Only remove zeros when zero count is more than 2 + if (zeroCount > 2) { + frame.size -= zeroCount; + } + } + + if (STATUS_FAILED(retStatus = ::putKinesisVideoFrame(streamHandle, &frame))) + { + DLOGE("Failed to put a frame with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to put a frame into the stream.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::putKinesisVideoFragmentMetadata(jlong streamHandle, jstring metadataName, jstring metadataValue, jboolean persistent) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (metadataName == NULL || metadataValue == NULL) + { + DLOGE("metadataName or metadataValue is NULL"); + throwNativeException(env, EXCEPTION_NAME, "metadataName or metadataValue is NULL.", STATUS_INVALID_OPERATION); + return; + } + + // Convert the jstring to PCHAR + PCHAR pMetadataNameStr = (PCHAR) env->GetStringUTFChars(metadataName, NULL); + PCHAR pMetadataValueStr = (PCHAR) env->GetStringUTFChars(metadataValue, NULL); + + + // Call the API + retStatus = ::putKinesisVideoFragmentMetadata(streamHandle, pMetadataNameStr, pMetadataValueStr, persistent == JNI_TRUE); + + + // Release the string + env->ReleaseStringUTFChars(metadataName, pMetadataNameStr); + env->ReleaseStringUTFChars(metadataValue, pMetadataValueStr); + + if (STATUS_FAILED(retStatus)) + { + DLOGE("Failed to put a metadata with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to put a metadata into the stream.", retStatus); + return; + } + +} + +void KinesisVideoClientWrapper::getKinesisVideoStreamData(jlong streamHandle, jlong uploadHandle, jobject dataBuffer, jint offset, jint length, jobject readResult) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + UINT32 filledSize = 0, bufferSize = 0; + PBYTE pBuffer = NULL; + jboolean isEos = JNI_FALSE; + jclass readResultClass; + jmethodID setterMethodId; + + if (NULL == readResult) + { + DLOGE("NULL ReadResult object"); + throwNativeException(env, EXCEPTION_NAME, "NULL ReadResult object is passsed.", STATUS_NULL_ARG); + goto CleanUp; + } + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + if (dataBuffer == NULL) + { + DLOGE("Invalid buffer object."); + throwNativeException(env, EXCEPTION_NAME, "Invalid buffer object.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Convert the buffer of stream data to get + if (!setStreamDataBuffer(env, dataBuffer, offset, &pBuffer)) + { + DLOGE("Failed converting kinesis video stream data buffer object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting kinesis video stream data buffer object.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + retStatus = ::getKinesisVideoStreamData(streamHandle, uploadHandle, pBuffer, (UINT32) length, &filledSize); + if (STATUS_SUCCESS != retStatus && STATUS_AWAITING_PERSISTED_ACK != retStatus + && STATUS_UPLOAD_HANDLE_ABORTED != retStatus + && STATUS_NO_MORE_DATA_AVAILABLE != retStatus && STATUS_END_OF_STREAM != retStatus) + { + char errMessage[256]; + SNPRINTF(errMessage, 256, "Failed to get data from the stream 0x%016" PRIx64 " with uploadHandle %" PRIu64 , (UINT64) streamHandle, (UINT64) uploadHandle); + DLOGE("Failed to get data from the stream with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, errMessage, retStatus); + goto CleanUp; + } + + if (STATUS_END_OF_STREAM == retStatus || STATUS_UPLOAD_HANDLE_ABORTED == retStatus) { + isEos = JNI_TRUE; + } + + // Get the class + readResultClass = env->GetObjectClass(readResult); + if (readResultClass == NULL){ + DLOGE("Failed to get ReadResult class object"); + throwNativeException(env, EXCEPTION_NAME, "Failed to get ReadResult class object.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Get the Java method id + setterMethodId = env->GetMethodID(readResultClass, "setReadResult", "(IZ)V"); + if (setterMethodId == NULL) + { + DLOGE("Failed to get the setter method id."); + throwNativeException(env, EXCEPTION_NAME, "Failed to get setter method id.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Call the setter method + env->CallVoidMethod(readResult, + setterMethodId, + filledSize, + isEos); + +CleanUp: + + if (!releaseStreamDataBuffer(env, dataBuffer, offset, pBuffer)) + { + DLOGE("Failed releasing kinesis video stream data buffer object."); + throwNativeException(env, EXCEPTION_NAME, "Failed releasing kinesis video stream data buffer object.", STATUS_INVALID_OPERATION); + } +} + +void KinesisVideoClientWrapper::kinesisVideoStreamFragmentAck(jlong streamHandle, jlong uploadHandle, jobject fragmentAck) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + FragmentAck ack; + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (fragmentAck == NULL) + { + DLOGE("Invalid fragment ack"); + throwNativeException(env, EXCEPTION_NAME, "Invalid fragment ack.", STATUS_INVALID_OPERATION); + return; + } + + // Convert the KinesisVideoFrame object + if (!setFragmentAck(env, fragmentAck, &ack)) + { + DLOGE("Failed converting frame object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting fragment ack object.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::kinesisVideoStreamFragmentAck(streamHandle, uploadHandle, &ack))) + { + DLOGE("Failed to report a fragment ack with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to report a fragment ack.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::kinesisVideoStreamParseFragmentAck(jlong streamHandle, jlong uploadHandle, jstring ack) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + return; + } + + if (ack == NULL) + { + DLOGE("Invalid ack"); + throwNativeException(env, EXCEPTION_NAME, "Invalid ack.", STATUS_INVALID_OPERATION); + return; + } + + // Convert the jstring to PCHAR + PCHAR pAckStr = (PCHAR) env->GetStringUTFChars(ack, NULL); + + // Call the API + retStatus = ::kinesisVideoStreamParseFragmentAck(streamHandle, uploadHandle, pAckStr, 0); + + // Release the string + env->ReleaseStringUTFChars(ack, pAckStr); + + if (STATUS_FAILED(retStatus)) + { + DLOGE("Failed to parse a fragment ack with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to parse a fragment ack.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::streamFormatChanged(jlong streamHandle, jobject codecPrivateData, jlong trackId) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + UINT32 bufferSize = 0; + PBYTE pBuffer = NULL; + BOOL releaseBuffer = FALSE; + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + if (!IS_VALID_STREAM_HANDLE(streamHandle)) + { + DLOGE("Invalid stream handle 0x%016" PRIx64, (UINT64) streamHandle); + throwNativeException(env, EXCEPTION_NAME, "Invalid stream handle.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Get the codec private data byte buffer - null object has a special semmantic of clearing the CPD + if (codecPrivateData != NULL) { + bufferSize = (UINT32) env->GetArrayLength((jbyteArray) codecPrivateData); + if (NULL == (pBuffer = (PBYTE) env->GetByteArrayElements((jbyteArray) codecPrivateData, NULL))) + { + DLOGE("Failed getting byte buffer from the java array."); + throwNativeException(env, EXCEPTION_NAME, "Failed getting byte buffer from the java array.", STATUS_INVALID_OPERATION); + goto CleanUp; + } + + // Make sure the buffer is released at the end + releaseBuffer = TRUE; + } else { + pBuffer = NULL; + bufferSize = 0; + } + + if (STATUS_FAILED(retStatus = ::kinesisVideoStreamFormatChanged(streamHandle, bufferSize, pBuffer, trackId))) + { + DLOGE("Failed to set the stream format with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to set the stream format.", retStatus); + goto CleanUp; + } + +CleanUp: + + if (releaseBuffer) + { + env->ReleaseByteArrayElements((jbyteArray) codecPrivateData, (jbyte*) pBuffer, JNI_ABORT); + } +} + +void KinesisVideoClientWrapper::describeStreamResult(jlong streamHandle, jint httpStatusCode, jobject streamDescription) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + StreamDescription streamDesc; + PStreamDescription pStreamDesc = NULL; + if (NULL != streamDescription) { + if (!setStreamDescription(env, streamDescription, &streamDesc)) { + DLOGE("Failed converting stream description object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting stream description object.", + STATUS_INVALID_OPERATION); + return; + } + + // Assign the converted object address. + pStreamDesc = &streamDesc; + } + + if (STATUS_FAILED(retStatus = ::describeStreamResultEvent(streamHandle, (SERVICE_CALL_RESULT) httpStatusCode, pStreamDesc))) + { + DLOGE("Failed to describe stream result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to describe stream result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::kinesisVideoStreamTerminated(jlong streamHandle, jlong uploadHandle, jint httpStatusCode) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::kinesisVideoStreamTerminated(streamHandle, uploadHandle, (SERVICE_CALL_RESULT) httpStatusCode))) + { + DLOGE("Failed to submit stream terminated event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to submit stream terminated event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::createStreamResult(jlong streamHandle, jint httpStatusCode, jstring streamArn) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + PCHAR pStreamArn = NULL; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (streamArn != NULL) { + pStreamArn = (PCHAR) env->GetStringUTFChars(streamArn, NULL); + } + + retStatus = ::createStreamResultEvent(streamHandle, (SERVICE_CALL_RESULT) httpStatusCode, pStreamArn); + + // Ensure we release the string + if (pStreamArn != NULL) { + env->ReleaseStringUTFChars(streamArn, pStreamArn); + } + + if (STATUS_FAILED(retStatus)) + { + DLOGE("Failed to create stream result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to create stream result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::putStreamResult(jlong streamHandle, jint httpStatusCode, jlong clientStreamHandle) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::putStreamResultEvent(streamHandle, (SERVICE_CALL_RESULT) httpStatusCode, (UINT64) clientStreamHandle))) + { + DLOGE("Failed to put stream result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to put stream result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::tagResourceResult(jlong customData, jint httpStatusCode) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::tagResourceResultEvent(customData, (SERVICE_CALL_RESULT) httpStatusCode))) + { + DLOGE("Failed on tag resource result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed on tag resource result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::getStreamingEndpointResult(jlong streamHandle, jint httpStatusCode, jstring streamingEndpoint) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + CHAR pEndpoint[MAX_URI_CHAR_LEN + 1]; + if (!setStreamingEndpoint(env, streamingEndpoint, pEndpoint)) + { + DLOGE("Failed converting streaming endpoint object."); + throwNativeException(env, EXCEPTION_NAME, "Failed converting streaming endpoint object.", STATUS_INVALID_OPERATION); + return; + } + + if (STATUS_FAILED(retStatus = ::getStreamingEndpointResultEvent(streamHandle, (SERVICE_CALL_RESULT) httpStatusCode, pEndpoint))) + { + DLOGE("Failed to get streaming endpoint result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to get streaming endpoint result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::getStreamingTokenResult(jlong streamHandle, jint httpStatusCode, jbyteArray streamingToken, jint tokenSize, jlong expiration) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (tokenSize > MAX_AUTH_LEN) { + DLOGE("Invalid token size"); + throwNativeException(env, EXCEPTION_NAME, "Invalid token size", STATUS_INVALID_OPERATION); + return; + } + + BYTE pToken[MAX_AUTH_LEN]; + env->GetByteArrayRegion(streamingToken, 0, tokenSize, (jbyte *) pToken); + + if (STATUS_FAILED(retStatus = ::getStreamingTokenResultEvent(streamHandle, + (SERVICE_CALL_RESULT) httpStatusCode, + pToken, + (UINT32) tokenSize, + (UINT64) expiration))) + { + DLOGE("Failed to get streaming token result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to get streaming token result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::createDeviceResult(jlong clientHandle, jint httpStatusCode, jstring deviceArn) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + PCHAR pDeviceArn = NULL; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (deviceArn != NULL) { + pDeviceArn = (PCHAR) env->GetStringUTFChars(deviceArn, NULL); + } + + retStatus = ::createDeviceResultEvent(clientHandle, (SERVICE_CALL_RESULT) httpStatusCode, pDeviceArn); + + // Ensure we release the string + if (pDeviceArn != NULL) { + env->ReleaseStringUTFChars(deviceArn, pDeviceArn); + } + + if (STATUS_FAILED(retStatus)) + { + DLOGE("Failed to create device result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed to create device result event.", retStatus); + return; + } +} + +void KinesisVideoClientWrapper::deviceCertToTokenResult(jlong clientHandle, jint httpStatusCode, jbyteArray token, jint tokenSize, jlong expiration) +{ + STATUS retStatus = STATUS_SUCCESS; + JNIEnv *env; + mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + + if (!IS_VALID_CLIENT_HANDLE(mClientHandle)) + { + DLOGE("Invalid client object"); + throwNativeException(env, EXCEPTION_NAME, "Invalid call after the client is freed.", STATUS_INVALID_OPERATION); + return; + } + + if (tokenSize > MAX_AUTH_LEN) { + DLOGE("Invalid token size"); + throwNativeException(env, EXCEPTION_NAME, "Invalid token size", STATUS_INVALID_OPERATION); + return; + } + + BYTE pToken[MAX_AUTH_LEN]; + env->GetByteArrayRegion(token, 0, tokenSize, (jbyte *) pToken); + + if (STATUS_FAILED(retStatus = ::deviceCertToTokenResultEvent(clientHandle, + (SERVICE_CALL_RESULT) httpStatusCode, + pToken, + (UINT32) tokenSize, + (UINT64) expiration))) + { + DLOGE("Failed the deviceCertToToken result event with status code 0x%08x", retStatus); + throwNativeException(env, EXCEPTION_NAME, "Failed the deviceCertToToken result event.", retStatus); + return; + } +} + +BOOL KinesisVideoClientWrapper::setCallbacks(JNIEnv* env, jobject thiz) +{ + CHECK(env != NULL && thiz != NULL); + + // The customData will point to this object + mClientCallbacks.customData = POINTER_TO_HANDLE(this); + + // Need the current version of the structure + mClientCallbacks.version = CALLBACKS_CURRENT_VERSION; + + // Set the function pointers + mClientCallbacks.getCurrentTimeFn = getCurrentTimeFunc; + mClientCallbacks.getRandomNumberFn = getRandomNumberFunc; + mClientCallbacks.createMutexFn = createMutexFunc; + mClientCallbacks.lockMutexFn = lockMutexFunc; + mClientCallbacks.unlockMutexFn = unlockMutexFunc; + mClientCallbacks.tryLockMutexFn = tryLockMutexFunc; + mClientCallbacks.freeMutexFn = freeMutexFunc; + mClientCallbacks.createConditionVariableFn = createConditionVariableFunc; + mClientCallbacks.signalConditionVariableFn = signalConditionVariableFunc; + mClientCallbacks.broadcastConditionVariableFn = broadcastConditionVariableFunc; + mClientCallbacks.waitConditionVariableFn = waitConditionVariableFunc; + mClientCallbacks.freeConditionVariableFn = freeConditionVariableFunc; + mClientCallbacks.getDeviceCertificateFn = getDeviceCertificateFunc; + mClientCallbacks.getSecurityTokenFn = getSecurityTokenFunc; + mClientCallbacks.getDeviceFingerprintFn = getDeviceFingerprintFunc; + mClientCallbacks.streamUnderflowReportFn = streamUnderflowReportFunc; + mClientCallbacks.storageOverflowPressureFn = storageOverflowPressureFunc; + mClientCallbacks.streamLatencyPressureFn = streamLatencyPressureFunc; + mClientCallbacks.streamConnectionStaleFn = streamConnectionStaleFunc; + mClientCallbacks.fragmentAckReceivedFn = fragmentAckReceivedFunc; + mClientCallbacks.droppedFrameReportFn = droppedFrameReportFunc; + mClientCallbacks.bufferDurationOverflowPressureFn = bufferDurationOverflowPressureFunc; + mClientCallbacks.droppedFragmentReportFn = droppedFragmentReportFunc; + mClientCallbacks.streamErrorReportFn = streamErrorReportFunc; + mClientCallbacks.streamDataAvailableFn = streamDataAvailableFunc; + mClientCallbacks.streamReadyFn = streamReadyFunc; + mClientCallbacks.streamClosedFn = streamClosedFunc; + mClientCallbacks.createStreamFn = createStreamFunc; + mClientCallbacks.describeStreamFn = describeStreamFunc; + mClientCallbacks.getStreamingEndpointFn = getStreamingEndpointFunc; + mClientCallbacks.getStreamingTokenFn = getStreamingTokenFunc; + mClientCallbacks.putStreamFn = putStreamFunc; + mClientCallbacks.tagResourceFn = tagResourceFunc; + mClientCallbacks.clientReadyFn = clientReadyFunc; + mClientCallbacks.createDeviceFn = createDeviceFunc; + mClientCallbacks.deviceCertToTokenFn = deviceCertToTokenFunc; + mClientCallbacks.logPrintFn = logPrintFunc; + + // TODO: Currently we set the shutdown callbacks to NULL. + // We need to expose these in the near future + mClientCallbacks.clientShutdownFn = NULL; + mClientCallbacks.streamShutdownFn = NULL; + + // Extract the method IDs for the callbacks and set a global reference + jclass thizCls = env->GetObjectClass(thiz); + if (thizCls == NULL) { + DLOGE("Failed to get the object class for the JNI object."); + return FALSE; + } + + // Setup the environment and the callbacks + if (NULL == (mGlobalJniObjRef = env->NewGlobalRef(thiz))) { + DLOGE("Failed to create a global reference for the JNI object."); + return FALSE; + } + + // Extract the method IDs + mGetDeviceCertificateMethodId = env->GetMethodID(thizCls, "getDeviceCertificate", "()Lcom/amazonaws/kinesisvideo/producer/AuthInfo;"); + if (mGetDeviceCertificateMethodId == NULL) { + DLOGE("Couldn't find method id getDeviceCertificate"); + return FALSE; + } + + mGetSecurityTokenMethodId = env->GetMethodID(thizCls, "getSecurityToken", "()Lcom/amazonaws/kinesisvideo/producer/AuthInfo;"); + if (mGetSecurityTokenMethodId == NULL) { + DLOGE("Couldn't find method id getSecurityToken"); + return FALSE; + } + + mGetDeviceFingerprintMethodId = env->GetMethodID(thizCls, "getDeviceFingerprint", "()Ljava/lang/String;"); + if (mGetDeviceFingerprintMethodId == NULL) { + DLOGE("Couldn't find method id getDeviceFingerprint"); + return FALSE; + } + + mStreamUnderflowReportMethodId = env->GetMethodID(thizCls, "streamUnderflowReport", "(J)V"); + if (mStreamUnderflowReportMethodId == NULL) { + DLOGE("Couldn't find method id streamUnderflowReport"); + return FALSE; + } + + mStorageOverflowPressureMethodId = env->GetMethodID(thizCls, "storageOverflowPressure", "(J)V"); + if (mStorageOverflowPressureMethodId == NULL) { + DLOGE("Couldn't find method id storageOverflowPressure"); + return FALSE; + } + + mStreamLatencyPressureMethodId = env->GetMethodID(thizCls, "streamLatencyPressure", "(JJ)V"); + if (mStreamLatencyPressureMethodId == NULL) { + DLOGE("Couldn't find method id streamLatencyPressure"); + return FALSE; + } + + mStreamConnectionStaleMethodId = env->GetMethodID(thizCls, "streamConnectionStale", "(JJ)V"); + if (mStreamConnectionStaleMethodId == NULL) { + DLOGE("Couldn't find method id streamConnectionStale"); + return FALSE; + } + + mFragmentAckReceivedMethodId = env->GetMethodID(thizCls, "fragmentAckReceived", "(JJLcom/amazonaws/kinesisvideo/producer/KinesisVideoFragmentAck;)V"); + if (mFragmentAckReceivedMethodId == NULL) { + DLOGE("Couldn't find method id fragmentAckReceived"); + return FALSE; + } + + mDroppedFrameReportMethodId = env->GetMethodID(thizCls, "droppedFrameReport", "(JJ)V"); + if (mDroppedFrameReportMethodId == NULL) { + DLOGE("Couldn't find method id droppedFrameReport"); + return FALSE; + } + + mBufferDurationOverflowPressureMethodId = env->GetMethodID(thizCls, "bufferDurationOverflowPressure", "(JJ)V"); + if (mBufferDurationOverflowPressureMethodId == NULL) { + DLOGE("Couldn't find method id bufferDurationOverflowPressure"); + return FALSE; + } + + mDroppedFragmentReportMethodId = env->GetMethodID(thizCls, "droppedFragmentReport", "(JJ)V"); + if (mDroppedFragmentReportMethodId == NULL) { + DLOGE("Couldn't find method id droppedFragmentReport"); + return FALSE; + } + + mStreamErrorReportMethodId = env->GetMethodID(thizCls, "streamErrorReport", "(JJJJ)V"); + if (mStreamErrorReportMethodId == NULL) { + DLOGE("Couldn't find method id streamErrorReport"); + return FALSE; + } + + mStreamDataAvailableMethodId = env->GetMethodID(thizCls, "streamDataAvailable", "(JLjava/lang/String;JJJ)V"); + if (mStreamDataAvailableMethodId == NULL) { + DLOGE("Couldn't find method id streamDataAvailable"); + return FALSE; + } + + mStreamReadyMethodId = env->GetMethodID(thizCls, "streamReady", "(J)V"); + if (mStreamReadyMethodId == NULL) { + DLOGE("Couldn't find method id streamReady"); + return FALSE; + } + + mStreamClosedMethodId = env->GetMethodID(thizCls, "streamClosed", "(JJ)V"); + if (mStreamClosedMethodId == NULL) { + DLOGE("Couldn't find method id streamClosed"); + return FALSE; + } + + mCreateStreamMethodId = env->GetMethodID(thizCls, "createStream", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJJ[BIJ)I"); + if (mCreateStreamMethodId == NULL) { + DLOGE("Couldn't find method id createStream"); + return FALSE; + } + + mDescribeStreamMethodId = env->GetMethodID(thizCls, "describeStream", "(Ljava/lang/String;JJ[BIJ)I"); + if (mDescribeStreamMethodId == NULL) { + DLOGE("Couldn't find method id describeStream"); + return FALSE; + } + + mGetStreamingEndpointMethodId = env->GetMethodID(thizCls, "getStreamingEndpoint", "(Ljava/lang/String;Ljava/lang/String;JJ[BIJ)I"); + if (mGetStreamingEndpointMethodId == NULL) { + DLOGE("Couldn't find method id getStreamingEndpoint"); + return FALSE; + } + + mGetStreamingTokenMethodId = env->GetMethodID(thizCls, "getStreamingToken", "(Ljava/lang/String;JJ[BIJ)I"); + if (mGetStreamingTokenMethodId == NULL) { + DLOGE("Couldn't find method id getStreamingToken"); + return FALSE; + } + + mPutStreamMethodId = env->GetMethodID(thizCls, "putStream", "(Ljava/lang/String;Ljava/lang/String;JZZLjava/lang/String;JJ[BIJ)I"); + if (mPutStreamMethodId == NULL) { + DLOGE("Couldn't find method id putStream"); + return FALSE; + } + + mTagResourceMethodId = env->GetMethodID(thizCls, "tagResource", "(Ljava/lang/String;[Lcom/amazonaws/kinesisvideo/producer/Tag;JJ[BIJ)I"); + if (mTagResourceMethodId == NULL) { + DLOGE("Couldn't find method id tagResource"); + return FALSE; + } + + mClientReadyMethodId = env->GetMethodID(thizCls, "clientReady", "(J)V"); + if (mClientReadyMethodId == NULL) { + DLOGE("Couldn't find method id clientReady"); + return FALSE; + } + + mCreateDeviceMethodId = env->GetMethodID(thizCls, "createDevice", "(Ljava/lang/String;JJ[BIJ)I"); + if (mCreateDeviceMethodId == NULL) { + DLOGE("Couldn't find method id createDevice"); + return FALSE; + } + + mDeviceCertToTokenMethodId = env->GetMethodID(thizCls, "deviceCertToToken", "(Ljava/lang/String;JJ[BIJ)I"); + if (mDeviceCertToTokenMethodId == NULL) { + DLOGE("Couldn't find method id deviceCertToToken"); + return FALSE; + } + + mLogPrintMethodId = env->GetMethodID(thizCls, "logPrint", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + if (mLogPrintMethodId == NULL) { + DLOGE("Couldn't find method id logPrint"); + return FALSE; + } + + return TRUE; +} + +////////////////////////////////////////////////////////////////////////////////////// +// Static callbacks definitions implemented here +////////////////////////////////////////////////////////////////////////////////////// +UINT64 KinesisVideoClientWrapper::getCurrentTimeFunc(UINT64 customData) +{ + DLOGS("TID 0x%016" PRIx64 " getCurrentTimeFunc called.", GETTID()); + UNUSED_PARAM(customData); + +#if defined _WIN32 || defined _WIN64 + // GETTIME implementation is the same as in Java for Windows platforms + return GETTIME(); +#elif defined __MACH__ || defined __CYGWIN__ + // GETTIME implementation is the same as in Java for Mac OSx platforms + return GETTIME(); +#else + timeval nowTime; + if (0 != gettimeofday(&nowTime, NULL)) { + return 0; + } + + // The precision needs to be on a 100th nanosecond resolution + return (UINT64)nowTime.tv_sec * HUNDREDS_OF_NANOS_IN_A_SECOND + (UINT64)nowTime.tv_usec * HUNDREDS_OF_NANOS_IN_A_MICROSECOND; +#endif +} + +UINT32 KinesisVideoClientWrapper::getRandomNumberFunc(UINT64 customData) +{ + DLOGS("TID 0x%016" PRIx64 " getRandomNumberFunc called.", GETTID()); + UNUSED_PARAM(customData); + return RAND(); +} + +MUTEX KinesisVideoClientWrapper::createMutexFunc(UINT64 customData, BOOL reentant) +{ + DLOGS("TID 0x%016" PRIx64 " createMutexFunc called.", GETTID()); + UNUSED_PARAM(customData); + return MUTEX_CREATE(reentant); +} + +VOID KinesisVideoClientWrapper::lockMutexFunc(UINT64 customData, MUTEX mutex) +{ + DLOGS("TID 0x%016" PRIx64 " lockMutexFunc called.", GETTID()); + UNUSED_PARAM(customData); + return MUTEX_LOCK(mutex); +} + +VOID KinesisVideoClientWrapper::unlockMutexFunc(UINT64 customData, MUTEX mutex) +{ + DLOGS("TID 0x%016" PRIx64 " unlockMutexFunc called.", GETTID()); + UNUSED_PARAM(customData); + return MUTEX_UNLOCK(mutex); +} + +BOOL KinesisVideoClientWrapper::tryLockMutexFunc(UINT64 customData, MUTEX mutex) +{ + DLOGS("TID 0x%016" PRIx64 " tryLockMutexFunc called.", GETTID()); + UNUSED_PARAM(customData); + return MUTEX_TRYLOCK(mutex); +} + +VOID KinesisVideoClientWrapper::freeMutexFunc(UINT64 customData, MUTEX mutex) +{ + DLOGS("TID 0x%016" PRIx64 " freeMutexFunc called.", GETTID()); + UNUSED_PARAM(customData); + return MUTEX_FREE(mutex); +} + +CVAR KinesisVideoClientWrapper::createConditionVariableFunc(UINT64 customData) +{ + DLOGS("TID 0x%016" PRIx64 " createConditionVariableFunc called.", GETTID()); + UNUSED_PARAM(customData); + return CVAR_CREATE(); +} + +STATUS KinesisVideoClientWrapper::signalConditionVariableFunc(UINT64 customData, CVAR cvar) +{ + DLOGS("TID 0x%016" PRIx64 " signalConditionVariableFunc called.", GETTID()); + UNUSED_PARAM(customData); + return CVAR_SIGNAL(cvar); +} + +STATUS KinesisVideoClientWrapper::broadcastConditionVariableFunc(UINT64 customData, CVAR cvar) +{ + DLOGS("TID 0x%016" PRIx64 " broadcastConditionVariableFunc called.", GETTID()); + UNUSED_PARAM(customData); + return CVAR_BROADCAST(cvar); +} + +STATUS KinesisVideoClientWrapper::waitConditionVariableFunc(UINT64 customData, CVAR cvar, MUTEX mutex, UINT64 timeout) +{ + DLOGS("TID 0x%016" PRIx64 " waitConditionVariableFunc called.", GETTID()); + UNUSED_PARAM(customData); + return CVAR_WAIT(cvar, mutex, timeout); +} + +VOID KinesisVideoClientWrapper::freeConditionVariableFunc(UINT64 customData, CVAR cvar) +{ + DLOGS("TID 0x%016" PRIx64 " freeConditionVariableFunc called.", GETTID()); + UNUSED_PARAM(customData); + return CVAR_FREE(cvar); +} + +////////////////////////////////////////////////////////////////////////////////////// +// Static callbacks definitions implemented in the Java layer +////////////////////////////////////////////////////////////////////////////////////// +STATUS KinesisVideoClientWrapper::getDeviceCertificateFunc(UINT64 customData, PBYTE* ppCert, PUINT32 pSize, PUINT64 pExpiration) +{ + DLOGS("TID 0x%016" PRIx64 " getDeviceCertificateFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL && ppCert != NULL && pSize != NULL && pExpiration != NULL); + + return pWrapper->getAuthInfo(pWrapper->mGetDeviceCertificateMethodId, ppCert, pSize, pExpiration); +} + +STATUS KinesisVideoClientWrapper::getSecurityTokenFunc(UINT64 customData, PBYTE* ppToken, PUINT32 pSize, PUINT64 pExpiration) +{ + DLOGS("TID 0x%016" PRIx64 " getSecurityTokenFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL && ppToken != NULL && pSize != NULL && pExpiration != NULL); + + return pWrapper->getAuthInfo(pWrapper->mGetSecurityTokenMethodId, ppToken, pSize, pExpiration); +} + +STATUS KinesisVideoClientWrapper::getDeviceFingerprintFunc(UINT64 customData, PCHAR* ppFingerprint) +{ + DLOGS("TID 0x%016" PRIx64 " getDeviceFingerprintFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL && ppFingerprint != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstr = NULL; + const jchar* bufferPtr = NULL; + UINT32 strLen; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstr = (jstring) env->CallObjectMethod(pWrapper->mGlobalJniObjRef, pWrapper->mGetDeviceFingerprintMethodId); + CHK_JVM_EXCEPTION(env); + + if (jstr != NULL) { + // Extract the bits from the byte buffer + bufferPtr = env->GetStringChars(jstr, NULL); + strLen = (UINT32)STRLEN((PCHAR) bufferPtr); + if (strLen >= MAX_AUTH_LEN) { + retStatus = STATUS_INVALID_ARG; + goto CleanUp; + } + + // Copy the bits + STRCPY((PCHAR) pWrapper->mAuthInfo.data, (PCHAR) bufferPtr); + pWrapper->mAuthInfo.type = AUTH_INFO_NONE; + pWrapper->mAuthInfo.size = strLen; + + *ppFingerprint = (PCHAR) pWrapper->mAuthInfo.data; + } else { + pWrapper->mAuthInfo.type = AUTH_INFO_NONE; + pWrapper->mAuthInfo.size = 0; + + // Set the returned values + *ppFingerprint = NULL; + } + +CleanUp: + + // Release the array object if allocated + if (bufferPtr != NULL) { + env->ReleaseStringChars(jstr, bufferPtr); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamUnderflowReportFunc(UINT64 customData, STREAM_HANDLE streamHandle) +{ + DLOGS("TID 0x%016" PRIx64 " streamUnderflowReportFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamUnderflowReportMethodId, streamHandle); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::storageOverflowPressureFunc(UINT64 customData, UINT64 remainingSize) +{ + DLOGS("TID 0x%016" PRIx64 " storageOverflowPressureFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStorageOverflowPressureMethodId, remainingSize); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamLatencyPressureFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 duration) +{ + DLOGS("TID 0x%016" PRIx64 " streamLatencyPressureFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamLatencyPressureMethodId, streamHandle, duration); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamConnectionStaleFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 duration) +{ + DLOGS("TID 0x%016" PRIx64 " streamConnectionStaleFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamConnectionStaleMethodId, streamHandle, duration); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::fragmentAckReceivedFunc(UINT64 customData, STREAM_HANDLE streamHandle, + UPLOAD_HANDLE upload_handle, PFragmentAck pFragmentAck) +{ + DLOGS("TID 0x%016" PRIx64 " fragmentAckReceivedFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrSequenceNum = NULL; + jobject ack = NULL; + jmethodID methodId = NULL; + jclass ackClass = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Get the class object for Ack + ackClass = env->FindClass("com/amazonaws/kinesisvideo/producer/KinesisVideoFragmentAck"); + CHK(ackClass != NULL, STATUS_INVALID_OPERATION); + + // Get the Ack constructor method id + methodId = env->GetMethodID(ackClass, "", "(IJLjava/lang/String;I)V"); + CHK(methodId != NULL, STATUS_INVALID_OPERATION); + + jstrSequenceNum = env->NewStringUTF(pFragmentAck->sequenceNumber); + CHK(jstrSequenceNum != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Create a new tag object + ack = env->NewObject(ackClass, + methodId, + (jint) pFragmentAck->ackType, + (jlong) pFragmentAck->timestamp, + jstrSequenceNum, + (jint) pFragmentAck->result); + CHK(ack != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mFragmentAckReceivedMethodId, streamHandle, upload_handle, ack); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::droppedFrameReportFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 frameTimecode) +{ + DLOGS("TID 0x%016" PRIx64 " droppedFrameReportFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mDroppedFrameReportMethodId, streamHandle, frameTimecode); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::bufferDurationOverflowPressureFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 remainingDuration){ + DLOGS("TID 0x%016" PRIx64 " bufferDurationOverflowPressureFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mBufferDurationOverflowPressureMethodId, streamHandle, remainingDuration); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::droppedFragmentReportFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 fragmentTimecode) +{ + DLOGS("TID 0x%016" PRIx64 " droppedFragmentReportFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mDroppedFragmentReportMethodId, streamHandle, fragmentTimecode); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamErrorReportFunc(UINT64 customData, STREAM_HANDLE streamHandle, + UPLOAD_HANDLE upload_handle, UINT64 fragmentTimecode, STATUS statusCode) +{ + DLOGS("TID 0x%016" PRIx64 " streamErrorReportFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamErrorReportMethodId, streamHandle, upload_handle, + fragmentTimecode, statusCode); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamReadyFunc(UINT64 customData, STREAM_HANDLE streamHandle) +{ + DLOGS("TID 0x%016" PRIx64 " streamReadyFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamReadyMethodId, streamHandle); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamClosedFunc(UINT64 customData, STREAM_HANDLE streamHandle, UINT64 uploadHandle) +{ + DLOGS("TID 0x%016" PRIx64 " streamClosedFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamClosedMethodId, streamHandle, uploadHandle); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::streamDataAvailableFunc(UINT64 customData, STREAM_HANDLE streamHandle, PCHAR streamName, UINT64 uploadHandle, UINT64 duration, UINT64 availableSize) +{ + DLOGS("TID 0x%016" PRIx64 " streamDataAvailableFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mStreamDataAvailableMethodId, streamHandle, NULL, uploadHandle, duration, availableSize); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::createStreamFunc(UINT64 customData, + PCHAR deviceName, + PCHAR streamName, + PCHAR contentType, + PCHAR kmsKeyId, + UINT64 retention, + PServiceCallContext pCallbackContext) +{ + DLOGS("TID 0x%016" PRIx64 " createStreamFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrDeviceName = NULL, jstrStreamName = NULL, jstrContentType = NULL, jstrKmsKeyId = NULL; + jbyteArray authByteArray = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstrDeviceName = env->NewStringUTF(deviceName); + jstrStreamName = env->NewStringUTF(streamName); + jstrContentType = env->NewStringUTF(contentType); + if (kmsKeyId != NULL) { + jstrKmsKeyId = env->NewStringUTF(kmsKeyId); + } + + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + if (jstrContentType == NULL || + jstrDeviceName == NULL || + jstrStreamName == NULL || + authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mCreateStreamMethodId, + jstrDeviceName, + jstrStreamName, + jstrContentType, + jstrKmsKeyId, + retention, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrDeviceName != NULL) { + env->DeleteLocalRef(jstrDeviceName); + } + + if (jstrStreamName != NULL) { + env->DeleteLocalRef(jstrStreamName); + } + + if (jstrContentType != NULL) { + env->DeleteLocalRef(jstrContentType); + } + + if (jstrKmsKeyId != NULL) { + env->DeleteLocalRef(jstrKmsKeyId); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::describeStreamFunc(UINT64 customData, + PCHAR streamName, + PServiceCallContext pCallbackContext) +{ + DLOGS("TID 0x%016" PRIx64 " describeStreamFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrStreamName = NULL; + jbyteArray authByteArray = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstrStreamName = env->NewStringUTF(streamName); + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + if (jstrStreamName == NULL || + authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mDescribeStreamMethodId, + jstrStreamName, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrStreamName != NULL) { + env->DeleteLocalRef(jstrStreamName); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::getStreamingEndpointFunc(UINT64 customData, + PCHAR streamName, + PCHAR apiName, + PServiceCallContext pCallbackContext) +{ + DLOGS("TID 0x%016" PRIx64 " getStreamingEndpointFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrStreamName = NULL; + jstring jstrApiName = NULL; + jbyteArray authByteArray = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstrStreamName = env->NewStringUTF(streamName); + jstrApiName = env->NewStringUTF(apiName); + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + if (jstrStreamName == NULL || + jstrApiName == NULL || + authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mGetStreamingEndpointMethodId, + jstrStreamName, + jstrApiName, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrStreamName != NULL) { + env->DeleteLocalRef(jstrStreamName); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::getStreamingTokenFunc(UINT64 customData, + PCHAR streamName, + STREAM_ACCESS_MODE accessMode, + PServiceCallContext pCallbackContext) +{ + DLOGS("TID 0x%016" PRIx64 " getStreamingTokenFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrStreamName = NULL; + jbyteArray authByteArray = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstrStreamName = env->NewStringUTF(streamName); + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + if (jstrStreamName == NULL || + authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mGetStreamingTokenMethodId, + jstrStreamName, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrStreamName != NULL) { + env->DeleteLocalRef(jstrStreamName); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::putStreamFunc(UINT64 customData, + PCHAR streamName, + PCHAR containerType, + UINT64 streamStartTime, + BOOL absoluteFragmentTimestamp, + BOOL ackRequired, + PCHAR streamingEndpoint, + PServiceCallContext pCallbackContext) +{ + DLOGS("TID 0x%016" PRIx64 " putStreamFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrStreamName = NULL, jstrContainerType = NULL, jstrStreamingEndpoint = NULL; + jbyteArray authByteArray = NULL; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jstrStreamName = env->NewStringUTF(streamName); + jstrContainerType = env->NewStringUTF(containerType); + jstrStreamingEndpoint = env->NewStringUTF(streamingEndpoint); + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + if (jstrStreamName == NULL || + jstrContainerType == NULL || + jstrStreamingEndpoint == NULL || + authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mPutStreamMethodId, + jstrStreamName, + jstrContainerType, + streamStartTime, + absoluteFragmentTimestamp == TRUE, + ackRequired == TRUE, + jstrStreamingEndpoint, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrStreamName != NULL) { + env->DeleteLocalRef(jstrStreamName); + } + + if (jstrContainerType != NULL) { + env->DeleteLocalRef(jstrContainerType); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::tagResourceFunc(UINT64 customData, + PCHAR streamArn, + UINT32 tagCount, + PTag tags, + PServiceCallContext pCallbackContext) +{ + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrStreamArn = NULL, jstrTagName = NULL, jstrTagValue = NULL; + jbyteArray authByteArray = NULL; + jobjectArray tagArray = NULL; + jobject tag = NULL; + jmethodID methodId = NULL; + jclass tagClass = NULL; + INT32 envState; + UINT32 i; + + DLOGS("TID 0x%016" PRIx64 " tagResourceFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Early return if no tags + CHK(tagCount != 0 && tags != NULL, STATUS_SUCCESS); + + // Get the ENV from the JavaVM and ensure we have a JVM thread + envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func to create a new string + jstrStreamArn = env->NewStringUTF(streamArn); + + // Allocate a new byte array + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + + // Get the class object for tags + tagClass = env->FindClass("com/amazonaws/kinesisvideo/producer/Tag"); + CHK(tagClass != NULL, STATUS_INVALID_OPERATION); + + // Get the Tag constructor method id + methodId = env->GetMethodID(tagClass, "", "(Ljava/lang/String;Ljava/lang/String;)V"); + CHK(methodId != NULL, STATUS_INVALID_OPERATION); + + // Allocate a new object array for tags + tagArray = env->NewObjectArray((jsize) tagCount, tagClass, NULL); + + if (jstrStreamArn == NULL || authByteArray == NULL || tagArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + for (i = 0; i < tagCount; i++) { + // Create the strings + jstrTagName = env->NewStringUTF(tags[i].name); + jstrTagValue = env->NewStringUTF(tags[i].value); + CHK(jstrTagName != NULL && jstrTagValue != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Create a new tag object + tag = env->NewObject(tagClass, methodId, jstrTagName, jstrTagValue); + CHK(tag != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Set the value of the array index + env->SetObjectArrayElement(tagArray, i, tag); + + // Remove the references to the constructed strings + env->DeleteLocalRef(jstrTagName); + env->DeleteLocalRef(jstrTagValue); + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mTagResourceMethodId, + jstrStreamArn, + tagArray, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrStreamArn != NULL) { + env->DeleteLocalRef(jstrStreamArn); + } + + if (authByteArray != NULL) { + env->DeleteLocalRef(authByteArray); + } + + if (tagArray != NULL) { + env->DeleteLocalRef(tagArray); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::getAuthInfo(jmethodID methodId, PBYTE* ppCert, PUINT32 pSize, PUINT64 pExpiration) +{ + // Get the ENV from the JavaVM + JNIEnv *env; + + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jbyteArray byteArray = NULL; + jobject jAuthInfoObj = NULL; + jbyte* bufferPtr = NULL; + jsize arrayLen = 0; + jclass authCls = NULL; + jint authType = 0; + jlong authExpiration = 0; + jmethodID authTypeMethodId = NULL; + jmethodID authDataMethodId = NULL; + jmethodID authExpirationMethodId = NULL; + + // Store this pointer so we can run the common macros + KinesisVideoClientWrapper *pWrapper = this; + + INT32 envState = mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + jAuthInfoObj = env->CallObjectMethod(mGlobalJniObjRef, methodId); + if (jAuthInfoObj == NULL) { + DLOGE("Failed to get the object for the AuthInfo object. methodId %s", methodId); + retStatus = STATUS_INVALID_ARG; + goto CleanUp; + } + + // Extract the method IDs for the auth object + authCls = env->GetObjectClass(jAuthInfoObj); + CHK_JVM_EXCEPTION(env); + if (authCls == NULL) { + DLOGE("Failed to get the object class for the AuthInfo object."); + retStatus = STATUS_INVALID_ARG; + goto CleanUp; + } + + // Extract the method ids + authTypeMethodId = env->GetMethodID(authCls, "getIntAuthType", "()I"); + CHK_JVM_EXCEPTION(env); + authDataMethodId = env->GetMethodID(authCls, "getData", "()[B"); + CHK_JVM_EXCEPTION(env); + authExpirationMethodId = env->GetMethodID(authCls, "getExpiration", "()J"); + CHK_JVM_EXCEPTION(env); + if (authTypeMethodId == NULL || authDataMethodId == NULL || authExpirationMethodId == NULL) { + DLOGE("Couldn't find methods in AuthType object"); + retStatus = STATUS_INVALID_ARG; + goto CleanUp; + } + + authType = env->CallIntMethod(jAuthInfoObj, authTypeMethodId); + CHK_JVM_EXCEPTION(env); + + authExpiration = env->CallLongMethod(jAuthInfoObj, authExpirationMethodId); + CHK_JVM_EXCEPTION(env); + + byteArray = (jbyteArray) env->CallObjectMethod(jAuthInfoObj, authDataMethodId); + CHK_JVM_EXCEPTION(env); + + if (byteArray != NULL) { + // Extract the bits from the byte buffer + bufferPtr = env->GetByteArrayElements(byteArray, NULL); + arrayLen = env->GetArrayLength(byteArray); + + if (arrayLen >= MAX_AUTH_LEN) { + retStatus = STATUS_INVALID_ARG; + goto CleanUp; + } + + // Copy the bits + MEMCPY(mAuthInfo.data, bufferPtr, arrayLen); + mAuthInfo.type = authInfoTypeFromInt((UINT32) authType); + mAuthInfo.expiration = (UINT64) authExpiration; + mAuthInfo.size = arrayLen; + + // Set the returned values + *ppCert = (PBYTE) &mAuthInfo.data; + *pSize = mAuthInfo.size; + *pExpiration = mAuthInfo.expiration; + } else { + mAuthInfo.type = AUTH_INFO_NONE; + mAuthInfo.size = 0; + mAuthInfo.expiration = 0; + + // Set the returned values + *ppCert = NULL; + *pSize = 0; + *pExpiration = 0; + } + +CleanUp: + + // Release the array object if allocated + if (byteArray != NULL) { + env->ReleaseByteArrayElements(byteArray, bufferPtr, 0); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::clientReadyFunc(UINT64 customData, CLIENT_HANDLE clientHandle) +{ + DLOGS("TID 0x%016" PRIx64 " clientReadyFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Get the ENV from the JavaVM + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + + INT32 envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func + env->CallVoidMethod(pWrapper->mGlobalJniObjRef, pWrapper->mClientReadyMethodId, (jlong) TO_WRAPPER_HANDLE(pWrapper)); + CHK_JVM_EXCEPTION(env); + +CleanUp: + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::createDeviceFunc(UINT64 customData, PCHAR deviceName, PServiceCallContext pCallbackContext) +{ + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrDeviceName = NULL; + jbyteArray authByteArray = NULL; + INT32 envState; + + DLOGS("TID 0x%016" PRIx64 " createDeviceFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Device name should be valid + CHK(deviceName != 0, STATUS_NULL_ARG); + + // Get the ENV from the JavaVM and ensure we have a JVM thread + envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func to create a new string + jstrDeviceName = env->NewStringUTF(deviceName); + + // Allocate a new byte array + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + + if (jstrDeviceName == NULL || authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mCreateDeviceMethodId, + jstrDeviceName, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrDeviceName != NULL) { + env->DeleteLocalRef(jstrDeviceName); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +STATUS KinesisVideoClientWrapper::deviceCertToTokenFunc(UINT64 customData, PCHAR deviceName, PServiceCallContext pCallbackContext) +{ + JNIEnv *env; + BOOL detached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrDeviceName = NULL; + jbyteArray authByteArray = NULL; + INT32 envState; + + DLOGS("TID 0x%016" PRIx64 " deviceCertToTokenFunc called.", GETTID()); + + KinesisVideoClientWrapper *pWrapper = FROM_WRAPPER_HANDLE(customData); + CHECK(pWrapper != NULL); + + // Device name should be valid + CHK(deviceName != 0, STATUS_NULL_ARG); + + // Get the ENV from the JavaVM and ensure we have a JVM thread + envState = pWrapper->mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + ATTACH_CURRENT_THREAD_TO_JVM(env); + + // Store the detached so we can detach the thread after the call + detached = TRUE; + } + + // Call the Java func to create a new string + jstrDeviceName = env->NewStringUTF(deviceName); + + // Allocate a new byte array + authByteArray = env->NewByteArray(pCallbackContext->pAuthInfo->size); + + if (jstrDeviceName == NULL || authByteArray == NULL) { + retStatus = STATUS_NOT_ENOUGH_MEMORY; + goto CleanUp; + } + + // Copy the bits into the managed array + env->SetByteArrayRegion(authByteArray, + 0, + pCallbackContext->pAuthInfo->size, + (const jbyte*) pCallbackContext->pAuthInfo->data); + + // Invoke the callback + retStatus = env->CallIntMethod(pWrapper->mGlobalJniObjRef, + pWrapper->mDeviceCertToTokenMethodId, + jstrDeviceName, + pCallbackContext->callAfter, + pCallbackContext->timeout, + authByteArray, + pCallbackContext->pAuthInfo->type, + pCallbackContext->customData + ); + + CHK_JVM_EXCEPTION(env); + +CleanUp: + + if (jstrDeviceName != NULL) { + env->DeleteLocalRef(jstrDeviceName); + } + + // Detach the thread if we have attached it to JVM + if (detached) { + pWrapper->mJvm->DetachCurrentThread(); + } + + return retStatus; +} + +AUTH_INFO_TYPE KinesisVideoClientWrapper::authInfoTypeFromInt(UINT32 authInfoType) +{ + switch (authInfoType) { + case 1: return AUTH_INFO_TYPE_CERT; + case 2: return AUTH_INFO_TYPE_STS; + case 3: return AUTH_INFO_NONE; + default: return AUTH_INFO_UNDEFINED; + } +} + +VOID KinesisVideoClientWrapper::logPrintFunc(UINT32 level, PCHAR tag, PCHAR fmt, ...) +{ + JNIEnv *env; + BOOL attached = FALSE; + STATUS retStatus = STATUS_SUCCESS; + jstring jstrTag = NULL, jstrFmt = NULL, jstrBuffer = NULL; + CHAR buffer[MAX_LOG_MESSAGE_LENGTH]; + va_list list; + INT32 envState; + + // Prevent infinite logging loops if mGlobalJniObjRef has already been freed + if (mGlobalJniObjRef == NULL) { + va_list args; + va_start(args, fmt); + vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, fmt, args); + va_end(args); + + // Print debug info + std::cout << "logPrintFunc called after free! " + << "Level: " << level << ", Tag: " << (tag ? tag : "NULL") + << ", Message: " << buffer << std::endl; + + CHK(FALSE, STATUS_SUCCESS); + } + CHK(mJvm != NULL, STATUS_SUCCESS); + + envState = mJvm->GetEnv((PVOID*) &env, JNI_VERSION_1_6); + if (envState == JNI_EDETACHED) { + if (mJvm->AttachCurrentThread((PVOID*) &env, NULL) != 0) { + goto CleanUp; + } + attached = TRUE; + } + + va_start(list, fmt); + vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, fmt, list); + va_end(list); + + if (tag != NULL && fmt != NULL && STRLEN(buffer) > 0) { + jstrTag = env->NewStringUTF(tag); + jstrFmt = env->NewStringUTF(fmt); + jstrBuffer = env->NewStringUTF(buffer); + } + + CHK(jstrTag != NULL, STATUS_NOT_ENOUGH_MEMORY); + CHK(jstrFmt != NULL, STATUS_NOT_ENOUGH_MEMORY); + CHK(jstrBuffer != NULL, STATUS_NOT_ENOUGH_MEMORY); + + env->CallVoidMethod(mGlobalJniObjRef, mLogPrintMethodId, level, jstrTag, jstrFmt, jstrBuffer); + + CHK_JVM_EXCEPTION(env); + + /* + Sample logs from PIC as displayed by log4j2 in Java Producer SDK + 2021-12-10 10:01:53,874 [main] TRACE c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoProducerJNI - Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream(): Enter + 2021-12-10 10:01:53,875 [main] INFO c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoProducerJNI - Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream(): Creating Kinesis Video stream. + 2021-12-10 10:01:53,875 [main] INFO c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] KinesisVideoClient - createKinesisVideoStream(): Creating Kinesis Video Stream. + 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Kinesis Video Stream Info + + 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Kinesis Video Stream Info + 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Stream name: NewStreamJava12 + 2021-12-10 10:01:53,875 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Streaming type: STREAMING_TYPE_REALTIME + 2021-12-10 10:01:53,876 [main] DEBUG c.a.k.j.c.KinesisVideoJavaClientFactory - [PIC] Stream - logStreamInfo(): Content type: video/h264 + */ + +CleanUp: + + if (jstrTag != NULL) { + env->DeleteLocalRef(jstrTag); + } + + if (jstrFmt != NULL) { + env->DeleteLocalRef(jstrFmt); + } + + if (jstrBuffer != NULL) { + env->DeleteLocalRef(jstrBuffer); + } + + // Detach the thread if we have attached it to JVM + if (attached) { + mJvm->DetachCurrentThread(); + } +} diff --git a/jni/com/amazonaws/kinesis/video/producer/jni/NativeProducerInterface.cpp b/jni/com/amazonaws/kinesis/video/producer/jni/NativeProducerInterface.cpp new file mode 100644 index 000000000..0c89d6e3a --- /dev/null +++ b/jni/com/amazonaws/kinesis/video/producer/jni/NativeProducerInterface.cpp @@ -0,0 +1,458 @@ +/** + * C part of the JNI interface to the native producer client functionality. + */ + +#define LOG_CLASS "KinesisVideoProducerJNI" +#include "com/amazonaws/kinesis/video/producer/jni/com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni.h" +#include "com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h" + +// Used to detect version mismatches at runtime between the native interface library and the Java code that uses it. +// This must match the value returned by EXPECTED_LIBRARY_VERSION in java JNI counterpart. +// +// IMPORTANT: This version number *must* be incremented every time a new library build is checked in. +// We are seeing a very high incidence of runtime failures due to APKs being deployed with the wrong native libraries, +// and they are much easier to diagnose when the numeric version check fails. +#define NATIVE_LIBRARY_VERSION "2.0" + +// +// JNI entry points +// +#ifdef __cplusplus +extern "C" { +#endif + /** + * Returns a hardcoded library version string. + */ + PUBLIC_API jstring JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getNativeLibraryVersion(JNIEnv* env, jobject thiz) + { + return env->NewStringUTF(NATIVE_LIBRARY_VERSION); + } + + /** + * Returns a string representing the date and time when this code was compiled. + */ + PUBLIC_API jstring JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getNativeCodeCompileTime(JNIEnv* env, jobject thiz) + { + return env->NewStringUTF(__DATE__ " " __TIME__); + } + + /** + * Releases the kinesis video client object. All operations will fail from this moment on + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_freeKinesisVideoClient(JNIEnv* env, jobject thiz, jlong handle) + { + ENTER(); + + + DLOGI("Freeing Kinesis Video client."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + + // Calling leave early since the logger is part of pWrapper. + // Avoiding errors by logging after free + LEAVE(); + + if (pWrapper != NULL) { + delete pWrapper; + } + } + + /** + * Creates and initializes the kinesis video client + */ + PUBLIC_API jlong JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoClient(JNIEnv* env, jobject thiz, jobject deviceInfo) + { + ENTER(); + + KinesisVideoClientWrapper* pWrapper = NULL; + jlong retValue = (jlong) NULL; + + DLOGI("Creating Kinesis Video client."); + CHECK(env != NULL && thiz != NULL); + + if (deviceInfo == NULL) { + DLOGE("DeviceInfo is NULL."); + throwNativeException(env, EXCEPTION_NAME, "DeviceInfo is NULL.", STATUS_NULL_ARG); + goto CleanUp; + } + + // Create the wrapper engine + pWrapper = new KinesisVideoClientWrapper(env, thiz, deviceInfo); + + // Returning the pointer as a handle + retValue = (jlong) TO_WRAPPER_HANDLE(pWrapper); + +CleanUp: + + LEAVE(); + return retValue; + } + + /** + * Stops KinesisVideo streams + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_stopKinesisVideoStreams(JNIEnv* env, jobject thiz, jlong handle) + { + ENTER(); + + DLOGI("Stopping Kinesis Video streams."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->stopKinesisVideoStreams(); + } + + LEAVE(); + } + + /** + * Stops a KinesisVideo stream + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_stopKinesisVideoStream(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle) + { + ENTER(); + + DLOGI("Stopping Kinesis Video stream."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->stopKinesisVideoStream(streamHandle); + } + + LEAVE(); + } + + /** + * Frees a KinesisVideo stream. + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_freeKinesisVideoStream(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle) + { + ENTER(); + + DLOGI("Stopping Kinesis Video stream."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->freeKinesisVideoStream(streamHandle); + } + + LEAVE(); + } + + /** + * Extracts the KinesisVideo client object metrics + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoMetrics(JNIEnv* env, jobject thiz, jlong handle, jobject kinesisVideoMetrics) + { + ENTERS(); + + DLOGS("Getting Kinesis Video metrics."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->getKinesisVideoMetrics(kinesisVideoMetrics); + } + + LEAVES(); + } + + /** + * Extracts the KinesisVideo client object metrics + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoStreamMetrics(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jobject kinesisVideoStreamMetrics) + { + ENTERS(); + + DLOGS("Getting Kinesis Video stream metrics."); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->getKinesisVideoStreamMetrics(streamHandle, kinesisVideoStreamMetrics); + } + + LEAVES(); + } + + /** + * Creates and initializes the kinesis video client + */ + PUBLIC_API jlong JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream(JNIEnv* env, jobject thiz, jlong handle, jobject streamInfo) + { + ENTER(); + jlong streamHandle = INVALID_STREAM_HANDLE_VALUE; + + DLOGI("Creating Kinesis Video stream."); + CHECK(env != NULL && thiz != NULL && streamInfo != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + streamHandle = pWrapper->createKinesisVideoStream(streamInfo); + } + + LEAVE(); + + return streamHandle; + } + + /** + * Puts a frame in to the frame buffer + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putKinesisVideoFrame(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jobject kinesisVideoFrame) + { + ENTERS(); + + DLOGS("Putting Kinesis Video frame for stream 0x%016" PRIx64 ".", streamHandle); + CHECK(env != NULL && thiz != NULL && kinesisVideoFrame != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->putKinesisVideoFrame(streamHandle, kinesisVideoFrame); + } + + LEAVES(); + } + + /** + * Puts a metadata in to the frame buffer + */ + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putKinesisVideoFragmentMetadata(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jstring metadataName, jstring metadataValue, jboolean persistent) + { + ENTERS(); + + DLOGS("Putting Kinesis Video metadata for stream 0x%016" PRIx64 ".", streamHandle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->putKinesisVideoFragmentMetadata(streamHandle, metadataName, metadataValue, persistent); + } + + LEAVES(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamFragmentAck(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jlong uploadHandle, jobject fragmentAck) + { + ENTERS(); + + DLOGS("Reporting Kinesis Video fragment ack for stream 0x%016" PRIx64 ".", streamHandle); + CHECK(env != NULL && thiz != NULL && fragmentAck != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->kinesisVideoStreamFragmentAck(streamHandle, uploadHandle, fragmentAck); + } + + LEAVES(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamParseFragmentAck(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jlong uploadHandle, jstring ack) + { + ENTERS(); + + DLOGS("Parsing Kinesis Video fragment ack for stream 0x%016" PRIx64 ".", streamHandle); + CHECK(env != NULL && thiz != NULL && ack != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->kinesisVideoStreamParseFragmentAck(streamHandle, uploadHandle, ack); + } + + LEAVES(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_describeStreamResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jint httpStatusCode, jobject streamDescription) + { + ENTER(); + + DLOGI("Describe stream event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->describeStreamResult(streamHandle, httpStatusCode, streamDescription); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getStreamingEndpointResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jint httpStatusCode, jstring streamingEndpoint) + { + ENTER(); + + DLOGI("get streaming endpoint event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->getStreamingEndpointResult(streamHandle, httpStatusCode, streamingEndpoint); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getStreamingTokenResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jint httpStatusCode, jobject streamingToken, jint tokenSize, jlong tokenExpiration) + { + ENTER(); + + DLOGI("get streaming token event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->getStreamingTokenResult(streamHandle, httpStatusCode, (jbyteArray) streamingToken, tokenSize, tokenExpiration); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createStreamResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jint httpStatusCode, jstring streamArn) + { + ENTERS(); + + DLOGI("create stream event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->createStreamResult(streamHandle, httpStatusCode, streamArn); + } + + LEAVES(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putStreamResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jint httpStatusCode, jlong clientStreamHandle) + { + ENTER(); + + DLOGI("put stream event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->putStreamResult(streamHandle, httpStatusCode, clientStreamHandle); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_tagResourceResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong customData, jint httpStatusCode) + { + ENTER(); + + DLOGI("tag resource event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->tagResourceResult(customData, httpStatusCode); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoStreamData(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jlong uploadHandle, jobject dataBuffer, jint offset, jint length, jobject readResult) + { + ENTERS(); + jint retStatus = STATUS_SUCCESS; + + DLOGS("get kinesis video stream data event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->getKinesisVideoStreamData(streamHandle, uploadHandle, dataBuffer, offset, length, readResult); + } + + LEAVES(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamFormatChanged(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jobject codecPrivateData, jlong trackId) + { + ENTER(); + + DLOGI("stream format changed event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->streamFormatChanged(streamHandle, codecPrivateData, trackId); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createDeviceResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong deviceHandle, jint httpStatusCode, jstring deviceArn) + { + ENTER(); + + DLOGI("create device event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->createDeviceResult(deviceHandle, httpStatusCode, deviceArn); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_deviceCertToTokenResultEvent(JNIEnv* env, jobject thiz, jlong handle, jlong deviceHandle, jint httpStatusCode, jobject token, jint tokenSize, jlong tokenExpiration) + { + ENTER(); + + DLOGI("device cert to token event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->deviceCertToTokenResult(deviceHandle, httpStatusCode, (jbyteArray) token, tokenSize, tokenExpiration); + } + + LEAVE(); + } + + PUBLIC_API void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamTerminated(JNIEnv* env, jobject thiz, jlong handle, jlong streamHandle, jlong uploadHandle, jint httpStatusCode) + { + ENTER(); + + DLOGI("Stream terminated event for handle 0x%016" PRIx64 ".", (UINT64) handle); + CHECK(env != NULL && thiz != NULL); + + KinesisVideoClientWrapper* pWrapper = FROM_WRAPPER_HANDLE(handle); + if (pWrapper != NULL) { + SyncMutex::Autolock l(pWrapper->getSyncLock(), __FUNCTION__); + pWrapper->kinesisVideoStreamTerminated(streamHandle, uploadHandle, httpStatusCode); + } + + LEAVE(); + } + +#ifdef __cplusplus +} // End extern "C" +#endif diff --git a/jni/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp b/jni/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp new file mode 100644 index 000000000..515122e58 --- /dev/null +++ b/jni/com/amazonaws/kinesis/video/producer/jni/Parameters.cpp @@ -0,0 +1,1119 @@ +/** + * Implementation of Kinesis Video parameters conversion + */ +#define LOG_CLASS "KinesisVideoParametersConversion" + +#include "com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h" + +BOOL setDeviceInfo(JNIEnv *env, jobject deviceInfo, PDeviceInfo pDeviceInfo) +{ + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + const char *retChars; + + CHECK(env != NULL && deviceInfo != NULL && pDeviceInfo != NULL); + + // Load DeviceInfo + jclass cls = env->GetObjectClass(deviceInfo); + if (cls == NULL) { + DLOGE("Failed to create DeviceInfo class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pDeviceInfo->version = env->CallIntMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getName", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getName"); + } else { + jstring retString = (jstring) env->CallObjectMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pDeviceInfo->name, retChars, MAX_DEVICE_NAME_LEN + 1); + + // Ensure we null terminate it + pDeviceInfo->name[MAX_DEVICE_NAME_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pDeviceInfo->name[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getStreamCount", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStreamCount"); + } else { + pDeviceInfo->streamCount = env->CallIntMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getStorageInfoVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStorageInfoVersion"); + } else { + pDeviceInfo->storageInfo.version = env->CallIntMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getDeviceStorageType", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getDeviceStorageType"); + } else { + pDeviceInfo->storageInfo.storageType = (DEVICE_STORAGE_TYPE) env->CallIntMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getSpillRatio", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getSpillRatio"); + } else { + pDeviceInfo->storageInfo.spillRatio = env->CallIntMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getStorageSize", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStorageSize"); + } else { + pDeviceInfo->storageInfo.storageSize = env->CallLongMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getRootDirectory", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getRootDirectory"); + } else { + jstring retString = (jstring) env->CallObjectMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pDeviceInfo->storageInfo.rootDirectory, retChars, MAX_PATH_LEN + 1); + pDeviceInfo->storageInfo.rootDirectory[MAX_PATH_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pDeviceInfo->storageInfo.rootDirectory[0] = '\0'; + } + } + + // Set the tags to empty first + pDeviceInfo->tagCount = 0; + pDeviceInfo->tags = NULL; + methodId = env->GetMethodID(cls, "getTags", "()[Lcom/amazonaws/kinesisvideo/producer/Tag;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTags"); + } else { + jobjectArray array = (jobjectArray) env->CallObjectMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (!setTags(env, array, &pDeviceInfo->tags, &pDeviceInfo->tagCount)) { + DLOGW("Failed getting/setting tags."); + } + } + + methodId = env->GetMethodID(cls, "getClientId", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getClientId"); + } else { + jstring retString = (jstring) env->CallObjectMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pDeviceInfo->clientId, retChars, MAX_CLIENT_ID_STRING_LENGTH + 1); + pDeviceInfo->clientId[MAX_CLIENT_ID_STRING_LENGTH] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + STRNCPY(pDeviceInfo->clientId, (PCHAR) "JNI", MAX_CLIENT_ID_STRING_LENGTH + 1); + } + } + + // Set the client info to empty first + methodId = env->GetMethodID(cls, "getClientInfo", "()Lcom/amazonaws/kinesisvideo/producer/ClientInfo;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getClientInfo"); + } else { + jobject clientInfo = (jobject) env->CallObjectMethod(deviceInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (!setClientInfo(env, clientInfo, &pDeviceInfo->clientInfo)) { + DLOGW("Failed getting/setting client info."); + } + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + +BOOL setClientInfo(JNIEnv *env, jobject clientInfo, PClientInfo pClientInfo) { + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + const char *retChars; + + CHECK(env != NULL && clientInfo != NULL && pClientInfo != NULL); + + // Load ClientInfo + jclass cls = env->GetObjectClass(clientInfo); + if (cls == NULL) { + DLOGE("Failed to create ClientInfo class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pClientInfo->version = env->CallIntMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getCreateClientTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getCreateClientTimeout"); + } else { + pClientInfo->createClientTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getCreateStreamTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getCreateStreamTimeout"); + } else { + pClientInfo->createStreamTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getStopStreamTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStopStreamTimeout"); + } else { + pClientInfo->stopStreamTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getServiceConnectionTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getServiceConnectionTimeout"); + } else { + pClientInfo->serviceCallConnectionTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getServiceCompletionTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getServiceCompletionTimeout"); + } else { + pClientInfo->serviceCallCompletionTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getOfflineBufferAvailabilityTimeout", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getOfflineBufferAvailabilityTimeout"); + } else { + pClientInfo->offlineBufferAvailabilityTimeout = env->CallLongMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getLoggerLogLevel", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getLoggerLogLevel"); + } else { + pClientInfo->loggerLogLevel = env->CallIntMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getLogMetric", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getLogMetric"); + } else { + pClientInfo->logMetric = env->CallBooleanMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getAutomaticStreamingFlags", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getAutomaticStreamingFlags"); + } else { + pClientInfo->automaticStreamingFlags = (AUTOMATIC_STREAMING_FLAGS) env->CallIntMethod(clientInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + + +BOOL setTags(JNIEnv *env, jobjectArray tagArray, PTag* ppTags, PUINT32 pTagCount) +{ + STATUS retStatus = STATUS_SUCCESS; + const char *retChars; + jclass tagClass = NULL; + jobject tagObj = NULL; + jstring retString = NULL; + jmethodID nameMethodId = NULL; + jmethodID valueMethodId = NULL; + PTag pTags = NULL; + BOOL retValue = TRUE; + UINT32 tagCount = 0, i = 0; + PCHAR pCurPtr = NULL; + + // Early exit in case of an optional array of tags + CHK(tagArray != NULL, STATUS_SUCCESS); + + // Get the length of the array + tagCount = (UINT32) env->GetArrayLength(tagArray); + CHK_JVM_EXCEPTION(env); + + // Allocate enough memory. + // NOTE: We need to add two NULL terminators for tag name and tag value + CHK(NULL != (pTags = (PTag) MEMCALLOC(tagCount, SIZEOF(Tag) + (MAX_TAG_NAME_LEN + MAX_TAG_VALUE_LEN + 2) * SIZEOF(CHAR))), STATUS_NOT_ENOUGH_MEMORY); + + // Iterate over and set the values. NOTE: the actual storage for the strings will follow the array + pCurPtr = (PCHAR) (pTags + tagCount); + for (;i < tagCount; i++) { + CHK(NULL != (tagObj = env->GetObjectArrayElement(tagArray, (jsize) i)), STATUS_INVALID_ARG); + CHK_JVM_EXCEPTION(env); + + // Get the Tag class object and the method IDs first time + if (tagClass == NULL) { + CHK(NULL != (tagClass = env->GetObjectClass(tagObj)), STATUS_INVALID_OPERATION); + CHK_JVM_EXCEPTION(env); + + CHK(NULL != (nameMethodId = env->GetMethodID(tagClass, "getName", "()Ljava/lang/String;")), STATUS_INVALID_OPERATION); + CHK_JVM_EXCEPTION(env); + + CHK(NULL != (valueMethodId = env->GetMethodID(tagClass, "getValue", "()Ljava/lang/String;")), STATUS_INVALID_OPERATION); + CHK_JVM_EXCEPTION(env); + } + + // Get the name of the tag + CHK(NULL != (retString = (jstring) env->CallObjectMethod(tagObj, nameMethodId)), STATUS_INVALID_ARG); + CHK_JVM_EXCEPTION(env); + + // Extract the chars and copy + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pCurPtr, retChars, MAX_TAG_NAME_LEN + 1); + pCurPtr[MAX_TAG_NAME_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + + // Set the tag pointer and increment the current pointer + pTags[i].name = pCurPtr; + pCurPtr += MAX_TAG_NAME_LEN; + + // Get the value of the tag + CHK(NULL != (retString = (jstring) env->CallObjectMethod(tagObj, valueMethodId)), STATUS_INVALID_ARG); + CHK_JVM_EXCEPTION(env); + + // Extract the chars and copy + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pCurPtr, retChars, MAX_TAG_VALUE_LEN + 1); + pCurPtr[MAX_TAG_VALUE_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + + // Set the tag pointer and increment the current pointer + pTags[i].value = pCurPtr; + pCurPtr += MAX_TAG_VALUE_LEN; + } + + // Set the tag count and the tags + *pTagCount = tagCount; + *ppTags = pTags; + +CleanUp: + + if (STATUS_FAILED(retStatus)) { + retValue = FALSE; + releaseTags(pTags); + } + + return retValue; +} + +VOID releaseTags(PTag tags) +{ + if (tags != NULL) { + MEMFREE(tags); + } +} + +BOOL setStreamInfo(JNIEnv* env, jobject streamInfo, PStreamInfo pStreamInfo) +{ + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + jbyteArray byteArray = NULL; + jbyte* bufferPtr = NULL; + jsize arrayLen = 0; + UINT32 trackInfoCount = 0; + const char *retChars; + + CHECK(env != NULL && streamInfo != NULL && pStreamInfo != NULL); + + // Load StreamInfo + jclass cls = env->GetObjectClass(streamInfo); + if (cls == NULL) { + DLOGE("Failed to create StreamInfo class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pStreamInfo->version = env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getName", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getName"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamInfo->name, retChars, MAX_STREAM_NAME_LEN + 1); + + // Just in case - null terminate the copied string + pStreamInfo->name[MAX_STREAM_NAME_LEN] = '\0'; + + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamInfo->name[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getStreamingType", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStreamingType"); + } else { + pStreamInfo->streamCaps.streamingType = (STREAMING_TYPE) env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getContentType", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getContentType"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamInfo->streamCaps.contentType, retChars, MAX_CONTENT_TYPE_LEN + 1); + pStreamInfo->streamCaps.contentType[MAX_CONTENT_TYPE_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamInfo->streamCaps.contentType[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getKmsKeyId", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getKmsKeyId"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamInfo->kmsKeyId, retChars, MAX_ARN_LEN + 1); + pStreamInfo->kmsKeyId[MAX_ARN_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamInfo->kmsKeyId[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getRetentionPeriod", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getRetentionPeriod"); + } else { + pStreamInfo->retention = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isAdaptive", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isAdaptive"); + } else { + pStreamInfo->streamCaps.adaptive = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isAllowStreamCreation", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isAllowStreamCreation"); + } else { + pStreamInfo->streamCaps.allowStreamCreation = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + + methodId = env->GetMethodID(cls, "getMaxLatency", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getMaxLatency"); + } else { + pStreamInfo->streamCaps.maxLatency = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getFragmentDuration", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getFragmentDuration"); + } else { + pStreamInfo->streamCaps.fragmentDuration = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isKeyFrameFragmentation", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isKeyFrameFragmentation"); + } else { + pStreamInfo->streamCaps.keyFrameFragmentation = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isFrameTimecodes", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isFrameTimecodes"); + } else { + pStreamInfo->streamCaps.frameTimecodes = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isAbsoluteFragmentTimes", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isAbsoluteFragmentTimes"); + } else { + pStreamInfo->streamCaps.absoluteFragmentTimes = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isFragmentAcks", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isFragmentAcks"); + } else { + pStreamInfo->streamCaps.fragmentAcks = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isRecoverOnError", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isRecoverOnError"); + } else { + pStreamInfo->streamCaps.recoverOnError = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "isRecalculateMetrics", "()Z"); + if (methodId == NULL) { + DLOGW("Couldn't find method id isRecalculateMetrics"); + } else { + pStreamInfo->streamCaps.recalculateMetrics = env->CallBooleanMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getNalAdaptationFlags", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getNalAdaptationFlags"); + } else { + pStreamInfo->streamCaps.nalAdaptationFlags = env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + // Initialize to NULL first in case of failures + pStreamInfo->streamCaps.segmentUuid = NULL; + methodId = env->GetMethodID(cls, "getSegmentUuidBytes", "()[B"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getSegmentUuidBytes"); + } else { + byteArray = (jbyteArray) env->CallObjectMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (byteArray != NULL) { + // Extract the bits from the byte buffer + bufferPtr = env->GetByteArrayElements(byteArray, NULL); + arrayLen = env->GetArrayLength(byteArray); + + // Ensure we have at least UUID size + CHK(arrayLen == MKV_SEGMENT_UUID_LEN, STATUS_INVALID_ARG_LEN); + + // Allocate a temp storage + pStreamInfo->streamCaps.segmentUuid = (PBYTE) MEMALLOC(MKV_SEGMENT_UUID_LEN); + CHK(pStreamInfo->streamCaps.segmentUuid != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Copy the bits + MEMCPY(pStreamInfo->streamCaps.segmentUuid, bufferPtr, MKV_SEGMENT_UUID_LEN); + + // Release the buffer + env->ReleaseByteArrayElements(byteArray, bufferPtr, JNI_ABORT); + } + } + + methodId = env->GetMethodID(cls, "getTrackInfoCount", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackInfoCount"); + } else { + trackInfoCount = (UINT32) env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + CHECK_EXT(trackInfoCount > 0, "TrackInfo count should be greater than 0"); + pStreamInfo->streamCaps.trackInfoCount = trackInfoCount; + pStreamInfo->streamCaps.trackInfoList = (PTrackInfo) MEMALLOC(trackInfoCount * SIZEOF(TrackInfo)); + MEMSET(pStreamInfo->streamCaps.trackInfoList, 0, SIZEOF(TrackInfo) * trackInfoCount); + CHK(pStreamInfo->streamCaps.trackInfoList != NULL, STATUS_NOT_ENOUGH_MEMORY); + + methodId = env->GetMethodID(cls, "getTrackInfoVersion", "(I)I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackInfoVersion"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + pStreamInfo->streamCaps.trackInfoList[i].version = (UINT64) env->CallIntMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + } + } + + methodId = env->GetMethodID(cls, "getTrackName", "(I)Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackName"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + jstring retString = (jstring) env->CallObjectMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamInfo->streamCaps.trackInfoList[i].trackName, retChars, MKV_MAX_TRACK_NAME_LEN + 1); + pStreamInfo->streamCaps.trackInfoList[i].trackName[MKV_MAX_TRACK_NAME_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamInfo->streamCaps.trackInfoList[i].trackName[0] = '\0'; + } + } + } + + methodId = env->GetMethodID(cls, "getCodecId", "(I)Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getCodecId"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + jstring retString = (jstring) env->CallObjectMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamInfo->streamCaps.trackInfoList[i].codecId, retChars, MKV_MAX_CODEC_ID_LEN + 1); + pStreamInfo->streamCaps.trackInfoList[i].codecId[MKV_MAX_CODEC_ID_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamInfo->streamCaps.trackInfoList[i].codecId[0] = '\0'; + } + } + } + + methodId = env->GetMethodID(cls, "getCodecPrivateData", "(I)[B"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getCodecPrivateData"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + byteArray = (jbyteArray) env->CallObjectMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + + if (byteArray != NULL) { + // Extract the bits from the byte buffer + bufferPtr = env->GetByteArrayElements(byteArray, NULL); + arrayLen = env->GetArrayLength(byteArray); + + // Allocate a temp storage + pStreamInfo->streamCaps.trackInfoList[i].codecPrivateDataSize = (UINT32) arrayLen; + pStreamInfo->streamCaps.trackInfoList[i].codecPrivateData = (PBYTE) MEMALLOC(arrayLen); + CHK(pStreamInfo->streamCaps.trackInfoList[i].codecPrivateData != NULL, STATUS_NOT_ENOUGH_MEMORY); + + // Copy the bits + MEMCPY(pStreamInfo->streamCaps.trackInfoList[i].codecPrivateData, bufferPtr, arrayLen); + + // Release the buffer + env->ReleaseByteArrayElements(byteArray, bufferPtr, JNI_ABORT); + } else { + pStreamInfo->streamCaps.trackInfoList[i].codecPrivateDataSize = 0; + pStreamInfo->streamCaps.trackInfoList[i].codecPrivateData = NULL; + } + } + } + + methodId = env->GetMethodID(cls, "getTrackInfoType", "(I)I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackInfoType"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + pStreamInfo->streamCaps.trackInfoList[i].trackType = (MKV_TRACK_INFO_TYPE) env->CallIntMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + } + } + + methodId = env->GetMethodID(cls, "getTrackId", "(I)J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackId"); + } else { + for(UINT32 i = 0; i < trackInfoCount; ++i) { + pStreamInfo->streamCaps.trackInfoList[i].trackId = (UINT64) env->CallLongMethod(streamInfo, methodId, i); + CHK_JVM_EXCEPTION(env); + } + } + + methodId = env->GetMethodID(cls, "getAvgBandwidthBps", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getAvgBandwidthBps"); + } else { + pStreamInfo->streamCaps.avgBandwidthBps = env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getFrameRate", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getFrameRate"); + } else { + pStreamInfo->streamCaps.frameRate = env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getBufferDuration", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getBufferDuration"); + } else { + pStreamInfo->streamCaps.bufferDuration = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getReplayDuration", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getReplayDuration"); + } else { + pStreamInfo->streamCaps.replayDuration = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getConnectionStalenessDuration", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getConnectionStalenessDuration"); + } else { + pStreamInfo->streamCaps.connectionStalenessDuration = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getTimecodeScale", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTimecodeScale"); + } else { + pStreamInfo->streamCaps.timecodeScale = env->CallLongMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + // Set the tags to empty first + pStreamInfo->tagCount = 0; + pStreamInfo->tags = NULL; + methodId = env->GetMethodID(cls, "getTags", "()[Lcom/amazonaws/kinesisvideo/producer/Tag;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTags"); + } else { + jobjectArray array = (jobjectArray) env->CallObjectMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + + if (!setTags(env, array, &pStreamInfo->tags, &pStreamInfo->tagCount)) { + DLOGW("Failed getting/setting tags."); + } + } + + methodId = env->GetMethodID(cls, "getFrameOrderMode", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getFrameOrderMode"); + } else { + pStreamInfo->streamCaps.frameOrderingMode = (FRAME_ORDER_MODE) env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getStorePressurePolicy", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStorePressurePolicy"); + } else { + pStreamInfo->streamCaps.storePressurePolicy = (CONTENT_STORE_PRESSURE_POLICY) env->CallIntMethod(streamInfo, methodId); + CHK_JVM_EXCEPTION(env); + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + +BOOL setFrame(JNIEnv* env, jobject kinesisVideoFrame, PFrame pFrame) +{ + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + CHECK(env != NULL && kinesisVideoFrame != NULL && pFrame != NULL); + + // Load KinesisVideoFrame + jclass cls = env->GetObjectClass(kinesisVideoFrame); + if (cls == NULL) { + DLOGE("Failed to create KinesisVideoFrame class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pFrame->version = env->CallIntMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getIndex", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getIndex"); + } else { + pFrame->index = env->CallIntMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getTrackId", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTrackId"); + } else { + pFrame->trackId = env->CallLongMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getFlags", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getFlags"); + } else { + pFrame->flags = (FRAME_FLAGS) env->CallIntMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getDecodingTs", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getDecodingTs"); + } else { + pFrame->decodingTs = env->CallLongMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getPresentationTs", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getPresentationTs"); + } else { + pFrame->presentationTs = env->CallLongMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getDuration", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getDuration"); + } else { + pFrame->duration = env->CallLongMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getSize", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getSize"); + } else { + pFrame->size = (FRAME_FLAGS) env->CallIntMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getData", "()Ljava/nio/ByteBuffer;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getData"); + } else { + jobject byteBuffer = (jstring) env->CallObjectMethod(kinesisVideoFrame, methodId); + CHK_JVM_EXCEPTION(env); + pFrame->frameData = (PBYTE) env->GetDirectBufferAddress(byteBuffer); + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + +BOOL setFragmentAck(JNIEnv* env, jobject fragmentAck, PFragmentAck pFragmentAck) +{ + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + CHECK(env != NULL && fragmentAck != NULL && pFragmentAck != NULL); + + // Load KinesisVideoFragmentAck + jclass cls = env->GetObjectClass(fragmentAck); + if (cls == NULL) { + DLOGE("Failed to create KinesisVideoFragmentAck class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pFragmentAck->version = env->CallIntMethod(fragmentAck, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getAckType", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getAckType"); + } else { + pFragmentAck->ackType = (FRAGMENT_ACK_TYPE) env->CallIntMethod(fragmentAck, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getTimestamp", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getTimestamp"); + } else { + pFragmentAck->timestamp = env->CallLongMethod(fragmentAck, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getSequenceNumber", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getSequenceNumber"); + } else { + jstring retString = (jstring) env->CallObjectMethod(fragmentAck, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + const char* retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pFragmentAck->sequenceNumber, retChars, MAX_FRAGMENT_SEQUENCE_NUMBER + 1); + pFragmentAck->sequenceNumber[MAX_FRAGMENT_SEQUENCE_NUMBER] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pFragmentAck->sequenceNumber[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getResult", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getResult"); + } else { + pFragmentAck->result = (SERVICE_CALL_RESULT) env->CallIntMethod(fragmentAck, methodId); + CHK_JVM_EXCEPTION(env); + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + +BOOL setStreamDescription(JNIEnv* env, jobject streamDescription, PStreamDescription pStreamDesc) +{ + STATUS retStatus = STATUS_SUCCESS; + jmethodID methodId = NULL; + const char *retChars; + + CHECK(env != NULL && streamDescription != NULL && pStreamDesc != NULL); + + // Load StreamDescription + jclass cls = env->GetObjectClass(streamDescription); + if (cls == NULL) { + DLOGE("Failed to create StreamDescription class."); + CHK(FALSE, STATUS_INVALID_OPERATION); + } + + // Retrieve the methods and call it + methodId = env->GetMethodID(cls, "getVersion", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getVersion"); + } else { + pStreamDesc->version = env->CallIntMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getDeviceName", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getDeviceName"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->deviceName, retChars, MAX_DEVICE_NAME_LEN + 1); + pStreamDesc->deviceName[MAX_DEVICE_NAME_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->deviceName[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getStreamName", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStreamName"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->streamName, retChars, MAX_STREAM_NAME_LEN + 1); + pStreamDesc->streamName[MAX_STREAM_NAME_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->streamName[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getContentType", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getContentType"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->contentType, retChars, MAX_CONTENT_TYPE_LEN + 1); + pStreamDesc->contentType[MAX_CONTENT_TYPE_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->contentType[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getUpdateVersion", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getUpdateVersion"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->updateVersion, retChars, MAX_UPDATE_VERSION_LEN + 1); + pStreamDesc->updateVersion[MAX_UPDATE_VERSION_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->updateVersion[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getStreamArn", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStreamArn"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->streamArn, retChars, MAX_ARN_LEN + 1); + pStreamDesc->streamArn[MAX_ARN_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->streamArn[0] = '\0'; + } + } + + methodId = env->GetMethodID(cls, "getCreationTime", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getCreationTime"); + } else { + pStreamDesc->creationTime = env->CallLongMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getStreamStatus", "()I"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getStreamStatus"); + } else { + pStreamDesc->streamStatus = (STREAM_STATUS) env->CallIntMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getRetention", "()J"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getRetention"); + } else { + pStreamDesc->retention = env->CallLongMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + } + + methodId = env->GetMethodID(cls, "getKmsKeyId", "()Ljava/lang/String;"); + if (methodId == NULL) { + DLOGW("Couldn't find method id getKmsKeyId"); + } else { + jstring retString = (jstring) env->CallObjectMethod(streamDescription, methodId); + CHK_JVM_EXCEPTION(env); + + if (retString != NULL) { + retChars = env->GetStringUTFChars(retString, NULL); + STRNCPY(pStreamDesc->kmsKeyId, retChars, MAX_ARN_LEN + 1); + pStreamDesc->kmsKeyId[MAX_ARN_LEN] = '\0'; + env->ReleaseStringUTFChars(retString, retChars); + } else { + pStreamDesc->kmsKeyId[0] = '\0'; + } + } + +CleanUp: + return STATUS_FAILED(retStatus) ? FALSE : TRUE; +} + +BOOL setStreamingEndpoint(JNIEnv* env, jstring streamingEndpoint, PCHAR pEndpoint) +{ + CHECK(env != NULL && streamingEndpoint != NULL && pEndpoint != NULL); + + const char *endpointChars = env->GetStringUTFChars(streamingEndpoint, NULL); + STRNCPY(pEndpoint, endpointChars, MAX_URI_CHAR_LEN + 1); + pEndpoint[MAX_URI_CHAR_LEN] = '\0'; + env->ReleaseStringUTFChars(streamingEndpoint, endpointChars); + + return TRUE; +} + +BOOL setStreamDataBuffer(JNIEnv* env, jobject dataBuffer, UINT32 offset, PBYTE* ppBuffer) +{ + PBYTE pBuffer = NULL; + + CHECK(env != NULL && ppBuffer != NULL); + + if (dataBuffer == NULL) { + return FALSE; + } + + // Get the byte pointer from the GC byte[] by pinning or copying. + pBuffer = (PBYTE) env->GetByteArrayElements((jbyteArray) dataBuffer, NULL); + + if (pBuffer == NULL) { + return FALSE; + } + + *ppBuffer = pBuffer + offset; + + return TRUE; +} + +BOOL releaseStreamDataBuffer(JNIEnv* env, jobject dataBuffer, UINT32 offset, PBYTE pBuffer) +{ + CHECK(env != NULL); + + if (dataBuffer == NULL || pBuffer == NULL) { + return TRUE; + } + + // Release/commit the changes and free. + env->ReleaseByteArrayElements((jbyteArray) dataBuffer, (jbyte*) (pBuffer - offset), 0); + + return TRUE; +} diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/JNICommon.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/JNICommon.h new file mode 100644 index 000000000..c4119f925 --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/JNICommon.h @@ -0,0 +1,47 @@ +/** + * Some boilerplate code used by all of our JNI interfaces. + */ + +#ifndef __JNICOMMON_H__ +#define __JNICOMMON_H__ + +#include // Basic native API +#include +#include + +#define EXCEPTION_NAME "com/amazonaws/kinesisvideo/producer/ProducerException" + +inline void throwNativeException(JNIEnv* env, const char* name, const char* msg, STATUS status) +{ + if (env->ExceptionCheck()) + { + env->ExceptionClear(); // Discard pending exception (should never happen) + DLOGW("Had to clear a pending exception found when throwing \"%s\" (code 0x%x)", msg, status); + } + + DLOGI("Throwing %s with message: %s", name, msg); + + jclass exceptionClass = env->FindClass(name); + CHECK(exceptionClass != NULL); + + jmethodID constructor = env->GetMethodID(exceptionClass, "", "(Ljava/lang/String;I)V"); + CHECK(constructor != NULL); + + jstring msgString = env->NewStringUTF(msg); + CHECK(msgString != NULL); + + // Convert status to an unsigned 64-bit value + const jint intStatus = (jint) status; + const jobject exception = env->NewObject(exceptionClass, constructor, msgString, intStatus); + CHECK(exception != NULL); + + if (env->Throw(jthrowable(exception)) != JNI_OK) + { + DLOGE("Failed throwing %s: %s (status 0x%x)", name, msg, status); + } + + env->DeleteLocalRef(msgString); + env->DeleteLocalRef(exception); +} + +#endif // __JNICOMMON_H__ diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h new file mode 100644 index 000000000..cbe27bfee --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/KinesisVideoClientWrapper.h @@ -0,0 +1,206 @@ +/** + * Simple kinesis video producer client wrapper for JNI + */ +#ifndef __KINESIS_VIDEO_PRODUCER_CLIENT_WRAPPER_H__ +#define __KINESIS_VIDEO_PRODUCER_CLIENT_WRAPPER_H__ + +#pragma once + +#define HAVE_PTHREADS 1 // Makes threads.h use pthreads + +#include "SyncMutex.h" +#include "TimedSemaphore.h" +#include "JNICommon.h" +#include "Parameters.h" + +#define TO_WRAPPER_HANDLE(p) ((jlong) (p)) +#define FROM_WRAPPER_HANDLE(h) ((KinesisVideoClientWrapper*) (h)) + +#define JNI_VER JNI_VERSION_1_6 + +#define CHK_JVM_EXCEPTION(env) \ + do { \ + /* If there was an exception we need */ \ + if (env->ExceptionCheck() == JNI_TRUE) { \ + /* Print out the message to the stderr */ \ + env->ExceptionDescribe(); \ + /* Clear the exception */ \ + env->ExceptionClear(); \ + /* Terminate the process as we didn't expect any exceptions */ \ + DLOGE("JVM threw an unexpected exception."); \ + retStatus = STATUS_INVALID_OPERATION; \ + goto CleanUp; \ + } \ + } while (FALSE) + +#ifdef ANDROID_BUILD +#define ATTACH_CURRENT_THREAD_TO_JVM(env) \ + do { \ + if (pWrapper->mJvm->AttachCurrentThread(&env, NULL) != 0) { \ + DLOGE("Fail to attache to JVM!");\ + return STATUS_INVALID_OPERATION; \ + } \ + } while (FALSE) +#else +#define ATTACH_CURRENT_THREAD_TO_JVM(env) \ + do { \ + if (pWrapper->mJvm->AttachCurrentThread((PVOID*) &env, NULL) != 0) { \ + DLOGE("Fail to attache to JVM!");\ + return STATUS_INVALID_OPERATION; \ + } \ + } while (FALSE) +#endif + +class KinesisVideoClientWrapper +{ + CLIENT_HANDLE mClientHandle; + static JavaVM *mJvm; // scope revised to static to make it accessible from static function- logPrintFunc + static jobject mGlobalJniObjRef; // scope revised to static to make it accessible from static function- logPrintFunc + ClientCallbacks mClientCallbacks; + DeviceInfo mDeviceInfo; + AuthInfo mAuthInfo; + SyncMutex mSyncLock; + + // Extracted method IDs + jmethodID mGetDeviceCertificateMethodId; + jmethodID mGetSecurityTokenMethodId; + jmethodID mGetDeviceFingerprintMethodId; + jmethodID mStreamUnderflowReportMethodId; + jmethodID mStorageOverflowPressureMethodId; + jmethodID mStreamLatencyPressureMethodId; + jmethodID mStreamConnectionStaleMethodId; + jmethodID mFragmentAckReceivedMethodId; + jmethodID mDroppedFrameReportMethodId; + jmethodID mBufferDurationOverflowPressureMethodId; + jmethodID mDroppedFragmentReportMethodId; + jmethodID mStreamErrorReportMethodId; + jmethodID mStreamDataAvailableMethodId; + jmethodID mStreamReadyMethodId; + jmethodID mStreamClosedMethodId; + jmethodID mCreateStreamMethodId; + jmethodID mDescribeStreamMethodId; + jmethodID mGetStreamingEndpointMethodId; + jmethodID mGetStreamingTokenMethodId; + jmethodID mPutStreamMethodId; + jmethodID mTagResourceMethodId; + jmethodID mClientReadyMethodId; + jmethodID mCreateDeviceMethodId; + jmethodID mDeviceCertToTokenMethodId; + static jmethodID mLogPrintMethodId; + + ////////////////////////////////////////////////////////////////////////////////////// + // Internal private methods + ////////////////////////////////////////////////////////////////////////////////////// + STATUS getAuthInfo(jmethodID, PBYTE*, PUINT32, PUINT64); + static AUTH_INFO_TYPE authInfoTypeFromInt(UINT32); + + ////////////////////////////////////////////////////////////////////////////////////// + // Static callbacks definitions + ////////////////////////////////////////////////////////////////////////////////////// + static UINT64 getCurrentTimeFunc(UINT64); + static UINT32 getRandomNumberFunc(UINT64); + static STATUS getDeviceCertificateFunc(UINT64, PBYTE*, PUINT32, PUINT64); + static STATUS getSecurityTokenFunc(UINT64, PBYTE*, PUINT32, PUINT64); + static STATUS getDeviceFingerprintFunc(UINT64, PCHAR*); + static STATUS streamUnderflowReportFunc(UINT64, STREAM_HANDLE); + static STATUS storageOverflowPressureFunc(UINT64, UINT64); + static STATUS streamLatencyPressureFunc(UINT64, STREAM_HANDLE, UINT64); + static STATUS streamConnectionStaleFunc(UINT64, STREAM_HANDLE, UINT64); + static STATUS fragmentAckReceivedFunc(UINT64, STREAM_HANDLE, UPLOAD_HANDLE, PFragmentAck); + static STATUS droppedFrameReportFunc(UINT64, STREAM_HANDLE, UINT64); + static STATUS bufferDurationOverflowPressureFunc(UINT64, STREAM_HANDLE, UINT64); + static STATUS droppedFragmentReportFunc(UINT64, STREAM_HANDLE, UINT64); + static STATUS streamErrorReportFunc(UINT64, STREAM_HANDLE, UPLOAD_HANDLE, UINT64, STATUS); + static STATUS streamDataAvailableFunc(UINT64, STREAM_HANDLE, PCHAR, UINT64, UINT64, UINT64); + static STATUS streamReadyFunc(UINT64, STREAM_HANDLE); + static STATUS streamClosedFunc(UINT64, STREAM_HANDLE, UINT64); + static MUTEX createMutexFunc(UINT64, BOOL); + static VOID lockMutexFunc(UINT64, MUTEX); + static VOID unlockMutexFunc(UINT64, MUTEX); + static BOOL tryLockMutexFunc(UINT64, MUTEX); + static VOID freeMutexFunc(UINT64, MUTEX); + static CVAR createConditionVariableFunc(UINT64); + static STATUS signalConditionVariableFunc(UINT64, CVAR); + static STATUS broadcastConditionVariableFunc(UINT64, CVAR); + static STATUS waitConditionVariableFunc(UINT64, CVAR, MUTEX, UINT64); + static VOID freeConditionVariableFunc(UINT64, CVAR); + static STATUS createStreamFunc(UINT64, + PCHAR, + PCHAR, + PCHAR, + PCHAR, + UINT64, + PServiceCallContext); + static STATUS describeStreamFunc(UINT64, + PCHAR, + PServiceCallContext); + static STATUS getStreamingEndpointFunc(UINT64, + PCHAR, + PCHAR, + PServiceCallContext); + static STATUS getStreamingTokenFunc(UINT64, + PCHAR, + STREAM_ACCESS_MODE, + PServiceCallContext); + static STATUS putStreamFunc(UINT64, + PCHAR, + PCHAR, + UINT64, + BOOL, + BOOL, + PCHAR, + PServiceCallContext); + + static STATUS tagResourceFunc(UINT64, + PCHAR, + UINT32, + PTag, + PServiceCallContext); + + static STATUS clientReadyFunc(UINT64, + CLIENT_HANDLE); + + static STATUS createDeviceFunc(UINT64, + PCHAR, + PServiceCallContext); + + static STATUS deviceCertToTokenFunc(UINT64, + PCHAR, + PServiceCallContext); + static VOID logPrintFunc(UINT32, PCHAR, PCHAR, ...); + +public: + KinesisVideoClientWrapper(JNIEnv* env, + jobject thiz, + jobject deviceInfo); + + ~KinesisVideoClientWrapper(); + SyncMutex& getSyncLock(); + void deleteGlobalRef(JNIEnv* env); + jobject getGlobalRef(); + void stopKinesisVideoStreams(); + STREAM_HANDLE createKinesisVideoStream(jobject streamInfo); + void getKinesisVideoMetrics(jobject kinesisVideoMetrics); + void getKinesisVideoStreamMetrics(jlong streamHandle, jobject kinesisVideoStreamMetrics); + void stopKinesisVideoStream(jlong streamHandle); + void freeKinesisVideoStream(jlong streamHandle); + void putKinesisVideoFrame(jlong streamHandle, jobject kinesisVideoFrame); + void putKinesisVideoFragmentMetadata(jlong streamHandle, jstring metadataName, jstring metadataValue, jboolean persistent); + void describeStreamResult(jlong streamHandle, jint httpStatusCode, jobject streamDescription); + void kinesisVideoStreamTerminated(jlong streamHandle, jlong uploadHandle, jint httpStatusCode); + void getStreamingEndpointResult(jlong streamHandle, jint httpStatusCode, jstring streamingEndpoint); + void getStreamingTokenResult(jlong streamHandle, jint httpStatusCode, jbyteArray token, jint tokenSize, jlong expiration); + void createStreamResult(jlong streamHandle, jint httpStatusCode, jstring streamArn); + void putStreamResult(jlong streamHandle, jint httpStatusCode, jlong clientStreamHandle); + void tagResourceResult(jlong customData, jint httpStatusCode); + void getKinesisVideoStreamData(jlong streamHandle, jlong uploadHandle, jobject dataBuffer, jint offset, jint length, jobject readResult); + void streamFormatChanged(jlong streamHandle, jobject codecPrivateData, jlong trackId); + void createDeviceResult(jlong clientHandle, jint httpStatusCode, jstring deviceArn); + void deviceCertToTokenResult(jlong clientHandle, jint httpStatusCode, jbyteArray token, jint tokenSize, jlong expiration); + void kinesisVideoStreamFragmentAck(jlong streamHandle, jlong uploadHandle, jobject fragmentAck); + void kinesisVideoStreamParseFragmentAck(jlong streamHandle, jlong uploadHandle, jstring ack); +private: + BOOL setCallbacks(JNIEnv* env, jobject thiz); +}; + +#endif // __KINESIS_VIDEO_PRODUCER_CLIENT_WRAPPER_H__ diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/Parameters.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/Parameters.h new file mode 100644 index 000000000..119c66263 --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/Parameters.h @@ -0,0 +1,20 @@ +/** + * Parameter conversion for JNI calls + */ +#ifndef __KINESIS_VIDEO_PARAMETERS_CONVERSION_H__ +#define __KINESIS_VIDEO_PARAMETERS_CONVERSION_H__ + +#pragma once + +BOOL setDeviceInfo(JNIEnv* env, jobject deviceInfo, PDeviceInfo pDeviceInfo); +BOOL setClientInfo(JNIEnv* env, jobject clientInfo, PClientInfo pClientInfo); +BOOL setStreamInfo(JNIEnv* env, jobject streamInfo, PStreamInfo pStreamInfo); +BOOL setFrame(JNIEnv* env, jobject kinesisVideoFrame, PFrame pFrame); +BOOL setFragmentAck(JNIEnv* env, jobject fragmentAck, PFragmentAck pFragmentAck); +BOOL setStreamDescription(JNIEnv* env, jobject streamDescription, PStreamDescription pStreamDesc); +BOOL setStreamingEndpoint(JNIEnv* env, jstring streamingEndpoint, PCHAR pEndpoint); +BOOL setStreamDataBuffer(JNIEnv* env, jobject dataBuffer, UINT32 offset, PBYTE* ppBuffer); +BOOL releaseStreamDataBuffer(JNIEnv* env, jobject dataBuffer, UINT32 offset, PBYTE pBuffer); +BOOL setTags(JNIEnv *env, jobjectArray tagArray, PTag* ppTags, PUINT32 pTagCount); +VOID releaseTags(PTag tags); +#endif // __KINESIS_VIDEO_PARAMETERS_CONVERSION_H__ diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/SyncMutex.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/SyncMutex.h new file mode 100644 index 000000000..2dd0a3942 --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/SyncMutex.h @@ -0,0 +1,157 @@ +/** + * A version of Android's Mutex object that can be entered multiple times by the same thread and can log its activity. + * Typically used to protect code regions from concurrent entry by more than one thread. + */ + +#ifndef __SYNC_MUTEX_H__ +#define __SYNC_MUTEX_H__ + +#include + +static const size_t gMaxMutexDescriptionSize = 100; +static const char* const gDefaultMutexDescription = "mutex"; + +class SyncMutex +{ + // Logging behavior + char mMutexDescription[gMaxMutexDescriptionSize]; + bool mLogsEnabled; + + // Mutex implementation primitives + MUTEX mMutex; + CVAR mCondition; + + SyncMutex(const SyncMutex&); // Prevent copies + SyncMutex& operator=(const SyncMutex&); // Prevent assignment + +public: + + SyncMutex() + { + initialize(); + } + + SyncMutex(const char* mutexName) + { + initialize(); + setName(mutexName); + } + + void initialize() + { + // Default logging configuration: none + setName(gDefaultMutexDescription); + mLogsEnabled = false; + + // Prepare pthreads primitives + mMutex = MUTEX_CREATE(TRUE); + mCondition = CVAR_CREATE(); + } + + ~SyncMutex() + { + MUTEX_FREE(mMutex); + CVAR_FREE(mCondition); + } + + // Set the mutex name to be shown in log messages. + void setName(const char* mutexName) + { + CHECK(mutexName); + STRNCPY(mMutexDescription, mutexName, sizeof mMutexDescription); + mMutexDescription[sizeof mMutexDescription - 1] = '\0'; // Don't trust strncpy() to NUL-terminate + } + + // Set the mutex name and associated media type to be shown in log messages. + void setName(const char* mutexName, const char* mediaType) + { + CHECK(mutexName); + CHECK(mediaType); + snprintf(mMutexDescription, sizeof mMutexDescription, "%s[%s]", mutexName, mediaType); + mMutexDescription[sizeof mMutexDescription - 1] = '\0'; // Don't trust snprintf() to NUL-terminate + } + + // Enable or disable detailed activity traces. + void setLoggingEnabled(bool enabled) + { + mLogsEnabled = enabled; + } + + // Acquire the mutex. + void lock(const char* function) + { + if (mLogsEnabled) + { + DLOGI("%s: locking %s", function, mMutexDescription); + } + + MUTEX_LOCK(mMutex); + } + + // Release the mutex. + void unlock(const char* function) + { + if (mLogsEnabled) + { + DLOGI("%s: unlocking %s", function, mMutexDescription); + } + + MUTEX_UNLOCK(mMutex); + } + + // Acquire the mutex and wait on the condition variable. + void wait(const char* function) + { + MUTEX_LOCK(mMutex); + + UINT64 before = 0; + if (mLogsEnabled) + { + DLOGI("%s: waiting on %s", function, mMutexDescription); + UINT64 before = GETTIME(); + } + + int status = CVAR_WAIT(mCondition, mMutex, INFINITE_TIME_VALUE); + CHECK_EXT(status == 0, "pthread_cond_wait() returned Unix errno %d", status); + + if (mLogsEnabled) + { + UINT64 after = GETTIME(); + UINT64 elapsed_ms = (after - before) / HUNDREDS_OF_NANOS_IN_A_MILLISECOND; + DLOGI("%s: waited %ldms for %s", function, elapsed_ms, mMutexDescription); + } + + MUTEX_UNLOCK(mMutex); + } + + // Signal the condition variable, allowing all blocked threads to continue. + void notifyAll(const char* function) + { + if (mLogsEnabled) + { + DLOGI("%s: signalling %s", function, mMutexDescription); + } + + STATUS status = CVAR_BROADCAST(mCondition); + CHECK_EXT(STATUS_SUCCEEDED(status), "pthread_cond_broadcast() returned Unix errno %d", status); + } + + /** + * Autolock: Helper object that locks a given mutex on creation and unlocks it again on destruction. This is + * typically used with automatic scope as a way to ensure that the mutex is unlocked no matter how the scope is + * exited (goto, return, etc). + */ + class Autolock { + + SyncMutex& mLock; + const char* mFunction; + + public: + Autolock(SyncMutex& mutex, const char* function) : mLock(mutex), mFunction(function) {mLock.lock(function);} + Autolock(SyncMutex* mutex, const char* function) : mLock(*mutex), mFunction(function) {mLock.lock(function);} + ~Autolock() {mLock.unlock(mFunction);} + }; + +}; + +#endif // __SYNC_MUTEX_H__ diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/TimedSemaphore.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/TimedSemaphore.h new file mode 100644 index 000000000..dfda7e97c --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/TimedSemaphore.h @@ -0,0 +1,125 @@ +/** + * Implementation of counting semaphores that support operation timeouts using condition variables. + * Useful on platforms whose runtime library lacks a sem_timedwait() implementation. + */ + +#ifndef __TIMED_SEMAPHORE_H__ +#define __TIMED_SEMAPHORE_H__ + +class TimedSemaphore +{ + TimedSemaphore(const TimedSemaphore&); // Prevent copy construction + TimedSemaphore& operator=(const TimedSemaphore&); // Prevent assignment + MUTEX mMutex; + CVAR mCond; + UINT32 mCount; + +public: + + TimedSemaphore() : mCount(0) + { + mMutex = MUTEX_CREATE(FALSE); + mCond = CVAR_CREATE(); + } + + ~TimedSemaphore() + { + MUTEX_FREE(mMutex); + CVAR_FREE(mCond); + } + + /** + * Waits until the semaphore count becomes positive, if necessary, then decrements it. + */ + void wait() + { + MUTEX_LOCK(mMutex); + + while (mCount <= 0) + { + if (STATUS_FAILED(CVAR_WAIT(mCond, mMutex, INFINITE_TIME_VALUE))) + { + CRASH("Fatal error in semaphore wait"); + break; + } + } + + mCount--; + + MUTEX_UNLOCK(mMutex); + } + + /** + * Decrements the semaphore count and returns true iff it is still positive. Does not block. + */ + bool tryWait() + { + MUTEX_LOCK(mMutex); + + bool sem = (mCount > 0); + if (sem) + { + mCount--; + } + + MUTEX_UNLOCK(mMutex); + return sem; + } + + /** + * Waits until the semaphore count becomes positive, if necessary, then decrements it and returns true. + * If the wait takes longer than the specified period of time, it gives up and returns false. + */ + bool timedWait(UINT32 relTimeOutMs) + { + MUTEX_LOCK(mMutex); + BOOL retVal = true; + + // Create default units duration + UINT64 duration = relTimeOutMs * HUNDREDS_OF_NANOS_IN_A_MILLISECOND; + + while (mCount == 0) + { + STATUS status = CVAR_WAIT(mCond, mMutex, duration); + if (status == STATUS_OPERATION_TIMED_OUT) + { + retVal = false; + break; + } + + // Any other failure is fatal + if (STATUS_FAILED(status)) + { + CRASH("Fatal error in timed semaphore wait"); + + // Unreachable - just to keep some static code analysis tools happy + break; + } + } + mCount--; + + MUTEX_UNLOCK(mMutex); + return retVal; + } + + /** + * Increments the semaphore count, and if it was zero, marks a thread (if any) currently waiting on this semaphore + * as ready-to-run. If multiple threads are waiting, only one will be woken, but it is not specified which. + * + * This method never blocks. + */ + void post() + { + MUTEX_LOCK(mMutex); + + mCount++; + + if (mCount == 1 && STATUS_FAILED(CVAR_SIGNAL(mCond))) { + CRASH("Signaling a condition variable failed; it probably wasn't initialized (errno = %d)", errno); + } + + MUTEX_UNLOCK(mMutex); + } +}; + +#endif // __TIMED_SEMAPHORE_H__ diff --git a/jni/include/com/amazonaws/kinesis/video/producer/jni/com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni.h b/jni/include/com/amazonaws/kinesis/video/producer/jni/com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni.h new file mode 100644 index 000000000..f4b8120af --- /dev/null +++ b/jni/include/com/amazonaws/kinesis/video/producer/jni/com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni.h @@ -0,0 +1,216 @@ +#include +/* Header for class com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni */ + +#ifndef _Included_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni +#define _Included_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni +#ifdef __cplusplus +extern "C" { +#endif +#undef com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_INVALID_CLIENT_HANDLE_VALUE +#define com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_INVALID_CLIENT_HANDLE_VALUE 0LL +#undef com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_INVALID_STREAM_HANDLE_VALUE +#define com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_INVALID_STREAM_HANDLE_VALUE 0LL +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getNativeLibraryVersion + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getNativeLibraryVersion + (JNIEnv *, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getNativeCodeCompileTime + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getNativeCodeCompileTime + (JNIEnv *, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: createKinesisVideoClient + * Signature: (Lcom/amazonaws/kinesisvideo/producer/DeviceInfo;)J + */ +JNIEXPORT jlong JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoClient + (JNIEnv *, jobject, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: freeKinesisVideoClient + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_freeKinesisVideoClient + (JNIEnv *, jobject, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: stopKinesisVideoStreams + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_stopKinesisVideoStreams + (JNIEnv *, jobject, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: stopKinesisVideoStream + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_stopKinesisVideoStream +(JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: freeKinesisVideoStream + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_freeKinesisVideoStream + (JNIEnv *, jobject, jlong, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getKinesisVideoMetrics + * Signature: (JLcom/amazonaws/kinesisvideo/internal/producer/KinesisVideoMetrics;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoMetrics +(JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getKinesisVideoStreamMetrics + * Signature: (JJLcom/amazonaws/kinesisvideo/internal/producer/KinesisVideoStreamMetrics;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoStreamMetrics +(JNIEnv *, jobject, jlong, jlong, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: createKinesisVideoStream + * Signature: (JLcom/amazonaws/kinesisvideo/producer/StreamInfo;)J + */ +JNIEXPORT jlong JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createKinesisVideoStream + (JNIEnv *, jobject, jlong, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: putKinesisVideoFrame + * Signature: (JJLcom/amazonaws/kinesisvideo/producer/KinesisVideoFrame;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putKinesisVideoFrame + (JNIEnv *, jobject, jlong, jlong, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: putKinesisVideoFragmentMetadata + * Signature: (JJLjava/lang/String;Ljava/lang/String;Z)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putKinesisVideoFragmentMetadata + (JNIEnv*, jobject, jlong, jlong, jstring, jstring, jboolean); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: kinesisVideoStreamFragmentAck + * Signature: (JJJLcom/amazonaws/kinesisvideo/producer/KinesisVideoFragmentAck;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamFragmentAck + (JNIEnv *, jobject, jlong, jlong, jlong, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: kinesisVideoStreamParseFragmentAck + * Signature: (JJJLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamParseFragmentAck +(JNIEnv *, jobject, jlong, jlong, jlong, jstring); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: describeStreamResultEvent + * Signature: (JJILcom/amazonaws/kinesisvideo/producer/StreamDescription;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_describeStreamResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getStreamingEndpointResultEvent + * Signature: (JJILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getStreamingEndpointResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jstring); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getStreamingTokenResultEvent + * Signature: (JJI[BIJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getStreamingTokenResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jobject, jint, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: createStreamResultEvent + * Signature: (JJILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createStreamResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jstring); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: putStreamResultEvent + * Signature: (JJIJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_putStreamResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jlong); + +/* +* Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni +* Method: tagResourceResultEvent +* Signature: (JJI)V +*/ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_tagResourceResultEvent + (JNIEnv *, jobject, jlong, jlong, jint); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: getKinesisVideoStreamData + * Signature: (JJJ[BIILcom/amazonaws/kinesisvideo/internal/producer/ReadResult;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_getKinesisVideoStreamData + (JNIEnv *, jobject, jlong, jlong, jlong, jobject, jint, jint, jobject); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: kinesisVideoStreamFormatChanged + * Signature: (JJ[BJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamFormatChanged + (JNIEnv *, jobject, jlong, jlong, jobject, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: createDeviceResultEvent + * Signature: (JJILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_createDeviceResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jstring); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: deviceCertToTokenResultEvent + * Signature: (JJI[BIJ)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_deviceCertToTokenResultEvent + (JNIEnv *, jobject, jlong, jlong, jint, jobject, jint, jlong); + +/* + * Class: com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni + * Method: kinesisVideoStreamTerminated + * Signature: (JJJI)V + */ +JNIEXPORT void JNICALL Java_com_amazonaws_kinesisvideo_internal_producer_jni_NativeKinesisVideoProducerJni_kinesisVideoStreamTerminated +(JNIEnv *, jobject, jlong, jlong, jlong, jint); + +#ifdef __cplusplus +} +#endif +#endif