T6.1: AV1 encoder/decoder with HW probe + SVT-AV1 SW fallback
- New: av1_obu.rs — OBU framer, depacketizer, keyframe detection, LEB128 helpers - New: dav1d.rs — SW AV1 decoder wrapper (shiguredo_dav1d) - New: svt_av1.rs — SW AV1 encoder wrapper (shiguredo_svt_av1) - Add CodecId::Av1Main = 12 with match-arm fixes in downstream crates - Add VideoToolboxAv1Decoder for macOS M3+ HW decode - Add MediaCodecAv1Encoder/Decoder for Android (video/av01) - Add extract_sequence_header_obu() helper for AV1 decoder CSD - Add 10-frame encode-decode roundtrip test (svt_av1 + dav1d) - Fix clippy unused import in dav1d.rs - 15 tests; all workspace tests pass; cargo fmt clean
This commit is contained in:
@@ -652,6 +652,99 @@ impl VideoDecoder for VideoToolboxHevcDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
/// macOS VideoToolbox AV1 decoder (decode-only; M3+).
|
||||
pub struct VideoToolboxAv1Decoder {
|
||||
#[cfg(target_os = "macos")]
|
||||
inner: Option<Decoder>,
|
||||
#[cfg(target_os = "macos")]
|
||||
width: u32,
|
||||
#[cfg(target_os = "macos")]
|
||||
height: u32,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
_width: u32,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
_height: u32,
|
||||
}
|
||||
|
||||
impl VideoToolboxAv1Decoder {
|
||||
pub fn new(width: u32, height: u32) -> Result<Self, VideoError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let config = DecoderConfig {
|
||||
codec: DecoderCodec::Av1 { width, height },
|
||||
pixel_format: PixelFormat::I420,
|
||||
};
|
||||
match Decoder::new(config) {
|
||||
Ok(decoder) => Ok(Self {
|
||||
inner: Some(decoder),
|
||||
width,
|
||||
height,
|
||||
}),
|
||||
Err(shiguredo_video_toolbox::Error::UnsupportedCodec { .. }) => {
|
||||
// AV1 decode not supported on this platform (e.g. M1/M2).
|
||||
Ok(Self {
|
||||
inner: None,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
Err(e) => Err(VideoError::PlatformError(format!(
|
||||
"AV1 decoder create failed: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let _ = (width, height);
|
||||
Ok(Self {
|
||||
_width: width,
|
||||
_height: height,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoDecoder for VideoToolboxAv1Decoder {
|
||||
fn decode(&mut self, access_unit: &[u8]) -> Result<Option<VideoFrame>, VideoError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if access_unit.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let decoder = self.inner.as_mut().ok_or(VideoError::NotInitialized)?;
|
||||
let decoded = decoder
|
||||
.decode(access_unit)
|
||||
.map_err(|e| VideoError::PlatformError(format!("decode failed: {e}")))?;
|
||||
match decoded {
|
||||
Some(DecodedFrame::I420(frame)) => {
|
||||
let y = frame.y_plane();
|
||||
let u = frame.u_plane();
|
||||
let v = frame.v_plane();
|
||||
let mut data = Vec::with_capacity(y.len() + u.len() + v.len());
|
||||
data.extend_from_slice(y);
|
||||
data.extend_from_slice(u);
|
||||
data.extend_from_slice(v);
|
||||
Ok(Some(VideoFrame {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
data,
|
||||
timestamp_ms: 0,
|
||||
}))
|
||||
}
|
||||
Some(DecodedFrame::Nv12(_)) => Err(VideoError::PlatformError(
|
||||
"unexpected NV12 output from decoder".to_string(),
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
let _ = access_unit;
|
||||
Err(VideoError::NotInitialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for HEVC parameter-set triple returned by `extract_vps_sps_pps`.
|
||||
type HevcParameterSets = (Option<Vec<u8>>, Option<Vec<u8>>, Option<Vec<u8>>);
|
||||
|
||||
@@ -792,4 +885,12 @@ mod tests {
|
||||
assert_eq!(sps, Some(vec![0x42, 0x01, 0x01, 0x01]));
|
||||
assert_eq!(pps, Some(vec![0x44, 0x01, 0xC1, 0x72]));
|
||||
}
|
||||
|
||||
// ---- AV1 ----
|
||||
|
||||
#[test]
|
||||
fn av1_decoder_instantiates() {
|
||||
let dec = VideoToolboxAv1Decoder::new(1280, 720);
|
||||
assert!(dec.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user