Skip to main content

oxideav_h265/
decoder.rs

1//! Registry decoder — the [`oxideav_core::Decoder`] contract over the
2//! whole-bitstream [`crate::sequence`] driver.
3//!
4//! [`make_decoder`] is the direct factory endpoint (the crate's
5//! historical direct-API convention); [`crate::register`] wires the
6//! same factory into the [`oxideav_core`] codec registry under the
7//! `"h265"` / `"hevc"` ids and the common container tags.
8//!
9//! Packets carry either Annex B byte-stream chunks (start-code
10//! delimited NAL units) or, when `CodecParameters::extradata` is an
11//! `hvcC` / `HEVCDecoderConfigurationRecord` (ISO/IEC 14496-15
12//! §8.3.3.1), length-prefixed NAL runs as ISO-BMFF samples carry them.
13//! Extradata in either form is fed ahead of the first packet so
14//! out-of-band parameter sets activate. Output frames come in output
15//! (PicOrderCntVal) order, with packet PTS values re-attached in
16//! ascending order; an empty packet flushes the reorder queue.
17
18use std::collections::BinaryHeap;
19use std::collections::VecDeque;
20
21use oxideav_core::{
22    CodecId, CodecParameters, Decoder, Error, Frame, Packet, Result, VideoFrame, VideoPlane,
23};
24
25use crate::hvcc::{extradata_is_hvcc, parse_hvcc, split_length_prefixed};
26use crate::picture::{Picture, Plane};
27use crate::sequence::{DecodedFrame, SequenceDecoder};
28
29/// The default reorder depth when no SPS has been activated yet (the
30/// §7.4.3.2.1 `sps_max_num_reorder_pics` bound once one has).
31const DEFAULT_REORDER: usize = 8;
32
33/// H.265 / HEVC Annex B streaming decoder.
34pub struct H265Decoder {
35    codec_id: CodecId,
36    seq: SequenceDecoder,
37    /// Decoded pictures not yet emitted, sorted on demand by
38    /// `(cvs_index, poc)`.
39    reorder: Vec<DecodedFrame>,
40    /// Frames ready to hand out.
41    ready: VecDeque<Frame>,
42    /// Min-heap of packet PTS values, re-attached in output order.
43    pts_queue: BinaryHeap<std::cmp::Reverse<i64>>,
44    /// `Some(n)` when the extradata was an `hvcC` record: packets are
45    /// length-prefixed NAL runs with `n`-byte big-endian sizes
46    /// (ISO/IEC 14496-15 §8.3.3.1.3 `lengthSizeMinusOne + 1`).
47    /// `None` for Annex B packets.
48    nal_length_size: Option<usize>,
49    flushed: bool,
50}
51
52impl std::fmt::Debug for H265Decoder {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.debug_struct("H265Decoder")
55            .field("codec_id", &self.codec_id)
56            .field("reorder", &self.reorder.len())
57            .field("ready", &self.ready.len())
58            .finish()
59    }
60}
61
62/// Direct factory endpoint: construct the software H.265 decoder.
63///
64/// The `extradata` form selects the packet framing: an `hvcC`
65/// (`HEVCDecoderConfigurationRecord`) extradata activates its carried
66/// parameter sets and switches packets to length-prefixed NAL runs;
67/// Annex B extradata (or none) keeps start-code framing.
68///
69/// # Errors
70/// [`Error::InvalidData`] when the `extradata` fails to parse.
71pub fn make_decoder(params: &CodecParameters) -> Result<Box<dyn Decoder>> {
72    let mut seq = SequenceDecoder::new();
73    let mut nal_length_size = None;
74    if !params.extradata.is_empty() {
75        if extradata_is_hvcc(&params.extradata) {
76            // hvcC record: out-of-band VPS/SPS/PPS (+ SEI) arrays.
77            let rec = parse_hvcc(&params.extradata)
78                .map_err(|e| Error::InvalidData(format!("h265 hvcC extradata: {e}")))?;
79            for unit in rec.nal_units {
80                seq.push_nal_unit(unit)
81                    .map_err(|e| Error::InvalidData(format!("h265 hvcC extradata: {e}")))?;
82            }
83            nal_length_size = Some(rec.length_size);
84        } else {
85            // Out-of-band parameter sets in Annex B form.
86            seq.push_annexb(&params.extradata)
87                .map_err(|e| Error::InvalidData(format!("h265 extradata: {e}")))?;
88        }
89    }
90    Ok(Box::new(H265Decoder {
91        codec_id: params.codec_id.clone(),
92        seq,
93        reorder: Vec::new(),
94        ready: VecDeque::new(),
95        pts_queue: BinaryHeap::new(),
96        nal_length_size,
97        flushed: false,
98    }))
99}
100
101impl H265Decoder {
102    /// Move decoded pictures into the reorder buffer and emit every
103    /// frame that is guaranteed next in output order.
104    fn drain(&mut self, flush: bool) {
105        self.reorder.extend(self.seq.take_decoded());
106        self.reorder.sort_by_key(|f| (f.cvs_index, f.poc));
107        let depth = if flush {
108            0
109        } else {
110            self.seq
111                .max_num_reorder_pics()
112                .map_or(DEFAULT_REORDER, |n| n as usize)
113        };
114        while self.reorder.len() > depth {
115            let f = self.reorder.remove(0);
116            if !f.output {
117                continue;
118            }
119            let pts = self.pts_queue.pop().map(|r| r.0);
120            self.ready
121                .push_back(Frame::Video(video_frame(&f.picture, pts)));
122        }
123    }
124}
125
126impl Decoder for H265Decoder {
127    fn codec_id(&self) -> &CodecId {
128        &self.codec_id
129    }
130
131    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
132        if packet.data.is_empty() {
133            // An empty packet is treated as a flush signal too.
134            return self.flush();
135        }
136        if let Some(pts) = packet.pts {
137            self.pts_queue.push(std::cmp::Reverse(pts));
138        }
139        if let Some(length_size) = self.nal_length_size {
140            let units = split_length_prefixed(&packet.data, length_size)
141                .map_err(|e| Error::InvalidData(format!("h265 decode: {e}")))?;
142            for unit in units {
143                self.seq
144                    .push_nal_unit(unit)
145                    .map_err(|e| Error::InvalidData(format!("h265 decode: {e}")))?;
146            }
147        } else {
148            self.seq
149                .push_annexb(&packet.data)
150                .map_err(|e| Error::InvalidData(format!("h265 decode: {e}")))?;
151        }
152        self.drain(false);
153        Ok(())
154    }
155
156    fn receive_frame(&mut self) -> Result<Frame> {
157        if let Some(f) = self.ready.pop_front() {
158            return Ok(f);
159        }
160        if self.flushed {
161            if !self.reorder.is_empty() {
162                self.drain(true);
163                if let Some(f) = self.ready.pop_front() {
164                    return Ok(f);
165                }
166            }
167            return Err(Error::Eof);
168        }
169        Err(Error::NeedMore)
170    }
171
172    fn flush(&mut self) -> Result<()> {
173        // Decode any pending picture and release the reorder queue.
174        self.seq
175            .flush()
176            .map_err(|e| Error::InvalidData(format!("h265 flush: {e}")))?;
177        self.flushed = true;
178        self.drain(true);
179        Ok(())
180    }
181}
182
183/// Pack a reconstructed [`Picture`] into a [`VideoFrame`] (8-bit planes
184/// as one byte per sample; higher bit depths as little-endian 16-bit,
185/// the planar `p010le`-family layout).
186fn video_frame(pic: &Picture, pts: Option<i64>) -> VideoFrame {
187    let planes = if pic.chroma_array_type() == 0 {
188        vec![Plane::Luma]
189    } else {
190        vec![Plane::Luma, Plane::Cb, Plane::Cr]
191    };
192    let wide = pic.bit_depth(Plane::Luma) > 8
193        || (pic.chroma_array_type() != 0 && pic.bit_depth(Plane::Cb) > 8);
194    let mut out = Vec::with_capacity(planes.len());
195    for plane in planes {
196        let (w, h) = pic.plane_dims(plane);
197        let buf = pic.plane(plane);
198        let mut data;
199        let stride;
200        if wide {
201            stride = w * 2;
202            data = Vec::with_capacity(w * h * 2);
203            for &v in buf.iter().take(w * h) {
204                data.extend_from_slice(&(v as u16).to_le_bytes());
205            }
206        } else {
207            stride = w;
208            data = Vec::with_capacity(w * h);
209            data.extend(buf.iter().take(w * h).map(|&v| v as u8));
210        }
211        out.push(VideoPlane { stride, data });
212    }
213    VideoFrame { pts, planes: out }
214}