From 2daf09c4b09ec72c07f0420885216919a9f718c1 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 00:59:34 +0900 Subject: [PATCH 01/51] ndk: Add AMidi interface --- ndk/src/lib.rs | 1 + ndk/src/media/mod.rs | 2 +- ndk/src/midi.rs | 271 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 ndk/src/midi.rs diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 00a69da0..264d9bb3 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -16,6 +16,7 @@ pub mod hardware_buffer_format; pub mod input_queue; pub mod looper; pub mod media; +pub mod midi; pub mod native_activity; pub mod native_window; pub mod surface_texture; diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index cf380e9d..d280f19d 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -12,7 +12,7 @@ use std::{mem::MaybeUninit, ptr::NonNull}; pub type Result = std::result::Result; -fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { +pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { let mut result = MaybeUninit::uninit(); let status = with_ptr(result.as_mut_ptr()); NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() }) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs new file mode 100644 index 00000000..d3513f8a --- /dev/null +++ b/ndk/src/midi.rs @@ -0,0 +1,271 @@ +//! Bindings for [`AMidiDevice`], [`AMidiInputPort`], and [`AMidiOutputPort`] +//! +//! See [the NDK guide](https://developer.android.com/ndk/guides/audio/midi) for +//! design and usage instructions, and [the NDK reference](https://developer.android.com/ndk/reference/group/midi) +//! for an API overview. +//! +//! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice +//! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport +//! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport +#![cfg(feature = "media")] + +pub use super::media::Result; +use super::media::{construct, NdkMediaError}; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::fmt; +use std::marker::PhantomData; +use std::os::raw::{c_int, c_uint}; +use std::ptr::NonNull; + +/// Result of [`MidiOutputPort::receive`]. +#[derive(Copy, Clone, Debug)] +#[repr(u32)] +pub enum MidiOpcode { + /// No MIDI messages are available. + NoMessage, + /// Received a MIDI message with the given length. + Data(usize), + /// Instructed to discard all pending MIDI data. + Flush, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[repr(u32)] +pub enum MidiDeviceType { + Bluetooth = ffi::AMIDI_DEVICE_TYPE_BLUETOOTH, + USB = ffi::AMIDI_DEVICE_TYPE_USB, + Virtual = ffi::AMIDI_DEVICE_TYPE_VIRTUAL, +} + +#[derive(Debug)] +pub struct MidiDevice { + inner: NonNull, +} + +impl MidiDevice { + fn from_ptr(inner: NonNull) -> Self { + Self { inner } + } + + fn as_ptr(&self) -> *mut ffi::AMidiDevice { + self.inner.as_ptr() + } + + /// Connects a native Midi Device object to the associated Java MidiDevice object. + /// + /// Use this AMidiDevice to access the rest of the native MIDI API. + pub fn from_java( + env: *mut jni_sys::JNIEnv, + midi_device_obj: jni_sys::jobject, + ) -> Result { + unsafe { + let ptr = construct(|res| ffi::AMidiDevice_fromJava(env, midi_device_obj, res))?; + Ok(Self::from_ptr(NonNull::new_unchecked(ptr))) + } + } + + /// Gets the number of input (sending) ports available on the specified MIDI device. + pub fn get_num_input_ports(&self) -> Result { + let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.as_ptr()) }; + if num_input_ports >= 0 { + Ok(num_input_ports as usize) + } else { + NdkMediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).map(|_| 0) + } + } + + /// Gets the number of output (receiving) ports available on the specified MIDI device. + pub fn get_num_output_ports(&self) -> Result { + let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.as_ptr()) }; + if num_output_ports >= 0 { + Ok(num_output_ports as usize) + } else { + Err( + NdkMediaError::from_status(ffi::media_status_t(num_output_ports as c_int)) + .unwrap_err(), + ) + } + } + + /// Gets the MIDI device type. + pub fn get_type(&self) -> Result { + let device_type = unsafe { ffi::AMidiDevice_getType(self.as_ptr()) }; + if device_type >= 0 { + let device_type = + MidiDeviceType::try_from_primitive(device_type as u32).map_err(|e| { + NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) + })?; + Ok(device_type) + } else { + Err(NdkMediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) + } + } + + /// Opens the input port so that the client can send data to it. + pub fn open_input_port<'a>(&'a self, port_number: i32) -> Result> { + unsafe { + let input_port = + construct(|res| ffi::AMidiInputPort_open(self.as_ptr(), port_number, res))?; + Ok(MidiInputPort::from_ptr(NonNull::new_unchecked(input_port))) + } + } + + /// Opens the output port so that the client can receive data from it. + pub fn open_output_port<'a>(&'a self, port_number: i32) -> Result> { + unsafe { + let output_port = + construct(|res| ffi::AMidiOutputPort_open(self.as_ptr(), port_number, res))?; + Ok(MidiOutputPort::from_ptr(NonNull::new_unchecked( + output_port, + ))) + } + } +} + +impl Drop for MidiDevice { + fn drop(&mut self) { + let status = unsafe { ffi::AMidiDevice_release(self.as_ptr()) }; + NdkMediaError::from_status(status).unwrap(); + } +} + +pub struct MidiInputPort<'a> { + inner: NonNull, + _marker: PhantomData<&'a MidiDevice>, +} + +impl<'a> fmt::Debug for MidiInputPort<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MidiInputPort") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a> MidiInputPort<'a> { + fn from_ptr(inner: NonNull) -> Self { + Self { + inner, + _marker: PhantomData, + } + } + + fn as_ptr(&self) -> *mut ffi::AMidiInputPort { + self.inner.as_ptr() + } + + /// Sends data to the specified input port. + pub fn send(&self, buffer: &[u8]) -> Result { + let num_bytes_sent = unsafe { + ffi::AMidiInputPort_send(self.as_ptr(), buffer.as_ptr(), buffer.len() as ffi::size_t) + }; + if num_bytes_sent >= 0 { + Ok(num_bytes_sent as usize) + } else { + Err( + NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) + .unwrap_err(), + ) + } + } + + /// Sends a message with a 'MIDI flush command code' to the specified port. + /// + /// This should cause a receiver to discard any pending MIDI data it may have accumulated and + /// not processed. + pub fn send_flush(&self) -> Result<()> { + let result = unsafe { ffi::AMidiInputPort_sendFlush(self.as_ptr()) }; + NdkMediaError::from_status(result) + } + + pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result { + let num_bytes_sent = unsafe { + ffi::AMidiInputPort_sendWithTimestamp( + self.as_ptr(), + buffer.as_ptr(), + buffer.len() as ffi::size_t, + timestamp, + ) + }; + if num_bytes_sent >= 0 { + Ok(num_bytes_sent as usize) + } else { + Err( + NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) + .unwrap_err(), + ) + } + } +} + +impl<'a> Drop for MidiInputPort<'a> { + fn drop(&mut self) { + unsafe { ffi::AMidiInputPort_close(self.as_ptr()) }; + } +} + +pub struct MidiOutputPort<'a> { + inner: NonNull, + _marker: PhantomData<&'a MidiDevice>, +} + +impl<'a> fmt::Debug for MidiOutputPort<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MidiOutputPort") + .field("inner", &self.inner) + .finish() + } +} + +impl<'a> MidiOutputPort<'a> { + fn from_ptr(inner: NonNull) -> Self { + Self { + inner, + _marker: PhantomData, + } + } + + fn as_ptr(&self) -> *mut ffi::AMidiOutputPort { + self.inner.as_ptr() + } + + /// Receives the next pending MIDI message. + /// + /// To retrieve all pending messages, the client should repeatedly call this method until it + /// returns `Some(_)`. + /// + /// Note that this is a non-blocking call. If there are no Midi messages are available, the + /// function returns `None` immediately (for 0 messages received). + pub fn receive(&self, buffer: &mut [u8]) -> Result<(MidiOpcode, i64)> { + let mut opcode = 0i32; + let mut timestamp = 0i64; + let mut num_bytes_received: ffi::size_t = 0; + let result = unsafe { + ffi::AMidiOutputPort_receive( + self.as_ptr(), + &mut opcode, + buffer.as_mut_ptr(), + buffer.len() as ffi::size_t, + &mut num_bytes_received, + &mut timestamp, + ) + }; + + if result < 0 { + Err(NdkMediaError::from_status(ffi::media_status_t(result as c_int)).unwrap_err()) + } else if result == 0 { + Ok((MidiOpcode::NoMessage, timestamp)) + } else if opcode as c_uint == ffi::AMIDI_OPCODE_DATA { + Ok((MidiOpcode::Data(num_bytes_received as usize), timestamp)) + } else { + Ok((MidiOpcode::Flush, timestamp)) + } + } +} + +impl<'a> Drop for MidiOutputPort<'a> { + fn drop(&mut self) { + unsafe { ffi::AMidiOutputPort_close(self.as_ptr()) }; + } +} From 9456589f1c01390aab79c29a7469a4c77778c442 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 01:10:32 +0900 Subject: [PATCH 02/51] ndk: Remove needless explicit lifetimes in midi.rs --- ndk/src/midi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index d3513f8a..65008aad 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -103,7 +103,7 @@ impl MidiDevice { } /// Opens the input port so that the client can send data to it. - pub fn open_input_port<'a>(&'a self, port_number: i32) -> Result> { + pub fn open_input_port(&self, port_number: i32) -> Result { unsafe { let input_port = construct(|res| ffi::AMidiInputPort_open(self.as_ptr(), port_number, res))?; @@ -112,7 +112,7 @@ impl MidiDevice { } /// Opens the output port so that the client can receive data from it. - pub fn open_output_port<'a>(&'a self, port_number: i32) -> Result> { + pub fn open_output_port(&self, port_number: i32) -> Result { unsafe { let output_port = construct(|res| ffi::AMidiOutputPort_open(self.as_ptr(), port_number, res))?; From 9cf85816b4069bd5690a14b5d0de267abf6024dc Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 01:17:37 +0900 Subject: [PATCH 03/51] ndk: Fix mismatching doc comments in midi.rs --- ndk/src/midi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 65008aad..71d34735 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -233,10 +233,10 @@ impl<'a> MidiOutputPort<'a> { /// Receives the next pending MIDI message. /// /// To retrieve all pending messages, the client should repeatedly call this method until it - /// returns `Some(_)`. + /// returns [`MidiOpcode::NoMessage`]. /// /// Note that this is a non-blocking call. If there are no Midi messages are available, the - /// function returns `None` immediately (for 0 messages received). + /// function returns [`MidiOpcode::NoMessage`] immediately (for 0 messages received). pub fn receive(&self, buffer: &mut [u8]) -> Result<(MidiOpcode, i64)> { let mut opcode = 0i32; let mut timestamp = 0i64; From 9f7c9ef94d4ab723e539d6879b1c55b1f2785908 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 01:21:35 +0900 Subject: [PATCH 04/51] ndk: Remove get_ prefix from midi.rs --- ndk/src/midi.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 71d34735..3dd5eff3 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -66,7 +66,7 @@ impl MidiDevice { } /// Gets the number of input (sending) ports available on the specified MIDI device. - pub fn get_num_input_ports(&self) -> Result { + pub fn num_input_ports(&self) -> Result { let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.as_ptr()) }; if num_input_ports >= 0 { Ok(num_input_ports as usize) @@ -76,7 +76,7 @@ impl MidiDevice { } /// Gets the number of output (receiving) ports available on the specified MIDI device. - pub fn get_num_output_ports(&self) -> Result { + pub fn num_output_ports(&self) -> Result { let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.as_ptr()) }; if num_output_ports >= 0 { Ok(num_output_ports as usize) @@ -89,7 +89,7 @@ impl MidiDevice { } /// Gets the MIDI device type. - pub fn get_type(&self) -> Result { + pub fn device_type(&self) -> Result { let device_type = unsafe { ffi::AMidiDevice_getType(self.as_ptr()) }; if device_type >= 0 { let device_type = From 466bf4af39e33c0646e15d33afe4c5d58408bfd4 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 01:26:27 +0900 Subject: [PATCH 05/51] ndk: Fix doc comments of MidiOutputPort::receive --- ndk/src/midi.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 3dd5eff3..1e636e62 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -44,7 +44,12 @@ pub struct MidiDevice { } impl MidiDevice { - fn from_ptr(inner: NonNull) -> Self { + /// Creates an `MidiDevice` from a pointer. + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to a native + /// `AMidiDevice`. + pub unsafe fn from_ptr(inner: NonNull) -> Self { Self { inner } } @@ -144,7 +149,12 @@ impl<'a> fmt::Debug for MidiInputPort<'a> { } impl<'a> MidiInputPort<'a> { - fn from_ptr(inner: NonNull) -> Self { + /// Creates an `MidiInputPort` from a pointer. + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to a native + /// `AMidiInputPort`. + pub unsafe fn from_ptr(inner: NonNull) -> Self { Self { inner, _marker: PhantomData, @@ -219,7 +229,12 @@ impl<'a> fmt::Debug for MidiOutputPort<'a> { } impl<'a> MidiOutputPort<'a> { - fn from_ptr(inner: NonNull) -> Self { + /// Creates an `MidiOutputPort` from a pointer. + /// + /// # Safety + /// By calling this function, you assert that the pointer is a valid pointer to a native + /// `AMidiOutputPort`. + pub unsafe fn from_ptr(inner: NonNull) -> Self { Self { inner, _marker: PhantomData, @@ -233,10 +248,10 @@ impl<'a> MidiOutputPort<'a> { /// Receives the next pending MIDI message. /// /// To retrieve all pending messages, the client should repeatedly call this method until it - /// returns [`MidiOpcode::NoMessage`]. + /// returns [`Ok(MidiOpcode::NoMessage)`]. /// /// Note that this is a non-blocking call. If there are no Midi messages are available, the - /// function returns [`MidiOpcode::NoMessage`] immediately (for 0 messages received). + /// function returns [`Ok(MidiOpcode::NoMessage)`] immediately (for 0 messages received). pub fn receive(&self, buffer: &mut [u8]) -> Result<(MidiOpcode, i64)> { let mut opcode = 0i32; let mut timestamp = 0i64; From 4013f682dde8c9e130d1a3ac50995ab7d8f523ce Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 11:39:19 +0900 Subject: [PATCH 06/51] ndk-sys: Add feature `midi` Added Cargo feature `midi` and optional linkage to `libamidi.so` --- ndk-sys/Cargo.toml | 1 + ndk-sys/src/lib.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/ndk-sys/Cargo.toml b/ndk-sys/Cargo.toml index 7c2ff123..ea54d2ff 100644 --- a/ndk-sys/Cargo.toml +++ b/ndk-sys/Cargo.toml @@ -19,6 +19,7 @@ test = [] audio = [] bitmap = [] media = [] +midi = [] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/ndk-sys/src/lib.rs b/ndk-sys/src/lib.rs index e2fe0814..a85f7a71 100644 --- a/ndk-sys/src/lib.rs +++ b/ndk-sys/src/lib.rs @@ -51,3 +51,7 @@ extern "C" {} #[cfg(all(feature = "audio", target_os = "android"))] #[link(name = "aaudio")] extern "C" {} + +#[cfg(all(feature = "midi", target_os = "android"))] +#[link(name = "amidi")] +extern "C" {} From ff6697c1be316e16da5adfffbe2bfb359994c09d Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 11:42:04 +0900 Subject: [PATCH 07/51] ndk: Add feature `midi` Added Cargo feature `midi`, which enables `ffi/midi`, `api-level-29`, and NdkMediaError in `media` --- ndk/Cargo.toml | 1 + ndk/src/media/mod.rs | 12 ++++++++++-- ndk/src/midi.rs | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ndk/Cargo.toml b/ndk/Cargo.toml index 76af756d..3dd3de0f 100644 --- a/ndk/Cargo.toml +++ b/ndk/Cargo.toml @@ -17,6 +17,7 @@ all = ["audio", "bitmap","media", "api-level-30"] audio = ["ffi/audio", "api-level-26"] bitmap = ["ffi/bitmap"] media = ["ffi/media"] +midi = ["ffi/midi", "api-level-29"] api-level-23 = [] api-level-24 = ["api-level-23"] diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index d280f19d..1cff068e 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -1,14 +1,20 @@ //! Bindings for the NDK media classes. //! //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media) -#![cfg(feature = "media")] +#![cfg(any(feature = "media", feature = "midi"))] mod error; + +#[cfg(feature = "media")] pub mod image_reader; +#[cfg(feature = "media")] pub mod media_codec; pub use error::NdkMediaError; -use std::{mem::MaybeUninit, ptr::NonNull}; +use std::mem::MaybeUninit; + +#[cfg(feature = "media")] +use std::ptr::NonNull; pub type Result = std::result::Result; @@ -18,6 +24,7 @@ pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() }) } +#[cfg(feature = "media")] fn construct_never_null( with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, ) -> Result> { @@ -35,6 +42,7 @@ fn construct_never_null( /// /// As such this function always asserts on `null` values, /// even when `cfg!(debug_assertions)` is disabled. +#[cfg(feature = "media")] fn get_unlikely_to_be_null(get_ptr: impl FnOnce() -> *mut T) -> NonNull { let result = get_ptr(); NonNull::new(result).expect("result should never be null") diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 1e636e62..7a99a445 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -7,7 +7,7 @@ //! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice //! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport //! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport -#![cfg(feature = "media")] +#![cfg(feature = "midi")] pub use super::media::Result; use super::media::{construct, NdkMediaError}; From 02630b0bbcdf20e658173e07e6b3deed8b4c18f6 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 18:31:54 +0900 Subject: [PATCH 08/51] ndk: Make Midi types implement Send --- ndk/src/midi.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 7a99a445..3709ace3 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -18,6 +18,14 @@ use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; use std::ptr::NonNull; +// There is no mention about thread-safety in the NDK reference, nut the official Android C++ MIDI +// sample stores `AMidiDevice*` and `AMidi{InputOutput}Port*` in global variables and accesses the +// ports from separate threads. +// See https://github.com/android/ndk-samples/blob/main/native-midi/app/src/main/cpp/AppMidiManager.cpp +unsafe impl Send for MidiDevice {} +unsafe impl<'a> Send for MidiInputPort<'a> {} +unsafe impl<'a> Send for MidiOutputPort<'a> {} + /// Result of [`MidiOutputPort::receive`]. #[derive(Copy, Clone, Debug)] #[repr(u32)] From d3d616e803d28c30ccd5d8670c5f309d1807b09d Mon Sep 17 00:00:00 2001 From: paxbun Date: Wed, 5 Oct 2022 18:41:59 +0900 Subject: [PATCH 09/51] ndk: Update ndk/src/midi.rs Fixed typos in comments about thread-safety in midi.rs Co-authored-by: Marijn Suijten --- ndk/src/midi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 3709ace3..07b64716 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -18,8 +18,8 @@ use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; use std::ptr::NonNull; -// There is no mention about thread-safety in the NDK reference, nut the official Android C++ MIDI -// sample stores `AMidiDevice*` and `AMidi{InputOutput}Port*` in global variables and accesses the +// There is no mention about thread-safety in the NDK reference, but the official Android C++ MIDI +// sample stores `AMidiDevice *` and `AMidi{Input,Output}Port *` in global variables and accesses the // ports from separate threads. // See https://github.com/android/ndk-samples/blob/main/native-midi/app/src/main/cpp/AppMidiManager.cpp unsafe impl Send for MidiDevice {} From 4f53506903998c000475def0b5a66b44b5796506 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 18:33:26 +0900 Subject: [PATCH 10/51] ndk: Fix mismatching doc comments in midi.rs --- ndk/src/midi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 07b64716..50a08a62 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -67,7 +67,7 @@ impl MidiDevice { /// Connects a native Midi Device object to the associated Java MidiDevice object. /// - /// Use this AMidiDevice to access the rest of the native MIDI API. + /// Use the returned [`MidiDevice`] to access the rest of the native MIDI API. pub fn from_java( env: *mut jni_sys::JNIEnv, midi_device_obj: jni_sys::jobject, From 15b917febf6f3045e3433d3fbf714a0d5ab61520 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 18:38:00 +0900 Subject: [PATCH 11/51] ndk: Fix link to ndk-samples with a permalink --- ndk/src/midi.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 50a08a62..af8dada3 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -21,7 +21,7 @@ use std::ptr::NonNull; // There is no mention about thread-safety in the NDK reference, but the official Android C++ MIDI // sample stores `AMidiDevice *` and `AMidi{Input,Output}Port *` in global variables and accesses the // ports from separate threads. -// See https://github.com/android/ndk-samples/blob/main/native-midi/app/src/main/cpp/AppMidiManager.cpp +// See https://github.com/android/ndk-samples/blob/7f6936ea044ee29c36b5c3ebd62bb3a64e1e6014/native-midi/app/src/main/cpp/AppMidiManager.cpp unsafe impl Send for MidiDevice {} unsafe impl<'a> Send for MidiInputPort<'a> {} unsafe impl<'a> Send for MidiOutputPort<'a> {} @@ -105,10 +105,9 @@ impl MidiDevice { pub fn device_type(&self) -> Result { let device_type = unsafe { ffi::AMidiDevice_getType(self.as_ptr()) }; if device_type >= 0 { - let device_type = - MidiDeviceType::try_from_primitive(device_type as u32).map_err(|e| { - NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) - })?; + let device_type = MidiDeviceType::try_from(device_type as u32).map_err(|e| { + NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) + })?; Ok(device_type) } else { Err(NdkMediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) From 5ae1cad34cfc160ca0ab5509730e83d8733d7e61 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 18:48:57 +0900 Subject: [PATCH 12/51] ndk: Replace try_from_primitives in midi.rs with try_from --- ndk/src/midi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index af8dada3..1ee00b5b 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -13,6 +13,7 @@ pub use super::media::Result; use super::media::{construct, NdkMediaError}; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::convert::TryFrom; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; @@ -28,7 +29,6 @@ unsafe impl<'a> Send for MidiOutputPort<'a> {} /// Result of [`MidiOutputPort::receive`]. #[derive(Copy, Clone, Debug)] -#[repr(u32)] pub enum MidiOpcode { /// No MIDI messages are available. NoMessage, @@ -38,8 +38,8 @@ pub enum MidiOpcode { Flush, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(u32)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] pub enum MidiDeviceType { Bluetooth = ffi::AMIDI_DEVICE_TYPE_BLUETOOTH, USB = ffi::AMIDI_DEVICE_TYPE_USB, From 06ebda5b9370aec7f4477446b80a981b78e45322 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 19:25:23 +0900 Subject: [PATCH 13/51] ndk: Make MidiOutputPort::receive return timestamp only with MidiOpcode::Data --- ndk/src/midi.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 1ee00b5b..0b3082c3 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -32,8 +32,8 @@ unsafe impl<'a> Send for MidiOutputPort<'a> {} pub enum MidiOpcode { /// No MIDI messages are available. NoMessage, - /// Received a MIDI message with the given length. - Data(usize), + /// Received a MIDI message with the given length and the timestamp. + Data { length: usize, timestamp: i64 }, /// Instructed to discard all pending MIDI data. Flush, } @@ -259,7 +259,7 @@ impl<'a> MidiOutputPort<'a> { /// /// Note that this is a non-blocking call. If there are no Midi messages are available, the /// function returns [`Ok(MidiOpcode::NoMessage)`] immediately (for 0 messages received). - pub fn receive(&self, buffer: &mut [u8]) -> Result<(MidiOpcode, i64)> { + pub fn receive(&self, buffer: &mut [u8]) -> Result { let mut opcode = 0i32; let mut timestamp = 0i64; let mut num_bytes_received: ffi::size_t = 0; @@ -277,11 +277,14 @@ impl<'a> MidiOutputPort<'a> { if result < 0 { Err(NdkMediaError::from_status(ffi::media_status_t(result as c_int)).unwrap_err()) } else if result == 0 { - Ok((MidiOpcode::NoMessage, timestamp)) + Ok(MidiOpcode::NoMessage) } else if opcode as c_uint == ffi::AMIDI_OPCODE_DATA { - Ok((MidiOpcode::Data(num_bytes_received as usize), timestamp)) + Ok(MidiOpcode::Data { + length: num_bytes_received as usize, + timestamp, + }) } else { - Ok((MidiOpcode::Flush, timestamp)) + Ok(MidiOpcode::Flush) } } } From 2c92479baa2fa6c641ecb3423a52c9f53342e801 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 19:34:26 +0900 Subject: [PATCH 14/51] ndk: Add ptr exposing functions to wrappers in midi.rs --- ndk/src/midi.rs | 55 ++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 0b3082c3..b35004cc 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -48,21 +48,24 @@ pub enum MidiDeviceType { #[derive(Debug)] pub struct MidiDevice { - inner: NonNull, + ptr: NonNull, } impl MidiDevice { - /// Creates an `MidiDevice` from a pointer. + /// Assumes ownership of `ptr` /// /// # Safety - /// By calling this function, you assert that the pointer is a valid pointer to a native - /// `AMidiDevice`. - pub unsafe fn from_ptr(inner: NonNull) -> Self { - Self { inner } + /// `ptr` must be a valid pointer to an Android [`ffi::AMidiDevice`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self { ptr } + } + + pub fn ptr(&self) -> NonNull { + self.ptr } fn as_ptr(&self) -> *mut ffi::AMidiDevice { - self.inner.as_ptr() + self.ptr.as_ptr() } /// Connects a native Midi Device object to the associated Java MidiDevice object. @@ -143,33 +146,36 @@ impl Drop for MidiDevice { } pub struct MidiInputPort<'a> { - inner: NonNull, + ptr: NonNull, _marker: PhantomData<&'a MidiDevice>, } impl<'a> fmt::Debug for MidiInputPort<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MidiInputPort") - .field("inner", &self.inner) + .field("inner", &self.ptr) .finish() } } impl<'a> MidiInputPort<'a> { - /// Creates an `MidiInputPort` from a pointer. + /// Assumes ownership of `ptr` /// /// # Safety - /// By calling this function, you assert that the pointer is a valid pointer to a native - /// `AMidiInputPort`. - pub unsafe fn from_ptr(inner: NonNull) -> Self { + /// `ptr` must be a valid pointer to an Android [`ffi::AMidiInputPort`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { Self { - inner, + ptr, _marker: PhantomData, } } + pub fn ptr(&self) -> NonNull { + self.ptr + } + fn as_ptr(&self) -> *mut ffi::AMidiInputPort { - self.inner.as_ptr() + self.ptr.as_ptr() } /// Sends data to the specified input port. @@ -223,33 +229,36 @@ impl<'a> Drop for MidiInputPort<'a> { } pub struct MidiOutputPort<'a> { - inner: NonNull, + ptr: NonNull, _marker: PhantomData<&'a MidiDevice>, } impl<'a> fmt::Debug for MidiOutputPort<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("MidiOutputPort") - .field("inner", &self.inner) + .field("inner", &self.ptr) .finish() } } impl<'a> MidiOutputPort<'a> { - /// Creates an `MidiOutputPort` from a pointer. + /// Assumes ownership of `ptr` /// /// # Safety - /// By calling this function, you assert that the pointer is a valid pointer to a native - /// `AMidiOutputPort`. - pub unsafe fn from_ptr(inner: NonNull) -> Self { + /// `ptr` must be a valid pointer to an Android [`ffi::AMidiOutputPort`]. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { Self { - inner, + ptr, _marker: PhantomData, } } + pub fn ptr(&self) -> NonNull { + self.ptr + } + fn as_ptr(&self) -> *mut ffi::AMidiOutputPort { - self.inner.as_ptr() + self.ptr.as_ptr() } /// Receives the next pending MIDI message. From b2ec4ea7fd41d7ba0dce3b3fd48da70e5e01bb8e Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 20:39:21 +0900 Subject: [PATCH 15/51] ndk: Raise error when 2 or more msgs received in MidiOutputPort --- ndk/src/midi.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index b35004cc..ea16fae7 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -272,7 +272,7 @@ impl<'a> MidiOutputPort<'a> { let mut opcode = 0i32; let mut timestamp = 0i64; let mut num_bytes_received: ffi::size_t = 0; - let result = unsafe { + let num_messages_received = unsafe { ffi::AMidiOutputPort_receive( self.as_ptr(), &mut opcode, @@ -283,17 +283,17 @@ impl<'a> MidiOutputPort<'a> { ) }; - if result < 0 { - Err(NdkMediaError::from_status(ffi::media_status_t(result as c_int)).unwrap_err()) - } else if result == 0 { - Ok(MidiOpcode::NoMessage) - } else if opcode as c_uint == ffi::AMIDI_OPCODE_DATA { - Ok(MidiOpcode::Data { + match num_messages_received { + r if r < 0 => { + Err(NdkMediaError::from_status(ffi::media_status_t(r as c_int)).unwrap_err()) + } + 0 => Ok(MidiOpcode::NoMessage), + 1 if opcode as c_uint == ffi::AMIDI_OPCODE_DATA => Ok(MidiOpcode::Data { length: num_bytes_received as usize, timestamp, - }) - } else { - Ok(MidiOpcode::Flush) + }), + 1 => Ok(MidiOpcode::Flush), + r => unreachable!("Result is positive integer {}", r), } } } From 6f262ccdf74cde5a340f21d8eed14d7124b3e936 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 20:41:04 +0900 Subject: [PATCH 16/51] ndk: Remove as_ptr() from structs in midi.rs --- ndk/src/midi.rs | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index ea16fae7..d34e9ee9 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -64,10 +64,6 @@ impl MidiDevice { self.ptr } - fn as_ptr(&self) -> *mut ffi::AMidiDevice { - self.ptr.as_ptr() - } - /// Connects a native Midi Device object to the associated Java MidiDevice object. /// /// Use the returned [`MidiDevice`] to access the rest of the native MIDI API. @@ -83,7 +79,7 @@ impl MidiDevice { /// Gets the number of input (sending) ports available on the specified MIDI device. pub fn num_input_ports(&self) -> Result { - let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.as_ptr()) }; + let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.ptr.as_ptr()) }; if num_input_ports >= 0 { Ok(num_input_ports as usize) } else { @@ -93,7 +89,7 @@ impl MidiDevice { /// Gets the number of output (receiving) ports available on the specified MIDI device. pub fn num_output_ports(&self) -> Result { - let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.as_ptr()) }; + let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.ptr.as_ptr()) }; if num_output_ports >= 0 { Ok(num_output_ports as usize) } else { @@ -106,7 +102,7 @@ impl MidiDevice { /// Gets the MIDI device type. pub fn device_type(&self) -> Result { - let device_type = unsafe { ffi::AMidiDevice_getType(self.as_ptr()) }; + let device_type = unsafe { ffi::AMidiDevice_getType(self.ptr.as_ptr()) }; if device_type >= 0 { let device_type = MidiDeviceType::try_from(device_type as u32).map_err(|e| { NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) @@ -121,7 +117,7 @@ impl MidiDevice { pub fn open_input_port(&self, port_number: i32) -> Result { unsafe { let input_port = - construct(|res| ffi::AMidiInputPort_open(self.as_ptr(), port_number, res))?; + construct(|res| ffi::AMidiInputPort_open(self.ptr.as_ptr(), port_number, res))?; Ok(MidiInputPort::from_ptr(NonNull::new_unchecked(input_port))) } } @@ -130,7 +126,7 @@ impl MidiDevice { pub fn open_output_port(&self, port_number: i32) -> Result { unsafe { let output_port = - construct(|res| ffi::AMidiOutputPort_open(self.as_ptr(), port_number, res))?; + construct(|res| ffi::AMidiOutputPort_open(self.ptr.as_ptr(), port_number, res))?; Ok(MidiOutputPort::from_ptr(NonNull::new_unchecked( output_port, ))) @@ -140,7 +136,7 @@ impl MidiDevice { impl Drop for MidiDevice { fn drop(&mut self) { - let status = unsafe { ffi::AMidiDevice_release(self.as_ptr()) }; + let status = unsafe { ffi::AMidiDevice_release(self.ptr.as_ptr()) }; NdkMediaError::from_status(status).unwrap(); } } @@ -174,14 +170,14 @@ impl<'a> MidiInputPort<'a> { self.ptr } - fn as_ptr(&self) -> *mut ffi::AMidiInputPort { - self.ptr.as_ptr() - } - /// Sends data to the specified input port. pub fn send(&self, buffer: &[u8]) -> Result { let num_bytes_sent = unsafe { - ffi::AMidiInputPort_send(self.as_ptr(), buffer.as_ptr(), buffer.len() as ffi::size_t) + ffi::AMidiInputPort_send( + self.ptr.as_ptr(), + buffer.as_ptr(), + buffer.len() as ffi::size_t, + ) }; if num_bytes_sent >= 0 { Ok(num_bytes_sent as usize) @@ -198,14 +194,14 @@ impl<'a> MidiInputPort<'a> { /// This should cause a receiver to discard any pending MIDI data it may have accumulated and /// not processed. pub fn send_flush(&self) -> Result<()> { - let result = unsafe { ffi::AMidiInputPort_sendFlush(self.as_ptr()) }; + let result = unsafe { ffi::AMidiInputPort_sendFlush(self.ptr.as_ptr()) }; NdkMediaError::from_status(result) } pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result { let num_bytes_sent = unsafe { ffi::AMidiInputPort_sendWithTimestamp( - self.as_ptr(), + self.ptr.as_ptr(), buffer.as_ptr(), buffer.len() as ffi::size_t, timestamp, @@ -224,7 +220,7 @@ impl<'a> MidiInputPort<'a> { impl<'a> Drop for MidiInputPort<'a> { fn drop(&mut self) { - unsafe { ffi::AMidiInputPort_close(self.as_ptr()) }; + unsafe { ffi::AMidiInputPort_close(self.ptr.as_ptr()) }; } } @@ -257,10 +253,6 @@ impl<'a> MidiOutputPort<'a> { self.ptr } - fn as_ptr(&self) -> *mut ffi::AMidiOutputPort { - self.ptr.as_ptr() - } - /// Receives the next pending MIDI message. /// /// To retrieve all pending messages, the client should repeatedly call this method until it @@ -274,7 +266,7 @@ impl<'a> MidiOutputPort<'a> { let mut num_bytes_received: ffi::size_t = 0; let num_messages_received = unsafe { ffi::AMidiOutputPort_receive( - self.as_ptr(), + self.ptr.as_ptr(), &mut opcode, buffer.as_mut_ptr(), buffer.len() as ffi::size_t, @@ -300,6 +292,6 @@ impl<'a> MidiOutputPort<'a> { impl<'a> Drop for MidiOutputPort<'a> { fn drop(&mut self) { - unsafe { ffi::AMidiOutputPort_close(self.as_ptr()) }; + unsafe { ffi::AMidiOutputPort_close(self.ptr.as_ptr()) }; } } From 01877bc64df72956c291038ceabbb71c3fe34d49 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 20:44:05 +0900 Subject: [PATCH 17/51] ndk: Add more strict opcode checks to MidiOutputPort --- ndk/src/midi.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index d34e9ee9..bebf894a 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -280,12 +280,15 @@ impl<'a> MidiOutputPort<'a> { Err(NdkMediaError::from_status(ffi::media_status_t(r as c_int)).unwrap_err()) } 0 => Ok(MidiOpcode::NoMessage), - 1 if opcode as c_uint == ffi::AMIDI_OPCODE_DATA => Ok(MidiOpcode::Data { - length: num_bytes_received as usize, - timestamp, - }), - 1 => Ok(MidiOpcode::Flush), - r => unreachable!("Result is positive integer {}", r), + 1 => match opcode as c_uint { + ffi::AMIDI_OPCODE_DATA => Ok(MidiOpcode::Data { + length: num_bytes_received as usize, + timestamp, + }), + ffi::AMIDI_OPCODE_FLUSH => Ok(MidiOpcode::Flush), + _ => unreachable!("Opcode is {}", opcode), + }, + r => unreachable!("Number of messages is positive integer {}", r), } } } From 48b441639353e7ac4a0e41027ff29affbe6fe0f9 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 20:50:25 +0900 Subject: [PATCH 18/51] ndk: Make msg for unexpected opcode in midi.rs more clear --- ndk/src/midi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index bebf894a..b76d4863 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -286,7 +286,7 @@ impl<'a> MidiOutputPort<'a> { timestamp, }), ffi::AMIDI_OPCODE_FLUSH => Ok(MidiOpcode::Flush), - _ => unreachable!("Opcode is {}", opcode), + _ => unreachable!("Unrecognized opcode {}", opcode), }, r => unreachable!("Number of messages is positive integer {}", r), } From aa26247d77a35a5fdcaecf1a2105812f8fde0d0d Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 20:55:38 +0900 Subject: [PATCH 19/51] ndk: Fix vague doc comments in midi.rs --- ndk/src/midi.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index b76d4863..294d8e07 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -77,7 +77,7 @@ impl MidiDevice { } } - /// Gets the number of input (sending) ports available on the specified MIDI device. + /// Gets the number of input (sending) ports available on this device. pub fn num_input_ports(&self) -> Result { let num_input_ports = unsafe { ffi::AMidiDevice_getNumInputPorts(self.ptr.as_ptr()) }; if num_input_ports >= 0 { @@ -87,7 +87,7 @@ impl MidiDevice { } } - /// Gets the number of output (receiving) ports available on the specified MIDI device. + /// Gets the number of output (receiving) ports available on this device. pub fn num_output_ports(&self) -> Result { let num_output_ports = unsafe { ffi::AMidiDevice_getNumOutputPorts(self.ptr.as_ptr()) }; if num_output_ports >= 0 { @@ -100,7 +100,7 @@ impl MidiDevice { } } - /// Gets the MIDI device type. + /// Gets the MIDI device type of this device. pub fn device_type(&self) -> Result { let device_type = unsafe { ffi::AMidiDevice_getType(self.ptr.as_ptr()) }; if device_type >= 0 { @@ -170,7 +170,7 @@ impl<'a> MidiInputPort<'a> { self.ptr } - /// Sends data to the specified input port. + /// Sends data to this port. pub fn send(&self, buffer: &[u8]) -> Result { let num_bytes_sent = unsafe { ffi::AMidiInputPort_send( @@ -189,7 +189,7 @@ impl<'a> MidiInputPort<'a> { } } - /// Sends a message with a 'MIDI flush command code' to the specified port. + /// Sends a message with a 'MIDI flush command code' to this port. /// /// This should cause a receiver to discard any pending MIDI data it may have accumulated and /// not processed. From 8235a5cdba2f4c15e34e9d135e0d23ec40401493 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 21:51:38 +0900 Subject: [PATCH 20/51] ndk: Add mod media_error Moved media::{construct, MediaErrorResult, NdkMediaError} to separate module as midi.rs is also dependent on them --- ndk/src/lib.rs | 1 + ndk/src/media/image_reader.rs | 2 +- ndk/src/media/mod.rs | 21 +++------------------ ndk/src/{media/error.rs => media_error.rs} | 12 +++++++++++- ndk/src/midi.rs | 4 ++-- 5 files changed, 18 insertions(+), 22 deletions(-) rename ndk/src/{media/error.rs => media_error.rs} (92%) diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 264d9bb3..5ff3958f 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -16,6 +16,7 @@ pub mod hardware_buffer_format; pub mod input_queue; pub mod looper; pub mod media; +pub mod media_error; pub mod midi; pub mod native_activity; pub mod native_window; diff --git a/ndk/src/media/image_reader.rs b/ndk/src/media/image_reader.rs index ce2daa1e..c5881ef1 100644 --- a/ndk/src/media/image_reader.rs +++ b/ndk/src/media/image_reader.rs @@ -5,7 +5,7 @@ #![cfg(feature = "api-level-24")] use super::NdkMediaError; -use super::{construct, construct_never_null, error::MediaErrorResult, Result}; +use super::{construct, construct_never_null, MediaErrorResult, Result}; use crate::native_window::NativeWindow; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::{ diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index 1cff068e..d6ef8abc 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -1,30 +1,16 @@ //! Bindings for the NDK media classes. //! //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media) -#![cfg(any(feature = "media", feature = "midi"))] +#![cfg(feature = "media")] -mod error; - -#[cfg(feature = "media")] pub mod image_reader; -#[cfg(feature = "media")] pub mod media_codec; -pub use error::NdkMediaError; -use std::mem::MaybeUninit; +pub use crate::media_error::{MediaErrorResult, NdkMediaError, Result}; -#[cfg(feature = "media")] +use crate::media_error::construct; use std::ptr::NonNull; -pub type Result = std::result::Result; - -pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { - let mut result = MaybeUninit::uninit(); - let status = with_ptr(result.as_mut_ptr()); - NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() }) -} - -#[cfg(feature = "media")] fn construct_never_null( with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, ) -> Result> { @@ -42,7 +28,6 @@ fn construct_never_null( /// /// As such this function always asserts on `null` values, /// even when `cfg!(debug_assertions)` is disabled. -#[cfg(feature = "media")] fn get_unlikely_to_be_null(get_ptr: impl FnOnce() -> *mut T) -> NonNull { let result = get_ptr(); NonNull::new(result).expect("result should never be null") diff --git a/ndk/src/media/error.rs b/ndk/src/media_error.rs similarity index 92% rename from ndk/src/media/error.rs rename to ndk/src/media_error.rs index 05cff87e..89d8dc7b 100644 --- a/ndk/src/media/error.rs +++ b/ndk/src/media_error.rs @@ -1,6 +1,16 @@ -use super::Result; +#![cfg(any(feature = "media", feature = "midi"))] + +use std::mem::MaybeUninit; use thiserror::Error; +pub type Result = std::result::Result; + +pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() }) +} + #[repr(i32)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum MediaErrorResult { diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 294d8e07..eb8f1b90 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -9,8 +9,8 @@ //! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport #![cfg(feature = "midi")] -pub use super::media::Result; -use super::media::{construct, NdkMediaError}; +pub use super::media_error::Result; +use super::media_error::{construct, NdkMediaError}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::convert::TryFrom; From 12df844ee1988e3b2f863da659e48553bbded613 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 22:00:21 +0900 Subject: [PATCH 21/51] ndk: Remove pub use for MediaErrorResult from media/mod.rs --- ndk/src/media/image_reader.rs | 3 ++- ndk/src/media/mod.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ndk/src/media/image_reader.rs b/ndk/src/media/image_reader.rs index c5881ef1..361ee698 100644 --- a/ndk/src/media/image_reader.rs +++ b/ndk/src/media/image_reader.rs @@ -5,7 +5,8 @@ #![cfg(feature = "api-level-24")] use super::NdkMediaError; -use super::{construct, construct_never_null, MediaErrorResult, Result}; +use super::{construct, construct_never_null, Result}; +use crate::media_error::MediaErrorResult; use crate::native_window::NativeWindow; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::{ diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index d6ef8abc..6baf5d5e 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -6,7 +6,7 @@ pub mod image_reader; pub mod media_codec; -pub use crate::media_error::{MediaErrorResult, NdkMediaError, Result}; +pub use crate::media_error::{NdkMediaError, Result}; use crate::media_error::construct; use std::ptr::NonNull; From 34357c723f51b6090cafce480807640ccd5c70ce Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 22:34:59 +0900 Subject: [PATCH 22/51] ndk: Remove pub use from media/mod.rs --- ndk/src/media/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index 6baf5d5e..192d588f 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -6,9 +6,7 @@ pub mod image_reader; pub mod media_codec; -pub use crate::media_error::{NdkMediaError, Result}; - -use crate::media_error::construct; +use crate::media_error::{construct, NdkMediaError, Result}; use std::ptr::NonNull; fn construct_never_null( From 23b18b016853cb82f8fc99659e9b232476e7954e Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 5 Oct 2022 22:36:55 +0900 Subject: [PATCH 23/51] ndk: Reflect midi.rs changes to CHANGELOG.md --- ndk/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index 7297184f..be7f4c5d 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - event: Add `tool_type` getter for `Pointer`. (#323) +- Add `AMidi` bindings. (#353) # 0.7.0 (2022-07-24) From c79c8c53a234d9b99fda5a4862c408a1b35526c6 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 15 Jun 2023 15:07:34 +0900 Subject: [PATCH 24/51] fix: Make MidiDevice::from_java unsafe --- ndk/src/midi.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index eb8f1b90..206f63fa 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -67,7 +67,11 @@ impl MidiDevice { /// Connects a native Midi Device object to the associated Java MidiDevice object. /// /// Use the returned [`MidiDevice`] to access the rest of the native MIDI API. - pub fn from_java( + /// + /// # Safety + /// `env` and `midi_device_obj` must be valid pointers to a [`jni_sys::JNIEnv`] instance and a + /// Java [`MidiDevice`](https://developer.android.com/reference/android/media/midi/MidiDevice) instance. + pub unsafe fn from_java( env: *mut jni_sys::JNIEnv, midi_device_obj: jni_sys::jobject, ) -> Result { From acce571e069190fc7d26ef885051572ac2ad5783 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Fri, 16 Jun 2023 16:12:51 +0900 Subject: [PATCH 25/51] fix(ndk): Remove all occurrences of ffi::size_t from midi.rs --- ndk/src/midi.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 206f63fa..6a5454fd 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -176,13 +176,8 @@ impl<'a> MidiInputPort<'a> { /// Sends data to this port. pub fn send(&self, buffer: &[u8]) -> Result { - let num_bytes_sent = unsafe { - ffi::AMidiInputPort_send( - self.ptr.as_ptr(), - buffer.as_ptr(), - buffer.len() as ffi::size_t, - ) - }; + let num_bytes_sent = + unsafe { ffi::AMidiInputPort_send(self.ptr.as_ptr(), buffer.as_ptr(), buffer.len()) }; if num_bytes_sent >= 0 { Ok(num_bytes_sent as usize) } else { @@ -207,7 +202,7 @@ impl<'a> MidiInputPort<'a> { ffi::AMidiInputPort_sendWithTimestamp( self.ptr.as_ptr(), buffer.as_ptr(), - buffer.len() as ffi::size_t, + buffer.len(), timestamp, ) }; @@ -267,13 +262,13 @@ impl<'a> MidiOutputPort<'a> { pub fn receive(&self, buffer: &mut [u8]) -> Result { let mut opcode = 0i32; let mut timestamp = 0i64; - let mut num_bytes_received: ffi::size_t = 0; + let mut num_bytes_received = 0; let num_messages_received = unsafe { ffi::AMidiOutputPort_receive( self.ptr.as_ptr(), &mut opcode, buffer.as_mut_ptr(), - buffer.len() as ffi::size_t, + buffer.len(), &mut num_bytes_received, &mut timestamp, ) From 8900c1cef7cad16b1a63a1276c61ca4f670e7486 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Fri, 16 Jun 2023 16:15:36 +0900 Subject: [PATCH 26/51] fix(ndk): Remove unnecessary casts from midi.rs --- ndk/src/midi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 6a5454fd..6f8f0535 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -281,7 +281,7 @@ impl<'a> MidiOutputPort<'a> { 0 => Ok(MidiOpcode::NoMessage), 1 => match opcode as c_uint { ffi::AMIDI_OPCODE_DATA => Ok(MidiOpcode::Data { - length: num_bytes_received as usize, + length: num_bytes_received, timestamp, }), ffi::AMIDI_OPCODE_FLUSH => Ok(MidiOpcode::Flush), From 95220e752dc02e58e5313b37853e941ba466b797 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Thu, 15 Jun 2023 16:43:51 +0200 Subject: [PATCH 27/51] ndk: Rework media error types - Clean up the names: we're already in the `ndk` crate so no need to repeat that in the type name; - Make the types available outside the `media` module, so that they can be used in `AMidi` bindings which are not located inside `libmediandk.so` (but `libamidi.so`); - Add documentation comments. --- ndk/CHANGELOG.md | 1 + ndk/src/lib.rs | 1 + ndk/src/media/image_reader.rs | 15 +++--- ndk/src/media/media_codec.rs | 38 +++++++-------- ndk/src/media/mod.rs | 24 ---------- ndk/src/{media/error.rs => media_error.rs} | 54 ++++++++++++++++------ 6 files changed, 68 insertions(+), 65 deletions(-) rename ndk/src/{media/error.rs => media_error.rs} (71%) diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index 14b8202c..53139995 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -8,6 +8,7 @@ - **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-TODO). (#370) - **Breaking:** Upgrade `bitflags` crate from `1` to `2`. (#394) - **Breaking:** Upgrade `num_enum` crate from `0.5.1` to `0.6`. (#398) +- **Breaking:** Renamed and moved "`media`" error types and helpers to the always-available `media_error` module. (#399) # 0.7.0 (2022-07-24) diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 00a69da0..824f8d7f 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -16,6 +16,7 @@ pub mod hardware_buffer_format; pub mod input_queue; pub mod looper; pub mod media; +pub mod media_error; pub mod native_activity; pub mod native_window; pub mod surface_texture; diff --git a/ndk/src/media/image_reader.rs b/ndk/src/media/image_reader.rs index ce2daa1e..da92a24a 100644 --- a/ndk/src/media/image_reader.rs +++ b/ndk/src/media/image_reader.rs @@ -4,8 +4,7 @@ //! [`AImage`]: https://developer.android.com/ndk/reference/group/media#aimage #![cfg(feature = "api-level-24")] -use super::NdkMediaError; -use super::{construct, construct_never_null, error::MediaErrorResult, Result}; +use crate::media_error::{construct, construct_never_null, MediaError, MediaStatus, Result}; use crate::native_window::NativeWindow; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::{ @@ -140,7 +139,7 @@ impl ImageReader { onImageAvailable: Some(on_image_available), }; let status = unsafe { ffi::AImageReader_setImageListener(self.as_ptr(), &mut listener) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-26")] @@ -168,7 +167,7 @@ impl ImageReader { }; let status = unsafe { ffi::AImageReader_setBufferRemovedListener(self.as_ptr(), &mut listener) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn get_window(&self) -> Result { @@ -202,9 +201,7 @@ impl ImageReader { match res { Ok(inner) => Ok(Some(Image { inner })), - Err(NdkMediaError::ErrorResult(MediaErrorResult::ImgreaderNoBufferAvailable)) => { - Ok(None) - } + Err(MediaError::MediaStatus(MediaStatus::ImgreaderNoBufferAvailable)) => Ok(None), Err(e) => Err(e), } } @@ -232,7 +229,7 @@ impl ImageReader { ffi::AImageReader_acquireLatestImage(self.as_ptr(), res) }); - if let Err(NdkMediaError::ErrorResult(MediaErrorResult::ImgreaderNoBufferAvailable)) = res { + if let Err(MediaError::MediaStatus(MediaStatus::ImgreaderNoBufferAvailable)) = res { return Ok(None); } @@ -291,7 +288,7 @@ impl Image { ) }; - NdkMediaError::from_status(status).map(|()| unsafe { + MediaError::from_status(status).map(|()| unsafe { std::slice::from_raw_parts(result_ptr.assume_init(), result_len.assume_init() as _) }) } diff --git a/ndk/src/media/media_codec.rs b/ndk/src/media/media_codec.rs index 3fb26ae8..f8dc105d 100644 --- a/ndk/src/media/media_codec.rs +++ b/ndk/src/media/media_codec.rs @@ -3,7 +3,7 @@ //! [`AMediaFormat`]: https://developer.android.com/ndk/reference/group/media#amediaformat //! [`AMediaCodec`]: https://developer.android.com/ndk/reference/group/media#amediacodec -use super::{NdkMediaError, Result}; +use crate::media_error::{MediaError, Result}; use crate::native_window::NativeWindow; use std::{ convert::TryInto, @@ -206,7 +206,7 @@ impl MediaFormat { impl Drop for MediaFormat { fn drop(&mut self) { let status = unsafe { ffi::AMediaFormat_delete(self.as_ptr()) }; - NdkMediaError::from_status(status).unwrap(); + MediaError::from_status(status).unwrap(); } } @@ -267,12 +267,12 @@ impl MediaCodec { }, ) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-26")] pub fn create_input_surface(&self) -> Result { - use super::construct_never_null; + use crate::media_error::construct_never_null; unsafe { let ptr = construct_never_null(|res| { ffi::AMediaCodec_createInputSurface(self.as_ptr(), res) @@ -283,7 +283,7 @@ impl MediaCodec { #[cfg(feature = "api-level-26")] pub fn create_persistent_input_surface() -> Result { - use super::construct_never_null; + use crate::media_error::construct_never_null; unsafe { let ptr = construct_never_null(|res| ffi::AMediaCodec_createPersistentInputSurface(res))?; @@ -311,7 +311,7 @@ impl MediaCodec { index: result as usize, })) } else { - NdkMediaError::from_status(ffi::media_status_t(result as _)).map(|()| None) + MediaError::from_status(ffi::media_status_t(result as _)).map(|()| None) } } @@ -339,13 +339,13 @@ impl MediaCodec { info, })) } else { - NdkMediaError::from_status(ffi::media_status_t(result as _)).map(|()| None) + MediaError::from_status(ffi::media_status_t(result as _)).map(|()| None) } } pub fn flush(&self) -> Result<()> { let status = unsafe { ffi::AMediaCodec_flush(self.as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-28")] @@ -363,7 +363,7 @@ impl MediaCodec { #[cfg(feature = "api-level-28")] pub fn name(&self) -> Result { - use super::construct; + use crate::media_error::construct; unsafe { let name_ptr = construct(|name| ffi::AMediaCodec_getName(self.as_ptr(), name))?; let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_owned(); @@ -391,13 +391,13 @@ impl MediaCodec { flags, ) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn release_output_buffer(&self, buffer: OutputBuffer, render: bool) -> Result<()> { let status = unsafe { ffi::AMediaCodec_releaseOutputBuffer(self.as_ptr(), buffer.index, render) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn release_output_buffer_at_time( @@ -408,49 +408,49 @@ impl MediaCodec { let status = unsafe { ffi::AMediaCodec_releaseOutputBufferAtTime(self.as_ptr(), buffer.index, timestamp_ns) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-26")] pub fn set_input_surface(&self, surface: &NativeWindow) -> Result<()> { let status = unsafe { ffi::AMediaCodec_setInputSurface(self.as_ptr(), surface.ptr().as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn set_output_surface(&self, surface: &NativeWindow) -> Result<()> { let status = unsafe { ffi::AMediaCodec_setOutputSurface(self.as_ptr(), surface.ptr().as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-26")] pub fn set_parameters(&self, params: MediaFormat) -> Result<()> { let status = unsafe { ffi::AMediaCodec_setParameters(self.as_ptr(), params.as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } #[cfg(feature = "api-level-26")] pub fn set_signal_end_of_input_stream(&self) -> Result<()> { let status = unsafe { ffi::AMediaCodec_signalEndOfInputStream(self.as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn start(&self) -> Result<()> { let status = unsafe { ffi::AMediaCodec_start(self.as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } pub fn stop(&self) -> Result<()> { let status = unsafe { ffi::AMediaCodec_stop(self.as_ptr()) }; - NdkMediaError::from_status(status) + MediaError::from_status(status) } } impl Drop for MediaCodec { fn drop(&mut self) { let status = unsafe { ffi::AMediaCodec_delete(self.as_ptr()) }; - NdkMediaError::from_status(status).unwrap(); + MediaError::from_status(status).unwrap(); } } diff --git a/ndk/src/media/mod.rs b/ndk/src/media/mod.rs index 7aefc25d..3e27b46e 100644 --- a/ndk/src/media/mod.rs +++ b/ndk/src/media/mod.rs @@ -3,29 +3,5 @@ //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media) #![cfg(feature = "media")] -mod error; pub mod image_reader; pub mod media_codec; - -pub use error::NdkMediaError; -use std::{mem::MaybeUninit, ptr::NonNull}; - -pub type Result = std::result::Result; - -fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { - let mut result = MaybeUninit::uninit(); - let status = with_ptr(result.as_mut_ptr()); - NdkMediaError::from_status(status).map(|()| unsafe { result.assume_init() }) -} - -fn construct_never_null( - with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, -) -> Result> { - let result = construct(with_ptr)?; - let non_null = if cfg!(debug_assertions) { - NonNull::new(result).expect("result should never be null") - } else { - unsafe { NonNull::new_unchecked(result) } - }; - Ok(non_null) -} diff --git a/ndk/src/media/error.rs b/ndk/src/media_error.rs similarity index 71% rename from ndk/src/media/error.rs rename to ndk/src/media_error.rs index 05cff87e..76537f9e 100644 --- a/ndk/src/media/error.rs +++ b/ndk/src/media_error.rs @@ -1,9 +1,13 @@ -use super::Result; +use std::{mem::MaybeUninit, ptr::NonNull}; + use thiserror::Error; +pub type Result = std::result::Result; + +/// Media Status codes for [`media_status_t`](https://developer.android.com/ndk/reference/group/media#group___media_1ga009a49041fe39f7bdc6d8b5cddbe760c) #[repr(i32)] #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MediaErrorResult { +pub enum MediaStatus { CodecErrorInsufficientResource = ffi::media_status_t::AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE.0, CodecErrorReclaimed = ffi::media_status_t::AMEDIACODEC_ERROR_RECLAIMED.0, ErrorUnknown = ffi::media_status_t::AMEDIA_ERROR_UNKNOWN.0, @@ -33,18 +37,20 @@ pub enum MediaErrorResult { ImgreaderImageNotLocked = ffi::media_status_t::AMEDIA_IMGREADER_IMAGE_NOT_LOCKED.0, } +/// Media Status codes in [`MediaStatus`] or raw [`ffi::media_status_t`] if unknown. #[derive(Debug, Error)] -pub enum NdkMediaError { - #[error("error Media result ({0:?})")] - ErrorResult(MediaErrorResult), - #[error("unknown Media error result ({0:?})")] - UnknownResult(ffi::media_status_t), +pub enum MediaError { + #[error("Media Status {0:?}")] + MediaStatus(MediaStatus), + #[error("Unknown Media Status {0:?}")] + UnknownStatus(ffi::media_status_t), } -impl NdkMediaError { +impl MediaError { + /// Returns [`Ok`] on [`ffi::media_status_t::AMEDIA_OK`], [`Err`] otherwise. pub(crate) fn from_status(status: ffi::media_status_t) -> Result<()> { - use MediaErrorResult::*; - let result = match status { + use MediaStatus::*; + Err(Self::MediaStatus(match status { ffi::media_status_t::AMEDIA_OK => return Ok(()), ffi::media_status_t::AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE => { CodecErrorInsufficientResource @@ -75,8 +81,30 @@ impl NdkMediaError { ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE => ImgreaderCannotLockImage, ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE => ImgreaderCannotUnlockImage, ffi::media_status_t::AMEDIA_IMGREADER_IMAGE_NOT_LOCKED => ImgreaderImageNotLocked, - _ => return Err(NdkMediaError::UnknownResult(status)), - }; - Err(NdkMediaError::ErrorResult(result)) + _ => return Err(MediaError::UnknownStatus(status)), + })) } } + +/// Calls the `with_ptr` construction function with a pointer to uninitialized stack memory, +/// expecting `with_ptr` to initialize it or otherwise return an error code. +pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { + let mut result = MaybeUninit::uninit(); + let status = with_ptr(result.as_mut_ptr()); + MediaError::from_status(status).map(|()| unsafe { result.assume_init() }) +} + +/// Calls the `with_ptr` construction function with a pointer to a pointer, and expects `with_ptr` +/// to initialize the second pointer to a valid address. That address is returned in the form of a +/// [`NonNull`] object. +pub(crate) fn construct_never_null( + with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, +) -> Result> { + let result = construct(with_ptr)?; + let non_null = if cfg!(debug_assertions) { + NonNull::new(result).expect("result should never be null") + } else { + unsafe { NonNull::new_unchecked(result) } + }; + Ok(non_null) +} From cc1c2e3dbf43b26d1cbb68c9ee6f6b868005922c Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Fri, 16 Jun 2023 16:47:19 +0900 Subject: [PATCH 28/51] fix(ndk): Reflect changes in media_error to midi --- ndk/src/midi.rs | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 6f8f0535..944d60bf 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -10,7 +10,7 @@ #![cfg(feature = "midi")] pub use super::media_error::Result; -use super::media_error::{construct, NdkMediaError}; +use super::media_error::{construct, MediaError}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::convert::TryFrom; @@ -87,7 +87,7 @@ impl MidiDevice { if num_input_ports >= 0 { Ok(num_input_ports as usize) } else { - NdkMediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).map(|_| 0) + MediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).map(|_| 0) } } @@ -98,7 +98,7 @@ impl MidiDevice { Ok(num_output_ports as usize) } else { Err( - NdkMediaError::from_status(ffi::media_status_t(num_output_ports as c_int)) + MediaError::from_status(ffi::media_status_t(num_output_ports as c_int)) .unwrap_err(), ) } @@ -108,12 +108,11 @@ impl MidiDevice { pub fn device_type(&self) -> Result { let device_type = unsafe { ffi::AMidiDevice_getType(self.ptr.as_ptr()) }; if device_type >= 0 { - let device_type = MidiDeviceType::try_from(device_type as u32).map_err(|e| { - NdkMediaError::UnknownResult(ffi::media_status_t(e.number as c_int)) - })?; + let device_type = MidiDeviceType::try_from(device_type as u32) + .map_err(|e| MediaError::UnknownStatus(ffi::media_status_t(e.number as c_int)))?; Ok(device_type) } else { - Err(NdkMediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) + Err(MediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) } } @@ -141,7 +140,7 @@ impl MidiDevice { impl Drop for MidiDevice { fn drop(&mut self) { let status = unsafe { ffi::AMidiDevice_release(self.ptr.as_ptr()) }; - NdkMediaError::from_status(status).unwrap(); + MediaError::from_status(status).unwrap(); } } @@ -181,10 +180,7 @@ impl<'a> MidiInputPort<'a> { if num_bytes_sent >= 0 { Ok(num_bytes_sent as usize) } else { - Err( - NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) - .unwrap_err(), - ) + Err(MediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)).unwrap_err()) } } @@ -194,7 +190,7 @@ impl<'a> MidiInputPort<'a> { /// not processed. pub fn send_flush(&self) -> Result<()> { let result = unsafe { ffi::AMidiInputPort_sendFlush(self.ptr.as_ptr()) }; - NdkMediaError::from_status(result) + MediaError::from_status(result) } pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result { @@ -209,10 +205,7 @@ impl<'a> MidiInputPort<'a> { if num_bytes_sent >= 0 { Ok(num_bytes_sent as usize) } else { - Err( - NdkMediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)) - .unwrap_err(), - ) + Err(MediaError::from_status(ffi::media_status_t(num_bytes_sent as c_int)).unwrap_err()) } } } @@ -276,7 +269,7 @@ impl<'a> MidiOutputPort<'a> { match num_messages_received { r if r < 0 => { - Err(NdkMediaError::from_status(ffi::media_status_t(r as c_int)).unwrap_err()) + Err(MediaError::from_status(ffi::media_status_t(r as c_int)).unwrap_err()) } 0 => Ok(MidiOpcode::NoMessage), 1 => match opcode as c_uint { From 408e46077c923fc3aa5ecec192cfe937dea696da Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 11:59:12 +0900 Subject: [PATCH 29/51] Update comments in ndk/src/midi.rs Co-authored-by: Marijn Suijten --- ndk/src/midi.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 944d60bf..2e288242 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -174,6 +174,8 @@ impl<'a> MidiInputPort<'a> { } /// Sends data to this port. + /// + /// Returns the number of bytes that were sent. pub fn send(&self, buffer: &[u8]) -> Result { let num_bytes_sent = unsafe { ffi::AMidiInputPort_send(self.ptr.as_ptr(), buffer.as_ptr(), buffer.len()) }; From 9a5744b673a5803e23e06f7e77835ff69d259e24 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 11:59:53 +0900 Subject: [PATCH 30/51] Update ndk/src/midi.rs Co-authored-by: Marijn Suijten --- ndk/src/midi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 2e288242..6d84f1f1 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -27,7 +27,7 @@ unsafe impl Send for MidiDevice {} unsafe impl<'a> Send for MidiInputPort<'a> {} unsafe impl<'a> Send for MidiOutputPort<'a> {} -/// Result of [`MidiOutputPort::receive`]. +/// Result of [`MidiOutputPort::receive()`]. #[derive(Copy, Clone, Debug)] pub enum MidiOpcode { /// No MIDI messages are available. From 412b6ad4b65d291db8fb2e74cb83adb8a05399cc Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 12:00:08 +0900 Subject: [PATCH 31/51] Update ndk/src/midi.rs Co-authored-by: Marijn Suijten --- ndk/src/midi.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 6d84f1f1..93ff5bc1 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -9,8 +9,7 @@ //! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport #![cfg(feature = "midi")] -pub use super::media_error::Result; -use super::media_error::{construct, MediaError}; +use super::media_error::{construct, MediaError, Result}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::convert::TryFrom; From aa4467e6d054fb7cbad52d695aa1cdda5279ce52 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 12:13:41 +0900 Subject: [PATCH 32/51] Update ndk/CHANGELOG.md Co-authored-by: Marijn Suijten --- ndk/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index bc8eb1e9..9b922345 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -1,8 +1,8 @@ # Unreleased - event: Add `tool_type` getter for `Pointer`. (#323) -- Add `AMidi` bindings. (#353) - input_queue: Allow any non-zero return code from `pre_dispatch()` again, as per documentation. (#325) +- Add `AMidi` bindings. (#353) - asset: Use entire asset length when mapping buffer. (#387) - Bump MSRV to 1.64 for `raw-window-handle 0.5.1`. (#388) - Bump optional `jni` dependency for doctest example from `0.19` to `0.21`. (#390) From 3cfaad4cb8a2aa5e7dc4ff77079a1ea641543a72 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 13:25:46 +0900 Subject: [PATCH 33/51] fix: Remove impl Send for MidiDevice Co-authored-by: Marijn Suijten --- ndk/src/midi.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 93ff5bc1..eb199ef4 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -22,7 +22,6 @@ use std::ptr::NonNull; // sample stores `AMidiDevice *` and `AMidi{Input,Output}Port *` in global variables and accesses the // ports from separate threads. // See https://github.com/android/ndk-samples/blob/7f6936ea044ee29c36b5c3ebd62bb3a64e1e6014/native-midi/app/src/main/cpp/AppMidiManager.cpp -unsafe impl Send for MidiDevice {} unsafe impl<'a> Send for MidiInputPort<'a> {} unsafe impl<'a> Send for MidiOutputPort<'a> {} From 2e036d2e1f5fe307cd01e0c859ca022f7639c297 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 13:27:48 +0900 Subject: [PATCH 34/51] Fix comments in ndk/src/media_error.rs --- ndk/src/media_error.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ndk/src/media_error.rs b/ndk/src/media_error.rs index 9b19b463..fa777464 100644 --- a/ndk/src/media_error.rs +++ b/ndk/src/media_error.rs @@ -1,11 +1,12 @@ //! Bindings for NDK media status codes. //! -//! Also used outside of `libmediandk.so` in `libmidi.so` for example. +//! Also used outside of `libmediandk.so` in `libamidi.so` for example. #![cfg(feature = "media")] // The cfg(feature) bounds for some pub(crate) fn uses are non-trivial and will become even more // complex going forward. Allow them to be unused when compiling with certain feature combinations. #![allow(dead_code)] + use std::{mem::MaybeUninit, ptr::NonNull}; use thiserror::Error; From aa79a19fb8a11644eb968472d79ae080fd7cb214 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 13:32:10 +0900 Subject: [PATCH 35/51] ndk: Make "midi" feature dependent on "media" --- ndk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/Cargo.toml b/ndk/Cargo.toml index 1edb0402..c59c10db 100644 --- a/ndk/Cargo.toml +++ b/ndk/Cargo.toml @@ -18,7 +18,7 @@ all = ["audio", "bitmap","media", "api-level-30"] audio = ["ffi/audio", "api-level-26"] bitmap = ["ffi/bitmap"] media = ["ffi/media"] -midi = ["ffi/midi", "api-level-29"] +midi = ["ffi/midi", "api-level-29", "media"] api-level-23 = [] api-level-24 = ["api-level-23"] From 0341b4d82185d07316f7a41d4c5c051dd091da72 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 15:28:02 +0900 Subject: [PATCH 36/51] fix: Remove redundant Result mapping from midi.rs --- ndk/src/midi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index eb199ef4..c6da23b0 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -85,7 +85,7 @@ impl MidiDevice { if num_input_ports >= 0 { Ok(num_input_ports as usize) } else { - MediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).map(|_| 0) + Err(MediaError::from_status(ffi::media_status_t(num_input_ports as c_int)).unwrap_err()) } } From c8b2b2e59f257b7ba0383304a47184e321c16f07 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 10 Aug 2023 15:57:49 +0900 Subject: [PATCH 37/51] ndk: Add MidiDeviceType::Unknown --- ndk/src/midi.rs | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index c6da23b0..08918e38 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -11,8 +11,6 @@ use super::media_error::{construct, MediaError, Result}; -use num_enum::{IntoPrimitive, TryFromPrimitive}; -use std::convert::TryFrom; use std::fmt; use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; @@ -37,11 +35,34 @@ pub enum MidiOpcode { } #[repr(u32)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum MidiDeviceType { - Bluetooth = ffi::AMIDI_DEVICE_TYPE_BLUETOOTH, - USB = ffi::AMIDI_DEVICE_TYPE_USB, - Virtual = ffi::AMIDI_DEVICE_TYPE_VIRTUAL, + Bluetooth, + USB, + Virtual, + Unknown(u32), +} + +impl From for MidiDeviceType { + fn from(value: u32) -> Self { + match value { + ffi::AMIDI_DEVICE_TYPE_BLUETOOTH => Self::Bluetooth, + ffi::AMIDI_DEVICE_TYPE_USB => Self::USB, + ffi::AMIDI_DEVICE_TYPE_VIRTUAL => Self::Virtual, + v => Self::Unknown(v), + } + } +} + +impl From for u32 { + fn from(val: MidiDeviceType) -> Self { + match val { + MidiDeviceType::Bluetooth => ffi::AMIDI_DEVICE_TYPE_BLUETOOTH, + MidiDeviceType::USB => ffi::AMIDI_DEVICE_TYPE_USB, + MidiDeviceType::Virtual => ffi::AMIDI_DEVICE_TYPE_VIRTUAL, + MidiDeviceType::Unknown(v) => v, + } + } } #[derive(Debug)] @@ -106,9 +127,7 @@ impl MidiDevice { pub fn device_type(&self) -> Result { let device_type = unsafe { ffi::AMidiDevice_getType(self.ptr.as_ptr()) }; if device_type >= 0 { - let device_type = MidiDeviceType::try_from(device_type as u32) - .map_err(|e| MediaError::UnknownStatus(ffi::media_status_t(e.number as c_int)))?; - Ok(device_type) + Ok(MidiDeviceType::from(device_type as u32)) } else { Err(MediaError::from_status(ffi::media_status_t(device_type)).unwrap_err()) } From 6044fc2744c26f8c86f1272e5bb047b0bc0cc480 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 16 Aug 2023 16:22:30 +0900 Subject: [PATCH 38/51] ndk: Add doc-comments to MidiInputPort --- ndk/src/midi.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 08918e38..b160997c 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -190,9 +190,11 @@ impl<'a> MidiInputPort<'a> { self.ptr } - /// Sends data to this port. + /// Sends data to this port. Returns the number of bytes that were sent. /// - /// Returns the number of bytes that were sent. + /// # Arguments + /// + /// * `buffer`: The array of bytes containing the data to send. pub fn send(&self, buffer: &[u8]) -> Result { let num_bytes_sent = unsafe { ffi::AMidiInputPort_send(self.ptr.as_ptr(), buffer.as_ptr(), buffer.len()) }; @@ -212,6 +214,15 @@ impl<'a> MidiInputPort<'a> { MediaError::from_status(result) } + /// Sends data to the specified input port with a timestamp. + /// + /// # Arguments + /// + /// * `buffer`: The array of bytes containing the data to send. + /// * `timestamp`: The `CLOCK_MONOTONIC` time in nanoseconds to associate with the sent data. + /// This is consistent with the value returned by [`java.lang.System.nanoTime()`]. + /// + /// [`java.lang.System.nanoTime()`]: https://developer.android.com/reference/java/lang/System#nanoTime() pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result { let num_bytes_sent = unsafe { ffi::AMidiInputPort_sendWithTimestamp( From f22054d8e67a76e6626628f1be7cfdb32dbafe8a Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 16 Aug 2023 17:39:51 +0900 Subject: [PATCH 39/51] ndk: Add doc-comments to fields of MidiOpcode::Data --- ndk/src/midi.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index b160997c..7287e82c 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -29,7 +29,24 @@ pub enum MidiOpcode { /// No MIDI messages are available. NoMessage, /// Received a MIDI message with the given length and the timestamp. - Data { length: usize, timestamp: i64 }, + Data { + /// The length of the received message. Callers shoud limit the passed `buffer` slice to + /// this length after [`MidiOutputPort::receive()`] returns. + /// ```no_run + /// let output_port: MidiOutputPort = ...; + /// let mut buffer = [0u8; 128]; + /// if let Ok(MidiOpcode::Data { length, .. }) = output_port.receive(&mut buffer) { + /// let buffer = &buffer[..length]; + /// ... + /// } + /// ``` + length: usize, + /// The timestamp associated with the message. This is consistent with the value returned by + /// [`java.lang.System.nanoTime()`]. + /// + /// [`java.lang.System.nanoTime()`]: https://developer.android.com/reference/java/lang/System#nanoTime() + timestamp: i64, + }, /// Instructed to discard all pending MIDI data. Flush, } From 734b81b372aaa656e5a176fb07511edf14c44bee Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 16 Aug 2023 18:12:23 +0900 Subject: [PATCH 40/51] ndk: Reference the Java Android MIDI docs in the doc-comments --- ndk/src/midi.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 7287e82c..2b02c61d 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -231,7 +231,9 @@ impl<'a> MidiInputPort<'a> { MediaError::from_status(result) } - /// Sends data to the specified input port with a timestamp. + /// Sends data to the specified input port with a timestamp. Sometimes it is convenient to send + /// MIDI messages with a timestamp. By scheduling events in the future we can mask scheduling + /// jitter. See the Java [Android MIDI docs] for more details. /// /// # Arguments /// @@ -239,6 +241,7 @@ impl<'a> MidiInputPort<'a> { /// * `timestamp`: The `CLOCK_MONOTONIC` time in nanoseconds to associate with the sent data. /// This is consistent with the value returned by [`java.lang.System.nanoTime()`]. /// + /// [Android docs]: https://developer.android.com/reference/android/media/midi/package-summary#send_a_noteon /// [`java.lang.System.nanoTime()`]: https://developer.android.com/reference/java/lang/System#nanoTime() pub fn send_with_timestamp(&self, buffer: &[u8], timestamp: i64) -> Result { let num_bytes_sent = unsafe { From 7220a9a744865d39b34d4fc0bf5359fca9171e0c Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Wed, 16 Aug 2023 18:16:55 +0900 Subject: [PATCH 41/51] ndk: Fix doc-comments of MidiOutputPort::receive --- ndk/src/midi.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index 2b02c61d..e9d7c357 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -30,7 +30,7 @@ pub enum MidiOpcode { NoMessage, /// Received a MIDI message with the given length and the timestamp. Data { - /// The length of the received message. Callers shoud limit the passed `buffer` slice to + /// The length of the received message. The caller should limit the passed `buffer` slice to /// this length after [`MidiOutputPort::receive()`] returns. /// ```no_run /// let output_port: MidiOutputPort = ...; @@ -302,6 +302,9 @@ impl<'a> MidiOutputPort<'a> { /// /// Note that this is a non-blocking call. If there are no Midi messages are available, the /// function returns [`Ok(MidiOpcode::NoMessage)`] immediately (for 0 messages received). + /// + /// When [`Ok(MidiOpcode::Data)`] is returned, the caller should limit `buffer` to + /// [`MidiOpcode::Data::length`]. See [`MidiOpcode`] for more details. pub fn receive(&self, buffer: &mut [u8]) -> Result { let mut opcode = 0i32; let mut timestamp = 0i64; From 241b096e6e58ed090f69a5c44d240132a70351c7 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 13:58:04 +0900 Subject: [PATCH 42/51] ndk: Drop Send for ndk::midi::Midi*Port --- ndk/src/midi.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index e9d7c357..f56e35f6 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -16,13 +16,6 @@ use std::marker::PhantomData; use std::os::raw::{c_int, c_uint}; use std::ptr::NonNull; -// There is no mention about thread-safety in the NDK reference, but the official Android C++ MIDI -// sample stores `AMidiDevice *` and `AMidi{Input,Output}Port *` in global variables and accesses the -// ports from separate threads. -// See https://github.com/android/ndk-samples/blob/7f6936ea044ee29c36b5c3ebd62bb3a64e1e6014/native-midi/app/src/main/cpp/AppMidiManager.cpp -unsafe impl<'a> Send for MidiInputPort<'a> {} -unsafe impl<'a> Send for MidiOutputPort<'a> {} - /// Result of [`MidiOutputPort::receive()`]. #[derive(Copy, Clone, Debug)] pub enum MidiOpcode { From d9ef3103eced899e62664f87238c1603f74f74e3 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 14:13:36 +0900 Subject: [PATCH 43/51] ndk: Describe about Java VM thread attachment in the safety section of doc-comments of MidiDevice --- ndk/src/midi.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi.rs b/ndk/src/midi.rs index f56e35f6..e52bc65c 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi.rs @@ -84,7 +84,8 @@ impl MidiDevice { /// Assumes ownership of `ptr` /// /// # Safety - /// `ptr` must be a valid pointer to an Android [`ffi::AMidiDevice`]. + /// `ptr` must be a valid pointer to an Android [`ffi::AMidiDevice`]. The calling thread also + /// must be attached to the Java VM during the lifetime of the returned instance. pub unsafe fn from_ptr(ptr: NonNull) -> Self { Self { ptr } } @@ -99,7 +100,9 @@ impl MidiDevice { /// /// # Safety /// `env` and `midi_device_obj` must be valid pointers to a [`jni_sys::JNIEnv`] instance and a - /// Java [`MidiDevice`](https://developer.android.com/reference/android/media/midi/MidiDevice) instance. + /// Java [`MidiDevice`](https://developer.android.com/reference/android/media/midi/MidiDevice) + /// instance. The calling thread also must be attached to the Java VM during the lifetime of the + /// returned instance. pub unsafe fn from_java( env: *mut jni_sys::JNIEnv, midi_device_obj: jni_sys::jobject, From 31159dce1fa12be8b91083694916b931a546b862 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 16:09:49 +0900 Subject: [PATCH 44/51] ndk: Add safe wrapper of midi --- ndk/src/{midi.rs => midi/mod.rs} | 18 ++- ndk/src/midi/safe.rs | 241 +++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 2 deletions(-) rename ndk/src/{midi.rs => midi/mod.rs} (92%) create mode 100644 ndk/src/midi/safe.rs diff --git a/ndk/src/midi.rs b/ndk/src/midi/mod.rs similarity index 92% rename from ndk/src/midi.rs rename to ndk/src/midi/mod.rs index e52bc65c..d3fd3c31 100644 --- a/ndk/src/midi.rs +++ b/ndk/src/midi/mod.rs @@ -9,6 +9,8 @@ //! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport #![cfg(feature = "midi")] +pub mod safe; + use super::media_error::{construct, MediaError, Result}; use std::fmt; @@ -75,6 +77,8 @@ impl From for u32 { } } +/// A wrapper over [`ffi::AMidiDevice`]. Note that [`MidiDevice`] is intentionally `!Send` and +/// `!Sync`; please use [`safe::SafeMidiDevice`] if you need the thread-safe version. #[derive(Debug)] pub struct MidiDevice { ptr: NonNull, @@ -146,7 +150,9 @@ impl MidiDevice { } } - /// Opens the input port so that the client can send data to it. + /// Opens the input port so that the client can send data to it. Note that the returned + /// [`MidiInputPort`] is intentionally `!Send` and `!Sync`; please use + /// [`safe::SafeMidiDevice::open_safe_input_port`] if you need the thread-safe version. pub fn open_input_port(&self, port_number: i32) -> Result { unsafe { let input_port = @@ -155,7 +161,9 @@ impl MidiDevice { } } - /// Opens the output port so that the client can receive data from it. + /// Opens the output port so that the client can receive data from it. Note that the returned + /// [`MidiOutputPort`] is intentionally `!Send` and `!Sync`; please use + /// [`safe::SafeMidiDevice::open_safe_output_port`] if you need the thread-safe version. pub fn open_output_port(&self, port_number: i32) -> Result { unsafe { let output_port = @@ -174,6 +182,9 @@ impl Drop for MidiDevice { } } +/// A wrapper over [`ffi::AMidiInputPort`]. Note that [`MidiInputPort`] is intentionally `!Send` and +/// `!Sync`; please use [`safe::SafeMidiDevice::open_safe_input_port`] if you need the thread-safe +/// version. pub struct MidiInputPort<'a> { ptr: NonNull, _marker: PhantomData<&'a MidiDevice>, @@ -262,6 +273,9 @@ impl<'a> Drop for MidiInputPort<'a> { } } +/// A wrapper over [`ffi::AMidiOutputPort`]. Note that [`MidiOutputPort`] is intentionally `!Send` +/// and `!Sync`; please use [`safe::SafeMidiDevice::open_safe_output_port`] if you need the +/// thread-safe version. pub struct MidiOutputPort<'a> { ptr: NonNull, _marker: PhantomData<&'a MidiDevice>, diff --git a/ndk/src/midi/safe.rs b/ndk/src/midi/safe.rs new file mode 100644 index 00000000..09c391de --- /dev/null +++ b/ndk/src/midi/safe.rs @@ -0,0 +1,241 @@ +//! Safe bindings for [`AMidiDevice`], [`AMidiInputPort`], and [`AMidiOutputPort`] +//! +//! Provides implementation of [`SafeMidiDeviceBox`] that ensures the current thread is attached to +//! the Java VM when being dropped, which is required by [`AMidiDevice_release`]. All other types +//! of this module holds an [`Arc`]. +//! +//! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice +//! [`AMidiDevice_release`]: https://developer.android.com/ndk/reference/group/midi#amididevice_release +//! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport +//! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport + +use std::mem::{ManuallyDrop, MaybeUninit}; +use std::ops::Deref; +use std::ptr::{self, NonNull}; +use std::sync::Arc; +use std::{fmt, mem}; + +use crate::media_error::Result; + +use super::*; + +/// The owner of [`MidiDevice`]. [`SafeMidiDevice`], [`SafeMidiInputPort`], and +/// [`SafeMidiOutputPort`] holds an [`Arc`]. to ensure that the underlying +/// [`MidiDevice`] is not dropped while the safe wrappers are alive. +/// +/// [`SafeMidiDeviceBox`] also holds a pointer to the current Java VM to attach the calling thread +/// of [`Drop::drop`] to the VM, which is required by [`ffi::AMidiDevice_release`]. +struct SafeMidiDeviceBox { + midi_device: ManuallyDrop, + java_vm: NonNull, +} + +// SAFETY: [`SafeMidiDeviceBox::drop`] attached the calling thread to the Java VM if required. +unsafe impl Send for SafeMidiDeviceBox {} + +// SAFETY: [`SafeMidiDeviceBox::drop`] attached the calling thread to the Java VM if required. +unsafe impl Sync for SafeMidiDeviceBox {} + +impl Drop for SafeMidiDeviceBox { + fn drop(&mut self) { + unsafe { + let java_vm_functions = self.java_vm.as_mut().as_ref().unwrap_unchecked(); + let java_vm = self.java_vm.as_ptr(); + let mut current_thread_was_attached = true; + let mut env = MaybeUninit::uninit(); + + // Try to get the current thread's JNIEnv + if (java_vm_functions.GetEnv.unwrap_unchecked())( + java_vm, + env.as_mut_ptr(), + jni_sys::JNI_VERSION_1_6, + ) != jni_sys::JNI_OK + { + // Current thread is not attached to the Java VM. Try to attach. + current_thread_was_attached = false; + if (java_vm_functions + .AttachCurrentThreadAsDaemon + .unwrap_unchecked())( + java_vm, env.as_mut_ptr(), ptr::null_mut() + ) != jni_sys::JNI_OK + { + panic!("failed to attach the current thread to the Java VM"); + } + } + + // Dropping MidiDevice requires the current thread to be attached to the Java VM. + ManuallyDrop::drop(&mut self.midi_device); + + // Releasing MidiDevice is complete; if the current thread was not attached to the VM, + // detach the current thread. + if !current_thread_was_attached { + (java_vm_functions.DetachCurrentThread.unwrap_unchecked())(java_vm); + } + } + } +} + +/// A thread-safe wrapper over [`MidiDevice`]. +pub struct SafeMidiDevice { + inner: Arc, +} + +impl SafeMidiDevice { + /// Assumes ownership of `ptr` + /// + /// # Safety + /// `env` and `ptr` must be valid pointers to a [`jni_sys::JNIEnv`] instance and an Android + /// [`ffi::AMidiDevice`]. + pub unsafe fn from_ptr(env: *mut jni_sys::JNIEnv, ptr: NonNull) -> Self { + Self::from_unsafe(env, MidiDevice::from_ptr(ptr)) + } + + /// Connects a native Midi Device object to the associated Java MidiDevice object. + /// + /// Use the returned [`SafeMidiDevice`] to access the rest of the native MIDI API. + /// + /// # Safety + /// `env` and `midi_device_obj` must be valid pointers to a [`jni_sys::JNIEnv`] instance and a + /// Java [`MidiDevice`](https://developer.android.com/reference/android/media/midi/MidiDevice) + /// instance. + pub unsafe fn from_java( + env: *mut jni_sys::JNIEnv, + midi_device_obj: jni_sys::jobject, + ) -> Result { + Ok(Self::from_unsafe( + env, + MidiDevice::from_java(env, midi_device_obj)?, + )) + } + + /// Wraps the given unsafe [`MidiDevice`] instance into a safe counterpart. + /// + /// # Safety + /// + /// `env` must be a valid pointer to a [`jni_sys::JNIEnv`] instance. + pub unsafe fn from_unsafe(env: *mut jni_sys::JNIEnv, midi_device: MidiDevice) -> Self { + let env_functions = env.as_mut().unwrap().as_ref().unwrap_unchecked(); + let mut java_vm = MaybeUninit::uninit(); + if (env_functions.GetJavaVM.unwrap_unchecked())(env, java_vm.as_mut_ptr()) + != jni_sys::JNI_OK + { + panic!("failed to get the current Java VM"); + } + let java_vm = NonNull::new_unchecked(java_vm.assume_init()); + + SafeMidiDevice { + inner: Arc::new(SafeMidiDeviceBox { + midi_device: ManuallyDrop::new(midi_device), + java_vm, + }), + } + } + + /// Opens the input port so that the client can send data to it. + pub fn open_safe_input_port(&self, port_number: i32) -> Result { + Ok(SafeMidiInputPort { + // Convert the returned MidiInputPort<'_> into a MidiInputPort<'static>. + // + // SAFETY: the associated MIDI device of the input port will be alive during the + // lifetime of the returned SafeMidiInputPort because it is hold by _device. + // Since Rust calls the destructor of fields in declaration order, the MIDI device + // will be alive even when the input port is being dropped. + inner: unsafe { mem::transmute(self.open_input_port(port_number)?) }, + _device: Arc::clone(&self.inner), + }) + } + + /// Opens the output port so that the client can receive data from it. + pub fn open_safe_output_port(&self, port_number: i32) -> Result { + Ok(SafeMidiOutputPort { + // Convert the returned MidiInputPort<'_> into a MidiInputPort<'static>. + // + // SAFETY: the associated MIDI device of the output port will be alive during the + // lifetime of the returned SafeMidiOutputPort because it is hold by _device. + // Since Rust calls the destructor of fields in declaration order, the MIDI device + // will be alive even when the output port is being dropped. + inner: unsafe { mem::transmute(self.open_output_port(port_number)?) }, + _device: Arc::clone(&self.inner), + }) + } +} + +impl Deref for SafeMidiDevice { + type Target = MidiDevice; + + fn deref(&self) -> &Self::Target { + &self.inner.midi_device + } +} + +impl fmt::Debug for SafeMidiDevice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SafeMidiDevice") + .field("ptr", &self.inner.midi_device.ptr) + .field("java_vm", &self.inner.java_vm) + .finish() + } +} + +pub struct SafeMidiInputPort { + inner: MidiInputPort<'static>, + _device: Arc, +} + +impl fmt::Debug for SafeMidiInputPort { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SafeMidiInputPort") + .field("inner", &self.inner.ptr) + .finish() + } +} + +impl Deref for SafeMidiInputPort { + type Target = MidiInputPort<'static>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: a AMidi port is a mere holder of an atomic state, a pointer to the associated MIDI +// device, a binder, and a file descriptor, all of which are safe to be sent to another thread. +// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/media/native/midi/amidi.cpp?q=symbol%3A%5CbAMIDI_Port%5Cb%20case%3Ayes +unsafe impl Send for SafeMidiInputPort {} + +// SAFETY: AMidiInputPort contains a file descriptor to a socket opened with the SOCK_SEQPACKET +// mode, which preserves message boundaries so the receiver of a message always reads the whole +// message. +// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/media/native/midi/amidi.cpp?q=symbol%3A%5CbAMIDI_PACKET_SIZE%5Cb%20case%3Ayes +unsafe impl Sync for SafeMidiInputPort {} + +pub struct SafeMidiOutputPort { + inner: MidiOutputPort<'static>, + _device: Arc, +} + +impl fmt::Debug for SafeMidiOutputPort { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SafeMidiOutputPort") + .field("inner", &self.inner.ptr) + .finish() + } +} + +impl Deref for SafeMidiOutputPort { + type Target = MidiOutputPort<'static>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +// SAFETY: a AMidi port is a mere holder of an atomic state, a pointer to the associated MIDI +// device, a binder, and a file descriptor, all of which are safe to be sent to another thread. +// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/media/native/midi/amidi.cpp?q=symbol%3A%5CbAMIDI_Port%5Cb%20case%3Ayes +unsafe impl Send for SafeMidiOutputPort {} + +// SAFETY: AMidiOutputPort is guarded by a atomic state ([`AMIDI_Port::state`]), which enables +// [`ffi::AMidiOutputPort_receive`] to detect accesses from multiple threads and return error. +// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/media/native/midi/amidi.cpp?q=symbol%3A%5CbMidiReceiver%3A%3Areceive%5Cb%20case%3Ayes +unsafe impl Sync for SafeMidiOutputPort {} From 129edc273f431c5cc971a7bc8a2d53653fbeaa0e Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 16:11:51 +0900 Subject: [PATCH 45/51] ndk: Fix typos in ndk::midi::safe --- ndk/src/midi/safe.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi/safe.rs b/ndk/src/midi/safe.rs index 09c391de..de20c2bd 100644 --- a/ndk/src/midi/safe.rs +++ b/ndk/src/midi/safe.rs @@ -30,10 +30,10 @@ struct SafeMidiDeviceBox { java_vm: NonNull, } -// SAFETY: [`SafeMidiDeviceBox::drop`] attached the calling thread to the Java VM if required. +// SAFETY: [`SafeMidiDeviceBox::drop`] attaches the calling thread to the Java VM if required. unsafe impl Send for SafeMidiDeviceBox {} -// SAFETY: [`SafeMidiDeviceBox::drop`] attached the calling thread to the Java VM if required. +// SAFETY: [`SafeMidiDeviceBox::drop`] attaches the calling thread to the Java VM if required. unsafe impl Sync for SafeMidiDeviceBox {} impl Drop for SafeMidiDeviceBox { From 83fe4b215b073faa5b80ab85a83176e5236b1df7 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 16:18:04 +0900 Subject: [PATCH 46/51] ndk: Fix clippy warnings in ndk::midi --- ndk/src/midi/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi/mod.rs b/ndk/src/midi/mod.rs index d3fd3c31..dedea996 100644 --- a/ndk/src/midi/mod.rs +++ b/ndk/src/midi/mod.rs @@ -153,7 +153,7 @@ impl MidiDevice { /// Opens the input port so that the client can send data to it. Note that the returned /// [`MidiInputPort`] is intentionally `!Send` and `!Sync`; please use /// [`safe::SafeMidiDevice::open_safe_input_port`] if you need the thread-safe version. - pub fn open_input_port(&self, port_number: i32) -> Result { + pub fn open_input_port(&self, port_number: i32) -> Result> { unsafe { let input_port = construct(|res| ffi::AMidiInputPort_open(self.ptr.as_ptr(), port_number, res))?; @@ -164,7 +164,7 @@ impl MidiDevice { /// Opens the output port so that the client can receive data from it. Note that the returned /// [`MidiOutputPort`] is intentionally `!Send` and `!Sync`; please use /// [`safe::SafeMidiDevice::open_safe_output_port`] if you need the thread-safe version. - pub fn open_output_port(&self, port_number: i32) -> Result { + pub fn open_output_port(&self, port_number: i32) -> Result> { unsafe { let output_port = construct(|res| ffi::AMidiOutputPort_open(self.ptr.as_ptr(), port_number, res))?; From 8c64160da942a667fcec49d4d228cf8675fd20c7 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 16:27:53 +0900 Subject: [PATCH 47/51] ndk: Make examples in ndk::midi compilable --- ndk/src/midi/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ndk/src/midi/mod.rs b/ndk/src/midi/mod.rs index dedea996..c177110b 100644 --- a/ndk/src/midi/mod.rs +++ b/ndk/src/midi/mod.rs @@ -28,11 +28,17 @@ pub enum MidiOpcode { /// The length of the received message. The caller should limit the passed `buffer` slice to /// this length after [`MidiOutputPort::receive()`] returns. /// ```no_run - /// let output_port: MidiOutputPort = ...; + /// use ndk::midi::{MidiOutputPort, MidiOpcode}; + /// + /// let output_port: MidiOutputPort = todo!(); /// let mut buffer = [0u8; 128]; /// if let Ok(MidiOpcode::Data { length, .. }) = output_port.receive(&mut buffer) { - /// let buffer = &buffer[..length]; - /// ... + /// // process message + /// if let [message, key, velocity] = &buffer[..length] { + /// if message & 0xF0 == 0x90 { /* Note On message */ } + /// else if message & 0xF0 == 0x80 { /* Note Off message */ } + /// else { /* */ } + /// } /// } /// ``` length: usize, From 5428ca748a27ebf8b65bf5cea10eb80a178c0492 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 16:33:08 +0900 Subject: [PATCH 48/51] ndk: Remove link to SafeMidiDeviceBox from the module-level doc-comment of ndk::midi::safe --- ndk/src/midi/safe.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/src/midi/safe.rs b/ndk/src/midi/safe.rs index de20c2bd..2fe59730 100644 --- a/ndk/src/midi/safe.rs +++ b/ndk/src/midi/safe.rs @@ -1,8 +1,8 @@ //! Safe bindings for [`AMidiDevice`], [`AMidiInputPort`], and [`AMidiOutputPort`] //! -//! Provides implementation of [`SafeMidiDeviceBox`] that ensures the current thread is attached to +//! Provides implementation of `SafeMidiDeviceBox` that ensures the current thread is attached to //! the Java VM when being dropped, which is required by [`AMidiDevice_release`]. All other types -//! of this module holds an [`Arc`]. +//! of this module holds an [`Arc`] of `SafeMidiDeviceBox`. //! //! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice //! [`AMidiDevice_release`]: https://developer.android.com/ndk/reference/group/midi#amididevice_release From 802ab4af3997531fb4ca640e2b66ab8d175b3c67 Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 17:21:55 +0900 Subject: [PATCH 49/51] ndk: Add more detailed description about safety of AMidi functions to ndk::midi::safe --- ndk/src/midi/safe.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ndk/src/midi/safe.rs b/ndk/src/midi/safe.rs index 2fe59730..acfb2f61 100644 --- a/ndk/src/midi/safe.rs +++ b/ndk/src/midi/safe.rs @@ -4,6 +4,9 @@ //! the Java VM when being dropped, which is required by [`AMidiDevice_release`]. All other types //! of this module holds an [`Arc`] of `SafeMidiDeviceBox`. //! +//! Note that all other functions except for [`AMidiDevice_fromJava`] and [`AMidiDevice_release`] +//! are safe to be called in any thread without the calling thread attached to the Java VM. +//! //! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice //! [`AMidiDevice_release`]: https://developer.android.com/ndk/reference/group/midi#amididevice_release //! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport From 300118e796d85bda634cc198356e453fa100ab7d Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 17:33:57 +0900 Subject: [PATCH 50/51] ndk: Add missing brackets to links to functions in doc-comments in ndk::midi --- ndk/src/midi/mod.rs | 8 ++++---- ndk/src/midi/safe.rs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ndk/src/midi/mod.rs b/ndk/src/midi/mod.rs index c177110b..763cb3da 100644 --- a/ndk/src/midi/mod.rs +++ b/ndk/src/midi/mod.rs @@ -158,7 +158,7 @@ impl MidiDevice { /// Opens the input port so that the client can send data to it. Note that the returned /// [`MidiInputPort`] is intentionally `!Send` and `!Sync`; please use - /// [`safe::SafeMidiDevice::open_safe_input_port`] if you need the thread-safe version. + /// [`safe::SafeMidiDevice::open_safe_input_port()`] if you need the thread-safe version. pub fn open_input_port(&self, port_number: i32) -> Result> { unsafe { let input_port = @@ -169,7 +169,7 @@ impl MidiDevice { /// Opens the output port so that the client can receive data from it. Note that the returned /// [`MidiOutputPort`] is intentionally `!Send` and `!Sync`; please use - /// [`safe::SafeMidiDevice::open_safe_output_port`] if you need the thread-safe version. + /// [`safe::SafeMidiDevice::open_safe_output_port()`] if you need the thread-safe version. pub fn open_output_port(&self, port_number: i32) -> Result> { unsafe { let output_port = @@ -189,7 +189,7 @@ impl Drop for MidiDevice { } /// A wrapper over [`ffi::AMidiInputPort`]. Note that [`MidiInputPort`] is intentionally `!Send` and -/// `!Sync`; please use [`safe::SafeMidiDevice::open_safe_input_port`] if you need the thread-safe +/// `!Sync`; please use [`safe::SafeMidiDevice::open_safe_input_port()`] if you need the thread-safe /// version. pub struct MidiInputPort<'a> { ptr: NonNull, @@ -280,7 +280,7 @@ impl<'a> Drop for MidiInputPort<'a> { } /// A wrapper over [`ffi::AMidiOutputPort`]. Note that [`MidiOutputPort`] is intentionally `!Send` -/// and `!Sync`; please use [`safe::SafeMidiDevice::open_safe_output_port`] if you need the +/// and `!Sync`; please use [`safe::SafeMidiDevice::open_safe_output_port()`] if you need the /// thread-safe version. pub struct MidiOutputPort<'a> { ptr: NonNull, diff --git a/ndk/src/midi/safe.rs b/ndk/src/midi/safe.rs index acfb2f61..83e44274 100644 --- a/ndk/src/midi/safe.rs +++ b/ndk/src/midi/safe.rs @@ -1,14 +1,14 @@ //! Safe bindings for [`AMidiDevice`], [`AMidiInputPort`], and [`AMidiOutputPort`] //! //! Provides implementation of `SafeMidiDeviceBox` that ensures the current thread is attached to -//! the Java VM when being dropped, which is required by [`AMidiDevice_release`]. All other types -//! of this module holds an [`Arc`] of `SafeMidiDeviceBox`. +//! the Java VM when being dropped, which is required by [`ffi::AMidiDevice_release()`]. All other +//! types of this module holds an [`Arc`] of `SafeMidiDeviceBox`. //! -//! Note that all other functions except for [`AMidiDevice_fromJava`] and [`AMidiDevice_release`] -//! are safe to be called in any thread without the calling thread attached to the Java VM. +//! Note that all other functions except for [`ffi::AMidiDevice_fromJava()`] and +//! [`ffi::AMidiDevice_release()`] are safe to be called in any thread without the calling thread +//! attached to the Java VM. //! //! [`AMidiDevice`]: https://developer.android.com/ndk/reference/group/midi#amididevice -//! [`AMidiDevice_release`]: https://developer.android.com/ndk/reference/group/midi#amididevice_release //! [`AMidiInputPort`]: https://developer.android.com/ndk/reference/group/midi#amidiinputport //! [`AMidiOutputPort`]: https://developer.android.com/ndk/reference/group/midi#amidioutputport @@ -27,16 +27,16 @@ use super::*; /// [`MidiDevice`] is not dropped while the safe wrappers are alive. /// /// [`SafeMidiDeviceBox`] also holds a pointer to the current Java VM to attach the calling thread -/// of [`Drop::drop`] to the VM, which is required by [`ffi::AMidiDevice_release`]. +/// of [`SafeMidiDeviceBox::drop()`] to the VM, which is required by [`ffi::AMidiDevice_release()`]. struct SafeMidiDeviceBox { midi_device: ManuallyDrop, java_vm: NonNull, } -// SAFETY: [`SafeMidiDeviceBox::drop`] attaches the calling thread to the Java VM if required. +// SAFETY: [`SafeMidiDeviceBox::drop()`] attaches the calling thread to the Java VM if required. unsafe impl Send for SafeMidiDeviceBox {} -// SAFETY: [`SafeMidiDeviceBox::drop`] attaches the calling thread to the Java VM if required. +// SAFETY: [`SafeMidiDeviceBox::drop()`] attaches the calling thread to the Java VM if required. unsafe impl Sync for SafeMidiDeviceBox {} impl Drop for SafeMidiDeviceBox { @@ -239,6 +239,6 @@ impl Deref for SafeMidiOutputPort { unsafe impl Send for SafeMidiOutputPort {} // SAFETY: AMidiOutputPort is guarded by a atomic state ([`AMIDI_Port::state`]), which enables -// [`ffi::AMidiOutputPort_receive`] to detect accesses from multiple threads and return error. +// [`ffi::AMidiOutputPort_receive()`] to detect accesses from multiple threads and return error. // https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/media/native/midi/amidi.cpp?q=symbol%3A%5CbMidiReceiver%3A%3Areceive%5Cb%20case%3Ayes unsafe impl Sync for SafeMidiOutputPort {} From 8086a7dba9a7bedcd9b9f00acf804afef8b3bead Mon Sep 17 00:00:00 2001 From: Chanjung Kim Date: Thu, 17 Aug 2023 17:50:46 +0900 Subject: [PATCH 51/51] ndk: Enable media_error when "midi" is enabled --- ndk/Cargo.toml | 2 +- ndk/src/media_error.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ndk/Cargo.toml b/ndk/Cargo.toml index 6036e4b2..7c2ed95d 100644 --- a/ndk/Cargo.toml +++ b/ndk/Cargo.toml @@ -18,7 +18,7 @@ all = ["audio", "bitmap","media", "api-level-30"] audio = ["ffi/audio", "api-level-26"] bitmap = ["ffi/bitmap"] media = ["ffi/media"] -midi = ["ffi/midi", "api-level-29", "media"] +midi = ["ffi/midi", "api-level-29"] api-level-23 = [] api-level-24 = ["api-level-23"] diff --git a/ndk/src/media_error.rs b/ndk/src/media_error.rs index 3acb9f93..63f8a44c 100644 --- a/ndk/src/media_error.rs +++ b/ndk/src/media_error.rs @@ -2,7 +2,7 @@ //! //! Also used outside of `libmediandk.so` in `libamidi.so` for example. -#![cfg(feature = "media")] +#![cfg(any(feature = "media", feature = "midi"))] // The cfg(feature) bounds for some pub(crate) fn uses are non-trivial and will become even more // complex going forward. Allow them to be unused when compiling with certain feature combinations. #![allow(dead_code)]