Maybe fix vc

This commit is contained in:
komaruworld committed 2026-09-05 20:06:02 +03:00
1 parent 92c7d7b12b
commit b71873f2a5
7 files changed
+1004 -8

No files matched your search

+79
View File
@@ -1,8 +1,10 @@
#include "mocktail/audio/webrtc_jni_audio_bridge.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <memory>
@@ -11,6 +13,7 @@
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -26,6 +29,7 @@ struct CaptureSession {
jnivm::WebRtcAudioRecordDataCallback data_callback = nullptr;
void* data_context = nullptr;
std::unique_ptr<AudioCapture> capture;
std::shared_ptr<std::atomic<bool>> microphone_muted;
};
struct PlayoutSession {
@@ -46,6 +50,9 @@ struct PlayoutSession {
struct WebRtcJniAudioBridgeContext {
std::mutex mutex;
std::unordered_set<const void*> initialized_managers;
std::shared_ptr<std::atomic<bool>> microphone_muted =
std::make_shared<std::atomic<bool>>(false);
std::unordered_map<const void*, std::shared_ptr<CaptureSession>>
capture_sessions;
std::unordered_map<const void*, std::shared_ptr<PlayoutSession>>
@@ -54,9 +61,71 @@ struct WebRtcJniAudioBridgeContext {
bool shutting_down = false;
};
bool GetManagerParameters(void* opaque_context,
jnivm::WebRtcAudioManagerParameters* parameters) {
auto* context = static_cast<WebRtcJniAudioBridgeContext*>(opaque_context);
if (context == nullptr || parameters == nullptr) return false;
std::lock_guard<std::mutex> lock(context->mutex);
if (context->shutting_down) return false;
// SDL converts between this WebRTC-facing format and the actual device.
// Match WebRtcAudioManager's default mono input/output and 10 ms frames.
// Advertising the format never opens a capture device, including when the
// microphone is disabled. Recording init remains the permission boundary.
*parameters = {};
parameters->sample_rate_hz = 48000;
parameters->output_channels = 1;
parameters->input_channels = 1;
parameters->output_buffer_size_frames = 480;
parameters->input_buffer_size_frames = 480;
// Android hardware effects, low-latency paths and AAudio are not emulated;
// WebRTC must retain its software processing and Java audio transport.
return true;
}
bool InitManager(void* opaque_context, const void* identity) {
auto* context = static_cast<WebRtcJniAudioBridgeContext*>(opaque_context);
if (context == nullptr || identity == nullptr) return false;
bool inserted = false;
{
std::lock_guard<std::mutex> lock(context->mutex);
if (context->shutting_down) return false;
inserted = context->initialized_managers.insert(identity).second;
}
if (inserted) {
std::cout << " [mocktail][audio] WebRTC audio manager initialized\n"
<< std::flush;
}
return true;
}
void DisposeManager(void* opaque_context, const void* identity) {
auto* context = static_cast<WebRtcJniAudioBridgeContext*>(opaque_context);
if (context == nullptr) return;
std::lock_guard<std::mutex> lock(context->mutex);
if (context->initialized_managers.erase(identity) != 0) {
std::cout << " [mocktail][audio] WebRTC audio manager disposed\n"
<< std::flush;
}
// Record/track objects own their streams and stop them independently.
}
void SetMicrophoneMute(void* opaque_context, bool muted) {
auto* context = static_cast<WebRtcJniAudioBridgeContext*>(opaque_context);
if (context == nullptr) return;
context->microphone_muted->store(muted, std::memory_order_release);
}
void OnCapturedAudio(void* opaque_session, std::size_t size_bytes) {
auto* session = static_cast<CaptureSession*>(opaque_session);
if (session != nullptr && session->data_callback != nullptr) {
if (session->microphone_muted != nullptr &&
session->microphone_muted->load(std::memory_order_acquire)) {
if (session->capture == nullptr ||
size_bytes > session->capture->buffer_size_bytes()) {
return;
}
std::memset(session->capture->buffer_data(), 0, size_bytes);
}
session->data_callback(session->data_context, session->identity,
size_bytes);
}
@@ -104,6 +173,7 @@ int Init(void* opaque_context, const void* identity, int sample_rate_hz,
session->identity = identity;
session->data_callback = data_callback;
session->data_context = data_context;
session->microphone_muted = context->microphone_muted;
SdlAudioCaptureOptions options;
options.output_spec.sample_rate_hz = sample_rate_hz;
@@ -480,6 +550,7 @@ void Shutdown(void* opaque_context) {
std::lock_guard<std::mutex> lock(context->mutex);
context->recording_enabled = false;
context->shutting_down = true;
context->initialized_managers.clear();
capture_sessions.swap(context->capture_sessions);
playout_sessions.swap(context->playout_sessions);
}
@@ -544,6 +615,13 @@ Status InstallWebRtcJniAudioBridge(jnivm::VM* vm) {
record_callbacks.shutdown = &Shutdown;
vm->SetWebRtcAudioRecordCallbacks(context, record_callbacks);
jnivm::WebRtcAudioManagerCallbacks manager_callbacks;
manager_callbacks.get_parameters = &GetManagerParameters;
manager_callbacks.init = &InitManager;
manager_callbacks.dispose = &DisposeManager;
manager_callbacks.set_microphone_mute = &SetMicrophoneMute;
vm->SetWebRtcAudioManagerCallbacks(context, manager_callbacks);
jnivm::WebRtcAudioTrackCallbacks track_callbacks;
track_callbacks.init = &InitPlayout;
track_callbacks.buffer_size_frames = &PlayoutBufferSizeFrames;
@@ -561,6 +639,7 @@ Status ShutdownWebRtcJniAudioBridge(jnivm::VM* vm) {
}
// Drop dispatch first, then let the recording binding's shared context stop
// both playout workers and capture streams.
vm->ClearWebRtcAudioManagerCallbacks();
vm->ClearWebRtcAudioTrackCallbacks();
vm->ClearWebRtcAudioRecordCallbacks();
return Status::Ok();
+267 -2
View File
@@ -2944,6 +2944,78 @@ bool IsFmodAudioDeviceMethod(jobject obj, jmethodID method_id,
ObjectClassName(obj) == "org/fmod/AudioDevice";
}
bool IsWebRtcAudioManagerMethod(jobject obj, jmethodID method_id,
const char* name, const char* signature) {
return obj != nullptr && method_id != nullptr &&
std::strcmp(MethodName(method_id), name) == 0 &&
std::strcmp(MethodSignature(method_id), signature) == 0 &&
ObjectClassName(obj) == "org/webrtc/voiceengine/WebRtcAudioManager";
}
bool HandleWebRtcAudioManagerBooleanMethod(jobject obj, jmethodID method_id,
jboolean* result) {
VM* vm = CurrentVM();
if (IsWebRtcAudioManagerMethod(obj, method_id, "init", "()Z")) {
*result = vm != nullptr && vm->DispatchWebRtcAudioManagerInit(obj)
? JNI_TRUE
: JNI_FALSE;
} else if (IsWebRtcAudioManagerMethod(obj, method_id,
"isDeviceBlacklistedForOpenSLESUsage",
"()Z")) {
// Host recording is provided by AudioRecord JNI, not Android OpenSL ES.
*result = JNI_TRUE;
} else if (IsWebRtcAudioManagerMethod(obj, method_id,
"isCommunicationModeEnabled", "()Z")) {
*result = BooleanFieldValue(obj, "initialized");
} else if (IsWebRtcAudioManagerMethod(obj, method_id,
"isLowLatencyOutputSupported", "()Z")) {
*result = BooleanFieldValue(obj, "lowLatencyOutput");
} else if (IsWebRtcAudioManagerMethod(obj, method_id,
"isLowLatencyInputSupported", "()Z")) {
*result = BooleanFieldValue(obj, "lowLatencyInput");
} else if (IsWebRtcAudioManagerMethod(obj, method_id, "isProAudioSupported",
"()Z")) {
*result = BooleanFieldValue(obj, "proAudio");
} else if (IsWebRtcAudioManagerMethod(obj, method_id, "isAAudioSupported",
"()Z")) {
*result = BooleanFieldValue(obj, "aAudio");
} else {
return false;
}
return true;
}
bool HandleWebRtcAudioManagerVoidMethodV(jobject obj, jmethodID method_id,
va_list args) {
VM* vm = CurrentVM();
if (IsWebRtcAudioManagerMethod(obj, method_id, "dispose", "()V")) {
if (vm != nullptr) vm->DispatchWebRtcAudioManagerDispose(obj);
} else if (IsWebRtcAudioManagerMethod(obj, method_id, "setMicrophoneMute",
"(Z)V")) {
const bool muted = va_arg(args, jint) != JNI_FALSE;
if (vm != nullptr) vm->DispatchWebRtcAudioManagerMicrophoneMute(obj, muted);
} else {
return false;
}
return true;
}
bool HandleWebRtcAudioManagerVoidMethodA(jobject obj, jmethodID method_id,
const jvalue* args) {
VM* vm = CurrentVM();
if (IsWebRtcAudioManagerMethod(obj, method_id, "dispose", "()V")) {
if (vm != nullptr) vm->DispatchWebRtcAudioManagerDispose(obj);
} else if (IsWebRtcAudioManagerMethod(obj, method_id, "setMicrophoneMute",
"(Z)V")) {
if (vm != nullptr && args != nullptr) {
vm->DispatchWebRtcAudioManagerMicrophoneMute(obj, args[0].z != JNI_FALSE);
}
} else {
return false;
}
return true;
}
bool IsWebRtcAudioRecordMethod(jobject obj, jmethodID method_id,
const char *name, const char *signature) {
return obj != nullptr && method_id != nullptr &&
@@ -4463,6 +4535,7 @@ void JNICALL CallVoidMethod(JNIEnv * /*env*/, jobject obj, jmethodID methodID,
va_list args;
va_start(args, methodID);
if (!HandleRobloxExperienceLifecycleVoidMethod(obj, methodID) &&
!HandleWebRtcAudioManagerVoidMethodV(obj, methodID, args) &&
!HandleRobloxOpenWebActivityMethodV(obj, methodID, args) &&
!HandleRobloxTextInputInstanceVoidMethodV(obj, methodID, args) &&
!HandleFmodAudioDeviceVoidMethodV(obj, methodID, args) &&
@@ -4493,8 +4566,11 @@ jboolean JNICALL CallBooleanMethod(JNIEnv* /*env*/, jobject obj,
va_list args;
va_start(args, methodID);
jboolean result = JNI_FALSE;
bool handled =
HandleWebRtcAudioRecordBooleanMethodV(obj, methodID, args, &result);
bool handled = HandleWebRtcAudioManagerBooleanMethod(obj, methodID, &result);
if (!handled) {
handled =
HandleWebRtcAudioRecordBooleanMethodV(obj, methodID, args, &result);
}
if (!handled) {
handled =
HandleWebRtcAudioTrackBooleanMethodV(obj, methodID, args, &result);
@@ -4566,6 +4642,14 @@ jobject ConstructObjectV(jclass clazz, jmethodID methodID, va_list args) {
std::strcmp(MethodName(methodID), "<init>") == 0 &&
std::strcmp(MethodSignature(methodID), "(J)V") == 0) {
SetLongFieldRaw(object, "ref", va_arg(args, jlong));
} else if (object_class->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioManager" &&
std::strcmp(MethodName(methodID), "<init>") == 0 &&
std::strcmp(MethodSignature(methodID), "(J)V") == 0) {
const jlong native_audio_manager = va_arg(args, jlong);
if (VM* vm = CurrentVM()) {
vm->DispatchWebRtcAudioManagerConstruct(object, native_audio_manager);
}
} else if (object_class->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioRecord" &&
std::strcmp(MethodName(methodID), "<init>") == 0 &&
@@ -4648,6 +4732,13 @@ jobject ConstructObjectA(jclass clazz, jmethodID methodID, const jvalue *args) {
std::strcmp(MethodName(methodID), "<init>") == 0 &&
std::strcmp(MethodSignature(methodID), "(J)V") == 0) {
SetLongFieldRaw(object, "ref", args[0].j);
} else if (object_class->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioManager" &&
std::strcmp(MethodName(methodID), "<init>") == 0 &&
std::strcmp(MethodSignature(methodID), "(J)V") == 0) {
if (VM* vm = CurrentVM()) {
vm->DispatchWebRtcAudioManagerConstruct(object, args[0].j);
}
} else if (object_class->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioRecord" &&
std::strcmp(MethodName(methodID), "<init>") == 0 &&
@@ -4853,6 +4944,7 @@ VM::~VM() {
ClearRobloxCredentialProvider();
ClearRobloxTextInputCallbacks();
ClearAndroidWindowCallbacks();
ClearWebRtcAudioManagerCallbacks();
ClearWebRtcAudioTrackCallbacks();
ClearWebRtcAudioRecordCallbacks();
ClearFmodAudioDeviceCallbacks();
@@ -5472,6 +5564,163 @@ void OnWebRtcAudioRecordData(void *context, const void *identity,
} // namespace
void VM::SetWebRtcAudioManagerCallbacks(
std::shared_ptr<void> context,
const WebRtcAudioManagerCallbacks& callbacks) {
WebRtcAudioManagerBinding previous;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
previous = std::move(webrtc_audio_manager_binding_);
webrtc_audio_manager_binding_ = {std::move(context), callbacks};
}
}
void VM::ClearWebRtcAudioManagerCallbacks() {
WebRtcAudioManagerBinding previous;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
previous = std::move(webrtc_audio_manager_binding_);
webrtc_audio_manager_binding_ = {};
}
}
void VM::RegisterWebRtcAudioManagerNative(const char* name,
const char* signature,
void* function) {
if (name == nullptr || signature == nullptr || function == nullptr ||
std::strcmp(name, "nativeCacheAudioParameters") != 0 ||
std::strcmp(signature, "(IIIZZZZZZZIIJ)V") != 0) {
return;
}
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
webrtc_cache_audio_parameters_native_ = function;
}
bool VM::DispatchWebRtcAudioManagerConstruct(jobject manager,
jlong native_audio_manager) {
const auto fail = [](const char* reason) {
std::cerr << " [mocktail][audio] WebRTC audio manager parameters_failed "
<< "reason=" << reason << '\n';
return false;
};
if (ObjectClassName(manager) != "org/webrtc/voiceengine/WebRtcAudioManager") {
return fail("invalid receiver");
}
SetLongFieldRaw(manager, "nativeAudioManager", native_audio_manager);
SetBooleanFieldRaw(manager, "initialized", JNI_FALSE);
SetBooleanFieldRaw(manager, "mocktailAudioParametersCached", JNI_FALSE);
WebRtcAudioManagerBinding binding;
void* native = nullptr;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
binding = webrtc_audio_manager_binding_;
native = webrtc_cache_audio_parameters_native_;
}
if (native == nullptr || native_audio_manager == 0) {
return fail("missing nativeCacheAudioParameters or native handle");
}
WebRtcAudioManagerParameters parameters;
if (binding.context == nullptr ||
binding.callbacks.get_parameters == nullptr ||
!binding.callbacks.get_parameters(binding.context.get(), &parameters)) {
return fail("host audio manager unavailable");
}
if (parameters.sample_rate_hz <= 0 || parameters.output_channels < 1 ||
parameters.output_channels > 2 || parameters.input_channels < 1 ||
parameters.input_channels > 2 ||
parameters.output_buffer_size_frames <= 0 ||
parameters.input_buffer_size_frames <= 0) {
return fail("invalid host PCM parameters");
}
SetIntFieldRaw(manager, "sampleRate", parameters.sample_rate_hz);
SetIntFieldRaw(manager, "outputChannels", parameters.output_channels);
SetIntFieldRaw(manager, "inputChannels", parameters.input_channels);
SetIntFieldRaw(manager, "outputBufferSize",
parameters.output_buffer_size_frames);
SetIntFieldRaw(manager, "inputBufferSize",
parameters.input_buffer_size_frames);
SetBooleanFieldRaw(manager, "lowLatencyOutput",
parameters.low_latency_output);
SetBooleanFieldRaw(manager, "lowLatencyInput", parameters.low_latency_input);
SetBooleanFieldRaw(manager, "proAudio", parameters.pro_audio);
SetBooleanFieldRaw(manager, "aAudio", parameters.aaudio);
// Execute the constructor's Java -> native callback before returning the
// object. The guest needs these formats even before it calls init(). No VM
// locks are held here: the native callback may make nested JNI calls.
using CacheAudioParameters = void(JNICALL*)(
JNIEnv*, jobject, jint, jint, jint, jboolean, jboolean, jboolean,
jboolean, jboolean, jboolean, jboolean, jint, jint, jlong);
reinterpret_cast<CacheAudioParameters>(native)(
GetJNIEnv(), manager, parameters.sample_rate_hz,
parameters.output_channels, parameters.input_channels,
parameters.hardware_aec, parameters.hardware_agc, parameters.hardware_ns,
parameters.low_latency_output, parameters.low_latency_input,
parameters.pro_audio, parameters.aaudio,
parameters.output_buffer_size_frames, parameters.input_buffer_size_frames,
native_audio_manager);
SetBooleanFieldRaw(manager, "mocktailAudioParametersCached", JNI_TRUE);
std::cout << " [mocktail][audio] WebRTC audio manager parameters "
<< "sample_rate_hz=" << parameters.sample_rate_hz
<< " output_channels=" << parameters.output_channels
<< " input_channels=" << parameters.input_channels << '\n'
<< std::flush;
return true;
}
bool VM::DispatchWebRtcAudioManagerInit(jobject manager) {
WebRtcAudioManagerBinding binding;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
binding = webrtc_audio_manager_binding_;
}
const bool initialized =
ObjectClassName(manager) == "org/webrtc/voiceengine/WebRtcAudioManager" &&
BooleanFieldValue(manager, "mocktailAudioParametersCached") == JNI_TRUE &&
binding.context != nullptr && binding.callbacks.init != nullptr &&
(BooleanFieldValue(manager, "initialized") == JNI_TRUE ||
binding.callbacks.init(binding.context.get(), manager));
SetBooleanFieldRaw(manager, "initialized",
initialized ? JNI_TRUE : JNI_FALSE);
if (!initialized) {
std::cerr << " [mocktail][audio] WebRTC audio manager init_failed "
"reason=parameters or host manager unavailable\n";
}
return initialized;
}
void VM::DispatchWebRtcAudioManagerDispose(jobject manager) {
if (ObjectClassName(manager) != "org/webrtc/voiceengine/WebRtcAudioManager" ||
BooleanFieldValue(manager, "initialized") != JNI_TRUE) {
return;
}
SetBooleanFieldRaw(manager, "initialized", JNI_FALSE);
WebRtcAudioManagerBinding binding;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
binding = webrtc_audio_manager_binding_;
}
if (binding.context != nullptr && binding.callbacks.dispose != nullptr) {
binding.callbacks.dispose(binding.context.get(), manager);
}
}
void VM::DispatchWebRtcAudioManagerMicrophoneMute(jobject manager, bool muted) {
if (ObjectClassName(manager) != "org/webrtc/voiceengine/WebRtcAudioManager" ||
BooleanFieldValue(manager, "mocktailAudioParametersCached") != JNI_TRUE) {
return;
}
WebRtcAudioManagerBinding binding;
{
std::lock_guard<std::mutex> lock(webrtc_audio_manager_mutex_);
binding = webrtc_audio_manager_binding_;
}
if (binding.context != nullptr &&
binding.callbacks.set_microphone_mute != nullptr) {
binding.callbacks.set_microphone_mute(binding.context.get(), muted);
}
}
void VM::SetWebRtcAudioRecordCallbacks(
std::shared_ptr<void> context,
const WebRtcAudioRecordCallbacks &callbacks) {
@@ -6232,6 +6481,14 @@ void VM::InitJNIFunctionTables() {
} else if (std::strcmp(name, "onTrimMemoryNative") == 0) {
mocktail_gameactivity_on_trim_memory_native = methods[i].fnPtr;
}
if (cls != nullptr &&
cls->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioManager") {
if (VM* vm = CurrentVM()) {
vm->RegisterWebRtcAudioManagerNative(
methods[i].name, methods[i].signature, methods[i].fnPtr);
}
}
if (cls != nullptr &&
cls->GetName() ==
"org/webrtc/voiceengine/WebRtcAudioRecord") {
@@ -6578,6 +6835,7 @@ void VM::InitJNIFunctionTables() {
std::cout << " [JNI] CallVoidMethodV: " << MethodName(methodID) << '\n';
}
if (!HandleRobloxExperienceLifecycleVoidMethod(obj, methodID) &&
!HandleWebRtcAudioManagerVoidMethodV(obj, methodID, args) &&
!HandleRobloxOpenWebActivityMethodV(obj, methodID, args) &&
!HandleRobloxTextInputInstanceVoidMethodV(obj, methodID, args) &&
!HandleFmodAudioDeviceVoidMethodV(obj, methodID, args) &&
@@ -6594,6 +6852,7 @@ void VM::InitJNIFunctionTables() {
std::cout << " [JNI] CallVoidMethodA: " << MethodName(methodID) << '\n';
}
if (!HandleRobloxExperienceLifecycleVoidMethod(obj, methodID) &&
!HandleWebRtcAudioManagerVoidMethodA(obj, methodID, args) &&
!HandleRobloxOpenWebActivityMethodA(obj, methodID, args) &&
!HandleRobloxTextInputInstanceVoidMethodA(obj, methodID, args) &&
!HandleFmodAudioDeviceVoidMethodA(obj, methodID, args) &&
@@ -6669,6 +6928,9 @@ void VM::InitJNIFunctionTables() {
std::cout << " [JNI] CallBooleanMethodV: " << MethodName(methodID) << '\n';
}
jboolean result = JNI_FALSE;
if (HandleWebRtcAudioManagerBooleanMethod(obj, methodID, &result)) {
return result;
}
if (HandleWebRtcAudioRecordBooleanMethodV(obj, methodID, args, &result)) {
return result;
}
@@ -6698,6 +6960,9 @@ void VM::InitJNIFunctionTables() {
std::cout << " [JNI] CallBooleanMethodA: " << MethodName(methodID) << '\n';
}
jboolean result = JNI_FALSE;
if (HandleWebRtcAudioManagerBooleanMethod(obj, methodID, &result)) {
return result;
}
if (HandleWebRtcAudioRecordBooleanMethodA(obj, methodID, args, &result)) {
return result;
}