Skip to main content

android_media/
mic.rs

1use {
2    super::{constants::*, load_class},
3    jni::{
4        JNIEnv,
5        errors::Error as JniError,
6        objects::{GlobalRef, JByteArray, JObject, JString},
7    },
8    std::{
9        collections::HashMap,
10        error::Error,
11        fmt::{Display, Formatter, Result as FmtResult},
12        mem::transmute,
13        sync::{
14            LazyLock, Mutex, PoisonError,
15            atomic::{AtomicI16, Ordering},
16        },
17    },
18    tokio::sync::oneshot::{Sender, channel, error::RecvError},
19    tracing::error,
20};
21
22#[derive(Debug)]
23pub enum MicError {
24    Jni(JniError),
25    Poison(String),
26    Recv(RecvError),
27    Read(String),
28}
29
30impl Display for MicError {
31    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
32        write!(f, "MicError: ")?;
33        match self {
34            Self::Jni(e) => Display::fmt(e, f),
35            Self::Poison(msg) => write!(f, "PoisonError: {}", msg),
36            Self::Recv(e) => Display::fmt(e, f),
37            Self::Read(msg) => write!(f, "ReadError: {}", msg),
38        }
39    }
40}
41
42impl Error for MicError {}
43
44impl From<JniError> for MicError {
45    fn from(value: JniError) -> Self {
46        Self::Jni(value)
47    }
48}
49
50impl From<RecvError> for MicError {
51    fn from(value: RecvError) -> Self {
52        Self::Recv(value)
53    }
54}
55
56impl<T> From<PoisonError<T>> for MicError {
57    fn from(value: PoisonError<T>) -> Self {
58        Self::Poison(value.to_string())
59    }
60}
61
62type MicResult<T> = Result<T, MicError>;
63
64/// Microphone read task callbacks
65static MIC_READ_CALLBACKS: LazyLock<Mutex<HashMap<i16, Sender<MicResult<Vec<u8>>>>>> =
66    LazyLock::new(|| Mutex::new(Default::default()));
67
68/// Rust wrapper for Android microphone service
69pub struct AudioMicrophone {
70    /// JNI environment
71    env: JNIEnv<'static>,
72    /// Java object instance
73    this: GlobalRef,
74    /// Sample rate (Hz)
75    sample_rate: SampleRate,
76    /// Audio channel count
77    channel_count: i32,
78    /// Audio encoding format
79    audio_format: AudioEncoding,
80}
81
82//noinspection SpellCheckingInspection
83impl AudioMicrophone {
84    /// Create a new AudioMicrophone instance
85    ///
86    /// # Arguments
87    /// * `env` - JNI environment
88    /// * `context` - GlobalRef to context object (used to get the correct ClassLoader)
89    /// * `sample_rate` - Sample rate (e.g., SampleRate::Rate16000)
90    /// * `channel_config` - Channel configuration (ChannelInConfig::Mono or ChannelInConfig::Stereo)
91    /// * `audio_format` - Audio encoding format (AudioEncoding::Pcm8bit, AudioEncoding::Pcm16bit, or AudioEncoding::PcmFloat)
92    ///
93    /// # Returns
94    /// Returns initialized AudioMicrophone instance
95    ///
96    /// # Examples
97    ///
98    /// See the README.md for complete usage examples.
99    pub fn new(
100        mut env: JNIEnv,
101        context: &GlobalRef,
102        sample_rate: SampleRate,
103        channel_config: ChannelInConfig,
104        audio_format: AudioEncoding,
105    ) -> MicResult<Self> {
106        let sample_rate_value = sample_rate.value();
107        let channel_config_value = channel_config.value();
108        let audio_format_value = audio_format.value();
109
110        // Load Mic class using ClassLoader
111        let mic_class = load_class(&mut env, context, "rust.android.Mic")?;
112
113        // Create Mic Java object
114        let mic_obj = env.new_object(
115            &mic_class,
116            "(III)V",
117            &[
118                sample_rate_value.into(),
119                channel_config_value.into(),
120                audio_format_value.into(),
121            ],
122        )?;
123        let this = env.new_global_ref(mic_obj)?;
124
125        // Initialize audio recorder
126        env.call_method(
127            &this,
128            "init",
129            "(IIII)V",
130            &[
131                AudioSource::Mic.value().into(),
132                sample_rate_value.into(),
133                channel_config_value.into(),
134                audio_format_value.into(),
135            ],
136        )?;
137
138        unsafe {
139            Ok(Self {
140                env: transmute(env),
141                this,
142                sample_rate,
143                channel_count: channel_config.channel_count(),
144                audio_format,
145            })
146        }
147    }
148
149    fn get_env(&self) -> JNIEnv<'_> {
150        unsafe { self.env.unsafe_clone() }
151    }
152
153    /// Start recording
154    pub fn start(&self) -> MicResult<()> {
155        self.get_env()
156            .call_method(&self.this, "startRecording", "()V", &[])?;
157
158        Ok(())
159    }
160
161    /// Stop recording
162    pub fn stop(&self) -> MicResult<()> {
163        self.get_env().call_method(&self.this, "stop", "()V", &[])?;
164        Ok(())
165    }
166
167    /// Release resources
168    pub fn release(&self) -> MicResult<()> {
169        self.get_env()
170            .call_method(&self.this, "release", "()V", &[])?;
171
172        Ok(())
173    }
174
175    /// Read audio data asynchronously
176    ///
177    /// # Arguments
178    /// * `duration_ms` - Duration of audio to read in milliseconds
179    ///
180    /// # Returns
181    /// * `Result<Vec<u8>, MicError>` - Audio data (raw PCM data)
182    ///
183    /// # Buffer Design
184    /// To prevent stuttering, the internal buffer size is automatically calculated as 2x the requested duration.
185    /// For example, if you want to read 1 second (1000ms) of data, the internal buffer will prepare at least 2 seconds of data.
186    ///
187    /// # Examples
188    ///
189    /// See README.md for complete usage examples.
190    /// ```
191    pub async fn read(&self, duration_ms: i32) -> MicResult<Vec<u8>> {
192        static ID: AtomicI16 = AtomicI16::new(0);
193        let task_id = ID.fetch_add(1, Ordering::AcqRel);
194        let (tx, rx) = channel();
195
196        {
197            let mut lock = MIC_READ_CALLBACKS.lock()?;
198            lock.insert(task_id, tx);
199        }
200
201        // Calculate number of bytes to read
202        // Formula: duration_ms / 1000 * sample_rate * channel_count * bytes_per_sample
203        let bytes_per_sample = self.audio_format.bytes_per_sample();
204        let samples_per_channel = (duration_ms * self.sample_rate.value()) / 1000;
205        let total_samples = samples_per_channel * self.channel_count;
206        let requested_bytes = total_samples * bytes_per_sample;
207
208        // Buffer design: use 2x requested duration as buffer size to prevent stuttering
209        let buffer_multiplier = 2;
210        let buffer_bytes = requested_bytes * buffer_multiplier;
211
212        // Call Java layer's read method
213        self.get_env()
214            .call_method(
215                &self.this,
216                "performRead",
217                "(SII)V",
218                &[task_id.into(), requested_bytes.into(), buffer_bytes.into()],
219            )?
220            .v()?;
221
222        rx.await?
223    }
224
225    /// Get sample rate
226    pub fn get_sample_rate(&self) -> i32 {
227        self.sample_rate.value()
228    }
229
230    /// Get channel count
231    pub fn get_channel_count(&self) -> i32 {
232        self.channel_count
233    }
234
235    /// Get audio format
236    pub fn get_audio_format(&self) -> i32 {
237        self.audio_format.value()
238    }
239
240    /// Get buffer size
241    pub fn get_buffer_size(&self) -> MicResult<i32> {
242        Ok(self
243            .get_env()
244            .call_method(&self.this, "getBufferSize", "()I", &[])?
245            .i()?)
246    }
247
248    /// Calculate number of bytes for a specified duration
249    pub fn calculate_bytes_for_duration(&self, duration_ms: i32) -> i32 {
250        let bytes_per_sample = self.audio_format.bytes_per_sample();
251        let samples_per_channel = (duration_ms * self.sample_rate.value()) / 1000;
252        samples_per_channel * self.channel_count * bytes_per_sample
253    }
254}
255
256unsafe impl Send for AudioMicrophone {}
257unsafe impl Sync for AudioMicrophone {}
258
259impl Drop for AudioMicrophone {
260    fn drop(&mut self) {
261        if let Err(e) = self.release() {
262            error!(?e, "Dropping error.");
263        }
264    }
265}
266
267//noinspection SpellCheckingInspection
268/// Microphone read success callback
269///
270/// This function is called by the Java layer after audio data is successfully read
271///
272/// # Arguments
273/// * `env` - JNI environment
274/// * `_this` - Java object
275/// * `task_id` - Task ID
276/// * `audio_data` - Audio data byte array
277#[allow(non_snake_case)]
278#[unsafe(no_mangle)]
279extern "C" fn Java_rust_android_Mic_onReadSuccess(
280    env: JNIEnv,
281    _this: JObject,
282    task_id: i16,
283    audio_data: JByteArray,
284) {
285    let Ok(mut lock) = MIC_READ_CALLBACKS.lock() else {
286        return;
287    };
288    let Some(tx) = lock.remove(&task_id) else {
289        return;
290    };
291    drop(lock);
292
293    if audio_data.is_null() {
294        let _ = tx.send(Err(MicError::Read("Read succeeded but audio data is empty".into())));
295        return;
296    }
297
298    let mut buf = vec![0; env.get_array_length(&audio_data).unwrap_or(0) as _];
299    let _ = match env.get_byte_array_region(&audio_data, 0, &mut buf) {
300        Ok(()) => tx.send(Ok(buf.into_iter().map(|i| i as _).collect::<Vec<_>>())),
301        Err(e) => tx.send(Err(e.into())),
302    };
303}
304
305//noinspection SpellCheckingInspection
306/// Microphone read failure callback
307///
308/// This function is called by the Java layer after audio data read fails
309///
310/// # Arguments
311/// * `env` - JNI environment
312/// * `_this` - Java object
313/// * `task_id` - Task ID
314/// * `error_message` - Error message (string)
315#[allow(non_snake_case)]
316#[unsafe(no_mangle)]
317extern "C" fn Java_rust_android_Mic_onReadFailure(
318    mut env: JNIEnv,
319    _this: JObject,
320    task_id: i16,
321    error_message: JString,
322) {
323    let Ok(mut lock) = MIC_READ_CALLBACKS.lock() else {
324        return;
325    };
326    let Some(tx) = lock.remove(&task_id) else {
327        return;
328    };
329
330    let error_msg = match env.get_string(&error_message) {
331        Ok(msg) => msg.to_string_lossy().to_string(),
332        Err(e) => format!("Parsing error failed: {}", e),
333    };
334    if let Err(e) = tx.send(Err(MicError::Read(format!(
335        "Mic read failed: {}",
336        error_msg
337    )))) {
338        error!(?e, "Sending error failed.");
339    }
340}