From b10b19b14a0ed53413f9f16c2354e3bfba11031d Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 17 Oct 2024 03:59:37 +0700 Subject: [PATCH 1/7] Update to roc-toolkit 0.3.0 --- README.md | 24 +- eclipse-format-prefs.xml | 1 + roc_jni/CMakeLists.txt | 69 ++-- .../org_rocstreaming_roctoolkit_RocContext.h | 16 +- .../org_rocstreaming_roctoolkit_RocReceiver.h | 40 +- .../org_rocstreaming_roctoolkit_RocSender.h | 40 +- roc_jni/src/main/impl/channel_layout.c | 13 + roc_jni/src/main/impl/channel_layout.h | 11 + roc_jni/src/main/impl/channel_set.c | 13 - roc_jni/src/main/impl/channel_set.h | 11 - roc_jni/src/main/impl/clock_source.c | 4 +- roc_jni/src/main/impl/clock_source.h | 2 +- roc_jni/src/main/impl/clock_sync_backend.c | 13 + roc_jni/src/main/impl/clock_sync_backend.h | 11 + roc_jni/src/main/impl/clock_sync_profile.c | 13 + roc_jni/src/main/impl/clock_sync_profile.h | 11 + roc_jni/src/main/impl/common.c | 38 +- roc_jni/src/main/impl/common.h | 14 +- roc_jni/src/main/impl/context.c | 51 ++- roc_jni/src/main/impl/context_config.c | 24 ++ roc_jni/src/main/impl/context_config.h | 11 + roc_jni/src/main/impl/endpoint.c | 32 +- roc_jni/src/main/impl/endpoint.h | 2 + roc_jni/src/main/impl/fec_encoding.c | 4 +- roc_jni/src/main/impl/fec_encoding.h | 2 +- roc_jni/src/main/impl/format.c | 11 + roc_jni/src/main/impl/format.h | 11 + roc_jni/src/main/impl/frame_encoding.c | 11 - roc_jni/src/main/impl/frame_encoding.h | 11 - roc_jni/src/main/impl/interface_config.c | 54 +++ roc_jni/src/main/impl/interface_config.h | 11 + roc_jni/src/main/impl/media_encoding.c | 36 ++ roc_jni/src/main/impl/media_encoding.h | 11 + roc_jni/src/main/impl/packet_encoding.c | 4 +- roc_jni/src/main/impl/packet_encoding.h | 2 +- roc_jni/src/main/impl/receiver.c | 155 +++----- roc_jni/src/main/impl/receiver_config.c | 79 ++++ roc_jni/src/main/impl/receiver_config.h | 11 + roc_jni/src/main/impl/resampler_backend.c | 4 +- roc_jni/src/main/impl/resampler_backend.h | 2 +- roc_jni/src/main/impl/resampler_profile.c | 4 +- roc_jni/src/main/impl/resampler_profile.h | 2 +- roc_jni/src/main/impl/sender.c | 171 +++----- roc_jni/src/main/impl/sender_config.c | 79 ++++ roc_jni/src/main/impl/sender_config.h | 11 + .../roctoolkit/ChannelLayout.java | 45 +++ .../rocstreaming/roctoolkit/ChannelSet.java | 24 -- .../org/rocstreaming/roctoolkit/Check.java | 14 + .../rocstreaming/roctoolkit/ClockSource.java | 26 +- .../roctoolkit/ClockSyncBackend.java | 56 +++ .../roctoolkit/ClockSyncProfile.java | 67 ++++ .../org/rocstreaming/roctoolkit/Endpoint.java | 64 +-- .../rocstreaming/roctoolkit/FecEncoding.java | 48 ++- .../org/rocstreaming/roctoolkit/Format.java | 27 ++ .../roctoolkit/FrameEncoding.java | 25 -- .../rocstreaming/roctoolkit/Interface.java | 107 ++--- .../roctoolkit/InterfaceConfig.java | 86 ++++ .../roctoolkit/InterfaceConfigValidator.java | 12 + .../roctoolkit/MediaEncoding.java | 57 +++ .../roctoolkit/MediaEncodingValidator.java | 16 + .../rocstreaming/roctoolkit/NativeObject.java | 12 +- .../roctoolkit/NativeObjectCleaner.java | 18 +- .../NativeObjectPhantomReference.java | 15 +- .../roctoolkit/PacketEncoding.java | 56 ++- .../org/rocstreaming/roctoolkit/Protocol.java | 121 +++--- .../roctoolkit/ResamplerBackend.java | 61 ++- .../roctoolkit/ResamplerProfile.java | 21 +- .../rocstreaming/roctoolkit/RocContext.java | 103 +++-- .../roctoolkit/RocContextConfig.java | 23 +- .../rocstreaming/roctoolkit/RocReceiver.java | 374 ++++++++++-------- .../roctoolkit/RocReceiverConfig.java | 130 +++--- .../RocReceiverConfigValidator.java | 6 +- .../rocstreaming/roctoolkit/RocSender.java | 342 ++++++++-------- .../roctoolkit/RocSenderConfig.java | 147 +++---- .../roctoolkit/RocSenderConfigValidator.java | 3 - .../org/rocstreaming/roctoolkit/Slot.java | 2 +- .../org/rocstreaming/roctoolkit/BaseTest.java | 12 + .../roctoolkit/MediaEncodingTest.java | 41 ++ .../roctoolkit/RocContextConfigTest.java | 21 +- .../roctoolkit/RocContextTest.java | 44 +++ .../roctoolkit/RocLoggerTest.java | 93 +++-- .../roctoolkit/RocReceiverConfigTest.java | 26 +- .../roctoolkit/RocReceiverTest.java | 131 ++++-- .../roctoolkit/RocSenderConfigTest.java | 26 +- .../roctoolkit/RocSenderTest.java | 163 ++++---- .../integration/RocSenderReceiverTest.java | 20 +- 86 files changed, 2441 insertions(+), 1394 deletions(-) create mode 100644 roc_jni/src/main/impl/channel_layout.c create mode 100644 roc_jni/src/main/impl/channel_layout.h delete mode 100644 roc_jni/src/main/impl/channel_set.c delete mode 100644 roc_jni/src/main/impl/channel_set.h create mode 100644 roc_jni/src/main/impl/clock_sync_backend.c create mode 100644 roc_jni/src/main/impl/clock_sync_backend.h create mode 100644 roc_jni/src/main/impl/clock_sync_profile.c create mode 100644 roc_jni/src/main/impl/clock_sync_profile.h create mode 100644 roc_jni/src/main/impl/context_config.c create mode 100644 roc_jni/src/main/impl/context_config.h create mode 100644 roc_jni/src/main/impl/format.c create mode 100644 roc_jni/src/main/impl/format.h delete mode 100644 roc_jni/src/main/impl/frame_encoding.c delete mode 100644 roc_jni/src/main/impl/frame_encoding.h create mode 100644 roc_jni/src/main/impl/interface_config.c create mode 100644 roc_jni/src/main/impl/interface_config.h create mode 100644 roc_jni/src/main/impl/media_encoding.c create mode 100644 roc_jni/src/main/impl/media_encoding.h create mode 100644 roc_jni/src/main/impl/receiver_config.c create mode 100644 roc_jni/src/main/impl/receiver_config.h create mode 100644 roc_jni/src/main/impl/sender_config.c create mode 100644 roc_jni/src/main/impl/sender_config.h create mode 100644 src/main/java/org/rocstreaming/roctoolkit/ChannelLayout.java delete mode 100644 src/main/java/org/rocstreaming/roctoolkit/ChannelSet.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/ClockSyncBackend.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/ClockSyncProfile.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/Format.java delete mode 100644 src/main/java/org/rocstreaming/roctoolkit/FrameEncoding.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java create mode 100644 src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java diff --git a/README.md b/README.md index 733fcf2b..53e0e2b5 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,17 @@ Documentation for the C API can be found [here](https://roc-streaming.org/toolki #### Sender ```java -import org.rocstreaming.roctoolkit; +import org.rocstreaming.roctoolkit.*; try (RocContext context = new RocContext()) { RocSenderConfig config = RocSenderConfig.builder() - .frameSampleRate(44100) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .fecEncoding(FecEncoding.RS8M) .clockSource(ClockSource.INTERNAL) .build(); @@ -67,13 +71,17 @@ try (RocContext context = new RocContext()) { #### Receiver ```java -import org.rocstreaming.roctoolkit; +import org.rocstreaming.roctoolkit.*; try (RocContext context = new RocContext()) { RocReceiverConfig config = RocReceiverConfig.builder() - .frameSampleRate(44100) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .clockSource(ClockSource.INTERNAL) .build(); diff --git a/eclipse-format-prefs.xml b/eclipse-format-prefs.xml index 3c013a39..dc3ca9e2 100644 --- a/eclipse-format-prefs.xml +++ b/eclipse-format-prefs.xml @@ -8,6 +8,7 @@ + diff --git a/roc_jni/CMakeLists.txt b/roc_jni/CMakeLists.txt index c4923d13..05775923 100644 --- a/roc_jni/CMakeLists.txt +++ b/roc_jni/CMakeLists.txt @@ -1,3 +1,4 @@ +# -*- cmake-tab-width: 4 -*- cmake_minimum_required(VERSION 3.10) project(RocJni C) @@ -12,21 +13,28 @@ if(APPLE) endif() add_library(roc_jni SHARED - src/main/impl/channel_set.c - src/main/impl/clock_source.c - src/main/impl/common.c - src/main/impl/context.c - src/main/impl/endpoint.c - src/main/impl/fec_encoding.c - src/main/impl/frame_encoding.c - src/main/impl/logger.c - src/main/impl/packet_encoding.c - src/main/impl/protocol.c - src/main/impl/receiver.c - src/main/impl/resampler_backend.c - src/main/impl/resampler_profile.c - src/main/impl/sender.c - ) + src/main/impl/channel_layout.c + src/main/impl/clock_source.c + src/main/impl/clock_sync_backend.c + src/main/impl/clock_sync_profile.c + src/main/impl/common.c + src/main/impl/context.c + src/main/impl/context_config.c + src/main/impl/endpoint.c + src/main/impl/fec_encoding.c + src/main/impl/format.c + src/main/impl/interface_config.c + src/main/impl/logger.c + src/main/impl/media_encoding.c + src/main/impl/packet_encoding.c + src/main/impl/protocol.c + src/main/impl/receiver.c + src/main/impl/receiver_config.c + src/main/impl/resampler_backend.c + src/main/impl/resampler_profile.c + src/main/impl/sender.c + src/main/impl/sender_config.c +) target_compile_options(roc_jni PRIVATE -Wall @@ -34,7 +42,7 @@ target_compile_options(roc_jni PRIVATE -Wno-system-headers -Wno-unused-but-set-variable -Wno-unused-parameter - ) +) if(NOT ANDROID) if (NOT CMAKE_CROSSCOMPILING) @@ -45,19 +53,19 @@ if(NOT ANDROID) endif() target_include_directories(roc_jni - PRIVATE src/main/impl/ - PUBLIC src/main/export/ - ) + PRIVATE src/main/impl/ + PUBLIC src/main/export/ +) if(ANDROID) # include libroc headers target_include_directories(roc_jni SYSTEM PRIVATE - ${ROC_DIR}/include/${ANDROID_ABI}) + ${ROC_DIR}/include/${ANDROID_ABI}) # link libroc shared library add_library(lib_roc SHARED IMPORTED) set_target_properties(lib_roc PROPERTIES IMPORTED_LOCATION - ${ROC_DIR}/lib/${ANDROID_ABI}/libroc.so) + ${ROC_DIR}/lib/${ANDROID_ABI}/libroc.so) target_link_libraries(roc_jni lib_roc) else() # include libroc headers @@ -75,22 +83,27 @@ else() AND NOT ROC_LIBRARY_PATH STREQUAL "") add_library(lib_roc SHARED IMPORTED) set_target_properties(lib_roc PROPERTIES IMPORTED_LOCATION - ${ROC_LIBRARY_PATH}/libroc${CMAKE_SHARED_LIBRARY_SUFFIX}) + ${ROC_LIBRARY_PATH}/libroc${CMAKE_SHARED_LIBRARY_SUFFIX}) target_link_libraries(roc_jni lib_roc) elseif(DEFINED ENV{ROC_LIBRARY_PATH} AND NOT ENV{ROC_LIBRARY_PATH} STREQUAL "") add_library(lib_roc SHARED IMPORTED) set_target_properties(lib_roc PROPERTIES IMPORTED_LOCATION - $ENV{ROC_LIBRARY_PATH}/libroc${CMAKE_SHARED_LIBRARY_SUFFIX}) + $ENV{ROC_LIBRARY_PATH}/libroc${CMAKE_SHARED_LIBRARY_SUFFIX}) target_link_libraries(roc_jni lib_roc) else() target_link_libraries(roc_jni -lroc) endif() endif() -add_custom_command(TARGET roc_jni POST_BUILD +add_custom_command( COMMENT "Copying compile_commands.json to project root" - COMMAND "${CMAKE_COMMAND}" -E copy - "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" - "${PROJECT_SOURCE_DIR}/compile_commands.json" - ) + DEPENDS roc_jni + OUTPUT ${PROJECT_SOURCE_DIR}/compile_commands.json + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json + ${PROJECT_SOURCE_DIR}/compile_commands.json +) +add_custom_target(compile_commands ALL + DEPENDS ${PROJECT_SOURCE_DIR}/compile_commands.json +) diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocContext.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocContext.h index 8735f5f4..1fbe405a 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocContext.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocContext.h @@ -9,20 +9,28 @@ extern "C" { #endif /* * Class: org_rocstreaming_roctoolkit_RocContext - * Method: open + * Method: nativeOpen * Signature: (Lorg/rocstreaming/roctoolkit/RocContextConfig;)J */ -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_open +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeOpen (JNIEnv *, jclass, jobject); /* * Class: org_rocstreaming_roctoolkit_RocContext - * Method: close + * Method: nativeClose * Signature: (J)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_close +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeClose (JNIEnv *, jclass, jlong); +/* + * Class: org_rocstreaming_roctoolkit_RocContext + * Method: nativeRegisterEncoding + * Signature: (JILorg/rocstreaming/roctoolkit/MediaEncoding;)V + */ +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeRegisterEncoding + (JNIEnv *, jclass, jlong, jint, jobject); + #ifdef __cplusplus } #endif diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h index 17ef7d11..dcbcd426 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocReceiver.h @@ -9,43 +9,51 @@ extern "C" { #endif /* * Class: org_rocstreaming_roctoolkit_RocReceiver - * Method: open + * Method: nativeOpen * Signature: (JLorg/rocstreaming/roctoolkit/RocReceiverConfig;)J */ -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_open +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeOpen (JNIEnv *, jclass, jlong, jobject); /* * Class: org_rocstreaming_roctoolkit_RocReceiver - * Method: setMulticastGroup - * Signature: (JIILjava/lang/String;)V + * Method: nativeClose + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_setMulticastGroup - (JNIEnv *, jobject, jlong, jint, jint, jstring); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeClose + (JNIEnv *, jclass, jlong); + +/* + * Class: org_rocstreaming_roctoolkit_RocReceiver + * Method: nativeConfigure + * Signature: (JIILorg/rocstreaming/roctoolkit/InterfaceConfig;)V + */ +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeConfigure + (JNIEnv *, jobject, jlong, jint, jint, jobject); /* * Class: org_rocstreaming_roctoolkit_RocReceiver - * Method: bind + * Method: nativeBind * Signature: (JIILorg/rocstreaming/roctoolkit/Endpoint;)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_bind +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeBind (JNIEnv *, jobject, jlong, jint, jint, jobject); /* * Class: org_rocstreaming_roctoolkit_RocReceiver - * Method: readFloats - * Signature: (J[F)V + * Method: nativeUnlink + * Signature: (JI)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_readFloats - (JNIEnv *, jobject, jlong, jfloatArray); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeUnlink + (JNIEnv *, jobject, jlong, jint); /* * Class: org_rocstreaming_roctoolkit_RocReceiver - * Method: close - * Signature: (J)V + * Method: nativeReadFloats + * Signature: (J[F)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_close - (JNIEnv *, jclass, jlong); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeReadFloats + (JNIEnv *, jobject, jlong, jfloatArray); #ifdef __cplusplus } diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h index 02825f27..5674e881 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocSender.h @@ -9,43 +9,51 @@ extern "C" { #endif /* * Class: org_rocstreaming_roctoolkit_RocSender - * Method: open + * Method: nativeOpen * Signature: (JLorg/rocstreaming/roctoolkit/RocSenderConfig;)J */ -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_open +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeOpen (JNIEnv *, jclass, jlong, jobject); /* * Class: org_rocstreaming_roctoolkit_RocSender - * Method: setOutgoingAddress - * Signature: (JIILjava/lang/String;)V + * Method: nativeClose + * Signature: (J)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_setOutgoingAddress - (JNIEnv *, jobject, jlong, jint, jint, jstring); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeClose + (JNIEnv *, jclass, jlong); + +/* + * Class: org_rocstreaming_roctoolkit_RocSender + * Method: nativeConfigure + * Signature: (JIILorg/rocstreaming/roctoolkit/InterfaceConfig;)V + */ +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConfigure + (JNIEnv *, jobject, jlong, jint, jint, jobject); /* * Class: org_rocstreaming_roctoolkit_RocSender - * Method: connect + * Method: nativeConnect * Signature: (JIILorg/rocstreaming/roctoolkit/Endpoint;)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_connect +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConnect (JNIEnv *, jobject, jlong, jint, jint, jobject); /* * Class: org_rocstreaming_roctoolkit_RocSender - * Method: writeFloats - * Signature: (J[F)V + * Method: nativeUnlink + * Signature: (JI)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_writeFloats - (JNIEnv *, jobject, jlong, jfloatArray); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeUnlink + (JNIEnv *, jobject, jlong, jint); /* * Class: org_rocstreaming_roctoolkit_RocSender - * Method: close - * Signature: (J)V + * Method: nativeWriteFloats + * Signature: (J[F)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_close - (JNIEnv *, jclass, jlong); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeWriteFloats + (JNIEnv *, jobject, jlong, jfloatArray); #ifdef __cplusplus } diff --git a/roc_jni/src/main/impl/channel_layout.c b/roc_jni/src/main/impl/channel_layout.c new file mode 100644 index 00000000..1bbf7397 --- /dev/null +++ b/roc_jni/src/main/impl/channel_layout.c @@ -0,0 +1,13 @@ +#include "channel_layout.h" +#include "common.h" + +#include + +roc_channel_layout get_channel_layout(JNIEnv* env, jobject jchannelLayout) { + jclass channelLayoutClass = NULL; + + channelLayoutClass = (*env)->FindClass(env, CHANNEL_LAYOUT_CLASS); + assert(channelLayoutClass != NULL); + + return (roc_channel_layout) get_enum_value(env, channelLayoutClass, jchannelLayout); +} diff --git a/roc_jni/src/main/impl/channel_layout.h b/roc_jni/src/main/impl/channel_layout.h new file mode 100644 index 00000000..c1f5ab62 --- /dev/null +++ b/roc_jni/src/main/impl/channel_layout.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define CHANNEL_LAYOUT_CLASS PACKAGE_BASE_NAME "/ChannelLayout" + +roc_channel_layout get_channel_layout(JNIEnv* env, jobject jchannelLayout); diff --git a/roc_jni/src/main/impl/channel_set.c b/roc_jni/src/main/impl/channel_set.c deleted file mode 100644 index a781f1e1..00000000 --- a/roc_jni/src/main/impl/channel_set.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "channel_set.h" -#include "common.h" - -#include - -roc_channel_set get_channel_set(JNIEnv* env, jobject jchannel_set) { - jclass channelSetClass = NULL; - - channelSetClass = (*env)->FindClass(env, CHANNEL_SET_CLASS); - assert(channelSetClass != NULL); - - return (roc_channel_set) get_enum_value(env, channelSetClass, jchannel_set); -} diff --git a/roc_jni/src/main/impl/channel_set.h b/roc_jni/src/main/impl/channel_set.h deleted file mode 100644 index afbdeb0b..00000000 --- a/roc_jni/src/main/impl/channel_set.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define CHANNEL_SET_CLASS PACKAGE_BASE_NAME "/ChannelSet" - -roc_channel_set get_channel_set(JNIEnv* env, jobject jchannel_set); diff --git a/roc_jni/src/main/impl/clock_source.c b/roc_jni/src/main/impl/clock_source.c index afc6eebd..c3efe744 100644 --- a/roc_jni/src/main/impl/clock_source.c +++ b/roc_jni/src/main/impl/clock_source.c @@ -3,11 +3,11 @@ #include -roc_clock_source get_clock_source(JNIEnv* env, jobject jclock_source) { +roc_clock_source get_clock_source(JNIEnv* env, jobject jclockSource) { jclass clockSourceClass = NULL; clockSourceClass = (*env)->FindClass(env, CLOCK_SOURCE_CLASS); assert(clockSourceClass != NULL); - return (roc_clock_source) get_enum_value(env, clockSourceClass, jclock_source); + return (roc_clock_source) get_enum_value(env, clockSourceClass, jclockSource); } diff --git a/roc_jni/src/main/impl/clock_source.h b/roc_jni/src/main/impl/clock_source.h index eee68e88..53ec84fd 100644 --- a/roc_jni/src/main/impl/clock_source.h +++ b/roc_jni/src/main/impl/clock_source.h @@ -8,4 +8,4 @@ #define CLOCK_SOURCE_CLASS PACKAGE_BASE_NAME "/ClockSource" -roc_clock_source get_clock_source(JNIEnv* env, jobject jclock_source); +roc_clock_source get_clock_source(JNIEnv* env, jobject jclockSource); diff --git a/roc_jni/src/main/impl/clock_sync_backend.c b/roc_jni/src/main/impl/clock_sync_backend.c new file mode 100644 index 00000000..9b180f8a --- /dev/null +++ b/roc_jni/src/main/impl/clock_sync_backend.c @@ -0,0 +1,13 @@ +#include "clock_sync_backend.h" +#include "common.h" + +#include + +roc_clock_sync_backend get_clock_sync_backend(JNIEnv* env, jobject jclockSyncBackend) { + jclass clockSyncBackend = NULL; + + clockSyncBackend = (*env)->FindClass(env, CLOCK_SYNC_BACKEND_CLASS); + assert(clockSyncBackend != NULL); + + return (roc_clock_sync_backend) get_enum_value(env, clockSyncBackend, jclockSyncBackend); +} diff --git a/roc_jni/src/main/impl/clock_sync_backend.h b/roc_jni/src/main/impl/clock_sync_backend.h new file mode 100644 index 00000000..5bc2050d --- /dev/null +++ b/roc_jni/src/main/impl/clock_sync_backend.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define CLOCK_SYNC_BACKEND_CLASS PACKAGE_BASE_NAME "/ClockSyncBackend" + +roc_clock_sync_backend get_clock_sync_backend(JNIEnv* env, jobject jclockSyncBackend); diff --git a/roc_jni/src/main/impl/clock_sync_profile.c b/roc_jni/src/main/impl/clock_sync_profile.c new file mode 100644 index 00000000..027b3119 --- /dev/null +++ b/roc_jni/src/main/impl/clock_sync_profile.c @@ -0,0 +1,13 @@ +#include "clock_sync_profile.h" +#include "common.h" + +#include + +roc_clock_sync_profile get_clock_sync_profile(JNIEnv* env, jobject jclockSyncProfile) { + jclass clockSyncProfile = NULL; + + clockSyncProfile = (*env)->FindClass(env, CLOCK_SYNC_PROFILE_CLASS); + assert(clockSyncProfile != NULL); + + return (roc_clock_sync_profile) get_enum_value(env, clockSyncProfile, jclockSyncProfile); +} diff --git a/roc_jni/src/main/impl/clock_sync_profile.h b/roc_jni/src/main/impl/clock_sync_profile.h new file mode 100644 index 00000000..dd09c5b8 --- /dev/null +++ b/roc_jni/src/main/impl/clock_sync_profile.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define CLOCK_SYNC_PROFILE_CLASS PACKAGE_BASE_NAME "/ClockSyncProfile" + +roc_clock_sync_profile get_clock_sync_profile(JNIEnv* env, jobject jclockSyncProfile); diff --git a/roc_jni/src/main/impl/common.c b/roc_jni/src/main/impl/common.c index a018309a..3b08b66c 100644 --- a/roc_jni/src/main/impl/common.c +++ b/roc_jni/src/main/impl/common.c @@ -3,23 +3,23 @@ #include int get_boolean_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "Z"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "Z"); assert(attrId != NULL); return (*env)->GetBooleanField(env, obj, attrId) == JNI_TRUE; } -int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { +int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - assert(attr_name != NULL); + assert(attrName != NULL); assert(error != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "I"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "I"); assert(attrId != NULL); jint ret = (*env)->GetIntField(env, obj, attrId); @@ -32,13 +32,13 @@ int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attr } unsigned int get_uint_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - assert(attr_name != NULL); + assert(attrName != NULL); assert(error != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "I"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "I"); assert(attrId != NULL); jint ret = (*env)->GetIntField(env, obj, attrId); @@ -51,13 +51,13 @@ unsigned int get_uint_field_value( } long long get_llong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - assert(attr_name != NULL); + assert(attrName != NULL); assert(error != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "J"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "J"); assert(attrId != NULL); jlong ret = (*env)->GetLongField(env, obj, attrId); @@ -67,13 +67,13 @@ long long get_llong_field_value( } unsigned long long get_ullong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - assert(attr_name != NULL); + assert(attrName != NULL); assert(error != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "J"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "J"); assert(attrId != NULL); jlong ret = (*env)->GetLongField(env, obj, attrId); @@ -86,13 +86,13 @@ unsigned long long get_ullong_field_value( } long long get_duration_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { assert(env != NULL); assert(clazz != NULL); - assert(attr_name != NULL); + assert(attrName != NULL); assert(error != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, "Ljava/time/Duration;"); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "Ljava/time/Duration;"); assert(attrId != NULL); jobject durationObj = (*env)->GetObjectField(env, obj, attrId); @@ -123,11 +123,11 @@ int get_enum_value(JNIEnv* env, jclass clazz, jobject enumObj) { } jobject get_object_field( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, const char* attr_class_name) { + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, const char* attrClassName) { assert(env != NULL); assert(clazz != NULL); - jfieldID attrId = (*env)->GetFieldID(env, clazz, attr_name, attr_class_name); + jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, attrClassName); assert(attrId != NULL); return (*env)->GetObjectField(env, obj, attrId); diff --git a/roc_jni/src/main/impl/common.h b/roc_jni/src/main/impl/common.h index 983d1343..875d8ebb 100644 --- a/roc_jni/src/main/impl/common.h +++ b/roc_jni/src/main/impl/common.h @@ -13,20 +13,20 @@ #define IO_EXCEPTION "java/io/IOException" int get_boolean_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); -int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); +int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); unsigned int get_uint_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); long long get_llong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); unsigned long long get_ullong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); long long get_duration_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, int* error); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); int get_enum_value(JNIEnv* env, jclass clazz, jobject enumObj); jobject get_object_field( - JNIEnv* env, jclass clazz, jobject obj, const char* attr_name, const char* attr_class_name); + JNIEnv* env, jclass clazz, jobject obj, const char* attrName, const char* attrClassName); diff --git a/roc_jni/src/main/impl/context.c b/roc_jni/src/main/impl/context.c index 8de75282..ac9fdee7 100644 --- a/roc_jni/src/main/impl/context.c +++ b/roc_jni/src/main/impl/context.c @@ -1,32 +1,12 @@ #include "org_rocstreaming_roctoolkit_RocContext.h" #include "common.h" +#include "context_config.h" +#include "media_encoding.h" #include -#define CONTEXT_CONFIG_CLASS PACKAGE_BASE_NAME "/RocContextConfig" - -static int context_config_unmarshal(JNIEnv* env, roc_context_config* conf, jobject jconfig) { - jclass contextConfigClass = NULL; - int err = 0; - - contextConfigClass = (*env)->FindClass(env, CONTEXT_CONFIG_CLASS); - assert(contextConfigClass != NULL); - - memset(conf, 0, sizeof(roc_context_config)); - - conf->max_packet_size - = get_uint_field_value(env, contextConfigClass, jconfig, "maxPacketSize", &err); - if (err) return err; - - conf->max_frame_size - = get_uint_field_value(env, contextConfigClass, jconfig, "maxFrameSize", &err); - if (err) return err; - - return 0; -} - -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_open( +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeOpen( JNIEnv* env, jclass contextClass, jobject config) { roc_context* context = NULL; roc_context_config context_config = {}; @@ -46,13 +26,30 @@ JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_open( return (jlong) context; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_close( - JNIEnv* env, jclass contextClass, jlong nativePtr) { - - roc_context* context = (roc_context*) nativePtr; +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeClose( + JNIEnv* env, jclass contextClass, jlong contextPtr) { + roc_context* context = (roc_context*) contextPtr; if (roc_context_close(context) != 0) { jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); (*env)->ThrowNew(env, exceptionClass, "Error closing context"); } } + +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeRegisterEncoding( + JNIEnv* env, jclass contextClass, jlong contextPtr, jint encodingId, jobject jencoding) { + roc_context* context = (roc_context*) contextPtr; + roc_media_encoding encoding = {}; + + if (media_encoding_unmarshal(env, &encoding, jencoding) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Invalid MediaEncoding"); + return; + } + + if (roc_context_register_encoding(context, (int) encodingId, &encoding) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error registering encoding"); + return; + } +} diff --git a/roc_jni/src/main/impl/context_config.c b/roc_jni/src/main/impl/context_config.c new file mode 100644 index 00000000..af99523e --- /dev/null +++ b/roc_jni/src/main/impl/context_config.c @@ -0,0 +1,24 @@ +#include "context_config.h" +#include "common.h" + +#include + +int context_config_unmarshal(JNIEnv* env, roc_context_config* conf, jobject jconfig) { + jclass contextConfigClass = NULL; + int err = 0; + + contextConfigClass = (*env)->FindClass(env, CONTEXT_CONFIG_CLASS); + assert(contextConfigClass != NULL); + + memset(conf, 0, sizeof(roc_context_config)); + + conf->max_packet_size + = get_uint_field_value(env, contextConfigClass, jconfig, "maxPacketSize", &err); + if (err) return err; + + conf->max_frame_size + = get_uint_field_value(env, contextConfigClass, jconfig, "maxFrameSize", &err); + if (err) return err; + + return 0; +} diff --git a/roc_jni/src/main/impl/context_config.h b/roc_jni/src/main/impl/context_config.h new file mode 100644 index 00000000..e534aac4 --- /dev/null +++ b/roc_jni/src/main/impl/context_config.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define CONTEXT_CONFIG_CLASS PACKAGE_BASE_NAME "/RocContextConfig" + +int context_config_unmarshal(JNIEnv* env, roc_context_config* config, jobject jconfig); diff --git a/roc_jni/src/main/impl/endpoint.c b/roc_jni/src/main/impl/endpoint.c index 60e69b8e..7ef972ea 100644 --- a/roc_jni/src/main/impl/endpoint.c +++ b/roc_jni/src/main/impl/endpoint.c @@ -8,8 +8,6 @@ #include -#define ENDPOINT_CLASS PACKAGE_BASE_NAME "/Endpoint" - int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint) { jclass endpointClass = NULL; jobject jprotocol = NULL; @@ -39,16 +37,21 @@ int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint) // protocol jprotocol = get_object_field(env, endpointClass, jendpoint, "protocol", "L" PROTOCOL_CLASS ";"); - assert(jprotocol != NULL); - protocol = get_protocol(env, jprotocol); - if ((err = roc_endpoint_set_protocol(*endpoint, protocol)) != 0) goto out; + if (jprotocol != NULL) { + protocol = get_protocol(env, jprotocol); + if ((err = roc_endpoint_set_protocol(*endpoint, protocol)) != 0) goto out; + } // host jhost = (jstring) get_object_field(env, endpointClass, jendpoint, "host", "Ljava/lang/String;"); - assert(jhost != NULL); - host = (*env)->GetStringUTFChars(env, jhost, 0); - assert(host != NULL); - if ((err = roc_endpoint_set_host(*endpoint, host)) != 0) goto out; + if (jhost != NULL) { + host = (*env)->GetStringUTFChars(env, jhost, 0); + if (host == NULL) { + err = -1; + goto out; + } + if ((err = roc_endpoint_set_host(*endpoint, host)) != 0) goto out; + } // port port = get_int_field_value(env, endpointClass, jendpoint, "port", &err); @@ -60,7 +63,10 @@ int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint) env, endpointClass, jendpoint, "resource", "Ljava/lang/String;"); if (jresource != NULL) { resource = (*env)->GetStringUTFChars(env, jresource, 0); - assert(resource != NULL); + if (resource == NULL) { + err = -1; + goto out; + } if ((err = roc_endpoint_set_resource(*endpoint, resource)) != 0) goto out; } @@ -158,7 +164,11 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_init( goto out; } uri = (*env)->GetStringUTFChars(env, juri, 0); - assert(uri != NULL); + if (uri == NULL) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Bad uri argument"); + goto out; + } if (roc_endpoint_set_uri(endpoint, uri) != 0) { jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); (*env)->ThrowNew(env, exceptionClass, "Bad uri argument"); diff --git a/roc_jni/src/main/impl/endpoint.h b/roc_jni/src/main/impl/endpoint.h index c46c22e9..c04fdd62 100644 --- a/roc_jni/src/main/impl/endpoint.h +++ b/roc_jni/src/main/impl/endpoint.h @@ -4,6 +4,8 @@ #include +#define ENDPOINT_CLASS PACKAGE_BASE_NAME "/Endpoint" + int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint); void endpoint_set_port(JNIEnv* env, jobject endpoint, int port); diff --git a/roc_jni/src/main/impl/fec_encoding.c b/roc_jni/src/main/impl/fec_encoding.c index 3340c539..c28c3a81 100644 --- a/roc_jni/src/main/impl/fec_encoding.c +++ b/roc_jni/src/main/impl/fec_encoding.c @@ -3,11 +3,11 @@ #include -roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfec_encoding) { +roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfecEncoding) { jclass fecEncodingClass = NULL; fecEncodingClass = (*env)->FindClass(env, FEC_ENCODING_CLASS); assert(fecEncodingClass != NULL); - return (roc_fec_encoding) get_enum_value(env, fecEncodingClass, jfec_encoding); + return (roc_fec_encoding) get_enum_value(env, fecEncodingClass, jfecEncoding); } diff --git a/roc_jni/src/main/impl/fec_encoding.h b/roc_jni/src/main/impl/fec_encoding.h index 3d4efe46..504a005d 100644 --- a/roc_jni/src/main/impl/fec_encoding.h +++ b/roc_jni/src/main/impl/fec_encoding.h @@ -8,4 +8,4 @@ #define FEC_ENCODING_CLASS PACKAGE_BASE_NAME "/FecEncoding" -roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfec_encoding); +roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfecEncoding); diff --git a/roc_jni/src/main/impl/format.c b/roc_jni/src/main/impl/format.c new file mode 100644 index 00000000..fd651d8d --- /dev/null +++ b/roc_jni/src/main/impl/format.c @@ -0,0 +1,11 @@ +#include "format.h" +#include "common.h" + +roc_format get_format(JNIEnv* env, jobject jformat) { + jclass formatClass = NULL; + + formatClass = (*env)->FindClass(env, FORMAT_CLASS); + assert(formatClass != NULL); + + return (roc_format) get_enum_value(env, formatClass, jformat); +} diff --git a/roc_jni/src/main/impl/format.h b/roc_jni/src/main/impl/format.h new file mode 100644 index 00000000..f1af79f9 --- /dev/null +++ b/roc_jni/src/main/impl/format.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define FORMAT_CLASS PACKAGE_BASE_NAME "/Format" + +roc_format get_format(JNIEnv* env, jobject jformat); diff --git a/roc_jni/src/main/impl/frame_encoding.c b/roc_jni/src/main/impl/frame_encoding.c deleted file mode 100644 index be3e7082..00000000 --- a/roc_jni/src/main/impl/frame_encoding.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "frame_encoding.h" -#include "common.h" - -roc_frame_encoding get_frame_encoding(JNIEnv* env, jobject jframe_encoding) { - jclass frameEncodingClass = NULL; - - frameEncodingClass = (*env)->FindClass(env, FRAME_ENCODING_CLASS); - assert(frameEncodingClass != NULL); - - return (roc_frame_encoding) get_enum_value(env, frameEncodingClass, jframe_encoding); -} diff --git a/roc_jni/src/main/impl/frame_encoding.h b/roc_jni/src/main/impl/frame_encoding.h deleted file mode 100644 index a1671beb..00000000 --- a/roc_jni/src/main/impl/frame_encoding.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define FRAME_ENCODING_CLASS PACKAGE_BASE_NAME "/FrameEncoding" - -roc_frame_encoding get_frame_encoding(JNIEnv* env, jobject jframe_encoding); diff --git a/roc_jni/src/main/impl/interface_config.c b/roc_jni/src/main/impl/interface_config.c new file mode 100644 index 00000000..10c78861 --- /dev/null +++ b/roc_jni/src/main/impl/interface_config.c @@ -0,0 +1,54 @@ +#include "interface_config.h" +#include "common.h" + +int interface_config_unmarshal(JNIEnv* env, roc_interface_config* config, jobject jconfig) { + jclass interfaceConfigClass = NULL; + jstring jOutgoingAddress = NULL; + const char* outgoingAddress = NULL; + jstring jMulticastGroup = NULL; + const char* multicastGroup = NULL; + int err = 0; + + interfaceConfigClass = (*env)->FindClass(env, INTERFACE_CONFIG_CLASS); + assert(interfaceConfigClass != NULL); + + // set all fields to zero + assert(config != NULL); + memset(config, 0, sizeof(*config)); + + // outgoing_address + jOutgoingAddress = (jstring) get_object_field( + env, interfaceConfigClass, jconfig, "outgoingAddress", "Ljava/lang/String;"); + if (jOutgoingAddress != NULL) { + outgoingAddress = (*env)->GetStringUTFChars(env, jOutgoingAddress, 0); + if (!outgoingAddress || strlen(outgoingAddress) >= sizeof(config->outgoing_address)) { + err = -1; + goto out; + } + strcpy(config->outgoing_address, outgoingAddress); + } + + // multicast_group + jMulticastGroup = (jstring) get_object_field( + env, interfaceConfigClass, jconfig, "multicastGroup", "Ljava/lang/String;"); + if (jMulticastGroup != NULL) { + multicastGroup = (*env)->GetStringUTFChars(env, jMulticastGroup, 0); + if (!multicastGroup || strlen(multicastGroup) >= sizeof(config->multicast_group)) { + err = -1; + goto out; + } + strcpy(config->multicast_group, multicastGroup); + } + + // reuse_address + config->reuse_address + = get_boolean_field_value(env, interfaceConfigClass, jconfig, "reuseAddress", &err); + if (err) goto out; + +out: + if (outgoingAddress != NULL) + (*env)->ReleaseStringUTFChars(env, jOutgoingAddress, outgoingAddress); + if (multicastGroup != NULL) (*env)->ReleaseStringUTFChars(env, jMulticastGroup, multicastGroup); + + return err; +} diff --git a/roc_jni/src/main/impl/interface_config.h b/roc_jni/src/main/impl/interface_config.h new file mode 100644 index 00000000..5899676b --- /dev/null +++ b/roc_jni/src/main/impl/interface_config.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define INTERFACE_CONFIG_CLASS PACKAGE_BASE_NAME "/InterfaceConfig" + +int interface_config_unmarshal(JNIEnv* env, roc_interface_config* config, jobject jconfig); diff --git a/roc_jni/src/main/impl/media_encoding.c b/roc_jni/src/main/impl/media_encoding.c new file mode 100644 index 00000000..335f220e --- /dev/null +++ b/roc_jni/src/main/impl/media_encoding.c @@ -0,0 +1,36 @@ +#include "media_encoding.h" +#include "channel_layout.h" +#include "common.h" +#include "format.h" + +int media_encoding_unmarshal(JNIEnv* env, roc_media_encoding* encoding, jobject jencoding) { + jclass mediaEncodingClass = NULL; + jobject jobj = NULL; + int err = 0; + + mediaEncodingClass = (*env)->FindClass(env, MEDIA_ENCODING_CLASS); + assert(mediaEncodingClass != NULL); + + // set all fields to zeros + assert(encoding != NULL); + memset(encoding, 0, sizeof(*encoding)); + + // rate + encoding->rate = get_int_field_value(env, mediaEncodingClass, jencoding, "rate", &err); + if (err) return err; + + // format + jobj = get_object_field(env, mediaEncodingClass, jencoding, "format", "L" FORMAT_CLASS ";"); + if (jobj != NULL) encoding->format = get_format(env, jobj); + + // channels + jobj = get_object_field( + env, mediaEncodingClass, jencoding, "channels", "L" CHANNEL_LAYOUT_CLASS ";"); + if (jobj != NULL) encoding->channels = get_channel_layout(env, jobj); + + // tracks + encoding->tracks = get_int_field_value(env, mediaEncodingClass, jencoding, "tracks", &err); + if (err) return err; + + return 0; +} diff --git a/roc_jni/src/main/impl/media_encoding.h b/roc_jni/src/main/impl/media_encoding.h new file mode 100644 index 00000000..6c7586e8 --- /dev/null +++ b/roc_jni/src/main/impl/media_encoding.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define MEDIA_ENCODING_CLASS PACKAGE_BASE_NAME "/MediaEncoding" + +int media_encoding_unmarshal(JNIEnv* env, roc_media_encoding* encoding, jobject jencoding); diff --git a/roc_jni/src/main/impl/packet_encoding.c b/roc_jni/src/main/impl/packet_encoding.c index 0b7144d5..8c407105 100644 --- a/roc_jni/src/main/impl/packet_encoding.c +++ b/roc_jni/src/main/impl/packet_encoding.c @@ -1,11 +1,11 @@ #include "packet_encoding.h" #include "common.h" -roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacket_encoding) { +roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacketEncoding) { jclass packetEncodingClass = NULL; packetEncodingClass = (*env)->FindClass(env, PACKET_ENCODING_CLASS); assert(packetEncodingClass != NULL); - return (roc_packet_encoding) get_enum_value(env, packetEncodingClass, jpacket_encoding); + return (roc_packet_encoding) get_enum_value(env, packetEncodingClass, jpacketEncoding); } diff --git a/roc_jni/src/main/impl/packet_encoding.h b/roc_jni/src/main/impl/packet_encoding.h index bb439049..09064866 100644 --- a/roc_jni/src/main/impl/packet_encoding.h +++ b/roc_jni/src/main/impl/packet_encoding.h @@ -8,4 +8,4 @@ #define PACKET_ENCODING_CLASS PACKAGE_BASE_NAME "/PacketEncoding" -roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacket_encoding); +roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacketEncoding); diff --git a/roc_jni/src/main/impl/receiver.c b/roc_jni/src/main/impl/receiver.c index c67f4329..b63ee1dd 100644 --- a/roc_jni/src/main/impl/receiver.c +++ b/roc_jni/src/main/impl/receiver.c @@ -1,93 +1,13 @@ #include "org_rocstreaming_roctoolkit_RocReceiver.h" -#include "channel_set.h" -#include "clock_source.h" #include "common.h" #include "endpoint.h" -#include "frame_encoding.h" -#include "resampler_backend.h" -#include "resampler_profile.h" +#include "interface_config.h" +#include "receiver_config.h" #include -#define RECEIVER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocReceiverConfig" - -static int receiver_config_unmarshal(JNIEnv* env, roc_receiver_config* config, jobject jconfig) { - jclass receiverConfigClass = NULL; - jobject jobj = NULL; - int err = 0; - - receiverConfigClass = (*env)->FindClass(env, RECEIVER_CONFIG_CLASS); - assert(receiverConfigClass != NULL); - - // set all fields to zeros - memset(config, 0, sizeof(*config)); - - // frame_sample_rate - config->frame_sample_rate - = get_uint_field_value(env, receiverConfigClass, jconfig, "frameSampleRate", &err); - if (err) return err; - - // frame_channels - jobj = get_object_field( - env, receiverConfigClass, jconfig, "frameChannels", "L" CHANNEL_SET_CLASS ";"); - if (jobj != NULL) config->frame_channels = (roc_channel_set) get_channel_set(env, jobj); - - // frame_encoding - jobj = get_object_field( - env, receiverConfigClass, jconfig, "frameEncoding", "L" FRAME_ENCODING_CLASS ";"); - if (jobj != NULL) config->frame_encoding = (roc_frame_encoding) get_frame_encoding(env, jobj); - - // clock_source - jobj = get_object_field( - env, receiverConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); - if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); - - // resampler_backend - jobj = get_object_field( - env, receiverConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); - if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); - - // resampler_profile - jobj = get_object_field( - env, receiverConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); - if (jobj != NULL) - config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); - - // target_latency - config->target_latency - = get_duration_field_value(env, receiverConfigClass, jconfig, "targetLatency", &err); - if (err) return err; - - // max_latency_overrun - config->max_latency_overrun - = get_duration_field_value(env, receiverConfigClass, jconfig, "maxLatencyOverrun", &err); - if (err) return err; - - // max_latency_underrun - config->max_latency_underrun - = get_duration_field_value(env, receiverConfigClass, jconfig, "maxLatencyUnderrun", &err); - if (err) return err; - - // no_playback_timeout - config->no_playback_timeout - = get_duration_field_value(env, receiverConfigClass, jconfig, "noPlaybackTimeout", &err); - if (err) return err; - - // broken_playback_timeout - config->broken_playback_timeout = get_duration_field_value( - env, receiverConfigClass, jconfig, "brokenPlaybackTimeout", &err); - if (err) return err; - - // breakage_detection_window - config->breakage_detection_window = get_duration_field_value( - env, receiverConfigClass, jconfig, "breakageDetectionWindow", &err); - if (err) return err; - - return 0; -} - -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_open( +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeOpen( JNIEnv* env, jclass receiverClass, jlong contextPtr, jobject jconfig) { roc_context* context = NULL; roc_receiver_config receiverConfig = {}; @@ -112,28 +32,48 @@ JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_open( return (jlong) receiver; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_setMulticastGroup( - JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot, jint interface, jstring jip) { +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeClose( + JNIEnv* env, jclass receiverClass, jlong receiverPtr) { + + roc_receiver* receiver = (roc_receiver*) receiverPtr; + + if (roc_receiver_close(receiver) != 0) { + jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error closing receiver"); + } +} + +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeConfigure( + JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot, jint interface, jobject jconfig) { roc_receiver* receiver = NULL; - const char* ip = NULL; + roc_interface_config config = {}; receiver = (roc_receiver*) receiverPtr; - ip = (*env)->GetStringUTFChars(env, jip, 0); - assert(ip != NULL); + if (jconfig == NULL) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + goto out; + } - if (roc_receiver_set_multicast_group(receiver, (roc_slot) slot, (roc_interface) interface, ip) + if (interface_config_unmarshal(env, &config, jconfig) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling config"); + goto out; + } + + if (roc_receiver_configure(receiver, (roc_slot) slot, (roc_interface) interface, &config) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't set multicast group"); + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error configuring receiver"); goto out; } out: - if (ip != NULL) (*env)->ReleaseStringUTFChars(env, jip, ip); + return; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_bind( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeBind( JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot, jint interface, jobject jendpoint) { roc_receiver* receiver = NULL; roc_endpoint* endpoint = NULL; @@ -165,7 +105,23 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_bind( } } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_readFloats( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeUnlink( + JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot) { + roc_receiver* receiver = NULL; + + receiver = (roc_receiver*) receiverPtr; + + if (roc_receiver_unlink(receiver, (roc_slot) slot) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error unlinking slot"); + goto out; + } + +out: + return; +} + +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeReadFloats( JNIEnv* env, jobject thisObj, jlong receiverPtr, jfloatArray jsamples) { roc_receiver* receiver = NULL; jfloat* samples = NULL; @@ -196,14 +152,3 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_readFloats( out: if (samples != NULL) (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); } - -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_close( - JNIEnv* env, jclass receiverClass, jlong receiverPtr) { - - roc_receiver* receiver = (roc_receiver*) receiverPtr; - - if (roc_receiver_close(receiver) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error closing receiver"); - } -} diff --git a/roc_jni/src/main/impl/receiver_config.c b/roc_jni/src/main/impl/receiver_config.c new file mode 100644 index 00000000..8af371b2 --- /dev/null +++ b/roc_jni/src/main/impl/receiver_config.c @@ -0,0 +1,79 @@ +#include "receiver_config.h" +#include "clock_source.h" +#include "clock_sync_backend.h" +#include "clock_sync_profile.h" +#include "common.h" +#include "endpoint.h" +#include "format.h" +#include "media_encoding.h" +#include "resampler_backend.h" +#include "resampler_profile.h" + +int receiver_config_unmarshal(JNIEnv* env, roc_receiver_config* config, jobject jconfig) { + jclass receiverConfigClass = NULL; + jobject jobj = NULL; + int err = 0; + + receiverConfigClass = (*env)->FindClass(env, RECEIVER_CONFIG_CLASS); + assert(receiverConfigClass != NULL); + + // set all fields to zeros + assert(config != NULL); + memset(config, 0, sizeof(*config)); + + // frame_encoding + jobj = get_object_field( + env, receiverConfigClass, jconfig, "frameEncoding", "L" MEDIA_ENCODING_CLASS ";"); + if (jobj != NULL) { + err = media_encoding_unmarshal(env, &config->frame_encoding, jobj); + if (err) return err; + } + + // clock_source + jobj = get_object_field( + env, receiverConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); + if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); + + // clock_sync_backend + jobj = get_object_field( + env, receiverConfigClass, jconfig, "clockSyncBackend", "L" CLOCK_SYNC_BACKEND_CLASS ";"); + if (jobj != NULL) config->clock_sync_backend = get_clock_sync_backend(env, jobj); + + // clock_sync_profile + jobj = get_object_field( + env, receiverConfigClass, jconfig, "clockSyncProfile", "L" CLOCK_SYNC_PROFILE_CLASS ";"); + if (jobj != NULL) config->clock_sync_profile = get_clock_sync_profile(env, jobj); + + // resampler_backend + jobj = get_object_field( + env, receiverConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); + if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); + + // resampler_profile + jobj = get_object_field( + env, receiverConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); + if (jobj != NULL) + config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); + + // target_latency + config->target_latency + = get_duration_field_value(env, receiverConfigClass, jconfig, "targetLatency", &err); + if (err) return err; + + // latency_tolerance + config->latency_tolerance + = get_duration_field_value(env, receiverConfigClass, jconfig, "latencyTolerance", &err); + if (err) return err; + + // no_playback_timeout + config->no_playback_timeout + = get_duration_field_value(env, receiverConfigClass, jconfig, "noPlaybackTimeout", &err); + if (err) return err; + + // choppy_playback_timeout + config->choppy_playback_timeout = get_duration_field_value( + env, receiverConfigClass, jconfig, "choppyPlaybackTimeout", &err); + if (err) return err; + + return 0; +} diff --git a/roc_jni/src/main/impl/receiver_config.h b/roc_jni/src/main/impl/receiver_config.h new file mode 100644 index 00000000..dbfe9c31 --- /dev/null +++ b/roc_jni/src/main/impl/receiver_config.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define RECEIVER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocReceiverConfig" + +int receiver_config_unmarshal(JNIEnv* env, roc_receiver_config* config, jobject jconfig); diff --git a/roc_jni/src/main/impl/resampler_backend.c b/roc_jni/src/main/impl/resampler_backend.c index d50bf701..be6b30b7 100644 --- a/roc_jni/src/main/impl/resampler_backend.c +++ b/roc_jni/src/main/impl/resampler_backend.c @@ -3,11 +3,11 @@ #include -roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresampler_backend) { +roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresamplerBackend) { jclass resamplerBackendClass = NULL; resamplerBackendClass = (*env)->FindClass(env, RESAMPLER_BACKEND_CLASS); assert(resamplerBackendClass != NULL); - return (roc_resampler_backend) get_enum_value(env, resamplerBackendClass, jresampler_backend); + return (roc_resampler_backend) get_enum_value(env, resamplerBackendClass, jresamplerBackend); } diff --git a/roc_jni/src/main/impl/resampler_backend.h b/roc_jni/src/main/impl/resampler_backend.h index 8c0fc6c3..f0bd811e 100644 --- a/roc_jni/src/main/impl/resampler_backend.h +++ b/roc_jni/src/main/impl/resampler_backend.h @@ -8,4 +8,4 @@ #define RESAMPLER_BACKEND_CLASS PACKAGE_BASE_NAME "/ResamplerBackend" -roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresampler_backend); +roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresamplerBackend); diff --git a/roc_jni/src/main/impl/resampler_profile.c b/roc_jni/src/main/impl/resampler_profile.c index d086710f..74bd2385 100644 --- a/roc_jni/src/main/impl/resampler_profile.c +++ b/roc_jni/src/main/impl/resampler_profile.c @@ -3,11 +3,11 @@ #include -roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresampler_profile) { +roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresamplerProfile) { jclass resamplerProfileClass = NULL; resamplerProfileClass = (*env)->FindClass(env, RESAMPLER_PROFILE_CLASS); assert(resamplerProfileClass != NULL); - return (roc_resampler_profile) get_enum_value(env, resamplerProfileClass, jresampler_profile); + return (roc_resampler_profile) get_enum_value(env, resamplerProfileClass, jresamplerProfile); } diff --git a/roc_jni/src/main/impl/resampler_profile.h b/roc_jni/src/main/impl/resampler_profile.h index f990ce66..e9f2b5d6 100644 --- a/roc_jni/src/main/impl/resampler_profile.h +++ b/roc_jni/src/main/impl/resampler_profile.h @@ -8,4 +8,4 @@ #define RESAMPLER_PROFILE_CLASS PACKAGE_BASE_NAME "/ResamplerProfile" -roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresampler_profile); +roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresamplerProfile); diff --git a/roc_jni/src/main/impl/sender.c b/roc_jni/src/main/impl/sender.c index 13a5a061..26dfcb32 100644 --- a/roc_jni/src/main/impl/sender.c +++ b/roc_jni/src/main/impl/sender.c @@ -1,107 +1,14 @@ #include "org_rocstreaming_roctoolkit_RocSender.h" -#include "channel_set.h" -#include "clock_source.h" #include "common.h" #include "endpoint.h" -#include "fec_encoding.h" -#include "frame_encoding.h" -#include "packet_encoding.h" -#include "resampler_backend.h" -#include "resampler_profile.h" +#include "interface_config.h" +#include "sender_config.h" #include #include -#define SENDER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocSenderConfig" - -static int sender_config_unmarshal(JNIEnv* env, roc_sender_config* config, jobject jconfig) { - jclass senderConfigClass = NULL; - jobject jobj = NULL; - int err = 0; - - senderConfigClass = (*env)->FindClass(env, SENDER_CONFIG_CLASS); - assert(senderConfigClass != NULL); - - // set all fields to zeros - memset(config, 0, sizeof(*config)); - - // frame_sample_rate - config->frame_sample_rate - = get_uint_field_value(env, senderConfigClass, jconfig, "frameSampleRate", &err); - if (err) return err; - - // frame_channels - jobj = get_object_field( - env, senderConfigClass, jconfig, "frameChannels", "L" CHANNEL_SET_CLASS ";"); - if (jobj != NULL) config->frame_channels = (roc_channel_set) get_channel_set(env, jobj); - - // frame_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "frameEncoding", "L" FRAME_ENCODING_CLASS ";"); - if (jobj != NULL) config->frame_encoding = (roc_frame_encoding) get_frame_encoding(env, jobj); - - // packet_sample_rate - config->packet_sample_rate - = get_uint_field_value(env, senderConfigClass, jconfig, "packetSampleRate", &err); - if (err) return err; - - // packet_channels - jobj = get_object_field( - env, senderConfigClass, jconfig, "packetChannels", "L" CHANNEL_SET_CLASS ";"); - if (jobj != NULL) config->packet_channels = (roc_channel_set) get_channel_set(env, jobj); - - // packet_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "packetEncoding", "L" PACKET_ENCODING_CLASS ";"); - if (jobj != NULL) - config->packet_encoding = (roc_packet_encoding) get_packet_encoding(env, jobj); - - // packet_length - config->packet_length - = get_duration_field_value(env, senderConfigClass, jconfig, "packetLength", &err); - if (err) return err; - - // packet_interleaving - config->packet_interleaving - = get_uint_field_value(env, senderConfigClass, jconfig, "packetInterleaving", &err); - if (err) return err; - - // clock_source - jobj = get_object_field( - env, senderConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); - if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); - - // resampler_backend - jobj = get_object_field( - env, senderConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); - if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); - - // resampler_profile - jobj = get_object_field( - env, senderConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); - if (jobj != NULL) - config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); - - // fec_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "fecEncoding", "L" FEC_ENCODING_CLASS ";"); - if (jobj != NULL) config->fec_encoding = (roc_fec_encoding) get_fec_encoding(env, jobj); - - // fec_block_source_packets - config->fec_block_source_packets - = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockSourcePackets", &err); - if (err) return err; - - // fec_block_repair_packets - config->fec_block_repair_packets - = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockRepairPackets", &err); - if (err) return err; - - return 0; -} - -JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_open( +JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeOpen( JNIEnv* env, jclass senderClass, jlong contextPtr, jobject jconfig) { roc_context* context = NULL; roc_sender_config config = {}; @@ -126,28 +33,47 @@ JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_open( return (jlong) sender; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_setOutgoingAddress( - JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot, jint interface, jstring jip) { +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeClose( + JNIEnv* env, jclass senderClass, jlong senderPtr) { + + roc_sender* sender = (roc_sender*) senderPtr; + + if (roc_sender_close(sender) != 0) { + jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error closing sender"); + } +} + +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConfigure( + JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot, jint interface, jobject jconfig) { roc_sender* sender = NULL; - const char* ip = NULL; + roc_interface_config config = {}; sender = (roc_sender*) senderPtr; - ip = (*env)->GetStringUTFChars(env, jip, 0); - assert(ip != NULL); + if (jconfig == NULL) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + goto out; + } - if (roc_sender_set_outgoing_address(sender, (roc_slot) slot, (roc_interface) interface, ip) - != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't set outgoing address"); + if (interface_config_unmarshal(env, &config, jconfig) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling config"); + goto out; + } + + if (roc_sender_configure(sender, (roc_slot) slot, (roc_interface) interface, &config) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error configuring sender"); goto out; } out: - if (ip != NULL) (*env)->ReleaseStringUTFChars(env, jip, ip); + return; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_connect( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConnect( JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot, jint interface, jobject jendpoint) { roc_sender* sender = NULL; roc_endpoint* endpoint = NULL; @@ -161,7 +87,7 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_connect( } if (endpoint_unmarshal(env, &endpoint, jendpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling endpoint"); goto out; } @@ -178,7 +104,23 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_connect( } } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_writeFloats( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeUnlink( + JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot) { + roc_sender* sender = NULL; + + sender = (roc_sender*) senderPtr; + + if (roc_sender_unlink(sender, (roc_slot) slot) != 0) { + jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); + (*env)->ThrowNew(env, exceptionClass, "Error unlinking slot"); + goto out; + } + +out: + return; +} + +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeWriteFloats( JNIEnv* env, jobject thisObj, jlong senderPtr, jfloatArray jsamples) { roc_sender* sender = NULL; jfloat* samples = NULL; @@ -209,14 +151,3 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_writeFloats( out: if (samples != NULL) (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); } - -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_close( - JNIEnv* env, jclass senderClass, jlong senderPtr) { - - roc_sender* sender = (roc_sender*) senderPtr; - - if (roc_sender_close(sender) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error closing sender"); - } -} diff --git a/roc_jni/src/main/impl/sender_config.c b/roc_jni/src/main/impl/sender_config.c new file mode 100644 index 00000000..45e0c1c8 --- /dev/null +++ b/roc_jni/src/main/impl/sender_config.c @@ -0,0 +1,79 @@ +#include "sender_config.h" +#include "clock_source.h" +#include "common.h" +#include "endpoint.h" +#include "fec_encoding.h" +#include "format.h" +#include "media_encoding.h" +#include "packet_encoding.h" +#include "resampler_backend.h" +#include "resampler_profile.h" + +int sender_config_unmarshal(JNIEnv* env, roc_sender_config* config, jobject jconfig) { + jclass senderConfigClass = NULL; + jobject jobj = NULL; + int err = 0; + + senderConfigClass = (*env)->FindClass(env, SENDER_CONFIG_CLASS); + assert(senderConfigClass != NULL); + + // set all fields to zeros + assert(config != NULL); + memset(config, 0, sizeof(*config)); + + // frame_encoding + jobj = get_object_field( + env, senderConfigClass, jconfig, "frameEncoding", "L" MEDIA_ENCODING_CLASS ";"); + if (jobj != NULL) { + err = media_encoding_unmarshal(env, &config->frame_encoding, jobj); + if (err) return err; + } + + // packet_encoding + jobj = get_object_field( + env, senderConfigClass, jconfig, "packetEncoding", "L" PACKET_ENCODING_CLASS ";"); + if (jobj != NULL) config->packet_encoding = get_packet_encoding(env, jobj); + + // packet_length + config->packet_length + = get_duration_field_value(env, senderConfigClass, jconfig, "packetLength", &err); + if (err) return err; + + // packet_interleaving + config->packet_interleaving + = get_uint_field_value(env, senderConfigClass, jconfig, "packetInterleaving", &err); + if (err) return err; + + // fec_encoding + jobj = get_object_field( + env, senderConfigClass, jconfig, "fecEncoding", "L" FEC_ENCODING_CLASS ";"); + if (jobj != NULL) config->fec_encoding = (roc_fec_encoding) get_fec_encoding(env, jobj); + + // fec_block_source_packets + config->fec_block_source_packets + = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockSourcePackets", &err); + if (err) return err; + + // fec_block_repair_packets + config->fec_block_repair_packets + = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockRepairPackets", &err); + if (err) return err; + + // clock_source + jobj = get_object_field( + env, senderConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); + if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); + + // resampler_backend + jobj = get_object_field( + env, senderConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); + if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); + + // resampler_profile + jobj = get_object_field( + env, senderConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); + if (jobj != NULL) + config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); + + return 0; +} diff --git a/roc_jni/src/main/impl/sender_config.h b/roc_jni/src/main/impl/sender_config.h new file mode 100644 index 00000000..052d62bc --- /dev/null +++ b/roc_jni/src/main/impl/sender_config.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "common.h" + +#include + +#define SENDER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocSenderConfig" + +int sender_config_unmarshal(JNIEnv* env, roc_sender_config* config, jobject jconfig); diff --git a/src/main/java/org/rocstreaming/roctoolkit/ChannelLayout.java b/src/main/java/org/rocstreaming/roctoolkit/ChannelLayout.java new file mode 100644 index 00000000..324876e9 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/ChannelLayout.java @@ -0,0 +1,45 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +/** + * Channel layout. + *

+ * Defines number of channels and meaning of each channel. + */ +public enum ChannelLayout { + + /** + * Multi-track audio. + *

+ * In multitrack layout, stream contains multiple channels which represent independent + * "tracks" without any special meaning (unlike stereo or surround) and hence without any + * special processing or mapping. + *

+ * The number of channels is arbitrary and is defined by {@code tracks} field of + * {@link MediaEncoding} struct. + */ + MULTITRACK(1), + + /** + * Mono. + *

+ * One channel with monophonic sound. + */ + MONO(2), + + /** + * Stereo. + *

+ * Two channels: left, right. + */ + STEREO(3), + ; + + final int value; + + ChannelLayout(int value) { + this.value = value; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/ChannelSet.java b/src/main/java/org/rocstreaming/roctoolkit/ChannelSet.java deleted file mode 100644 index 38b0bf15..00000000 --- a/src/main/java/org/rocstreaming/roctoolkit/ChannelSet.java +++ /dev/null @@ -1,24 +0,0 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 - -package org.rocstreaming.roctoolkit; - -/** - * Channel set. - */ -public enum ChannelSet { - - /** - * Stereo. - *

- * Two channels: left and right. - */ - STEREO(0x3), - ; - - final int value; - - ChannelSet(int value) { - this.value = value; - } -} diff --git a/src/main/java/org/rocstreaming/roctoolkit/Check.java b/src/main/java/org/rocstreaming/roctoolkit/Check.java index a72e7b42..f88de16d 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Check.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Check.java @@ -41,4 +41,18 @@ static long notNegative(long value, String name) { } return value; } + + static int inRange(int value, int min_value, int max_value, String name) { + if (value < min_value || value > max_value) { + throw new IllegalArgumentException(name + " must be in range [" + min_value + "; " + max_value + "]"); + } + return value; + } + + static long inRange(long value, long min_value, long max_value, String name) { + if (value < min_value || value > max_value) { + throw new IllegalArgumentException(name + " must be in range [" + min_value + "; " + max_value + "]"); + } + return value; + } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/ClockSource.java b/src/main/java/org/rocstreaming/roctoolkit/ClockSource.java index ebf4edd7..5b874c77 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/ClockSource.java +++ b/src/main/java/org/rocstreaming/roctoolkit/ClockSource.java @@ -1,27 +1,37 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; /** * Clock source for sender or receiver. + *

+ * Defines wo is responsible to invoke read or write in proper time. */ public enum ClockSource { /** * Sender or receiver is clocked by external user-defined clock. *

- * Write and read operations are non-blocking. The user is responsible to - * call them in time, according to the external clock. + * Write and read operations are non-blocking. The user is responsible to call them in + * time, according to the external clock. + *

+ * Use when samples source (from where you read them to pass to receiver) or destination + * (to where you write them after obtaining from sender) is active and has its own clock, + * e.g. it is a sound card. */ EXTERNAL(0), /** - * Sender or receiver is clocked by an internal clock. + * Sender or receiver is clocked by an internal pipeline clock. + *

+ * Write and read operations are blocking. They automatically wait until it's time to + * process the next bunch of samples according to the configured sample rate, based on a + * CPU timer. *

- * Write and read operations are blocking. They automatically wait until - * it's time to process the next bunch of samples according to the - * configured sample rate. + * Use when samples source (from where you read them to pass to receiver) or destination + * (to where you write them after obtaining from sender) is passive and does now have + * clock, e.g. it is a file on disk. */ INTERNAL(1), ; diff --git a/src/main/java/org/rocstreaming/roctoolkit/ClockSyncBackend.java b/src/main/java/org/rocstreaming/roctoolkit/ClockSyncBackend.java new file mode 100644 index 00000000..498baa2e --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/ClockSyncBackend.java @@ -0,0 +1,56 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +/** + * Clock synchronization algorithm. + *

+ * Defines how sender and receiver clocks are synchronized. + */ +public enum ClockSyncBackend { + + /** + * Disable clock synchronization. + *

+ * In this mode, sender and receiver clocks are not synchronized. This mode is generally + * not recommended, since clock drift will lead to periodic playback disruptions caused + * by underruns and overruns. + */ + DISABLE(-1), + + /** + * Default backend. + *

+ * Current default is {@link ClockSyncBackend#NIQ}. + */ + DEFAULT(0), + + /** + * Clock synchronization based on network incoming queue size. + *

+ * In this mode, receiver monitors incoming queue size and adjusts playback clock speed + * to match the estimated capture clock speed. + *

+ * Pros: + *

    + *
  • works with any protocol (does not require RTCP or NTP)
  • + *
+ *

+ * Cons: + *

    + *
  • synchronizes only clock speed, but not position; different receivers will have different + * (constant) delays
  • + *
  • affected by network jitter; spikes in packet delivery will cause slow oscillations in + * clock speed
  • + *
+ */ + NIQ(2), + ; + + final int value; + + ClockSyncBackend(int value) { + this.value = value; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/ClockSyncProfile.java b/src/main/java/org/rocstreaming/roctoolkit/ClockSyncProfile.java new file mode 100644 index 00000000..69fa3351 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/ClockSyncProfile.java @@ -0,0 +1,67 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +/** + * Clock synchronization profile. + *

+ * Defines what latency and jitter are tolerated by clock synchronization algorithm. + */ +public enum ClockSyncProfile { + + /** + * Default profile. + *

+ * When {@link ClockSyncBackend#NIQ} is used, selects {@link ClockSyncProfile#RESPONSIVE} + * if target latency is low, and {@link ClockSyncProfile#GRADUAL} if target latency is + * high. + */ + DEFAULT(0), + + /** + * Responsive clock adjustment. + *

+ * Clock speed is adjusted quickly and accurately. + *

+ * Requires high precision clock adjustment, hence recommended for use with + * {@link ResamplerBackend#BUILTIN}. + *

+ * Pros: + *

    + *
  • allows very low latency or synchronization error
  • + *
+ *

+ * Cons: + *

    + *
  • does not work well with some resampler backends
  • + *
  • does not work well with {@link ClockSyncBackend#NIQ} if network jitter is high
  • + *
+ */ + RESPONSIVE(1), + + /** + * Gradual clock adjustment. + *

+ * Clock speed is adjusted slowly and smoothly. + *

+ * Pros: + *

    + *
  • works well even with high network jitter
  • + *
  • works well with any resampler backend
  • + *
+ *

+ * Cons: + *

    + *
  • does not allow very low latency or synchronization error
  • + *
+ */ + GRADUAL(2), + ; + + final int value; + + ClockSyncProfile(int value) { + this.value = value; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java index 33338000..ec9fb2fb 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java @@ -7,47 +7,55 @@ /** * Network endpoint. *

- * Endpoint is a network entry point of a peer. The definition includes the - * protocol being used, network host and port, and, for some protocols, a - * resource. All these parts together are unambiguously represented - * by a URI. The user may set or get the entire URI or its individual parts. - *

+ * Endpoint is a network entry point of a node. The definition includes the protocol being used, + * network host and port, and, for some protocols, a resource. All these parts together are + * unambiguously represented by a URI. The user may set or get the entire URI or its individual + * parts. + * + * *

Endpoint URI

*

- * Endpoint URI syntax is a subset of the syntax defined in RFC 3986: - *

- * protocol://host[:port][/path][?query] - *

- * Examples: + * Endpoint URI syntax is a subset of the syntax defined in RFC 3986: Examples: *

    - *
  • "rtsp://localhost:123/path?query"
  • - *
  • "rtp+rs8m://localhost:123"
  • - *
  • "rtp://127.0.0.1:123"
  • - *
  • "rtp://[::1]:123"
  • + *
  • {@code rtsp://localhost:123/path?query}
  • + *
  • {@code rtp+rs8m://localhost:123}
  • + *
  • {@code rtp://127.0.0.1:123}
  • + *
  • {@code rtp://[::1]:123}
  • *
*

* The following protocols (schemes) are supported: *

    - *
  • "rtp://" ({@link Protocol#RTP RTP})
  • - *
  • "rtp+rs8m://" ({@link Protocol#RTP_RS8M_SOURCE RTP_RS8M_SOURCE})
  • - *
  • "rs8m://" ({@link Protocol#RS8M_REPAIR RS8M_REPAIR})
  • - *
  • "rtp+ldpc://" ({@link Protocol#RTP_LDPC_SOURCE RTP_LDPC_SOURCE})
  • - *
  • "ldpc://" ({@link Protocol#LDPC_REPAIR LDPC_REPAIR})
  • + *
  • {@code rtp://} ( {@link Protocol#RTP} )
  • + *
  • {@code rtp+rs8m://} ( {@link Protocol#RTP_RS8M_SOURCE} )
  • + *
  • {@code rs8m://} ( {@link Protocol#RS8M_REPAIR} )
  • + *
  • {@code rtp+ldpc://} ( {@link Protocol#RTP_LDPC_SOURCE} )
  • + *
  • {@code ldpc://} ( {@link Protocol#LDPC_REPAIR} )
  • *
*

- * The host field should be either FQDN (domain name), or IPv4 address, or - * IPv6 address in square brackets. + * The host field should be either FQDN (domain name), or IPv4 address, or IPv6 address in square + * brackets. + *

+ * The port field can be omitted if the protocol defines standard port. Otherwise, the port can + * not be omitted. For example, RTSP defines standard port, but RTP doesn't. *

- * The port field can be omitted if the protocol defines standard port. Otherwise, - * the port can not be omitted. For example, RTSP defines standard port, - * but RTP doesn't. + * The path and query fields are allowed only for protocols that support them. For example, + * they're supported by RTSP, but not by RTP. + * + * + *

Field invalidation

*

- * The path and query fields are allowed only for protocols that support them. - * For example, they're supported by RTSP, but not by RTP. + * If some field is attempted to be set to an invalid value (for example, an invalid port + * number), this specific field is marked as invalid until it is successfully set to some valid + * value. *

- *

Thread-safety

+ * Sender and receiver refuse to bind or connect an endpoint which has invalid fields or doesn't + * have some mandatory fields. Hence, it is safe to ignore errors returned by endpoint setters + * and check only for errors returned by bind and connect operations. + * + * + *

Thread safety

*

- * Can't be used concurrently + * Should not be used concurrently. */ @Getter @Builder(builderClassName = "Builder", toBuilder = true) diff --git a/src/main/java/org/rocstreaming/roctoolkit/FecEncoding.java b/src/main/java/org/rocstreaming/roctoolkit/FecEncoding.java index 448b72f2..e4e01246 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/FecEncoding.java +++ b/src/main/java/org/rocstreaming/roctoolkit/FecEncoding.java @@ -1,10 +1,12 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; /** * Forward Error Correction encoding. + *

+ * Each FEC encoding is caompatible with specific protocols. */ public enum FecEncoding { @@ -12,6 +14,16 @@ public enum FecEncoding { * No FEC encoding. *

* Compatible with {@link Protocol#RTP} protocol. + *

+ * Pros: + *

    + *
  • compatible with third-party software that does not support FECFRAME
  • + *
+ *

+ * Cons: + *

    + *
  • no packet recovery
  • + *
*/ DISABLE(-1), @@ -25,18 +37,38 @@ public enum FecEncoding { /** * Reed-Solomon FEC encoding (RFC 6865) with m=8. *

- * Good for small block sizes (below 256 packets). Compatible with {@link - * Protocol#RTP_RS8M_SOURCE} and {@link Protocol#RS8M_REPAIR} protocols for - * source and repair endpoints. + * Good for small block sizes (below 256 packets). Compatible with + * {@link Protocol#RTP_RS8M_SOURCE} and {@link Protocol#RS8M_REPAIR} protocols for source + * and repair endpoints. + *

+ * Pros: + *

    + *
  • good repair capabilities even on small block sizes
  • + *
+ *

+ * Cons: + *

    + *
  • high CPU usage on large block sizes
  • + *
*/ RS8M(1), /** * LDPC-Staircase FEC encoding (RFC 6816). *

- * Good for large block sizes (above 1024 packets). Compatible with {@link - * Protocol#RTP_LDPC_SOURCE} and {@link Protocol#LDPC_REPAIR} protocols for - * source and repair endpoints. + * Good for large block sizes (above 1024 packets). Compatible with + * {@link Protocol#RTP_LDPC_SOURCE} and {@link Protocol#LDPC_REPAIR} protocols for source + * and repair endpoints. + *

+ * Pros: + *

    + *
  • low CPU usage even on large block sizes
  • + *
+ *

+ * Cons: + *

    + *
  • low repair capabilities on small block sizes
  • + *
*/ LDPC_STAIRCASE(2), ; diff --git a/src/main/java/org/rocstreaming/roctoolkit/Format.java b/src/main/java/org/rocstreaming/roctoolkit/Format.java new file mode 100644 index 00000000..e50926e4 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/Format.java @@ -0,0 +1,27 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +/** + * Sample format. + *

+ * Defines how each sample is represented. Does not define channels layout and sample rate. + */ +public enum Format { + + /** + * PCM floats. + *

+ * Uncompressed samples coded as 32-bit native-endian floats in range [-1; 1]. Channels + * are interleaved, e.g. two channels are encoded as "L R L R...". + */ + PCM_FLOAT32(1), + ; + + final int value; + + Format(int value) { + this.value = value; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/FrameEncoding.java b/src/main/java/org/rocstreaming/roctoolkit/FrameEncoding.java deleted file mode 100644 index 775031f6..00000000 --- a/src/main/java/org/rocstreaming/roctoolkit/FrameEncoding.java +++ /dev/null @@ -1,25 +0,0 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 - -package org.rocstreaming.roctoolkit; - -/** - * Frame encoding. - */ -public enum FrameEncoding { - - /** - * PCM floats. - *

- * Uncompressed samples coded as floats in range [-1; 1]. Channels are - * interleaved, e.g. two channels are encoded as "L R L R...". - */ - PCM_FLOAT(1), - ; - - final int value; - - FrameEncoding(int value) { - this.value = value; - } -} diff --git a/src/main/java/org/rocstreaming/roctoolkit/Interface.java b/src/main/java/org/rocstreaming/roctoolkit/Interface.java index d7aa8e51..3cbd655c 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Interface.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Interface.java @@ -1,60 +1,55 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; /** * Network interface. *

- * Interface is a way to access the peer (sender or receiver) via network. + * Interface is a way to access the node (sender or receiver) via network. *

- * Each peer slot has multiple interfaces, one of each type. The user - * interconnects peers by binding one of the first peer's interfaces to an URI - * and then connecting the corresponding second peer's interface to that URI. + * Each node slot has multiple interfaces, one of each type. The user interconnects nodes by + * binding one of the first node's interfaces to an URI and then connecting the corresponding + * second node's interface to that URI. *

* A URI is represented by {@link Endpoint} object. *

- * The interface defines the type of the communication with the remote peer and - * the set of protocols (URI schemes) that can be used with this particular - * interface. + * The interface defines the type of the communication with the remote node and the set of + * protocols (URI schemes) that can be used with this particular interface. *

- * {@link Interface#CONSOLIDATED} is an interface for high-level protocols which - * automatically manage all necessary communication: transport streams, control - * messages, parameter negotiation, etc. When a consolidated connection is - * established, peers may automatically setup lower-level interfaces like {@link - * Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR}, and {@link - * Interface#AUDIO_CONTROL}. + * {@link Interface#CONSOLIDATED} is an interface for high-level protocols which automatically + * manage all necessary communication: transport streams, control messages, parameter + * negotiation, etc. When a consolidated connection is established, nodes may automatically setup + * lower-level interfaces like {@link Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR}, + * and {@link Interface#AUDIO_CONTROL}. *

- * {@link Interface#CONSOLIDATED} is mutually exclusive with lower-level - * interfaces. In most cases, the user needs only {@link - * Interface#CONSOLIDATED}. However, the lower-level interfaces may be useful if - * an external signaling mechanism is used or for compatibility with third-party + * {@link Interface#CONSOLIDATED} is mutually exclusive with lower-level interfaces. In most + * cases, the user needs only {@link Interface#CONSOLIDATED}. However, the lower-level interfaces + * may be useful if an external signaling mechanism is used or for compatibility with third-party * software. *

- * {@link Interface#AUDIO_SOURCE} and {@link Interface#AUDIO_REPAIR} are - * lower-level unidirectional transport-only interfaces. The first is used to - * transmit audio stream, and the second is used to transmit redundant repair - * stream, if FEC is enabled. + * {@link Interface#AUDIO_SOURCE} and {@link Interface#AUDIO_REPAIR} are lower-level + * unidirectional transport-only interfaces. The first is used to transmit audio stream, and the + * second is used to transmit redundant repair stream, if FEC is enabled. *

- * {@link Interface#AUDIO_CONTROL} is a lower-level interface for control - * streams. If you use {@link Interface#AUDIO_SOURCE} and {@link - * Interface#AUDIO_REPAIR}, you usually also need to use {@link - * Interface#AUDIO_CONTROL} to enable carrying additional non-transport - * information. + * {@link Interface#AUDIO_CONTROL} is a lower-level interface for control streams. If you use + * {@link Interface#AUDIO_SOURCE} and {@link Interface#AUDIO_REPAIR}, you usually also need to + * use {@link Interface#AUDIO_CONTROL} to enable carrying additional non-transport information. */ public enum Interface { /** - * Interface that consolidates all types of streams (source, repair, - * control). + * Interface that consolidates all types of streams (source, repair, control). *

- * Allowed operations:

    - *
  • bind (sender, receiver)
  • - *
  • connect (sender, receiver)
  • + * Allowed operations: + *
      + *
    • bind (sender, receiver)
    • + *
    • connect (sender, receiver)
    • *
    *

    - * Allowed protocols:

      - *
    • {@link Protocol#RTSP}
    • + * Allowed protocols: + *
        + *
      • {@link Protocol#RTSP}
      • *
      */ CONSOLIDATED(1), @@ -62,15 +57,17 @@ public enum Interface { /** * Interface for audio stream source data. *

      - * Allowed operations:

        - *
      • bind (receiver)
      • - *
      • connect (sender)
      • + * Allowed operations: + *
          + *
        • bind (receiver)
        • + *
        • connect (sender)
        • *
        *

        - * Allowed protocols:

          - *
        • {@link Protocol#RTP}
        • - *
        • {@link Protocol#RTP_RS8M_SOURCE}
        • - *
        • {@link Protocol#RTP_LDPC_SOURCE}
        • + * Allowed protocols: + *
            + *
          • {@link Protocol#RTP}
          • + *
          • {@link Protocol#RTP_RS8M_SOURCE}
          • + *
          • {@link Protocol#RTP_LDPC_SOURCE}
          • *
          */ AUDIO_SOURCE(11), @@ -78,14 +75,16 @@ public enum Interface { /** * Interface for audio stream repair data. *

          - * Allowed operations:

            - *
          • bind (receiver)
          • - *
          • connect (sender)
          • + * Allowed operations: + *
              + *
            • bind (receiver)
            • + *
            • connect (sender)
            • *
            *

            - * Allowed protocols:

              - *
            • {@link Protocol#RS8M_REPAIR}
            • - *
            • {@link Protocol#LDPC_REPAIR}
            • + * Allowed protocols: + *
                + *
              • {@link Protocol#RS8M_REPAIR}
              • + *
              • {@link Protocol#LDPC_REPAIR}
              • *
              */ AUDIO_REPAIR(12), @@ -93,13 +92,15 @@ public enum Interface { /** * Interface for audio control messages. *

              - * Allowed operations:

                - *
              • bind (sender, receiver)
              • - *
              • connect (sender, receiver)
              • + * Allowed operations: + *
                  + *
                • bind (sender, receiver)
                • + *
                • connect (sender, receiver)
                • *
                *

                - * Allowed protocols:

                  - *
                • {@link Protocol#RTCP}
                • + * Allowed protocols: + *
                    + *
                  • {@link Protocol#RTCP}
                  • *
                  */ AUDIO_CONTROL(13), diff --git a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java new file mode 100644 index 00000000..bd774a9e --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java @@ -0,0 +1,86 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +import java.time.Duration; +import lombok.*; + +/** + * Interface configuration. + *

                  + * Sender and receiver can have multiple slots ( {@link Slot} ), and each slot + * can be bound or connected to multiple interfaces ( {@link Interface} ). + *

                  + * Each such interface has its own configuration, defined by this class. + *

                  + * See {@link RocSender#configure()}, {@link RocReceiver#configure()}. + */ +@Getter +@Builder(builderClassName = "Builder", toBuilder = true) +@ToString +@EqualsAndHashCode +public class InterfaceConfig { + + /** + * Outgoing IP address. + *

                  + * If non-empty, explicitly identifies the OS network interface, by its IP address, from + * which to send outgoing packets. If NULL, the network interface is selected + * automatically by the OS, depending on the address of remote endpoint. + *

                  + * For example, if eth0 has IP address "192.168.0.1", then setting outgoing address to + * "192.168.0.1" will force usage of eth0 interface. + *

                  + * Setting it to {@code 0.0.0.0} (for IPv4) or to {@code ::} (for IPv6) gives the same + * effect as if it was NULL. + *

                  + * By default, empty. + */ + private String outgoingAddress; + + /** + * Multicast group IP address. + *

                  + * Multicast group should be set only when binding interface to an endpoint with + * multicast IP address. If present, it defines an IP address of the OS network interface + * on which to join the multicast group. If not present, no multicast group is joined. + *

                  + * It's possible to receive multicast traffic from only those OS network interfaces, on + * which the process has joined the multicast group. When using multicast, the user + * should either set this field, or join multicast group manually using OS-specific API. + *

                  + * It is allowed to set multicast group to {@code 0.0.0.0} (for IPv4) or to {@code ::} + * (for IPv6), to be able to receive multicast traffic from all available interfaces. + * However, this may not be desirable for security reasons. + *

                  + * By default, empty. + */ + private String multicastGroup; + + /** + * Socket address reuse flag. + *

                  + * When true (non-zero), SO_REUSEADDR is enabled for socket, regardless of socket type, + * unless binding to ephemeral port (when port is set to zero). + *

                  + * When false (zero), SO_REUSEADDR is enabled for socket only if it has multicast type, + * unless binding to ephemeral port (when port is set to zero). + *

                  + * For TCP-based protocols, SO_REUSEADDR allows immediate reuse of recently closed socket + * in TIME_WAIT state, which may be useful you want to be able to restart server quickly. + *

                  + * For UDP-based protocols, SO_REUSEADDR allows multiple processes to bind to the same + * address, which may be useful if you're using socket activation mechanism. + *

                  + * By default, false. + */ + private boolean reuseAddress; + + /** + * Construct lombok builder for {@link InterfaceConfig}. + */ + public static InterfaceConfig.Builder builder() { + return new InterfaceConfigValidator(); + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java new file mode 100644 index 00000000..55da3166 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java @@ -0,0 +1,12 @@ +package org.rocstreaming.roctoolkit; + +/** + * A InterfaceConfigValidator adds validation to InterfaceConfig builder. + */ +class InterfaceConfigValidator extends InterfaceConfig.Builder { + @Override + public InterfaceConfig build() { + InterfaceConfig config = super.build(); + return config; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java b/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java new file mode 100644 index 00000000..3de24766 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java @@ -0,0 +1,57 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + +package org.rocstreaming.roctoolkit; + +import java.time.Duration; +import lombok.*; + +/** + * Media encoding. + *

                  + * Defines format and parameters of samples encoded in frames or packets. + */ +@Getter +@Builder(builderClassName = "Builder", toBuilder = true) +@ToString +@EqualsAndHashCode +public class MediaEncoding { + + /** + * Sample frequency. + *

                  + * Defines number of samples per channel per second (e.g. 44100). + */ + private int rate; + + /** + * Sample format. + *

                  + * Defines sample precision and encoding. + */ + private Format format; + + /** + * Channel layout. + *

                  + * Defines number of channels and meaning of each channel. + */ + private ChannelLayout channels; + + /** + * Multi-track channel count. + *

                  + * If {@code channels} is {@link ChannelLayout#MULTITRACK}, defines number of channels + * (which represent independent "tracks"). For other channel layouts should be zero. + *

                  + * Should be in range [1; 1024]. + */ + private int tracks; + + /** + * Construct lombok builder for {@link MediaEncoding}. + */ + public static MediaEncoding.Builder builder() { + return new MediaEncodingValidator(); + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java b/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java new file mode 100644 index 00000000..5b5ddc60 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java @@ -0,0 +1,16 @@ +package org.rocstreaming.roctoolkit; + +/** + * A MediaEncodingValidator adds validation to MediaEncoding builder. + */ +class MediaEncodingValidator extends MediaEncoding.Builder { + @Override + public MediaEncoding build() { + MediaEncoding encoding = super.build(); + Check.notNegative(encoding.getRate(), "rate"); + Check.notNull(encoding.getFormat(), "format"); + Check.notNull(encoding.getChannels(), "channels"); + Check.notNegative(encoding.getTracks(), "tracks"); + return encoding; + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java index 09189810..93dfc8f7 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java @@ -1,12 +1,12 @@ package org.rocstreaming.roctoolkit; /** - * A NativeObject represents an underlying native roc object. + * A {@code NativeObject} represents an underlying native roc object. */ class NativeObject implements AutoCloseable { /** - * NativeObject finalizer thread. + * {@code NativeObject} finalizer thread. */ private final static NativeObjectCleaner NATIVE_OBJECT_CLEANER = NativeObjectCleaner.getInstance(); @@ -21,21 +21,21 @@ class NativeObject implements AutoCloseable { } /** - * Construct a NativeObject. + * Construct a {@code NativeObject}. * * @param ptr native pointer to a roc native object * @param dependsOn dependency for finalization ordering - * @param destructor destructor method for closing NativeObject. + * @param destructor destructor method for closing {@code NativeObject}. */ protected NativeObject(long ptr, NativeObject dependsOn, Destructor destructor) { this.resource = NATIVE_OBJECT_CLEANER.register(this, ptr, dependsOn, destructor); } /** - * Get NativeObject native pointer. + * Get {@code NativeObject} native pointer. * * @return the native roc object pointer associated to this - * NativeObject. + * {@code NativeObject}. */ long getPtr() { return this.resource.getPtr(); diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java index 291e3655..f2c44ae0 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java @@ -28,13 +28,13 @@ class NativeObjectCleaner extends Thread { /** * Set to keep phantom references to prevent being garbage collected, - * otherwise reference will be collected by GC and won't be queued to {@link NativeObjectCleaner#referenceQueue} - * when related {@link NativeObject} collected by GC + * otherwise reference will be collected by GC and won't be queued to + * {@link NativeObjectCleaner#referenceQueue} when related {@link NativeObject} collected by GC */ private final Set set = Collections.newSetFromMap(new ConcurrentHashMap<>()); /** - * Create a new NativeObjectCleaner. + * Create a new {@code NativeObjectCleaner}. */ private NativeObjectCleaner() { super("RocNativeObjectCleaner"); @@ -43,16 +43,16 @@ private NativeObjectCleaner() { } /** - * Get NativeObjectCleaner instance. + * Get {@code NativeObjectCleaner} instance. * - * @return the NativeObjectCleaner singleton instance. + * @return the {@code NativeObjectCleaner} singleton instance. */ static NativeObjectCleaner getInstance() { return instance; } /** - * Register a {@link NativeObject} in NativeObjectCleaner. + * Register a {@link NativeObject} in {@code NativeObjectCleaner}. * * @param nativeObj {@link NativeObject} to add. * @param ptr Underlying roc object native pointer. @@ -68,7 +68,7 @@ NativeObjectPhantomReference register(NativeObject nativeObj, long ptr, NativeOb } /** - * Remove a reference from NativeObjectCleaner. + * Remove a reference from {@code NativeObjectCleaner}. * * @param reference the {@link NativeObjectPhantomReference} to unregister. */ @@ -78,10 +78,10 @@ void unregister(NativeObjectPhantomReference reference) { } /** - * Entrypoint method of NativeObjectCleaner. + * Entrypoint method of {@code NativeObjectCleaner}. *

                  * Remove any phantom reachable {@link NativeObjectPhantomReference} from the - * {@link NativeObjectCleaner#referenceQueue} associated with this NativeObjectCleaner + * {@link NativeObjectCleaner#referenceQueue} associated with this {@code NativeObjectCleaner} * and close it. */ @Override diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java index 02296242..09807581 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java @@ -5,9 +5,10 @@ import java.util.concurrent.atomic.AtomicBoolean; /** - * NativeObjectPhantomReference is associated with a {@link NativeObject} and owns its entire lifetime; + * {@code NativeObjectPhantomReference} is associated with a {@link NativeObject} and owns its + * entire lifetime. * - * A NativeObjectPhantomReference contains necessary data for closing the native object + * A {@code NativeObjectPhantomReference} contains necessary data for closing the native object * after it becomes phantom reachable. */ class NativeObjectPhantomReference extends PhantomReference implements AutoCloseable { @@ -18,7 +19,8 @@ class NativeObjectPhantomReference extends PhantomReference implem private final long ptr; /** - * Dependency for finalization ordering. Keep strong reference to prevent it from being collected by GC + * Dependency for finalization ordering. Keep strong reference to prevent it from being + * collected by GC */ @SuppressWarnings({"FieldCanBeLocal", "unused"}) private final NativeObject dependsOn; @@ -34,12 +36,13 @@ class NativeObjectPhantomReference extends PhantomReference implem private volatile boolean isOpen; /** - * Construct a new NativeObjectPhantomReference. + * Construct a new {@code NativeObjectPhantomReference}. * * @param referent {@link NativeObject} associated. * @param queue Reference queue containing phantom reachable native objects. * @param ptr Underlying roc object native pointer. - * @param dependsOn Dependency for finalization ordering. Keep strong reference to prevent it from being collected by GC + * @param dependsOn Dependency for finalization ordering. Keep strong reference to prevent + * it from being collected by GC * @param destructor Destructor method. */ NativeObjectPhantomReference(NativeObject referent, ReferenceQueue queue, long ptr, NativeObject dependsOn, Destructor destructor) { @@ -53,7 +56,7 @@ class NativeObjectPhantomReference extends PhantomReference implem /** * Get {@link NativeObject} native pointer. * - * @return the native roc object pointer associated to this NativeObjectPhantomReference. + * @return the native roc object pointer associated to this {@code NativeObjectPhantomReference}. */ long getPtr() { return ptr; diff --git a/src/main/java/org/rocstreaming/roctoolkit/PacketEncoding.java b/src/main/java/org/rocstreaming/roctoolkit/PacketEncoding.java index e6a5773e..d456f7c5 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/PacketEncoding.java +++ b/src/main/java/org/rocstreaming/roctoolkit/PacketEncoding.java @@ -1,26 +1,64 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; +import lombok.EqualsAndHashCode; + /** * Packet encoding. + *

                  + * Each packet encoding defines sample format, channel layout, and rate. Each packet encoding is + * caompatible with specific protocols. */ -public enum PacketEncoding { +@EqualsAndHashCode +public class PacketEncoding { /** - * PCM signed 16-bit. + * PCM signed 16-bit, 1 channel, 44100 rate. *

                  - * "L16" encoding from RTP A/V Profile (RFC 3551). Uncompressed samples - * coded as interleaved 16-bit signed big-endian integers in two's + * Represents 1-channel L16 stereo encoding from RTP A/V Profile (RFC 3551). Uses + * uncompressed samples coded as interleaved 16-bit signed big-endian integers in two's * complement notation. + *

                  + * Supported by protocols: + *

                    + *
                  • {@link Protocol#RTP}
                  • + *
                  • {@link Protocol#RTP_RS8M_SOURCE}
                  • + *
                  • {@link Protocol#RTP_LDPC_SOURCE}
                  • + *
                  */ - AVP_L16(2), - ; + static PacketEncoding AVP_L16_MONO = new PacketEncoding(11); + + /** + * PCM signed 16-bit, 2 channels, 44100 rate. + *

                  + * Represents 2-channel L16 stereo encoding from RTP A/V Profile (RFC 3551). Uses + * uncompressed samples coded as interleaved 16-bit signed big-endian integers in two's + * complement notation. + *

                  + * Supported by protocols: + *

                    + *
                  • {@link Protocol#RTP}
                  • + *
                  • {@link Protocol#RTP_RS8M_SOURCE}
                  • + *
                  • {@link Protocol#RTP_LDPC_SOURCE}
                  • + *
                  + */ + static PacketEncoding AVP_L16_STEREO = new PacketEncoding(10); final int value; - PacketEncoding(int value) { + public PacketEncoding(int value) { this.value = value; } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return "PacketEncoding(" + value + ")"; + } + } diff --git a/src/main/java/org/rocstreaming/roctoolkit/Protocol.java b/src/main/java/org/rocstreaming/roctoolkit/Protocol.java index bd11f400..bdacacf5 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Protocol.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Protocol.java @@ -1,5 +1,5 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; @@ -13,13 +13,15 @@ public enum Protocol { /** * RTSP 1.0 (RFC 2326) or RTSP 2.0 (RFC 7826). *

                  - * Interfaces:

                    - *
                  • {@link Interface#CONSOLIDATED}
                  • + * Interfaces: + *
                      + *
                    • {@link Interface#CONSOLIDATED}
                    • *
                    *

                    - * Transports:

                      - *
                    • for signaling: TCP
                    • - *
                    • for media: RTP and RTCP over UDP or TCP
                    • + * Transports: + *
                        + *
                      • for signaling: TCP
                      • + *
                      • for media: RTP and RTCP over UDP or TCP
                      • *
                      */ RTSP(10), @@ -27,42 +29,51 @@ public enum Protocol { /** * RTP over UDP (RFC 3550). *

                      - * Interfaces:

                        - *
                      • {@link Interface#AUDIO_SOURCE}
                      • + * Interfaces: + *
                          + *
                        • {@link Interface#AUDIO_SOURCE}
                        • *
                        *

                        - * Transports:

                          - *
                        • UDP
                        • + * Transports: + *
                            + *
                          • UDP
                          • *
                          *

                          - * Audio encodings:

                            - *
                          • {@link PacketEncoding#AVP_L16}
                          • + * Audio encodings: + *
                              + *
                            • {@link PacketEncoding#AVP_L16_MONO}
                            • + *
                            • {@link PacketEncoding#AVP_L16_STEREO}
                            • + *
                            • encodings registered using {@link RocContext#registerEncoding()}
                            • *
                            *

                            - * FEC encodings:

                              - *
                            • none
                            • + * FEC encodings: + *
                                + *
                              • none
                              • *
                              */ RTP(20), /** - * RTP source packet (RFC 3550) + FECFRAME Reed-Solomon footer (RFC 6865) - * with m=8. + * RTP source packet (RFC 3550) + FECFRAME Reed-Solomon footer (RFC 6865) with m=8. *

                              - * Interfaces:

                                - *
                              • {@link Interface#AUDIO_SOURCE}
                              • + * Interfaces: + *
                                  + *
                                • {@link Interface#AUDIO_SOURCE}
                                • *
                                *

                                - * Transports:

                                  - *
                                • UDP
                                • + * Transports: + *
                                    + *
                                  • UDP
                                  • *
                                  *

                                  - * Audio encodings:

                                    - *
                                  • similar to {@link Protocol#RTP}
                                  • + * Audio encodings: + *
                                      + *
                                    • similar to {@link Protocol#RTP}
                                    • *
                                    *

                                    - * FEC encodings:

                                      - *
                                    • {@link FecEncoding#RS8M}
                                    • + * FEC encodings: + *
                                        + *
                                      • {@link FecEncoding#RS8M}
                                      • *
                                      */ RTP_RS8M_SOURCE(30), @@ -70,16 +81,19 @@ public enum Protocol { /** * FEC repair packet + FECFRAME Reed-Solomon header (RFC 6865) with m=8. *

                                      - * Interfaces:

                                        - *
                                      • {@link Interface#AUDIO_REPAIR}
                                      • + * Interfaces: + *
                                          + *
                                        • {@link Interface#AUDIO_REPAIR}
                                        • *
                                        *

                                        - * Transports:

                                          - *
                                        • UDP
                                        • + * Transports: + *
                                            + *
                                          • UDP
                                          • *
                                          *

                                          - * FEC encodings:

                                            - *
                                          • {@link FecEncoding#RS8M}
                                          • + * FEC encodings: + *
                                              + *
                                            • {@link FecEncoding#RS8M}
                                            • *
                                            */ RS8M_REPAIR(31), @@ -87,20 +101,24 @@ public enum Protocol { /** * RTP source packet (RFC 3550) + FECFRAME LDPC-Staircase footer (RFC 6816). *

                                            - * Interfaces:

                                              - *
                                            • {@link Interface#AUDIO_SOURCE}
                                            • + * Interfaces: + *
                                                + *
                                              • {@link Interface#AUDIO_SOURCE}
                                              • *
                                              *

                                              - * Transports:

                                                - *
                                              • UDP
                                              • + * Transports: + *
                                                  + *
                                                • UDP
                                                • *
                                                *

                                                - * Audio encodings:

                                                  - *
                                                • similar to {@link Protocol#RTP}
                                                • + * Audio encodings: + *
                                                    + *
                                                  • similar to {@link Protocol#RTP}
                                                  • *
                                                  *

                                                  - * FEC encodings:

                                                    - *
                                                  • {@link FecEncoding#LDPC_STAIRCASE}
                                                  • + * FEC encodings: + *
                                                      + *
                                                    • {@link FecEncoding#LDPC_STAIRCASE}
                                                    • *
                                                    */ RTP_LDPC_SOURCE(32), @@ -108,16 +126,19 @@ public enum Protocol { /** * FEC repair packet + FECFRAME LDPC-Staircase header (RFC 6816). *

                                                    - * Interfaces:

                                                      - *
                                                    • {@link Interface#AUDIO_REPAIR}
                                                    • + * Interfaces: + *
                                                        + *
                                                      • {@link Interface#AUDIO_REPAIR}
                                                      • *
                                                      *

                                                      - * Transports:

                                                        - *
                                                      • UDP
                                                      • + * Transports: + *
                                                          + *
                                                        • UDP
                                                        • *
                                                        *

                                                        - * FEC encodings:

                                                          - *
                                                        • {@link FecEncoding#LDPC_STAIRCASE}
                                                        • + * FEC encodings: + *
                                                            + *
                                                          • {@link FecEncoding#LDPC_STAIRCASE}
                                                          • *
                                                          */ LDPC_REPAIR(33), @@ -125,12 +146,14 @@ public enum Protocol { /** * RTCP over UDP (RFC 3550). *

                                                          - * Interfaces:

                                                            - *
                                                          • {@link Interface#AUDIO_CONTROL}
                                                          • + * Interfaces: + *
                                                              + *
                                                            • {@link Interface#AUDIO_CONTROL}
                                                            • *
                                                            *

                                                            - * Transports:

                                                              - *
                                                            • UDP
                                                            • + * Transports: + *
                                                                + *
                                                              • UDP
                                                              • *
                                                              */ RTCP(70), diff --git a/src/main/java/org/rocstreaming/roctoolkit/ResamplerBackend.java b/src/main/java/org/rocstreaming/roctoolkit/ResamplerBackend.java index ba8f8d06..140ffe72 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/ResamplerBackend.java +++ b/src/main/java/org/rocstreaming/roctoolkit/ResamplerBackend.java @@ -1,35 +1,80 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; /** * Resampler backend. *

                                                              - * Affects speed and quality. Some backends may be disabled at build time. + * Affects CPU usage, quality, and clock synchronization precision. Some backends may be disabled + * at build time. */ public enum ResamplerBackend { /** * Default backend. *

                                                              - * Depends on what was enabled at build time. + * Selects {@link ResamplerBackend#BUILTIN} when using + * {@link ClockSyncProfile#RESPONSIVE}, or when SpeexDSP is disabled. + *

                                                              + * Otherwise, selects {@link ResamplerBackend#SPEEX}. */ DEFAULT(0), /** - * Slow built-in resampler. + * CPU-intensive good-quality high-precision built-in resampler. + *

                                                              + * This backend controls clock speed with very high precision, and hence is useful when + * latency or synchronization error should be very low. + *

                                                              + * This backend has higher CPU usage, especially on high resampling quality and on CPUs + * with small L3 caches. + *

                                                              + * The implementation is based on bandlimited interpolation algorithm. + *

                                                              + * This backend is always available. *

                                                              - * Always available. + * Recommended for {@link ClockSyncProfile#RESPONSIVE} and on good CPUs. */ BUILTIN(1), /** - * Fast good-quality resampler from SpeexDSP. + * Fast good-quality low-precision resampler based on SpeexDSP. *

                                                              - * May be disabled at build time. + * This backend has low CPU usage even on high resampler quality and cheap CPUs. + *

                                                              + * This backend controls clock speed with lower precision, and is not so good when + * latency or synchronization error should be very low. + *

                                                              + * This backend is available only when SpeexDSP was enabled at build time. + *

                                                              + * Recommended for {@link ClockSyncProfile#GRADUAL} and on cheap CPUs. */ SPEEX(2), + + /** + * Fast medium-quality and medium-precision resampler combining SpeexDSP with decimation. + *

                                                              + * This backend uses SpeexDSP for converting between base rates (e.g. 44100 vs 48000) and + * decimation/expansion (dropping or duplicating samples) for clock drift compensation. + *

                                                              + * Typical decimation rate needed to compensate clock drift is below 0.5ms/second (20 + * samples/second on 48Khz), which gives tolerable quality despite usage of decimation, + * especially for speech. + *

                                                              + * When frame and packet sample rates are equal (e.g. both are 44100), only decimation + * stage is needed, and this becomes fastest possible backend working almost as fast as + * memcpy(). + *

                                                              + * When frame and packet rates are different, usage of this backend compared to + * {@link ResamplerBackend#SPEEX} allows to sacrify some quality, but somewhat improve + * scaling precision and CPU usage in return. + *

                                                              + * This backend is available only when SpeexDSP was enabled at build time. + *

                                                              + * Recommended when CPU resources are extremely limited. + */ + SPEEXDEC(3), ; final int value; diff --git a/src/main/java/org/rocstreaming/roctoolkit/ResamplerProfile.java b/src/main/java/org/rocstreaming/roctoolkit/ResamplerProfile.java index 1b188002..246f72f3 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/ResamplerProfile.java +++ b/src/main/java/org/rocstreaming/roctoolkit/ResamplerProfile.java @@ -1,24 +1,15 @@ -// DO NOT EDIT! Code generated by generate_enums script from roc-toolkit -// roc-toolkit git tag: v0.2.5-11-g14d642e9, commit: 14d642e9 +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 package org.rocstreaming.roctoolkit; /** * Resampler profile. *

                                                              - * Affects speed and quality. Each resampler backend treats profile in its own - * way. + * Affects CPU usage and quality. Each resampler backend treats profile in its own way. */ public enum ResamplerProfile { - /** - * Do not perform resampling. - *

                                                              - * Clock drift compensation will be disabled in this case. If in doubt, do - * not disable resampling. - */ - DISABLE(-1), - /** * Default profile. *

                                                              @@ -27,17 +18,17 @@ public enum ResamplerProfile { DEFAULT(0), /** - * High quality, low speed. + * High quality, higher CPU usage. */ HIGH(1), /** - * Medium quality, medium speed. + * Medium quality, medium CPU usage. */ MEDIUM(2), /** - * Low quality, high speed. + * Low quality, lower CPU usage. */ LOW(3), ; diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java index 051311c7..e4f3eae3 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java @@ -6,51 +6,48 @@ /** * Shared context. *

                                                              - * Context contains memory pools and network worker threads, shared among objects attached - * to the context. It is allowed both to create a separate context for every object, or - * to create a single context shared between multiple objects. + * Context contains memory pools and network worker threads, shared among objects attached to the + * context. It is allowed both to create a separate context for every object, or to create a + * single context shared between multiple objects. + * + * + *

                                                              Life cycle

                                                              + *

                                                              + * A context is created using {@link RocContext()} and destroyed using + * {@link RocContext#close()}. Objects can be attached and detached to an opened context at any + * moment from any thread. However, the user should ensure that the context is not closed until + * there are no objects attached to the context. + * + * + *

                                                              Thread safety

                                                              + *

                                                              + * Can be used concurrently. + * + * + *

                                                              Auto closing

                                                              *

                                                              - *

                                                              Lifecycle

                                                              - * A context is created using {@link #RocContext() RocContext()} or - * {@link #RocContext(RocContextConfig) RocContext(RocContextConfig)} and destroyed using - * {@link #close() close()}. RocContext class implements - * {@link AutoCloseable AutoCloseable} so if it is used in a try-with-resources - * statement the object is closed automatically at the end of the statement. - * Objects can be attached and detached to an opened context at any moment from - * any thread. However, the user should ensure that the context is not closed - * until there are no objects attached to the context. + * {@code RocContext} class implements {@link AutoCloseable}, so if it is used in a + * try-with-resources statement, the object is closed automatically at the end of the statement. + * * - * @see RocSender - * @see RocReceiver + * @see {@link RocSender} + * @see {@link RocReceiver} */ public class RocContext extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocContext.class.getName()); - /** - * Validate context constructor parameters and open a new context if validation is successful. - * - * @param config should point to an initialized config. - * - * @return the native roc context pointer. - * - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if there are not enough resources. - */ private static long construct(RocContextConfig config) throws IllegalArgumentException, Exception { Check.notNull(config, "config"); LOGGER.log(Level.FINE, "starting RocContext.open(), config={0}", new Object[]{config}); - long ptr = open(config); + long ptr = nativeOpen(config); LOGGER.log(Level.FINE, "finished RocContext.open(), ptr={0}", new Object[]{toHex(ptr)}); return ptr; } - /** - * Destruct native object - */ private static void destroy(long ptr) throws Exception { LOGGER.log(Level.FINE, "starting RocContext.close(), ptr={0}", new Object[]{toHex(ptr)}); - close(ptr); + nativeClose(ptr); LOGGER.log(Level.FINE, "finished RocContext.close(), ptr={0}", new Object[]{toHex(ptr)}); } @@ -59,8 +56,8 @@ private static void destroy(long ptr) throws Exception { *

                                                              * Allocates and initializes a new context. May start some background threads. * - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if there are not enough resources. + * @throws IllegalArgumentException if the arguments are invalid. + * @throws Exception if there are not enough resources. */ public RocContext() throws IllegalArgumentException, Exception { this(RocContextConfig.builder().build()); @@ -71,15 +68,49 @@ public RocContext() throws IllegalArgumentException, Exception { *

                                                              * Allocates and initializes a new context. May start some background threads. * - * @param config should point to an initialized config. + * @param config should point to an initialized config. * - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if there are not enough resources. + * @throws IllegalArgumentException if the arguments are invalid. + * @throws Exception if there are not enough resources. */ public RocContext(RocContextConfig config) throws IllegalArgumentException, Exception { super(construct(config), null, RocContext::destroy); } - private static native long open(RocContextConfig config) throws IllegalArgumentException, Exception; - private static native void close(long nativePtr) throws Exception; + /** + * Register custom encoding. + *

                                                              + * Registers {@code encoding} with given {@code encoding_id}. Registered encodings + * complement built-in encodings defined by {@link PacketEncoding} enum. Whenever you + * need to specify packet encoding, you can use both built-in and registered encodings. + *

                                                              + * On sender, you should register custom encoding and set to {@code packetEncoding} field + * of {@link RocSenderConfig}, if you need to force specific encoding of packets, but + * built-in set of encodings is not enough. + *

                                                              + * On receiver, you should register custom encoding with same id and specification, if + * you did so on sender, and you're not using any signaling protocol (like RTSP) that is + * capable of automatic exchange of encoding information. + *

                                                              + * In case of RTP, encoding id is mapped directly to payload type field (PT). + * + * @param encodingId is numeric encoding identifier in range {@code [1; 127]}. + * @param encoding is encoding specification to be associated with this id. + * + * @throws IllegalArgumentException if the arguments are invalid. + */ + public void registerEncoding(int encodingId, MediaEncoding encoding) throws IllegalArgumentException { + Check.inRange(encodingId, 1, 127, "encodingId"); + Check.notNull(encoding, "encoding"); + + LOGGER.log(Level.FINE, "starting RocContext.registerEncoding(), ptr={0}, encodingId={1}, encoding={2}", + new Object[]{toHex(getPtr()), encodingId, encoding}); + nativeRegisterEncoding(getPtr(), encodingId, encoding); + LOGGER.log(Level.FINE, "finished RocContext.registerEncoding(), ptr={0}", new Object[]{toHex(getPtr())}); + } + + private static native long nativeOpen(RocContextConfig config) throws IllegalArgumentException, Exception; + private static native void nativeClose(long contextPtr) throws Exception; + + private static native void nativeRegisterEncoding(long contextPtr, int encodingId, MediaEncoding encoding) throws IllegalArgumentException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java index 89831ee9..20e3c160 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java @@ -1,5 +1,9 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + package org.rocstreaming.roctoolkit; +import java.time.Duration; import lombok.*; /** @@ -17,23 +21,24 @@ public class RocContextConfig { /** * Maximum size in bytes of a network packet. - * Defines the amount of bytes allocated per network packet. - * Sender and receiver won't handle packets larger than this. - * If zero or unset, default value is used. - * Should not be negative. + *

                                                              + * Defines the amount of bytes allocated per network packet. Sender and receiver won't + * handle packets larger than this. If zero, default value is used. */ private int maxPacketSize; /** * Maximum size in bytes of an audio frame. - * Defines the amount of bytes allocated per intermediate internal - * frame in the pipeline. Does not limit the size of the frames - * provided by user. - * If zero or unset, default value is used. - * Should not be negative. + *

                                                              + * Defines the amount of bytes allocated per intermediate internal frame in the pipeline. + * Does not limit the size of the frames provided by user. If zero, default value is + * used. */ private int maxFrameSize; + /** + * Construct lombok builder for {@link RocContextConfig}. + */ public static RocContextConfig.Builder builder() { return new RocContextConfigValidator(); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java index a9cd797a..f1434b00 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java @@ -5,156 +5,179 @@ import java.util.logging.Logger; /** - * Receiver peer. + * Receiver node. *

                                                              - * Receiver gets the network packets from multiple senders, decodes audio streams - * from them, mixes multiple streams into a single stream, and returns it to the user. + * Receiver gets the network packets from multiple senders, decodes audio streams from them, + * mixes multiple streams into a single stream, and returns it to the user. + * * *

                                                              Context

                                                              *

                                                              - * Receiver is automatically attached to a context when opened and detached from it when - * closed. The user should not close the context until the receiver is closed. + * Receiver is automatically attached to a context when opened and detached from it when closed. + * The user should not close the context until the receiver is closed. *

                                                              - * Receiver work consists of two parts: packet reception and stream decoding. The - * decoding part is performed in the receiver itself, and the reception part is - * performed in the context network worker threads. + * Receiver work consists of two parts: packet reception and stream decoding. The decoding part + * is performed in the receiver itself, and the reception part is performed in the context + * network worker threads. + * * - *

                                                              Lifecycle

                                                              + *

                                                              Life cycle

                                                              + *

                                                              *

                                                                - *
                                                              • A receiver is created using {@link RocReceiver#RocReceiver RocReceiver()}.
                                                              • - *
                                                              • Optionally, the receiver parameters may be fine-tuned using - * {@link RocReceiver#setMulticastGroup setMulticastGroup()} function.
                                                              • - *
                                                              • The receiver either binds local endpoints using {@link RocReceiver#bind bind()}, - * allowing senders connecting to them, or itself connects to remote sender endpoints - * using {@link RocReceiver#connect connect()}. What approach to use is up to the user.
                                                              • - *
                                                              • The audio stream is iteratively read from the receiver using {@link RocReceiver#read read()}. - * Receiver returns the mixed stream from all connected senders.
                                                              • - *
                                                              • The receiver is destroyed using {@link RocReceiver#close close()}. RocReceiver - * class implements {@link AutoCloseable AutoCloseable} so if it is used in a try-with-resources - * statement the object is closed automatically at the end of the statement.
                                                              • + *
                                                              • A receiver is created using {@link RocReceiver()}.
                                                              • + *
                                                              • Optionally, the receiver parameters may be fine-tuned using + * {@link RocReceiver#configure()}.
                                                              • + *
                                                              • The receiver either binds local endpoints using {@link RocReceiver#bind()}, allowing + * senders connecting to them, or itself connects to remote sender endpoints using + * roc_receiver_connect(). What approach to use is up to the user.
                                                              • + *
                                                              • The audio stream is iteratively read from the receiver using {@link RocReceiver#read()}. + * Receiver returns the mixed stream from all connected senders.
                                                              • + *
                                                              • The receiver is destroyed using {@link RocReceiver#close()}.
                                                              • *
                                                              * + * *

                                                              Slots, interfaces, and endpoints

                                                              *

                                                              * Receiver has one or multiple slots, which may be independently bound or connected. - * Slots may be used to bind receiver to multiple addresses. Slots are numbered from - * zero and are created automatically. In simple cases just use {@link Slot#DEFAULT}. + * Slots may be used to bind receiver to multiple addresses. Slots are numbered from zero and are + * created automatically. In simple cases just use {@code ROC_SLOT_DEFAULT}. *

                                                              - * Each slot has its own set of interfaces, one per each type defined in {@link Interface}. - * The interface defines the type of the communication with the remote peer + * Each slot has its own set of interfaces, one per each type defined in + * {@link Interface}. The interface defines the type of the communication with the remote node * and the set of the protocols supported by it. *

                                                              * Supported actions with the interface: + *

                                                              *

                                                                - *
                                                              • Call {@link RocReceiver#bind bind()} to bind the interface to a local {@link Endpoint}. - * In this case the receiver accepts connections from senders mixes their streams into the single output stream.
                                                              • - *
                                                              • Call {@link RocReceiver#connect connect()} to connect the interface to a remote {@link Endpoint}. - * In this case the receiver initiates connection to the sender and requests it to - * start sending media stream to the receiver.
                                                              • + *
                                                              • Call {@link RocReceiver#bind()} to bind the interface to a local {@link Endpoint}. In + * this case the receiver accepts connections from senders mixes their streams into the + * single output stream.
                                                              • + *
                                                              • Call roc_receiver_connect() to connect the interface to a remote {@link Endpoint}. In + * this case the receiver initiates connection to the sender and requests it to start + * sending media stream to the receiver.
                                                              • *
                                                              *

                                                              * Supported interface configurations: + *

                                                              *

                                                                - *
                                                              • Bind {@link Interface#CONSOLIDATED} to a local endpoint (e.g. be an RTSP server).
                                                              • - *
                                                              • Connect {@link Interface#CONSOLIDATED} to a remote endpoint (e.g. be an RTSP client).
                                                              • - *
                                                              • Bind {@link Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR} (optionally, for FEC), - * and {@link Interface#AUDIO_CONTROL} (optionally, for control messages) to local endpoints - * (e.g. be an RTP/FECFRAME/RTCP sender).
                                                              • + *
                                                              • Bind {@link Interface#CONSOLIDATED} to a local endpoint (e.g. be an RTSP server).
                                                              • + *
                                                              • Connect {@link Interface#CONSOLIDATED} to a remote endpoint (e.g. be an RTSP + * client).
                                                              • + *
                                                              • Bind {@link Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR} (optionally, for + * FEC), and {@link Interface#AUDIO_CONTROL} (optionally, for control messages) to local + * endpoints (e.g. be an RTP/FECFRAME/RTCP receiver).
                                                              • *
                                                              + *

                                                              + * Slots can be removed using {@link RocReceiver#unlink()}. Removing a slot also removes all its + * interfaces and terminates all associated connections. + *

                                                              + * Slots can be added and removed at any time on fly and from any thread. It is safe to do it + * from another thread concurrently with reading frames. Operations with slots won't block + * concurrent reads. + * * *

                                                              FEC scheme

                                                              *

                                                              - * If {@link Interface#CONSOLIDATED} is used, it automatically creates all necessary - * transport interfaces and the user should not bother about them. + * If {@link Interface#CONSOLIDATED} is used, it automatically creates all necessary transport + * interfaces and the user should not bother about them. *

                                                              * Otherwise, the user should manually configure {@link Interface#AUDIO_SOURCE} and * {@link Interface#AUDIO_REPAIR} interfaces: + *

                                                              *

                                                                - *
                                                              • If FEC is disabled {@link FecEncoding#DISABLE}, only {@link Interface#AUDIO_SOURCE} - * should be configured. It will be used to transmit audio packets.
                                                              • - *
                                                              • If FEC is enabled, both {@link Interface#AUDIO_SOURCE} and {@link Interface#AUDIO_REPAIR} - * interfaces should be configured. The second interface will be used to transmit redundant repair data.
                                                              • + *
                                                              • If FEC is disabled ( {@link FecEncoding#DISABLE} ), only {@link Interface#AUDIO_SOURCE} + * should be configured. It will be used to transmit audio packets.
                                                              • + *
                                                              • If FEC is enabled, both {@link Interface#AUDIO_SOURCE} and + * {@link Interface#AUDIO_REPAIR} interfaces should be configured. The second interface + * will be used to transmit redundant repair data.
                                                              • *
                                                              *

                                                              - * The protocols for the two interfaces should correspond to each other and to the FEC - * scheme. For example, if {@link FecEncoding#RS8M} is used, the protocols should be + * The protocols for the two interfaces should correspond to each other and to the FEC scheme. + * For example, if {@link FecEncoding#RS8M} is used, the protocols should be * {@link Protocol#RTP_RS8M_SOURCE} and {@link Protocol#RS8M_REPAIR}. * + * *

                                                              Sessions

                                                              - * Receiver creates a session object for every sender connected to it. Sessions can appear - * and disappear at any time. Multiple sessions can be active at the same time. *

                                                              - * A session is identified by the sender address. A session may contain multiple packet - * streams sent to different receiver ports. If the sender employs FEC, the session will - * contain source and repair packet streams. Otherwise, the session will contain a single - * source packet stream. + * Receiver creates a session object for every sender connected to it. Sessions can appear and + * disappear at any time. Multiple sessions can be active at the same time. + *

                                                              + * A session is identified by the sender address. A session may contain multiple packet streams + * sent to different receiver ports. If the sender employs FEC, the session will contain source + * and repair packet streams. Otherwise, the session will contain a single source packet stream. *

                                                              - * A session is created automatically on the reception of the first packet from a new - * address and destroyed when there are no packets during a timeout. A session is also - * destroyed on other events like a large latency underrun or overrun or broken playback, - * but if the sender continues to send packets, it will be created again shortly. + * A session is created automatically on the reception of the first packet from a new address and + * destroyed when there are no packets during a timeout. A session is also destroyed on other + * events like a large latency underrun or overrun or broken playback, but if the sender + * continues to send packets, it will be created again shortly. + * * *

                                                              Mixing

                                                              - * Receiver mixes audio streams from all currently active sessions into a single output - * stream. *

                                                              - * The output stream continues no matter how much active sessions there are at the moment. - * In particular, if there are no sessions, the receiver produces a stream with all zeros. + * Receiver mixes audio streams from all currently active sessions into a single output stream. + *

                                                              + * The output stream continues no matter how much active sessions there are at the moment. In + * particular, if there are no sessions, the receiver produces a stream with all zeros. *

                                                              - * Sessions can be added and removed from the output stream at any time, probably in the - * middle of a frame. + * Sessions can be added and removed from the output stream at any time, probably in the middle + * of a frame. + * * *

                                                              Sample rate

                                                              - * Every session may have a different sample rate. And even if nominally all of them are - * of the same rate, device frequencies usually differ by a few tens of Hertz. *

                                                              - * Receiver compensates these differences by adjusting the rate of every session stream to - * the rate of the receiver output stream using a per-session resampler. The frequencies - * factor between the sender and the receiver clocks is calculated dynamically for every - * session based on the session incoming packet queue size. + * Every session may have a different sample rate. And even if nominally all of them are of the + * same rate, device frequencies usually differ by a few tens of Hertz. *

                                                              - * Resampling is a quite time-consuming operation. The user can choose between completely - * disabling resampling (at the cost of occasional underruns or overruns) or several - * resampler profiles providing different compromises between CPU consumption and quality. + * Receiver compensates these differences by adjusting the rate of every session stream to the + * rate of the receiver output stream using a per-session resampler. The frequencies factor + * between the sender and the receiver clocks is calculated dynamically for every session based + * on the session incoming packet queue size. + *

                                                              + * Resampling is a quite time-consuming operation. The user can choose between several resampler + * profiles providing different compromises between CPU consumption and quality. + * * *

                                                              Clock source

                                                              - * Receiver should decode samples at a constant rate that is configured when the receiver - * is created. There are two ways to accomplish this: - *
                                                                - *
                                                              • - * If the user enabled internal clock {@link ClockSource#INTERNAL}, - * the receiver employs a CPU timer to block reads until it's time to - * decode the next bunch of samples according to the configured sample rate. *

                                                                - * This mode is useful when the user passes samples to a non-realtime destination, e.g. to an audio file. - *

                                                              • - *
                                                              • - * If the user enabled external clock {@link ClockSource#EXTERNAL}, - * the samples read from the receiver are decoded immediately and hence the user is - * responsible to call read operation according to the sample rate. + * Receiver should decode samples at a constant rate that is configured when the receiver is + * created. There are two ways to accomplish this: *

                                                                - * This mode is useful when the user passes samples to a realtime destination with its - * own clock, e.g. to an audio device. Internal clock should not be used in this case - * because the audio device and the CPU might have slightly different clocks, and the - * difference will eventually lead to an underrun or an overrun. - *

                                                              • + *
                                                                  + *
                                                                • If the user enabled internal clock ( {@link ClockSource#INTERNAL} ), the receiver + * employs a CPU timer to block reads until it's time to decode the next bunch of samples + * according to the configured sample rate. This mode is useful when the user passes + * samples to a non-realtime destination, e.g. to an audio file.
                                                                • + *
                                                                • If the user enabled external clock ( {@link ClockSource#EXTERNAL} ), the samples read + * from the receiver are decoded immediately and hence the user is responsible to call read + * operation according to the sample rate. This mode is useful when the user passes samples + * to a realtime destination with its own clock, e.g. to an audio device. Internal clock + * should not be used in this case because the audio device and the CPU might have slightly + * different clocks, and the difference will eventually lead to an underrun or an + * overrun.
                                                                • *
                                                                * - *

                                                                Thread-safety

                                                                + * + *

                                                                Thread safety

                                                                *

                                                                - * Can be used concurrently + * Can be used concurrently. + * * *

                                                                Example

                                                                *
                                                                  * {@code
                                                                - * RocReceiverConfig config = RocReceiverConfig.builder()
                                                                - *             .frameSampleRate(SAMPLE_RATE)
                                                                - *             .frameChannels(ChannelSet.STEREO)
                                                                - *             .frameEncoding(FrameEncoding.PCM_FLOAT)
                                                                - *             .build();
                                                                + * RocReceiverConfig receiverConfig = RocReceiverConfig.builder()
                                                                + *         .frameEncoding(
                                                                + *                 MediaEncoding.builder()
                                                                + *                         .rate(44100)
                                                                + *                         .format(Format.PCM_FLOAT32)
                                                                + *                         .channels(ChannelLayout.STEREO)
                                                                + *                         .build()
                                                                + *         )
                                                                + *         .clockSource(ClockSource.INTERNAL)
                                                                +           .build();
                                                                  * try (
                                                                  *     RocContext context = new RocContext();
                                                                - *     RocReceiver receiver = new RocReceiver(context, config);
                                                                + *     RocReceiver receiver = new RocReceiver(context, receiverConfig);
                                                                  * ) {
                                                                  *     receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:0"));
                                                                  *     receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:0"));
                                                                @@ -164,6 +187,7 @@
                                                                  * }
                                                                  * 
                                                                * + * * @see RocContext * @see RocReceiverConfig * @see java.lang.AutoCloseable @@ -172,34 +196,22 @@ public class RocReceiver extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocReceiver.class.getName()); - /** - * Validate receiver constructor parameters and open a new receiver if validation is successful. - * - * @param context should point to an opened context. - * @param config should point to an initialized config. - * @return the native roc receiver pointer. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the receiver. - */ private static long construct(RocContext context, RocReceiverConfig config) throws IllegalArgumentException, Exception { Check.notNull(context, "context"); Check.notNull(config, "config"); LOGGER.log(Level.FINE, "starting RocReceiver.open(), context ptr={0}, config={1}", new Object[]{toHex(context.getPtr()), config}); - long ptr = open(context.getPtr(), config); + long ptr = nativeOpen(context.getPtr(), config); LOGGER.log(Level.FINE, "finished RocReceiver.open(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); return ptr; } - /** - * Destruct native object - */ private static void destroy(long ptr, RocContext context) throws Exception { LOGGER.log(Level.FINE, "starting RocReceiver.close(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); - close(ptr); + nativeClose(ptr); LOGGER.log(Level.FINE, "finished RocReceiver.close(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); } @@ -209,74 +221,71 @@ private static void destroy(long ptr, RocContext context) throws Exception { *

                                                                * Allocates and initializes a new receiver, and attaches it to the context. * - * @param context should point to an opened context. - * @param config should point to an initialized config. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the receiver. + * @param context should point to an opened context. + * @param config should point to an initialized config. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws Exception if an error occurred when creating the receiver. */ public RocReceiver(RocContext context, RocReceiverConfig config) throws IllegalArgumentException, Exception { super(construct(context, config), context, ptr -> destroy(ptr, context)); } /** - * Set receiver interface multicast group. - *

                                                                - * Optional. - *

                                                                - * Multicast group should be set only when binding receiver interface to an endpoint with - * multicast IP address. If present, it defines an IP address of the OS network interface - * on which to join the multicast group. If not present, no multicast group is joined. + * Set receiver interface configuration. *

                                                                - * It's possible to receive multicast traffic from only those OS network interfaces, on - * which the process has joined the multicast group. When using multicast, the user should - * either call this function, or join multicast group manually using OS-specific API. - *

                                                                - * It is allowed to set multicast group to `0.0.0.0` (for IPv4) or to `::` (for IPv6), - * to be able to receive multicast traffic from all available interfaces. However, this - * may not be desirable for security reasons. - *

                                                                - * Each slot's interface can have only one multicast group. The function should be called - * before calling {@link RocReceiver#bind bind()} for the interface. It should not be called when - * calling {@link RocReceiver#connect connect()} for the interface. + * Updates configuration of specified interface of specified slot. If + * called, the call should be done before calling {@link RocReceiver#bind()} + * or roc_receiver_connect() for the same interface. *

                                                                * Automatically initializes slot with given index if it's used first time. + *

                                                                + * If an error happens during configure, the whole slot is disabled and + * marked broken. The slot index remains reserved. The user is responsible + * for removing the slot using {@link RocReceiver#unlink()}, after which + * slot index can be reused. * - * @param slot specifies the receiver slot - * @param iface specifies the receiver interface - * @param ip should be IPv4 or IPv6 address - * @throws IllegalArgumentException if the arguments are invalid. + * @param slot specifies the receiver slot. + * @param iface specifies the receiver interface. + * @param config specifies settings for the specified interface. */ - public void setMulticastGroup(Slot slot, Interface iface, String ip) throws IllegalArgumentException, Exception { + public void configure(Slot slot, Interface iface, InterfaceConfig config) throws IllegalArgumentException { Check.notNull(slot, "slot"); Check.notNull(iface, "iface"); - Check.notEmpty(ip, "ip"); + Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocReceiver.setMulticastGroup(), ptr={0}, slot={1}, iface={2}, ip={3}", - new Object[]{toHex(getPtr()), slot, iface, ip}); - setMulticastGroup(getPtr(), slot.getValue(), iface.value, ip); - LOGGER.log(Level.FINE, "finished RocReceiver.setMulticastGroup(), ptr={0}", new Object[]{toHex(getPtr())}); + LOGGER.log(Level.FINE, "starting RocReceiver.configure(), ptr={0}, slot={1}, iface={2}, config={3}", + new Object[]{toHex(getPtr()), slot, iface, config}); + nativeConfigure(getPtr(), slot.getValue(), iface.value, config); + LOGGER.log(Level.FINE, "finished RocReceiver.configure(), ptr={0}", new Object[]{toHex(getPtr())}); } /** * Bind the receiver interface to a local endpoint. *

                                                                - * Checks that the endpoint is valid and supported by the interface, allocates - * a new ingoing port, and binds it to the local endpoint. + * Checks that the endpoint is valid and supported by the interface, + * allocates a new ingoing port, and binds it to the local endpoint. *

                                                                - * Each slot's interface can be bound or connected only once. - * May be called multiple times for different slots or interfaces. + * Each slot's interface can be bound or connected only once. May be called + * multiple times for different slots or interfaces. *

                                                                * Automatically initializes slot with given index if it's used first time. *

                                                                - * If endpoint has explicitly set zero port, the receiver is bound to a randomly - * chosen ephemeral port. If the function succeeds, the actual port to which the - * receiver was bound is written back to endpoint. + * If an error happens during bind, the whole slot is disabled and marked + * broken. The slot index remains reserved. The user is responsible for + * removing the slot using {@link RocReceiver#unlink()}, after which slot + * index can be reused. + *

                                                                + * If {@code endpoint} has explicitly set zero port, the receiver is bound + * to a randomly chosen ephemeral port. If the function succeeds, the actual + * port to which the receiver was bound is written back to {@code endpoint}. + * + * @param slot specifies the receiver slot. + * @param iface specifies the receiver interface. + * @param endpoint specifies the receiver endpoint. * - * @param slot specifies the receiver slot - * @param iface specifies the receiver interface - * @param endpoint specifies the receiver endpoint - * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if the address can't be bound or if there are not enough resources. + * @throws IllegalArgumentException if the arguments are invalid. + * @throws IOException if the address can't be bound or there are not enough resources. */ public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalArgumentException, IOException { Check.notNull(slot, "slot"); @@ -285,44 +294,63 @@ public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalAr LOGGER.log(Level.FINE, "starting RocReceiver.bind(), ptr={0}, slot={1}, iface={2}, endpoint={3}", new Object[]{toHex(getPtr()), slot, iface, endpoint}); - bind(getPtr(), slot.getValue(), iface.value, endpoint); + nativeBind(getPtr(), slot.getValue(), iface.value, endpoint); LOGGER.log(Level.FINE, "finished RocReceiver.bind(), ptr={0}, endpoint={1}", new Object[]{toHex(getPtr()), endpoint}); } - public void connect(Endpoint endpoint) { - throw new RuntimeException("connect not implemented"); + /** + * Delete receiver slot. + *

                                                                + * Disconnects, unbinds, and removes all slot interfaces and removes the + * slot. All associated connections to remote nodes are properly terminated. + *

                                                                + * After unlinking the slot, it can be re-created again by re-using slot + * index. + * + * @param slot specifies the receiver slot to delete. + * + * @throws IllegalArgumentException if the arguments are invalid. + */ + public void unlink(Slot slot) throws IllegalArgumentException { + Check.notNull(slot, "slot"); + + LOGGER.log(Level.FINE, "starting RocReceiver.unlink(), ptr={0}, slot={1}", new Object[]{toHex(getPtr()), slot}); + nativeUnlink(getPtr(), slot.getValue()); + LOGGER.log(Level.FINE, "finished RocReceiver.connect(), ptr={0}", new Object[]{toHex(getPtr())}); } /** * Read samples from the receiver. *

                                                                - * Reads network packets received on bound ports, routes packets to sessions, repairs lost - * packets, decodes samples, resamples and mixes them, and finally stores samples into the - * provided frame. + * Reads retrieved network packets, decodes packets, routes packets to + * sessions, repairs losses, extracts samples, adjusts sample rate and + * channel layout, compensates clock drift, mixes samples from all sessions, + * and finally stores samples into the provided frame. *

                                                                - * If {@link ClockSource#INTERNAL} is used, the function blocks until it's time to decode the - * samples according to the configured sample rate. + * If {@link ClockSource#INTERNAL} is used, the function blocks until it's + * time to decode the samples according to the configured sample rate. *

                                                                - * Until the receiver is connected to at least one sender, it produces silence. - * If the receiver is connected to multiple senders, it mixes their streams into one. + * Until the receiver is connected to at least one sender, it produces + * silence. If the receiver is connected to multiple senders, it mixes their + * streams into one. + * + * @param samples should point to an initialized {@code float} array which will be + * filled with samples. * - * @param samples should point to an initialized float array which will be - * filled with samples. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if there are not enough resources. + * @throws IllegalArgumentException if the arguments are invalid. + * @throws IOException if there are not enough resources. */ public void read(float[] samples) throws IllegalArgumentException, IOException { Check.notNull(samples, "samples"); - readFloats(getPtr(), samples); + nativeReadFloats(getPtr(), samples); } - private static native long open(long contextPtr, RocReceiverConfig config) throws IllegalArgumentException, Exception; - - private native void setMulticastGroup(long receiverPtr, int slot, int iface, String ip) throws IllegalArgumentException; - - private native void bind(long receiverPtr, int slot, int iface, Endpoint endpoint) throws IllegalArgumentException, IOException; + private static native long nativeOpen(long contextPtr, RocReceiverConfig config) throws IllegalArgumentException, Exception; + private static native void nativeClose(long receiverPtr) throws IOException; - private native void readFloats(long receiverPtr, float[] samples) throws IOException; + private native void nativeConfigure(long receiverPtr, int slot, int iface, InterfaceConfig config) throws IllegalArgumentException; + private native void nativeBind(long receiverPtr, int slot, int iface, Endpoint endpoint) throws IllegalArgumentException, IOException; + private native void nativeUnlink(long receiverPtr, int slot) throws IllegalArgumentException; - private static native void close(long receiverPtr) throws IOException; + private native void nativeReadFloats(long receiverPtr, float[] samples) throws IOException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java index f9f9729b..1b0c45a3 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java @@ -1,3 +1,6 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + package org.rocstreaming.roctoolkit; import java.time.Duration; @@ -17,117 +20,96 @@ public class RocReceiverConfig { /** - * The rate of the samples in the frames returned to the user. - * Number of samples per channel per second. - * Should be set to a positive value. + * The encoding used in frames returned by receiver. + *

                                                                + * Frame encoding defines sample format, channel layout, and sample rate in local frames + * returned by receiver to user. Should be set (zero value is invalid). */ - private int frameSampleRate; + private MediaEncoding frameEncoding; /** - * The channel set in the frames returned to the user. - * Should be set to a non-null value. + * Clock source. + *

                                                                + * Defines whether read operation will be blocking or non-blocking. If zero, + * {@link ClockSource#EXTERNAL} is used. */ - private ChannelSet frameChannels; + private ClockSource clockSource; /** - * The sample encoding in the frames returned to the user. - * Should be set to a non-null value. + * Clock synchronization backend. + *

                                                                + * Defines how sender and receiver clocks are synchronized. If zero, default value is + * used. */ - private FrameEncoding frameEncoding; + private ClockSyncBackend clockSyncBackend; /** - * Clock source to use. - * Defines whether read operation will be blocking or non-blocking. - * If null or unset, default value is used. + * Clock synchronization profile. + *

                                                                + * Defines what latency and network jitter are tolerated. If zero, default value is used. */ - private ClockSource clockSource; + private ClockSyncProfile clockSyncProfile; /** - * Resampler backend to use. - * If null or unset, default value is used. + * Resampler backend. + *

                                                                + * Affects CPU usage, quality, and clock synchronization precision. If zero, default + * value is used. */ private ResamplerBackend resamplerBackend; /** - * Resampler profile to use. - * If null or unset, default value is used. - * If resampling is enabled, the receiver employs resampler for two purposes: - *

                                                                  - *
                                                                • - * adjust the sender clock to the receiver clock, which - * may differ a bit - *
                                                                • - *
                                                                • - * convert the packet sample rate to the frame sample - * rate if they are different - *
                                                                • - *
                                                                + * Resampler profile. + *

                                                                + * Affects CPU usage and quality. If zero, default value is used. */ private ResamplerProfile resamplerProfile; /** * Target latency, in nanoseconds. - * The session will not start playing until it accumulates the - * requested latency. - * Then, if resampler is enabled, the session will adjust its clock - * to keep actual latency as close as possible to the target latency. - * If zero or unset, default value is used. - * Should not be negative. + *

                                                                + * The session will not start playing until it accumulates the requested latency. Then, + * if clock synchronization is enabled, the session will adjust its clock to keep actual + * latency as close as possible to the target latency. If zero, default value is used. */ private Duration targetLatency; /** - * Maximum delta between current and target latency, in nanoseconds. - * If current latency becomes larger than the target latency plus - * this value, the session is terminated. - * If zero or unset, default value is used. - * Should not be negative. + * Maximum allowed delta between current and target latency, in nanoseconds. + *

                                                                + * If session latency differs from the target latency by more than given value, the + * session is terminated (it can then automatically restart). Receiver itself is not + * terminated; if there are no sessions, it will produce zeros. If zero, default value is + * used. */ - private Duration maxLatencyOverrun; - - /** - * Maximum delta between target and current latency, in nanoseconds. - * If current latency becomes smaller than the target latency minus - * this value, the session is terminated. - * May be larger than the target latency because current latency may - * be negative, which means that the playback run ahead of the last - * packet received from network. - * If zero or unset, default value is used. - * Should not be negative. - */ - private Duration maxLatencyUnderrun; + private Duration latencyTolerance; /** * Timeout for the lack of playback, in nanoseconds. - * If there is no playback during this period, the session is terminated. - * This mechanism allows to detect dead, hanging, or broken clients - * generating invalid packets. - * If zero or unset, default value is used. - * If negative, the timeout is disabled. + *

                                                                + * If there is no playback during this period, the session is terminated (it can then + * automatically restart). Receiver itself is not terminated; if there are no sessions, + * it will produce zeros. This mechanism allows to detect dead, hanging, or incompatible + * clients that generate unparseable packets. If zero, default value is used. If + * negative, the timeout is disabled. */ private Duration noPlaybackTimeout; /** - * Timeout for broken playback, in nanoseconds. - * If there the playback is considered broken during this period, - * the session is terminated. The playback is broken if there is - * a breakage detected at every breakageDetectionWindow - * during brokenPlaybackTimeout. - * This mechanism allows to detect vicious circles like when all - * client packets are a bit late and receiver constantly drops them - * producing unpleasant noise. - * If zero or unset, default value is used. - * If negative, the timeout is disabled. + * Timeout for choppy playback, in nanoseconds. + *

                                                                + * If there is constant stuttering during this period, the session is terminated (it can + * then automatically restart). Receiver itself is not terminated; if there are no + * sessions, it will produce zeros. This mechanism allows to detect situations when + * playback continues but there are frequent glitches, for example because there is a + * high ratio of late packets. If zero, default value is used. If negative, the timeout + * is disabled. */ - private Duration brokenPlaybackTimeout; + private Duration choppyPlaybackTimeout; /** - * Breakage detection window, in nanoseconds. - * If zero or unset, default value is used. - * Should not be negative. + * Construct lombok builder for {@link RocReceiverConfig}. */ - private Duration breakageDetectionWindow; - public static RocReceiverConfig.Builder builder() { return new RocReceiverConfigValidator(); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java index ab58ba9d..faf10185 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java @@ -7,13 +7,9 @@ class RocReceiverConfigValidator extends RocReceiverConfig.Builder { @Override public RocReceiverConfig build() { RocReceiverConfig config = super.build(); - Check.notNegative(config.getFrameSampleRate(), "frameSampleRate"); - Check.notNull(config.getFrameChannels(), "frameChannels"); Check.notNull(config.getFrameEncoding(), "frameEncoding"); Check.notNegative(config.getTargetLatency(), "targetLatency"); - Check.notNegative(config.getMaxLatencyOverrun(), "maxLatencyOverrun"); - Check.notNegative(config.getMaxLatencyUnderrun(), "maxLatencyUnderrun"); - Check.notNegative(config.getBreakageDetectionWindow(), "breakageDetectionWindow"); + Check.notNegative(config.getLatencyTolerance(), "latencyTolerance"); return config; } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java index 0ed1f766..57c60e0a 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java @@ -5,136 +5,149 @@ import java.util.logging.Logger; /** - * Sender peer. + * Sender node. *

                                                                - * Sender gets an audio stream from the user, encodes it into network packets, and - * transmits them to a remote receiver. + * Sender gets an audio stream from the user, encodes it into network packets, and transmits them + * to a remote receiver. + * * *

                                                                Context

                                                                *

                                                                - * Sender is automatically attached to a context when opened and detached from it when - * closed. The user should not close the context until the sender is closed. + * Sender is automatically attached to a context when opened and detached from it when closed. + * The user should not close the context until the sender is closed. *

                                                                - * Sender work consists of two parts: stream encoding and packet transmission. The - * encoding part is performed in the sender itself, and the transmission part is - * performed in the context network worker threads. + * Sender work consists of two parts: stream encoding and packet transmission. The encoding part + * is performed in the sender itself, and the transmission part is performed in the context + * network worker threads. * - *

                                                                Lifecycle

                                                                - *

                                                                - *

                                                                  - *
                                                                • A sender is created using {@link RocSender#RocSender RocSender()}.
                                                                • - *
                                                                • Optionally, the sender parameters may be fine-tuned using - * {@link RocSender#setOutgoingAddress(Slot, Interface, String)} function.
                                                                • - *
                                                                • The sender either binds local endpoints using {@link RocSender#bind bind()}, - * allowing receivers connecting to them, or itself connects to remote receiver endpoints - * using {@link RocSender#connect connect()}. What approach to use is up to the user.
                                                                • - *
                                                                • The audio stream is iteratively written to the sender using {@link RocSender#write write()}. - * The sender encodes the stream into packets and send to connected receiver(s).
                                                                • - *
                                                                • The sender is destroyed using {@link RocSender#close close()}.
                                                                • - *
                                                                + * + *

                                                                Life cycle

                                                                *

                                                                - * RocSender class implements {@link AutoCloseable AutoCloseable} so if it is used in a - * try-with-resources statement the object is closed automatically at the end of the statement. + *

                                                                  + *
                                                                • A sender is created using {@link RocSender()}.
                                                                • + *
                                                                • Optionally, the sender parameters may be fine-tuned using + * {@link RocSender#configure()}.
                                                                • + *
                                                                • The sender either binds local endpoints using roc_sender_bind(), allowing receivers + * connecting to them, or itself connects to remote receiver endpoints using + * {@link RocSender#connect()}. What approach to use is up to the user.
                                                                • + *
                                                                • The audio stream is iteratively written to the sender using {@link RocSender#write()}. + * The sender encodes the stream into packets and send to connected receiver(s).
                                                                • + *
                                                                • The sender is destroyed using {@link RocSender#close()}.
                                                                • + *
                                                                + * * *

                                                                Slots, interfaces, and endpoints

                                                                *

                                                                - * Sender has one or multiple slots, which may be independently bound or connected. - * Slots may be used to connect sender to multiple receivers. Slots are numbered from - * zero and are created automatically. In simple cases just use {@link Slot#DEFAULT}. + * Sender has one or multiple slots, which may be independently bound or connected. Slots + * may be used to connect sender to multiple receivers. Slots are numbered from zero and are + * created automatically. In simple cases just use {@code ROC_SLOT_DEFAULT}. *

                                                                - * Each slot has its own set of interfaces, one per each type defined in {@link Interface}. - * The interface defines the type of the communication with the remote peer + * Each slot has its own set of interfaces, one per each type defined in + * {@link Interface}. The interface defines the type of the communication with the remote node * and the set of the protocols supported by it. *

                                                                * Supported actions with the interface: + *

                                                                *

                                                                  - *
                                                                • Call {@link RocSender#bind bind()} to bind the interface to a local {@link Endpoint}. - * In this case the sender accepts connections from receivers and sends media stream - * to all connected receivers.
                                                                • - *
                                                                • Call {@link RocSender#connect(Slot, Interface, Endpoint)} to connect the interface - * to a remote {@link Endpoint}. In this case the sender initiates connection to the - * receiver and starts sending media stream to it.
                                                                • + *
                                                                • Call roc_sender_bind() to bind the interface to a local {@link Endpoint}. In this case + * the sender accepts connections from receivers and sends media stream to all connected + * receivers.
                                                                • + *
                                                                • Call {@link RocSender#connect()} to connect the interface to a remote {@link Endpoint}. + * In this case the sender initiates connection to the receiver and starts sending media + * stream to it.
                                                                • *
                                                                *

                                                                * Supported interface configurations: + *

                                                                *

                                                                  - *
                                                                • Connect {@link Interface#CONSOLIDATED} to a remote endpoint (e.g. be an RTSP client).
                                                                • - *
                                                                • Bind {@link Interface#CONSOLIDATED} to a local endpoint (e.g. be an RTSP server).
                                                                • - *
                                                                • Connect {@link Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR} (optionally, for FEC), - * and {@link Interface#AUDIO_CONTROL} (optionally, for control messages) to remote endpoints - * (e.g. be an RTP/FECFRAME/RTCP sender).
                                                                • + *
                                                                • Connect {@link Interface#CONSOLIDATED} to a remote endpoint (e.g. be an RTSP + * client).
                                                                • + *
                                                                • Bind {@link Interface#CONSOLIDATED} to a local endpoint (e.g. be an RTSP server).
                                                                • + *
                                                                • Connect {@link Interface#AUDIO_SOURCE}, {@link Interface#AUDIO_REPAIR} (optionally, for + * FEC), and {@link Interface#AUDIO_CONTROL} (optionally, for control messages) to remote + * endpoints (e.g. be an RTP/FECFRAME/RTCP sender).
                                                                • *
                                                                + *

                                                                + * Slots can be removed using {@link RocSender#unlink()}. Removing a slot also removes all its + * interfaces and terminates all associated connections. + *

                                                                + * Slots can be added and removed at any time on fly and from any thread. It is safe to do it + * from another thread concurrently with writing frames. Operations with slots won't block + * concurrent writes. + * * *

                                                                FEC scheme

                                                                *

                                                                - * If {@link Interface#CONSOLIDATED} is used, it automatically creates all necessary - * transport interfaces and the user should not bother about them. + * If {@link Interface#CONSOLIDATED} is used, it automatically creates all necessary transport + * interfaces and the user should not bother about them. *

                                                                * Otherwise, the user should manually configure {@link Interface#AUDIO_SOURCE} and * {@link Interface#AUDIO_REPAIR} interfaces: + *

                                                                *

                                                                  - *
                                                                • If FEC is disabled {@link FecEncoding#DISABLE}, only {@link Interface#AUDIO_SOURCE} - * should be configured. It will be used to transmit audio packets.
                                                                • - *
                                                                • If FEC is enabled, both {@link Interface#AUDIO_SOURCE} and {@link Interface#AUDIO_REPAIR} - * interfaces should be configured. The second interface will be used to transmit redundant repair data.
                                                                • + *
                                                                • If FEC is disabled ( {@link FecEncoding#DISABLE} ), only {@link Interface#AUDIO_SOURCE} + * should be configured. It will be used to transmit audio packets.
                                                                • + *
                                                                • If FEC is enabled, both {@link Interface#AUDIO_SOURCE} and + * {@link Interface#AUDIO_REPAIR} interfaces should be configured. The second interface + * will be used to transmit redundant repair data.
                                                                • *
                                                                *

                                                                - * The protocols for the two interfaces should correspond to each other and to the FEC - * scheme. For example, if {@link FecEncoding#RS8M} is used, the protocols should be + * The protocols for the two interfaces should correspond to each other and to the FEC scheme. + * For example, if {@link FecEncoding#RS8M} is used, the protocols should be * {@link Protocol#RTP_RS8M_SOURCE} and {@link Protocol#RS8M_REPAIR}. * + * *

                                                                Sample rate

                                                                *

                                                                * If the sample rate of the user frames and the sample rate of the network packets are * different, the sender employs resampler to convert one rate to another. *

                                                                - * Resampling is a quite time-consuming operation. The user can choose between completely - * disabling resampling (and so use the same rate for frames and packets) or several - * resampler profiles providing different compromises between CPU consumption and quality. + * Resampling is a quite time-consuming operation. The user can choose between several resampler + * profiles providing different compromises between CPU consumption and quality. + * * *

                                                                Clock source

                                                                *

                                                                - * Sender should encode samples at a constant rate that is configured when the sender - * is created. There are two ways to accomplish this: - *

                                                                - *

                                                                  - *
                                                                • - * If the user enabled internal clock {@link ClockSource#INTERNAL}, the sender employs a - * CPU timer to block writes until it's time to encode the next bunch of samples - * according to the configured sample rate. + * Sender should encode samples at a constant rate that is configured when the sender is created. + * There are two ways to accomplish this: *

                                                                  - * This mode is useful when the user gets samples from a non-realtime source, e.g. - * from an audio file. - *

                                                                • - *
                                                                • - * If the user enabled external clock {@link ClockSource#EXTERNAL}, the samples written to - * the sender are encoded and sent immediately, and hence the user is responsible to - * call write operation according to the sample rate. - *

                                                                  - * This mode is useful when the user gets samples from a realtime source with its own - * clock, e.g. from an audio device. Internal clock should not be used in this case - * because the audio device and the CPU might have slightly different clocks, and the - * difference will eventually lead to an underrun or an overrun. - *

                                                                • - *
                                                                + *
                                                                  + *
                                                                • If the user enabled internal clock ( {@link ClockSource#INTERNAL} ), the sender employs + * a CPU timer to block writes until it's time to encode the next bunch of samples + * according to the configured sample rate. This mode is useful when the user gets samples + * from a non-realtime source, e.g. from an audio file.
                                                                • + *
                                                                • If the user enabled external clock ( {@link ClockSource#EXTERNAL} ), the samples written + * to the sender are encoded and sent immediately, and hence the user is responsible to + * call write operation according to the sample rate. This mode is useful when the user + * gets samples from a realtime source with its own clock, e.g. from an audio device. + * Internal clock should not be used in this case because the audio device and the CPU + * might have slightly different clocks, and the difference will eventually lead to an + * underrun or an overrun.
                                                                • + *
                                                                + * * - *

                                                                Thread-safety

                                                                + *

                                                                Thread safety

                                                                *

                                                                - * Can be used concurrently + * Can be used concurrently. + * * *

                                                                Example

                                                                *
                                                                  * {@code
                                                                - * RocSenderConfig config = RocSenderConfig.builder()
                                                                - *             .frameSampleRate(SAMPLE_RATE)
                                                                - *             .frameChannels(ChannelSet.STEREO)
                                                                - *             .frameEncoding(FrameEncoding.PCM_FLOAT)
                                                                - *             .resamplerProfile(ResamplerProfile.DISABLE)
                                                                - *             .fecEncoding(FecEncoding.RS8M)
                                                                - *             .build();
                                                                + * RocSenderConfig senderConfig = RocSenderConfig.builder()
                                                                + *         .frameEncoding(
                                                                + *                 MediaEncoding.builder()
                                                                + *                         .rate(44100)
                                                                + *                         .format(Format.PCM_FLOAT32)
                                                                + *                         .channels(ChannelLayout.STEREO)
                                                                + *                         .build()
                                                                + *         )
                                                                + *         .fecEncoding(FecEncoding.RS8M)
                                                                + *         .clockSource(ClockSource.INTERNAL)
                                                                + *         .build();
                                                                  * try (
                                                                  *     RocContext context = new RocContext();
                                                                - *     RocSender sender = new RocSender(context, config);
                                                                + *     RocSender sender = new RocSender(context, senderConfig);
                                                                  * ) {
                                                                  *     sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001"));
                                                                  *     sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002"));
                                                                @@ -144,6 +157,7 @@
                                                                  * }
                                                                  * 
                                                                * + * * @see RocContext * @see RocSenderConfig * @see java.lang.AutoCloseable @@ -152,34 +166,22 @@ public class RocSender extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocSender.class.getName()); - /** - * Validate sender constructor parameters and open a new sender if validation is successful. - * - * @param context should point to an opened context. - * @param config should point to an initialized config. - * @return the native roc sender pointer. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the sender. - */ private static long construct(RocContext context, RocSenderConfig config) throws IllegalArgumentException, Exception { Check.notNull(context, "context"); Check.notNull(config, "config"); LOGGER.log(Level.FINE, "starting RocSender.open(), context ptr={0}, config={1}", new Object[]{toHex(context.getPtr()), config}); - long ptr = open(context.getPtr(), config); + long ptr = nativeOpen(context.getPtr(), config); LOGGER.log(Level.FINE, "finished RocSender.open(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); return ptr; } - /** - * Destruct native object - */ private static void destroy(long ptr, RocContext context) throws Exception { LOGGER.log(Level.FINE, "starting RocSender.close(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); - close(ptr); + nativeClose(ptr); LOGGER.log(Level.FINE, "finished RocSender.close(), context ptr={0}, ptr={1}", new Object[]{toHex(context.getPtr()), toHex(ptr)}); } @@ -188,117 +190,133 @@ private static void destroy(long ptr, RocContext context) throws Exception { * Open a new sender. * Allocates and initializes a new sender, and attaches it to the context. * - * @param context should point to an opened context. - * @param config should point to an initialized config. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the sender. + * @param context should point to an opened context. + * @param config should point to an initialized config. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws Exception if an error occurred when creating the sender. */ public RocSender(RocContext context, RocSenderConfig config) throws IllegalArgumentException, Exception { super(construct(context, config), context, ptr -> destroy(ptr, context)); } /** - * Set sender interface outgoing address. - *

                                                                - * Optional. Should be used only when connecting an interface to a remote endpoint. - *

                                                                - * If set, explicitly defines the IP address of the OS network interface from which to - * send the outgoing packets. If not set, the outgoing interface is selected automatically - * by the OS, depending on the remote endpoint address. - *

                                                                - * It is allowed to set outgoing address to `0.0.0.0` (for IPv4) or to `::` (for IPv6), - * to achieve the same behavior as if it wasn't set, i.e. to let the OS to select the - * outgoing interface automatically. + * Set sender interface configuration. *

                                                                - * By default, the outgoing address is not set. - *

                                                                - * Each slot's interface can have only one outgoing address. The function should be called - * before calling {@link RocSender#connect connect()} for this slot and interface. It should not be - * called when calling {@link RocSender#bind bind()} for the interface. + * Updates configuration of specified interface of specified slot. If + * called, the call should be done before calling roc_sender_bind() or + * {@link RocSender#connect()} for the same interface. *

                                                                * Automatically initializes slot with given index if it's used first time. *

                                                                - * **Parameters** + * If an error happens during configure, the whole slot is disabled and + * marked broken. The slot index remains reserved. The user is responsible + * for removing the slot using {@link RocSender#unlink()}, after which slot + * index can be reused. + * + * @param slot slot specifies the sender slot. + * @param iface iface specifies the sender interface. + * @param config settings for the specified interface. * - * @param slot specifies the sender slot - * @param iface specifies the sender interface - * @param ip should be IPv4 or IPv6 address - * @throws Exception if an error occurred + * @throws IllegalArgumentException if the arguments are invalid. */ - public void setOutgoingAddress(Slot slot, Interface iface, String ip) throws Exception { + public void configure(Slot slot, Interface iface, InterfaceConfig config) throws IllegalArgumentException { Check.notNull(slot, "slot"); Check.notNull(iface, "iface"); - Check.notEmpty(ip, "ip"); - - LOGGER.log(Level.FINE, "starting RocSender.setOutgoingAddress(), ptr={0}, slot={1}, iface={2}, ip={3}", - new Object[]{toHex(getPtr()), slot, iface, ip}); - setOutgoingAddress(getPtr(), slot.getValue(), iface.value, ip); - LOGGER.log(Level.FINE, "finished RocSender.setOutgoingAddress(), ptr={0}", new Object[]{toHex(getPtr())}); - } + Check.notNull(config, "config"); - public void bind(Endpoint endpoint) throws IllegalArgumentException, IOException { - throw new RuntimeException("bind not implemented"); + LOGGER.log(Level.FINE, "starting RocSender.configure(), ptr={0}, slot={1}, iface={2}, config={3}", + new Object[]{toHex(getPtr()), slot, iface, config}); + nativeConfigure(getPtr(), slot.getValue(), iface.value, config); + LOGGER.log(Level.FINE, "finished RocSender.configure(), ptr={0}", new Object[]{toHex(getPtr())}); } /** * Connect the sender interface to a remote receiver endpoint. *

                                                                - * Checks that the endpoint is valid and supported by the interface, allocates - * a new outgoing port, and connects it to the remote endpoint. + * Checks that the endpoint is valid and supported by the interface, + * allocates a new outgoing port, and connects it to the remote endpoint. *

                                                                - * Each slot's interface can be bound or connected only once. - * May be called multiple times for different slots or interfaces. + * Each slot's interface can be bound or connected only once. May be called + * multiple times for different slots or interfaces. *

                                                                * Automatically initializes slot with given index if it's used first time. + *

                                                                + * If an error happens during connect, the whole slot is disabled and marked + * broken. The slot index remains reserved. The user is responsible for + * removing the slot using {@link RocSender#unlink()}, after which slot + * index can be reused. * - * @param slot slot specifies the sender slot - * @param iface iface specifies the sender interface - * @param endpoint endpoint specifies the receiver endpoint - * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if was error during connect + * @param slot slot specifies the sender slot. + * @param iface iface specifies the sender interface. + * @param endpoint endpoint specifies the receiver endpoint. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws IOException if was error during connect. */ - public void connect(Slot slot, Interface iface, Endpoint endpoint) throws IllegalArgumentException, - IOException { + public void connect(Slot slot, Interface iface, Endpoint endpoint) throws IllegalArgumentException, IOException { Check.notNull(slot, "slot"); Check.notNull(iface, "iface"); Check.notNull(endpoint, "endpoint"); LOGGER.log(Level.FINE, "starting RocSender.connect(), ptr={0}, slot={1}, iface={2}, endpoint={3}", new Object[]{toHex(getPtr()), slot, iface, endpoint}); - connect(getPtr(), slot.getValue(), iface.value, endpoint); + nativeConnect(getPtr(), slot.getValue(), iface.value, endpoint); + LOGGER.log(Level.FINE, "finished RocSender.connect(), ptr={0}", new Object[]{toHex(getPtr())}); + } + + /** + * Delete sender slot. + *

                                                                + * Disconnects, unbinds, and removes all slot interfaces and removes the + * slot. All associated connections to remote nodes are properly terminated. + *

                                                                + * After unlinking the slot, it can be re-created again by re-using slot + * index. + * + * @param slot specifies the sender slot to delete. + * + * @throws IllegalArgumentException if the arguments are invalid. + */ + public void unlink(Slot slot) throws IllegalArgumentException { + Check.notNull(slot, "slot"); + + LOGGER.log(Level.FINE, "starting RocSender.unlink(), ptr={0}, slot={1}", new Object[]{toHex(getPtr()), slot}); + nativeUnlink(getPtr(), slot.getValue()); LOGGER.log(Level.FINE, "finished RocSender.connect(), ptr={0}", new Object[]{toHex(getPtr())}); } /** * Encode samples to packets and transmit them to the receiver. *

                                                                - * Encodes samples to packets and enqueues them for transmission by the network worker - * thread of the context. + * Encodes samples to packets and enqueues them for transmission by the + * network worker thread of the context. *

                                                                - * If {@link ClockSource#INTERNAL} is used, the function blocks until it's time to transmit the - * samples according to the configured sample rate. The function returns after encoding - * and enqueuing the packets, without waiting when the packets are actually transmitted. + * If {@link ClockSource#INTERNAL} is used, the function blocks until it's + * time to transmit the samples according to the configured sample rate. The + * function returns after encoding and enqueuing the packets, without + * waiting when the packets are actually transmitted. *

                                                                - * Until the sender is connected to at least one receiver, the stream is just dropped. - * If the sender is connected to multiple receivers, the stream is duplicated to - * each of them. + * Until the sender is connected to at least one receiver, the stream is + * just dropped. If the sender is connected to multiple receivers, the + * stream is duplicated to each of them. * - * @param samples array of samples to send. - * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if the sender if there are not enough resources. + * @param samples array of samples to send. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws IOException if the sender if there are not enough resources. */ public void write(float[] samples) throws IllegalArgumentException, IOException { Check.notNull(samples, "samples"); - writeFloats(getPtr(), samples); + nativeWriteFloats(getPtr(), samples); } - private static native long open(long contextPtr, RocSenderConfig config) throws IllegalArgumentException, Exception; - - private native void setOutgoingAddress(long senderPtr, int slot, int iface, String ip) throws Exception; - - private native void connect(long senderPtr, int slot, int iface, Endpoint endpoint) throws IOException; + private static native long nativeOpen(long contextPtr, RocSenderConfig config) throws IllegalArgumentException, Exception; + private static native void nativeClose(long senderPtr) throws IOException; - private native void writeFloats(long senderPtr, float[] samples) throws IOException; + private native void nativeConfigure(long senderPtr, int slot, int iface, InterfaceConfig config) throws IllegalArgumentException; + private native void nativeConnect(long senderPtr, int slot, int iface, Endpoint endpoint) throws IllegalArgumentException, IOException; + private native void nativeUnlink(long senderPtr, int slot) throws IllegalArgumentException; - private static native void close(long senderPtr) throws IOException; + private native void nativeWriteFloats(long senderPtr, float[] samples) throws IOException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java index 7b55d274..f7f3b344 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java @@ -1,3 +1,6 @@ +// Code generated by bindgen.py from roc-streaming/bindgen +// roc-toolkit git tag: v0.3.0, commit: 57b932b8 + package org.rocstreaming.roctoolkit; import java.time.Duration; @@ -17,111 +20,119 @@ public class RocSenderConfig { /** - * The rate of the samples in the frames returned to the user. - * Number of samples per channel per second. - * Should be set to a positive value. + * The encoding used in frames passed to sender. + *

                                                                + * Frame encoding defines sample format, channel layout, and sample rate in local frames + * created by user and passed to sender. Should be set (zero value is invalid). */ - private int frameSampleRate; + private MediaEncoding frameEncoding; /** - * The channel set in the frames returned to the user. - * Should be set to a non-null value. + * The encoding used for packets produced by sender. + *

                                                                + * Packet encoding defines sample format, channel layout, and sample rate in network + * packets. If packet encoding differs from frame encoding, conversion is performed + * automatically. + *

                                                                + * If zero, sender selects packet encoding automatically based on {@code frameEncoding}. + * This automatic selection matches only encodings that have exact same sample rate and + * channel layout, and hence don't require conversions. If you need conversions, you + * should set packet encoding explicitly. + *

                                                                + * If you want to force specific packet encoding, and built-in set of encodings is not + * enough, you can use {@link RocContext#registerEncoding()} to register custom encoding, + * set {@code packetEncoding} to registered identifier. If you use signaling protocol + * like RTSP, it's enough to register in just on sender; otherwise, you need to do the + * same on receiver as well. */ - private ChannelSet frameChannels; - - /** - * The sample encoding in the frames returned to the user. - * Should be set to a non-null value. - */ - private FrameEncoding frameEncoding; + private PacketEncoding packetEncoding; /** - * The rate of the samples in the packets generated by sender. - * Number of samples per channel per second. - * If zero or unset, default value is used. - * Should not be negative. + * The length of the packets produced by sender, in nanoseconds. + *

                                                                + * Number of nanoseconds encoded per packet. The samples written to the sender are + * buffered until the full packet is accumulated or the sender is flushed or closed. + * Larger number reduces packet overhead but also increases latency. If zero, default + * value is used. */ - private int packetSampleRate; + private Duration packetLength; /** - * The channel set in the packets generated by sender. - * If null or unset, default value is used. + * Enable packet interleaving. + *

                                                                + * If non-zero, the sender shuffles packets before sending them. This may increase + * robustness but also increases latency. */ - private ChannelSet packetChannels; + private int packetInterleaving; /** - * The sample encoding in the packets generated by sender. - * If null or unset, default value is used. + * FEC encoding to use. + *

                                                                + * If non-zero, the sender employs a FEC encoding to generate redundant packets which may + * be used on receiver to restore lost packets. This requires both sender and receiver to + * use two separate source and repair endpoints. */ - private PacketEncoding packetEncoding; + private FecEncoding fecEncoding; /** - * The length of the packets produced by sender, in nanoseconds. - * Number of nanoseconds encoded per packet. - * The samples written to the sender are buffered until the full - * packet is accumulated or the sender is flushed or closed. - * Larger number reduces packet overhead but also increases latency. - * If zero or unset, default value is used. - * Should not be negative. + * Number of source packets per FEC block. + *

                                                                + * Used if some FEC encoding is selected. + *

                                                                + * Sender divides stream into blocks of N source (media) packets, and adds M repair + * (redundancy) packets to each block, where N is {@code fecBlockSourcePackets} and M is + * {@code fecBlockRepairPackets}. + *

                                                                + * Larger number of source packets in block increases robustness (repair ratio), but also + * increases latency. + *

                                                                + * If zero, default value is used. */ - private Duration packetLength; + private int fecBlockSourcePackets; /** - * Enable packet interleaving. - * If non-zero, the sender shuffles packets before sending them. This - * may increase robustness but also increases latency. + * Number of repair packets per FEC block. + *

                                                                + * Used if some FEC encoding is selected. + *

                                                                + * Sender divides stream into blocks of N source (media) packets, and adds M repair + * (redundancy) packets to each block, where N is {@code fecBlockSourcePackets} and M is + * {@code fecBlockRepairPackets}. + *

                                                                + * Larger number of repair packets in block increases robustness (repair ratio), but also + * increases traffic. Number of repair packets usually should be 1/2 or 2/3 of the number + * of source packets. + *

                                                                + * If zero, default value is used. */ - private int packetInterleaving; + private int fecBlockRepairPackets; /** * Clock source to use. - * Defines whether write operation will be blocking or non-blocking. - * If null or unset, default value is used. + *

                                                                + * Defines whether write operation will be blocking or non-blocking. If zero, default + * value is used ( {@link ClockSource#EXTERNAL} ). */ private ClockSource clockSource; /** * Resampler backend to use. - * If null or unset, default value is used. + *

                                                                + * If zero, default value is used. */ private ResamplerBackend resamplerBackend; /** * Resampler profile to use. - * If null or unset, default value is used. - * If resampling is enabled, the sender employs resampler if the frame sample rate - * differs from the packet sample rate. + *

                                                                + * If non-zero, the sender employs resampler if the frame sample rate differs from the + * packet sample rate. */ private ResamplerProfile resamplerProfile; /** - * FEC encoding to use. - * If null or unset, default value is used. - * If FEC is enabled, the sender employs a FEC codec to generate redundant - * packets which may be used on receiver to restore lost packets. - * This requires both sender and receiver to use two separate source - * and repair ports. - */ - private FecEncoding fecEncoding; - - /** - * Number of source packets per FEC block. - * Used if some FEC encoding is selected. - * Larger number increases robustness but also increases latency. - * If zero or unset, default value is used. - * Should not be negative. - */ - private int fecBlockSourcePackets; - - /** - * Number of repair packets per FEC block. - * Used if some FEC encoding is selected. - * Larger number increases robustness but also increases traffic. - * If zero or unset, default value is used. - * Should not be negative. + * Construct lombok builder for {@link RocSenderConfig}. */ - private int fecBlockRepairPackets; - public static RocSenderConfig.Builder builder() { return new RocSenderConfigValidator(); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java index dcfec5ed..ee259983 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java @@ -7,10 +7,7 @@ class RocSenderConfigValidator extends RocSenderConfig.Builder { @Override public RocSenderConfig build() { RocSenderConfig config = super.build(); - Check.notNegative(config.getFrameSampleRate(), "frameSampleRate"); - Check.notNull(config.getFrameChannels(), "frameChannels"); Check.notNull(config.getFrameEncoding(), "frameEncoding"); - Check.notNegative(config.getPacketSampleRate(), "packetSampleRate"); Check.notNegative(config.getPacketLength(), "packetLength"); Check.notNegative(config.getFecBlockSourcePackets(), "fecBlockSourcePackets"); Check.notNegative(config.getFecBlockRepairPackets(), "fecBlockRepairPackets"); diff --git a/src/main/java/org/rocstreaming/roctoolkit/Slot.java b/src/main/java/org/rocstreaming/roctoolkit/Slot.java index 11545b32..11fd47fd 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Slot.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Slot.java @@ -39,6 +39,6 @@ public int getValue() { @Override public String toString() { - return "Slot(" + value + ')'; + return "Slot(" + value + ")"; } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java index cea6e7bc..a0e0cc47 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java @@ -1,17 +1,29 @@ package org.rocstreaming.roctoolkit; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; import java.util.logging.LogManager; public class BaseTest { + static private Level originalLogLevel; + @BeforeAll static void configureLogger() throws IOException { try (InputStream is = RocReceiverTest.class.getClassLoader().getResourceAsStream("logging.properties")) { LogManager.getLogManager().readConfiguration(is); } + + originalLogLevel = RocLogger.LOGGER.getLevel(); + RocLogger.LOGGER.setLevel(Level.OFF); + } + + @AfterAll + static public void restoreLogger() { + RocLogger.LOGGER.setLevel(originalLogLevel); } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java b/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java new file mode 100644 index 00000000..555fc7f7 --- /dev/null +++ b/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java @@ -0,0 +1,41 @@ +package org.rocstreaming.roctoolkit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Duration; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class MediaEncodingTest { + + private static MediaEncoding.Builder validBuilder() { + return MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO); + } + + @Test + public void testValidEncoding() { + assertDoesNotThrow(() -> validBuilder().build()); + } + + private static Stream invalidEncodingArguments() { + return Stream.of( + Arguments.of("rate must not be negative", validBuilder().rate(-1)), + Arguments.of("format must not be null", validBuilder().format(null)), + Arguments.of("channels must not be null", validBuilder().channels(null)) + ); + } + + @ParameterizedTest() + @MethodSource("invalidEncodingArguments") + public void testInvalidEncoding(String error, MediaEncoding.Builder builder) { + Exception e = assertThrows(IllegalArgumentException.class, builder::build); + assertEquals(error, e.getMessage()); + } +} diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java index 6509dbf4..4f97a99d 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java @@ -1,25 +1,34 @@ package org.rocstreaming.roctoolkit; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; class RocContextConfigTest { - private static Stream testInvalidConfigArguments() { + private static RocContextConfig.Builder validBuilder() { + return RocContextConfig.builder(); + } + + @Test + public void testValidConfig() { + assertDoesNotThrow(() -> validBuilder().build()); + } + + private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("maxFrameSize must not be negative", RocContextConfig.builder().maxFrameSize(-1)), - Arguments.of("maxPacketSize must not be negative", RocContextConfig.builder().maxPacketSize(-1)) + Arguments.of("maxFrameSize must not be negative", validBuilder().maxFrameSize(-1)), + Arguments.of("maxPacketSize must not be negative", validBuilder().maxPacketSize(-1)) ); } @ParameterizedTest - @MethodSource("testInvalidConfigArguments") + @MethodSource("invalidConfigArguments") public void testInvalidConfig(String error, RocContextConfig.Builder builder) { Exception e = assertThrows(IllegalArgumentException.class, builder::build); assertEquals(error, e.getMessage()); diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java index 9060f4f9..2ef91444 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java @@ -1,6 +1,11 @@ package org.rocstreaming.roctoolkit; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -46,4 +51,43 @@ public void testCloseWithAttachedReceiver() { } }); } + + private static MediaEncoding validEncoding() { + return MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build(); + } + + @Test + public void testRegisterEncoding() { + assertDoesNotThrow(() -> { + try (RocContext context = new RocContext()) { + context.registerEncoding(100, validEncoding()); + } + }); + } + + private static Stream invalidRegisterEncodingArguments() { + return Stream.of( + Arguments.of("encodingId must be in range [1; 127]", -1, validEncoding()), + Arguments.of("encodingId must be in range [1; 127]", 0, validEncoding()), + Arguments.of("encodingId must be in range [1; 127]", 128, validEncoding()), + Arguments.of("encoding must not be null", 100, null), + // encoding id already registered + Arguments.of("Error registering encoding", PacketEncoding.AVP_L16_MONO.getValue(), validEncoding()) + ); + } + + @ParameterizedTest() + @MethodSource("invalidRegisterEncodingArguments") + public void testInvalidEncoding(String error, int encodingId, MediaEncoding encoding) { + Exception e = assertThrows(IllegalArgumentException.class, () -> { + try (RocContext context = new RocContext()) { + context.registerEncoding(encodingId, encoding); + } + }); + assertEquals(error, e.getMessage()); + } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java index 091b3a51..5bcc3d17 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java @@ -18,7 +18,16 @@ public class RocLoggerTest extends BaseTest { - private static Stream testLogLevelProvider() { + private Handler wrapHandler(Consumer consumer) { + return new MemoryHandler(new ConsoleHandler(), 1000, Level.ALL) { + @Override + public synchronized void publish(LogRecord record) { + consumer.accept(record); + } + }; + } + + private static Stream allLogLevels() { return Stream.of( Arguments.of(Level.OFF, false, false), Arguments.of(Level.SEVERE, true, false), @@ -29,68 +38,68 @@ private static Stream testLogLevelProvider() { } @ParameterizedTest - @MethodSource("testLogLevelProvider") + @MethodSource("allLogLevels") public void testLogLevel(Level level, boolean expectError, boolean expectInfo) { Level originalLevel = RocLogger.LOGGER.getLevel(); - Map msgCount = new ConcurrentHashMap<>(); - Handler handler = wrapHandler(record -> msgCount.compute(record.getLevel(), (k, v) -> v == null ? 1 : v + 1)); + Handler handler = wrapHandler( + record -> msgCount.compute(record.getLevel(), (k, v) -> v == null ? 1 : v + 1)); RocLogger.LOGGER.addHandler(handler); + RocLogger.LOGGER.setLevel(Level.FINE); - assertDoesNotThrow(() -> { - RocLogger.LOGGER.setLevel(level); - try { - // trigger error logs - new Endpoint("invalid"); - } catch (Exception ignored) { - } - // trigger info logs - //noinspection EmptyTryBlock - try (RocContext ignored = new RocContext()) { - } - }); - await().atMost(Duration.FIVE_MINUTES) - .untilAsserted(() -> { - assertEquals(expectError, msgCount.containsKey(Level.SEVERE)); - assertEquals(expectInfo, msgCount.containsKey(Level.INFO)); - }); - RocLogger.LOGGER.removeHandler(handler); - RocLogger.LOGGER.setLevel(originalLevel); + try { + assertDoesNotThrow(() -> { + RocLogger.LOGGER.setLevel(level); + try { + // trigger error logs + new Endpoint("invalid"); + } catch (Exception ignored) { + } + // trigger info logs + //noinspection EmptyTryBlock + try (RocContext ignored = new RocContext()) { + } + }); + await().atMost(Duration.FIVE_MINUTES) + .untilAsserted(() -> { + assertEquals(expectError, msgCount.containsKey(Level.SEVERE)); + assertEquals(expectInfo, msgCount.containsKey(Level.INFO)); + }); + } finally { + RocLogger.LOGGER.removeHandler(handler); + RocLogger.LOGGER.setLevel(originalLevel); + } } @Test public void testSetHandler() throws Exception { + Level originalLevel = RocLogger.LOGGER.getLevel(); AtomicBoolean hasLogOpen = new AtomicBoolean(); AtomicBoolean hasLogClose = new AtomicBoolean(); Handler handler = wrapHandler(record -> { - if (!record.getSourceClassName().equals("libroc")) { + if (!record.getSourceClassName().startsWith("roc_")) { return; } - if (record.getMessage().startsWith("roc_context_open")) { + if (record.getMessage().contains("roc_context_open")) { hasLogOpen.set(true); } - if (record.getMessage().startsWith("roc_context_close")) { + if (record.getMessage().contains("roc_context_close")) { hasLogClose.set(true); } }); RocLogger.LOGGER.addHandler(handler); + RocLogger.LOGGER.setLevel(Level.FINE); - //noinspection EmptyTryBlock - try (RocContext ignored = new RocContext()) { - } - - await().atMost(Duration.FIVE_MINUTES) - .until(() -> hasLogOpen.get() && hasLogClose.get()); - - RocLogger.LOGGER.removeHandler(handler); - } - - private Handler wrapHandler(Consumer consumer) { - return new MemoryHandler(new ConsoleHandler(), 1000, Level.ALL) { - @Override - public synchronized void publish(LogRecord record) { - consumer.accept(record); + try { + //noinspection EmptyTryBlock + try (RocContext ignored = new RocContext()) { } - }; + + await().atMost(Duration.FIVE_MINUTES) + .until(() -> hasLogOpen.get() && hasLogClose.get()); + } finally { + RocLogger.LOGGER.removeHandler(handler); + RocLogger.LOGGER.setLevel(originalLevel); + } } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java index 177547df..40ebeb5f 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java @@ -1,5 +1,6 @@ package org.rocstreaming.roctoolkit; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -13,25 +14,30 @@ class RocReceiverConfigTest { private static RocReceiverConfig.Builder validBuilder() { return RocReceiverConfig.builder() - .frameSampleRate(44100) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT); + .frameEncoding( + MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ); } - private static Stream testInvalidConfigArguments() { + @Test + public void testValidConfig() { + assertDoesNotThrow(() -> validBuilder().build()); + } + + private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("frameSampleRate must not be negative", validBuilder().frameSampleRate(-1)), - Arguments.of("frameChannels must not be null", validBuilder().frameChannels(null)), Arguments.of("frameEncoding must not be null", validBuilder().frameEncoding(null)), Arguments.of("targetLatency must not be negative", validBuilder().targetLatency(Duration.ofNanos(-1))), - Arguments.of("maxLatencyOverrun must not be negative", validBuilder().maxLatencyOverrun(Duration.ofNanos(-1))), - Arguments.of("maxLatencyUnderrun must not be negative", validBuilder().maxLatencyUnderrun(Duration.ofNanos(-1))), - Arguments.of("breakageDetectionWindow must not be negative", validBuilder().breakageDetectionWindow(Duration.ofNanos(-1))) + Arguments.of("latencyTolerance must not be negative", validBuilder().latencyTolerance(Duration.ofNanos(-1))) ); } @ParameterizedTest() - @MethodSource("testInvalidConfigArguments") + @MethodSource("invalidConfigArguments") public void testInvalidConfig(String error, RocReceiverConfig.Builder builder) { Exception e = assertThrows(IllegalArgumentException.class, builder::build); assertEquals(error, e.getMessage()); diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java index 0f35ff24..f56d3374 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java @@ -16,9 +16,13 @@ public class RocReceiverTest extends BaseTest { private static final int SAMPLE_RATE = 44100; public static final RocReceiverConfig CONFIG = RocReceiverConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .build(); private RocContext context; @@ -44,18 +48,20 @@ public void testCreationAndDeinitialization() { @Test public void testCreationAndDeinitializationWithFullConfig() { RocReceiverConfig config = RocReceiverConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .clockSource(ClockSource.INTERNAL) .resamplerBackend(ResamplerBackend.BUILTIN) .resamplerProfile(ResamplerProfile.HIGH) .targetLatency(Duration.ofNanos(1000)) - .maxLatencyOverrun(Duration.ofNanos(500)) - .maxLatencyUnderrun(Duration.ofNanos(500)) + .latencyTolerance(Duration.ofNanos(500)) .noPlaybackTimeout(Duration.ofNanos(2000)) - .brokenPlaybackTimeout(Duration.ofNanos(2000)) - .breakageDetectionWindow(Duration.ofNanos(2000)) + .choppyPlaybackTimeout(Duration.ofNanos(2000)) .build(); assertDoesNotThrow(() -> { //noinspection EmptyTryBlock @@ -64,7 +70,7 @@ public void testCreationAndDeinitializationWithFullConfig() { }); } - private static Stream testInvalidCreationArguments() throws Exception { + private static Stream invalidCreationArguments() throws Exception { return Stream.of( Arguments.of( "context must not be null", @@ -80,13 +86,38 @@ private static Stream testInvalidCreationArguments() throws Exception } @ParameterizedTest - @MethodSource("testInvalidCreationArguments") + @MethodSource("invalidCreationArguments") public void testInvalidCreation(String errorMessage, Class exceptionClass, RocContext context, RocReceiverConfig config) { Exception exception = assertThrows(exceptionClass, () -> new RocReceiver(context, config)); assertEquals(errorMessage, exception.getMessage()); } - private static Stream testInvalidSetMulticastGroupArguments() { + @Test + public void testConfigureBeforeBind() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build(); + assertDoesNotThrow(() -> { + receiver.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig); + receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://224.0.0.1:0")); + }); + } + } + + @Test + public void testConfigureAfterBind() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build(); + receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://224.0.0.1:0")); + Exception exception = assertThrows(Exception.class, () -> receiver.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); + assertEquals("Error configuring receiver", exception.getMessage()); + } + } + + private static Stream invalidConfigureArguments() { return Stream.of( Arguments.of( "slot must not be null", @@ -97,37 +128,25 @@ private static Stream testInvalidSetMulticastGroupArguments() { "iface must not be null", Slot.DEFAULT, null, - "0.0.0.0"), - Arguments.of( - "ip must not be empty", - Slot.DEFAULT, - Interface.AUDIO_SOURCE, - null) + "0.0.0.0") ); } @ParameterizedTest - @MethodSource("testInvalidSetMulticastGroupArguments") - public void testInvalidSetMulticastGroup(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { + @MethodSource("invalidConfigureArguments") + public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .multicastGroup(ip) + .build(); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> receiver.setMulticastGroup(slot, iface, ip) + () -> receiver.configure(slot, iface, ifaceConfig) ); assertEquals(errorMessage, exception.getMessage()); } } - @Test - public void testSetMulticastGroup() throws Exception { - try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { - assertDoesNotThrow(() -> { - receiver.setMulticastGroup(Slot.DEFAULT, Interface.AUDIO_SOURCE, "0.0.0.0"); - receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://224.0.0.1:0")); - }); - } - } - @Test public void testBind() throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { @@ -143,7 +162,7 @@ public void testBindEphemeralPort() throws Exception { Endpoint repairEndpoint = new Endpoint("rs8m://0.0.0.0:0"); receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, sourceEndpoint); receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, repairEndpoint); - // + int sourcePort = sourceEndpoint.getPort(); int repairPort = repairEndpoint.getPort(); assertNotEquals(0, sourcePort); @@ -153,7 +172,7 @@ public void testBindEphemeralPort() throws Exception { } } - private static Stream testInvalidBindArguments() { + private static Stream invalidBindArguments() { return Stream.of( Arguments.of( "slot must not be null", @@ -174,7 +193,7 @@ private static Stream testInvalidBindArguments() { } @ParameterizedTest - @MethodSource("testInvalidBindArguments") + @MethodSource("invalidBindArguments") public void testInvalidBind(String errorMessage, Slot slot, Interface iface, Endpoint endpoint) throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { IllegalArgumentException exception = assertThrows( @@ -186,17 +205,37 @@ public void testInvalidBind(String errorMessage, Slot slot, Interface iface, End } @Test - public void testInvalidRead() throws Exception { + public void testUnlink() throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { - receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); - receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://127.0.0.1:0")); - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> receiver.read(null)); - assertEquals("samples must not be null", exception.getMessage()); + assertDoesNotThrow(() -> { + Slot slot1 = new Slot(1); + Slot slot2 = new Slot(2); + receiver.bind(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + receiver.bind(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + receiver.unlink(slot1); + receiver.unlink(slot2); + }); } } @Test - public void testReadZeroizedArray() throws Exception { + public void testInvalidUnlink() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + assertDoesNotThrow(() -> { + Slot slot1 = new Slot(1); + Slot slot2 = new Slot(2); + receiver.bind(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + receiver.bind(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + receiver.unlink(slot1); + assertThrows(IllegalArgumentException.class, () -> receiver.unlink(slot1)); + receiver.unlink(slot2); + assertThrows(IllegalArgumentException.class, () -> receiver.unlink(slot2)); + }); + } + } + + @Test + public void testRead() throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:0")); receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:0")); @@ -205,4 +244,14 @@ public void testReadZeroizedArray() throws Exception { assertArrayEquals(new float[]{0.0f, 0.0f}, samples); } } + + @Test + public void testInvalidRead() throws Exception { + try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { + receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); + receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://127.0.0.1:0")); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> receiver.read(null)); + assertEquals("samples must not be null", exception.getMessage()); + } + } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java index cfa9a3a5..75a7afe9 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java @@ -1,5 +1,6 @@ package org.rocstreaming.roctoolkit; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -7,24 +8,29 @@ import java.time.Duration; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; class RocSenderConfigTest { private static RocSenderConfig.Builder validBuilder() { return RocSenderConfig.builder() - .frameSampleRate(44100) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT); + .frameEncoding( + MediaEncoding.builder() + .rate(44100) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ); } - private static Stream testInvalidConfigArguments() { + @Test + public void testValidConfig() { + assertDoesNotThrow(() -> validBuilder().build()); + } + + private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("frameSampleRate must not be negative", validBuilder().frameSampleRate(-1)), - Arguments.of("frameChannels must not be null", validBuilder().frameChannels(null)), Arguments.of("frameEncoding must not be null", validBuilder().frameEncoding(null)), - Arguments.of("packetSampleRate must not be negative", validBuilder().packetSampleRate(-1)), Arguments.of("packetLength must not be negative", validBuilder().packetLength(Duration.ofNanos(-1))), Arguments.of("fecBlockSourcePackets must not be negative", validBuilder().fecBlockSourcePackets(-1)), Arguments.of("fecBlockRepairPackets must not be negative", validBuilder().fecBlockRepairPackets(-1)) @@ -32,7 +38,7 @@ private static Stream testInvalidConfigArguments() { } @ParameterizedTest() - @MethodSource("testInvalidConfigArguments") + @MethodSource("invalidConfigArguments") public void testInvalidConfig(String error, RocSenderConfig.Builder builder) { Exception e = assertThrows(IllegalArgumentException.class, builder::build); assertEquals(error, e.getMessage()); diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java index b9e34bd1..6627508e 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java @@ -19,9 +19,13 @@ public class RocSenderTest extends BaseTest { private static final int SAMPLE_RATE = 44100; public static final RocSenderConfig CONFIG = RocSenderConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .build(); private final int SINE_RATE = 440; private final int SINE_SAMPLES = (SAMPLE_RATE * 5); @@ -67,20 +71,22 @@ public void testCreationAndDeinitialization() { @Test public void testCreationAndDeinitializationWithFullConfig() { RocSenderConfig config = RocSenderConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) - .packetSampleRate(44100) - .packetChannels(ChannelSet.STEREO) - .packetEncoding(PacketEncoding.AVP_L16) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) + .packetEncoding(PacketEncoding.AVP_L16_STEREO) .packetLength(Duration.ofNanos(2000)) .packetInterleaving(1) - .clockSource(ClockSource.INTERNAL) - .resamplerBackend(ResamplerBackend.BUILTIN) - .resamplerProfile(ResamplerProfile.HIGH) .fecEncoding(FecEncoding.RS8M) .fecBlockSourcePackets(10) .fecBlockRepairPackets(10) + .clockSource(ClockSource.INTERNAL) + .resamplerBackend(ResamplerBackend.BUILTIN) + .resamplerProfile(ResamplerProfile.HIGH) .build(); assertDoesNotThrow(() -> { //noinspection EmptyTryBlock @@ -89,7 +95,7 @@ public void testCreationAndDeinitializationWithFullConfig() { }); } - private static Stream testInvalidCreationArguments() throws Exception { + private static Stream invalidCreationArguments() throws Exception { return Stream.of( Arguments.of( "context must not be null", @@ -105,13 +111,38 @@ private static Stream testInvalidCreationArguments() throws Exception } @ParameterizedTest - @MethodSource("testInvalidCreationArguments") + @MethodSource("invalidCreationArguments") public void testInvalidCreation(String errorMessage, Class exceptionClass, RocContext context, RocSenderConfig config) { Exception exception = assertThrows(exceptionClass, () -> new RocSender(context, config)); assertEquals(errorMessage, exception.getMessage()); } - private static Stream testInvalidSetOutgoingAddressArguments() { + @Test + void testConfigureBeforeConnect() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .outgoingAddress("127.0.0.1") + .build(); + assertDoesNotThrow(() -> sender.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); + sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); + sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); + } + } + + @Test + void testConfigureAfterConnect() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .outgoingAddress("127.0.0.1") + .build(); + sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); + sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); + Exception exception = assertThrows(Exception.class, () -> sender.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); + assertEquals("Error configuring sender", exception.getMessage()); + } + } + + private static Stream invalidConfigureArguments() { return Stream.of( Arguments.of( "slot must not be null", @@ -122,76 +153,25 @@ private static Stream testInvalidSetOutgoingAddressArguments() { "iface must not be null", Slot.DEFAULT, null, - "0.0.0.0"), - Arguments.of( - "ip must not be empty", - Slot.DEFAULT, - Interface.AUDIO_SOURCE, - null) + "0.0.0.0") ); } @ParameterizedTest - @MethodSource("testInvalidSetOutgoingAddressArguments") - public void testInvalidSetOutgoingAddress(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { + @MethodSource("invalidConfigureArguments") + public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { try (RocSender receiver = new RocSender(context, CONFIG)) { + InterfaceConfig ifaceConfig = InterfaceConfig.builder() + .outgoingAddress(ip) + .build(); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> receiver.setOutgoingAddress(slot, iface, ip) + () -> receiver.configure(slot, iface, ifaceConfig) ); assertEquals(errorMessage, exception.getMessage()); } } - @Test - void testSetOutgoingAddress() throws Exception { - try (RocSender sender = new RocSender(context, CONFIG)) { - assertDoesNotThrow(() -> sender.setOutgoingAddress(Slot.DEFAULT, Interface.AUDIO_SOURCE, "127.0.0.1")); - sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); - sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); - } - } - - @Test - void testSetOutgoingAddressAfterConnect() throws Exception { - try (RocSender sender = new RocSender(context, CONFIG)) { - sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); - sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); - Exception exception = assertThrows(Exception.class, () -> sender.setOutgoingAddress(Slot.DEFAULT, Interface.AUDIO_SOURCE, "127.0.0.1")); - assertEquals("Can't set outgoing address", exception.getMessage()); - } - } - - @Disabled("bind not implemented in roc 0.2.x yet") - @Test - public void testBind() throws Exception { - try (RocSender sender = new RocSender(context, CONFIG)) { - assertDoesNotThrow(() -> sender.bind(new Endpoint("rtp+rs8m://127.0.0.1:0"))); - } - } - - @Disabled("bind not implemented in roc 0.2.x yet") - @Test - public void testBindEphemeralPort() throws Exception { - try (RocSender sender = new RocSender(context, CONFIG)) { - Endpoint senderEndpoint = new Endpoint("rtp+rs8m://127.0.0.1:0"); - sender.bind(senderEndpoint); - assertNotEquals(0, senderEndpoint.getPort()); - } - } - - @Disabled("bind not implemented in roc 0.2.x yet") - @Test - public void testInvalidBind() throws Exception { - try (RocSender sender = new RocSender(context, CONFIG)) { - assertThrows(IllegalArgumentException.class, () -> sender.bind(null)); - sender.bind(new Endpoint("rtp+rs8m://127.0.0.1:0")); - assertThrows(IOException.class, () -> { - sender.bind(new Endpoint("rtp+rs8m://127.0.0.1:0")); - }); - } - } - @Test public void testConnect() throws Exception { try (RocSender sender = new RocSender(context, CONFIG)) { @@ -202,7 +182,7 @@ public void testConnect() throws Exception { } } - private static Stream testInvalidConnectArguments() { + private static Stream invalidConnectArguments() { return Stream.of( Arguments.of( "slot must not be null", @@ -223,7 +203,7 @@ private static Stream testInvalidConnectArguments() { } @ParameterizedTest - @MethodSource("testInvalidConnectArguments") + @MethodSource("invalidConnectArguments") public void testInvalidConnect(String errorMessage, Slot slot, Interface iface, Endpoint endpoint) throws Exception { try (RocSender sender = new RocSender(context, CONFIG)) { IllegalArgumentException exception = assertThrows( @@ -234,6 +214,36 @@ public void testInvalidConnect(String errorMessage, Slot slot, Interface iface, } } + @Test + public void testUnlink() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + assertDoesNotThrow(() -> { + Slot slot1 = new Slot(1); + Slot slot2 = new Slot(2); + sender.connect(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10001")); + sender.connect(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10002")); + sender.unlink(slot1); + sender.unlink(slot2); + }); + } + } + + @Test + public void testInvalidUnlink() throws Exception { + try (RocSender sender = new RocSender(context, CONFIG)) { + assertDoesNotThrow(() -> { + Slot slot1 = new Slot(1); + Slot slot2 = new Slot(2); + sender.connect(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10001")); + sender.connect(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10002")); + sender.unlink(slot1); + assertThrows(IllegalArgumentException.class, () -> sender.unlink(slot1)); + sender.unlink(slot2); + assertThrows(IllegalArgumentException.class, () -> sender.unlink(slot2)); + }); + } + } + @Test public void testWrite() throws Exception { try (RocSender sender = new RocSender(context, CONFIG)) { @@ -248,9 +258,6 @@ public void testWrite() throws Exception { @Test public void testInvalidWrite() throws Exception { try (RocSender sender = new RocSender(context, CONFIG)) { - // bind not implemented in roc 0.2.x yet - // assertThrows(IOException.class, () -> sender.write(samples)); // write before bind - // sender.bind(); sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); assertThrows(IllegalArgumentException.class, () -> sender.write(null)); diff --git a/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java b/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java index b9281eb5..d1f04484 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java @@ -22,16 +22,24 @@ public class RocSenderReceiverTest extends BaseTest { .build(); private static final RocSenderConfig SENDER_CONFIG = RocSenderConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .clockSource(ClockSource.INTERNAL) .build(); private static final RocReceiverConfig RECEIVER_CONFIG = RocReceiverConfig.builder() - .frameSampleRate(SAMPLE_RATE) - .frameChannels(ChannelSet.STEREO) - .frameEncoding(FrameEncoding.PCM_FLOAT) + .frameEncoding( + MediaEncoding.builder() + .rate(SAMPLE_RATE) + .format(Format.PCM_FLOAT32) + .channels(ChannelLayout.STEREO) + .build() + ) .clockSource(ClockSource.INTERNAL) .build(); From 2da43272532065c8e141da4d51fd54305f772312 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 1 May 2025 18:28:47 +0900 Subject: [PATCH 2/7] Switch CI to roc-toolkit 0.3.0 --- .github/workflows/build.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 39119368..db4b2389 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -58,7 +58,7 @@ jobs: env: # TODO: set to master - ROC_REVISION: v0.2.6 + ROC_REVISION: v0.3.0 steps: - name: Checkout @@ -111,7 +111,7 @@ jobs: env: # TODO: set to master - ROC_REVISION: v0.2.6 + ROC_REVISION: v0.3.0 JAVA_VERSION: ${{ matrix.jdk }} SDK_LEVEL: ${{ matrix.sdk }} API_LEVEL: ${{ matrix.api }} @@ -159,7 +159,7 @@ jobs: env: # TODO: set to master - ROC_REVISION: v0.2.6 + ROC_REVISION: v0.3.0 SDK_LEVEL: ${{ matrix.sdk }} API_LEVEL: ${{ matrix.api }} NDK_VERSION: ${{ matrix.ndk }} From 8ec1ca683820c7fc05a1efb4c92e6267a35940f0 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 1 May 2025 22:52:36 +0900 Subject: [PATCH 3/7] Refine logging --- .../org_rocstreaming_roctoolkit_RocLogger.h | 8 +- roc_jni/src/main/impl/logger.c | 4 +- .../rocstreaming/roctoolkit/RocContext.java | 46 ++++++++--- .../rocstreaming/roctoolkit/RocLogLevel.java | 2 +- .../rocstreaming/roctoolkit/RocLogger.java | 40 +++++---- .../rocstreaming/roctoolkit/RocReceiver.java | 82 ++++++++++++++----- .../rocstreaming/roctoolkit/RocSender.java | 82 ++++++++++++++----- .../roctoolkit/RocLoggerTest.java | 4 +- 8 files changed, 183 insertions(+), 85 deletions(-) diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocLogger.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocLogger.h index a8b95974..6a2c2a68 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocLogger.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_RocLogger.h @@ -9,18 +9,18 @@ extern "C" { #endif /* * Class: org_rocstreaming_roctoolkit_RocLogger - * Method: setLevel + * Method: nativeSetLevel * Signature: (Lorg/rocstreaming/roctoolkit/RocLogLevel;)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_setLevel +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetLevel (JNIEnv *, jclass, jobject); /* * Class: org_rocstreaming_roctoolkit_RocLogger - * Method: setHandler + * Method: nativeSetHandler * Signature: (Lorg/rocstreaming/roctoolkit/RocLogHandler;)V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_setHandler +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetHandler (JNIEnv *, jclass, jobject); #ifdef __cplusplus diff --git a/roc_jni/src/main/impl/logger.c b/roc_jni/src/main/impl/logger.c index d640a91d..e8bc8d83 100644 --- a/roc_jni/src/main/impl/logger.c +++ b/roc_jni/src/main/impl/logger.c @@ -148,7 +148,7 @@ static void logger_handler(const roc_log_message* message, void* argument) { pthread_mutex_unlock(&logMutex); } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_setLevel( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetLevel( JNIEnv* env, jclass clazz, jobject jlevel) { roc_log_level level = (roc_log_level) 0; int success = 0; @@ -172,7 +172,7 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_setLevel( } } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_setHandler( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetHandler( JNIEnv* env, jclass clazz, jobject jhandler) { jclass handlerClass = NULL; jmethodID handlerMethod = NULL; diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java index e4f3eae3..962f39e2 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java @@ -39,16 +39,32 @@ public class RocContext extends NativeObject { private static long construct(RocContextConfig config) throws IllegalArgumentException, Exception { Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocContext.open(), config={0}", new Object[]{config}); - long ptr = nativeOpen(config); - LOGGER.log(Level.FINE, "finished RocContext.open(), ptr={0}", new Object[]{toHex(ptr)}); - return ptr; + + try { + LOGGER.log(Level.FINE, "entering RocContext(), config={0}", config); + + long ptr = nativeOpen(config); + + LOGGER.log(Level.FINE, "leaving RocContext(), ptr={0}", toHex(ptr)); + return ptr; + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocContext(), exception={0}", exc); + throw exc; + } } private static void destroy(long ptr) throws Exception { - LOGGER.log(Level.FINE, "starting RocContext.close(), ptr={0}", new Object[]{toHex(ptr)}); - nativeClose(ptr); - LOGGER.log(Level.FINE, "finished RocContext.close(), ptr={0}", new Object[]{toHex(ptr)}); + try { + LOGGER.log(Level.FINE, "entering RocContext.close(), ptr={0}", toHex(ptr)); + + nativeClose(ptr); + + LOGGER.log(Level.FINE, "leaving RocContext.close(), ptr={0}", toHex(ptr)); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocContext.close(), ptr={0}, exception={1}", + new Object[]{toHex(ptr), exc}); + throw exc; + } } /** @@ -103,10 +119,18 @@ public void registerEncoding(int encodingId, MediaEncoding encoding) throws Ille Check.inRange(encodingId, 1, 127, "encodingId"); Check.notNull(encoding, "encoding"); - LOGGER.log(Level.FINE, "starting RocContext.registerEncoding(), ptr={0}, encodingId={1}, encoding={2}", - new Object[]{toHex(getPtr()), encodingId, encoding}); - nativeRegisterEncoding(getPtr(), encodingId, encoding); - LOGGER.log(Level.FINE, "finished RocContext.registerEncoding(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocContext.registerEncoding(), ptr={0}, encodingId={1}, encoding={2}", + new Object[]{toHex(getPtr()), encodingId, encoding}); + + nativeRegisterEncoding(getPtr(), encodingId, encoding); + + LOGGER.log(Level.FINE, "leaving RocContext.registerEncoding(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocContext.registerEncoding(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } private static native long nativeOpen(RocContextConfig config) throws IllegalArgumentException, Exception; diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocLogLevel.java b/src/main/java/org/rocstreaming/roctoolkit/RocLogLevel.java index b475311d..d1221aa6 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocLogLevel.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocLogLevel.java @@ -5,7 +5,7 @@ * * @see RocLogger#setLevel(RocLogLevel) */ -public enum RocLogLevel { +enum RocLogLevel { /** * No messages. diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java b/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java index c62a4641..f80ac9aa 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java @@ -7,7 +7,7 @@ class RocLogger { static final Logger LOGGER = Logger.getLogger(RocLogger.class.getName()); - private static final RocLogHandler DEFAULT_HANDLER = (level, component, message) -> { + private static final RocLogHandler HANDLER = (level, component, message) -> { Level julLevel = mapLogLevel(level); if (LOGGER.isLoggable(julLevel)) { LOGGER.logp(julLevel, component, "", message); @@ -16,39 +16,37 @@ class RocLogger { static { RocLibrary.loadLibrary(); - setLevel(RocLogLevel.DEBUG); // set debug level and rely on jul to filter messages. - setHandler(DEFAULT_HANDLER); - // Jvm could be terminated before roclib, so we need to clear callback to avoid crash - Runtime.getRuntime().addShutdownHook(new Thread(() -> setHandler(null))); + // Set debug level and rely on jul to filter messages. + nativeSetLevel(RocLogLevel.DEBUG); + // Register callback for libroc log messages. + nativeSetHandler(HANDLER); + // JVM could be terminated before libroc, so we need to unregister Java callback + // from libroc before shutting down, to avoid crash. + Runtime.getRuntime().addShutdownHook(new Thread(() -> nativeSetHandler(null))); + } + + private RocLogger() { } private static Level mapLogLevel(RocLogLevel level) { switch (level) { case NONE: return Level.OFF; + case ERROR: + return Level.SEVERE; case INFO: return Level.INFO; case DEBUG: return Level.FINE; case TRACE: - return Level.FINER; + return Level.FINEST; default: - return Level.SEVERE; + break; } + // Can't happen. + return Level.FINE; } - /** - * Set maximum log level. - *

                                                                - * Messages with log levels higher than param level will be dropped. - * By default the log level is set to {@link RocLogLevel#ERROR ERROR}. - * - * @param level maximum log level. - */ - private native static void setLevel(RocLogLevel level); - - private native static void setHandler(RocLogHandler handler); - - private RocLogger() { - } + private native static void nativeSetLevel(RocLogLevel level); + private native static void nativeSetHandler(RocLogHandler handler); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java index f1434b00..ad3426d4 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java @@ -200,20 +200,32 @@ private static long construct(RocContext context, RocReceiverConfig config) thro Check.notNull(context, "context"); Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocReceiver.open(), context ptr={0}, config={1}", - new Object[]{toHex(context.getPtr()), config}); - long ptr = nativeOpen(context.getPtr(), config); - LOGGER.log(Level.FINE, "finished RocReceiver.open(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); - return ptr; + try { + LOGGER.log(Level.FINE, "entering RocReceiver(), contextPtr={0}, config={1}", + new Object[]{toHex(context.getPtr()), config}); + + long ptr = nativeOpen(context.getPtr(), config); + + LOGGER.log(Level.FINE, "leaving RocReceiver(), ptr={0}", toHex(ptr)); + return ptr; + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver(), exception={0}", exc); + throw exc; + } } private static void destroy(long ptr, RocContext context) throws Exception { - LOGGER.log(Level.FINE, "starting RocReceiver.close(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); - nativeClose(ptr); - LOGGER.log(Level.FINE, "finished RocReceiver.close(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); + try { + LOGGER.log(Level.FINE, "entering RocReceiver.close(), ptr={0}", toHex(ptr)); + + nativeClose(ptr); + + LOGGER.log(Level.FINE, "leaving RocReceiver.close(), ptr={0}", toHex(ptr)); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver.close(), ptr={0}, exception={1}", + new Object[]{toHex(context.getPtr()), toHex(ptr), exc}); + throw exc; + } } /** @@ -254,10 +266,18 @@ public void configure(Slot slot, Interface iface, InterfaceConfig config) throws Check.notNull(iface, "iface"); Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocReceiver.configure(), ptr={0}, slot={1}, iface={2}, config={3}", - new Object[]{toHex(getPtr()), slot, iface, config}); - nativeConfigure(getPtr(), slot.getValue(), iface.value, config); - LOGGER.log(Level.FINE, "finished RocReceiver.configure(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocReceiver.configure(), ptr={0}, slot={1}, iface={2}, config={3}", + new Object[]{toHex(getPtr()), slot, iface, config}); + + nativeConfigure(getPtr(), slot.getValue(), iface.value, config); + + LOGGER.log(Level.FINE, "leaving RocReceiver.configure(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver.configure(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -292,10 +312,18 @@ public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalAr Check.notNull(iface, "iface"); Check.notNull(endpoint, "endpoint"); - LOGGER.log(Level.FINE, "starting RocReceiver.bind(), ptr={0}, slot={1}, iface={2}, endpoint={3}", - new Object[]{toHex(getPtr()), slot, iface, endpoint}); - nativeBind(getPtr(), slot.getValue(), iface.value, endpoint); - LOGGER.log(Level.FINE, "finished RocReceiver.bind(), ptr={0}, endpoint={1}", new Object[]{toHex(getPtr()), endpoint}); + try { + LOGGER.log(Level.FINE, "entering RocReceiver.bind(), ptr={0}, slot={1}, iface={2}, endpoint={3}", + new Object[]{toHex(getPtr()), slot, iface, endpoint}); + + nativeBind(getPtr(), slot.getValue(), iface.value, endpoint); + + LOGGER.log(Level.FINE, "leaving RocReceiver.bind(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver.bind(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -314,9 +342,18 @@ public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalAr public void unlink(Slot slot) throws IllegalArgumentException { Check.notNull(slot, "slot"); - LOGGER.log(Level.FINE, "starting RocReceiver.unlink(), ptr={0}, slot={1}", new Object[]{toHex(getPtr()), slot}); - nativeUnlink(getPtr(), slot.getValue()); - LOGGER.log(Level.FINE, "finished RocReceiver.connect(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocReceiver.unlink(), ptr={0}, slot={1}", + new Object[]{toHex(getPtr()), slot}); + + nativeUnlink(getPtr(), slot.getValue()); + + LOGGER.log(Level.FINE, "leaving RocReceiver.unlink(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocReceiver.unlink(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -342,6 +379,7 @@ public void unlink(Slot slot) throws IllegalArgumentException { */ public void read(float[] samples) throws IllegalArgumentException, IOException { Check.notNull(samples, "samples"); + nativeReadFloats(getPtr(), samples); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java index 57c60e0a..cb361a15 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java @@ -170,20 +170,32 @@ private static long construct(RocContext context, RocSenderConfig config) throws Check.notNull(context, "context"); Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocSender.open(), context ptr={0}, config={1}", - new Object[]{toHex(context.getPtr()), config}); - long ptr = nativeOpen(context.getPtr(), config); - LOGGER.log(Level.FINE, "finished RocSender.open(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); - return ptr; + try { + LOGGER.log(Level.FINE, "entering RocSender(), contextPtr={0}, config={1}", + new Object[]{toHex(context.getPtr()), config}); + + long ptr = nativeOpen(context.getPtr(), config); + + LOGGER.log(Level.FINE, "leaving RocSender(), ptr={0}", toHex(ptr)); + return ptr; + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender(), exception={0}", exc); + throw exc; + } } private static void destroy(long ptr, RocContext context) throws Exception { - LOGGER.log(Level.FINE, "starting RocSender.close(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); - nativeClose(ptr); - LOGGER.log(Level.FINE, "finished RocSender.close(), context ptr={0}, ptr={1}", - new Object[]{toHex(context.getPtr()), toHex(ptr)}); + try { + LOGGER.log(Level.FINE, "entering RocSender.close(), ptr={0}", toHex(ptr)); + + nativeClose(ptr); + + LOGGER.log(Level.FINE, "leaving RocSender.close(), ptr={0}", toHex(ptr)); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender.close(), ptr={0}, exception={1}", + new Object[]{toHex(context.getPtr()), toHex(ptr), exc}); + throw exc; + } } /** @@ -225,10 +237,18 @@ public void configure(Slot slot, Interface iface, InterfaceConfig config) throws Check.notNull(iface, "iface"); Check.notNull(config, "config"); - LOGGER.log(Level.FINE, "starting RocSender.configure(), ptr={0}, slot={1}, iface={2}, config={3}", - new Object[]{toHex(getPtr()), slot, iface, config}); - nativeConfigure(getPtr(), slot.getValue(), iface.value, config); - LOGGER.log(Level.FINE, "finished RocSender.configure(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocSender.configure(), ptr={0}, slot={1}, iface={2}, config={3}", + new Object[]{toHex(getPtr()), slot, iface, config}); + + nativeConfigure(getPtr(), slot.getValue(), iface.value, config); + + LOGGER.log(Level.FINE, "leaving RocSender.configure(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender.configure(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -259,10 +279,18 @@ public void connect(Slot slot, Interface iface, Endpoint endpoint) throws Illega Check.notNull(iface, "iface"); Check.notNull(endpoint, "endpoint"); - LOGGER.log(Level.FINE, "starting RocSender.connect(), ptr={0}, slot={1}, iface={2}, endpoint={3}", - new Object[]{toHex(getPtr()), slot, iface, endpoint}); - nativeConnect(getPtr(), slot.getValue(), iface.value, endpoint); - LOGGER.log(Level.FINE, "finished RocSender.connect(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocSender.connect(), ptr={0}, slot={1}, iface={2}, endpoint={3}", + new Object[]{toHex(getPtr()), slot, iface, endpoint}); + + nativeConnect(getPtr(), slot.getValue(), iface.value, endpoint); + + LOGGER.log(Level.FINE, "leaving RocSender.connect(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender.connect(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -281,9 +309,18 @@ public void connect(Slot slot, Interface iface, Endpoint endpoint) throws Illega public void unlink(Slot slot) throws IllegalArgumentException { Check.notNull(slot, "slot"); - LOGGER.log(Level.FINE, "starting RocSender.unlink(), ptr={0}, slot={1}", new Object[]{toHex(getPtr()), slot}); - nativeUnlink(getPtr(), slot.getValue()); - LOGGER.log(Level.FINE, "finished RocSender.connect(), ptr={0}", new Object[]{toHex(getPtr())}); + try { + LOGGER.log(Level.FINE, "entering RocSender.unlink(), ptr={0}, slot={1}", + new Object[]{toHex(getPtr()), slot}); + + nativeUnlink(getPtr(), slot.getValue()); + + LOGGER.log(Level.FINE, "leaving RocSender.unlink(), ptr={0}", toHex(getPtr())); + } catch (Exception exc) { + LOGGER.log(Level.SEVERE, "exception in RocSender.unlink(), ptr={0}, exception={1}", + new Object[]{toHex(getPtr()), exc}); + throw exc; + } } /** @@ -308,6 +345,7 @@ public void unlink(Slot slot) throws IllegalArgumentException { */ public void write(float[] samples) throws IllegalArgumentException, IOException { Check.notNull(samples, "samples"); + nativeWriteFloats(getPtr(), samples); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java index 5bcc3d17..3ac38422 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocLoggerTest.java @@ -33,7 +33,7 @@ private static Stream allLogLevels() { Arguments.of(Level.SEVERE, true, false), Arguments.of(Level.INFO, true, true), Arguments.of(Level.FINE, true, true), - Arguments.of(Level.FINER, true, true) + Arguments.of(Level.FINEST, true, true) ); } @@ -45,7 +45,7 @@ public void testLogLevel(Level level, boolean expectError, boolean expectInfo) { Handler handler = wrapHandler( record -> msgCount.compute(record.getLevel(), (k, v) -> v == null ? 1 : v + 1)); RocLogger.LOGGER.addHandler(handler); - RocLogger.LOGGER.setLevel(Level.FINE); + RocLogger.LOGGER.setLevel(Level.FINEST); try { assertDoesNotThrow(() -> { From 189797b56fdbe3c4e166147f07d121a9b336e9ee Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 1 May 2025 23:01:18 +0900 Subject: [PATCH 4/7] Rename Endpoint methods for consistency --- .../org_rocstreaming_roctoolkit_Endpoint.h | 20 +++++----- roc_jni/src/main/impl/endpoint.c | 6 +-- .../org/rocstreaming/roctoolkit/Endpoint.java | 38 +++++++++++++------ 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_Endpoint.h b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_Endpoint.h index b89ed12e..7486b48b 100644 --- a/roc_jni/src/main/export/org_rocstreaming_roctoolkit_Endpoint.h +++ b/roc_jni/src/main/export/org_rocstreaming_roctoolkit_Endpoint.h @@ -9,26 +9,26 @@ extern "C" { #endif /* * Class: org_rocstreaming_roctoolkit_Endpoint - * Method: getUri - * Signature: ()Ljava/lang/String; + * Method: nativeParseUri + * Signature: (Ljava/lang/String;)V */ -JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_getUri - (JNIEnv *, jobject); +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeParseUri + (JNIEnv *, jobject, jstring); /* * Class: org_rocstreaming_roctoolkit_Endpoint - * Method: init - * Signature: (Ljava/lang/String;)V + * Method: nativeFormatUri + * Signature: ()Ljava/lang/String; */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_init - (JNIEnv *, jobject, jstring); +JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeFormatUri + (JNIEnv *, jobject); /* * Class: org_rocstreaming_roctoolkit_Endpoint - * Method: validate + * Method: nativeValidate * Signature: ()V */ -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_validate +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeValidate (JNIEnv *, jobject); #ifdef __cplusplus diff --git a/roc_jni/src/main/impl/endpoint.c b/roc_jni/src/main/impl/endpoint.c index 7ef972ea..d08313cf 100644 --- a/roc_jni/src/main/impl/endpoint.c +++ b/roc_jni/src/main/impl/endpoint.c @@ -134,7 +134,7 @@ void endpoint_set_resource(JNIEnv* env, jobject endpoint, const char* buf) { (*env)->SetObjectField(env, endpoint, attrId, (*env)->NewStringUTF(env, buf)); } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_init( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeParseUri( JNIEnv* env, jobject thisObj, jstring juri) { jclass endpointClass = NULL; roc_endpoint* endpoint = NULL; @@ -226,7 +226,7 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_init( } } -JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_getUri( +JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeFormatUri( JNIEnv* env, jobject thisObj) { roc_endpoint* endpoint = NULL; jstring juri = NULL; @@ -263,7 +263,7 @@ JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_getUri( return juri; } -JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_validate( +JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeValidate( JNIEnv* env, jobject thisObj) { roc_endpoint* endpoint = NULL; char* uri = NULL; diff --git a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java index ec9fb2fb..4522fb83 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java @@ -94,12 +94,14 @@ public class Endpoint { private String resource; /** - * Create endpoint from uri + * Create endpoint from URI * - * @param uri uri + * @param uri URI to parse + * + * @throws IllegalArgumentException if URI is invalid */ - public Endpoint(String uri) { - init(uri); + public Endpoint(String uri) throws IllegalArgumentException { + nativeParseUri(uri); } /** @@ -115,14 +117,16 @@ public Endpoint(String uri) { *

                                                                * If port is set to -1, the standard port for endpoint protocol is used. This is * allowed only if the protocol defines its standard port. - * @param resource resource nullable. Specifies percent-encoded path and query + * @param resource resource is nullable. Specifies percent-encoded path and query + * + * @throws IllegalArgumentException if URI components don't form a valid URI */ - public Endpoint(Protocol protocol, String host, int port, String resource) { + public Endpoint(Protocol protocol, String host, int port, String resource) throws IllegalArgumentException { this.protocol = Check.notNull(protocol, "protocol"); this.host = Check.notEmpty(host, "host"); this.port = port; this.resource = resource; - validate(); + nativeValidate(); } /** @@ -138,19 +142,29 @@ public Endpoint(Protocol protocol, String host, int port, String resource) { *

                                                                * If port is set to -1, the standard port for endpoint protocol is used. This is * allowed only if the protocol defines its standard port. + * + * @throws IllegalArgumentException if URI components don't form a valid URI */ public Endpoint(Protocol protocol, String host, int port) { this(protocol, host, port, null); } + /** + * Get string URI describing this endpoint. + */ + public String getUri() { + return nativeFormatUri(); + } + + /** + * Get string URI describing this endpoint. + */ @Override public String toString() { return getUri(); } - public native String getUri(); - - private native void init(String uri) throws IllegalArgumentException; - - private native void validate() throws IllegalArgumentException; + private native void nativeParseUri(String uri) throws IllegalArgumentException; + private native String nativeFormatUri(); + private native void nativeValidate() throws IllegalArgumentException; } From 1beb663d66281aa478f9393cdf5f8ae64cc7e8b3 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 1 May 2025 23:30:04 +0900 Subject: [PATCH 5/7] Overhaul error handling - Introduce RocException. Use it for system errors instead of Exception or IOException. - Update "throws" clauses. Explicitly list only checked exceptions. Don't list unchecked. But document all exceptions. - Rework C assertions. Remove assertions for failures caused by incorrect calls of C code from Java code. Instead, throw Java exceptions. Keep assertions only for internal violations on the C side. - Rework C helpers. All helpers return bool. If they return false, they also throw a java exception with failure details. Also make helpers more high level to simplify code that use them. - Remove boilerplate C helpers for enums, instead add a single function read_enum_field() an use it for all enums. - Improvements in logger on C side: cache java log levels, properly use global references. - Enable assertions in release builds too. - Additional validation in MediaEncoding builder to catch more user errors earlier. --- roc_jni/CMakeLists.txt | 14 +- roc_jni/src/main/impl/channel_layout.c | 13 - roc_jni/src/main/impl/channel_layout.h | 11 - roc_jni/src/main/impl/clock_source.c | 13 - roc_jni/src/main/impl/clock_source.h | 11 - roc_jni/src/main/impl/clock_sync_backend.c | 13 - roc_jni/src/main/impl/clock_sync_backend.h | 11 - roc_jni/src/main/impl/clock_sync_profile.c | 13 - roc_jni/src/main/impl/clock_sync_profile.h | 11 - roc_jni/src/main/impl/common.c | 134 ------ roc_jni/src/main/impl/common.h | 32 -- roc_jni/src/main/impl/context.c | 88 +++- roc_jni/src/main/impl/context_config.c | 37 +- roc_jni/src/main/impl/context_config.h | 10 +- roc_jni/src/main/impl/endpoint.c | 396 ++++++++++------- roc_jni/src/main/impl/endpoint.h | 12 +- roc_jni/src/main/impl/exceptions.h | 23 + roc_jni/src/main/impl/fec_encoding.c | 13 - roc_jni/src/main/impl/fec_encoding.h | 11 - roc_jni/src/main/impl/format.c | 11 - roc_jni/src/main/impl/format.h | 11 - roc_jni/src/main/impl/helpers.c | 419 ++++++++++++++++++ roc_jni/src/main/impl/helpers.h | 54 +++ roc_jni/src/main/impl/interface_config.c | 66 ++- roc_jni/src/main/impl/interface_config.h | 11 +- roc_jni/src/main/impl/logger.c | 241 ++++++---- roc_jni/src/main/impl/media_encoding.c | 56 ++- roc_jni/src/main/impl/media_encoding.h | 10 +- roc_jni/src/main/impl/package.h | 21 + roc_jni/src/main/impl/packet_encoding.c | 11 - roc_jni/src/main/impl/packet_encoding.h | 11 - roc_jni/src/main/impl/platform.h | 13 + roc_jni/src/main/impl/protocol.c | 29 -- roc_jni/src/main/impl/protocol.h | 14 - roc_jni/src/main/impl/receiver.c | 175 +++++--- roc_jni/src/main/impl/receiver_config.c | 117 ++--- roc_jni/src/main/impl/receiver_config.h | 10 +- roc_jni/src/main/impl/resampler_backend.c | 13 - roc_jni/src/main/impl/resampler_backend.h | 11 - roc_jni/src/main/impl/resampler_profile.c | 13 - roc_jni/src/main/impl/resampler_profile.h | 11 - roc_jni/src/main/impl/sender.c | 165 ++++--- roc_jni/src/main/impl/sender_config.c | 117 ++--- roc_jni/src/main/impl/sender_config.h | 10 +- .../org/rocstreaming/roctoolkit/Check.java | 14 +- .../rocstreaming/roctoolkit/Destructor.java | 8 +- .../org/rocstreaming/roctoolkit/Endpoint.java | 12 +- .../roctoolkit/InterfaceConfigValidator.java | 3 +- .../roctoolkit/MediaEncodingValidator.java | 22 +- .../rocstreaming/roctoolkit/NativeObject.java | 5 +- .../roctoolkit/NativeObjectCleaner.java | 2 +- .../NativeObjectPhantomReference.java | 10 +- .../roctoolkit/ProtocolUtils.java | 17 - .../rocstreaming/roctoolkit/RocContext.java | 28 +- .../roctoolkit/RocContextConfigValidator.java | 8 +- .../rocstreaming/roctoolkit/RocException.java | 10 + .../rocstreaming/roctoolkit/RocLogger.java | 5 +- .../rocstreaming/roctoolkit/RocReceiver.java | 57 +-- .../RocReceiverConfigValidator.java | 10 +- .../rocstreaming/roctoolkit/RocSender.java | 55 +-- .../roctoolkit/RocSenderConfigValidator.java | 12 +- .../org/rocstreaming/roctoolkit/BaseTest.java | 3 +- .../rocstreaming/roctoolkit/EndpointTest.java | 28 +- .../roctoolkit/MediaEncodingTest.java | 12 +- .../roctoolkit/NativeObjectCleanerTest.java | 30 +- .../roctoolkit/RocContextConfigTest.java | 4 +- .../roctoolkit/RocContextTest.java | 22 +- .../roctoolkit/RocReceiverConfigTest.java | 6 +- .../roctoolkit/RocReceiverTest.java | 46 +- .../roctoolkit/RocSenderConfigTest.java | 8 +- .../roctoolkit/RocSenderTest.java | 47 +- .../integration/RocSenderReceiverTest.java | 3 +- 72 files changed, 1709 insertions(+), 1264 deletions(-) delete mode 100644 roc_jni/src/main/impl/channel_layout.c delete mode 100644 roc_jni/src/main/impl/channel_layout.h delete mode 100644 roc_jni/src/main/impl/clock_source.c delete mode 100644 roc_jni/src/main/impl/clock_source.h delete mode 100644 roc_jni/src/main/impl/clock_sync_backend.c delete mode 100644 roc_jni/src/main/impl/clock_sync_backend.h delete mode 100644 roc_jni/src/main/impl/clock_sync_profile.c delete mode 100644 roc_jni/src/main/impl/clock_sync_profile.h delete mode 100644 roc_jni/src/main/impl/common.c delete mode 100644 roc_jni/src/main/impl/common.h create mode 100644 roc_jni/src/main/impl/exceptions.h delete mode 100644 roc_jni/src/main/impl/fec_encoding.c delete mode 100644 roc_jni/src/main/impl/fec_encoding.h delete mode 100644 roc_jni/src/main/impl/format.c delete mode 100644 roc_jni/src/main/impl/format.h create mode 100644 roc_jni/src/main/impl/helpers.c create mode 100644 roc_jni/src/main/impl/helpers.h create mode 100644 roc_jni/src/main/impl/package.h delete mode 100644 roc_jni/src/main/impl/packet_encoding.c delete mode 100644 roc_jni/src/main/impl/packet_encoding.h create mode 100644 roc_jni/src/main/impl/platform.h delete mode 100644 roc_jni/src/main/impl/protocol.c delete mode 100644 roc_jni/src/main/impl/protocol.h delete mode 100644 roc_jni/src/main/impl/resampler_backend.c delete mode 100644 roc_jni/src/main/impl/resampler_backend.h delete mode 100644 roc_jni/src/main/impl/resampler_profile.c delete mode 100644 roc_jni/src/main/impl/resampler_profile.h delete mode 100644 src/main/java/org/rocstreaming/roctoolkit/ProtocolUtils.java create mode 100644 src/main/java/org/rocstreaming/roctoolkit/RocException.java diff --git a/roc_jni/CMakeLists.txt b/roc_jni/CMakeLists.txt index 05775923..f19eaf25 100644 --- a/roc_jni/CMakeLists.txt +++ b/roc_jni/CMakeLists.txt @@ -13,25 +13,15 @@ if(APPLE) endif() add_library(roc_jni SHARED - src/main/impl/channel_layout.c - src/main/impl/clock_source.c - src/main/impl/clock_sync_backend.c - src/main/impl/clock_sync_profile.c - src/main/impl/common.c src/main/impl/context.c src/main/impl/context_config.c src/main/impl/endpoint.c - src/main/impl/fec_encoding.c - src/main/impl/format.c + src/main/impl/helpers.c src/main/impl/interface_config.c src/main/impl/logger.c src/main/impl/media_encoding.c - src/main/impl/packet_encoding.c - src/main/impl/protocol.c src/main/impl/receiver.c src/main/impl/receiver_config.c - src/main/impl/resampler_backend.c - src/main/impl/resampler_profile.c src/main/impl/sender.c src/main/impl/sender_config.c ) @@ -42,6 +32,8 @@ target_compile_options(roc_jni PRIVATE -Wno-system-headers -Wno-unused-but-set-variable -Wno-unused-parameter + # enable assertions (even in release build) + -UNDEBUG ) if(NOT ANDROID) diff --git a/roc_jni/src/main/impl/channel_layout.c b/roc_jni/src/main/impl/channel_layout.c deleted file mode 100644 index 1bbf7397..00000000 --- a/roc_jni/src/main/impl/channel_layout.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "channel_layout.h" -#include "common.h" - -#include - -roc_channel_layout get_channel_layout(JNIEnv* env, jobject jchannelLayout) { - jclass channelLayoutClass = NULL; - - channelLayoutClass = (*env)->FindClass(env, CHANNEL_LAYOUT_CLASS); - assert(channelLayoutClass != NULL); - - return (roc_channel_layout) get_enum_value(env, channelLayoutClass, jchannelLayout); -} diff --git a/roc_jni/src/main/impl/channel_layout.h b/roc_jni/src/main/impl/channel_layout.h deleted file mode 100644 index c1f5ab62..00000000 --- a/roc_jni/src/main/impl/channel_layout.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define CHANNEL_LAYOUT_CLASS PACKAGE_BASE_NAME "/ChannelLayout" - -roc_channel_layout get_channel_layout(JNIEnv* env, jobject jchannelLayout); diff --git a/roc_jni/src/main/impl/clock_source.c b/roc_jni/src/main/impl/clock_source.c deleted file mode 100644 index c3efe744..00000000 --- a/roc_jni/src/main/impl/clock_source.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "clock_source.h" -#include "common.h" - -#include - -roc_clock_source get_clock_source(JNIEnv* env, jobject jclockSource) { - jclass clockSourceClass = NULL; - - clockSourceClass = (*env)->FindClass(env, CLOCK_SOURCE_CLASS); - assert(clockSourceClass != NULL); - - return (roc_clock_source) get_enum_value(env, clockSourceClass, jclockSource); -} diff --git a/roc_jni/src/main/impl/clock_source.h b/roc_jni/src/main/impl/clock_source.h deleted file mode 100644 index 53ec84fd..00000000 --- a/roc_jni/src/main/impl/clock_source.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define CLOCK_SOURCE_CLASS PACKAGE_BASE_NAME "/ClockSource" - -roc_clock_source get_clock_source(JNIEnv* env, jobject jclockSource); diff --git a/roc_jni/src/main/impl/clock_sync_backend.c b/roc_jni/src/main/impl/clock_sync_backend.c deleted file mode 100644 index 9b180f8a..00000000 --- a/roc_jni/src/main/impl/clock_sync_backend.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "clock_sync_backend.h" -#include "common.h" - -#include - -roc_clock_sync_backend get_clock_sync_backend(JNIEnv* env, jobject jclockSyncBackend) { - jclass clockSyncBackend = NULL; - - clockSyncBackend = (*env)->FindClass(env, CLOCK_SYNC_BACKEND_CLASS); - assert(clockSyncBackend != NULL); - - return (roc_clock_sync_backend) get_enum_value(env, clockSyncBackend, jclockSyncBackend); -} diff --git a/roc_jni/src/main/impl/clock_sync_backend.h b/roc_jni/src/main/impl/clock_sync_backend.h deleted file mode 100644 index 5bc2050d..00000000 --- a/roc_jni/src/main/impl/clock_sync_backend.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define CLOCK_SYNC_BACKEND_CLASS PACKAGE_BASE_NAME "/ClockSyncBackend" - -roc_clock_sync_backend get_clock_sync_backend(JNIEnv* env, jobject jclockSyncBackend); diff --git a/roc_jni/src/main/impl/clock_sync_profile.c b/roc_jni/src/main/impl/clock_sync_profile.c deleted file mode 100644 index 027b3119..00000000 --- a/roc_jni/src/main/impl/clock_sync_profile.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "clock_sync_profile.h" -#include "common.h" - -#include - -roc_clock_sync_profile get_clock_sync_profile(JNIEnv* env, jobject jclockSyncProfile) { - jclass clockSyncProfile = NULL; - - clockSyncProfile = (*env)->FindClass(env, CLOCK_SYNC_PROFILE_CLASS); - assert(clockSyncProfile != NULL); - - return (roc_clock_sync_profile) get_enum_value(env, clockSyncProfile, jclockSyncProfile); -} diff --git a/roc_jni/src/main/impl/clock_sync_profile.h b/roc_jni/src/main/impl/clock_sync_profile.h deleted file mode 100644 index dd09c5b8..00000000 --- a/roc_jni/src/main/impl/clock_sync_profile.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define CLOCK_SYNC_PROFILE_CLASS PACKAGE_BASE_NAME "/ClockSyncProfile" - -roc_clock_sync_profile get_clock_sync_profile(JNIEnv* env, jobject jclockSyncProfile); diff --git a/roc_jni/src/main/impl/common.c b/roc_jni/src/main/impl/common.c deleted file mode 100644 index 3b08b66c..00000000 --- a/roc_jni/src/main/impl/common.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "common.h" - -#include - -int get_boolean_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "Z"); - assert(attrId != NULL); - - return (*env)->GetBooleanField(env, obj, attrId) == JNI_TRUE; -} - -int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - assert(attrName != NULL); - assert(error != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "I"); - assert(attrId != NULL); - - jint ret = (*env)->GetIntField(env, obj, attrId); - if (ret < INT_MIN || ret > INT_MAX) { - *error = -1; - return 0; - } - - return (int) ret; -} - -unsigned int get_uint_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - assert(attrName != NULL); - assert(error != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "I"); - assert(attrId != NULL); - - jint ret = (*env)->GetIntField(env, obj, attrId); - if (ret < 0 || (unsigned int) ret > UINT_MAX) { - *error = -1; - return 0; - } - - return (unsigned int) ret; -} - -long long get_llong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - assert(attrName != NULL); - assert(error != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "J"); - assert(attrId != NULL); - - jlong ret = (*env)->GetLongField(env, obj, attrId); - assert(sizeof(long long) == sizeof(jlong)); - - return (long long) ret; -} - -unsigned long long get_ullong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - assert(attrName != NULL); - assert(error != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "J"); - assert(attrId != NULL); - - jlong ret = (*env)->GetLongField(env, obj, attrId); - if (ret < 0) { - *error = -1; - return 0LL; - } - - return (unsigned long long) ret; -} - -long long get_duration_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error) { - assert(env != NULL); - assert(clazz != NULL); - assert(attrName != NULL); - assert(error != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, "Ljava/time/Duration;"); - assert(attrId != NULL); - - jobject durationObj = (*env)->GetObjectField(env, obj, attrId); - if (durationObj == NULL) { - return 0; - } - - jclass durationClass = (*env)->FindClass(env, "java/time/Duration"); - assert(durationClass != NULL); - - jmethodID toNanosMethodId = (*env)->GetMethodID(env, durationClass, "toNanos", "()J"); - assert(toNanosMethodId != NULL); - - jlong ret = (*env)->CallLongMethod(env, durationObj, toNanosMethodId); - return (long long) ret; -} - -int get_enum_value(JNIEnv* env, jclass clazz, jobject enumObj) { - assert(env != NULL); - assert(clazz != NULL); - - if (enumObj != NULL) { - jfieldID attrId = (*env)->GetFieldID(env, clazz, "value", "I"); - return (*env)->GetIntField(env, enumObj, attrId); - } - - return 0; -} - -jobject get_object_field( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, const char* attrClassName) { - assert(env != NULL); - assert(clazz != NULL); - - jfieldID attrId = (*env)->GetFieldID(env, clazz, attrName, attrClassName); - assert(attrId != NULL); - - return (*env)->GetObjectField(env, obj, attrId); -} diff --git a/roc_jni/src/main/impl/common.h b/roc_jni/src/main/impl/common.h deleted file mode 100644 index 875d8ebb..00000000 --- a/roc_jni/src/main/impl/common.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -#include -#include - -#define JNI_VERSION JNI_VERSION_1_6 - -#define PACKAGE_BASE_NAME "org/rocstreaming/roctoolkit" -#define EXCEPTION "java/lang/Exception" -#define ILLEGAL_ARGUMENTS_EXCEPTION "java/lang/IllegalArgumentException" -#define IO_EXCEPTION "java/io/IOException" - -int get_boolean_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); - -int get_int_field_value(JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); -unsigned int get_uint_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); - -long long get_llong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); -unsigned long long get_ullong_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); - -long long get_duration_field_value( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, int* error); - -int get_enum_value(JNIEnv* env, jclass clazz, jobject enumObj); -jobject get_object_field( - JNIEnv* env, jclass clazz, jobject obj, const char* attrName, const char* attrClassName); diff --git a/roc_jni/src/main/impl/context.c b/roc_jni/src/main/impl/context.c index ac9fdee7..0661124f 100644 --- a/roc_jni/src/main/impl/context.c +++ b/roc_jni/src/main/impl/context.c @@ -1,55 +1,95 @@ #include "org_rocstreaming_roctoolkit_RocContext.h" -#include "common.h" #include "context_config.h" +#include "exceptions.h" +#include "helpers.h" #include "media_encoding.h" +#include "package.h" #include +#include + JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeOpen( - JNIEnv* env, jclass contextClass, jobject config) { - roc_context* context = NULL; + JNIEnv* env, jclass jclass, jobject jconfig) { + assert(env); + roc_context_config context_config = {}; + roc_context* context = NULL; - if (context_config_unmarshal(env, &context_config, config) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Wrong context configuration values"); - return (jlong) NULL; + if (!jconfig) { + throw_exception( + env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContextConfig: must not be null"); + goto out; + } + + if (!context_config_unmarshal(env, jconfig, &context_config)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContextConfig"); + goto out; } if (roc_context_open(&context_config, &context) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error opening context"); - return (jlong) NULL; + throw_exception(env, ROC_EXCEPTION, "Failed to open RocContext"); + goto out; + } + + if (!context) { + throw_exception(env, ASSERTION_ERROR, "RocContext is null"); + goto out; } +out: return (jlong) context; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeClose( - JNIEnv* env, jclass contextClass, jlong contextPtr) { - roc_context* context = (roc_context*) contextPtr; + JNIEnv* env, jclass jclass, jlong jcontext) { + assert(env); + + roc_context* context = (roc_context*) jcontext; + + if (!jcontext) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContext: must not be null"); + goto out; + } if (roc_context_close(context) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error closing context"); + throw_exception(env, ILLEGAL_STATE_EXCEPTION, + "Can't close RocContext before closing associated RocSender/RocReceiver(s)"); + goto out; } + +out: + return; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocContext_nativeRegisterEncoding( - JNIEnv* env, jclass contextClass, jlong contextPtr, jint encodingId, jobject jencoding) { - roc_context* context = (roc_context*) contextPtr; + JNIEnv* env, jclass jclass, jlong jcontext, jint jencoding_id, jobject jencoding) { + assert(env); + + roc_context* context = (roc_context*) jcontext; roc_media_encoding encoding = {}; - if (media_encoding_unmarshal(env, &encoding, jencoding) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Invalid MediaEncoding"); - return; + if (!jcontext) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContext: must not be null"); + goto out; } - if (roc_context_register_encoding(context, (int) encodingId, &encoding) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error registering encoding"); - return; + if (!jencoding) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid MediaEncoding: must not be null"); + goto out; } + + if (!media_encoding_unmarshal(env, jencoding, &encoding)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid MediaEncoding"); + goto out; + } + + if (roc_context_register_encoding(context, (int) jencoding_id, &encoding) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to register MediaEncoding"); + goto out; + } + +out: + return; } diff --git a/roc_jni/src/main/impl/context_config.c b/roc_jni/src/main/impl/context_config.c index af99523e..c1a04fea 100644 --- a/roc_jni/src/main/impl/context_config.c +++ b/roc_jni/src/main/impl/context_config.c @@ -1,24 +1,31 @@ #include "context_config.h" -#include "common.h" +#include "helpers.h" +#include "package.h" -#include +#include +#include -int context_config_unmarshal(JNIEnv* env, roc_context_config* conf, jobject jconfig) { - jclass contextConfigClass = NULL; - int err = 0; +bool context_config_unmarshal(JNIEnv* env, jobject jconfig, roc_context_config* result) { + assert(env); + assert(jconfig); + assert(result); - contextConfigClass = (*env)->FindClass(env, CONTEXT_CONFIG_CLASS); - assert(contextConfigClass != NULL); + memset(result, 0, sizeof(*result)); - memset(conf, 0, sizeof(roc_context_config)); + jclass jclass = find_class(env, CONTEXT_CONFIG_CLASS); + if (!jclass) { + return false; + } - conf->max_packet_size - = get_uint_field_value(env, contextConfigClass, jconfig, "maxPacketSize", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jconfig, CONTEXT_CONFIG_CLASS, "maxPacketSize", + &result->max_packet_size)) { + return false; + } - conf->max_frame_size - = get_uint_field_value(env, contextConfigClass, jconfig, "maxFrameSize", &err); - if (err) return err; + if (!read_uint_field( + env, jclass, jconfig, CONTEXT_CONFIG_CLASS, "maxFrameSize", &result->max_frame_size)) { + return false; + } - return 0; + return true; } diff --git a/roc_jni/src/main/impl/context_config.h b/roc_jni/src/main/impl/context_config.h index e534aac4..ffa187a6 100644 --- a/roc_jni/src/main/impl/context_config.h +++ b/roc_jni/src/main/impl/context_config.h @@ -1,11 +1,11 @@ #pragma once -#include - -#include "common.h" +#include "platform.h" +#include #include -#define CONTEXT_CONFIG_CLASS PACKAGE_BASE_NAME "/RocContextConfig" +#include -int context_config_unmarshal(JNIEnv* env, roc_context_config* config, jobject jconfig); +ATTR_NODISCARD bool context_config_unmarshal( + JNIEnv* env, jobject jconfig, roc_context_config* result); diff --git a/roc_jni/src/main/impl/endpoint.c b/roc_jni/src/main/impl/endpoint.c index d08313cf..f0ff5616 100644 --- a/roc_jni/src/main/impl/endpoint.c +++ b/roc_jni/src/main/impl/endpoint.c @@ -1,295 +1,373 @@ #include "org_rocstreaming_roctoolkit_Endpoint.h" -#include "common.h" #include "endpoint.h" -#include "protocol.h" +#include "exceptions.h" +#include "helpers.h" -#include +#include +#include #include +#include -int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint) { - jclass endpointClass = NULL; - jobject jprotocol = NULL; - roc_protocol protocol = (roc_protocol) 0; - jstring jhost = NULL; - const char* host = NULL; - int port = 0; - jstring jresource = NULL; - const char* resource = NULL; - int err = 0; +bool endpoint_unmarshal(JNIEnv* env, jobject jendpoint, roc_endpoint** result) { + assert(env); + assert(jendpoint); + assert(result); + + jclass jclass = NULL; + jobject jhost = NULL; + jobject jresource = NULL; + + const char* host_str = NULL; + const char* resource_str = NULL; + + int port_value = 0; + int enum_value = 0; - // endpoint - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + bool success = false; - assert(endpoint != NULL); - assert(*endpoint == NULL); + *result = NULL; - if (jendpoint == NULL) { - err = -1; + jclass = find_class(env, ENDPOINT_CLASS); + if (!jclass) { goto out; } - if ((err = roc_endpoint_allocate(endpoint)) != 0) { - *endpoint = NULL; + + if (roc_endpoint_allocate(result) != 0) { + throw_exception(env, ASSERTION_ERROR, "Failed to allocate endpoint"); goto out; } // protocol - jprotocol = get_object_field(env, endpointClass, jendpoint, "protocol", "L" PROTOCOL_CLASS ";"); - if (jprotocol != NULL) { - protocol = get_protocol(env, jprotocol); - if ((err = roc_endpoint_set_protocol(*endpoint, protocol)) != 0) goto out; + if (!read_enum_field( + env, jclass, jendpoint, ENDPOINT_CLASS, "protocol", PROTOCOL_CLASS, &enum_value)) { + goto out; + } + if (enum_value != 0) { + if (roc_endpoint_set_protocol(*result, (roc_protocol) enum_value) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint protocol"); + goto out; + } } // host - jhost = (jstring) get_object_field(env, endpointClass, jendpoint, "host", "Ljava/lang/String;"); - if (jhost != NULL) { - host = (*env)->GetStringUTFChars(env, jhost, 0); - if (host == NULL) { - err = -1; + if (!read_object_field( + env, jclass, jendpoint, ENDPOINT_CLASS, "host", "java/lang/String", &jhost)) { + goto out; + } + if (jhost) { + host_str = (*env)->GetStringUTFChars(env, jhost, 0); + if (roc_endpoint_set_host(*result, host_str) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint host"); goto out; } - if ((err = roc_endpoint_set_host(*endpoint, host)) != 0) goto out; } // port - port = get_int_field_value(env, endpointClass, jendpoint, "port", &err); - if (err) goto out; - if ((err = roc_endpoint_set_port(*endpoint, port)) != 0) goto out; + if (!read_int_field(env, jclass, jendpoint, ENDPOINT_CLASS, "port", &port_value)) { + goto out; + } + if (roc_endpoint_set_port(*result, port_value) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint port"); + goto out; + } // resource - jresource = (jstring) get_object_field( - env, endpointClass, jendpoint, "resource", "Ljava/lang/String;"); - if (jresource != NULL) { - resource = (*env)->GetStringUTFChars(env, jresource, 0); - if (resource == NULL) { - err = -1; + if (!read_object_field( + env, jclass, jendpoint, ENDPOINT_CLASS, "resource", "java/lang/String", &jresource)) { + goto out; + } + if (jresource) { + resource_str = (*env)->GetStringUTFChars(env, jresource, 0); + if (roc_endpoint_set_resource(*result, resource_str) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint resource"); goto out; } - if ((err = roc_endpoint_set_resource(*endpoint, resource)) != 0) goto out; } + success = true; + out: - if (host != NULL) (*env)->ReleaseStringUTFChars(env, jhost, host); - if (resource != NULL) (*env)->ReleaseStringUTFChars(env, jresource, resource); + if (host_str) { + (*env)->ReleaseStringUTFChars(env, jhost, host_str); + } + if (resource_str) { + (*env)->ReleaseStringUTFChars(env, jresource, resource_str); + } - if (err != 0 && *endpoint != NULL) { - roc_endpoint_deallocate(*endpoint); - *endpoint = NULL; + if (!success && *result) { + roc_endpoint_deallocate(*result); + *result = NULL; } - return err; + return success; } -void endpoint_set_protocol(JNIEnv* env, jobject endpoint, roc_protocol protocol) { - jclass endpointClass = NULL; - jfieldID attrId = NULL; - jobject protocolObj = NULL; +bool endpoint_set_protocol(JNIEnv* env, jobject jendpoint, roc_protocol value) { + assert(env); + assert(jendpoint); - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + jclass jendpoint_class = find_class(env, ENDPOINT_CLASS); + if (!jendpoint_class) { + return false; + } - attrId = (*env)->GetFieldID(env, endpointClass, "protocol", "L" PROTOCOL_CLASS ";"); - assert(attrId != NULL); + jfieldID jprotocol_field + = find_field(env, jendpoint_class, ENDPOINT_CLASS, "protocol", PROTOCOL_CLASS); + if (!jprotocol_field) { + return false; + } + + jclass jprotocol_class = find_class(env, PROTOCOL_CLASS); + if (!jprotocol_class) { + return false; + } + + jobject jprotocol_value = find_enum_constant(env, jprotocol_class, PROTOCOL_CLASS, (int) value); + if (!jprotocol_value) { + return false; + } - protocolObj = get_protocol_enum(env, protocol); - assert(protocolObj != NULL); - (*env)->SetObjectField(env, endpoint, attrId, protocolObj); + (*env)->SetObjectField(env, jendpoint, jprotocol_field, jprotocol_value); + return true; } -void endpoint_set_host(JNIEnv* env, jobject endpoint, const char* buf) { - jclass endpointClass = NULL; - jfieldID attrId = NULL; +bool endpoint_set_host(JNIEnv* env, jobject jendpoint, const char* value) { + assert(env); + assert(jendpoint); - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + jclass jclass = find_class(env, ENDPOINT_CLASS); + if (!jclass) { + return false; + } + + jfieldID jfid = find_field(env, jclass, ENDPOINT_CLASS, "host", "java/lang/String"); + if (!jfid) { + return false; + } - attrId = (*env)->GetFieldID(env, endpointClass, "host", "Ljava/lang/String;"); - assert(attrId != NULL); - (*env)->SetObjectField(env, endpoint, attrId, (*env)->NewStringUTF(env, buf)); + jobject jvalue = value ? (*env)->NewStringUTF(env, value) : NULL; + + (*env)->SetObjectField(env, jendpoint, jfid, jvalue); + return true; } -void endpoint_set_port(JNIEnv* env, jobject endpoint, int port) { - jclass endpointClass = NULL; - jfieldID attrId = NULL; +bool endpoint_set_port(JNIEnv* env, jobject jendpoint, int value) { + assert(env); + assert(jendpoint); - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + jclass jclass = find_class(env, ENDPOINT_CLASS); + if (!jclass) { + return false; + } - attrId = (*env)->GetFieldID(env, endpointClass, "port", "I"); - assert(attrId != NULL); - (*env)->SetIntField(env, endpoint, attrId, port); + jfieldID jfid = find_field(env, jclass, ENDPOINT_CLASS, "port", "I"); + if (!jfid) { + return false; + } + + (*env)->SetIntField(env, jendpoint, jfid, value); + return true; } -void endpoint_set_resource(JNIEnv* env, jobject endpoint, const char* buf) { - jclass endpointClass = NULL; - jfieldID attrId = NULL; +bool endpoint_set_resource(JNIEnv* env, jobject jendpoint, const char* value) { + assert(env); + assert(jendpoint); + + jclass jclass = find_class(env, ENDPOINT_CLASS); + if (!jclass) { + return false; + } + + jfieldID jfid = find_field(env, jclass, ENDPOINT_CLASS, "resource", "java/lang/String"); + if (!jfid) { + return false; + } - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + jobject jvalue = value ? (*env)->NewStringUTF(env, value) : NULL; - attrId = (*env)->GetFieldID(env, endpointClass, "resource", "Ljava/lang/String;"); - assert(attrId != NULL); - (*env)->SetObjectField(env, endpoint, attrId, (*env)->NewStringUTF(env, buf)); + (*env)->SetObjectField(env, jendpoint, jfid, jvalue); + return true; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeParseUri( - JNIEnv* env, jobject thisObj, jstring juri) { - jclass endpointClass = NULL; + JNIEnv* env, jobject jobj, jstring juri) { + assert(env); + + jclass jclass = NULL; roc_endpoint* endpoint = NULL; const char* uri = NULL; roc_protocol protocol = (roc_protocol) 0; char* host = NULL; - size_t hostSz = 0; + size_t host_size = 0; int port = 0; char* resource = NULL; - size_t resourceSz = 0; + size_t resource_size = 0; - // endpoint - endpointClass = (*env)->FindClass(env, ENDPOINT_CLASS); - assert(endpointClass != NULL); + jclass = find_class(env, ENDPOINT_CLASS); + if (!jclass) { + goto out; + } if (roc_endpoint_allocate(&endpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't allocate roc_endpoint"); - endpoint = NULL; + throw_exception(env, ASSERTION_ERROR, "Failed to allocate endpoint"); goto out; - }; + } - // uri - if (juri == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad uri argument"); + // parse uri + if (!juri) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: must not be null"); goto out; } uri = (*env)->GetStringUTFChars(env, juri, 0); - if (uri == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad uri argument"); + if (!uri) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: must not be null"); goto out; } if (roc_endpoint_set_uri(endpoint, uri) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad uri argument"); + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri"); goto out; } - // protocol + // copy protocol if (roc_endpoint_get_protocol(endpoint, &protocol) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get protocol from endpoint"); + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: bad protocol"); + goto out; + } + if (!endpoint_set_protocol(env, jobj, protocol)) { + throw_exception(env, ASSERTION_ERROR, "Failed to update endpoint"); goto out; } - endpoint_set_protocol(env, thisObj, protocol); - // host - if (roc_endpoint_get_host(endpoint, NULL, &hostSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get host from endpoint"); + // copy host + if (roc_endpoint_get_host(endpoint, NULL, &host_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: bad host"); + goto out; + } + host = (char*) calloc(host_size + 1, sizeof(char)); + if (roc_endpoint_get_host(endpoint, host, &host_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: bad host"); goto out; } - host = (char*) malloc(hostSz); - if (roc_endpoint_get_host(endpoint, host, &hostSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get host from endpoint"); + if (!endpoint_set_host(env, jobj, host)) { + throw_exception(env, ASSERTION_ERROR, "Failed to update endpoint"); goto out; } - endpoint_set_host(env, thisObj, host); - // port - if (roc_endpoint_get_port(endpoint, &port) == 0) { - endpoint_set_port(env, thisObj, port); - } else { - endpoint_set_port(env, thisObj, -1); + // copy port + if (roc_endpoint_get_port(endpoint, &port) != 0) { + // port is optional + port = -1; + } + if (!endpoint_set_port(env, jobj, port)) { + throw_exception(env, ASSERTION_ERROR, "Failed to update endpoint"); + goto out; } - // resource - if (roc_endpoint_get_resource(endpoint, NULL, &resourceSz) == 0) { - resource = (char*) malloc(resourceSz); - if (roc_endpoint_get_resource(endpoint, resource, &resourceSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get resource from endpoint"); + // copy resource + if (roc_endpoint_get_resource(endpoint, NULL, &resource_size) != 0) { + if (!endpoint_set_resource(env, jobj, NULL)) { + throw_exception(env, ASSERTION_ERROR, "Failed to update endpoint"); + goto out; + } + } else { + resource = (char*) calloc(resource_size + 1, sizeof(char)); + if (roc_endpoint_get_resource(endpoint, resource, &resource_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri: bad resource"); + goto out; + } + if (!endpoint_set_resource(env, jobj, resource)) { + throw_exception(env, ASSERTION_ERROR, "Failed to update endpoint"); goto out; } - endpoint_set_resource(env, thisObj, resource); } out: - if (juri != NULL && uri != NULL) (*env)->ReleaseStringUTFChars(env, juri, uri); + if (uri) (*env)->ReleaseStringUTFChars(env, juri, uri); free(host); free(resource); - if (endpoint != NULL) { + if (endpoint) { roc_endpoint_deallocate(endpoint); } } JNIEXPORT jstring JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeFormatUri( - JNIEnv* env, jobject thisObj) { + JNIEnv* env, jobject jendpoint) { + assert(env); + roc_endpoint* endpoint = NULL; jstring juri = NULL; char* uri = NULL; - size_t uriSz = 0; + size_t uri_size = 0; - if (endpoint_unmarshal(env, &endpoint, thisObj) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't unmarshal roc_endpoint"); + if (!jendpoint) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint: must not be null"); goto out; } - if (roc_endpoint_get_uri(endpoint, NULL, &uriSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get uri from endpoint"); + if (!endpoint_unmarshal(env, jendpoint, &endpoint)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint"); goto out; } - uri = (char*) malloc(uriSz); - if (roc_endpoint_get_uri(endpoint, uri, &uriSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Can't get uri from endpoint"); + + if (roc_endpoint_get_uri(endpoint, NULL, &uri_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri"); + goto out; + } + uri = (char*) calloc(uri_size + 1, sizeof(char)); + if (roc_endpoint_get_uri(endpoint, uri, &uri_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri"); goto out; } - assert(uri != NULL); juri = (*env)->NewStringUTF(env, uri); + assert(juri); out: - if (endpoint != NULL) { + free(uri); + + if (endpoint) { roc_endpoint_deallocate(endpoint); } - free(uri); return juri; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_Endpoint_nativeValidate( - JNIEnv* env, jobject thisObj) { + JNIEnv* env, jobject jendpoint) { + assert(env); + roc_endpoint* endpoint = NULL; char* uri = NULL; - size_t uriSz = 0; + size_t uri_size = 0; - if (endpoint_unmarshal(env, &endpoint, thisObj) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Invalid roc_endpoint"); + if (!jendpoint) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint: must not be null"); goto out; } - if (roc_endpoint_get_uri(endpoint, NULL, &uriSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Invalid roc_endpoint"); + if (!endpoint_unmarshal(env, jendpoint, &endpoint)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint"); goto out; } - uri = (char*) malloc(uriSz); - if (roc_endpoint_get_uri(endpoint, uri, &uriSz) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Invalid roc_endpoint"); + + if (roc_endpoint_get_uri(endpoint, NULL, &uri_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri"); + goto out; + } + uri = (char*) calloc(uri_size + 1, sizeof(char)); + if (roc_endpoint_get_uri(endpoint, uri, &uri_size) != 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid endpoint uri"); goto out; } out: - if (endpoint != NULL) { + free(uri); + + if (endpoint) { roc_endpoint_deallocate(endpoint); } - free(uri); } diff --git a/roc_jni/src/main/impl/endpoint.h b/roc_jni/src/main/impl/endpoint.h index c04fdd62..a5da0ac1 100644 --- a/roc_jni/src/main/impl/endpoint.h +++ b/roc_jni/src/main/impl/endpoint.h @@ -1,11 +1,15 @@ #pragma once -#include +#include "platform.h" +#include #include -#define ENDPOINT_CLASS PACKAGE_BASE_NAME "/Endpoint" +#include -int endpoint_unmarshal(JNIEnv* env, roc_endpoint** endpoint, jobject jendpoint); +ATTR_NODISCARD bool endpoint_unmarshal(JNIEnv* env, jobject jendpoint, roc_endpoint** result); -void endpoint_set_port(JNIEnv* env, jobject endpoint, int port); +ATTR_NODISCARD bool endpoint_set_protocol(JNIEnv* env, jobject jendpoint, roc_protocol value); +ATTR_NODISCARD bool endpoint_set_host(JNIEnv* env, jobject jendpoint, const char* value); +ATTR_NODISCARD bool endpoint_set_port(JNIEnv* env, jobject jendpoint, int value); +ATTR_NODISCARD bool endpoint_set_resource(JNIEnv* env, jobject jendpoint, const char* resource); diff --git a/roc_jni/src/main/impl/exceptions.h b/roc_jni/src/main/impl/exceptions.h new file mode 100644 index 00000000..bb9ab6d7 --- /dev/null +++ b/roc_jni/src/main/impl/exceptions.h @@ -0,0 +1,23 @@ +#pragma once + +#include "package.h" + +// User called us with invalid arguments. +// Indicates bug in caller's code. +// Unchecked exception. +#define ILLEGAL_ARGUMENT_EXCEPTION "java/lang/IllegalArgumentException" + +// User called us before fulfilling prerequisites. +// Indicates bug in caller's code. +// Unchecked exception. +#define ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException" + +// Unexpected internal failure: class lookup failure, allocation failure, etc. +// Indicates bug in JNI code. +// Unchecked exception. +#define ASSERTION_ERROR "java/lang/AssertionError" + +// Generic system failure caused by external means. +// Indicates condition to be handled in caller's code. +// Checked exception. +#define ROC_EXCEPTION PACKAGE_NAME "/RocException" diff --git a/roc_jni/src/main/impl/fec_encoding.c b/roc_jni/src/main/impl/fec_encoding.c deleted file mode 100644 index c28c3a81..00000000 --- a/roc_jni/src/main/impl/fec_encoding.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "fec_encoding.h" -#include "common.h" - -#include - -roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfecEncoding) { - jclass fecEncodingClass = NULL; - - fecEncodingClass = (*env)->FindClass(env, FEC_ENCODING_CLASS); - assert(fecEncodingClass != NULL); - - return (roc_fec_encoding) get_enum_value(env, fecEncodingClass, jfecEncoding); -} diff --git a/roc_jni/src/main/impl/fec_encoding.h b/roc_jni/src/main/impl/fec_encoding.h deleted file mode 100644 index 504a005d..00000000 --- a/roc_jni/src/main/impl/fec_encoding.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define FEC_ENCODING_CLASS PACKAGE_BASE_NAME "/FecEncoding" - -roc_fec_encoding get_fec_encoding(JNIEnv* env, jobject jfecEncoding); diff --git a/roc_jni/src/main/impl/format.c b/roc_jni/src/main/impl/format.c deleted file mode 100644 index fd651d8d..00000000 --- a/roc_jni/src/main/impl/format.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "format.h" -#include "common.h" - -roc_format get_format(JNIEnv* env, jobject jformat) { - jclass formatClass = NULL; - - formatClass = (*env)->FindClass(env, FORMAT_CLASS); - assert(formatClass != NULL); - - return (roc_format) get_enum_value(env, formatClass, jformat); -} diff --git a/roc_jni/src/main/impl/format.h b/roc_jni/src/main/impl/format.h deleted file mode 100644 index f1af79f9..00000000 --- a/roc_jni/src/main/impl/format.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define FORMAT_CLASS PACKAGE_BASE_NAME "/Format" - -roc_format get_format(JNIEnv* env, jobject jformat); diff --git a/roc_jni/src/main/impl/helpers.c b/roc_jni/src/main/impl/helpers.c new file mode 100644 index 00000000..f96ac58a --- /dev/null +++ b/roc_jni/src/main/impl/helpers.c @@ -0,0 +1,419 @@ +#include "helpers.h" +#include "exceptions.h" + +#include +#include +#include +#include +#include + +void throw_exception(JNIEnv* env, const char* exception, const char* message, ...) { + assert(env); + assert(exception); + assert(message); + + // Another exception is pending, ignore subsequent exceptions during this native call. + if ((*env)->ExceptionCheck(env)) return; + + char text[1024] = {}; + va_list args; + va_start(args, message); + vsnprintf(text, sizeof(text) - 1, message, args); + va_end(args); + + jclass jclass = (*env)->FindClass(env, exception); + assert(jclass); + + (*env)->ThrowNew(env, jclass, text); +} + +jclass find_class(JNIEnv* env, const char* class_name) { + assert(env); + assert(class_name); + + jclass result = (*env)->FindClass(env, class_name); + if (!result) { + return NULL; + } + + return result; +} + +jmethodID find_method(JNIEnv* env, jclass jclass, const char* class_name, const char* method_name, + const char* method_sig) { + assert(env); + assert(jclass); + assert(class_name); + assert(method_name); + assert(method_sig); + + jmethodID result = (*env)->GetMethodID(env, jclass, method_name, method_sig); + if (!result) { + return NULL; + } + + return result; +} + +jfieldID find_field(JNIEnv* env, jclass jclass, const char* class_name, const char* field_name, + const char* field_type) { + assert(env); + assert(jclass); + assert(class_name); + assert(field_name); + assert(field_type); + + char field_sig[256] = {}; + if (strlen(field_type) == 1) { + strncpy(field_sig, field_type, sizeof(field_sig) - 1); + } else { + snprintf(field_sig, sizeof(field_sig) - 1, "L%s;", field_type); + } + + jfieldID result = (*env)->GetFieldID(env, jclass, field_name, field_sig); + if (!result) { + return NULL; + } + + return result; +} + +jobject find_enum_constant(JNIEnv* env, jclass jclass, const char* enum_class, int enum_value) { + assert(env); + assert(jclass); + assert(enum_class); + assert(enum_value); + + jfieldID value_field = (*env)->GetFieldID(env, jclass, "value", "I"); + if (!value_field) { + return NULL; + } + + char method_sig[256] = {}; + snprintf(method_sig, sizeof(method_sig) - 1, "()[L%s;", enum_class); + + jmethodID values_method = (*env)->GetStaticMethodID(env, jclass, "values", method_sig); + if (!values_method) { + return NULL; + } + + jobjectArray enum_values_array + = (jobjectArray) (*env)->CallStaticObjectMethod(env, jclass, values_method); + if ((*env)->ExceptionCheck(env)) { + return NULL; + } + if (!enum_values_array) { + throw_exception(env, ASSERTION_ERROR, "%s.values() returned null", enum_class); + return NULL; + } + + jsize enum_values_count = (*env)->GetArrayLength(env, enum_values_array); + + jobject result = NULL; + + for (jsize i = 0; i < enum_values_count; i++) { + jobject value_object = (*env)->GetObjectArrayElement(env, enum_values_array, i); + if (!value_object) { + continue; + } + + jint value_code = (*env)->GetIntField(env, value_object, value_field); + if (value_code == enum_value) { + result = value_object; + break; + } + + (*env)->DeleteLocalRef(env, value_object); + } + + (*env)->DeleteLocalRef(env, enum_values_array); + + if (!result) { + throw_exception(env, ASSERTION_ERROR, "Missing constant for value %d in %s enum", + enum_value, enum_class); + return NULL; + } + + return result; +} + +bool read_bool_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, int* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "Z"); + if (!jfid) { + return false; + } + + *result = (*env)->GetBooleanField(env, jobj, jfid) == JNI_TRUE; + return true; +} + +bool read_int_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, int* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "I"); + if (!jfid) { + return false; + } + + *result = (int) (*env)->GetIntField(env, jobj, jfid); + return true; +} + +bool read_uint_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, unsigned int* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "I"); + if (!jfid) { + return false; + } + + jint jfval = (*env)->GetIntField(env, jobj, jfid); + if (jfval < 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid %s.%s: most not be negative", + class_name, field_name); + return false; + } + + *result = (unsigned int) jfval; + return true; +} + +bool read_long_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, long long* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "J"); + if (!jfid) { + return false; + } + + *result = (long long) (*env)->GetLongField(env, jobj, jfid); + return true; +} + +bool read_ulong_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, unsigned long long* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "J"); + if (!jfid) { + return false; + } + + jlong jfval = (*env)->GetIntField(env, jobj, jfid); + if (jfval < 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid %s.%s: must not be negative", + class_name, field_name); + return false; + } + + *result = (unsigned long long) jfval; + return true; +} + +bool read_signed_duration_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, long long* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "java/time/Duration"); + if (!jfid) { + return false; + } + + jclass jduration_class = find_class(env, "java/time/Duration"); + if (!jduration_class) { + return false; + } + + jmethodID jto_nanos_method = find_method(env, jduration_class, "Duration", "toNanos", "()J"); + if (!jto_nanos_method) { + return false; + } + + jobject duration_obj = (*env)->GetObjectField(env, jobj, jfid); + if (!duration_obj) { + // treat null as zero value + *result = 0; + return true; + } + + jlong jfval = (*env)->CallLongMethod(env, duration_obj, jto_nanos_method); + if ((*env)->ExceptionCheck(env)) { + return false; + } + + *result = (long long) jfval; + return true; +} + +bool read_unsigned_duration_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, unsigned long long* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(result); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "java/time/Duration"); + if (!jfid) { + return false; + } + + jclass jduration_class = find_class(env, "java/time/Duration"); + if (!jduration_class) { + return false; + } + + jmethodID jto_nanos_method = find_method(env, jduration_class, "Duration", "toNanos", "()J"); + if (!jto_nanos_method) { + return false; + } + + jobject duration_obj = (*env)->GetObjectField(env, jobj, jfid); + if (!duration_obj) { + // treat null as zero value + *result = 0; + return true; + } + + jlong jfval = (*env)->CallLongMethod(env, duration_obj, jto_nanos_method); + if ((*env)->ExceptionCheck(env)) { + return false; + } + if (jfval < 0) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid %s.%s: must not be negative", + class_name, field_name); + return false; + } + + *result = (unsigned long long) jfval; + return true; +} + +bool read_object_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, const char* field_type, jobject* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(field_type); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, field_type); + if (!jfid) { + return false; + } + + *result = (*env)->GetObjectField(env, jobj, jfid); + return true; +} + +bool read_string_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, char* buf, size_t bufsz) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(field_name); + assert(buf); + assert(bufsz > 1); + + jfieldID jfid = find_field(env, jobj_class, class_name, field_name, "java/lang/String"); + if (!jfid) { + return false; + } + + jstring jfval = (*env)->GetObjectField(env, jobj, jfid); + if (!jfval) { + // treat null as empty string + buf[0] = '\0'; + return true; + } + + const char* str = (*env)->GetStringUTFChars(env, jfval, 0); + if (!str) { + throw_exception(env, ASSERTION_ERROR, "GetStringUTFChars() failed"); + return false; + } + if (strlen(str) >= bufsz) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid %s.%s: must not exceed %lu bytes", + class_name, field_name, (unsigned long) bufsz - 1); + (*env)->ReleaseStringUTFChars(env, jfval, str); + return false; + } + + strcpy(buf, str); + (*env)->ReleaseStringUTFChars(env, jfval, str); + return true; +} + +bool read_enum_field(JNIEnv* env, jclass jobj_class, jobject jobj, const char* class_name, + const char* field_name, const char* field_type, int* result) { + assert(env); + assert(jobj_class); + assert(jobj); + assert(class_name); + assert(field_name); + assert(field_type); + assert(result); + + jfieldID jobject_fid = find_field(env, jobj_class, class_name, field_name, field_type); + if (!jobject_fid) { + return false; + } + + jclass jenum_class = find_class(env, field_type); + if (!jenum_class) { + return false; + } + + jfieldID jenum_value_fid = find_field(env, jenum_class, field_type, "value", "I"); + if (!jenum_value_fid) { + return false; + } + + jobject jenum_object = (*env)->GetObjectField(env, jobj, jobject_fid); + if (!jenum_object) { + // treat null as zero value + *result = 0; + return true; + } + + *result = (int) (*env)->GetIntField(env, jenum_object, jenum_value_fid); + return true; +} diff --git a/roc_jni/src/main/impl/helpers.h b/roc_jni/src/main/impl/helpers.h new file mode 100644 index 00000000..d97a9252 --- /dev/null +++ b/roc_jni/src/main/impl/helpers.h @@ -0,0 +1,54 @@ +#pragma once + +#include "platform.h" + +#include + +#include +#include + +#define JNI_VERSION JNI_VERSION_1_6 + +ATTR_PRINTF(3, 4) +void throw_exception(JNIEnv* env, const char* exception, const char* message, ...); + +ATTR_NODISCARD jclass find_class(JNIEnv* env, const char* class_name); + +ATTR_NODISCARD jmethodID find_method(JNIEnv* env, jclass jclass, const char* class_name, + const char* method_name, const char* method_sig); + +ATTR_NODISCARD jfieldID find_field(JNIEnv* env, jclass jclass, const char* class_name, + const char* field_name, const char* field_type); + +ATTR_NODISCARD jobject find_enum_constant( + JNIEnv* env, jclass jclass, const char* enum_class, int enum_value); + +ATTR_NODISCARD bool read_bool_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, int* result); + +ATTR_NODISCARD bool read_int_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, int* result); + +ATTR_NODISCARD bool read_uint_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, unsigned int* result); + +ATTR_NODISCARD bool read_long_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, long long* result); + +ATTR_NODISCARD bool read_ulong_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, unsigned long long* result); + +ATTR_NODISCARD bool read_signed_duration_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, long long* result); + +ATTR_NODISCARD bool read_unsigned_duration_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, unsigned long long* result); + +ATTR_NODISCARD bool read_object_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, const char* field_type, jobject* result); + +ATTR_NODISCARD bool read_string_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, char* buf, size_t bufsz); + +ATTR_NODISCARD bool read_enum_field(JNIEnv* env, jclass jobj_class, jobject jobj, + const char* class_name, const char* field_name, const char* field_type, int* result); diff --git a/roc_jni/src/main/impl/interface_config.c b/roc_jni/src/main/impl/interface_config.c index 10c78861..9737f6a7 100644 --- a/roc_jni/src/main/impl/interface_config.c +++ b/roc_jni/src/main/impl/interface_config.c @@ -1,54 +1,40 @@ #include "interface_config.h" -#include "common.h" +#include "exceptions.h" +#include "helpers.h" +#include "package.h" -int interface_config_unmarshal(JNIEnv* env, roc_interface_config* config, jobject jconfig) { - jclass interfaceConfigClass = NULL; - jstring jOutgoingAddress = NULL; - const char* outgoingAddress = NULL; - jstring jMulticastGroup = NULL; - const char* multicastGroup = NULL; - int err = 0; +#include +#include - interfaceConfigClass = (*env)->FindClass(env, INTERFACE_CONFIG_CLASS); - assert(interfaceConfigClass != NULL); +bool interface_config_unmarshal(JNIEnv* env, jobject jconfig, roc_interface_config* result) { + assert(env); + assert(jconfig); + assert(result); - // set all fields to zero - assert(config != NULL); - memset(config, 0, sizeof(*config)); + memset(result, 0, sizeof(*result)); + + jclass jclass = find_class(env, INTERFACE_CONFIG_CLASS); + if (!jclass) { + return false; + } // outgoing_address - jOutgoingAddress = (jstring) get_object_field( - env, interfaceConfigClass, jconfig, "outgoingAddress", "Ljava/lang/String;"); - if (jOutgoingAddress != NULL) { - outgoingAddress = (*env)->GetStringUTFChars(env, jOutgoingAddress, 0); - if (!outgoingAddress || strlen(outgoingAddress) >= sizeof(config->outgoing_address)) { - err = -1; - goto out; - } - strcpy(config->outgoing_address, outgoingAddress); + if (!read_string_field(env, jclass, jconfig, INTERFACE_CONFIG_CLASS, "outgoingAddress", + result->outgoing_address, sizeof(result->outgoing_address))) { + return false; } // multicast_group - jMulticastGroup = (jstring) get_object_field( - env, interfaceConfigClass, jconfig, "multicastGroup", "Ljava/lang/String;"); - if (jMulticastGroup != NULL) { - multicastGroup = (*env)->GetStringUTFChars(env, jMulticastGroup, 0); - if (!multicastGroup || strlen(multicastGroup) >= sizeof(config->multicast_group)) { - err = -1; - goto out; - } - strcpy(config->multicast_group, multicastGroup); + if (!read_string_field(env, jclass, jconfig, INTERFACE_CONFIG_CLASS, "multicastGroup", + result->multicast_group, sizeof(result->multicast_group))) { + return false; } // reuse_address - config->reuse_address - = get_boolean_field_value(env, interfaceConfigClass, jconfig, "reuseAddress", &err); - if (err) goto out; - -out: - if (outgoingAddress != NULL) - (*env)->ReleaseStringUTFChars(env, jOutgoingAddress, outgoingAddress); - if (multicastGroup != NULL) (*env)->ReleaseStringUTFChars(env, jMulticastGroup, multicastGroup); + if (!read_bool_field( + env, jclass, jconfig, INTERFACE_CONFIG_CLASS, "reuseAddress", &result->reuse_address)) { + return false; + } - return err; + return true; } diff --git a/roc_jni/src/main/impl/interface_config.h b/roc_jni/src/main/impl/interface_config.h index 5899676b..add119fa 100644 --- a/roc_jni/src/main/impl/interface_config.h +++ b/roc_jni/src/main/impl/interface_config.h @@ -1,11 +1,12 @@ #pragma once -#include - -#include "common.h" +#include "helpers.h" +#include "platform.h" +#include #include -#define INTERFACE_CONFIG_CLASS PACKAGE_BASE_NAME "/InterfaceConfig" +#include -int interface_config_unmarshal(JNIEnv* env, roc_interface_config* config, jobject jconfig); +ATTR_NODISCARD bool interface_config_unmarshal( + JNIEnv* env, jobject jconfig, roc_interface_config* result); diff --git a/roc_jni/src/main/impl/logger.c b/roc_jni/src/main/impl/logger.c index e8bc8d83..0a7862b6 100644 --- a/roc_jni/src/main/impl/logger.c +++ b/roc_jni/src/main/impl/logger.c @@ -1,111 +1,146 @@ #include "org_rocstreaming_roctoolkit_RocLogger.h" -#include "common.h" +#include "exceptions.h" +#include "helpers.h" +#include "package.h" #include +#include #include +#include -#define LOG_LEVEL_CLASS PACKAGE_BASE_NAME "/RocLogLevel" +#define MAX_LEVELS 16 -static pthread_mutex_t logMutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t logger_mutex = PTHREAD_MUTEX_INITIALIZER; static struct { JavaVM* vm; - jclass levelClass; - jobject handlerObject; - jmethodID handlerMethod; -} logState = { 0 }; - -static const char* logLevelMapping(roc_log_level level) { - const char* ret = "ERROR"; + jclass level_class; + jobject level_objects[MAX_LEVELS]; + jclass handler_class; + jobject handler_object; + jmethodID handler_method; +} logger_state; + +static const char* map_log_level(roc_log_level level) { switch (level) { case ROC_LOG_NONE: - ret = "NONE"; - break; + return "NONE"; case ROC_LOG_ERROR: - ret = "ERROR"; - break; + return "ERROR"; case ROC_LOG_INFO: - ret = "INFO"; - break; + return "INFO"; case ROC_LOG_DEBUG: - ret = "DEBUG"; - break; + return "DEBUG"; case ROC_LOG_TRACE: - ret = "TRACE"; - break; + return "TRACE"; default: break; } - return ret; + return NULL; }; JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + assert(vm); + + pthread_mutex_lock(&logger_mutex); + JNIEnv* env = NULL; - jclass levelClass = NULL; jint result = JNI_VERSION; - - pthread_mutex_lock(&logMutex); + jclass level_class; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION) != JNI_OK) { result = JNI_ERR; goto out; } - assert(env != NULL); + level_class = (*env)->FindClass(env, LOG_LEVEL_CLASS); + if (!level_class) { + goto out; + } - levelClass = (*env)->FindClass(env, LOG_LEVEL_CLASS); - assert(levelClass != NULL); + logger_state.level_class = (jclass) (*env)->NewGlobalRef(env, level_class); - logState.levelClass = (jclass) (*env)->NewGlobalRef(env, levelClass); - assert(logState.levelClass != NULL); + for (int i = 0; i < MAX_LEVELS; i++) { + const char* level_field_name = map_log_level((roc_log_level) i); + if (!level_field_name) { + continue; + } + jfieldID level_field_id + = (*env)->GetStaticFieldID(env, level_class, level_field_name, "L" LOG_LEVEL_CLASS ";"); + if (!level_field_id) { + continue; + } + jobject level_object = (*env)->GetStaticObjectField(env, level_class, level_field_id); + if (!level_object) { + continue; + } + logger_state.level_objects[i] = (*env)->NewGlobalRef(env, level_object); + } out: - pthread_mutex_unlock(&logMutex); + pthread_mutex_unlock(&logger_mutex); return result; } JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) { + assert(vm); + JNIEnv* env = NULL; - pthread_mutex_lock(&logMutex); + pthread_mutex_lock(&logger_mutex); + + (*logger_state.vm)->GetEnv(vm, (void**) &env, JNI_VERSION); - (*logState.vm)->GetEnv(vm, (void**) &env, JNI_VERSION); + if (logger_state.level_class) { + (*env)->DeleteGlobalRef(env, logger_state.level_class); + } + for (int i = 0; i < MAX_LEVELS; i++) { + if (logger_state.level_objects[i]) { + (*env)->DeleteGlobalRef(env, logger_state.level_objects[i]); + } + } - assert(env != NULL); - (*env)->DeleteGlobalRef(env, logState.levelClass); + if (logger_state.handler_class) { + (*env)->DeleteGlobalRef(env, logger_state.handler_class); + } + if (logger_state.handler_object) { + (*env)->DeleteGlobalRef(env, logger_state.handler_object); + } - logState.vm = NULL; - logState.handlerObject = NULL; - logState.handlerMethod = NULL; + memset(&logger_state, 0, sizeof(logger_state)); - pthread_mutex_unlock(&logMutex); + pthread_mutex_unlock(&logger_mutex); } static void logger_handler(const roc_log_message* message, void* argument) { + assert(message); + JNIEnv* env = NULL; - jfieldID levelFieldID = NULL; - jobject msgLevel = NULL; - jstring msgModule = NULL; - jstring msgText = NULL; jint result = 0; int detach = 0; - pthread_mutex_lock(&logMutex); + jobject msg_level = NULL; + jstring msg_module = NULL; + jstring msg_text = NULL; - if (logState.vm == NULL || logState.handlerObject == NULL || logState.handlerMethod == NULL) { + pthread_mutex_lock(&logger_mutex); + + if (logger_state.vm == NULL || logger_state.level_class == NULL + || logger_state.handler_class == NULL || logger_state.handler_object == NULL + || logger_state.handler_method == NULL) { goto out; } // check if it is needed to attach current thread - if ((result = (*logState.vm)->GetEnv(logState.vm, (void**) &env, JNI_VERSION)) + if ((result = (*logger_state.vm)->GetEnv(logger_state.vm, (void**) &env, JNI_VERSION)) == JNI_EDETACHED) { #ifdef __ANDROID__ - if ((*logState.vm)->AttachCurrentThread(logState.vm, &env, 0) == JNI_OK) + if ((*logger_state.vm)->AttachCurrentThread(logger_state.vm, &env, 0) == JNI_OK) #else - if ((*logState.vm)->AttachCurrentThread(logState.vm, (void**) &env, 0) == JNI_OK) + if ((*logger_state.vm)->AttachCurrentThread(logger_state.vm, (void**) &env, 0) == JNI_OK) #endif detach = 1; else { @@ -117,55 +152,59 @@ static void logger_handler(const roc_log_message* message, void* argument) { goto out; } - assert(env != NULL); - - levelFieldID = (*env)->GetStaticFieldID( - env, logState.levelClass, logLevelMapping(message->level), "L" LOG_LEVEL_CLASS ";"); - assert(levelFieldID != NULL); + assert(env); - msgLevel = (*env)->GetStaticObjectField(env, logState.levelClass, levelFieldID); - assert(msgLevel != NULL); + if (message->level < 0 || message->level >= MAX_LEVELS) goto out; + msg_level = logger_state.level_objects[message->level]; + if (!msg_level) goto out; - msgModule = (*env)->NewStringUTF(env, message->module); - assert(msgModule != NULL); + msg_module = (*env)->NewStringUTF(env, message->module); + if (!msg_module) goto out; - msgText = (*env)->NewStringUTF(env, message->text); - assert(msgText != NULL); + msg_text = (*env)->NewStringUTF(env, message->text); + if (!msg_text) goto out; - (*env)->CallVoidMethod( - env, logState.handlerObject, logState.handlerMethod, msgLevel, msgModule, msgText); + (*env)->CallVoidMethod(env, logger_state.handler_object, logger_state.handler_method, msg_level, + msg_module, msg_text); +out: if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); } -out: if (detach) { - (*logState.vm)->DetachCurrentThread(logState.vm); + (*logger_state.vm)->DetachCurrentThread(logger_state.vm); } - pthread_mutex_unlock(&logMutex); + pthread_mutex_unlock(&logger_mutex); } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetLevel( - JNIEnv* env, jclass clazz, jobject jlevel) { + JNIEnv* env, jclass jlogger_class, jobject jlevel) { + assert(env); + + jfieldID value_field = NULL; roc_log_level level = (roc_log_level) 0; - int success = 0; + bool success = false; - pthread_mutex_lock(&logMutex); + pthread_mutex_lock(&logger_mutex); + + if (!jlevel) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocLogLevel: must not be null"); + goto out; + } - if (jlevel == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "no logger level provided"); + value_field = (*env)->GetFieldID(env, logger_state.level_class, "value", "I"); + if (!value_field) { goto out; } - level = (roc_log_level) get_enum_value(env, logState.levelClass, jlevel); - success = 1; + level = (roc_log_level) (*env)->GetIntField(env, jlevel, value_field); + success = true; out: - pthread_mutex_unlock(&logMutex); + pthread_mutex_unlock(&logger_mutex); if (success) { roc_log_set_level(level); @@ -173,40 +212,56 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetLevel } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocLogger_nativeSetHandler( - JNIEnv* env, jclass clazz, jobject jhandler) { - jclass handlerClass = NULL; - jmethodID handlerMethod = NULL; - int success = 0; + JNIEnv* env, jclass jlogger_class, jobject jhandler) { + assert(env); + + jclass handler_class = NULL; + jmethodID handler_method = NULL; + bool success = false; + + pthread_mutex_lock(&logger_mutex); - pthread_mutex_lock(&logMutex); + if (!jhandler) { + success = true; + goto out; + } - if (jhandler == NULL) { - roc_log_set_handler(NULL, NULL); + handler_class = (*env)->GetObjectClass(env, jhandler); + if (!handler_class) { goto out; } - handlerClass = (*env)->GetObjectClass(env, jhandler); - assert(handlerClass != NULL); + handler_method = (jmethodID) (*env)->GetMethodID( + env, handler_class, "log", "(L" LOG_LEVEL_CLASS ";Ljava/lang/String;Ljava/lang/String;)V"); + if (!handler_method) { + goto out; + } - handlerMethod = (jmethodID) (*env)->GetMethodID( - env, handlerClass, "log", "(L" LOG_LEVEL_CLASS ";Ljava/lang/String;Ljava/lang/String;)V"); - assert(handlerMethod != NULL); + if (logger_state.vm == NULL) { + (*env)->GetJavaVM(env, &logger_state.vm); + } - if (logState.vm == NULL) { - (*env)->GetJavaVM(env, &logState.vm); + if (logger_state.handler_class != NULL) { + (*env)->DeleteGlobalRef(env, logger_state.handler_class); } - if (logState.handlerObject != NULL) { - (*env)->DeleteGlobalRef(env, logState.handlerObject); + if (logger_state.handler_object != NULL) { + (*env)->DeleteGlobalRef(env, logger_state.handler_object); } - logState.handlerObject = (jobject) (*env)->NewGlobalRef(env, jhandler); - logState.handlerMethod = handlerMethod; - success = 1; + logger_state.handler_class = (jclass) (*env)->NewGlobalRef(env, handler_class); + logger_state.handler_object = (*env)->NewGlobalRef(env, jhandler); + logger_state.handler_method = handler_method; + + success = true; out: - pthread_mutex_unlock(&logMutex); + pthread_mutex_unlock(&logger_mutex); if (success) { - roc_log_set_handler(logger_handler, NULL); + if (jhandler) { + roc_log_set_handler(logger_handler, NULL); + } else { + roc_log_set_handler(NULL, NULL); + } } } diff --git a/roc_jni/src/main/impl/media_encoding.c b/roc_jni/src/main/impl/media_encoding.c index 335f220e..97fa1026 100644 --- a/roc_jni/src/main/impl/media_encoding.c +++ b/roc_jni/src/main/impl/media_encoding.c @@ -1,36 +1,48 @@ #include "media_encoding.h" -#include "channel_layout.h" -#include "common.h" -#include "format.h" +#include "exceptions.h" +#include "helpers.h" +#include "package.h" -int media_encoding_unmarshal(JNIEnv* env, roc_media_encoding* encoding, jobject jencoding) { - jclass mediaEncodingClass = NULL; - jobject jobj = NULL; - int err = 0; +#include +#include - mediaEncodingClass = (*env)->FindClass(env, MEDIA_ENCODING_CLASS); - assert(mediaEncodingClass != NULL); +bool media_encoding_unmarshal(JNIEnv* env, jobject jencoding, roc_media_encoding* result) { + assert(env); + assert(jencoding); + assert(result); - // set all fields to zeros - assert(encoding != NULL); - memset(encoding, 0, sizeof(*encoding)); + memset(result, 0, sizeof(*result)); + + jclass jclass = find_class(env, MEDIA_ENCODING_CLASS); + if (!jclass) { + return false; + } + + int enum_value = 0; // rate - encoding->rate = get_int_field_value(env, mediaEncodingClass, jencoding, "rate", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jencoding, MEDIA_ENCODING_CLASS, "rate", &result->rate)) { + return false; + } // format - jobj = get_object_field(env, mediaEncodingClass, jencoding, "format", "L" FORMAT_CLASS ";"); - if (jobj != NULL) encoding->format = get_format(env, jobj); + if (!read_enum_field( + env, jclass, jencoding, MEDIA_ENCODING_CLASS, "format", FORMAT_CLASS, &enum_value)) { + return false; + } + result->format = (roc_format) enum_value; // channels - jobj = get_object_field( - env, mediaEncodingClass, jencoding, "channels", "L" CHANNEL_LAYOUT_CLASS ";"); - if (jobj != NULL) encoding->channels = get_channel_layout(env, jobj); + if (!read_enum_field(env, jclass, jencoding, MEDIA_ENCODING_CLASS, "channels", + CHANNEL_LAYOUT_CLASS, &enum_value)) { + return false; + } + result->channels = (roc_channel_layout) enum_value; // tracks - encoding->tracks = get_int_field_value(env, mediaEncodingClass, jencoding, "tracks", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jencoding, MEDIA_ENCODING_CLASS, "tracks", &result->tracks)) { + return false; + } - return 0; + return true; } diff --git a/roc_jni/src/main/impl/media_encoding.h b/roc_jni/src/main/impl/media_encoding.h index 6c7586e8..ae93e218 100644 --- a/roc_jni/src/main/impl/media_encoding.h +++ b/roc_jni/src/main/impl/media_encoding.h @@ -1,11 +1,11 @@ #pragma once -#include - -#include "common.h" +#include "platform.h" +#include #include -#define MEDIA_ENCODING_CLASS PACKAGE_BASE_NAME "/MediaEncoding" +#include -int media_encoding_unmarshal(JNIEnv* env, roc_media_encoding* encoding, jobject jencoding); +ATTR_NODISCARD bool media_encoding_unmarshal( + JNIEnv* env, jobject jencoding, roc_media_encoding* result); diff --git a/roc_jni/src/main/impl/package.h b/roc_jni/src/main/impl/package.h new file mode 100644 index 00000000..4e023a63 --- /dev/null +++ b/roc_jni/src/main/impl/package.h @@ -0,0 +1,21 @@ +#pragma once + +#define PACKAGE_NAME "org/rocstreaming/roctoolkit" + +#define CHANNEL_LAYOUT_CLASS PACKAGE_NAME "/ChannelLayout" +#define CLOCK_SOURCE_CLASS PACKAGE_NAME "/ClockSource" +#define CLOCK_SYNC_BACKEND_CLASS PACKAGE_NAME "/ClockSyncBackend" +#define CLOCK_SYNC_PROFILE_CLASS PACKAGE_NAME "/ClockSyncProfile" +#define CONTEXT_CONFIG_CLASS PACKAGE_NAME "/RocContextConfig" +#define ENDPOINT_CLASS PACKAGE_NAME "/Endpoint" +#define FEC_ENCODING_CLASS PACKAGE_NAME "/FecEncoding" +#define FORMAT_CLASS PACKAGE_NAME "/Format" +#define INTERFACE_CONFIG_CLASS PACKAGE_NAME "/InterfaceConfig" +#define LOG_LEVEL_CLASS PACKAGE_NAME "/RocLogLevel" +#define MEDIA_ENCODING_CLASS PACKAGE_NAME "/MediaEncoding" +#define PACKET_ENCODING_CLASS PACKAGE_NAME "/PacketEncoding" +#define PROTOCOL_CLASS PACKAGE_NAME "/Protocol" +#define RECEIVER_CONFIG_CLASS PACKAGE_NAME "/RocReceiverConfig" +#define RESAMPLER_BACKEND_CLASS PACKAGE_NAME "/ResamplerBackend" +#define RESAMPLER_PROFILE_CLASS PACKAGE_NAME "/ResamplerProfile" +#define SENDER_CONFIG_CLASS PACKAGE_NAME "/RocSenderConfig" diff --git a/roc_jni/src/main/impl/packet_encoding.c b/roc_jni/src/main/impl/packet_encoding.c deleted file mode 100644 index 8c407105..00000000 --- a/roc_jni/src/main/impl/packet_encoding.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "packet_encoding.h" -#include "common.h" - -roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacketEncoding) { - jclass packetEncodingClass = NULL; - - packetEncodingClass = (*env)->FindClass(env, PACKET_ENCODING_CLASS); - assert(packetEncodingClass != NULL); - - return (roc_packet_encoding) get_enum_value(env, packetEncodingClass, jpacketEncoding); -} diff --git a/roc_jni/src/main/impl/packet_encoding.h b/roc_jni/src/main/impl/packet_encoding.h deleted file mode 100644 index 09064866..00000000 --- a/roc_jni/src/main/impl/packet_encoding.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define PACKET_ENCODING_CLASS PACKAGE_BASE_NAME "/PacketEncoding" - -roc_packet_encoding get_packet_encoding(JNIEnv* env, jobject jpacketEncoding); diff --git a/roc_jni/src/main/impl/platform.h b/roc_jni/src/main/impl/platform.h new file mode 100644 index 00000000..f64aed55 --- /dev/null +++ b/roc_jni/src/main/impl/platform.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __GNUC__ +#define ATTR_PRINTF(n, m) __attribute__((format(printf, n, m))) +#else +#define ATTR_PRINTF(n, m) +#endif + +#ifdef __GNUC__ +#define ATTR_NODISCARD __attribute__((__warn_unused_result__)) +#else +#define ATTR_NODISCARD +#endif diff --git a/roc_jni/src/main/impl/protocol.c b/roc_jni/src/main/impl/protocol.c deleted file mode 100644 index 0688ac15..00000000 --- a/roc_jni/src/main/impl/protocol.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "protocol.h" -#include "common.h" - -roc_protocol get_protocol(JNIEnv* env, jobject jprotocol) { - jclass protocolClass = NULL; - - protocolClass = (*env)->FindClass(env, PROTOCOL_CLASS); - assert(protocolClass != NULL); - - return (roc_protocol) get_enum_value(env, protocolClass, jprotocol); -} - -jobject get_protocol_enum(JNIEnv* env, roc_protocol protocol) { - jclass protocolUtilsClass = NULL; - jobject jprotocol = NULL; - jmethodID getProtocolMethodID = NULL; - - protocolUtilsClass = (*env)->FindClass(env, PROTOCOL_UTILS_CLASS); - assert(protocolUtilsClass != NULL); - - getProtocolMethodID - = (*env)->GetStaticMethodID(env, protocolUtilsClass, "getByValue", "(I)L" PROTOCOL_CLASS ";"); - assert(getProtocolMethodID != NULL); - - jprotocol = (jobject) (*env)->CallStaticObjectMethod( - env, protocolUtilsClass, getProtocolMethodID, (jint) protocol); - assert(jprotocol != NULL); - return jprotocol; -} diff --git a/roc_jni/src/main/impl/protocol.h b/roc_jni/src/main/impl/protocol.h deleted file mode 100644 index 9eefce21..00000000 --- a/roc_jni/src/main/impl/protocol.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define PROTOCOL_CLASS PACKAGE_BASE_NAME "/Protocol" -#define PROTOCOL_UTILS_CLASS PACKAGE_BASE_NAME "/ProtocolUtils" - -roc_protocol get_protocol(JNIEnv* env, jobject jprotocol); - -jobject get_protocol_enum(JNIEnv* env, roc_protocol protocol); diff --git a/roc_jni/src/main/impl/receiver.c b/roc_jni/src/main/impl/receiver.c index b63ee1dd..290068b2 100644 --- a/roc_jni/src/main/impl/receiver.c +++ b/roc_jni/src/main/impl/receiver.c @@ -1,30 +1,47 @@ #include "org_rocstreaming_roctoolkit_RocReceiver.h" -#include "common.h" #include "endpoint.h" +#include "exceptions.h" +#include "helpers.h" #include "interface_config.h" #include "receiver_config.h" #include +#include +#include + JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeOpen( - JNIEnv* env, jclass receiverClass, jlong contextPtr, jobject jconfig) { - roc_context* context = NULL; - roc_receiver_config receiverConfig = {}; + JNIEnv* env, jclass jclass, jlong jcontext, jobject jconfig) { + assert(env); + + roc_context* context = (roc_context*) jcontext; + roc_receiver_config receiver_config = {}; roc_receiver* receiver = NULL; - context = (roc_context*) contextPtr; + if (!jcontext) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContext: must not be null"); + goto out; + } - if (receiver_config_unmarshal(env, &receiverConfig, jconfig) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + if (!jconfig) { + throw_exception( + env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiverConfig: must not be null"); goto out; } - if ((roc_receiver_open(context, &receiverConfig, &receiver)) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error opening receiver"); - receiver = NULL; + if (!receiver_config_unmarshal(env, jconfig, &receiver_config)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiverConfig"); + goto out; + } + + if (roc_receiver_open(context, &receiver_config, &receiver) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to open RocReceiver"); + goto out; + } + + if (!receiver) { + throw_exception(env, ASSERTION_ERROR, "RocReceiver is null"); goto out; } @@ -33,39 +50,52 @@ JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeOpen( } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeClose( - JNIEnv* env, jclass receiverClass, jlong receiverPtr) { + JNIEnv* env, jclass jclass, jlong jreceiver) { + assert(env); - roc_receiver* receiver = (roc_receiver*) receiverPtr; + roc_receiver* receiver = (roc_receiver*) jreceiver; + + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } if (roc_receiver_close(receiver) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error closing receiver"); + throw_exception(env, ASSERTION_ERROR, "Failed to close RocReceiver"); + goto out; } + +out: + return; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeConfigure( - JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot, jint interface, jobject jconfig) { - roc_receiver* receiver = NULL; - roc_interface_config config = {}; + JNIEnv* env, jobject jobj, jlong jreceiver, jint jslot, jint jinterface, jobject jconfig) { + assert(env); + + roc_receiver* receiver = (roc_receiver*) jreceiver; + roc_interface_config interface_config = {}; - receiver = (roc_receiver*) receiverPtr; + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } - if (jconfig == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + if (!jconfig) { + throw_exception( + env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid InterfaceConfig: must not be null"); goto out; } - if (interface_config_unmarshal(env, &config, jconfig) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling config"); + if (!interface_config_unmarshal(env, jconfig, &interface_config)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid InterfaceConfig"); goto out; } - if (roc_receiver_configure(receiver, (roc_slot) slot, (roc_interface) interface, &config) + if (roc_receiver_configure( + receiver, (roc_slot) jslot, (roc_interface) jinterface, &interface_config) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error configuring receiver"); + throw_exception(env, ROC_EXCEPTION, "Failed to configure RocReceiver interface"); goto out; } @@ -74,46 +104,62 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeConfig } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeBind( - JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot, jint interface, jobject jendpoint) { - roc_receiver* receiver = NULL; + JNIEnv* env, jobject jobj, jlong jreceiver, jint jslot, jint jinterface, jobject jendpoint) { + assert(env); + + roc_receiver* receiver = (roc_receiver*) jreceiver; roc_endpoint* endpoint = NULL; int port = 0; - receiver = (roc_receiver*) receiverPtr; + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } - if (endpoint_unmarshal(env, &endpoint, jendpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad endpoint argument"); + if (!jendpoint) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid Endpoint: must not be null"); goto out; } - if (roc_receiver_bind(receiver, (roc_slot) slot, (roc_interface) interface, endpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error binding receiver"); + if (!endpoint_unmarshal(env, jendpoint, &endpoint)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid Endpoint"); goto out; } - if (roc_endpoint_get_port(endpoint, &port) == 0) { - endpoint_set_port(env, jendpoint, port); - } else { - endpoint_set_port(env, jendpoint, -1); + if (roc_receiver_bind(receiver, (roc_slot) jslot, (roc_interface) jinterface, endpoint) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to bind RocReceiver endpoint"); + goto out; + } + + if (roc_endpoint_get_port(endpoint, &port) != 0) { + throw_exception(env, ASSERTION_ERROR, "Failed to read RocReceiver endpoint"); + goto out; + } + + if (!endpoint_set_port(env, jendpoint, port)) { + throw_exception(env, ASSERTION_ERROR, "Failed to write RocReceiver endpoint"); + goto out; } out: - if (endpoint != NULL) { + if (endpoint) { roc_endpoint_deallocate(endpoint); } } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeUnlink( - JNIEnv* env, jobject thisObj, jlong receiverPtr, jint slot) { - roc_receiver* receiver = NULL; + JNIEnv* env, jobject jobj, jlong jreceiver, jint jslot) { + assert(env); - receiver = (roc_receiver*) receiverPtr; + roc_receiver* receiver = (roc_receiver*) jreceiver; - if (roc_receiver_unlink(receiver, (roc_slot) slot) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error unlinking slot"); + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } + + if (roc_receiver_unlink(receiver, (roc_slot) jslot) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to unlink RocReceiver slot"); goto out; } @@ -122,33 +168,38 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeUnlink } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocReceiver_nativeReadFloats( - JNIEnv* env, jobject thisObj, jlong receiverPtr, jfloatArray jsamples) { - roc_receiver* receiver = NULL; + JNIEnv* env, jobject jobj, jlong jreceiver, jfloatArray jsamples) { + assert(env); + + roc_receiver* receiver = (roc_receiver*) jreceiver; jfloat* samples = NULL; - jsize len = 0; + jsize samples_count = 0; roc_frame frame = {}; - receiver = (roc_receiver*) receiverPtr; + if (!jreceiver) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocReceiver: must not be null"); + goto out; + } - if (jsamples == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad samples argument"); + if (!jsamples) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid samples array: must not be null"); goto out; } + samples = (*env)->GetFloatArrayElements(env, jsamples, 0); - len = (*env)->GetArrayLength(env, jsamples); - assert(samples != NULL); + samples_count = (*env)->GetArrayLength(env, jsamples); memset(&frame, 0, sizeof(frame)); frame.samples = samples; - frame.samples_size = len * sizeof(float); + frame.samples_size = samples_count * sizeof(float); if (roc_receiver_read(receiver, &frame) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error reading frame"); + throw_exception(env, ROC_EXCEPTION, "Failed to read frame from RocReceiver"); goto out; } out: - if (samples != NULL) (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); + if (samples) { + (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); + } } diff --git a/roc_jni/src/main/impl/receiver_config.c b/roc_jni/src/main/impl/receiver_config.c index 8af371b2..a90b63dd 100644 --- a/roc_jni/src/main/impl/receiver_config.c +++ b/roc_jni/src/main/impl/receiver_config.c @@ -1,79 +1,96 @@ #include "receiver_config.h" -#include "clock_source.h" -#include "clock_sync_backend.h" -#include "clock_sync_profile.h" -#include "common.h" #include "endpoint.h" -#include "format.h" +#include "exceptions.h" +#include "helpers.h" #include "media_encoding.h" -#include "resampler_backend.h" -#include "resampler_profile.h" -int receiver_config_unmarshal(JNIEnv* env, roc_receiver_config* config, jobject jconfig) { - jclass receiverConfigClass = NULL; - jobject jobj = NULL; - int err = 0; +#include +#include - receiverConfigClass = (*env)->FindClass(env, RECEIVER_CONFIG_CLASS); - assert(receiverConfigClass != NULL); +bool receiver_config_unmarshal(JNIEnv* env, jobject jconfig, roc_receiver_config* result) { + assert(env); + assert(jconfig); + assert(result); - // set all fields to zeros - assert(config != NULL); - memset(config, 0, sizeof(*config)); + memset(result, 0, sizeof(*result)); + + jclass jclass = find_class(env, RECEIVER_CONFIG_CLASS); + if (!jclass) { + return false; + } + + jobject jencoding = NULL; + int enum_value = 0; // frame_encoding - jobj = get_object_field( - env, receiverConfigClass, jconfig, "frameEncoding", "L" MEDIA_ENCODING_CLASS ";"); - if (jobj != NULL) { - err = media_encoding_unmarshal(env, &config->frame_encoding, jobj); - if (err) return err; + if (!read_object_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "frameEncoding", + MEDIA_ENCODING_CLASS, &jencoding)) { + return false; + } + if (jencoding) { + if (!media_encoding_unmarshal(env, jencoding, &result->frame_encoding)) { + return false; + } } // clock_source - jobj = get_object_field( - env, receiverConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); - if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); + if (!read_enum_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "clockSource", + CLOCK_SOURCE_CLASS, &enum_value)) { + return false; + } + result->clock_source = (roc_clock_source) enum_value; // clock_sync_backend - jobj = get_object_field( - env, receiverConfigClass, jconfig, "clockSyncBackend", "L" CLOCK_SYNC_BACKEND_CLASS ";"); - if (jobj != NULL) config->clock_sync_backend = get_clock_sync_backend(env, jobj); + if (!read_enum_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "clockSyncBackend", + CLOCK_SYNC_BACKEND_CLASS, &enum_value)) { + return false; + } + result->clock_sync_backend = (roc_clock_sync_backend) enum_value; // clock_sync_profile - jobj = get_object_field( - env, receiverConfigClass, jconfig, "clockSyncProfile", "L" CLOCK_SYNC_PROFILE_CLASS ";"); - if (jobj != NULL) config->clock_sync_profile = get_clock_sync_profile(env, jobj); + if (!read_enum_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "clockSyncProfile", + CLOCK_SYNC_PROFILE_CLASS, &enum_value)) { + return false; + } + result->clock_sync_profile = (roc_clock_sync_profile) enum_value; // resampler_backend - jobj = get_object_field( - env, receiverConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); - if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); + if (!read_enum_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "resamplerBackend", + RESAMPLER_BACKEND_CLASS, &enum_value)) { + return false; + } + result->resampler_backend = (roc_resampler_backend) enum_value; // resampler_profile - jobj = get_object_field( - env, receiverConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); - if (jobj != NULL) - config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); + if (!read_enum_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "resamplerProfile", + RESAMPLER_PROFILE_CLASS, &enum_value)) { + return false; + } + result->resampler_profile = (roc_resampler_profile) enum_value; // target_latency - config->target_latency - = get_duration_field_value(env, receiverConfigClass, jconfig, "targetLatency", &err); - if (err) return err; + if (!read_unsigned_duration_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, "targetLatency", + &result->target_latency)) { + return false; + } // latency_tolerance - config->latency_tolerance - = get_duration_field_value(env, receiverConfigClass, jconfig, "latencyTolerance", &err); - if (err) return err; + if (!read_unsigned_duration_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, + "latencyTolerance", &result->latency_tolerance)) { + return false; + } // no_playback_timeout - config->no_playback_timeout - = get_duration_field_value(env, receiverConfigClass, jconfig, "noPlaybackTimeout", &err); - if (err) return err; + if (!read_signed_duration_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, + "noPlaybackTimeout", &result->no_playback_timeout)) { + return false; + } // choppy_playback_timeout - config->choppy_playback_timeout = get_duration_field_value( - env, receiverConfigClass, jconfig, "choppyPlaybackTimeout", &err); - if (err) return err; + if (!read_signed_duration_field(env, jclass, jconfig, RECEIVER_CONFIG_CLASS, + "choppyPlaybackTimeout", &result->choppy_playback_timeout)) { + return false; + } - return 0; + return true; } diff --git a/roc_jni/src/main/impl/receiver_config.h b/roc_jni/src/main/impl/receiver_config.h index dbfe9c31..888d4fb7 100644 --- a/roc_jni/src/main/impl/receiver_config.h +++ b/roc_jni/src/main/impl/receiver_config.h @@ -1,11 +1,11 @@ #pragma once -#include - -#include "common.h" +#include "platform.h" +#include #include -#define RECEIVER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocReceiverConfig" +#include -int receiver_config_unmarshal(JNIEnv* env, roc_receiver_config* config, jobject jconfig); +ATTR_NODISCARD bool receiver_config_unmarshal( + JNIEnv* env, jobject jconfig, roc_receiver_config* result); diff --git a/roc_jni/src/main/impl/resampler_backend.c b/roc_jni/src/main/impl/resampler_backend.c deleted file mode 100644 index be6b30b7..00000000 --- a/roc_jni/src/main/impl/resampler_backend.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "resampler_backend.h" -#include "common.h" - -#include - -roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresamplerBackend) { - jclass resamplerBackendClass = NULL; - - resamplerBackendClass = (*env)->FindClass(env, RESAMPLER_BACKEND_CLASS); - assert(resamplerBackendClass != NULL); - - return (roc_resampler_backend) get_enum_value(env, resamplerBackendClass, jresamplerBackend); -} diff --git a/roc_jni/src/main/impl/resampler_backend.h b/roc_jni/src/main/impl/resampler_backend.h deleted file mode 100644 index f0bd811e..00000000 --- a/roc_jni/src/main/impl/resampler_backend.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define RESAMPLER_BACKEND_CLASS PACKAGE_BASE_NAME "/ResamplerBackend" - -roc_resampler_backend get_resampler_backend(JNIEnv* env, jobject jresamplerBackend); diff --git a/roc_jni/src/main/impl/resampler_profile.c b/roc_jni/src/main/impl/resampler_profile.c deleted file mode 100644 index 74bd2385..00000000 --- a/roc_jni/src/main/impl/resampler_profile.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "resampler_profile.h" -#include "common.h" - -#include - -roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresamplerProfile) { - jclass resamplerProfileClass = NULL; - - resamplerProfileClass = (*env)->FindClass(env, RESAMPLER_PROFILE_CLASS); - assert(resamplerProfileClass != NULL); - - return (roc_resampler_profile) get_enum_value(env, resamplerProfileClass, jresamplerProfile); -} diff --git a/roc_jni/src/main/impl/resampler_profile.h b/roc_jni/src/main/impl/resampler_profile.h deleted file mode 100644 index e9f2b5d6..00000000 --- a/roc_jni/src/main/impl/resampler_profile.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#include "common.h" - -#include - -#define RESAMPLER_PROFILE_CLASS PACKAGE_BASE_NAME "/ResamplerProfile" - -roc_resampler_profile get_resampler_profile(JNIEnv* env, jobject jresamplerProfile); diff --git a/roc_jni/src/main/impl/sender.c b/roc_jni/src/main/impl/sender.c index 26dfcb32..5c95887e 100644 --- a/roc_jni/src/main/impl/sender.c +++ b/roc_jni/src/main/impl/sender.c @@ -1,31 +1,47 @@ #include "org_rocstreaming_roctoolkit_RocSender.h" -#include "common.h" #include "endpoint.h" +#include "exceptions.h" +#include "helpers.h" #include "interface_config.h" #include "sender_config.h" -#include #include +#include +#include + JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeOpen( - JNIEnv* env, jclass senderClass, jlong contextPtr, jobject jconfig) { - roc_context* context = NULL; - roc_sender_config config = {}; + JNIEnv* env, jclass jclass, jlong jcontext, jobject jconfig) { + assert(env); + + roc_context* context = (roc_context*) jcontext; + roc_sender_config sender_config = {}; roc_sender* sender = NULL; - context = (roc_context*) contextPtr; + if (!jcontext) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocContext: must not be null"); + goto out; + } + + if (!jconfig) { + throw_exception( + env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSenderConfig: must not be null"); + goto out; + } + + if (!sender_config_unmarshal(env, jconfig, &sender_config)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSenderConfig"); + goto out; + } - if (sender_config_unmarshal(env, &config, jconfig) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + if (roc_sender_open(context, &sender_config, &sender) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to open RocSender"); goto out; } - if ((roc_sender_open(context, &config, &sender)) != 0) { - jclass exceptionClass = (*env)->FindClass(env, EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error opening sender"); - sender = NULL; + if (!sender) { + throw_exception(env, ASSERTION_ERROR, "RocSender is null"); goto out; } @@ -34,38 +50,52 @@ JNIEXPORT jlong JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeOpen( } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeClose( - JNIEnv* env, jclass senderClass, jlong senderPtr) { + JNIEnv* env, jclass jclass, jlong jsender) { + assert(env); - roc_sender* sender = (roc_sender*) senderPtr; + roc_sender* sender = (roc_sender*) jsender; + + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); + goto out; + } if (roc_sender_close(sender) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error closing sender"); + throw_exception(env, ASSERTION_ERROR, "Failed to close RocSender"); + goto out; } + +out: + return; } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConfigure( - JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot, jint interface, jobject jconfig) { - roc_sender* sender = NULL; - roc_interface_config config = {}; + JNIEnv* env, jobject jobj, jlong jsender, jint jslot, jint jinterface, jobject jconfig) { + assert(env); - sender = (roc_sender*) senderPtr; + roc_sender* sender = (roc_sender*) jsender; + roc_interface_config interface_config = {}; - if (jconfig == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad config argument"); + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); goto out; } - if (interface_config_unmarshal(env, &config, jconfig) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling config"); + if (!jconfig) { + throw_exception( + env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid InterfaceConfig: must not be null"); goto out; } - if (roc_sender_configure(sender, (roc_slot) slot, (roc_interface) interface, &config) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error configuring sender"); + if (!interface_config_unmarshal(env, jconfig, &interface_config)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid InterfaceConfig"); + goto out; + } + + if (roc_sender_configure( + sender, (roc_slot) jslot, (roc_interface) jinterface, &interface_config) + != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to configure RocSender interface"); goto out; } @@ -74,45 +104,51 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConfigur } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeConnect( - JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot, jint interface, jobject jendpoint) { - roc_sender* sender = NULL; + JNIEnv* env, jobject jobj, jlong jsender, jint jslot, jint jinterface, jobject jendpoint) { + assert(env); + + roc_sender* sender = (roc_sender*) jsender; roc_endpoint* endpoint = NULL; - sender = (roc_sender*) senderPtr; + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); + goto out; + } - if (jendpoint == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad endpoint argument"); + if (!jendpoint) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid Endpoint: must not be null"); goto out; } - if (endpoint_unmarshal(env, &endpoint, jendpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error unmarshalling endpoint"); + if (!endpoint_unmarshal(env, jendpoint, &endpoint)) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid Endpoint"); goto out; } - if (roc_sender_connect(sender, (roc_slot) slot, (roc_interface) interface, endpoint) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error connecting sender"); + if (roc_sender_connect(sender, (roc_slot) jslot, (roc_interface) jinterface, endpoint) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to connect RocSender endpoint"); goto out; } out: - if (endpoint != NULL) { + if (endpoint) { roc_endpoint_deallocate(endpoint); } } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeUnlink( - JNIEnv* env, jobject thisObj, jlong senderPtr, jint slot) { - roc_sender* sender = NULL; + JNIEnv* env, jobject jobj, jlong jsender, jint jslot) { + assert(env); - sender = (roc_sender*) senderPtr; + roc_sender* sender = (roc_sender*) jsender; - if (roc_sender_unlink(sender, (roc_slot) slot) != 0) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error unlinking slot"); + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); + goto out; + } + + if (roc_sender_unlink(sender, (roc_slot) jslot) != 0) { + throw_exception(env, ROC_EXCEPTION, "Failed to unlink RocSender slot"); goto out; } @@ -121,33 +157,38 @@ JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeUnlink( } JNIEXPORT void JNICALL Java_org_rocstreaming_roctoolkit_RocSender_nativeWriteFloats( - JNIEnv* env, jobject thisObj, jlong senderPtr, jfloatArray jsamples) { - roc_sender* sender = NULL; + JNIEnv* env, jobject jobj, jlong jsender, jfloatArray jsamples) { + assert(env); + + roc_sender* sender = (roc_sender*) jsender; jfloat* samples = NULL; - jsize len = 0; + jsize samples_count = 0; roc_frame frame = {}; - sender = (roc_sender*) senderPtr; + if (!jsender) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid RocSender: must not be null"); + goto out; + } - if (jsamples == NULL) { - jclass exceptionClass = (*env)->FindClass(env, ILLEGAL_ARGUMENTS_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Bad samples argument"); + if (!jsamples) { + throw_exception(env, ILLEGAL_ARGUMENT_EXCEPTION, "Invalid samples array: must not be null"); goto out; } + samples = (*env)->GetFloatArrayElements(env, jsamples, 0); - len = (*env)->GetArrayLength(env, jsamples); - assert(samples != NULL); + samples_count = (*env)->GetArrayLength(env, jsamples); memset(&frame, 0, sizeof(frame)); frame.samples = samples; - frame.samples_size = len * sizeof(float); + frame.samples_size = samples_count * sizeof(float); if (roc_sender_write(sender, &frame) != 0) { - jclass exceptionClass = (*env)->FindClass(env, IO_EXCEPTION); - (*env)->ThrowNew(env, exceptionClass, "Error writing frame"); + throw_exception(env, ROC_EXCEPTION, "Failed to write frame to RocSender"); goto out; } out: - if (samples != NULL) (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); + if (samples) { + (*env)->ReleaseFloatArrayElements(env, jsamples, samples, 0); + } } diff --git a/roc_jni/src/main/impl/sender_config.c b/roc_jni/src/main/impl/sender_config.c index 45e0c1c8..2174e30a 100644 --- a/roc_jni/src/main/impl/sender_config.c +++ b/roc_jni/src/main/impl/sender_config.c @@ -1,79 +1,96 @@ #include "sender_config.h" -#include "clock_source.h" -#include "common.h" #include "endpoint.h" -#include "fec_encoding.h" -#include "format.h" +#include "exceptions.h" +#include "helpers.h" #include "media_encoding.h" -#include "packet_encoding.h" -#include "resampler_backend.h" -#include "resampler_profile.h" -int sender_config_unmarshal(JNIEnv* env, roc_sender_config* config, jobject jconfig) { - jclass senderConfigClass = NULL; - jobject jobj = NULL; - int err = 0; +#include +#include - senderConfigClass = (*env)->FindClass(env, SENDER_CONFIG_CLASS); - assert(senderConfigClass != NULL); +bool sender_config_unmarshal(JNIEnv* env, jobject jconfig, roc_sender_config* result) { + assert(env); + assert(jconfig); + assert(result); - // set all fields to zeros - assert(config != NULL); - memset(config, 0, sizeof(*config)); + memset(result, 0, sizeof(*result)); + + jclass jclass = find_class(env, SENDER_CONFIG_CLASS); + if (!jclass) { + return false; + } + + jobject jencoding = NULL; + int enum_value = 0; // frame_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "frameEncoding", "L" MEDIA_ENCODING_CLASS ";"); - if (jobj != NULL) { - err = media_encoding_unmarshal(env, &config->frame_encoding, jobj); - if (err) return err; + if (!read_object_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "frameEncoding", + MEDIA_ENCODING_CLASS, &jencoding)) { + return false; + } + if (jencoding) { + if (!media_encoding_unmarshal(env, jencoding, &result->frame_encoding)) { + return false; + } } // packet_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "packetEncoding", "L" PACKET_ENCODING_CLASS ";"); - if (jobj != NULL) config->packet_encoding = get_packet_encoding(env, jobj); + if (!read_enum_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "packetEncoding", + PACKET_ENCODING_CLASS, &enum_value)) { + return false; + } + result->packet_encoding = (roc_packet_encoding) enum_value; // packet_length - config->packet_length - = get_duration_field_value(env, senderConfigClass, jconfig, "packetLength", &err); - if (err) return err; + if (!read_unsigned_duration_field( + env, jclass, jconfig, SENDER_CONFIG_CLASS, "packetLength", &result->packet_length)) { + return false; + } // packet_interleaving - config->packet_interleaving - = get_uint_field_value(env, senderConfigClass, jconfig, "packetInterleaving", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "packetInterleaving", + &result->packet_interleaving)) { + return false; + } // fec_encoding - jobj = get_object_field( - env, senderConfigClass, jconfig, "fecEncoding", "L" FEC_ENCODING_CLASS ";"); - if (jobj != NULL) config->fec_encoding = (roc_fec_encoding) get_fec_encoding(env, jobj); + if (!read_enum_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "fecEncoding", + FEC_ENCODING_CLASS, &enum_value)) { + return false; + } + result->fec_encoding = (roc_fec_encoding) enum_value; // fec_block_source_packets - config->fec_block_source_packets - = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockSourcePackets", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "fecBlockSourcePackets", + &result->fec_block_source_packets)) { + return false; + } // fec_block_repair_packets - config->fec_block_repair_packets - = get_uint_field_value(env, senderConfigClass, jconfig, "fecBlockRepairPackets", &err); - if (err) return err; + if (!read_uint_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "fecBlockRepairPackets", + &result->fec_block_repair_packets)) { + return false; + } // clock_source - jobj = get_object_field( - env, senderConfigClass, jconfig, "clockSource", "L" CLOCK_SOURCE_CLASS ";"); - if (jobj != NULL) config->clock_source = get_clock_source(env, jobj); + if (!read_enum_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "clockSource", + CLOCK_SOURCE_CLASS, &enum_value)) { + return false; + } + result->clock_source = (roc_clock_source) enum_value; // resampler_backend - jobj = get_object_field( - env, senderConfigClass, jconfig, "resamplerBackend", "L" RESAMPLER_BACKEND_CLASS ";"); - if (jobj != NULL) config->resampler_backend = get_resampler_backend(env, jobj); + if (!read_enum_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "resamplerBackend", + RESAMPLER_BACKEND_CLASS, &enum_value)) { + return false; + } + result->resampler_backend = (roc_resampler_backend) enum_value; // resampler_profile - jobj = get_object_field( - env, senderConfigClass, jconfig, "resamplerProfile", "L" RESAMPLER_PROFILE_CLASS ";"); - if (jobj != NULL) - config->resampler_profile = (roc_resampler_profile) get_resampler_profile(env, jobj); + if (!read_enum_field(env, jclass, jconfig, SENDER_CONFIG_CLASS, "resamplerProfile", + RESAMPLER_PROFILE_CLASS, &enum_value)) { + return false; + } + result->resampler_profile = (roc_resampler_profile) enum_value; - return 0; + return true; } diff --git a/roc_jni/src/main/impl/sender_config.h b/roc_jni/src/main/impl/sender_config.h index 052d62bc..28246f88 100644 --- a/roc_jni/src/main/impl/sender_config.h +++ b/roc_jni/src/main/impl/sender_config.h @@ -1,11 +1,11 @@ #pragma once -#include - -#include "common.h" +#include "platform.h" +#include #include -#define SENDER_CONFIG_CLASS PACKAGE_BASE_NAME "/RocSenderConfig" +#include -int sender_config_unmarshal(JNIEnv* env, roc_sender_config* config, jobject jconfig); +ATTR_NODISCARD bool sender_config_unmarshal( + JNIEnv* env, jobject jconfig, roc_sender_config* result); diff --git a/src/main/java/org/rocstreaming/roctoolkit/Check.java b/src/main/java/org/rocstreaming/roctoolkit/Check.java index f88de16d..dbfc74eb 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Check.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Check.java @@ -8,14 +8,14 @@ private Check() { static T notNull(T value, String name) { if (value == null) { - throw new IllegalArgumentException(name + " must not be null"); + throw new IllegalArgumentException("Invalid " + name + ": must not be null"); } return value; } static String notEmpty(String value, String name) { if (value == null || value.isEmpty()) { - throw new IllegalArgumentException(name + " must not be empty"); + throw new IllegalArgumentException("Invalid " + name + ": must not be empty"); } return value; } @@ -23,35 +23,35 @@ static String notEmpty(String value, String name) { static Duration notNegative(Duration value, String name) { // Null ("unset") duration is fine, it's treated as zero on JNI side. if (value != null && value.isNegative()) { - throw new IllegalArgumentException(name + " must not be negative"); + throw new IllegalArgumentException("Invalid " + name + ": must not be negative"); } return value; } static int notNegative(int value, String name) { if (value < 0) { - throw new IllegalArgumentException(name + " must not be negative"); + throw new IllegalArgumentException("Invalid " + name + ": must not be negative"); } return value; } static long notNegative(long value, String name) { if (value < 0) { - throw new IllegalArgumentException(name + " must not be negative"); + throw new IllegalArgumentException("Invalid " + name + ": must not be negative"); } return value; } static int inRange(int value, int min_value, int max_value, String name) { if (value < min_value || value > max_value) { - throw new IllegalArgumentException(name + " must be in range [" + min_value + "; " + max_value + "]"); + throw new IllegalArgumentException("Invalid " + name + ": must be in range [" + min_value + "; " + max_value + "]"); } return value; } static long inRange(long value, long min_value, long max_value, String name) { if (value < min_value || value > max_value) { - throw new IllegalArgumentException(name + " must be in range [" + min_value + "; " + max_value + "]"); + throw new IllegalArgumentException("Invalid " + name + ": must be in range [" + min_value + "; " + max_value + "]"); } return value; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/Destructor.java b/src/main/java/org/rocstreaming/roctoolkit/Destructor.java index 3f15073d..809b138b 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Destructor.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Destructor.java @@ -13,14 +13,14 @@ interface Destructor { * user or asynchronously by {@link NativeObjectCleaner}. * *

                                                                - * Note: It's important that this method is declared static and not as an + * Note: It's important that this method is declared {@code static} and not as an * instance method for avoiding object resurrection. *

                                                                * * @param resource {@link NativeObject#ptr NativeObject.ptr} to be closed. * - * @throws Exception if the {@link NativeObject} cannot be closed (for example - * for still opened {@link NativeObject} dependencies). + * @throws IllegalStateException if the {@link NativeObject} cannot be closed because + * it still has opened {@link NativeObject} dependencies. */ - void close(long resource) throws Exception; + void close(long resource); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java index 4522fb83..8012d493 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java +++ b/src/main/java/org/rocstreaming/roctoolkit/Endpoint.java @@ -100,7 +100,7 @@ public class Endpoint { * * @throws IllegalArgumentException if URI is invalid */ - public Endpoint(String uri) throws IllegalArgumentException { + public Endpoint(String uri) { nativeParseUri(uri); } @@ -121,9 +121,9 @@ public Endpoint(String uri) throws IllegalArgumentException { * * @throws IllegalArgumentException if URI components don't form a valid URI */ - public Endpoint(Protocol protocol, String host, int port, String resource) throws IllegalArgumentException { - this.protocol = Check.notNull(protocol, "protocol"); - this.host = Check.notEmpty(host, "host"); + public Endpoint(Protocol protocol, String host, int port, String resource) { + this.protocol = Check.notNull(protocol, "endpoint protocol"); + this.host = Check.notEmpty(host, "endpoint host"); this.port = port; this.resource = resource; nativeValidate(); @@ -164,7 +164,7 @@ public String toString() { return getUri(); } - private native void nativeParseUri(String uri) throws IllegalArgumentException; + private native void nativeParseUri(String uri); private native String nativeFormatUri(); - private native void nativeValidate() throws IllegalArgumentException; + private native void nativeValidate(); } diff --git a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java index 55da3166..0184a84c 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfigValidator.java @@ -1,12 +1,13 @@ package org.rocstreaming.roctoolkit; /** - * A InterfaceConfigValidator adds validation to InterfaceConfig builder. + * Adds validation to {@link InterfaceConfig} builder. */ class InterfaceConfigValidator extends InterfaceConfig.Builder { @Override public InterfaceConfig build() { InterfaceConfig config = super.build(); + return config; } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java b/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java index 5b5ddc60..ff369bfe 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/MediaEncodingValidator.java @@ -1,16 +1,28 @@ package org.rocstreaming.roctoolkit; /** - * A MediaEncodingValidator adds validation to MediaEncoding builder. + * Adds validation to {@link MediaEncoding} builder. */ class MediaEncodingValidator extends MediaEncoding.Builder { @Override public MediaEncoding build() { MediaEncoding encoding = super.build(); - Check.notNegative(encoding.getRate(), "rate"); - Check.notNull(encoding.getFormat(), "format"); - Check.notNull(encoding.getChannels(), "channels"); - Check.notNegative(encoding.getTracks(), "tracks"); + + Check.notNegative(encoding.getRate(), "MediaEncoding.rate"); + Check.notNull(encoding.getFormat(), "MediaEncoding.format"); + Check.notNull(encoding.getChannels(), "MediaEncoding.channels"); + Check.notNegative(encoding.getTracks(), "MediaEncoding.tracks"); + + if (encoding.getChannels() == ChannelLayout.MULTITRACK) { + if (encoding.getTracks() < 1 || encoding.getTracks() > 1024) { + throw new IllegalArgumentException("Invalid MediaEncoding: when 'channels' is MULTITRACK, 'tracks' must be in range [1; 1024]"); + } + } else { + if (encoding.getTracks() != 0) { + throw new IllegalArgumentException("Invalid MediaEncoding: when 'channels' isn't MULTITRACK, 'tracks' must be zero"); + } + } + return encoding; } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java index 93dfc8f7..b10c3414 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObject.java @@ -44,10 +44,11 @@ long getPtr() { /** * Close the native object and unregister it from the {@link NativeObjectCleaner}. * - * @throws Exception if the underlying roc native object cannot be closed. + * @throws IllegalStateException if the underlying roc native object cannot be closed because + * it still has opened {@link NativeObject} dependencies. */ @Override - public void close() throws Exception { + public void close() { resource.close(); resource.clear(); NATIVE_OBJECT_CLEANER.unregister(resource); diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java index f2c44ae0..72a517a6 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectCleaner.java @@ -82,7 +82,7 @@ void unregister(NativeObjectPhantomReference reference) { *

                                                                * Remove any phantom reachable {@link NativeObjectPhantomReference} from the * {@link NativeObjectCleaner#referenceQueue} associated with this {@code NativeObjectCleaner} - * and close it. + * and {@code close()} it. */ @Override public void run() { diff --git a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java index 09807581..2be8f26d 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java +++ b/src/main/java/org/rocstreaming/roctoolkit/NativeObjectPhantomReference.java @@ -64,17 +64,19 @@ long getPtr() { /** * Close the native object. + * + * @throws IllegalStateException if the {@link NativeObject} cannot be closed because + * it still has opened {@link NativeObject} dependencies. */ @Override - public synchronized void close() throws Exception { + public synchronized void close() { if (isOpen) { destructor.close(ptr); // destructor.close(ptr) could throw exception e.g. if someone tried to close context while // sender/receiver still opened. - // In such case NativeObjectCleaner try to close it one more time after NativeObject - // will be collected by GC + // In such case NativeObjectCleaner will try to close it one more time after NativeObject + // is collected by GC. isOpen = false; } } - } diff --git a/src/main/java/org/rocstreaming/roctoolkit/ProtocolUtils.java b/src/main/java/org/rocstreaming/roctoolkit/ProtocolUtils.java deleted file mode 100644 index 372627a1..00000000 --- a/src/main/java/org/rocstreaming/roctoolkit/ProtocolUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.rocstreaming.roctoolkit; - -import lombok.NoArgsConstructor; - -@SuppressWarnings("unused") // used by JNI -@NoArgsConstructor -class ProtocolUtils { - - private static Protocol getByValue(int value) { - for (Protocol protocol : Protocol.values()) { - if (value == protocol.value) { - return protocol; - } - } - return null; - } -} diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java index 962f39e2..19313d64 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContext.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContext.java @@ -37,8 +37,8 @@ public class RocContext extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocContext.class.getName()); - private static long construct(RocContextConfig config) throws IllegalArgumentException, Exception { - Check.notNull(config, "config"); + private static long construct(RocContextConfig config) throws RocException { + Check.notNull(config, "RocContextConfig"); try { LOGGER.log(Level.FINE, "entering RocContext(), config={0}", config); @@ -53,14 +53,14 @@ private static long construct(RocContextConfig config) throws IllegalArgumentExc } } - private static void destroy(long ptr) throws Exception { + private static void destroy(long ptr) { try { LOGGER.log(Level.FINE, "entering RocContext.close(), ptr={0}", toHex(ptr)); nativeClose(ptr); LOGGER.log(Level.FINE, "leaving RocContext.close(), ptr={0}", toHex(ptr)); - } catch (Exception exc) { + } catch (RuntimeException exc) { LOGGER.log(Level.SEVERE, "exception in RocContext.close(), ptr={0}, exception={1}", new Object[]{toHex(ptr), exc}); throw exc; @@ -73,9 +73,9 @@ private static void destroy(long ptr) throws Exception { * Allocates and initializes a new context. May start some background threads. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if there are not enough resources. + * @throws RocException if operation failed. */ - public RocContext() throws IllegalArgumentException, Exception { + public RocContext() throws RocException { this(RocContextConfig.builder().build()); } @@ -87,9 +87,9 @@ public RocContext() throws IllegalArgumentException, Exception { * @param config should point to an initialized config. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if there are not enough resources. + * @throws RocException if operation failed. */ - public RocContext(RocContextConfig config) throws IllegalArgumentException, Exception { + public RocContext(RocContextConfig config) throws RocException { super(construct(config), null, RocContext::destroy); } @@ -114,10 +114,11 @@ public RocContext(RocContextConfig config) throws IllegalArgumentException, Exce * @param encoding is encoding specification to be associated with this id. * * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if operation failed. */ - public void registerEncoding(int encodingId, MediaEncoding encoding) throws IllegalArgumentException { + public void registerEncoding(int encodingId, MediaEncoding encoding) throws RocException { Check.inRange(encodingId, 1, 127, "encodingId"); - Check.notNull(encoding, "encoding"); + Check.notNull(encoding, "MediaEncoding"); try { LOGGER.log(Level.FINE, "entering RocContext.registerEncoding(), ptr={0}, encodingId={1}, encoding={2}", @@ -133,8 +134,7 @@ public void registerEncoding(int encodingId, MediaEncoding encoding) throws Ille } } - private static native long nativeOpen(RocContextConfig config) throws IllegalArgumentException, Exception; - private static native void nativeClose(long contextPtr) throws Exception; - - private static native void nativeRegisterEncoding(long contextPtr, int encodingId, MediaEncoding encoding) throws IllegalArgumentException; + private static native long nativeOpen(RocContextConfig config) throws RocException; + private static native void nativeClose(long contextPtr); + private static native void nativeRegisterEncoding(long contextPtr, int encodingId, MediaEncoding encoding) throws RocException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfigValidator.java index 0b920919..0a66ca42 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfigValidator.java @@ -1,14 +1,16 @@ package org.rocstreaming.roctoolkit; /** - * A RocContextConfigValidator adds validation to RocContextConfig builder. + * Adds validation to {@link RocContextConfig} builder. */ class RocContextConfigValidator extends RocContextConfig.Builder { @Override public RocContextConfig build() { RocContextConfig config = super.build(); - Check.notNegative(config.getMaxPacketSize(), "maxPacketSize"); - Check.notNegative(config.getMaxFrameSize(), "maxFrameSize"); + + Check.notNegative(config.getMaxPacketSize(), "RocContextConfig.maxPacketSize"); + Check.notNegative(config.getMaxFrameSize(), "RocContextConfig.maxFrameSize"); + return config; } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocException.java b/src/main/java/org/rocstreaming/roctoolkit/RocException.java new file mode 100644 index 00000000..ac5e0e10 --- /dev/null +++ b/src/main/java/org/rocstreaming/roctoolkit/RocException.java @@ -0,0 +1,10 @@ +package org.rocstreaming.roctoolkit; + +/** + * Base class for all roctoolkit checked exceptions. + */ +public class RocException extends Exception { + public RocException(String message) { + super(message); + } +} diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java b/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java index f80ac9aa..1c66b4b1 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocLogger.java @@ -22,7 +22,10 @@ class RocLogger { nativeSetHandler(HANDLER); // JVM could be terminated before libroc, so we need to unregister Java callback // from libroc before shutting down, to avoid crash. - Runtime.getRuntime().addShutdownHook(new Thread(() -> nativeSetHandler(null))); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + nativeSetLevel(RocLogLevel.NONE); + nativeSetHandler(null); + })); } private RocLogger() { diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java index ad3426d4..bba96345 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiver.java @@ -1,6 +1,5 @@ package org.rocstreaming.roctoolkit; -import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -196,9 +195,9 @@ public class RocReceiver extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocReceiver.class.getName()); - private static long construct(RocContext context, RocReceiverConfig config) throws IllegalArgumentException, Exception { - Check.notNull(context, "context"); - Check.notNull(config, "config"); + private static long construct(RocContext context, RocReceiverConfig config) throws RocException { + Check.notNull(context, "RocContext"); + Check.notNull(config, "RocReceiverConfig"); try { LOGGER.log(Level.FINE, "entering RocReceiver(), contextPtr={0}, config={1}", @@ -214,14 +213,14 @@ private static long construct(RocContext context, RocReceiverConfig config) thro } } - private static void destroy(long ptr, RocContext context) throws Exception { + private static void destroy(long ptr, RocContext context) { try { LOGGER.log(Level.FINE, "entering RocReceiver.close(), ptr={0}", toHex(ptr)); nativeClose(ptr); LOGGER.log(Level.FINE, "leaving RocReceiver.close(), ptr={0}", toHex(ptr)); - } catch (Exception exc) { + } catch (RuntimeException exc) { LOGGER.log(Level.SEVERE, "exception in RocReceiver.close(), ptr={0}, exception={1}", new Object[]{toHex(context.getPtr()), toHex(ptr), exc}); throw exc; @@ -237,9 +236,9 @@ private static void destroy(long ptr, RocContext context) throws Exception { * @param config should point to an initialized config. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the receiver. + * @throws RocException if operation failed. */ - public RocReceiver(RocContext context, RocReceiverConfig config) throws IllegalArgumentException, Exception { + public RocReceiver(RocContext context, RocReceiverConfig config) throws RocException { super(construct(context, config), context, ptr -> destroy(ptr, context)); } @@ -260,11 +259,14 @@ public RocReceiver(RocContext context, RocReceiverConfig config) throws IllegalA * @param slot specifies the receiver slot. * @param iface specifies the receiver interface. * @param config specifies settings for the specified interface. + * + * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if operation failed. */ - public void configure(Slot slot, Interface iface, InterfaceConfig config) throws IllegalArgumentException { - Check.notNull(slot, "slot"); - Check.notNull(iface, "iface"); - Check.notNull(config, "config"); + public void configure(Slot slot, Interface iface, InterfaceConfig config) throws RocException { + Check.notNull(slot, "Slot"); + Check.notNull(iface, "Interface"); + Check.notNull(config, "InterfaceConfig"); try { LOGGER.log(Level.FINE, "entering RocReceiver.configure(), ptr={0}, slot={1}, iface={2}, config={3}", @@ -305,12 +307,12 @@ public void configure(Slot slot, Interface iface, InterfaceConfig config) throws * @param endpoint specifies the receiver endpoint. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if the address can't be bound or there are not enough resources. + * @throws RocException if operation failed. */ - public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalArgumentException, IOException { - Check.notNull(slot, "slot"); - Check.notNull(iface, "iface"); - Check.notNull(endpoint, "endpoint"); + public void bind(Slot slot, Interface iface, Endpoint endpoint) throws RocException { + Check.notNull(slot, "Slot"); + Check.notNull(iface, "Interface"); + Check.notNull(endpoint, "Endpoint"); try { LOGGER.log(Level.FINE, "entering RocReceiver.bind(), ptr={0}, slot={1}, iface={2}, endpoint={3}", @@ -338,9 +340,10 @@ public void bind(Slot slot, Interface iface, Endpoint endpoint) throws IllegalAr * @param slot specifies the receiver slot to delete. * * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if operation failed. */ - public void unlink(Slot slot) throws IllegalArgumentException { - Check.notNull(slot, "slot"); + public void unlink(Slot slot) throws RocException { + Check.notNull(slot, "Slot"); try { LOGGER.log(Level.FINE, "entering RocReceiver.unlink(), ptr={0}, slot={1}", @@ -375,20 +378,20 @@ public void unlink(Slot slot) throws IllegalArgumentException { * filled with samples. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if there are not enough resources. + * @throws RocException if operation failed. */ - public void read(float[] samples) throws IllegalArgumentException, IOException { + public void read(float[] samples) throws RocException { Check.notNull(samples, "samples"); nativeReadFloats(getPtr(), samples); } - private static native long nativeOpen(long contextPtr, RocReceiverConfig config) throws IllegalArgumentException, Exception; - private static native void nativeClose(long receiverPtr) throws IOException; + private static native long nativeOpen(long contextPtr, RocReceiverConfig config) throws RocException; + private static native void nativeClose(long receiverPtr); - private native void nativeConfigure(long receiverPtr, int slot, int iface, InterfaceConfig config) throws IllegalArgumentException; - private native void nativeBind(long receiverPtr, int slot, int iface, Endpoint endpoint) throws IllegalArgumentException, IOException; - private native void nativeUnlink(long receiverPtr, int slot) throws IllegalArgumentException; + private native void nativeConfigure(long receiverPtr, int slot, int iface, InterfaceConfig config) throws RocException; + private native void nativeBind(long receiverPtr, int slot, int iface, Endpoint endpoint) throws RocException; + private native void nativeUnlink(long receiverPtr, int slot) throws RocException; - private native void nativeReadFloats(long receiverPtr, float[] samples) throws IOException; + private native void nativeReadFloats(long receiverPtr, float[] samples) throws RocException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java index faf10185..71a66c06 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfigValidator.java @@ -1,15 +1,17 @@ package org.rocstreaming.roctoolkit; /** - * A RocReceiverConfigValidator adds validation to RocReceiverConfig builder. + * Adds validation to {@link RocReceiverConfig} builder. */ class RocReceiverConfigValidator extends RocReceiverConfig.Builder { @Override public RocReceiverConfig build() { RocReceiverConfig config = super.build(); - Check.notNull(config.getFrameEncoding(), "frameEncoding"); - Check.notNegative(config.getTargetLatency(), "targetLatency"); - Check.notNegative(config.getLatencyTolerance(), "latencyTolerance"); + + Check.notNull(config.getFrameEncoding(), "RocReceiverConfig.frameEncoding"); + Check.notNegative(config.getTargetLatency(), "RocReceiverConfig.targetLatency"); + Check.notNegative(config.getLatencyTolerance(), "RocReceiverConfig.latencyTolerance"); + return config; } } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java index cb361a15..a737a647 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSender.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSender.java @@ -1,6 +1,5 @@ package org.rocstreaming.roctoolkit; -import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -166,9 +165,9 @@ public class RocSender extends NativeObject { private static final Logger LOGGER = Logger.getLogger(RocSender.class.getName()); - private static long construct(RocContext context, RocSenderConfig config) throws IllegalArgumentException, Exception { - Check.notNull(context, "context"); - Check.notNull(config, "config"); + private static long construct(RocContext context, RocSenderConfig config) throws RocException { + Check.notNull(context, "RocContext"); + Check.notNull(config, "RocSenderConfig"); try { LOGGER.log(Level.FINE, "entering RocSender(), contextPtr={0}, config={1}", @@ -184,14 +183,14 @@ private static long construct(RocContext context, RocSenderConfig config) throws } } - private static void destroy(long ptr, RocContext context) throws Exception { + private static void destroy(long ptr, RocContext context) { try { LOGGER.log(Level.FINE, "entering RocSender.close(), ptr={0}", toHex(ptr)); nativeClose(ptr); LOGGER.log(Level.FINE, "leaving RocSender.close(), ptr={0}", toHex(ptr)); - } catch (Exception exc) { + } catch (RuntimeException exc) { LOGGER.log(Level.SEVERE, "exception in RocSender.close(), ptr={0}, exception={1}", new Object[]{toHex(context.getPtr()), toHex(ptr), exc}); throw exc; @@ -206,9 +205,9 @@ private static void destroy(long ptr, RocContext context) throws Exception { * @param config should point to an initialized config. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws Exception if an error occurred when creating the sender. + * @throws RocException if operation failed. */ - public RocSender(RocContext context, RocSenderConfig config) throws IllegalArgumentException, Exception { + public RocSender(RocContext context, RocSenderConfig config) throws RocException { super(construct(context, config), context, ptr -> destroy(ptr, context)); } @@ -231,11 +230,12 @@ public RocSender(RocContext context, RocSenderConfig config) throws IllegalArgum * @param config settings for the specified interface. * * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if operation failed. */ - public void configure(Slot slot, Interface iface, InterfaceConfig config) throws IllegalArgumentException { - Check.notNull(slot, "slot"); - Check.notNull(iface, "iface"); - Check.notNull(config, "config"); + public void configure(Slot slot, Interface iface, InterfaceConfig config) throws RocException { + Check.notNull(slot, "Slot"); + Check.notNull(iface, "Interface"); + Check.notNull(config, "InterfaceConfig"); try { LOGGER.log(Level.FINE, "entering RocSender.configure(), ptr={0}, slot={1}, iface={2}, config={3}", @@ -272,12 +272,12 @@ public void configure(Slot slot, Interface iface, InterfaceConfig config) throws * @param endpoint endpoint specifies the receiver endpoint. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if was error during connect. + * @throws RocException if operation failed. */ - public void connect(Slot slot, Interface iface, Endpoint endpoint) throws IllegalArgumentException, IOException { - Check.notNull(slot, "slot"); - Check.notNull(iface, "iface"); - Check.notNull(endpoint, "endpoint"); + public void connect(Slot slot, Interface iface, Endpoint endpoint) throws RocException { + Check.notNull(slot, "Slot"); + Check.notNull(iface, "Interface"); + Check.notNull(endpoint, "Endpoint"); try { LOGGER.log(Level.FINE, "entering RocSender.connect(), ptr={0}, slot={1}, iface={2}, endpoint={3}", @@ -305,9 +305,10 @@ public void connect(Slot slot, Interface iface, Endpoint endpoint) throws Illega * @param slot specifies the sender slot to delete. * * @throws IllegalArgumentException if the arguments are invalid. + * @throws RocException if operation failed. */ - public void unlink(Slot slot) throws IllegalArgumentException { - Check.notNull(slot, "slot"); + public void unlink(Slot slot) throws RocException { + Check.notNull(slot, "Slot"); try { LOGGER.log(Level.FINE, "entering RocSender.unlink(), ptr={0}, slot={1}", @@ -341,20 +342,20 @@ public void unlink(Slot slot) throws IllegalArgumentException { * @param samples array of samples to send. * * @throws IllegalArgumentException if the arguments are invalid. - * @throws IOException if the sender if there are not enough resources. + * @throws RocException if operation failed. */ - public void write(float[] samples) throws IllegalArgumentException, IOException { + public void write(float[] samples) throws RocException { Check.notNull(samples, "samples"); nativeWriteFloats(getPtr(), samples); } - private static native long nativeOpen(long contextPtr, RocSenderConfig config) throws IllegalArgumentException, Exception; - private static native void nativeClose(long senderPtr) throws IOException; + private static native long nativeOpen(long contextPtr, RocSenderConfig config) throws RocException; + private static native void nativeClose(long senderPtr); - private native void nativeConfigure(long senderPtr, int slot, int iface, InterfaceConfig config) throws IllegalArgumentException; - private native void nativeConnect(long senderPtr, int slot, int iface, Endpoint endpoint) throws IllegalArgumentException, IOException; - private native void nativeUnlink(long senderPtr, int slot) throws IllegalArgumentException; + private native void nativeConfigure(long senderPtr, int slot, int iface, InterfaceConfig config) throws RocException; + private native void nativeConnect(long senderPtr, int slot, int iface, Endpoint endpoint) throws RocException; + private native void nativeUnlink(long senderPtr, int slot) throws RocException; - private native void nativeWriteFloats(long senderPtr, float[] samples) throws IOException; + private native void nativeWriteFloats(long senderPtr, float[] samples) throws RocException; } diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java index ee259983..676ed346 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfigValidator.java @@ -1,16 +1,18 @@ package org.rocstreaming.roctoolkit; /** - * A RocSenderConfigValidator adds validation to RocSenderConfig builder. + * Adds validation to {@link RocSenderConfig} builder. */ class RocSenderConfigValidator extends RocSenderConfig.Builder { @Override public RocSenderConfig build() { RocSenderConfig config = super.build(); - Check.notNull(config.getFrameEncoding(), "frameEncoding"); - Check.notNegative(config.getPacketLength(), "packetLength"); - Check.notNegative(config.getFecBlockSourcePackets(), "fecBlockSourcePackets"); - Check.notNegative(config.getFecBlockRepairPackets(), "fecBlockRepairPackets"); + + Check.notNull(config.getFrameEncoding(), "RocSenderConfig.frameEncoding"); + Check.notNegative(config.getPacketLength(), "RocSenderConfig.packetLength"); + Check.notNegative(config.getFecBlockSourcePackets(), "RocSenderConfig.fecBlockSourcePackets"); + Check.notNegative(config.getFecBlockRepairPackets(), "RocSenderConfig.fecBlockRepairPackets"); + return config; } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java index a0e0cc47..18bf7386 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.LogManager; @@ -13,7 +12,7 @@ public class BaseTest { static private Level originalLogLevel; @BeforeAll - static void configureLogger() throws IOException { + static void configureLogger() throws Exception { try (InputStream is = RocReceiverTest.class.getClassLoader().getResourceAsStream("logging.properties")) { LogManager.getLogManager().readConfiguration(is); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/EndpointTest.java b/src/test/java/org/rocstreaming/roctoolkit/EndpointTest.java index 2a0973a3..197a36b2 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/EndpointTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/EndpointTest.java @@ -149,7 +149,7 @@ public static List endpointsSource() { params.uri = ""; params.uriException = IllegalArgumentException.class; params.componentsException = IllegalArgumentException.class; - params.componentsExceptionMsg = "protocol must not be null"; + params.componentsExceptionMsg = "Invalid endpoint protocol: must not be null"; result.add(params); } { @@ -159,7 +159,7 @@ public static List endpointsSource() { params.protocol = Protocol.RTSP; params.uriException = IllegalArgumentException.class; params.componentsException = IllegalArgumentException.class; - params.componentsExceptionMsg = "host must not be empty"; + params.componentsExceptionMsg = "Invalid endpoint host: must not be empty"; result.add(params); } { @@ -170,7 +170,7 @@ public static List endpointsSource() { params.port = 12345; params.uriException = IllegalArgumentException.class; params.componentsException = IllegalArgumentException.class; - params.componentsExceptionMsg = "host must not be empty"; + params.componentsExceptionMsg = "Invalid endpoint host: must not be empty"; result.add(params); } { @@ -182,7 +182,7 @@ public static List endpointsSource() { params.port = 12345; params.uriException = IllegalArgumentException.class; params.componentsException = IllegalArgumentException.class; - params.componentsExceptionMsg = "host must not be empty"; + params.componentsExceptionMsg = "Invalid endpoint host: must not be empty"; result.add(params); } { @@ -193,8 +193,8 @@ public static List endpointsSource() { params.host = "192.168.0.1"; params.port = 655356; params.uriException = IllegalArgumentException.class; - params.componentsException = Exception.class; - params.componentsExceptionMsg = "Invalid roc_endpoint"; + params.componentsException = IllegalArgumentException.class; + params.componentsExceptionMsg = "Invalid endpoint port"; result.add(params); } { @@ -205,8 +205,8 @@ public static List endpointsSource() { params.host = "192.168.0.1"; params.port = -2; params.uriException = IllegalArgumentException.class; - params.componentsException = Exception.class; - params.componentsExceptionMsg = "Invalid roc_endpoint"; + params.componentsException = IllegalArgumentException.class; + params.componentsExceptionMsg = "Invalid endpoint port"; result.add(params); } { @@ -218,8 +218,8 @@ public static List endpointsSource() { params.port = -1; params.resource = "??"; params.uriException = IllegalArgumentException.class; - params.componentsException = Exception.class; - params.componentsExceptionMsg = "Invalid roc_endpoint"; + params.componentsException = IllegalArgumentException.class; + params.componentsExceptionMsg = "Invalid endpoint resource"; result.add(params); } { @@ -231,8 +231,8 @@ public static List endpointsSource() { params.port = 12345; params.resource = "/path"; params.uriException = IllegalArgumentException.class; - params.componentsException = Exception.class; - params.componentsExceptionMsg = "Invalid roc_endpoint"; + params.componentsException = IllegalArgumentException.class; + params.componentsExceptionMsg = "Invalid endpoint uri"; result.add(params); } { @@ -243,8 +243,8 @@ public static List endpointsSource() { params.host = "192.168.0.1"; params.port = -1; params.uriException = IllegalArgumentException.class; - params.componentsException = Exception.class; - params.componentsExceptionMsg = "Invalid roc_endpoint"; + params.componentsException = IllegalArgumentException.class; + params.componentsExceptionMsg = "Invalid endpoint uri"; result.add(params); } return result; diff --git a/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java b/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java index 555fc7f7..d185256a 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/MediaEncodingTest.java @@ -26,9 +26,15 @@ public void testValidEncoding() { private static Stream invalidEncodingArguments() { return Stream.of( - Arguments.of("rate must not be negative", validBuilder().rate(-1)), - Arguments.of("format must not be null", validBuilder().format(null)), - Arguments.of("channels must not be null", validBuilder().channels(null)) + Arguments.of("Invalid MediaEncoding.rate: must not be negative", validBuilder().rate(-1)), + Arguments.of("Invalid MediaEncoding.format: must not be null", validBuilder().format(null)), + Arguments.of("Invalid MediaEncoding.channels: must not be null", validBuilder().channels(null)), + Arguments.of("Invalid MediaEncoding: when 'channels' is MULTITRACK, 'tracks' must be in range [1; 1024]", + validBuilder().channels(ChannelLayout.MULTITRACK).tracks(0)), + Arguments.of("Invalid MediaEncoding: when 'channels' is MULTITRACK, 'tracks' must be in range [1; 1024]", + validBuilder().channels(ChannelLayout.MULTITRACK).tracks(1025)), + Arguments.of("Invalid MediaEncoding: when 'channels' isn't MULTITRACK, 'tracks' must be zero", + validBuilder().channels(ChannelLayout.STEREO).tracks(1)) ); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/NativeObjectCleanerTest.java b/src/test/java/org/rocstreaming/roctoolkit/NativeObjectCleanerTest.java index faf4e978..b698aaac 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/NativeObjectCleanerTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/NativeObjectCleanerTest.java @@ -18,34 +18,31 @@ void senderAutoClosingTest() throws Exception { @SuppressWarnings("unused") RocSender sender = new RocSender(context, RocSenderTest.CONFIG); - Exception exception = assertThrows(Exception.class, context::close); - assertEquals("Error closing context", exception.getMessage()); // sender still using context + Exception exception = assertThrows(IllegalStateException.class, context::close); + assertEquals("Can't close RocContext before closing associated RocSender/RocReceiver(s)", + exception.getMessage()); // sender still using context //noinspection UnusedAssignment sender = null; System.gc(); - long timeout = TimeUnit.MINUTES.toMillis(3); - while (timeout > 0) { - Thread.sleep(50); - timeout -= 50; - try { - assertDoesNotThrow(context::close); - return; - } catch (AssertionFailedError ignore) { - } - System.gc(); - } - fail("failed to close context because sender wasn't auto closed"); + await().atMost(Duration.FIVE_MINUTES) + .untilAsserted(() -> { + System.gc(); + assertDoesNotThrow(context::close); + Thread.sleep(50); + }); } @Test void receiverAutoClosingTest() throws Exception { RocContext context = new RocContext(); + @SuppressWarnings("unused") RocReceiver receiver = new RocReceiver(context, RocReceiverTest.CONFIG); - Exception exception = assertThrows(Exception.class, context::close); - assertEquals("Error closing context", exception.getMessage()); // receiver still using context + Exception exception = assertThrows(IllegalStateException.class, context::close); + assertEquals("Can't close RocContext before closing associated RocSender/RocReceiver(s)", + exception.getMessage()); // receiver still using context //noinspection UnusedAssignment receiver = null; @@ -54,6 +51,7 @@ void receiverAutoClosingTest() throws Exception { .untilAsserted(() -> { System.gc(); assertDoesNotThrow(context::close); + Thread.sleep(50); }); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java index 4f97a99d..0ccec5f8 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocContextConfigTest.java @@ -22,8 +22,8 @@ public void testValidConfig() { private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("maxFrameSize must not be negative", validBuilder().maxFrameSize(-1)), - Arguments.of("maxPacketSize must not be negative", validBuilder().maxPacketSize(-1)) + Arguments.of("Invalid RocContextConfig.maxFrameSize: must not be negative", validBuilder().maxFrameSize(-1)), + Arguments.of("Invalid RocContextConfig.maxPacketSize: must not be negative", validBuilder().maxPacketSize(-1)) ); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java index 2ef91444..cd21533f 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocContextTest.java @@ -22,13 +22,13 @@ public void testWithDefaultConfig() { @Test public void testWithNullConfig() { //noinspection resource - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new RocContext(null)); - assertEquals("config must not be null", e.getMessage()); + Exception e = assertThrows(IllegalArgumentException.class, () -> new RocContext(null)); + assertEquals("Invalid RocContextConfig: must not be null", e.getMessage()); } @Test public void testCloseWithAttachedSender() { - assertThrows(Exception.class, () -> { + assertThrows(IllegalStateException.class, () -> { RocSender sender = null; try (RocContext context = new RocContext()) { sender = new RocSender(context, RocSenderTest.CONFIG); @@ -41,7 +41,7 @@ public void testCloseWithAttachedSender() { @Test public void testCloseWithAttachedReceiver() { - assertThrows(Exception.class, () -> { + assertThrows(IllegalStateException.class, () -> { RocReceiver receiver = null; try (RocContext context = new RocContext()) { receiver = new RocReceiver(context, RocReceiverTest.CONFIG); @@ -71,19 +71,19 @@ public void testRegisterEncoding() { private static Stream invalidRegisterEncodingArguments() { return Stream.of( - Arguments.of("encodingId must be in range [1; 127]", -1, validEncoding()), - Arguments.of("encodingId must be in range [1; 127]", 0, validEncoding()), - Arguments.of("encodingId must be in range [1; 127]", 128, validEncoding()), - Arguments.of("encoding must not be null", 100, null), + Arguments.of("Invalid encodingId: must be in range [1; 127]", IllegalArgumentException.class, -1, validEncoding()), + Arguments.of("Invalid encodingId: must be in range [1; 127]", IllegalArgumentException.class, 0, validEncoding()), + Arguments.of("Invalid encodingId: must be in range [1; 127]", IllegalArgumentException.class, 128, validEncoding()), + Arguments.of("Invalid MediaEncoding: must not be null", IllegalArgumentException.class, 100, null), // encoding id already registered - Arguments.of("Error registering encoding", PacketEncoding.AVP_L16_MONO.getValue(), validEncoding()) + Arguments.of("Failed to register MediaEncoding", RocException.class, PacketEncoding.AVP_L16_MONO.getValue(), validEncoding()) ); } @ParameterizedTest() @MethodSource("invalidRegisterEncodingArguments") - public void testInvalidEncoding(String error, int encodingId, MediaEncoding encoding) { - Exception e = assertThrows(IllegalArgumentException.class, () -> { + public void testInvalidEncoding(String error, Class exception, int encodingId, MediaEncoding encoding) { + Exception e = assertThrows(exception, () -> { try (RocContext context = new RocContext()) { context.registerEncoding(encodingId, encoding); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java index 40ebeb5f..b25f211a 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverConfigTest.java @@ -30,9 +30,9 @@ public void testValidConfig() { private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("frameEncoding must not be null", validBuilder().frameEncoding(null)), - Arguments.of("targetLatency must not be negative", validBuilder().targetLatency(Duration.ofNanos(-1))), - Arguments.of("latencyTolerance must not be negative", validBuilder().latencyTolerance(Duration.ofNanos(-1))) + Arguments.of("Invalid RocReceiverConfig.frameEncoding: must not be null", validBuilder().frameEncoding(null)), + Arguments.of("Invalid RocReceiverConfig.targetLatency: must not be negative", validBuilder().targetLatency(Duration.ofNanos(-1))), + Arguments.of("Invalid RocReceiverConfig.latencyTolerance: must not be negative", validBuilder().latencyTolerance(Duration.ofNanos(-1))) ); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java index f56d3374..37960a57 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocReceiverTest.java @@ -32,7 +32,7 @@ public void beforeEach() throws Exception { } @AfterEach - public void afterEach() throws Exception { + public void afterEach() { this.context.close(); } @@ -73,12 +73,12 @@ public void testCreationAndDeinitializationWithFullConfig() { private static Stream invalidCreationArguments() throws Exception { return Stream.of( Arguments.of( - "context must not be null", + "Invalid RocContext: must not be null", IllegalArgumentException.class, null, CONFIG), Arguments.of( - "config must not be null", + "Invalid RocReceiverConfig: must not be null", IllegalArgumentException.class, new RocContext(), null) @@ -112,36 +112,42 @@ public void testConfigureAfterBind() throws Exception { .outgoingAddress("0.0.0.0") .build(); receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://224.0.0.1:0")); - Exception exception = assertThrows(Exception.class, () -> receiver.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); - assertEquals("Error configuring receiver", exception.getMessage()); + Exception exception = assertThrows(RocException.class, () -> receiver.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); + assertEquals("Failed to configure RocReceiver interface", exception.getMessage()); } } private static Stream invalidConfigureArguments() { return Stream.of( Arguments.of( - "slot must not be null", + "Invalid Slot: must not be null", null, Interface.AUDIO_SOURCE, - "0.0.0.0"), + InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build()), Arguments.of( - "iface must not be null", + "Invalid Interface: must not be null", Slot.DEFAULT, null, - "0.0.0.0") + InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build()), + Arguments.of( + "Invalid InterfaceConfig: must not be null", + Slot.DEFAULT, + Interface.AUDIO_SOURCE, + null) ); } @ParameterizedTest @MethodSource("invalidConfigureArguments") - public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { + public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, InterfaceConfig config) throws Exception { try (RocReceiver receiver = new RocReceiver(context, CONFIG)) { - InterfaceConfig ifaceConfig = InterfaceConfig.builder() - .multicastGroup(ip) - .build(); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> receiver.configure(slot, iface, ifaceConfig) + () -> receiver.configure(slot, iface, config) ); assertEquals(errorMessage, exception.getMessage()); } @@ -175,17 +181,17 @@ public void testBindEphemeralPort() throws Exception { private static Stream invalidBindArguments() { return Stream.of( Arguments.of( - "slot must not be null", + "Invalid Slot: must not be null", null, Interface.AUDIO_SOURCE, new Endpoint("rtsp://0.0.0.0")), Arguments.of( - "iface must not be null", + "Invalid Interface: must not be null", Slot.DEFAULT, null, new Endpoint("rtsp://0.0.0.0")), Arguments.of( - "endpoint must not be null", + "Invalid Endpoint: must not be null", Slot.DEFAULT, Interface.AUDIO_SOURCE, null) @@ -227,9 +233,9 @@ public void testInvalidUnlink() throws Exception { receiver.bind(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); receiver.bind(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); receiver.unlink(slot1); - assertThrows(IllegalArgumentException.class, () -> receiver.unlink(slot1)); + assertThrows(RocException.class, () -> receiver.unlink(slot1)); receiver.unlink(slot2); - assertThrows(IllegalArgumentException.class, () -> receiver.unlink(slot2)); + assertThrows(RocException.class, () -> receiver.unlink(slot2)); }); } } @@ -251,7 +257,7 @@ public void testInvalidRead() throws Exception { receiver.bind(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:0")); receiver.bind(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://127.0.0.1:0")); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> receiver.read(null)); - assertEquals("samples must not be null", exception.getMessage()); + assertEquals("Invalid samples: must not be null", exception.getMessage()); } } } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java index 75a7afe9..24c5585a 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocSenderConfigTest.java @@ -30,10 +30,10 @@ public void testValidConfig() { private static Stream invalidConfigArguments() { return Stream.of( - Arguments.of("frameEncoding must not be null", validBuilder().frameEncoding(null)), - Arguments.of("packetLength must not be negative", validBuilder().packetLength(Duration.ofNanos(-1))), - Arguments.of("fecBlockSourcePackets must not be negative", validBuilder().fecBlockSourcePackets(-1)), - Arguments.of("fecBlockRepairPackets must not be negative", validBuilder().fecBlockRepairPackets(-1)) + Arguments.of("Invalid RocSenderConfig.frameEncoding: must not be null", validBuilder().frameEncoding(null)), + Arguments.of("Invalid RocSenderConfig.packetLength: must not be negative", validBuilder().packetLength(Duration.ofNanos(-1))), + Arguments.of("Invalid RocSenderConfig.fecBlockSourcePackets: must not be negative", validBuilder().fecBlockSourcePackets(-1)), + Arguments.of("Invalid RocSenderConfig.fecBlockRepairPackets: must not be negative", validBuilder().fecBlockRepairPackets(-1)) ); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java index 6627508e..98f6947e 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/RocSenderTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.io.IOException; import java.time.Duration; import java.util.stream.Stream; @@ -55,7 +54,7 @@ public void beforeEach() throws Exception { } @AfterEach - public void afterEach() throws Exception { + public void afterEach() { this.context.close(); } @@ -98,12 +97,12 @@ public void testCreationAndDeinitializationWithFullConfig() { private static Stream invalidCreationArguments() throws Exception { return Stream.of( Arguments.of( - "context must not be null", + "Invalid RocContext: must not be null", IllegalArgumentException.class, null, CONFIG), Arguments.of( - "config must not be null", + "Invalid RocSenderConfig: must not be null", IllegalArgumentException.class, new RocContext(), null) @@ -137,36 +136,42 @@ void testConfigureAfterConnect() throws Exception { .build(); sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); - Exception exception = assertThrows(Exception.class, () -> sender.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); - assertEquals("Error configuring sender", exception.getMessage()); + Exception exception = assertThrows(RocException.class, () -> sender.configure(Slot.DEFAULT, Interface.AUDIO_SOURCE, ifaceConfig)); + assertEquals("Failed to configure RocSender interface", exception.getMessage()); } } private static Stream invalidConfigureArguments() { return Stream.of( Arguments.of( - "slot must not be null", + "Invalid Slot: must not be null", null, Interface.AUDIO_SOURCE, - "0.0.0.0"), + InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build()), Arguments.of( - "iface must not be null", + "Invalid Interface: must not be null", Slot.DEFAULT, null, - "0.0.0.0") + InterfaceConfig.builder() + .outgoingAddress("0.0.0.0") + .build()), + Arguments.of( + "Invalid InterfaceConfig: must not be null", + Slot.DEFAULT, + Interface.AUDIO_SOURCE, + null) ); } @ParameterizedTest @MethodSource("invalidConfigureArguments") - public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, String ip) throws Exception { + public void testInvalidConfigure(String errorMessage, Slot slot, Interface iface, InterfaceConfig config) throws Exception { try (RocSender receiver = new RocSender(context, CONFIG)) { - InterfaceConfig ifaceConfig = InterfaceConfig.builder() - .outgoingAddress(ip) - .build(); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> receiver.configure(slot, iface, ifaceConfig) + () -> receiver.configure(slot, iface, config) ); assertEquals(errorMessage, exception.getMessage()); } @@ -185,17 +190,17 @@ public void testConnect() throws Exception { private static Stream invalidConnectArguments() { return Stream.of( Arguments.of( - "slot must not be null", + "Invalid Slot: must not be null", null, Interface.AUDIO_SOURCE, new Endpoint("rtsp://0.0.0.0")), Arguments.of( - "iface must not be null", + "Invalid Interface: must not be null", Slot.DEFAULT, null, new Endpoint("rtsp://0.0.0.0")), Arguments.of( - "endpoint must not be null", + "Invalid Endpoint: must not be null", Slot.DEFAULT, Interface.AUDIO_SOURCE, null) @@ -237,9 +242,9 @@ public void testInvalidUnlink() throws Exception { sender.connect(slot1, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10001")); sender.connect(slot2, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://127.0.0.1:10002")); sender.unlink(slot1); - assertThrows(IllegalArgumentException.class, () -> sender.unlink(slot1)); + assertThrows(RocException.class, () -> sender.unlink(slot1)); sender.unlink(slot2); - assertThrows(IllegalArgumentException.class, () -> sender.unlink(slot2)); + assertThrows(RocException.class, () -> sender.unlink(slot2)); }); } } @@ -270,7 +275,7 @@ public void testInvalidConnectAfterWrite() throws Exception { sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); sender.connect(Slot.DEFAULT, Interface.AUDIO_REPAIR, new Endpoint("rs8m://0.0.0.0:10002")); sender.write(samples); - assertThrows(IOException.class, () -> { + assertThrows(RocException.class, () -> { sender.connect(Slot.DEFAULT, Interface.AUDIO_SOURCE, new Endpoint("rtp+rs8m://0.0.0.0:10001")); }); } diff --git a/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java b/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java index d1f04484..4fff5e39 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/integration/RocSenderReceiverTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; import org.rocstreaming.roctoolkit.*; -import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; @@ -66,7 +65,7 @@ void testWriteRead() throws Exception { while (running.get()) { try { sender.write(samples); - } catch (IOException e) { + } catch (RocException e) { throw new RuntimeException(e); } } From 67f4f22b19e0fb2049fbb4d2888308d22a06fd85 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Mon, 5 May 2025 01:36:29 +0900 Subject: [PATCH 6/7] Enable logs when running tests with --info or --debug --- HACKING.md | 5 +++++ src/test/java/org/rocstreaming/roctoolkit/BaseTest.java | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/HACKING.md b/HACKING.md index c743e2ac..4e401bba 100644 --- a/HACKING.md +++ b/HACKING.md @@ -17,6 +17,11 @@ Run tests: ./gradlew test ``` +Run tests and print logs: +``` +./gradlew test --info +``` + Generate documentation: ``` ./gradlew javadoc diff --git a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java index 18bf7386..8f0b80ab 100644 --- a/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java +++ b/src/test/java/org/rocstreaming/roctoolkit/BaseTest.java @@ -18,7 +18,14 @@ static void configureLogger() throws Exception { } originalLogLevel = RocLogger.LOGGER.getLevel(); - RocLogger.LOGGER.setLevel(Level.OFF); + + String gradleLogLevel = System.getProperty("org.gradle.logging.level"); + + if (gradleLogLevel == null || gradleLogLevel.equals("INFO") || gradleLogLevel.equals("DEBUG")) { + RocLogger.LOGGER.setLevel(Level.FINE); + } else { + RocLogger.LOGGER.setLevel(Level.OFF); + } } @AfterAll From 072956d44eef21c07391e7cef577f4d15ba2e282 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Mon, 5 May 2025 01:37:47 +0900 Subject: [PATCH 7/7] Regenerate comments --- src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java | 2 +- src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java | 2 +- src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java | 2 +- .../java/org/rocstreaming/roctoolkit/RocReceiverConfig.java | 2 +- src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java index bd774a9e..d486c144 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/InterfaceConfig.java @@ -78,7 +78,7 @@ public class InterfaceConfig { private boolean reuseAddress; /** - * Construct lombok builder for {@link InterfaceConfig}. + * Construct builder for {@link InterfaceConfig}. */ public static InterfaceConfig.Builder builder() { return new InterfaceConfigValidator(); diff --git a/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java b/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java index 3de24766..43a87715 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java +++ b/src/main/java/org/rocstreaming/roctoolkit/MediaEncoding.java @@ -49,7 +49,7 @@ public class MediaEncoding { private int tracks; /** - * Construct lombok builder for {@link MediaEncoding}. + * Construct builder for {@link MediaEncoding}. */ public static MediaEncoding.Builder builder() { return new MediaEncodingValidator(); diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java index 20e3c160..6d2f38cc 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocContextConfig.java @@ -37,7 +37,7 @@ public class RocContextConfig { private int maxFrameSize; /** - * Construct lombok builder for {@link RocContextConfig}. + * Construct builder for {@link RocContextConfig}. */ public static RocContextConfig.Builder builder() { return new RocContextConfigValidator(); diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java index 1b0c45a3..5cc82669 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocReceiverConfig.java @@ -108,7 +108,7 @@ public class RocReceiverConfig { private Duration choppyPlaybackTimeout; /** - * Construct lombok builder for {@link RocReceiverConfig}. + * Construct builder for {@link RocReceiverConfig}. */ public static RocReceiverConfig.Builder builder() { return new RocReceiverConfigValidator(); diff --git a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java index f7f3b344..5d6bb0a1 100644 --- a/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java +++ b/src/main/java/org/rocstreaming/roctoolkit/RocSenderConfig.java @@ -131,7 +131,7 @@ public class RocSenderConfig { private ResamplerProfile resamplerProfile; /** - * Construct lombok builder for {@link RocSenderConfig}. + * Construct builder for {@link RocSenderConfig}. */ public static RocSenderConfig.Builder builder() { return new RocSenderConfigValidator();