From 02fd56771756cb03f3df3a434dda79ac44505bb3 Mon Sep 17 00:00:00 2001 From: Yury Date: Tue, 5 Nov 2024 13:55:22 +0300 Subject: [PATCH] Add av::AudioFile. Refs #24 --- cidre/pomace/av/av.h | 2 + cidre/src/av.rs | 1 + cidre/src/av/audio.rs | 3 + cidre/src/av/audio/file.rs | 188 +++++++++++++++++++++++++++++++++++++ cidre/src/ns/error.rs | 1 + 5 files changed, 195 insertions(+) create mode 100644 cidre/src/av/audio/file.rs diff --git a/cidre/pomace/av/av.h b/cidre/pomace/av/av.h index 6da32cc8..7d601595 100644 --- a/cidre/pomace/av/av.h +++ b/cidre/pomace/av/av.h @@ -51,6 +51,7 @@ Class AV_AUDIO_PCM_BUFFER; Class AV_AUDIO_COMPRESSED_BUFFER; Class AV_AUDIO_FORMAT; Class AV_AUDIO_CONVERTER; +Class AV_AUDIO_FILE; Class AV_PLAYER; @@ -172,6 +173,7 @@ static void av_initializer(void) AV_AUDIO_PCM_BUFFER = [AVAudioPCMBuffer class]; AV_AUDIO_COMPRESSED_BUFFER = [AVAudioCompressedBuffer class]; AV_AUDIO_CONVERTER = [AVAudioConverter class]; + AV_AUDIO_FILE = [AVAudioFile class]; AV_PLAYER = [AVPlayer class]; diff --git a/cidre/src/av.rs b/cidre/src/av.rs index e8838020..6ecd168a 100644 --- a/cidre/src/av.rs +++ b/cidre/src/av.rs @@ -126,6 +126,7 @@ pub use audio::Converter as AudioConverter; pub use audio::ConverterInputBlock as AudioConverterInputBlock; pub use audio::ConverterInputStatus as AudioConverterInputStatus; pub use audio::Engine as AudioEngine; +pub use audio::File as AudioFile; pub use audio::Format as AudioFormat; pub use audio::FrameCount as AudioFrameCount; pub use audio::FramePos as AudioFramePos; diff --git a/cidre/src/av/audio.rs b/cidre/src/av/audio.rs index f853c67f..85f1b104 100644 --- a/cidre/src/av/audio.rs +++ b/cidre/src/av/audio.rs @@ -107,6 +107,9 @@ mod format; pub use format::CommonFormat; pub use format::Format; +mod file; +pub use file::File; + mod channel_layout; pub use channel_layout::ChannelLayout; diff --git a/cidre/src/av/audio/file.rs b/cidre/src/av/audio/file.rs new file mode 100644 index 00000000..8df4b678 --- /dev/null +++ b/cidre/src/av/audio/file.rs @@ -0,0 +1,188 @@ +use crate::{arc, av, define_cls, define_obj_type, ns, objc}; + +define_obj_type!( + #[doc(alias = "AVAudioFile")] + pub File(ns::Id) +); + +impl arc::A { + #[objc::msg_send(initForReading:error:)] + pub unsafe fn init_for_reading_err<'ear>( + self, + file_url: &ns::Url, + err: *mut Option<&'ear ns::Error>, + ) -> Option>; + + #[objc::msg_send(initForReading:commonFormat:interleaved:error:)] + pub unsafe fn init_for_reading_common_format_err<'ear>( + self, + file_url: &ns::Url, + common_format: av::audio::CommonFormat, + interleaved: bool, + err: *mut Option<&'ear ns::Error>, + ) -> Option>; + + #[objc::msg_send(initForWriting:settings:error:)] + pub unsafe fn init_for_writing_err<'ear>( + self, + file_url: &ns::Url, + settings: &ns::Dictionary, + err: *mut Option<&'ear ns::Error>, + ) -> Option>; + + #[objc::msg_send(initForWriting:settings:commonFormat:interleaved:error:)] + pub unsafe fn init_for_writing_common_format_err<'ear>( + self, + file_url: &ns::Url, + settings: &ns::Dictionary, + common_format: av::audio::CommonFormat, + interleaved: bool, + err: *mut Option<&'ear ns::Error>, + ) -> Option>; +} + +impl File { + define_cls!(AV_AUDIO_FILE); + + pub fn open_read<'ear>(file_url: &ns::Url) -> ns::Result<'ear, arc::R> { + ns::if_none(|err| unsafe { Self::alloc().init_for_reading_err(file_url, err) }) + } + + pub fn open_read_common_format<'ear>( + file_url: &ns::Url, + common_format: av::audio::CommonFormat, + interleaved: bool, + ) -> ns::Result<'ear, arc::R> { + ns::if_none(|err| unsafe { + Self::alloc().init_for_reading_common_format_err( + file_url, + common_format, + interleaved, + err, + ) + }) + } + + pub fn open_write<'ear>( + file_url: &ns::Url, + settings: &ns::Dictionary, + ) -> ns::Result<'ear, arc::R> { + ns::if_none(|err| unsafe { Self::alloc().init_for_writing_err(file_url, settings, err) }) + } + + pub fn open_write_common_format<'ear>( + file_url: &ns::Url, + settings: &ns::Dictionary, + common_format: av::audio::CommonFormat, + interleaved: bool, + ) -> ns::Result<'ear, arc::R> { + ns::if_none(|err| unsafe { + Self::alloc().init_for_writing_common_format_err( + file_url, + settings, + common_format, + interleaved, + err, + ) + }) + } + + #[objc::msg_send(close)] + pub fn close(&mut self); + + #[objc::msg_send(readIntoBuffer:error:)] + pub unsafe fn read_err<'ear>( + &mut self, + buffer: &mut av::audio::PcmBuf, + err: *mut Option<&'ear ns::Error>, + ) -> bool; + + pub fn read(&mut self, buffer: &mut av::audio::PcmBuf) -> ns::Result { + ns::if_false(|err| unsafe { self.read_err(buffer, err) }) + } + + #[objc::msg_send(readIntoBuffer:frameCount:error:)] + pub unsafe fn read_n_err<'ear>( + &mut self, + buffer: &mut av::audio::PcmBuf, + frame_count: av::AudioFrameCount, + err: *mut Option<&'ear ns::Error>, + ) -> bool; + + pub fn read_n<'ear>( + &mut self, + buffer: &mut av::audio::PcmBuf, + frame_count: av::AudioFrameCount, + ) -> ns::Result { + ns::if_false(|err| unsafe { self.read_n_err(buffer, frame_count, err) }) + } + + #[objc::msg_send(writeFromBuffer:error:)] + pub unsafe fn write_err<'ear>( + &mut self, + buffer: &av::audio::PcmBuf, + err: *mut Option<&'ear ns::Error>, + ) -> bool; + + pub fn write<'ear>(&mut self, buffer: &av::audio::PcmBuf) -> ns::Result { + ns::if_false(|err| unsafe { self.write_err(buffer, err) }) + } + + /// Whether the file is open or not. + #[objc::msg_send(isOpen)] + pub fn is_open(&self) -> bool; + + /// The URL the file is reading or writing. + #[objc::msg_send(url)] + pub fn url(&self) -> arc::R; + + /// The on-disk format of the file. + #[objc::msg_send(fileFormat)] + pub fn file_format(&self) -> arc::R; + + /// The processing format of the file. + #[objc::msg_send(processingFormat)] + pub fn processing_format(&self) -> arc::R; + + /// The number of sample frames in the file. + /// + /// This can be expensive to compute for the first time. + #[objc::msg_send(length)] + pub fn len(&self) -> av::audio::FramePos; + + /// The position in the file at which the next read or write will occur. + #[objc::msg_send(framePosition)] + pub fn frame_pos(&self) -> av::audio::FramePos; + + #[objc::msg_send(setFramePosition:)] + pub fn set_frame_pos(&mut self, val: av::audio::FramePos); +} + +extern "C" { + static AV_AUDIO_FILE: &'static objc::Class; +} + +#[cfg(test)] +mod tests { + use crate::{av, ns}; + + #[test] + fn basics() { + let _ = std::fs::remove_file("/tmp/foo.caf"); + let url = ns::Url::with_fs_path_str("/tmp/foo.caf", false); + let _err = av::AudioFile::open_read(&url).expect_err("Should fail"); + + let settings = ns::Dictionary::new(); + let mut file = av::AudioFile::open_write_common_format( + &url, + &settings, + av::AudioCommonFormat::PcmF32, + true, + ) + .expect("Error creating file"); + assert!(file.is_open()); + assert_eq!(0, file.len()); + file.close(); + assert!(!file.is_open()); + } +} diff --git a/cidre/src/ns/error.rs b/cidre/src/ns/error.rs index f3b40900..1446ccb0 100644 --- a/cidre/src/ns/error.rs +++ b/cidre/src/ns/error.rs @@ -45,6 +45,7 @@ define_obj_type!( ); unsafe impl Send for Error {} +unsafe impl Sync for Error {} impl std::error::Error for Error {}