Skip to content

Conversation

@vshcryabets
Copy link
Owner

No description provided.

@vshcryabets vshcryabets self-assigned this Sep 29, 2025
@vshcryabets vshcryabets requested a review from Copilot September 30, 2025 05:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR converts synchronous source and encoder operations to asynchronous ones by introducing std::future<void> return types for key lifecycle methods. The changes enable better error handling and resource management in the multimedia framework.

Key changes:

  • Convert Source/Encoder lifecycle methods (startProducing, stopProducing, close) to return std::future<void>
  • Update test code to properly wait for async operations using .get()
  • Add CTest integration and test discovery for better test automation

Reviewed Changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
linux/CMakeLists.txt Add CTest support and compiler warnings for better code quality
libausbc/src/test/java/com/vsh/source/PullToPushSourceTests.kt Update tests to use async patterns and add new test case
libausbc/src/test/java/com/vsh/source/EmptyConsumer.kt Add test consumer implementation
Multiple C++ source files Convert lifecycle methods to async and update method signatures
Multiple C++ header files Update interface declarations to return futures
Java source files Update Java interfaces to support the new async patterns

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 32 to 34
override fun startProducing(configuration: Source.ProducingConfiguration) {
TODO("Not yet implemented")
}
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method contains a TODO indicating incomplete implementation. Either implement the method or document why the implementation is deferred.

Copilot uses AI. Check for mistakes.
@vshcryabets vshcryabets requested a review from Copilot October 4, 2025 17:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 41 out of 43 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (2)

libausbc/src/main/jni/modules/sources/src/TestFileSource.cpp:1

  • close() now returns a std::future and launches an async task that captures this. Calling it from the destructor without waiting can cause use-after-free. Call close().get() here (or make close synchronous) to ensure cleanup completes before destruction.
#include "TestFileSource.h"

libausbc/src/main/jni/modules/sources/src/jni/JniTestSource.cpp:1

  • stopProducing() and close() now return futures, but the JNI shims return without waiting. This breaks synchronous semantics on the Java side. Append .get() in both calls to ensure the operations complete before returning.
#include <jni.h>

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 9 to 14
class OpenConfiguration(
tag: String,
val pullSource: JniSource<*, *>
) : Source.OpenConfiguration(tag) {
}
val pullSource: JniSource<*, *>,
consumer: Consumer
) : PushSource.OpenConfiguration(tag, consumer)
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenConfiguration passes a Consumer to PushSource.OpenConfiguration, but PushSource.OpenConfiguration now requires a JniConsumer. This won't compile and also prevents native wiring of the consumer. Change the parameter type to JniConsumer and pass it through.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to 27
super.open(configuration)
val sourcePtr = configuration.pullSource.getNativeObject()
this.openConfig = configuration
nativeOpen(sourcePtr, configuration.tag)
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativeOpen is called with the pullSource pointer as the first argument, but on the native side the first parameter is the PullToPushSource instance pointer. Additionally, nativeOpen is not declared as an external method in this class and the C++ registration does not expose it. Define an external nativeOpen with the correct signature and fix the call, e.g. nativeOpen(getNativeObject(), configuration.tag, sourcePtr, configuration.consumer.getNativeObject()).

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 17
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
source->open(openConfig);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consumer/frame callback is not set when opening PullToPushSource, so pushed frames will be dropped. Extend the JNI signature to accept a consumer ID and set it here via JniSourcesRepo (or pass a callback), e.g. openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId); then source->open(openConfig).

