//! Thin unsafe wrapper around the mainline FFmpeg `AV_CODEC_ID_XMA2` decoder. //! //! Unlike canary's vendored `XMAFRAMES` (one frame per packet, custom padding //! header), the distro xma2 decoder consumes whole 2 KB XMA2 packets //! (`block_align == 2048`), needs `extradata` declaring the channel/stream //! layout, and buffers samples internally across packets. We drive it with the //! guest's raw 2 KB packets and pull whatever 512-sample float-planar frames it //! emits, returning them as interleaved S16 big-endian PCM (canary `ConvertFrame`). use std::os::raw::c_int; use std::ptr; use ffmpeg_sys_next as ff; /// One xma2 decoder instance, configured for a fixed (sample_rate, channels). pub struct Xma2Codec { codec: *const ff::AVCodec, ctx: *mut ff::AVCodecContext, frame: *mut ff::AVFrame, packet: *mut ff::AVPacket, extradata: Vec, channels: u32, } // FFmpeg objects are not Send/Sync by default; the decoder is only ever touched // on the CPU scheduler thread (decode_pending), so this is sound for our use. unsafe impl Send for Xma2Codec {} impl Xma2Codec { /// Build XMA2WAVEFORMATEX extradata (34 bytes) for a single XMA2 stream. /// Layout (little-endian, per FFmpeg `xma_decode_init` / xma2defs.h): /// [0..2] NumStreams (u16) = 1 /// [2..6] ChannelMask (u32) = mono/stereo mask /// [6..34] remaining XMA2WAVEFORMATEX fields (unused by the decoder) fn build_extradata(channels: u32) -> Vec { let mut e = vec![0u8; 34]; // NumStreams = 1 e[0..2].copy_from_slice(&1u16.to_le_bytes()); // ChannelMask: 0x3 (FL|FR) for stereo, 0x4 (FC) for mono. let mask: u32 = if channels >= 2 { 0x3 } else { 0x4 }; e[2..6].copy_from_slice(&mask.to_le_bytes()); e } pub fn new(sample_rate: u32, channels: u32) -> Result { unsafe { let codec = ff::avcodec_find_decoder(ff::AVCodecID::AV_CODEC_ID_XMA2); if codec.is_null() { return Err("xma2 decoder not found in libavcodec".into()); } let ctx = ff::avcodec_alloc_context3(codec); if ctx.is_null() { return Err("avcodec_alloc_context3 failed".into()); } let mut extradata = Self::build_extradata(channels); // FFmpeg requires extradata to be allocated with av_malloc and // padded; copy our bytes into an av_malloc'd buffer. let pad = ff::AV_INPUT_BUFFER_PADDING_SIZE as usize; let raw = ff::av_mallocz(extradata.len() + pad) as *mut u8; if raw.is_null() { ff::avcodec_free_context(&mut (ctx as *mut _)); return Err("av_mallocz extradata failed".into()); } ptr::copy_nonoverlapping(extradata.as_ptr(), raw, extradata.len()); (*ctx).extradata = raw; (*ctx).extradata_size = extradata.len() as c_int; (*ctx).sample_rate = sample_rate as c_int; (*ctx).block_align = 2048; ff::av_channel_layout_default(&mut (*ctx).ch_layout, channels as c_int); let ret = ff::avcodec_open2(ctx, codec, ptr::null_mut()); if ret < 0 { let mut ctxm = ctx; ff::avcodec_free_context(&mut ctxm); return Err(format!("avcodec_open2 failed: {}", av_err(ret))); } let frame = ff::av_frame_alloc(); let packet = ff::av_packet_alloc(); if frame.is_null() || packet.is_null() { let mut ctxm = ctx; ff::avcodec_free_context(&mut ctxm); return Err("av_frame_alloc/av_packet_alloc failed".into()); } // keep our Vec alive as the source of truth for length extradata.shrink_to_fit(); Ok(Self { codec, ctx, frame, packet, extradata, channels, }) } } pub fn channels(&self) -> u32 { self.channels } /// Feed one raw 2 KB XMA2 packet (header + data) to the decoder. Returns the /// number of bytes the decoder accepted (0 = buffered, needs no new packet /// yet / EAGAIN). Decoded frames are pulled via [`receive_frame`]. pub fn send_packet(&mut self, packet: &[u8]) -> Result<(), String> { unsafe { // av_packet_from_data takes ownership of an av_malloc buffer; simpler // to point at our own bytes via a stack packet with a padded copy. let pad = ff::AV_INPUT_BUFFER_PADDING_SIZE as usize; let buf = ff::av_malloc(packet.len() + pad) as *mut u8; if buf.is_null() { return Err("av_malloc packet failed".into()); } ptr::copy_nonoverlapping(packet.as_ptr(), buf, packet.len()); ptr::write_bytes(buf.add(packet.len()), 0, pad); ff::av_packet_unref(self.packet); // Wrap buf so FFmpeg frees it. let ret = ff::av_packet_from_data(self.packet, buf, packet.len() as c_int); if ret < 0 { ff::av_free(buf as *mut _); return Err(format!("av_packet_from_data failed: {}", av_err(ret))); } let ret = ff::avcodec_send_packet(self.ctx, self.packet); if ret == ff::AVERROR(ff::EAGAIN) { // Decoder full — caller should drain frames first then retry. return Err("EAGAIN".into()); } if ret < 0 { return Err(format!("avcodec_send_packet failed: {}", av_err(ret))); } Ok(()) } } /// Signal end-of-stream so the decoder flushes its internal FIFO. pub fn send_eof(&mut self) { unsafe { let _ = ff::avcodec_send_packet(self.ctx, ptr::null()); } } /// Pull one decoded frame as interleaved S16 big-endian PCM, or None if the /// decoder needs more input (EAGAIN) or is drained (EOF). Returns /// (samples_per_channel, interleaved_s16be_bytes). pub fn receive_frame(&mut self) -> Option<(u32, Vec)> { unsafe { let ret = ff::avcodec_receive_frame(self.ctx, self.frame); if ret < 0 { return None; } let nb = (*self.frame).nb_samples as u32; if nb == 0 { return None; } let ch = (*self.frame).ch_layout.nb_channels.max(1) as u32; let out = convert_frame_planar_to_s16be(self.frame, ch, nb); Some((nb, out)) } } } impl Drop for Xma2Codec { fn drop(&mut self) { unsafe { if !self.frame.is_null() { ff::av_frame_free(&mut self.frame); } if !self.packet.is_null() { ff::av_packet_free(&mut self.packet); } if !self.ctx.is_null() { ff::avcodec_free_context(&mut self.ctx); } let _ = &self.codec; let _ = &self.extradata; } } } /// Convert FFmpeg planar-float output to interleaved S16 big-endian PCM /// (faithful to canary `XmaContext::ConvertFrame`: saturate to [-1,1], scale by /// 2^15-1, byte-swap each sample). `channels` planes of `nb_samples` floats. unsafe fn convert_frame_planar_to_s16be( frame: *mut ff::AVFrame, channels: u32, nb_samples: u32, ) -> Vec { const SCALE: f32 = ((1i32 << 15) - 1) as f32; let mut out = Vec::with_capacity((nb_samples * channels * 2) as usize); unsafe { // extended_data[ch] points to a plane of f32 (AV_SAMPLE_FMT_FLTP). let ext = (*frame).extended_data; for i in 0..nb_samples as isize { for ch in 0..channels as isize { let plane = *ext.offset(ch) as *const f32; let s = if plane.is_null() { 0.0 } else { *plane.offset(i) }; let clamped = s.clamp(-1.0, 1.0) * SCALE; let v = clamped as i16; out.extend_from_slice(&v.to_be_bytes()); } } } out } fn av_err(code: c_int) -> String { unsafe { let mut buf = [0i8; ff::AV_ERROR_MAX_STRING_SIZE as usize]; ff::av_strerror(code, buf.as_mut_ptr(), buf.len()); let cstr = std::ffi::CStr::from_ptr(buf.as_ptr()); cstr.to_string_lossy().into_owned() } }