Skip to main content

zvidlib_hevc_decoder/
lib.rs

1//! Native HEVC/H.265 decoding with platform acceleration and a dependency-free software fallback.
2//!
3//! The software decoder is portable and also builds for `wasm32`, where it is the browser's
4//! fallback when WebCodecs cannot decode a track (issue #504); the platform backends and the
5//! encoder's public factory stay native-only.
6
7// internal — exposed for the criterion benchmark suite; not part of the stable API
8#[cfg(not(target_arch = "wasm32"))]
9pub mod decode_bench;
10// internal — exposed for the stage-attribution example; not part of the stable API
11#[cfg(not(target_arch = "wasm32"))]
12#[doc(hidden)]
13pub use engine::inter_pred::narrow_interp;
14// internal — exposed for the stage-attribution example; not part of the stable API
15#[cfg(not(target_arch = "wasm32"))]
16#[doc(hidden)]
17pub use engine::profile as decode_profile;
18// internal — the HEVC engine, shared with the encoder crate; not part of the stable API
19#[doc(hidden)]
20pub use zvidlib_hevc::engine;
21
22#[allow(unused_imports)]
23use zvidlib_color::color_convert;
24#[allow(unused_imports)]
25use zvidlib_core::*;
26// The platform backends the factory selects between.
27#[cfg(all(
28    feature = "hardware",
29    any(windows, target_os = "linux"),
30    target_pointer_width = "64"
31))]
32use zvidlib_hardware::nvdec;
33// internal — the hardware backends' readback seam, re-exported for the
34// benchmark suite; not part of the stable API
35#[cfg(all(feature = "hardware", not(target_arch = "wasm32")))]
36#[allow(unused_imports)]
37#[doc(hidden)]
38pub use zvidlib_hardware::readback;
39#[cfg(all(feature = "hardware", target_os = "macos"))]
40use zvidlib_hardware::videotoolbox;
41#[cfg(all(feature = "hardware", windows))]
42use zvidlib_hardware::windows_mf;
43// The containers and conformance harness the tests read their fixtures with.
44#[cfg(test)]
45#[allow(unused_imports)]
46use zvidlib_container::*;
47
48use std::cmp::Reverse;
49use std::collections::BinaryHeap;
50use std::panic::{AssertUnwindSafe, catch_unwind};
51
52use crate::{
53    CancellationToken, Codec, CodecImplementation, CodecProfile, CodecSupport, ColorRange,
54    DecodedVideoFrame, EncodedVideoSample, Error, ErrorKind, FrameIndex, HardwarePreference,
55    Limits, PixelFormat, Plane, Result, VideoDecoder, VideoDecoderConfig, VideoDecoderFactory,
56    VideoFrame,
57};
58use engine::hvcc::{HvccRecord, parse_hvcc, split_length_prefixed};
59use engine::picture::{Picture, Plane as HevcPlane, sub_wh_c};
60use engine::sequence::{DecodedFrame, SequenceDecoder, SequenceError};
61use engine::sps::SeqParameterSet;
62
63const DEFAULT_REORDER_DEPTH: usize = 8;
64
65/// Returns the native HEVC Main and Main 10 decoder backend.
66///
67/// For 8-bit Main, `Prefer` and `Require` select NVIDIA NVDEC on supported 64-bit Windows/Linux
68/// hosts, D3D11-aware Media Foundation on Windows, or VideoToolbox on macOS. `Prefer` falls back to
69/// the dependency-free software decoder when acceleration is unavailable; `Avoid` always selects
70/// software. Main 10 tracks with more than 8 bits per sample are always decoded in software, since
71/// none of the accelerated backends is wired for them, so `Require` reports them unavailable.
72pub fn native_hevc_video_decoder_factory() -> impl VideoDecoderFactory {
73    HevcDecoderFactory
74}
75
76#[derive(Clone, Copy, Debug)]
77struct HevcDecoderFactory;
78
79impl VideoDecoderFactory for HevcDecoderFactory {
80    fn capability(&self, configuration: &VideoDecoderConfig) -> CodecSupport {
81        if let unsupported @ (CodecSupport::UnsupportedCodec
82        | CodecSupport::UnsupportedProfile
83        | CodecSupport::InvalidConfiguration { .. }) =
84            self.capability_without_hardware(configuration)
85        {
86            return unsupported;
87        }
88        let parsed = match ParsedConfiguration::parse(configuration, &Limits::default()) {
89            Ok(parsed) => parsed,
90            Err(error) => return invalid_configuration(error.message()),
91        };
92        if configuration.hardware != HardwarePreference::Avoid
93            && !parsed.high_bit_depth
94            && hardware_available(configuration)
95        {
96            return CodecSupport::Supported {
97                implementation: CodecImplementation::Hardware,
98            };
99        }
100        if configuration.hardware == HardwarePreference::Require {
101            return CodecSupport::HardwareUnavailable;
102        }
103        CodecSupport::Supported {
104            implementation: CodecImplementation::Software,
105        }
106    }
107
108    fn create(
109        &self,
110        configuration: &VideoDecoderConfig,
111        limits: &Limits,
112    ) -> Result<Box<dyn VideoDecoder>> {
113        match self.capability_without_hardware(configuration) {
114            CodecSupport::Supported { .. } => {}
115            CodecSupport::UnsupportedCodec => {
116                return Err(Error::new(
117                    ErrorKind::Unsupported,
118                    "native HEVC decoder requires HEVC",
119                ));
120            }
121            CodecSupport::UnsupportedProfile => {
122                return Err(Error::new(
123                    ErrorKind::Unsupported,
124                    "native HEVC decoder supports the Main and Main 10 profiles",
125                ));
126            }
127            CodecSupport::HardwareUnavailable => unreachable!("hardware is not checked here"),
128            CodecSupport::InvalidConfiguration { reason } => {
129                return Err(Error::new(ErrorKind::InvalidInput, reason));
130            }
131            _ => {
132                return Err(Error::new(
133                    ErrorKind::Unsupported,
134                    "native HEVC decoder does not support this configuration",
135                ));
136            }
137        }
138        let parsed = ParsedConfiguration::parse(configuration, limits)?;
139        if configuration.hardware == HardwarePreference::Require && parsed.high_bit_depth {
140            // Every accelerated backend below is 8-bit Main only, and would only notice a 10-bit
141            // sequence once decoding had started.
142            return Err(Error::new(
143                ErrorKind::Unsupported,
144                "hardware HEVC decoding is unavailable (no accelerated backend decodes more than 8 bits per sample)",
145            ));
146        }
147        if configuration.hardware != HardwarePreference::Avoid && !parsed.high_bit_depth {
148            #[cfg_attr(
149                any(target_arch = "wasm32", not(feature = "hardware")),
150                allow(unused_mut)
151            )]
152            let mut hardware_errors = Vec::<String>::new();
153            #[cfg(all(
154                feature = "hardware",
155                any(windows, target_os = "linux"),
156                target_pointer_width = "64"
157            ))]
158            match nvdec::create(configuration, limits, &parsed.record) {
159                Ok(decoder) => return Ok(decoder),
160                Err(error) => hardware_errors.push(format!("NVDEC: {}", error.message())),
161            }
162            #[cfg(all(feature = "hardware", windows))]
163            match windows_mf::create(configuration, limits, &parsed.record) {
164                Ok(decoder) => return Ok(decoder),
165                Err(error) => {
166                    hardware_errors.push(format!("Media Foundation: {}", error.message()));
167                }
168            }
169            #[cfg(all(feature = "hardware", target_os = "macos"))]
170            match videotoolbox::create(configuration, limits, &parsed.record) {
171                Ok(decoder) => return Ok(decoder),
172                Err(error) => {
173                    hardware_errors.push(format!("VideoToolbox: {}", error.message()));
174                }
175            }
176            if configuration.hardware == HardwarePreference::Require {
177                let detail = if hardware_errors.is_empty() {
178                    "no accelerated backend exists for this target".to_owned()
179                } else {
180                    hardware_errors.join("; ")
181                };
182                return Err(Error::new(
183                    ErrorKind::Unsupported,
184                    format!("hardware HEVC decoding is unavailable ({detail})"),
185                ));
186            }
187        }
188        Ok(Box::new(HevcDecoder::new(
189            configuration.clone(),
190            *limits,
191            parsed,
192        )?))
193    }
194}
195
196impl HevcDecoderFactory {
197    fn capability_without_hardware(&self, configuration: &VideoDecoderConfig) -> CodecSupport {
198        if configuration.codec != Codec::Hevc {
199            return CodecSupport::UnsupportedCodec;
200        }
201        if !matches!(
202            configuration.profile,
203            CodecProfile::HevcMain | CodecProfile::HevcMain10
204        ) {
205            return CodecSupport::UnsupportedProfile;
206        }
207        if configuration.output_format != PixelFormat::Rgba8 {
208            return invalid_configuration("native HEVC decoding currently outputs RGBA8");
209        }
210        if configuration.color_range != ColorRange::Limited {
211            return invalid_configuration("native HEVC RGBA output requires limited-range input");
212        }
213        CodecSupport::Supported {
214            implementation: CodecImplementation::Software,
215        }
216    }
217}
218
219fn hardware_available(_configuration: &VideoDecoderConfig) -> bool {
220    #[cfg(all(
221        feature = "hardware",
222        any(windows, target_os = "linux"),
223        target_pointer_width = "64"
224    ))]
225    if nvdec::is_available(_configuration.coded_dimensions) {
226        return true;
227    }
228    #[cfg(all(feature = "hardware", windows))]
229    if windows_mf::is_available(_configuration.coded_dimensions) {
230        return true;
231    }
232    #[cfg(all(feature = "hardware", target_os = "macos"))]
233    if videotoolbox::is_available(_configuration.coded_dimensions) {
234        return true;
235    }
236    false
237}
238
239fn invalid_configuration(reason: impl Into<String>) -> CodecSupport {
240    CodecSupport::InvalidConfiguration {
241        reason: reason.into(),
242    }
243}
244
245#[derive(Debug)]
246struct ParsedConfiguration {
247    record: HvccRecord,
248    /// Whether either component carries more than 8 bits per sample, which only the software
249    /// decoder handles.
250    high_bit_depth: bool,
251}
252
253impl ParsedConfiguration {
254    fn parse(configuration: &VideoDecoderConfig, limits: &Limits) -> Result<Self> {
255        if configuration.configuration.len() as u64 > limits.max_allocation_bytes {
256            return Err(limit("HEVC configuration exceeds the allocation limit"));
257        }
258        let payload = hvcc_payload(&configuration.configuration)?;
259        let record = parse_hvcc(payload)
260            .map_err(|error| malformed(format!("invalid hvcC configuration: {error}")))?;
261        let max_bit_depth_minus8 = max_bit_depth_minus8(configuration.profile);
262        let profile_idc_allowed = match configuration.profile {
263            // A Main 10 decoder also decodes Main.
264            CodecProfile::HevcMain10 => matches!(record.general_profile_idc, 1 | 2),
265            _ => record.general_profile_idc == 1,
266        };
267        if !profile_idc_allowed
268            || record.bit_depth_luma_minus8 > max_bit_depth_minus8
269            || record.bit_depth_chroma_minus8 > max_bit_depth_minus8
270            || record.chroma_format_idc != 1
271        {
272            return Err(Error::new(
273                ErrorKind::Unsupported,
274                match configuration.profile {
275                    CodecProfile::HevcMain10 => {
276                        "native HEVC decoder requires an 8- to 10-bit 4:2:0 Main or Main 10 hvcC configuration"
277                    }
278                    _ => {
279                        "native HEVC decoder requires an 8-bit 4:2:0 Main-profile hvcC configuration"
280                    }
281                },
282            ));
283        }
284        let sps_unit = record
285            .nal_units
286            .iter()
287            .find(|unit| unit.header.nal_unit_type == 33)
288            .ok_or_else(|| malformed("hvcC configuration does not contain an SPS"))?;
289        let sps = SeqParameterSet::parse(&sps_unit.rbsp)
290            .map_err(|error| malformed(format!("invalid HEVC SPS: {error}")))?;
291        validate_sps(&sps, configuration, limits)?;
292        let high_bit_depth = record.bit_depth_luma_minus8 != 0
293            || record.bit_depth_chroma_minus8 != 0
294            || sps.bit_depth_luma_minus8 != 0
295            || sps.bit_depth_chroma_minus8 != 0;
296        Ok(Self {
297            record,
298            high_bit_depth,
299        })
300    }
301}
302
303/// The deepest samples `profile` allows, as `bit_depth_*_minus8`: 8 bits for Main, 10 for
304/// Main 10.
305fn max_bit_depth_minus8(profile: CodecProfile) -> u8 {
306    match profile {
307        CodecProfile::HevcMain10 => 2,
308        _ => 0,
309    }
310}
311
312fn hvcc_payload(configuration: &[u8]) -> Result<&[u8]> {
313    if configuration.len() >= 8 && &configuration[4..8] == b"hvcC" {
314        let declared = u32::from_be_bytes(configuration[..4].try_into().expect("four bytes"));
315        if usize::try_from(declared).ok() != Some(configuration.len()) {
316            return Err(malformed(
317                "hvcC box size does not match its configuration bytes",
318            ));
319        }
320        return Ok(&configuration[8..]);
321    }
322    if configuration.first() == Some(&1) {
323        return Ok(configuration);
324    }
325    Err(malformed("HEVC configuration is not an hvcC record"))
326}
327
328fn validate_sps(
329    sps: &SeqParameterSet,
330    configuration: &VideoDecoderConfig,
331    limits: &Limits,
332) -> Result<()> {
333    let max_bit_depth_minus8 = max_bit_depth_minus8(configuration.profile);
334    if sps.chroma_format_idc != 1
335        || sps.separate_color_plane_flag
336        || sps.bit_depth_luma_minus8 > max_bit_depth_minus8
337        || sps.bit_depth_chroma_minus8 > max_bit_depth_minus8
338    {
339        return Err(Error::new(
340            ErrorKind::Unsupported,
341            match configuration.profile {
342                CodecProfile::HevcMain10 => {
343                    "native HEVC decoder requires SPS 8- to 10-bit 4:2:0 syntax"
344                }
345                _ => "native HEVC decoder requires SPS Main-profile 8-bit 4:2:0 syntax",
346            },
347        ));
348    }
349    if sps.pic_width_in_luma_samples > limits.max_width
350        || sps.pic_height_in_luma_samples > limits.max_height
351    {
352        return Err(limit("HEVC SPS coded dimensions exceed configured limits"));
353    }
354    let (sub_width, sub_height) = sub_wh_c(sps.chroma_format_idc);
355    let cropped_width = usize::try_from(sps.pic_width_in_luma_samples)
356        .ok()
357        .and_then(|width| {
358            let crop = usize::try_from(
359                sps.conformance_window.left_offset + sps.conformance_window.right_offset,
360            )
361            .ok()?
362            .checked_mul(sub_width)?;
363            width.checked_sub(crop)
364        })
365        .ok_or_else(|| malformed("HEVC SPS horizontal conformance window is invalid"))?;
366    let cropped_height = usize::try_from(sps.pic_height_in_luma_samples)
367        .ok()
368        .and_then(|height| {
369            let crop = usize::try_from(
370                sps.conformance_window.top_offset + sps.conformance_window.bottom_offset,
371            )
372            .ok()?
373            .checked_mul(sub_height)?;
374            height.checked_sub(crop)
375        })
376        .ok_or_else(|| malformed("HEVC SPS vertical conformance window is invalid"))?;
377    if cropped_width != configuration.coded_dimensions.width as usize
378        || cropped_height != configuration.coded_dimensions.height as usize
379    {
380        return Err(malformed(
381            "HEVC SPS display dimensions do not match decoder configuration",
382        ));
383    }
384    let pixels = u64::from(sps.pic_width_in_luma_samples)
385        .checked_mul(u64::from(sps.pic_height_in_luma_samples))
386        .ok_or_else(|| limit("HEVC picture dimensions overflow"))?;
387    let picture_bytes = pixels
388        .checked_mul(6)
389        .ok_or_else(|| limit("HEVC picture allocation overflows"))?;
390    let output_bytes = u64::from(configuration.coded_dimensions.width)
391        .checked_mul(u64::from(configuration.coded_dimensions.height))
392        .and_then(|value| value.checked_mul(4))
393        .ok_or_else(|| limit("HEVC RGBA output allocation overflows"))?;
394    let ordering = sps.sub_layer_ordering_info[usize::from(sps.max_sub_layers_minus1)];
395    let retained_pictures = u64::from(ordering.max_dec_pic_buffering_minus1)
396        .checked_add(2)
397        .ok_or_else(|| limit("HEVC decoded-picture-buffer size overflows"))?;
398    let decoder_bytes = picture_bytes
399        .checked_mul(retained_pictures)
400        .and_then(|value| value.checked_add(output_bytes))
401        .ok_or_else(|| limit("HEVC decoder allocation budget overflows"))?;
402    if decoder_bytes > limits.max_allocation_bytes {
403        return Err(limit(
404            "HEVC decoded-picture buffer exceeds the allocation limit",
405        ));
406    }
407    Ok(())
408}
409
410#[derive(Debug)]
411struct HevcDecoder {
412    configuration: VideoDecoderConfig,
413    limits: Limits,
414    configuration_nals: Vec<engine::nal::NalUnit>,
415    nal_length_size: usize,
416    sequence: SequenceDecoder,
417    reorder: Vec<DecodedFrame>,
418    presentation_indexes: BinaryHeap<Reverse<FrameIndex>>,
419    /// Whether a caller wants the pictures being decoded, or is only walking past them.
420    output_wanted: bool,
421}
422
423impl HevcDecoder {
424    fn new(
425        configuration: VideoDecoderConfig,
426        limits: Limits,
427        parsed: ParsedConfiguration,
428    ) -> Result<Self> {
429        let configuration_nals = parsed.record.nal_units;
430        let nal_length_size = parsed.record.length_size;
431        let mut decoder = Self {
432            configuration,
433            limits,
434            configuration_nals,
435            nal_length_size,
436            sequence: SequenceDecoder::new(),
437            reorder: Vec::new(),
438            presentation_indexes: BinaryHeap::new(),
439            output_wanted: true,
440        };
441        decoder.reinitialize()?;
442        Ok(decoder)
443    }
444
445    fn reinitialize(&mut self) -> Result<()> {
446        self.sequence = SequenceDecoder::new();
447        for unit in self.configuration_nals.clone() {
448            self.sequence.push_nal_unit(unit).map_err(sequence_error)?;
449        }
450        self.reorder.clear();
451        self.presentation_indexes.clear();
452        Ok(())
453    }
454
455    fn collect(&mut self, flush: bool) -> Result<Vec<DecodedVideoFrame>> {
456        let pictures = self.collect_pictures(flush)?;
457        if !self.output_wanted {
458            // The pictures are still taken from the reorder queue above, which is what keeps the
459            // decoder's own bookkeeping straight; only their conversion is skipped.
460            return Ok(Vec::new());
461        }
462        let mut output = Vec::with_capacity(pictures.len());
463        for (presentation_index, picture) in pictures {
464            output.push(DecodedVideoFrame {
465                presentation_index,
466                frame: picture_to_rgba(&picture, &self.configuration, &self.limits)?,
467            });
468        }
469        Ok(output)
470    }
471
472    /// The output pictures ready at this point, in presentation order, before
473    /// color conversion.
474    ///
475    /// Issue #220: this is the decoder's own product. [`collect`] converts each
476    /// one to RGBA, which is the round trip an application pays but is not
477    /// decoding, and `hevc_decoder_bench` times the two intervals separately so
478    /// a whole-frame SIMD ratio is not diluted by a stage no HEVC kernel
479    /// touches.
480    ///
481    /// [`collect`]: Self::collect
482    fn collect_pictures(&mut self, flush: bool) -> Result<Vec<(FrameIndex, Picture)>> {
483        self.reorder.extend(self.sequence.take_decoded());
484        self.reorder
485            .sort_by_key(|frame| (frame.cvs_index, frame.poc));
486        let depth = if flush {
487            0
488        } else {
489            self.sequence
490                .max_num_reorder_pics()
491                .map_or(DEFAULT_REORDER_DEPTH, |depth| depth as usize)
492        };
493        let mut output = Vec::new();
494        while self.reorder.len() > depth {
495            let decoded = self.reorder.remove(0);
496            if !decoded.output {
497                continue;
498            }
499            let presentation_index = self
500                .presentation_indexes
501                .pop()
502                .ok_or_else(|| {
503                    malformed("HEVC decoder produced a picture without a submitted identity")
504                })?
505                .0;
506            output.push((presentation_index, decoded.picture));
507        }
508        Ok(output)
509    }
510
511    /// Parses one access unit into the sequence decoder, without collecting
512    /// anything it made ready.
513    ///
514    /// The push and the collect are separate so [`VideoDecoder::submit`] and
515    /// the benchmark surface's picture-only path share exactly the same
516    /// bitstream handling and differ only in what they do with the result.
517    fn push_sample(
518        &mut self,
519        sample: &EncodedVideoSample,
520        cancellation: &CancellationToken,
521    ) -> Result<()> {
522        check_canceled(cancellation)?;
523        if sample.data.len() as u64 > self.limits.max_allocation_bytes {
524            return Err(limit("HEVC access unit exceeds the allocation limit"));
525        }
526        let units = split_length_prefixed(&sample.data, self.nal_length_size)
527            .map_err(|error| malformed(format!("invalid HEVC access unit: {error}")))?;
528        self.presentation_indexes
529            .push(Reverse(sample.presentation_index));
530        let skipped_before = self.sequence.skipped_pictures();
531        let result = catch_unwind(AssertUnwindSafe(|| {
532            for unit in units {
533                self.sequence.push_nal_unit(unit)?;
534            }
535            Ok::<(), SequenceError>(())
536        }));
537        match result {
538            Ok(Ok(())) => {
539                // A leading picture the decoder discarded because decoding
540                // started at its IRAP will never be output, so its identity
541                // must not be handed to the next picture that is.
542                if self.sequence.skipped_pictures() != skipped_before {
543                    self.presentation_indexes
544                        .retain(|Reverse(index)| *index != sample.presentation_index);
545                }
546                Ok(())
547            }
548            Ok(Err(error)) => Err(sequence_error(error)),
549            Err(_) => Err(malformed(
550                "HEVC bitstream triggered an invalid decoder state",
551            )),
552        }
553    }
554
555    /// Decodes one access unit and stops at the decoded pictures.
556    ///
557    /// The picture-only half of the issue #220 split, used by
558    /// [`crate::hevc_decoder_bench`].
559    #[cfg(not(target_arch = "wasm32"))]
560    fn submit_pictures(
561        &mut self,
562        sample: &EncodedVideoSample,
563        cancellation: &CancellationToken,
564    ) -> Result<Vec<Picture>> {
565        self.push_sample(sample, cancellation)?;
566        Ok(self
567            .collect_pictures(false)?
568            .into_iter()
569            .map(|(_, picture)| picture)
570            .collect())
571    }
572}
573
574impl VideoDecoder for HevcDecoder {
575    fn submit(
576        &mut self,
577        sample: &EncodedVideoSample,
578        cancellation: &CancellationToken,
579    ) -> Result<Vec<DecodedVideoFrame>> {
580        self.push_sample(sample, cancellation)?;
581        self.collect(false)
582    }
583
584    fn drain(&mut self, cancellation: &CancellationToken) -> Result<Vec<DecodedVideoFrame>> {
585        check_canceled(cancellation)?;
586        match catch_unwind(AssertUnwindSafe(|| self.sequence.flush())) {
587            Ok(Ok(())) => self.collect(true),
588            Ok(Err(error)) => Err(sequence_error(error)),
589            Err(_) => Err(malformed(
590                "HEVC bitstream triggered an invalid decoder state while draining",
591            )),
592        }
593    }
594
595    fn reset(&mut self) -> Result<()> {
596        self.reinitialize()
597    }
598
599    fn set_output_wanted(&mut self, wanted: bool) {
600        self.output_wanted = wanted;
601    }
602}
603
604/// Issue #189 stage attribution: color conversion is not decoding, but it is
605/// on the path every whole-frame measurement takes, so it is reported as its
606/// own stage rather than left in the unattributed remainder.
607fn picture_to_rgba(
608    picture: &Picture,
609    configuration: &VideoDecoderConfig,
610    limits: &Limits,
611) -> Result<VideoFrame> {
612    let _profile = engine::profile::scope(engine::profile::Stage::ColorConvert);
613    let max_bit_depth = 8 + max_bit_depth_minus8(configuration.profile);
614    if picture.chroma_array_type() != 1
615        || !(8..=max_bit_depth).contains(&picture.bit_depth_luma())
616        || !(8..=max_bit_depth).contains(&picture.bit_depth_chroma())
617    {
618        return Err(Error::new(
619            ErrorKind::Unsupported,
620            "decoded HEVC picture is not 4:2:0 at a bit depth its profile allows",
621        ));
622    }
623    let width = configuration.coded_dimensions.width as usize;
624    let height = configuration.coded_dimensions.height as usize;
625    if picture.width_luma() < width || picture.height_luma() < height {
626        return Err(malformed(
627            "decoded HEVC picture is smaller than its display dimensions",
628        ));
629    }
630    let stride = width
631        .checked_mul(4)
632        .ok_or_else(|| limit("HEVC RGBA stride overflows"))?;
633    let length = stride
634        .checked_mul(height)
635        .ok_or_else(|| limit("HEVC RGBA allocation overflows"))?;
636    if length as u64 > limits.max_allocation_bytes {
637        return Err(limit("HEVC RGBA output exceeds the allocation limit"));
638    }
639    let luma = picture.plane(HevcPlane::Luma);
640    let cb = picture.plane(HevcPlane::Cb);
641    let cr = picture.plane(HevcPlane::Cr);
642    let chroma_width = picture.plane_dims(HevcPlane::Cb).0;
643    let mut rgba = vec![0_u8; length];
644    if picture.bit_depth_luma() == 8 && picture.bit_depth_chroma() == 8 {
645        color_convert::convert_yuv420_to_rgba(
646            luma,
647            picture.width_luma(),
648            cb,
649            cr,
650            chroma_width,
651            width,
652            height,
653            &mut rgba,
654            stride,
655        );
656    } else {
657        color_convert::convert_high_bit_depth_yuv420_to_rgba(
658            luma,
659            picture.width_luma(),
660            picture.bit_depth_luma(),
661            cb,
662            cr,
663            chroma_width,
664            picture.bit_depth_chroma(),
665            width,
666            height,
667            &mut rgba,
668            stride,
669        );
670    }
671    VideoFrame::new(
672        configuration.coded_dimensions,
673        PixelFormat::Rgba8,
674        configuration.color_range,
675        vec![Plane { data: rgba, stride }],
676        limits,
677    )
678}
679
680fn check_canceled(cancellation: &CancellationToken) -> Result<()> {
681    if cancellation.is_canceled() {
682        Err(Error::new(ErrorKind::Canceled, "codec operation canceled"))
683    } else {
684        Ok(())
685    }
686}
687
688fn sequence_error(error: SequenceError) -> Error {
689    match error {
690        SequenceError::Unsupported(message) => Error::new(ErrorKind::Unsupported, message),
691        other => malformed(format!("invalid HEVC bitstream: {other}")),
692    }
693}
694
695fn malformed(message: impl Into<String>) -> Error {
696    Error::new(ErrorKind::MalformedMedia, message)
697}
698
699fn limit(message: impl Into<String>) -> Error {
700    Error::new(ErrorKind::ResourceLimit, message)
701}
702
703/// The SIMD dispatch sites in this crate, each with the instruction set it
704/// resolves to right now. `zvidlib::simd::active_by_site` reports every crate's
705/// sites together and documents what each one covers.
706#[doc(hidden)]
707#[must_use]
708pub fn simd_sites() -> Vec<(&'static str, SimdIsa)> {
709    // The HEVC kernels have no vector backend on `wasm32`, so they are not
710    // reported there.
711    #[cfg(target_arch = "wasm32")]
712    return Vec::new();
713    #[cfg(not(target_arch = "wasm32"))]
714    vec![
715        (
716            "hevc_prediction_filters",
717            engine::simd::detected_isa().as_simd_isa(),
718        ),
719        (
720            "hevc_transforms",
721            engine::transform_simd::detected().as_simd_isa(),
722        ),
723    ]
724}
725
726#[cfg(test)]
727mod tests {
728    use super::*;
729
730    #[test]
731    fn rejects_truncated_configuration_without_panicking() {
732        let limits = Limits::default();
733        let configuration = VideoDecoderConfig {
734            codec: Codec::Hevc,
735            profile: CodecProfile::HevcMain,
736            coded_dimensions: crate::VideoDimensions::new(16, 16, &limits).unwrap(),
737            output_format: PixelFormat::Rgba8,
738            color_range: ColorRange::Limited,
739            hardware: HardwarePreference::Avoid,
740            configuration: vec![1, 2, 3],
741        };
742        let support = native_hevc_video_decoder_factory().capability(&configuration);
743        assert!(matches!(support, CodecSupport::InvalidConfiguration { .. }));
744    }
745
746    fn block_on<T>(future: impl std::future::Future<Output = T>) -> T {
747        use std::task::{Context, Poll, Waker};
748        let mut future = Box::pin(future);
749        let mut context = Context::from_waker(Waker::noop());
750        loop {
751            if let Poll::Ready(value) = future.as_mut().poll(&mut context) {
752                return value;
753            }
754        }
755    }
756
757    /// The 12-frame HEVC Main 10 fixture's decoder configuration and decode-order samples.
758    fn main10_fixture(
759        profile: CodecProfile,
760        hardware: HardwarePreference,
761    ) -> (VideoDecoderConfig, Vec<EncodedVideoSample>) {
762        use crate::Mp4DemuxerOptions;
763        use crate::io::MemorySource;
764        use crate::mp4_demux::Mp4Demuxer;
765
766        let limits = Limits::default();
767        let source = MemorySource::new(
768            include_bytes!("../tests/fixtures/bbb_hevc_main10_128x72.mp4").to_vec(),
769        );
770        let movie = block_on(Mp4Demuxer::open(&source, Mp4DemuxerOptions::default())).unwrap();
771        let track = &movie.tracks[0];
772        let samples = block_on(track.to_encoded_video_samples(&source, &limits)).unwrap();
773        let configuration = VideoDecoderConfig {
774            codec: Codec::Hevc,
775            profile,
776            coded_dimensions: track.dimensions.unwrap(),
777            output_format: PixelFormat::Rgba8,
778            color_range: ColorRange::Limited,
779            hardware,
780            configuration: track.decoder_config.clone(),
781        };
782        (configuration, samples)
783    }
784
785    /// Issue #508: the 10-bit samples themselves are FFmpeg's `yuv420p10le` decode of the same
786    /// fixture exactly, and their RGBA conversion is the fixture's, which applies the documented
787    /// matrix to that FFmpeg decode.
788    #[test]
789    fn main10_pictures_match_an_independent_reference_decode() {
790        let digests = |fixture: &str| {
791            fixture
792                .lines()
793                .map(|line| line.split_once(' ').unwrap().1.to_owned())
794                .collect::<Vec<_>>()
795        };
796        let expected = digests(include_str!(
797            "../tests/fixtures/bbb_hevc_main10_128x72_yuv420p10le.sha256"
798        ));
799        let expected_rgba = digests(include_str!(
800            "../tests/fixtures/bbb_hevc_main10_128x72_rgba.sha256"
801        ));
802        let limits = Limits::default();
803        let (configuration, samples) =
804            main10_fixture(CodecProfile::HevcMain10, HardwarePreference::Avoid);
805        let parsed = ParsedConfiguration::parse(&configuration, &limits).unwrap();
806        assert!(parsed.high_bit_depth);
807        let mut decoder = HevcDecoder::new(configuration.clone(), limits, parsed).unwrap();
808        let cancellation = CancellationToken::new();
809        let mut pictures = Vec::new();
810        for sample in &samples {
811            decoder.push_sample(sample, &cancellation).unwrap();
812            pictures.extend(decoder.collect_pictures(false).unwrap());
813        }
814        decoder.sequence.flush().unwrap();
815        pictures.extend(decoder.collect_pictures(true).unwrap());
816        pictures.sort_by_key(|(index, _)| *index);
817
818        assert_eq!(pictures.len(), expected.len());
819        for (((index, picture), expected), expected_rgba) in
820            pictures.iter().zip(&expected).zip(&expected_rgba)
821        {
822            assert_eq!(picture.bit_depth_luma(), 10);
823            assert_eq!(picture.bit_depth_chroma(), 10);
824            let digest = crate::conformance::FrameDigest(crate::conformance::sha256(
825                &picture.to_planar_le16(),
826            ));
827            assert_eq!(&digest.to_hex(), expected, "frame {}", index.0);
828            let rgba = picture_to_rgba(picture, &configuration, &limits).unwrap();
829            let digest = crate::conformance::FrameDigest::from_frame(&rgba).unwrap();
830            assert_eq!(&digest.to_hex(), expected_rgba, "frame {} RGBA", index.0);
831        }
832    }
833
834    #[test]
835    fn main10_is_decoded_in_software_whatever_the_hardware_preference() {
836        let factory = native_hevc_video_decoder_factory();
837        for hardware in [HardwarePreference::Avoid, HardwarePreference::Prefer] {
838            let (configuration, _) = main10_fixture(CodecProfile::HevcMain10, hardware);
839            assert_eq!(
840                factory.capability(&configuration),
841                CodecSupport::Supported {
842                    implementation: CodecImplementation::Software
843                },
844                "{hardware:?}"
845            );
846        }
847        let (configuration, _) =
848            main10_fixture(CodecProfile::HevcMain10, HardwarePreference::Require);
849        assert_eq!(
850            factory.capability(&configuration),
851            CodecSupport::HardwareUnavailable
852        );
853        let error = factory
854            .create(&configuration, &Limits::default())
855            .err()
856            .expect("no accelerated backend decodes Main 10");
857        assert_eq!(error.kind(), ErrorKind::Unsupported);
858    }
859
860    #[test]
861    fn a_main_profile_configuration_still_refuses_a_10_bit_track() {
862        let (configuration, _) = main10_fixture(CodecProfile::HevcMain, HardwarePreference::Avoid);
863        let factory = native_hevc_video_decoder_factory();
864        assert!(matches!(
865            factory.capability(&configuration),
866            CodecSupport::InvalidConfiguration { .. }
867        ));
868        let error = factory
869            .create(&configuration, &Limits::default())
870            .err()
871            .expect("HEVC Main is 8-bit");
872        assert_eq!(error.kind(), ErrorKind::Unsupported);
873    }
874
875    #[test]
876    fn canonical_conversion_uses_decoder_integer_rounding() {
877        use color_convert::{clip_u8, multiply_high};
878
879        assert_eq!(multiply_high(-8, -3_209), 0);
880        assert_eq!(clip_u8(multiply_high(0, 9_539)), 0);
881    }
882
883    /// Issue #506: the 32-frame-cadence fixture is an open-GOP stream. Each CRA picture after
884    /// the first is followed in decode order by RASL pictures presented before it, and those
885    /// reference pictures from before the CRA. Every frame from 29 onwards used to fail from a
886    /// fresh reader: 29 to 31 because the reader started at the CRA that leads them, and later
887    /// frames because a decode started at a CRA tried to reconstruct its RASL pictures. Every
888    /// frame must now match FFmpeg's decode walked in order, in reverse and alternating, and
889    /// the frames around CRA boundaries must match it from a fresh reader.
890    #[test]
891    fn open_gop_leading_pictures_decode_from_every_starting_point() {
892        use crate::io::MemorySource;
893        use crate::{
894            ExactFrameReader, FrameDigest, FrameIndex, Mp4DemuxerOptions,
895            VideoDecoderConformanceVector, VideoDimensions, verify_video_decoder_conformance,
896        };
897
898        let expected = include_str!("../tests/fixtures/bbb_hevc_512x288_gop32_rgba.sha256")
899            .lines()
900            .map(|line| FrameDigest::from_hex(line.split_once(' ').unwrap().1).unwrap())
901            .collect::<Vec<_>>();
902        let limits = Limits::default();
903        let source = MemorySource::new(
904            include_bytes!("../tests/fixtures/bbb_hevc_512x288_gop32.mp4").to_vec(),
905        );
906        let vector = block_on(VideoDecoderConformanceVector::from_mp4(
907            "open-GOP HEVC Main fixture",
908            &source,
909            Mp4DemuxerOptions::default(),
910            1,
911            VideoDecoderConfig {
912                codec: Codec::Hevc,
913                profile: CodecProfile::HevcMain,
914                coded_dimensions: VideoDimensions::new(512, 288, &limits).unwrap(),
915                output_format: PixelFormat::Rgba8,
916                color_range: ColorRange::Limited,
917                hardware: HardwarePreference::Avoid,
918                configuration: Vec::new(),
919            },
920            &expected,
921        ))
922        .unwrap();
923        assert_eq!(vector.samples.len(), 768);
924        // The fixture's shape, which is what makes it a regression test: the CRA at frame 32
925        // is followed in decode order by the RASL pictures 30, 29 and 31.
926        let order = vector.samples[29..33]
927            .iter()
928            .map(|sample| (sample.presentation_index.0, sample.random_access))
929            .collect::<Vec<_>>();
930        assert_eq!(order, [(32, true), (30, false), (29, false), (31, false)]);
931
932        let factory = native_hevc_video_decoder_factory();
933        let report = verify_video_decoder_conformance(&factory, &vector, limits).unwrap();
934        assert_eq!(report.frames_verified, 3 * 768);
935
936        // A fresh reader for every frame of the first two CRA boundaries, and for the leading
937        // and trailing pictures of later ones.
938        let cancellation = CancellationToken::new();
939        for index in (20..=72).chain([413, 415, 416, 767]) {
940            let expected = &expected[index];
941            let mut reader = ExactFrameReader::new(
942                &factory,
943                vector.configuration.clone(),
944                vector.samples.clone(),
945                limits,
946            )
947            .unwrap();
948            let frame = reader
949                .get(FrameIndex(index as u64), &cancellation)
950                .unwrap_or_else(|error| panic!("frame {index} from a fresh reader: {error}"));
951            assert_eq!(
952                FrameDigest::from_frame(&frame).unwrap(),
953                *expected,
954                "frame {index} from a fresh reader"
955            );
956            // A frame at or after a CRA is reached from that CRA, skipping its RASL pictures,
957            // not by walking the whole track from frame 0.
958            if index == 40 {
959                assert!(
960                    reader.statistics().samples_submitted <= 16,
961                    "frame 40 is decoded from the CRA at frame 32: {:?}",
962                    reader.statistics()
963                );
964            }
965        }
966    }
967}