Copilot uses AI. Check for mistakes.
Comment on lines 37 to 49
void JniPullToPushSource_register(JNIEnv *env) {
jclass clazz = env->FindClass("com/vsh/source/PullToPushSource");
if (clazz != nullptr) {
static const JNINativeMethod methods[] = {
{CONST_LITERAL("nativeCreate"), CONST_LITERAL("()J"),
(void *) &Java_com_vsh_source_PullToPushSource_nativeCreate},
{CONST_LITERAL("nativeRelease"), CONST_LITERAL("(J)V"),
(void *) &Java_com_vsh_source_PullToPushSource_nativeRelease},
};
env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(clazz);
}
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativeOpen is implemented but not registered, so calls from Kotlin will fail with UnsatisfiedLinkError. Add the nativeOpen entry with the correct signature to this methods array.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +54
JNIEXPORT void JNICALL
Java_com_vsh_source_CountConsumer_nativeRelease(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->removeConsumer(sourceId);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_vsh_source_CountConsumer_nativeStopConsuming(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->getConsumer(sourceId)->stopConsuming();
}

void JniCountConsumer_register(JNIEnv *env) {
JniCountConsumer *source = new JniCountConsumer();
source->stopConsuming();
delete source;
jclass clazz = env->FindClass("com/vsh/source/CountConsumer");
if (clazz != nullptr) {
static const JNINativeMethod methods[] = {
{CONST_LITERAL("nativeCreate"), CONST_LITERAL("()J"),
(void *) &Java_com_vsh_source_CountConsumer_nativeCreate},
{CONST_LITERAL("nativeRelease"), CONST_LITERAL("(J)V"),
(void *) &Java_com_vsh_source_CountConsumer_nativeRelease},
{CONST_LITERAL("nativeStopConsuming"), CONST_LITERAL("(J)V"),
(void *) &Java_com_vsh_source_CountConsumer_nativeStopConsuming},
};
env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(clazz);
}
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch between registered signatures (J/long) and implementation parameters (jint) for nativeRelease/nativeStopConsuming. Align to a single type across Java/Kotlin/C++ (e.g., use jint/int everywhere for repo IDs) and update the method descriptors to (I)V, or change C++ to take jlong.

Copilot uses AI. Check for mistakes.

class CountConsumer: JniConsumer {
private var nativePtr: Long = 0

Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativePtr is never initialized, so nativeRelease/nativeStopConsuming will be no-ops. Initialize it (e.g., in an init block) by calling nativeCreate(), and ensure close() calls nativeRelease.

Suggested change
init {
nativePtr = nativeCreate()
}

Copilot uses AI. Check for mistakes.
@vshcryabets vshcryabets requested a review from Copilot October 4, 2025 18:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 48 out of 50 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (1)

libausbc/src/main/jni/modules/sources/src/TestFileSource.cpp:1

  • close() is now [[nodiscard]] and returns std::future. The destructor should await it to ensure resources are released and to satisfy compile flags; use close().get();
#include "TestFileSource.h"

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 13 to 19
jlong source_ptr,
jstring tag,
jlong pull_source_ptr) {
auto *source = reinterpret_cast<PullToPushSource*>(source_ptr);
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JNI signature and ownership are incorrect. Kotlin declares nativeOpen(srcId: Int, tag: String, pullSrcId: Int), but this JNI function expects jlongs and treats them as raw pointers, then wraps them into shared_ptr, causing UB and mismatched signatures (will throw UnsatisfiedLinkError). Use jint IDs and resolve both source and pullSource from JniSourcesRepo; do not construct shared_ptr from a raw pointer.

Suggested change
jlong source_ptr,
jstring tag,
jlong pull_source_ptr) {
auto *source = reinterpret_cast<PullToPushSource*>(source_ptr);
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
jint srcId,
jstring tag,
jint pullSrcId) {
// Look up the PullToPushSource by srcId
auto source = std::dynamic_pointer_cast<PullToPushSource>(
JniSourcesRepo::getInstance()->getSource(srcId)
);
if (!source) {
// Optionally throw an exception or log error
return;
}
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
// Look up the PullSource by pullSrcId
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSrcId)
);

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 19
jlong pull_source_ptr) {
auto *source = reinterpret_cast<PullToPushSource*>(source_ptr);
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenConfiguration carries a consumer on the native side (via auvc::PushSource::OpenConfiguration), but the JNI bridge doesn't set it. Since OpenConfiguration in Kotlin now includes a consumer, extend nativeOpen to accept a consumer ID and set openConfig.consumer using JniSourcesRepo->getConsumer(consumerId) so frames can be delivered.

Suggested change
jlong pull_source_ptr) {
auto *source = reinterpret_cast<PullToPushSource*>(source_ptr);
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
jlong pull_source_ptr,
jint consumerId) {
auto *source = reinterpret_cast<PullToPushSource*>(source_ptr);
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::shared_ptr<PullSource>(reinterpret_cast<PullSource*>(pull_source_ptr));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +34
Java_com_vsh_source_CountConsumer_nativeRelease(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->removeConsumer(sourceId);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_vsh_source_CountConsumer_nativeStopConsuming(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->getConsumer(sourceId)->stopConsuming();
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JNI parameter types mismatch. Kotlin declares nativeRelease(ptr: Long) and nativeStopConsuming(ptr: Long), and JniCountConsumer_register binds signatures '(J)V', but these functions use jint. This mismatch will fail linkage or call conventions. Make both functions take jlong or change Kotlin/registration to use Int '(I)V' consistently with repo IDs.

Suggested change
Java_com_vsh_source_CountConsumer_nativeRelease(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->removeConsumer(sourceId);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_vsh_source_CountConsumer_nativeStopConsuming(JNIEnv *env, jobject thiz, jint sourceId) {
JniSourcesRepo::getInstance()->getConsumer(sourceId)->stopConsuming();
Java_com_vsh_source_CountConsumer_nativeRelease(JNIEnv *env, jobject thiz, jlong sourceId) {
JniSourcesRepo::getInstance()->removeConsumer(static_cast<int>(sourceId));
}
extern "C"
JNIEXPORT void JNICALL
Java_com_vsh_source_CountConsumer_nativeStopConsuming(JNIEnv *env, jobject thiz, jlong sourceId) {
JniSourcesRepo::getInstance()->getConsumer(static_cast<int>(sourceId))->stopConsuming();

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +50
static const JNINativeMethod methods[] = {
{CONST_LITERAL("nativeCreate"), CONST_LITERAL("()J"),
(void *) &Java_com_vsh_source_CountConsumer_nativeCreate},
{CONST_LITERAL("nativeRelease"), CONST_LITERAL("(J)V"),
(void *) &Java_com_vsh_source_CountConsumer_nativeRelease},
{CONST_LITERAL("nativeStopConsuming"), CONST_LITERAL("(J)V"),
(void *) &Java_com_vsh_source_CountConsumer_nativeStopConsuming},
};
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registered signatures use '(J)V' for nativeRelease and nativeStopConsuming, but the C++ functions are defined with jint parameters (see lines 27 and 33). Update the function signatures to jlong or change the registration to '(I)V' to match the implementation and Kotlin side.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +66
JNIEXPORT void JNICALL
Java_com_vsh_source_TestSource_nativeStopCapturing(JNIEnv *env, jobject thiz, jint sourceId)
{
JniSourcesRepo::getInstance()->getSource(sourceId)->stopProducing().get();
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible null dereference: getSource(sourceId) may return nullptr. Retrieve the shared_ptr, validate it, and throw a Java exception if not found before dereferencing.

Copilot uses AI. Check for mistakes.
}
}

env->DeleteLocalRef(cls);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard DeleteLocalRef with a null check to avoid invoking it on a null reference in error paths. For example: if (cls) env->DeleteLocalRef(cls);

Suggested change
env->DeleteLocalRef(cls);
if (cls) env->DeleteLocalRef(cls);

Copilot uses AI. Check for mistakes.
external override fun nativeGetSupportedResolutions(ptr: Long): Map<Integer, List<SourceResolution>>
external override fun nativeGetSupportedFrameFormats(ptr: Long): List<Integer>
private external fun nativeCreate(): Int
external override fun nativeRelease(srcId: Int)
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Kotlin signature for nativeOpen uses Int IDs, but the current C++ binding expects jlong pointers and casts them (see JniPullToPushSource.cpp lines 14–20). Align both sides to use jint IDs via JniSourcesRepo and update the native implementation accordingly.

Suggested change
external override fun nativeRelease(srcId: Int)
external override fun nativeRelease(srcId: Int)
/**
* Opens the native source.
*
* @param srcId The source ID as managed by JniSourcesRepo (not a pointer).
* @param tag The tag for the source.
* @param pullSrcId The pull source ID as managed by JniSourcesRepo (not a pointer).
*
* Note: The IDs must be Ints (jint) and are NOT native pointers. The native implementation
* must use JniSourcesRepo to resolve these IDs to actual source objects.
*/

Copilot uses AI. Check for mistakes.
@vshcryabets vshcryabets requested a review from Copilot October 4, 2025 19:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 48 out of 50 changed files in this pull request and generated 6 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

nativeOpen(
srcId = _srcId.get(),
tag = configuration.tag,
pullSrcId = configuration.pullSource.getSrcId().orElseThrow()
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativeOpen is declared with four parameters (srcId, tag, pullSrcId, consumerId) but is called with only three, which will not compile. Pass the consumerId as well, e.g., use configuration.consumer.getNativeObject().toInt().

Suggested change
pullSrcId = configuration.pullSource.getSrcId().orElseThrow()
pullSrcId = configuration.pullSource.getSrcId().orElseThrow(),
consumerId = configuration.consumer.getNativeObject().toInt()

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +13
class CountConsumer: JniConsumer {
private var nativePtr: Long = 0

override fun getNativeObject(): Long = nativePtr

override fun close() {
if (nativePtr != 0L) {
nativeRelease(nativePtr)
nativePtr = 0L
}
}
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nativePtr is never initialized, so getNativeObject() returns 0 and native calls are no-ops. Initialize nativePtr (e.g., in an init block or factory) by calling nativeCreate(), and ensure it’s released in close().

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +24
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);
env->ReleaseStringUTFChars(tag, native_tag);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

native_tag is retrieved but never used. Remove GetStringUTFChars/ReleaseStringUTFChars or assign the tag into the open configuration if needed to avoid unnecessary JNI calls.

Suggested change
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);
env->ReleaseStringUTFChars(tag, native_tag);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +24
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);
env->ReleaseStringUTFChars(tag, native_tag);
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

native_tag is retrieved but never used. Remove GetStringUTFChars/ReleaseStringUTFChars or assign the tag into the open configuration if needed to avoid unnecessary JNI calls.

Suggested change
const char *native_tag = env->GetStringUTFChars(tag, nullptr);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);
env->ReleaseStringUTFChars(tag, native_tag);
auto openConfig = PullToPushSource::OpenConfiguration();
openConfig.pullSource = std::dynamic_pointer_cast<PullSource>(
JniSourcesRepo::getInstance()->getSource(pullSourceId));
openConfig.consumer = JniSourcesRepo::getInstance()->getConsumer(consumerId);
source->open(openConfig);

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +26
jclass cls = env->FindClass("com/vsh/source/BytePixelBufferFrame");
if (cls != nullptr) {
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard DeleteLocalRef with cls != nullptr. While many JVMs tolerate DeleteLocalRef(nullptr), it’s safer to only delete when a local reference was actually obtained.

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 44
}

env->DeleteLocalRef(cls);
return result;
Copy link

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guard DeleteLocalRef with cls != nullptr. While many JVMs tolerate DeleteLocalRef(nullptr), it’s safer to only delete when a local reference was actually obtained.

Copilot uses AI. Check for mistakes.
@vshcryabets vshcryabets merged commit 15d928e into master Oct 4, 2025
1 check passed
@vshcryabets vshcryabets deleted the update/jni-pull-to-push branch October 4, 2025 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants