use crate::options::DEFAULT_MAX_INPUT_BYTES;
pub const DEFAULT_MAX_OUTPUT_BYTES: u64 = 1 << 32;
pub const DEFAULT_MAX_CHANNELS: u32 = 8;
pub const DEFAULT_MAX_METADATA_BYTES: u64 = 16 << 20;
pub const DEFAULT_MAX_INDEX_ENTRIES: u32 = 1 << 20;
pub const DEFAULT_MAX_BOX_DEPTH: u32 = 32;
pub const DEFAULT_MAX_WORKSPACE_BYTES: u64 = 8 << 20;
pub const DEFAULT_MAX_BUFFERED_INPUT_BYTES: u64 = 1 << 20;
pub const DEFAULT_MAX_DECLARED_AU_BYTES: u32 = 64 << 10;
pub const LC_FILTERBANK_BYTES_PER_CH: u64 = (1024 + 2048) * 4;
pub const SBR_STATE_BYTES_PER_CH: u64 =
320 * 8 + 32 * 64 * 16 + 1280 * 8 + 128 * 64 * 16 + 8 * 32 * 16 + 40 * 64 * 16;
pub const PS_STATE_BYTES: u64 = 2 * 32 * 64 * 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputScope {
FiniteComplete,
StreamingFeed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetKind {
Input,
Output,
Channel,
Metadata,
Workspace,
AccessUnit,
}
impl core::fmt::Display for BudgetKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Input => "input",
Self::Output => "output",
Self::Channel => "channel",
Self::Metadata => "metadata",
Self::Workspace => "workspace",
Self::AccessUnit => "access-unit",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BudgetExceeded {
pub kind: BudgetKind,
pub observed: u64,
pub max: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct MemoryBudgets {
pub max_input_bytes: u64,
pub max_buffered_input_bytes: u64,
pub max_output_bytes: u64,
pub max_channels: u32,
pub max_metadata_bytes: u64,
pub max_index_entries: u32,
pub max_box_depth: u32,
pub max_workspace_bytes: u64,
pub max_declared_au_bytes: u32,
}
impl Default for MemoryBudgets {
fn default() -> Self {
Self {
max_input_bytes: DEFAULT_MAX_INPUT_BYTES,
max_buffered_input_bytes: DEFAULT_MAX_BUFFERED_INPUT_BYTES,
max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES,
max_channels: DEFAULT_MAX_CHANNELS,
max_metadata_bytes: DEFAULT_MAX_METADATA_BYTES,
max_index_entries: DEFAULT_MAX_INDEX_ENTRIES,
max_box_depth: DEFAULT_MAX_BOX_DEPTH,
max_workspace_bytes: DEFAULT_MAX_WORKSPACE_BYTES,
max_declared_au_bytes: DEFAULT_MAX_DECLARED_AU_BYTES,
}
}
}
impl MemoryBudgets {
pub fn unbounded_collection() -> Self {
Self {
max_output_bytes: u64::MAX / 2,
..Self::default()
}
}
pub fn validate(&self) -> Result<(), BudgetExceeded> {
fn nz(kind: BudgetKind, v: u64) -> Result<(), BudgetExceeded> {
if v == 0 {
Err(BudgetExceeded {
kind,
observed: 0,
max: 1,
})
} else {
Ok(())
}
}
nz(BudgetKind::Input, self.max_input_bytes)?;
nz(BudgetKind::Input, self.max_buffered_input_bytes)?;
nz(BudgetKind::Output, self.max_output_bytes)?;
nz(BudgetKind::Channel, u64::from(self.max_channels))?;
nz(BudgetKind::Metadata, self.max_metadata_bytes)?;
nz(BudgetKind::Metadata, u64::from(self.max_index_entries))?;
nz(BudgetKind::Metadata, u64::from(self.max_box_depth))?;
nz(BudgetKind::Workspace, self.max_workspace_bytes)?;
nz(
BudgetKind::AccessUnit,
u64::from(self.max_declared_au_bytes),
)?;
Ok(())
}
pub fn check_input_finite(&self, len: u64) -> Result<usize, BudgetExceeded> {
check_planned(BudgetKind::Input, len, self.max_input_bytes)
}
pub fn check_buffered(&self, buffered: u64) -> Result<usize, BudgetExceeded> {
check_planned(BudgetKind::Input, buffered, self.max_buffered_input_bytes)
}
pub fn check_declared_au(&self, len: u64) -> Result<usize, BudgetExceeded> {
check_planned(
BudgetKind::AccessUnit,
len,
u64::from(self.max_declared_au_bytes),
)
}
pub fn check_channels(&self, n: u32) -> Result<(), BudgetExceeded> {
if n == 0 || n > self.max_channels {
return Err(BudgetExceeded {
kind: BudgetKind::Channel,
observed: u64::from(n),
max: u64::from(self.max_channels),
});
}
Ok(())
}
pub fn check_output(&self, channels: u32, samples: u64) -> Result<usize, BudgetExceeded> {
self.check_channels(channels)?;
let Some(bytes) = pcm_bytes(channels, samples) else {
return Err(BudgetExceeded {
kind: BudgetKind::Output,
observed: u64::MAX,
max: self.effective_output_bytes(),
});
};
check_planned(BudgetKind::Output, bytes, self.effective_output_bytes())
}
pub fn check_index(&self, entries: u64, bytes_per_entry: u64) -> Result<usize, BudgetExceeded> {
if entries > u64::from(self.max_index_entries) {
return Err(BudgetExceeded {
kind: BudgetKind::Metadata,
observed: entries,
max: u64::from(self.max_index_entries),
});
}
let Some(bytes) = entries.checked_mul(bytes_per_entry) else {
return Err(BudgetExceeded {
kind: BudgetKind::Metadata,
observed: u64::MAX,
max: self.max_metadata_bytes,
});
};
check_planned(BudgetKind::Metadata, bytes, self.max_metadata_bytes)
}
pub fn check_box_depth(&self, depth: u32) -> Result<(), BudgetExceeded> {
if depth > self.max_box_depth {
return Err(BudgetExceeded {
kind: BudgetKind::Metadata,
observed: u64::from(depth),
max: u64::from(self.max_box_depth),
});
}
Ok(())
}
pub fn check_workspace(
&self,
channels: u32,
he: bool,
ps: bool,
) -> Result<usize, BudgetExceeded> {
self.check_channels(channels)?;
let Some(bytes) = codec_workspace_bytes(channels, he, ps) else {
return Err(BudgetExceeded {
kind: BudgetKind::Workspace,
observed: u64::MAX,
max: self.max_workspace_bytes,
});
};
check_planned(BudgetKind::Workspace, bytes, self.max_workspace_bytes)
}
pub fn effective_output_bytes(&self) -> u64 {
self.max_output_bytes.min(isize::MAX as u64)
}
}
#[inline]
pub fn pcm_bytes(channels: u32, samples: u64) -> Option<u64> {
u64::from(channels).checked_mul(samples)?.checked_mul(4)
}
pub fn codec_workspace_bytes(channels: u32, he: bool, ps: bool) -> Option<u64> {
let ch = u64::from(channels);
let mut n = ch.checked_mul(LC_FILTERBANK_BYTES_PER_CH)?;
if he {
n = n.checked_add(ch.checked_mul(SBR_STATE_BYTES_PER_CH)?)?;
}
if ps {
n = n.checked_add(PS_STATE_BYTES)?;
}
Some(n)
}
pub fn workspace_lower_bound_bytes(channels: u32, he: bool, ps: bool) -> Option<u64> {
codec_workspace_bytes(channels, he, ps)?.checked_add(DEFAULT_MAX_BUFFERED_INPUT_BYTES)
}
#[inline]
pub fn allocable_bytes(planned: u64) -> Option<usize> {
if planned > isize::MAX as u64 {
return None;
}
usize::try_from(planned).ok()
}
#[inline]
pub fn input_cap_applies(scope: InputScope) -> bool {
matches!(scope, InputScope::FiniteComplete)
}
pub fn check_planned(kind: BudgetKind, planned: u64, max: u64) -> Result<usize, BudgetExceeded> {
if planned > max {
return Err(BudgetExceeded {
kind,
observed: planned,
max,
});
}
allocable_bytes(planned).ok_or(BudgetExceeded {
kind,
observed: planned,
max: max.min(isize::MAX as u64),
})
}
#[cfg(test)]
#[path = "budgets_tests.rs"]
mod budgets_tests;