fix(audio): migrate Android JNI paths
This commit is contained in:
@@ -49,6 +49,7 @@ pub(crate) struct AndroidRenderRingConsumer {
|
||||
}
|
||||
|
||||
impl AndroidRenderRingConsumer {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn drain_into_zero_filling(&self, out: &mut [f32]) {
|
||||
let mut chunks = out.chunks_exact_mut(2);
|
||||
for frame_out in &mut chunks {
|
||||
@@ -59,6 +60,13 @@ impl AndroidRenderRingConsumer {
|
||||
*sample = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn drain_stereo_into_zero_filling(&self, out: &mut [(f32, f32)]) {
|
||||
for frame_out in out {
|
||||
let frame = self.frames.pop().unwrap_or([0.0, 0.0]);
|
||||
*frame_out = (frame[0], frame[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -516,24 +516,39 @@ impl AudioOutputCallback for OutputCallback {
|
||||
frames: &mut [(f32, f32)],
|
||||
) -> DataCallbackResult {
|
||||
let _ = catch_unwind(AssertUnwindSafe(|| {
|
||||
let buf: &mut [f32] = bytemuck::cast_slice_mut::<(f32, f32), f32>(frames);
|
||||
self.pcm_consumer.drain_into_zero_filling(buf);
|
||||
self.pcm_consumer.drain_stereo_into_zero_filling(frames);
|
||||
let gain = f32::from_bits(self.output_gain.load(Ordering::Relaxed));
|
||||
let muted = self.output_muted.load(Ordering::Relaxed);
|
||||
if muted {
|
||||
for s in buf.iter_mut() {
|
||||
*s = 0.0;
|
||||
for frame in frames.iter_mut() {
|
||||
*frame = (0.0, 0.0);
|
||||
}
|
||||
} else if gain != 1.0 {
|
||||
for s in buf.iter_mut() {
|
||||
*s *= gain;
|
||||
for (left, right) in frames.iter_mut() {
|
||||
*left *= gain;
|
||||
*right *= gain;
|
||||
}
|
||||
}
|
||||
let mut sum_squares = 0.0_f32;
|
||||
for (left, right) in frames.iter() {
|
||||
sum_squares += left * left + right * right;
|
||||
}
|
||||
let sample_count = frames.len() * 2;
|
||||
let dbfs = if sample_count == 0 {
|
||||
-120.0
|
||||
} else {
|
||||
let rms = (sum_squares / sample_count as f32).sqrt();
|
||||
if rms <= 0.000_001 {
|
||||
-120.0
|
||||
} else {
|
||||
20.0 * rms.log10()
|
||||
}
|
||||
};
|
||||
self.audio_processing_stats
|
||||
.update_render(crate::frame::dbfs(buf), frames.len() as u32);
|
||||
.update_render(dbfs, frames.len() as u32);
|
||||
|
||||
for chunk in buf.chunks_exact(2) {
|
||||
self.pending_render_ref[self.pending_render_ref_len] = (chunk[0] + chunk[1]) * 0.5;
|
||||
for (left, right) in frames.iter() {
|
||||
self.pending_render_ref[self.pending_render_ref_len] = (left + right) * 0.5;
|
||||
self.pending_render_ref_len += 1;
|
||||
if self.pending_render_ref_len == crate::frame::FRAME_10MS_SAMPLES {
|
||||
self.render_reference.write(&self.pending_render_ref);
|
||||
@@ -587,11 +602,13 @@ pub struct AndroidVoiceUnit {
|
||||
|
||||
#[derive(Default)]
|
||||
struct HardwareEffectHandles {
|
||||
aec: Option<jni::objects::GlobalRef>,
|
||||
ns: Option<jni::objects::GlobalRef>,
|
||||
agc: Option<jni::objects::GlobalRef>,
|
||||
aec: Option<AndroidGlobalObject>,
|
||||
ns: Option<AndroidGlobalObject>,
|
||||
agc: Option<AndroidGlobalObject>,
|
||||
}
|
||||
|
||||
type AndroidGlobalObject = jni::refs::Global<jni::objects::JObject<'static>>;
|
||||
|
||||
impl AndroidVoiceUnit {
|
||||
/// Open the input + output streams (SDD-111 + SDD-112) and,
|
||||
/// once a session id is available, attach SDD-113 hardware
|
||||
@@ -1297,33 +1314,11 @@ fn attach_hardware_effects_inner(
|
||||
session_id: AudioSessionId,
|
||||
effects: &crate::AudioEffects,
|
||||
) -> HardwareEffectHandles {
|
||||
let ctx = ndk_context::android_context();
|
||||
if ctx.vm().is_null() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
"android: ndk_context vm null; cannot bind hardware effects (software fallback engages)"
|
||||
);
|
||||
return HardwareEffectHandles::default();
|
||||
}
|
||||
let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as *mut _) } {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, "android: JavaVM::from_raw failed; effects not bound");
|
||||
return HardwareEffectHandles::default();
|
||||
}
|
||||
};
|
||||
let mut env = match jvm.attach_current_thread() {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, "android: attach_current_thread failed; effects not bound");
|
||||
return HardwareEffectHandles::default();
|
||||
}
|
||||
};
|
||||
|
||||
with_android_env("hardware effects", |env| {
|
||||
let mut handles = HardwareEffectHandles::default();
|
||||
if effects.aec {
|
||||
handles.aec = create_effect(
|
||||
&mut env,
|
||||
env,
|
||||
"android/media/audiofx/AcousticEchoCanceler",
|
||||
session_id,
|
||||
"AEC",
|
||||
@@ -1331,7 +1326,7 @@ fn attach_hardware_effects_inner(
|
||||
}
|
||||
if effects.noise_suppression {
|
||||
handles.ns = create_effect(
|
||||
&mut env,
|
||||
env,
|
||||
"android/media/audiofx/NoiseSuppressor",
|
||||
session_id,
|
||||
"NS",
|
||||
@@ -1339,20 +1334,51 @@ fn attach_hardware_effects_inner(
|
||||
}
|
||||
if effects.agc {
|
||||
handles.agc = create_effect(
|
||||
&mut env,
|
||||
env,
|
||||
"android/media/audiofx/AutomaticGainControl",
|
||||
session_id,
|
||||
"AGC",
|
||||
);
|
||||
}
|
||||
handles
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn with_android_env<R>(
|
||||
operation: &str,
|
||||
op: impl for<'local> FnOnce(&mut jni::Env<'local>) -> R,
|
||||
) -> Option<R> {
|
||||
let ctx = ndk_context::android_context();
|
||||
if ctx.vm().is_null() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
operation,
|
||||
"android: ndk_context vm null; JNI call skipped"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm() as *mut _) };
|
||||
match jvm.attach_current_thread(|env| Ok::<R, jni::errors::Error>(op(env))) {
|
||||
Ok(value) => Some(value),
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, operation, "android: attach_current_thread failed");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SDD-113 item 3: probe the static `isAvailable()` on each effect
|
||||
/// class before calling `create(int)`. Returns `false` on any JNI
|
||||
/// failure so the caller engages the software fallback.
|
||||
fn effect_is_available(env: &mut jni::JNIEnv, class: &jni::objects::JClass, label: &str) -> bool {
|
||||
match env.call_static_method(class, "isAvailable", "()Z", &[]) {
|
||||
fn effect_is_available(env: &mut jni::Env<'_>, class: &jni::objects::JClass, label: &str) -> bool {
|
||||
match env.call_static_method(
|
||||
class,
|
||||
jni::jni_str!("isAvailable"),
|
||||
jni::jni_sig!("()Z"),
|
||||
&[],
|
||||
) {
|
||||
Ok(v) => match v.z() {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
@@ -1370,14 +1396,14 @@ fn effect_is_available(env: &mut jni::JNIEnv, class: &jni::objects::JClass, labe
|
||||
}
|
||||
|
||||
fn create_effect(
|
||||
env: &mut jni::JNIEnv,
|
||||
env: &mut jni::Env<'_>,
|
||||
fqcn: &str,
|
||||
session_id: AudioSessionId,
|
||||
label: &str,
|
||||
) -> Option<jni::objects::GlobalRef> {
|
||||
) -> Option<AndroidGlobalObject> {
|
||||
use jni::objects::JValue;
|
||||
// Class.create(int) -> ClassInstance|null
|
||||
let class = match env.find_class(fqcn) {
|
||||
let class = match env.find_class(jni::strings::JNIString::new(fqcn)) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, effect = label, "android: find_class failed; effect not bound — software fallback engages");
|
||||
@@ -1393,10 +1419,18 @@ fn create_effect(
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let create_sig = match jni::signature::RuntimeMethodSignature::from_str(format!("(I)L{fqcn};"))
|
||||
{
|
||||
Ok(sig) => sig,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, effect = label, "android: create() signature parse failed");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let inst = match env.call_static_method(
|
||||
&class,
|
||||
"create",
|
||||
&format!("(I)L{fqcn};"),
|
||||
jni::jni_str!("create"),
|
||||
create_sig.method_signature(),
|
||||
&[JValue::Int(session_id)],
|
||||
) {
|
||||
Ok(v) => match v.l() {
|
||||
@@ -1421,8 +1455,8 @@ fn create_effect(
|
||||
// setEnabled(true) -> int (success code)
|
||||
if let Err(e) = env.call_method(
|
||||
&inst,
|
||||
"setEnabled",
|
||||
"(Z)I",
|
||||
jni::jni_str!("setEnabled"),
|
||||
jni::jni_sig!("(Z)I"),
|
||||
&[JValue::Bool(jni::sys::JNI_TRUE)],
|
||||
) {
|
||||
let _ = env.exception_clear();
|
||||
@@ -1458,34 +1492,28 @@ fn release_hardware_effects_inner(handles: &mut HardwareEffectHandles) {
|
||||
if aec.is_none() && ns.is_none() && agc.is_none() {
|
||||
return;
|
||||
}
|
||||
let ctx = ndk_context::android_context();
|
||||
if ctx.vm().is_null() {
|
||||
return;
|
||||
}
|
||||
// SAFETY: vm is non-null and owned for process lifetime via JNI_OnLoad.
|
||||
let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as *mut _) } {
|
||||
Ok(v) => v,
|
||||
Err(_) => return,
|
||||
};
|
||||
let mut env = match jvm.attach_current_thread() {
|
||||
Ok(e) => e,
|
||||
Err(_) => return,
|
||||
};
|
||||
let _ = with_android_env("release hardware effects", |env| {
|
||||
for (effect, label) in [(aec, "AEC"), (ns, "NS"), (agc, "AGC")] {
|
||||
if let Some(g) = effect {
|
||||
let _ = env.call_method(
|
||||
g.as_obj(),
|
||||
"setEnabled",
|
||||
"(Z)I",
|
||||
jni::jni_str!("setEnabled"),
|
||||
jni::jni_sig!("(Z)I"),
|
||||
&[jni::objects::JValue::Bool(jni::sys::JNI_FALSE)],
|
||||
);
|
||||
let _ = env.exception_clear();
|
||||
let _ = env.call_method(g.as_obj(), "release", "()V", &[]);
|
||||
let _ = env.exception_clear();
|
||||
env.exception_clear();
|
||||
let _ = env.call_method(
|
||||
g.as_obj(),
|
||||
jni::jni_str!("release"),
|
||||
jni::jni_sig!("()V"),
|
||||
&[],
|
||||
);
|
||||
env.exception_clear();
|
||||
drop(g);
|
||||
info!(target: "chanora_audio", effect = label, "android: hardware effect released");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Process-global BackendEvent sender for JNI callbacks --------
|
||||
@@ -1560,7 +1588,7 @@ pub fn chanora_android_stop_voice_service() -> bool {
|
||||
fn call_voice_service_static(method: &str) -> bool {
|
||||
use jni::objects::{JObject, JValue};
|
||||
let ctx = ndk_context::android_context();
|
||||
if ctx.vm().is_null() || ctx.context().is_null() {
|
||||
if ctx.context().is_null() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
method,
|
||||
@@ -1568,34 +1596,19 @@ fn call_voice_service_static(method: &str) -> bool {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// SAFETY: vm/context populated by chanora_bridge::android_init at
|
||||
// JNI_OnLoad + initChanoraContext; both pointers are valid for
|
||||
// the process lifetime.
|
||||
let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as *mut _) } {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, method, "android: JavaVM::from_raw failed");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let mut env = match jvm.attach_current_thread() {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, method, "android: attach_current_thread failed");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
with_android_env("voice foreground service", |env| {
|
||||
// SAFETY: ndk_context::context() is the application Context
|
||||
// jobject; valid global ref for process lifetime.
|
||||
let context_obj = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
|
||||
let class = match load_app_class(&mut env, &context_obj, ANDROID_VOICE_FG_SERVICE_FQCN) {
|
||||
let context_obj = unsafe { JObject::from_raw(env, ctx.context() as jni::sys::jobject) };
|
||||
let class = match load_app_class(env, &context_obj, ANDROID_VOICE_FG_SERVICE_FQCN) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
match env.call_static_method(
|
||||
&class,
|
||||
method,
|
||||
"(Landroid/content/Context;)V",
|
||||
jni::strings::JNIString::new(method),
|
||||
jni::jni_sig!("(Landroid/content/Context;)V"),
|
||||
&[JValue::Object(&context_obj)],
|
||||
) {
|
||||
Ok(_) => {
|
||||
@@ -1603,19 +1616,21 @@ fn call_voice_service_static(method: &str) -> bool {
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = env.exception_clear();
|
||||
env.exception_clear();
|
||||
warn!(target: "chanora_audio", error = %e, method, "android: foreground service static call failed");
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn load_app_class<'local>(
|
||||
env: &mut jni::JNIEnv<'local>,
|
||||
env: &mut jni::Env<'local>,
|
||||
context_obj: &jni::objects::JObject<'local>,
|
||||
slash_name: &str,
|
||||
) -> Option<jni::objects::JClass<'local>> {
|
||||
match env.find_class(slash_name) {
|
||||
match env.find_class(jni::strings::JNIString::new(slash_name)) {
|
||||
Ok(c) => return Some(c),
|
||||
Err(e) => {
|
||||
let _ = env.exception_clear();
|
||||
@@ -1626,8 +1641,8 @@ fn load_app_class<'local>(
|
||||
let loader = match env
|
||||
.call_method(
|
||||
context_obj,
|
||||
"getClassLoader",
|
||||
"()Ljava/lang/ClassLoader;",
|
||||
jni::jni_str!("getClassLoader"),
|
||||
jni::jni_sig!("()Ljava/lang/ClassLoader;"),
|
||||
&[],
|
||||
)
|
||||
.and_then(|v| v.l())
|
||||
@@ -1652,13 +1667,20 @@ fn load_app_class<'local>(
|
||||
match env
|
||||
.call_method(
|
||||
&loader,
|
||||
"loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;",
|
||||
jni::jni_str!("loadClass"),
|
||||
jni::jni_sig!("(Ljava/lang/String;)Ljava/lang/Class;"),
|
||||
&[jni::objects::JValue::Object(&class_name_obj)],
|
||||
)
|
||||
.and_then(|v| v.l())
|
||||
{
|
||||
Ok(class_obj) => Some(jni::objects::JClass::from(class_obj)),
|
||||
Ok(class_obj) => match env.cast_local::<jni::objects::JClass>(class_obj) {
|
||||
Ok(class) => Some(class),
|
||||
Err(e) => {
|
||||
env.exception_clear();
|
||||
warn!(target: "chanora_audio", error = %e, class = %dotted_name, "android: ClassLoader.loadClass returned non-Class object");
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = env.exception_clear();
|
||||
warn!(target: "chanora_audio", error = %e, class = %dotted_name, "android: ClassLoader.loadClass failed");
|
||||
@@ -1689,7 +1711,7 @@ fn load_app_class<'local>(
|
||||
pub extern "system" fn Java_app_chanora_chanora_1flutter_AndroidAudioFocusController_publishFocusChange<
|
||||
'local,
|
||||
>(
|
||||
_env: jni::JNIEnv<'local>,
|
||||
_env: jni::EnvUnowned<'local>,
|
||||
_class: jni::objects::JClass<'local>,
|
||||
state: jni::sys::jint,
|
||||
) {
|
||||
@@ -1727,7 +1749,7 @@ pub extern "system" fn Java_app_chanora_chanora_1flutter_AndroidAudioFocusContro
|
||||
pub extern "system" fn Java_app_chanora_chanora_1flutter_AndroidBluetoothScoController_publishScoStateChange<
|
||||
'local,
|
||||
>(
|
||||
_env: jni::JNIEnv<'local>,
|
||||
_env: jni::EnvUnowned<'local>,
|
||||
_class: jni::objects::JClass<'local>,
|
||||
state: jni::sys::jint,
|
||||
) {
|
||||
@@ -1776,7 +1798,7 @@ pub fn chanora_android_stop_bluetooth_sco() -> bool {
|
||||
fn call_static_void_context(fqcn: &str, method: &str) -> bool {
|
||||
use jni::objects::{JObject, JValue};
|
||||
let ctx = ndk_context::android_context();
|
||||
if ctx.vm().is_null() || ctx.context().is_null() {
|
||||
if ctx.context().is_null() {
|
||||
warn!(
|
||||
target: "chanora_audio",
|
||||
class = fqcn,
|
||||
@@ -1785,29 +1807,17 @@ fn call_static_void_context(fqcn: &str, method: &str) -> bool {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm() as *mut _) } {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, class = fqcn, method, "android: JavaVM::from_raw failed");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let mut env = match jvm.attach_current_thread() {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
warn!(target: "chanora_audio", error = %e, class = fqcn, method, "android: attach_current_thread failed");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let context_obj = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
|
||||
let class = match load_app_class(&mut env, &context_obj, fqcn) {
|
||||
|
||||
with_android_env("static context call", |env| {
|
||||
let context_obj = unsafe { JObject::from_raw(env, ctx.context() as jni::sys::jobject) };
|
||||
let class = match load_app_class(env, &context_obj, fqcn) {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
match env.call_static_method(
|
||||
&class,
|
||||
method,
|
||||
"(Landroid/content/Context;)V",
|
||||
jni::strings::JNIString::new(method),
|
||||
jni::jni_sig!("(Landroid/content/Context;)V"),
|
||||
&[JValue::Object(&context_obj)],
|
||||
) {
|
||||
Ok(_) => {
|
||||
@@ -1815,9 +1825,11 @@ fn call_static_void_context(fqcn: &str, method: &str) -> bool {
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = env.exception_clear();
|
||||
env.exception_clear();
|
||||
warn!(target: "chanora_audio", error = %e, class = fqcn, method, "android: static call failed");
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -2448,6 +2448,13 @@ impl std::fmt::Display for AudioModeError {
|
||||
#[cfg(target_os = "android")]
|
||||
impl std::error::Error for AudioModeError {}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
impl From<jni::errors::Error> for AudioModeError {
|
||||
fn from(value: jni::errors::Error) -> Self {
|
||||
Self::JniAttachFailed(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// JNI helper shared by `android_get_audio_mode` and
|
||||
/// `android_set_audio_mode`: attach to the current thread and return
|
||||
/// the `AudioManager` jobject. Centralised so SDD-108's two platform
|
||||
@@ -2455,7 +2462,10 @@ impl std::error::Error for AudioModeError {}
|
||||
#[cfg(target_os = "android")]
|
||||
fn android_audio_manager_call<F, R>(op: F) -> Result<R, AudioModeError>
|
||||
where
|
||||
F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject) -> Result<R, AudioModeError>
|
||||
F: for<'local> FnOnce(
|
||||
&mut jni::Env<'local>,
|
||||
&jni::objects::JObject<'local>,
|
||||
) -> Result<R, AudioModeError>
|
||||
+ std::panic::UnwindSafe,
|
||||
{
|
||||
use jni::objects::{JObject, JString, JValue};
|
||||
@@ -2472,13 +2482,9 @@ where
|
||||
// at a live JavaVM* set by our bridge_init JNI hook. The
|
||||
// unsafe block contains only the cast required by
|
||||
// `JavaVM::from_raw`.
|
||||
let jvm = unsafe { jni::JavaVM::from_raw(vm_ptr as *mut _) }
|
||||
.map_err(|e| AudioModeError::JniAttachFailed(format!("jvm from_raw: {e}")))?;
|
||||
let mut env = jvm
|
||||
.attach_current_thread()
|
||||
.map_err(|e| AudioModeError::JniAttachFailed(format!("attach: {e}")))?;
|
||||
|
||||
let context_obj = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
|
||||
let jvm = unsafe { jni::JavaVM::from_raw(vm_ptr as *mut _) };
|
||||
jvm.attach_current_thread(|env| -> Result<R, AudioModeError> {
|
||||
let context_obj = unsafe { JObject::from_raw(env, ctx.context() as jni::sys::jobject) };
|
||||
|
||||
let service_name: JString =
|
||||
env.new_string("audio")
|
||||
@@ -2486,12 +2492,13 @@ where
|
||||
method: "new_string",
|
||||
detail: e.to_string(),
|
||||
})?;
|
||||
let service_name_obj = JObject::from(service_name);
|
||||
let audio_manager = env
|
||||
.call_method(
|
||||
&context_obj,
|
||||
"getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;",
|
||||
&[JValue::Object(&service_name.into())],
|
||||
jni::jni_str!("getSystemService"),
|
||||
jni::jni_sig!("(Ljava/lang/String;)Ljava/lang/Object;"),
|
||||
&[JValue::Object(&service_name_obj)],
|
||||
)
|
||||
.map_err(|e| AudioModeError::MethodCallFailed {
|
||||
method: "getSystemService",
|
||||
@@ -2507,7 +2514,8 @@ where
|
||||
"AudioManager service is null".to_string(),
|
||||
));
|
||||
}
|
||||
op(&mut env, &audio_manager)
|
||||
op(env, &audio_manager)
|
||||
})
|
||||
});
|
||||
match result {
|
||||
Ok(inner) => inner,
|
||||
@@ -2526,7 +2534,12 @@ where
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn android_get_audio_mode() -> Result<i32, AudioModeError> {
|
||||
android_audio_manager_call(|env, audio_manager| {
|
||||
env.call_method(audio_manager, "getMode", "()I", &[])
|
||||
env.call_method(
|
||||
audio_manager,
|
||||
jni::jni_str!("getMode"),
|
||||
jni::jni_sig!("()I"),
|
||||
&[],
|
||||
)
|
||||
.map_err(|e| AudioModeError::MethodCallFailed {
|
||||
method: "getMode",
|
||||
detail: e.to_string(),
|
||||
@@ -2549,7 +2562,12 @@ pub fn android_get_audio_mode() -> Result<i32, AudioModeError> {
|
||||
pub fn android_set_audio_mode(mode: i32) -> Result<(), AudioModeError> {
|
||||
use jni::objects::JValue;
|
||||
android_audio_manager_call(move |env, audio_manager| {
|
||||
env.call_method(audio_manager, "setMode", "(I)V", &[JValue::Int(mode)])
|
||||
env.call_method(
|
||||
audio_manager,
|
||||
jni::jni_str!("setMode"),
|
||||
jni::jni_sig!("(I)V"),
|
||||
&[JValue::Int(mode)],
|
||||
)
|
||||
.map_err(|e| AudioModeError::MethodCallFailed {
|
||||
method: "setMode",
|
||||
detail: e.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user