cros_codecs/decoder/
stateless.rs

1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Stateless decoders.
6//!
7//! Stateless here refers to the backend API targeted by these decoders. The decoders themselves do
8//! hold the decoding state so the backend doesn't need to.
9//!
10//! The [`StatelessDecoder`] struct is the basis of all stateless decoders. It is created by
11//! combining a codec codec to a [backend](crate::backend), after which bitstream units can be
12//! submitted through the [`StatelessDecoder::decode`] method.
13
14pub mod av1;
15pub mod h264;
16pub mod h265;
17pub mod vp8;
18pub mod vp9;
19
20use std::os::fd::AsFd;
21use std::os::fd::BorrowedFd;
22
23use nix::errno::Errno;
24use nix::sys::epoll::Epoll;
25use nix::sys::epoll::EpollCreateFlags;
26use nix::sys::epoll::EpollEvent;
27use nix::sys::epoll::EpollFlags;
28use nix::sys::eventfd::EventFd;
29use thiserror::Error;
30
31use crate::codec::vp8::parser::ParseFrameError;
32use crate::decoder::BlockingMode;
33use crate::decoder::DecodedHandle;
34use crate::decoder::DecoderEvent;
35use crate::decoder::DecoderFormatNegotiator;
36use crate::decoder::DynDecodedHandle;
37use crate::decoder::FramePool;
38use crate::decoder::ReadyFramesQueue;
39use crate::decoder::StreamInfo;
40use crate::DecodedFormat;
41use crate::Resolution;
42
43/// Error returned by `new_picture` methods of the backend, usually to indicate which kind of
44/// resource is needed before the picture can be successfully created.
45#[derive(Error, Debug)]
46pub enum NewPictureError {
47    /// Indicates that the backend needs one output buffer to be returned to the pool before it can
48    /// proceed.
49    #[error("need one output buffer to be returned before operation can proceed")]
50    OutOfOutputBuffers,
51    /// No frame pool could satisfy the frame requirements. This indicate either an unhandled DRC
52    /// or an invalid stream.
53    #[error("no frame pool can satisfy the requested frame resolution {0:?}")]
54    NoFramePool(Resolution),
55    /// An unrecoverable backend error has occured.
56    #[error(transparent)]
57    BackendError(#[from] anyhow::Error),
58}
59
60pub type NewPictureResult<T> = Result<T, NewPictureError>;
61
62/// Error returned by stateless backend methods.
63#[derive(Error, Debug)]
64pub enum StatelessBackendError {
65    #[error(transparent)]
66    Other(#[from] anyhow::Error),
67}
68
69/// Result type returned by stateless backend methods.
70pub type StatelessBackendResult<T> = Result<T, StatelessBackendError>;
71
72/// Decoder implementations can use this struct to represent their decoding state.
73///
74/// `F` is a type containing the parsed stream format, that the decoder will use for format
75/// negotiation with the client.
76#[derive(Default)]
77enum DecodingState<F> {
78    /// Decoder will ignore all input until format and resolution information passes by.
79    #[default]
80    AwaitingStreamInfo,
81    /// Decoder is stopped until the client has confirmed the output format.
82    AwaitingFormat(F),
83    /// Decoder is currently decoding input.
84    Decoding,
85    /// Decoder has been reset after a flush, and can resume with the current parameters after
86    /// seeing a key frame.
87    Reset,
88}
89
90/// Error returned by the [`StatelessVideoDecoder::decode`] method.
91#[derive(Debug, Error)]
92pub enum DecodeError {
93    #[error("not enough output buffers available to continue, need {0} more")]
94    NotEnoughOutputBuffers(usize),
95    #[error("cannot accept more input until pending events are processed")]
96    CheckEvents,
97    #[error("error while parsing frame: {0}")]
98    ParseFrameError(#[from] ParseFrameError),
99    #[error(transparent)]
100    DecoderError(#[from] anyhow::Error),
101    #[error(transparent)]
102    BackendError(#[from] StatelessBackendError),
103}
104
105/// Convenience conversion for codecs that process a single frame per decode call.
106impl From<NewPictureError> for DecodeError {
107    fn from(err: NewPictureError) -> Self {
108        match err {
109            NewPictureError::OutOfOutputBuffers => DecodeError::NotEnoughOutputBuffers(1),
110            e @ NewPictureError::NoFramePool(_) => {
111                DecodeError::BackendError(StatelessBackendError::Other(anyhow::anyhow!(e)))
112            }
113            NewPictureError::BackendError(e) => {
114                DecodeError::BackendError(StatelessBackendError::Other(e))
115            }
116        }
117    }
118}
119
120mod private {
121    use super::*;
122
123    /// Private trait for methods we need to expose for crate types (e.g.
124    /// [`DecoderFormatNegotiator`]s), but don't want to be directly used by the client.
125    pub(super) trait StatelessVideoDecoder {
126        /// Try to apply `format` to output frames. If successful, all frames emitted after the
127        /// call will be in the new format.
128        fn try_format(&mut self, format: DecodedFormat) -> anyhow::Result<()>;
129    }
130}
131
132/// Specifies the type of picture that a backend will create for a given codec.
133///
134/// The picture type is state that is preserved from the start of a given frame to its submission
135/// to the backend. Some codecs don't need it, in this case they can just set `Picture` to `()`.
136pub trait StatelessDecoderBackendPicture<Codec: StatelessCodec> {
137    /// Backend-specific type representing a frame being decoded. Useful for decoders that need
138    /// to render a frame in several steps and to preserve its state in between.
139    ///
140    /// Backends that don't use this can simply set it to `()`.
141    type Picture;
142}
143
144pub trait TryFormat<Codec: StatelessCodec> {
145    /// Try to alter the decoded format.
146    fn try_format(
147        &mut self,
148        format_info: &Codec::FormatInfo,
149        format: DecodedFormat,
150    ) -> anyhow::Result<()>;
151}
152
153/// Common trait shared by all stateless video decoder backends, providing codec-independent
154/// methods.
155pub trait StatelessDecoderBackend {
156    /// The type that the backend returns as a result of a decode operation.
157    /// This will usually be some backend-specific type with a resource and a
158    /// resource pool so that said buffer can be reused for another decode
159    /// operation when it goes out of scope.
160    type Handle: DecodedHandle;
161
162    type FramePool: FramePool<Descriptor = <Self::Handle as DecodedHandle>::Descriptor>;
163
164    /// Returns the current decoding parameters, as parsed from the stream.
165    fn stream_info(&self) -> Option<&StreamInfo>;
166
167    /// Returns the frame pool currently in use by the backend for `layer`.
168    fn frame_pool(&mut self, layer: PoolLayer) -> Vec<&mut Self::FramePool>;
169}
170
171/// Helper to implement [`DecoderFormatNegotiator`] for stateless decoders.
172pub struct StatelessDecoderFormatNegotiator<'a, H, FP, D, FH, F>
173where
174    H: DecodedHandle,
175    FP: FramePool<Descriptor = H::Descriptor>,
176    D: StatelessVideoDecoder<Handle = H, FramePool = FP>,
177    F: Fn(&mut D, &FH),
178{
179    decoder: &'a mut D,
180    format_hint: FH,
181    apply_format: F,
182}
183
184impl<'a, H, FP, D, FH, F> StatelessDecoderFormatNegotiator<'a, H, FP, D, FH, F>
185where
186    H: DecodedHandle,
187    FP: FramePool<Descriptor = H::Descriptor>,
188    D: StatelessVideoDecoder<Handle = H, FramePool = FP>,
189    F: Fn(&mut D, &FH),
190{
191    /// Creates a new format negotiator.
192    ///
193    /// `decoder` is the decoder negotiation is done for. The decoder is exclusively borrowed as
194    /// long as this object exists.
195    ///
196    /// `format_hint` is a codec-specific structure describing the properties of the format.
197    ///
198    /// `apply_format` is a closure called when the object is dropped, and is responsible for
199    /// applying the format and allowing decoding to resume.
200    fn new(decoder: &'a mut D, format_hint: FH, apply_format: F) -> Self {
201        Self {
202            decoder,
203            format_hint,
204            apply_format,
205        }
206    }
207}
208
209impl<'a, H, FP, D, FH, F> DecoderFormatNegotiator
210    for StatelessDecoderFormatNegotiator<'a, H, FP, D, FH, F>
211where
212    H: DecodedHandle,
213    FP: FramePool<Descriptor = H::Descriptor>,
214    D: StatelessVideoDecoder<Handle = H, FramePool = FP> + private::StatelessVideoDecoder,
215    F: Fn(&mut D, &FH),
216{
217    type Descriptor = H::Descriptor;
218
219    fn try_format(&mut self, format: DecodedFormat) -> anyhow::Result<()> {
220        self.decoder.try_format(format)
221    }
222
223    fn frame_pool(
224        &mut self,
225        layer: PoolLayer,
226    ) -> Vec<&mut dyn FramePool<Descriptor = Self::Descriptor>> {
227        self.decoder
228            .frame_pool(layer)
229            .into_iter()
230            .map(|p| p as &mut dyn FramePool<Descriptor = _>)
231            .collect()
232    }
233
234    fn stream_info(&self) -> &StreamInfo {
235        self.decoder.stream_info().unwrap()
236    }
237}
238
239impl<'a, H, FP, D, FH, F> Drop for StatelessDecoderFormatNegotiator<'a, H, FP, D, FH, F>
240where
241    H: DecodedHandle,
242    FP: FramePool<Descriptor = H::Descriptor>,
243    D: StatelessVideoDecoder<Handle = H, FramePool = FP>,
244    F: Fn(&mut D, &FH),
245{
246    fn drop(&mut self) {
247        (self.apply_format)(self.decoder, &self.format_hint)
248    }
249}
250
251/// Controls the pool returned by [`StatelessVideoDecoder::frame_pool`].
252#[derive(Debug, Clone, Copy)]
253pub enum PoolLayer {
254    /// The pool for the highest spatial layer.
255    Highest,
256    /// The pool for the given resolution.
257    Layer(Resolution),
258    /// All pools.
259    All,
260}
261
262/// Stateless video decoder interface.
263///
264/// A stateless decoder differs from a stateful one in that its input and output queues are not
265/// operating independently: a new decode unit can only be processed if there is already an output
266/// resource available to receive its decoded content.
267///
268/// Therefore [`decode`] can refuse work if there is no output resource
269/// available at the time of calling, in which case the caller is responsible for calling
270/// [`decode`] again with the same parameters after processing at least one
271/// pending output frame and returning it to the decoder.
272///
273/// The `M` generic parameter is the type of the memory descriptor backing the output frames.
274///
275/// [`decode`]: StatelessVideoDecoder::decode
276pub trait StatelessVideoDecoder {
277    /// Type of the [`DecodedHandle`]s that decoded frames are returned into.
278    type Handle: DecodedHandle;
279
280    /// [`FramePool`] providing frames to decode into. Its descriptor must be the same as
281    /// [`StatelessVideoDecoder::Handle`].
282    type FramePool: FramePool<Descriptor = <Self::Handle as DecodedHandle>::Descriptor> + ?Sized;
283
284    /// Attempts to decode `bitstream` if the current conditions allow it.
285    ///
286    /// This method will return [`DecodeError::CheckEvents`] if processing cannot take place until
287    /// pending events are handled. This could either be because a change of output format has
288    /// been detected that the client should acknowledge, or because there are no available output
289    /// resources and dequeueing and returning pending frames will fix that. After the cause has
290    /// been addressed, the client is responsible for calling this method again with the same data.
291    ///
292    /// The return value is the number of bytes in `bitstream` that have been processed. Usually
293    /// this will be equal to the length of `bitstream`, but some codecs may only do partial
294    /// processing if e.g. several units are sent at the same time. It is the responsibility of the
295    /// caller to check that all submitted input has been processed, and to resubmit the
296    /// unprocessed part if it hasn't. See the documentation of each codec for their expectations.
297    fn decode(&mut self, timestamp: u64, bitstream: &[u8]) -> Result<usize, DecodeError>;
298
299    /// Flush the decoder i.e. finish processing all pending decode requests and make sure the
300    /// resulting frames are ready to be retrieved via [`next_event`].
301    ///
302    /// Note that after flushing, a key frame must be submitted before decoding can resume.
303    ///
304    /// [`next_event`]: StatelessVideoDecoder::next_event
305    fn flush(&mut self) -> Result<(), DecodeError>;
306
307    /// Returns the frame pool for `resolution` in use with the decoder. If
308    /// `resolution` is None, the pool of the highest resolution is returned.
309    ///
310    /// Multiple pools may be in use for SVC streams, since each spatial layer
311    /// will receive its frames from a separate pool.
312    ///
313    /// Useful to add new frames as decode targets.
314    fn frame_pool(&mut self, layer: PoolLayer) -> Vec<&mut Self::FramePool>;
315
316    fn stream_info(&self) -> Option<&StreamInfo>;
317
318    /// Returns the next event, if there is any pending.
319    fn next_event(&mut self) -> Option<DecoderEvent<Self::Handle>>;
320
321    /// Returns a file descriptor that signals `POLLIN` whenever an event is pending on this
322    /// decoder.
323    fn poll_fd(&self) -> BorrowedFd;
324
325    /// Transforms the decoder into a [`StatelessVideoDecoder`] trait object.
326    ///
327    /// All decoders going through this method present the same virtual interface when they return.
328    /// This is useful in order avoid monomorphization of application code that can control
329    /// decoders using various codecs or backends.
330    fn into_trait_object(
331        self,
332    ) -> DynStatelessVideoDecoder<<Self::Handle as DecodedHandle>::Descriptor>
333    where
334        Self: Sized + 'static,
335        Self::FramePool: Sized + 'static,
336        Self::Handle: 'static,
337    {
338        Box::new(DynStatelessVideoDecoderWrapper(self))
339    }
340}
341
342/// Wrapper type for a `StatelessVideoDecoder` that can be turned into a trait object with a common
343/// interface.
344struct DynStatelessVideoDecoderWrapper<D: StatelessVideoDecoder>(D);
345
346impl<D> StatelessVideoDecoder for DynStatelessVideoDecoderWrapper<D>
347where
348    D: StatelessVideoDecoder,
349    <D as StatelessVideoDecoder>::FramePool: Sized + 'static,
350    <D as StatelessVideoDecoder>::Handle: 'static,
351{
352    type Handle = DynDecodedHandle<<D::Handle as DecodedHandle>::Descriptor>;
353    type FramePool = dyn FramePool<Descriptor = <D::FramePool as FramePool>::Descriptor>;
354
355    fn decode(&mut self, timestamp: u64, bitstream: &[u8]) -> Result<usize, DecodeError> {
356        self.0.decode(timestamp, bitstream)
357    }
358
359    fn flush(&mut self) -> Result<(), DecodeError> {
360        self.0.flush()
361    }
362
363    fn frame_pool(&mut self, layer: PoolLayer) -> Vec<&mut Self::FramePool> {
364        self.0
365            .frame_pool(layer)
366            .into_iter()
367            .map(|p| p as &mut Self::FramePool)
368            .collect()
369    }
370
371    fn stream_info(&self) -> Option<&StreamInfo> {
372        self.0.stream_info()
373    }
374
375    fn next_event(&mut self) -> Option<DecoderEvent<Self::Handle>> {
376        self.0.next_event().map(|e| match e {
377            DecoderEvent::FrameReady(h) => {
378                DecoderEvent::FrameReady(Box::new(h) as DynDecodedHandle<_>)
379            }
380            DecoderEvent::FormatChanged(n) => DecoderEvent::FormatChanged(n),
381        })
382    }
383
384    fn poll_fd(&self) -> BorrowedFd {
385        self.0.poll_fd()
386    }
387}
388
389pub type DynStatelessVideoDecoder<D> = Box<
390    dyn StatelessVideoDecoder<
391        Handle = DynDecodedHandle<D>,
392        FramePool = dyn FramePool<Descriptor = D>,
393    >,
394>;
395
396pub trait StatelessCodec {
397    /// Type providing current format information for the codec: resolution, color format, etc.
398    ///
399    /// For H.264 this would be the Sps, for VP8 or VP9 the frame header.
400    type FormatInfo;
401    /// State that needs to be kept during a decoding operation, typed by backend.
402    type DecoderState<H: DecodedHandle, P>;
403}
404
405/// A struct that serves as a basis to implement a stateless decoder.
406///
407/// A stateless decoder is defined by three generic parameters:
408///
409/// * A codec, represented by a type that implements [`StatelessCodec`]. This type defines the
410/// codec-specific decoder state and other codec properties.
411/// * A backend, i.e. an interface to talk to the hardware that accelerates decoding. An example is
412/// the VAAPI backend that uses VAAPI for acceleration. The backend will typically itself be typed
413/// against a memory decriptor, defining how memory is provided for decoded frames.
414///
415/// So for instance, a decoder for the H264 codec, using VAAPI for acceleration with self-managed
416/// memory, will have the following type:
417///
418/// ```text
419/// let decoder: StatelessDecoder<H264, VaapiBackend<()>>;
420/// ```
421///
422/// This struct just manages the high-level decoder state as well as the queue of decoded frames.
423/// All the rest is left to codec-specific code.
424pub struct StatelessDecoder<C, B>
425where
426    C: StatelessCodec,
427    B: StatelessDecoderBackend + StatelessDecoderBackendPicture<C>,
428{
429    /// The current coded resolution
430    coded_resolution: Resolution,
431
432    /// Whether the decoder should block on decode operations.
433    blocking_mode: BlockingMode,
434
435    ready_queue: ReadyFramesQueue<B::Handle>,
436
437    decoding_state: DecodingState<C::FormatInfo>,
438
439    /// The backend used for hardware acceleration.
440    backend: B,
441
442    /// Codec-specific state.
443    codec: C::DecoderState<B::Handle, B::Picture>,
444
445    /// Signaled whenever the decoder is in `AwaitingFormat` state.
446    awaiting_format_event: EventFd,
447
448    /// Union of `awaiting_format_event` and `ready_queue` to signal whenever there is an event
449    /// (frame ready or format change) pending.
450    epoll_fd: Epoll,
451}
452
453#[derive(Debug, Error)]
454pub enum NewStatelessDecoderError {
455    #[error("failed to create EventFd for ready frames queue: {0}")]
456    ReadyFramesQueue(Errno),
457    #[error("failed to create EventFd for awaiting format event: {0}")]
458    AwaitingFormatEventFd(Errno),
459    #[error("failed to create Epoll for decoder: {0}")]
460    Epoll(Errno),
461    #[error("failed to add poll FDs to decoder Epoll: {0}")]
462    EpollAdd(Errno),
463}
464
465impl<C, B> StatelessDecoder<C, B>
466where
467    C: StatelessCodec,
468    B: StatelessDecoderBackend + StatelessDecoderBackendPicture<C> + TryFormat<C>,
469    C::DecoderState<B::Handle, B::Picture>: Default,
470{
471    pub fn new(backend: B, blocking_mode: BlockingMode) -> Result<Self, NewStatelessDecoderError> {
472        let ready_queue =
473            ReadyFramesQueue::new().map_err(NewStatelessDecoderError::ReadyFramesQueue)?;
474        let awaiting_format_event =
475            EventFd::new().map_err(NewStatelessDecoderError::AwaitingFormatEventFd)?;
476        let epoll_fd =
477            Epoll::new(EpollCreateFlags::empty()).map_err(NewStatelessDecoderError::Epoll)?;
478        epoll_fd
479            .add(
480                ready_queue.poll_fd(),
481                EpollEvent::new(EpollFlags::EPOLLIN, 1),
482            )
483            .map_err(NewStatelessDecoderError::EpollAdd)?;
484        epoll_fd
485            .add(
486                awaiting_format_event.as_fd(),
487                EpollEvent::new(EpollFlags::EPOLLIN, 2),
488            )
489            .map_err(NewStatelessDecoderError::EpollAdd)?;
490
491        Ok(Self {
492            backend,
493            blocking_mode,
494            coded_resolution: Default::default(),
495            decoding_state: Default::default(),
496            ready_queue,
497            codec: Default::default(),
498            awaiting_format_event,
499            epoll_fd,
500        })
501    }
502
503    /// Switch the decoder into `AwaitingFormat` state, making it refuse any input until the
504    /// `FormatChanged` event is processed.
505    fn await_format_change(&mut self, format_info: C::FormatInfo) {
506        self.decoding_state = DecodingState::AwaitingFormat(format_info);
507        self.awaiting_format_event.write(1).unwrap();
508    }
509
510    /// Returns the next pending event, if any, using `on_format_changed` as the format change
511    /// callback of the [`StatelessDecoderFormatNegotiator`] if there is a resolution change event
512    /// pending.
513    fn query_next_event<F>(&mut self, on_format_changed: F) -> Option<DecoderEvent<B::Handle>>
514    where
515        Self: StatelessVideoDecoder<Handle = B::Handle, FramePool = B::FramePool>,
516        C::FormatInfo: Clone,
517        F: Fn(&mut Self, &C::FormatInfo) + 'static,
518    {
519        // The next event is either the next frame, or, if we are awaiting negotiation, the format
520        // change event that will allow us to keep going.
521        self.ready_queue
522            .next()
523            .map(DecoderEvent::FrameReady)
524            .or_else(|| {
525                if let DecodingState::AwaitingFormat(format_info) = &self.decoding_state {
526                    Some(DecoderEvent::FormatChanged(Box::new(
527                        StatelessDecoderFormatNegotiator::new(
528                            self,
529                            format_info.clone(),
530                            move |decoder, sps| {
531                                on_format_changed(decoder, sps);
532                                decoder.decoding_state = DecodingState::Decoding;
533                                // Stop signaling the format change event.
534                                decoder.awaiting_format_event.read().unwrap();
535                            },
536                        ),
537                    )))
538                } else {
539                    None
540                }
541            })
542    }
543}
544
545impl<C, B> private::StatelessVideoDecoder for StatelessDecoder<C, B>
546where
547    C: StatelessCodec,
548    B: StatelessDecoderBackend + StatelessDecoderBackendPicture<C> + TryFormat<C>,
549{
550    fn try_format(&mut self, format: crate::DecodedFormat) -> anyhow::Result<()> {
551        match &self.decoding_state {
552            DecodingState::AwaitingFormat(sps) => self.backend.try_format(sps, format),
553            _ => Err(anyhow::anyhow!(
554                "current decoder state does not allow format change"
555            )),
556        }
557    }
558}
559
560#[cfg(test)]
561pub(crate) mod tests {
562    use crate::decoder::stateless::StatelessVideoDecoder;
563    use crate::decoder::DecodedHandle;
564    use crate::decoder::FramePool;
565
566    /// Stream that can be used in tests, along with the CRC32 of all of its frames.
567    pub struct TestStream {
568        /// Bytestream to decode.
569        pub stream: &'static [u8],
570        /// Expected CRC for each frame, one per line.
571        pub crcs: &'static str,
572    }
573
574    /// Run the codec-specific `decoding_loop` on a `decoder` with a given `test`, linearly
575    /// decoding the stream until its end.
576    ///
577    /// If `check_crcs` is `true`, then the expected CRCs of the decoded images are compared
578    /// against the existing result. We may want to set this to false when using a decoder backend
579    /// that does not produce actual frames.
580    ///
581    /// `dump_yuv` will dump all the decoded frames into `/tmp/framexxx.yuv`. Set this to true in
582    /// order to debug the output of the test.
583    pub fn test_decode_stream<D, H, FP, L>(
584        decoding_loop: L,
585        mut decoder: D,
586        test: &TestStream,
587        check_crcs: bool,
588        dump_yuv: bool,
589    ) where
590        H: DecodedHandle,
591        FP: FramePool,
592        D: StatelessVideoDecoder<Handle = H, FramePool = FP>,
593        L: Fn(&mut D, &[u8], &mut dyn FnMut(H)) -> anyhow::Result<()>,
594    {
595        let mut crcs = test.crcs.lines().enumerate();
596
597        decoding_loop(&mut decoder, test.stream, &mut |handle| {
598            let (frame_num, expected_crc) = crcs.next().expect("decoded more frames than expected");
599
600            if check_crcs || dump_yuv {
601                handle.sync().unwrap();
602                let picture = handle.dyn_picture();
603                let mut backend_handle = picture.dyn_mappable_handle().unwrap();
604
605                let buffer_size = backend_handle.image_size();
606                let mut nv12 = vec![0; buffer_size];
607
608                backend_handle.read(&mut nv12).unwrap();
609
610                if dump_yuv {
611                    std::fs::write(format!("/tmp/frame{:03}.yuv", frame_num), &nv12).unwrap();
612                }
613
614                if check_crcs {
615                    let frame_crc = format!("{:08x}", crc32fast::hash(&nv12));
616                    assert_eq!(frame_crc, expected_crc, "at frame {}", frame_num);
617                }
618            }
619        })
620        .unwrap();
621
622        assert_eq!(crcs.next(), None, "decoded less frames than expected");
623    }
624}