use core::ptr::{addr_of, addr_of_mut, copy_nonoverlapping, read_unaligned, write_unaligned};
use ffmpeg_next::{
codec::Parameters,
ffi::{
AV_INPUT_BUFFER_PADDING_SIZE, AVChannelCustom, AVChannelOrder, AVCodecParameters,
AVPacketSideData, AVPacketSideDataType, av_mallocz,
},
};
use crate::{
FfmpegBytes,
demuxer::{
DemuxError, ParametersAlloc, ParametersChannelMap, ParametersCopy, ParametersMissing,
ParametersOpaque, ParametersTooLarge,
},
extras::{ExtradataPolicy, SideDataEntry, measure_parameters},
};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Ratio {
num: i32,
den: i32,
}
impl Ratio {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(num: i32, den: i32) -> Self {
Self { num, den }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn num(&self) -> i32 {
self.num
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn den(&self) -> i32 {
self.den
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct DolbyVisionConfig {
profile: u8,
compatibility_id: u8,
}
impl DolbyVisionConfig {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(profile: u8, compatibility_id: u8) -> Self {
Self {
profile,
compatibility_id,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn profile(&self) -> u8 {
self.profile
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn compatibility_id(&self) -> u8 {
self.compatibility_id
}
}
const DOVI_CONFIG_PROFILE_OFFSET: usize = 2;
const DOVI_CONFIG_COMPATIBILITY_ID_OFFSET: usize = 7;
const DOVI_CONFIG_MIN_BYTES: usize = DOVI_CONFIG_COMPATIBILITY_ID_OFFSET + 1;
fn parse_dolby_vision_config(bytes: &[u8]) -> Option<DolbyVisionConfig> {
if bytes.len() < DOVI_CONFIG_MIN_BYTES {
return None;
}
Some(DolbyVisionConfig::new(
bytes[DOVI_CONFIG_PROFILE_OFFSET],
bytes[DOVI_CONFIG_COMPATIBILITY_ID_OFFSET],
))
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CustomChannel {
id: i32,
name: [u8; 16],
}
impl CustomChannel {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(id: i32, name: [u8; 16]) -> Self {
Self { id, name }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn id(&self) -> i32 {
self.id
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn name_bytes(&self) -> &[u8; 16] {
&self.name
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct ChannelLayoutTicket {
order: i32,
channels: i32,
mask: u64,
map: Vec<CustomChannel>,
}
impl ChannelLayoutTicket {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn order(&self) -> i32 {
self.order
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channels(&self) -> i32 {
self.channels
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mask(&self) -> u64 {
self.mask
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn map(&self) -> &[CustomChannel] {
self.map.as_slice()
}
}
#[derive(Clone)]
pub struct CodecTicket {
stream_index: usize,
codec_type: i32,
codec_id: i32,
codec_tag: u32,
extradata: FfmpegBytes,
coded_side_data: Vec<SideDataEntry>,
format: i32,
bit_rate: i64,
bits_per_coded_sample: i32,
bits_per_raw_sample: i32,
profile: i32,
level: i32,
width: i32,
height: i32,
sample_aspect_ratio: Ratio,
framerate: Ratio,
field_order: i32,
color_range: i32,
color_primaries: i32,
color_trc: i32,
color_space: i32,
chroma_location: i32,
video_delay: i32,
ch_layout: ChannelLayoutTicket,
sample_rate: i32,
block_align: i32,
frame_size: i32,
initial_padding: i32,
trailing_padding: i32,
seek_preroll: i32,
alpha_mode: i32,
footprint_bytes: usize,
}
impl CodecTicket {
pub fn mirror(
source: &Parameters,
stream_index: usize,
budget: usize,
) -> Result<Self, DemuxError> {
Self::mirror_with(source, stream_index, budget, ExtradataPolicy::Copy)
}
pub(crate) fn mirror_with(
source: &Parameters,
stream_index: usize,
budget: usize,
extradata_policy: ExtradataPolicy,
) -> Result<Self, DemuxError> {
let par = unsafe { source.as_ptr() };
if par.is_null() {
return Err(DemuxError::ParametersMissing(ParametersMissing::new(
stream_index,
)));
}
unsafe { Self::from_raw(par, stream_index, budget, extradata_policy) }
}
pub(crate) unsafe fn from_raw(
par: *const AVCodecParameters,
stream_index: usize,
budget: usize,
extradata_policy: ExtradataPolicy,
) -> Result<Self, DemuxError> {
let too_large = |bytes: usize| {
DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, bytes, budget))
};
unsafe { validate_channel_layout(par, stream_index) }?;
let footprint = unsafe { measure_parameters(par) }.ok_or_else(|| too_large(usize::MAX))?;
let footprint_bytes = match extradata_policy {
ExtradataPolicy::Copy => footprint.total(),
ExtradataPolicy::Omit => footprint.total_without_extradata(),
}
.ok_or_else(|| too_large(usize::MAX))?;
if footprint_bytes > budget {
return Err(too_large(footprint_bytes));
}
let ticket = unsafe {
Self {
stream_index,
codec_type: read_unaligned(addr_of!((*par).codec_type).cast::<i32>()),
codec_id: read_unaligned(addr_of!((*par).codec_id).cast::<i32>()),
codec_tag: (*par).codec_tag,
extradata: extradata_of(par, extradata_policy, stream_index)?,
coded_side_data: side_data_of(par, stream_index)?,
format: (*par).format,
bit_rate: (*par).bit_rate,
bits_per_coded_sample: (*par).bits_per_coded_sample,
bits_per_raw_sample: (*par).bits_per_raw_sample,
profile: (*par).profile,
level: (*par).level,
width: (*par).width,
height: (*par).height,
sample_aspect_ratio: Ratio::new(
(*par).sample_aspect_ratio.num,
(*par).sample_aspect_ratio.den,
),
framerate: Ratio::new((*par).framerate.num, (*par).framerate.den),
field_order: read_unaligned(addr_of!((*par).field_order).cast::<i32>()),
color_range: read_unaligned(addr_of!((*par).color_range).cast::<i32>()),
color_primaries: read_unaligned(addr_of!((*par).color_primaries).cast::<i32>()),
color_trc: read_unaligned(addr_of!((*par).color_trc).cast::<i32>()),
color_space: read_unaligned(addr_of!((*par).color_space).cast::<i32>()),
chroma_location: read_unaligned(addr_of!((*par).chroma_location).cast::<i32>()),
video_delay: (*par).video_delay,
ch_layout: channel_layout_of(par, stream_index)?,
sample_rate: (*par).sample_rate,
block_align: (*par).block_align,
frame_size: (*par).frame_size,
initial_padding: (*par).initial_padding,
trailing_padding: (*par).trailing_padding,
seek_preroll: (*par).seek_preroll,
alpha_mode: read_unaligned(addr_of!((*par).alpha_mode).cast::<i32>()),
footprint_bytes,
}
};
Ok(ticket)
}
pub fn rebuild(&self) -> Result<Parameters, DemuxError> {
let stream_index = self.stream_index;
let mut out = Parameters::new();
let dst = unsafe { out.as_mut_ptr() };
if dst.is_null() {
return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
stream_index,
)));
}
unsafe {
write_unaligned(
addr_of_mut!((*dst).codec_type).cast::<i32>(),
self.codec_type,
);
write_unaligned(addr_of_mut!((*dst).codec_id).cast::<i32>(), self.codec_id);
(*dst).codec_tag = self.codec_tag;
(*dst).format = self.format;
(*dst).bit_rate = self.bit_rate;
(*dst).bits_per_coded_sample = self.bits_per_coded_sample;
(*dst).bits_per_raw_sample = self.bits_per_raw_sample;
(*dst).profile = self.profile;
(*dst).level = self.level;
(*dst).width = self.width;
(*dst).height = self.height;
(*dst).sample_aspect_ratio.num = self.sample_aspect_ratio.num();
(*dst).sample_aspect_ratio.den = self.sample_aspect_ratio.den();
(*dst).framerate.num = self.framerate.num();
(*dst).framerate.den = self.framerate.den();
write_unaligned(
addr_of_mut!((*dst).field_order).cast::<i32>(),
self.field_order,
);
write_unaligned(
addr_of_mut!((*dst).color_range).cast::<i32>(),
self.color_range,
);
write_unaligned(
addr_of_mut!((*dst).color_primaries).cast::<i32>(),
self.color_primaries,
);
write_unaligned(addr_of_mut!((*dst).color_trc).cast::<i32>(), self.color_trc);
write_unaligned(
addr_of_mut!((*dst).color_space).cast::<i32>(),
self.color_space,
);
write_unaligned(
addr_of_mut!((*dst).chroma_location).cast::<i32>(),
self.chroma_location,
);
(*dst).video_delay = self.video_delay;
(*dst).sample_rate = self.sample_rate;
(*dst).block_align = self.block_align;
(*dst).frame_size = self.frame_size;
(*dst).initial_padding = self.initial_padding;
(*dst).trailing_padding = self.trailing_padding;
(*dst).seek_preroll = self.seek_preroll;
write_unaligned(
addr_of_mut!((*dst).alpha_mode).cast::<i32>(),
self.alpha_mode,
);
}
unsafe {
write_extradata(dst, self.extradata.as_slice(), stream_index)?;
write_side_data(dst, &self.coded_side_data, stream_index)?;
write_channel_layout(dst, &self.ch_layout, stream_index)?;
}
Ok(out)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn footprint_bytes(&self) -> usize {
self.footprint_bytes
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> usize {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec_type(&self) -> i32 {
self.codec_type
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec_id(&self) -> i32 {
self.codec_id
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn codec_tag(&self) -> u32 {
self.codec_tag
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn extradata(&self) -> &[u8] {
self.extradata.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extradata_ref(&self) -> &FfmpegBytes {
&self.extradata
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn coded_side_data(&self) -> &[SideDataEntry] {
self.coded_side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn dolby_vision_config(&self) -> Option<DolbyVisionConfig> {
let kind = AVPacketSideDataType::AV_PKT_DATA_DOVI_CONF as i32;
self
.coded_side_data
.iter()
.find(|entry| entry.kind() == kind)
.and_then(|entry| parse_dolby_vision_config(entry.data()))
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn format(&self) -> i32 {
self.format
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bit_rate(&self) -> i64 {
self.bit_rate
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bits_per_coded_sample(&self) -> i32 {
self.bits_per_coded_sample
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bits_per_raw_sample(&self) -> i32 {
self.bits_per_raw_sample
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn profile(&self) -> i32 {
self.profile
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn level(&self) -> i32 {
self.level
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> i32 {
self.width
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> i32 {
self.height
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn sample_aspect_ratio(&self) -> Ratio {
self.sample_aspect_ratio
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn framerate(&self) -> Ratio {
self.framerate
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn field_order(&self) -> i32 {
self.field_order
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn color_range(&self) -> i32 {
self.color_range
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn color_primaries(&self) -> i32 {
self.color_primaries
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn color_trc(&self) -> i32 {
self.color_trc
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn color_space(&self) -> i32 {
self.color_space
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn chroma_location(&self) -> i32 {
self.chroma_location
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn video_delay(&self) -> i32 {
self.video_delay
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn ch_layout(&self) -> &ChannelLayoutTicket {
&self.ch_layout
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn sample_rate(&self) -> i32 {
self.sample_rate
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn block_align(&self) -> i32 {
self.block_align
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn frame_size(&self) -> i32 {
self.frame_size
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn initial_padding(&self) -> i32 {
self.initial_padding
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn trailing_padding(&self) -> i32 {
self.trailing_padding
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn seek_preroll(&self) -> i32 {
self.seek_preroll
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn alpha_mode(&self) -> i32 {
self.alpha_mode
}
}
impl core::fmt::Debug for CodecTicket {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CodecTicket")
.field(
"medium",
&crate::boundary::media_kind_from_raw(self.codec_type),
)
.field("codec_id", &self.codec_id)
.field("codec_tag", &format_args!("{:#010x}", self.codec_tag))
.field("format", &self.format)
.field("width", &self.width)
.field("height", &self.height)
.field("sample_rate", &self.sample_rate)
.field("channels", &self.ch_layout.channels)
.field("extradata_len", &self.extradata.len())
.field("coded_side_data", &self.coded_side_data.len())
.field("footprint_bytes", &self.footprint_bytes)
.finish_non_exhaustive()
}
}
unsafe fn extradata_of(
par: *const AVCodecParameters,
policy: ExtradataPolicy,
stream_index: usize,
) -> Result<FfmpegBytes, DemuxError> {
if matches!(policy, ExtradataPolicy::Omit) {
return Ok(FfmpegBytes::empty());
}
let (ptr, size) = unsafe { ((*par).extradata, (*par).extradata_size) };
let Ok(len) = usize::try_from(size) else {
return Ok(FfmpegBytes::empty());
};
if ptr.is_null() || len == 0 {
return Ok(FfmpegBytes::empty());
}
FfmpegBytes::try_copy_from_slice(unsafe { core::slice::from_raw_parts(ptr, len) })
.ok_or_else(|| DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index)))
}
unsafe fn side_data_of(
par: *const AVCodecParameters,
stream_index: usize,
) -> Result<Vec<SideDataEntry>, DemuxError> {
let (array, count) = unsafe { ((*par).coded_side_data, (*par).nb_coded_side_data) };
let alloc = || DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index));
let Ok(count) = usize::try_from(count) else {
return Ok(Vec::new());
};
if array.is_null() || count == 0 {
return Ok(Vec::new());
}
let mut entries = Vec::new();
entries.try_reserve_exact(count).map_err(|_| alloc())?;
for index in 0..count {
let (kind, data, size) = unsafe {
let entry = array.add(index);
(
read_unaligned(addr_of!((*entry).type_).cast::<i32>()),
read_unaligned(addr_of!((*entry).data)),
read_unaligned(addr_of!((*entry).size)),
)
};
let payload = if data.is_null() || size == 0 {
FfmpegBytes::empty()
} else {
FfmpegBytes::try_copy_from_slice(unsafe { core::slice::from_raw_parts(data, size) })
.ok_or_else(alloc)?
};
entries.push(SideDataEntry::new(kind, payload));
}
Ok(entries)
}
pub(crate) unsafe fn validate_channel_layout(
par: *const AVCodecParameters,
stream_index: usize,
) -> Result<(), DemuxError> {
let (order, channels, opaque) = unsafe {
(
read_unaligned(addr_of!((*par).ch_layout.order).cast::<i32>()),
(*par).ch_layout.nb_channels,
(*par).ch_layout.opaque,
)
};
if !opaque.is_null() {
return Err(DemuxError::ParametersOpaque(ParametersOpaque::new(
stream_index,
None,
)));
}
unsafe { crate::channel_layout::layout_preflight(addr_of!((*par).ch_layout)) }
.map_err(|fault| crate::demuxer::layout_fault_to_demux(stream_index, fault))?;
if order != AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32 {
return Ok(());
}
let map = unsafe { (*par).ch_layout.u.map };
let count = channels.max(0) as usize;
for index in 0..count {
let opaque = unsafe { read_unaligned(addr_of!((*map.add(index)).opaque)) };
if !opaque.is_null() {
return Err(DemuxError::ParametersOpaque(ParametersOpaque::new(
stream_index,
Some(index),
)));
}
}
Ok(())
}
unsafe fn channel_layout_of(
par: *const AVCodecParameters,
stream_index: usize,
) -> Result<ChannelLayoutTicket, DemuxError> {
unsafe { validate_channel_layout(par, stream_index) }?;
let (order, channels) = unsafe {
(
read_unaligned(addr_of!((*par).ch_layout.order).cast::<i32>()),
(*par).ch_layout.nb_channels,
)
};
let mut layout = ChannelLayoutTicket {
order,
channels,
mask: 0,
map: Vec::new(),
};
match crate::channel_layout::LayoutArm::of(order) {
crate::channel_layout::LayoutArm::Mask => {
layout.mask = unsafe { (*par).ch_layout.u.mask };
return Ok(layout);
}
crate::channel_layout::LayoutArm::Undefined => return Ok(layout),
crate::channel_layout::LayoutArm::Map => {}
}
let map = unsafe { (*par).ch_layout.u.map };
let count = channels.max(0) as usize;
layout
.map
.try_reserve_exact(count)
.map_err(|_| DemuxError::ParametersAlloc(ParametersAlloc::new(stream_index)))?;
for index in 0..count {
let (id, name) = unsafe {
let entry = map.add(index);
(
read_unaligned(addr_of!((*entry).id).cast::<i32>()),
read_unaligned(addr_of!((*entry).name).cast::<[u8; 16]>()),
)
};
layout.map.push(CustomChannel::new(id, name));
}
Ok(layout)
}
fn seat_alloc_failed(stream_index: usize) -> DemuxError {
DemuxError::ParametersCopy(ParametersCopy::new(
stream_index,
ffmpeg_next::Error::Other {
errno: libc::ENOMEM,
},
))
}
unsafe fn write_extradata(
dst: *mut AVCodecParameters,
payload: &[u8],
stream_index: usize,
) -> Result<(), DemuxError> {
if payload.is_empty() {
return Ok(());
}
let size = i32::try_from(payload.len()).map_err(|_| seat_alloc_failed(stream_index))?;
let padded = payload
.len()
.checked_add(AV_INPUT_BUFFER_PADDING_SIZE as usize)
.ok_or_else(|| seat_alloc_failed(stream_index))?;
unsafe {
let buffer = av_mallocz(padded).cast::<u8>();
if buffer.is_null() {
return Err(seat_alloc_failed(stream_index));
}
copy_nonoverlapping(payload.as_ptr(), buffer, payload.len());
(*dst).extradata = buffer;
(*dst).extradata_size = size;
}
Ok(())
}
unsafe fn write_side_data(
dst: *mut AVCodecParameters,
entries: &[SideDataEntry],
stream_index: usize,
) -> Result<(), DemuxError> {
if entries.is_empty() {
return Ok(());
}
let count = i32::try_from(entries.len()).map_err(|_| seat_alloc_failed(stream_index))?;
let bytes = entries
.len()
.checked_mul(core::mem::size_of::<AVPacketSideData>())
.ok_or_else(|| seat_alloc_failed(stream_index))?;
unsafe {
let array = av_mallocz(bytes).cast::<AVPacketSideData>();
if array.is_null() {
return Err(seat_alloc_failed(stream_index));
}
(*dst).coded_side_data = array;
(*dst).nb_coded_side_data = count;
for (index, entry) in entries.iter().enumerate() {
let into = array.add(index);
write_unaligned(addr_of_mut!((*into).type_).cast::<i32>(), entry.kind());
let payload = entry.data();
if payload.is_empty() {
continue;
}
let buffer = av_mallocz(payload.len()).cast::<u8>();
if buffer.is_null() {
return Err(seat_alloc_failed(stream_index));
}
copy_nonoverlapping(payload.as_ptr(), buffer, payload.len());
write_unaligned(addr_of_mut!((*into).data), buffer);
write_unaligned(addr_of_mut!((*into).size), payload.len());
}
}
Ok(())
}
unsafe fn write_channel_layout(
dst: *mut AVCodecParameters,
layout: &ChannelLayoutTicket,
stream_index: usize,
) -> Result<(), DemuxError> {
unsafe {
write_unaligned(
addr_of_mut!((*dst).ch_layout.order).cast::<i32>(),
layout.order(),
);
(*dst).ch_layout.opaque = core::ptr::null_mut();
}
match crate::channel_layout::LayoutArm::of(layout.order()) {
crate::channel_layout::LayoutArm::Mask => {
unsafe {
(*dst).ch_layout.nb_channels = layout.channels();
(*dst).ch_layout.u.mask = layout.mask();
}
return Ok(());
}
crate::channel_layout::LayoutArm::Undefined => {
unsafe {
(*dst).ch_layout.nb_channels = layout.channels();
}
return Ok(());
}
crate::channel_layout::LayoutArm::Map => {}
}
debug_assert_eq!(
layout.map().len(),
layout.channels().max(0) as usize,
"a custom layout's map length is its channel count",
);
let count = layout.map().len();
if count == 0 {
return Err(DemuxError::ParametersChannelMap(ParametersChannelMap::new(
stream_index,
layout.channels(),
)));
}
let declared = i32::try_from(count).map_err(|_| seat_alloc_failed(stream_index))?;
let bytes = count
.checked_mul(core::mem::size_of::<AVChannelCustom>())
.ok_or_else(|| seat_alloc_failed(stream_index))?;
unsafe {
let map = av_mallocz(bytes).cast::<AVChannelCustom>();
if map.is_null() {
return Err(seat_alloc_failed(stream_index));
}
(*dst).ch_layout.u.map = map;
(*dst).ch_layout.nb_channels = declared;
for (index, channel) in layout.map().iter().enumerate() {
let into = map.add(index);
write_unaligned(addr_of_mut!((*into).id).cast::<i32>(), channel.id());
write_unaligned(
addr_of_mut!((*into).name).cast::<[u8; 16]>(),
*channel.name_bytes(),
);
write_unaligned(addr_of_mut!((*into).opaque), core::ptr::null_mut());
}
}
Ok(())
}
#[cfg(test)]
mod tests;