use std::cell::OnceCell;
use std::io::Cursor;
use anyhow::{Context, anyhow};
use log::{info, trace};
use pw::keys;
use pw::link::Link;
use pw::properties::properties;
use pw::spa::param::ParamType;
use pw::spa::param::audio::{AudioFormat, AudioInfoRaw};
use pw::spa::pod::serialize::PodSerializer;
use pw::spa::pod::{Object, Pod, Value};
use pw::spa::sys::{SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_MAX_CHANNELS};
use pw::spa::utils::{Direction, SpaTypes};
use pw::stream::StreamState::Unconnected;
use pw::stream::{Stream, StreamFlags, StreamListener, StreamRc};
use uuid::Uuid;
use crate::common::audiofox::AUDIOFOX;
use crate::common::builder::{CHUNK_SIZE_U8, ChunkBuilder};
use crate::common::callback::AudioCallback;
use crate::common::chunk::Chunk;
use crate::common::result::Result;
use crate::platform::linux::registry::{PortName, RegId, RegName};
use crate::platform::platform::command::CmdListener;
#[derive(Default)]
pub struct StreamState {
pub id: RegId,
pub in_left: PortName,
pub in_right: PortName,
pub names: Vec<RegName>,
pub ids: Vec<RegId>,
pub links: Vec<Link>,
listener: OnceCell<StreamListener<AudioInfoRaw>>,
stream: OnceCell<StreamRc>,
}
impl StreamState {
pub fn start(&self, listener: &CmdListener, uuid: Uuid, callback: AudioCallback) -> Result<()> {
let stream = StreamRc::new(
listener.core.clone(), AUDIOFOX, properties! {
*keys::NODE_ID => uuid.to_string(), *keys::NODE_NAME => AUDIOFOX,
*keys::MEDIA_CATEGORY => "Capture",
*keys::MEDIA_ROLE => "Music",
*keys::MEDIA_TYPE => "Audio",
}).context("Failed to create audio stream")?;
let builder = ChunkBuilder::new_u8();
let stream_listener = stream
.add_local_listener_with_user_data(AudioInfoRaw::default())
.param_changed(Self::on_update(uuid))
.process(Self::on_buffer(callback, builder))
.state_changed(Self::on_state_change(listener.clone()))
.register()
.context("Failed to register stream listener")?;
Self::finalize(&stream)?;
self.stream.set(stream).map_err(|_| anyhow!("Stream already initialized"))?;
self.listener.set(stream_listener).map_err(|_| anyhow!("Stream listener already initialized"))?;
Ok(())
}
fn on_update(uuid: Uuid) -> impl Fn(&Stream, &mut AudioInfoRaw, u32, Option<&Pod>) {
move |_, user_data, id, param| {
let Some(param) = param else {
trace!("stream uuid={uuid}: format reset");
return;
};
if id == ParamType::Format.as_raw() && user_data.parse(param).is_ok() {
let prefix = if log::log_enabled!(log::Level::Debug) {
format!("stream uuid={uuid}: ")
} else {
String::new()
};
info!("{}capturing at rate:{}, channels:{}", prefix, user_data.rate(), user_data.channels());
}
}
}
fn on_buffer(callback: AudioCallback, mut builder: ChunkBuilder<u8, CHUNK_SIZE_U8>) -> impl FnMut(&Stream, &mut AudioInfoRaw) {
move |stream, _user_data| match stream.dequeue_buffer() {
None => trace!("no buffer available"),
Some(mut buffer) => {
let datas = buffer.datas_mut();
if datas.is_empty() {
return;
}
let data = &mut datas[0];
let size = data.chunk().size() as usize;
if let Some(sample_slice) = data.data() {
let samples = &mut sample_slice[..size];
if let Some(data) = builder.extend_from_slice(samples) {
callback(Chunk::from(&builder));
builder.reset_with_overflow(data);
}
}
}
}
}
fn on_state_change(listener: CmdListener) -> impl FnMut(&Stream, &mut AudioInfoRaw, pw::stream::StreamState, pw::stream::StreamState) {
move |stream, _user_data, _old, new| {
if new == Unconnected {
let uuid: Uuid = stream.properties().get(*keys::NODE_SESSION)
.expect("Stream should always have a UUID")
.parse()
.expect("Stream UUID should never be invalid");
listener.on_end(uuid);
}
}
}
fn finalize(stream: &StreamRc) -> Result<()> {
let mut audio_info = AudioInfoRaw::new();
audio_info.set_format(AudioFormat::F32LE);
let mut positions = [0u32; SPA_AUDIO_MAX_CHANNELS as usize];
positions[0] = SPA_AUDIO_CHANNEL_FL;
positions[1] = SPA_AUDIO_CHANNEL_FR;
audio_info.set_position(positions);
audio_info.set_channels(2);
let props = PodSerializer::serialize(
Cursor::new(Vec::new()),
&Value::Object(Object {
type_: SpaTypes::ObjectParamFormat.as_raw(),
id: ParamType::EnumFormat.as_raw(),
properties: audio_info.into(),
})).context("Failed to serialize audio properties")?
.0.into_inner();
stream.connect(
Direction::Input,
None,
StreamFlags::MAP_BUFFERS | StreamFlags::RT_PROCESS,
&mut [Pod::from_bytes(&props).context("Failed to create props Pod")?],
).context("Failed to connect the stream")?;
Ok(())
}
}