#![forbid(unsafe_code)]
use std::collections::VecDeque;
use mediaway_common::{AudioFrame, Bytes, CodecKind, Packet, Rational, SampleFormat, StreamInfo};
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PcmError {
#[error("PCM sample format does not match the configured format")]
SampleFormatMismatch,
#[error("PCM sample rate does not match the configured rate")]
SampleRateMismatch,
#[error("PCM channel count does not match the configured channel count")]
ChannelCountMismatch,
#[error("PCM passthrough session closed")]
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PcmFormat {
pub sample_rate: u32,
pub channels: u16,
pub sample_format: SampleFormat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PcmPassthroughConfig {
pub format: PcmFormat,
pub time_base: Rational,
}
impl PcmPassthroughConfig {
#[must_use]
pub const fn new(format: PcmFormat, time_base: Rational) -> Self {
Self { format, time_base }
}
}
#[allow(clippy::missing_const_for_fn, reason = "StreamInfo holds Bytes")]
fn stream_info(config: PcmPassthroughConfig) -> StreamInfo {
StreamInfo::Audio {
id: 0,
codec: CodecKind::RawAudio,
time_base: config.time_base,
extra_data: Bytes::new(),
sample_rate: config.format.sample_rate,
channels: config.format.channels,
}
}
pub struct PcmEncoder {
config: PcmPassthroughConfig,
stream_info: StreamInfo,
pending: VecDeque<Packet>,
closed: bool,
}
impl PcmEncoder {
#[must_use]
pub fn new(config: PcmPassthroughConfig) -> Self {
Self {
config,
stream_info: stream_info(config),
pending: VecDeque::new(),
closed: false,
}
}
#[must_use]
pub const fn stream_info(&self) -> &StreamInfo {
&self.stream_info
}
pub fn push_frame(&mut self, frame: &AudioFrame) -> Result<(), PcmError> {
if self.closed {
return Err(PcmError::Closed);
}
if frame.format != self.config.format.sample_format {
return Err(PcmError::SampleFormatMismatch);
}
if frame.sample_rate != self.config.format.sample_rate {
return Err(PcmError::SampleRateMismatch);
}
if frame.channels != self.config.format.channels {
return Err(PcmError::ChannelCountMismatch);
}
self.pending.push_back(Packet {
stream_id: self.stream_info.id(),
pts: frame.pts,
dts: frame.pts,
duration: frame.duration,
is_keyframe: true,
is_discard: false,
payload: frame.data.clone(),
});
Ok(())
}
pub fn poll_packet(&mut self) -> Result<Option<Packet>, PcmError> {
Ok(self.pending.pop_front())
}
pub const fn flush(&mut self) -> Result<(), PcmError> {
self.closed = true;
Ok(())
}
}
pub struct PcmDecoder {
config: PcmPassthroughConfig,
stream_info: StreamInfo,
pending: VecDeque<AudioFrame>,
closed: bool,
}
impl PcmDecoder {
#[must_use]
pub fn new(config: PcmPassthroughConfig) -> Self {
Self {
config,
stream_info: stream_info(config),
pending: VecDeque::new(),
closed: false,
}
}
#[must_use]
pub const fn stream_info(&self) -> &StreamInfo {
&self.stream_info
}
pub fn push_packet(&mut self, packet: &Packet) -> Result<(), PcmError> {
if self.closed {
return Err(PcmError::Closed);
}
self.pending.push_back(AudioFrame {
pts: packet.pts,
duration: packet.duration,
sample_rate: self.config.format.sample_rate,
channels: self.config.format.channels,
format: self.config.format.sample_format,
data: packet.payload.clone(),
});
Ok(())
}
pub fn poll_frame(&mut self) -> Result<Option<AudioFrame>, PcmError> {
Ok(self.pending.pop_front())
}
pub const fn flush(&mut self) -> Result<(), PcmError> {
self.closed = true;
Ok(())
}
}
#[cfg(test)]
#[path = "pcm_tests.rs"]
mod tests;