sapi_lite/audio/
stream.rs

1use std::path::Path;
2use std::ptr::null;
3
4use windows as Windows;
5use Windows::core::{GUID, HRESULT};
6use Windows::Win32::Foundation::E_OUTOFMEMORY;
7use Windows::Win32::Media::Speech::{
8    ISpStream, SpStream, SPFILEMODE, SPFM_CREATE_ALWAYS, SPFM_OPEN_READONLY,
9};
10use Windows::Win32::System::Com::{CoCreateInstance, IStream, CLSCTX_ALL};
11use Windows::Win32::UI::Shell::SHCreateMemStream;
12
13use crate::com_util::Intf;
14use crate::Result;
15
16use super::AudioFormat;
17
18/// An audio stream to read from or write to.
19pub struct AudioStream {
20    intf: Intf<ISpStream>,
21}
22
23#[allow(non_upper_case_globals)]
24const SPDFID_WaveFormatEx: GUID = GUID::from_u128(0xc31adbae_527f_4ff5_a230_f62bb61ff70c);
25
26impl AudioStream {
27    /// Opens the file at the given path and returns a read-only audio stream with the specified
28    /// format.
29    pub fn open_file<P: AsRef<Path>>(path: P, format: &AudioFormat) -> Result<Self> {
30        Self::from_file(path, format, SPFM_OPEN_READONLY)
31    }
32
33    /// Creates an empty file at the given path and returns a writable audio stream with the
34    /// specified format.
35    pub fn create_file<P: AsRef<Path>>(path: P, format: &AudioFormat) -> Result<Self> {
36        Self::from_file(path, format, SPFM_CREATE_ALWAYS)
37    }
38
39    /// Wraps a COM stream and returns an audio stream with the specified format.
40    pub fn from_stream<S: Into<IStream>>(stream: S, format: &AudioFormat) -> Result<Self> {
41        let intf: ISpStream = unsafe { CoCreateInstance(&SpStream, None, CLSCTX_ALL) }?;
42        unsafe { intf.SetBaseStream(stream.into(), &SPDFID_WaveFormatEx, &format.to_sapi()) }?;
43        Ok(Self { intf: Intf(intf) })
44    }
45
46    fn from_file<P: AsRef<Path>>(path: P, format: &AudioFormat, mode: SPFILEMODE) -> Result<Self> {
47        let intf: ISpStream = unsafe { CoCreateInstance(&SpStream, None, CLSCTX_ALL) }?;
48        unsafe {
49            intf.BindToFile(
50                path.as_ref().as_os_str(),
51                mode,
52                &SPDFID_WaveFormatEx,
53                &format.to_sapi(),
54                0,
55            )
56        }?;
57        Ok(AudioStream { intf: Intf(intf) })
58    }
59
60    pub(crate) fn to_sapi(&self) -> ISpStream {
61        self.intf.0.clone()
62    }
63}
64
65/// A data stream backed by a dynamically-sized memory buffer.
66pub struct MemoryStream {
67    intf: Intf<IStream>,
68}
69
70impl MemoryStream {
71    /// Creates a new stream and initializes its content with a copy of the given data.
72    pub fn new(init_data: Option<&[u8]>) -> Result<Self> {
73        Ok(Self {
74            intf: Intf(Self::create_stream(init_data)?),
75        })
76    }
77
78    /// If successful, returns a stream backed by the same memory buffer, but with its own
79    /// independent seek pointer.
80    pub fn try_clone(&self) -> Result<Self> {
81        unsafe { self.intf.Clone() }.map(|intf| Self { intf: Intf(intf) })
82    }
83
84    fn create_stream(init_data: Option<&[u8]>) -> std::result::Result<IStream, HRESULT> {
85        let size = init_data
86            .map(|buf| buf.len())
87            .unwrap_or(0)
88            .try_into()
89            .map_err(|_| E_OUTOFMEMORY)?;
90        unsafe { SHCreateMemStream(init_data.map(|buf| buf.as_ptr()).unwrap_or(null()), size) }
91            .ok_or(E_OUTOFMEMORY)
92    }
93}
94
95impl From<MemoryStream> for IStream {
96    fn from(source: MemoryStream) -> Self {
97        source.intf.0
98    }
99}