blob: 8d5a1270f334294c196d073aafaea77c7d78574d [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl_fuchsia_media::FormatDetails;
pub trait ElementaryStream {
fn format_details(&self, version_ordinal: u64) -> FormatDetails;
/// Whether _all_ chunks in the elementary stream will be marked with access unit boundaries.
/// These are units for stream processors (e.g. for H264 decoder, NALs). When input is not in
/// access units, the server must parse and/or buffer the bitstream.
fn is_access_units(&self) -> bool;
fn stream<'a>(&'a self) -> Box<dyn Iterator<Item = ElementaryStreamChunk> + 'a>;
/// Returns the elementary stream with chunks capped at a given size. Chunks bigger than the cap
/// will be divided into multiple chunks. Order is retained. Timestamps are not extrapolated.
/// Access unit boundaries are corrected.
fn capped_chunks<'a>(
&'a self,
max_size: usize,
) -> Box<dyn Iterator<Item = ElementaryStreamChunk> + 'a> {
Box::new(self.stream().flat_map(move |chunk| CappedSizeChunks {
src: chunk,
offset: 0,
max_size,
}))
}
fn video_frame_count(&self) -> usize {
self.stream()
.filter(|chunk| match chunk.significance {
Significance::Video(VideoSignificance::Picture) => true,
_ => false,
})
.count()
}
}
#[derive(Clone, Debug)]
pub struct ElementaryStreamChunk {
pub start_access_unit: bool,
pub known_end_access_unit: bool,
pub data: Vec<u8>,
pub significance: Significance,
pub timestamp: Option<u64>,
}
#[derive(Copy, Clone, Debug)]
pub enum Significance {
Video(VideoSignificance),
Audio(AudioSignificance),
}
#[derive(Copy, Clone, Debug)]
pub enum VideoSignificance {
Picture,
NotPicture,
}
#[derive(Copy, Clone, Debug)]
pub enum AudioSignificance {
PcmFrames,
Encoded,
}
struct CappedSizeChunks {
src: ElementaryStreamChunk,
offset: usize,
max_size: usize,
}
impl Iterator for CappedSizeChunks {
type Item = ElementaryStreamChunk;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.src.data.len() {
return None;
}
let len = std::cmp::min(self.src.data.len() - self.offset, self.max_size);
let next_offset = self.offset + len;
let is_first_subchunk = self.offset == 0;
let is_last_subchunk = next_offset == self.src.data.len();
let chunk = ElementaryStreamChunk {
start_access_unit: self.src.start_access_unit && is_first_subchunk,
known_end_access_unit: self.src.known_end_access_unit && is_last_subchunk,
data: self.src.data[self.offset..next_offset].to_vec(),
timestamp: if is_first_subchunk { self.src.timestamp } else { None },
significance: self.src.significance,
};
self.offset = next_offset;
Some(chunk)
}
}
/// Wraps an elementary stream and adds sequential dummy timestamps to its chunks.
pub struct TimestampedStream<S, I> {
pub source: S,
pub timestamps: I,
}
impl<S, I> ElementaryStream for TimestampedStream<S, I>
where
S: ElementaryStream,
I: Iterator<Item = u64> + Clone,
{
fn format_details(&self, version_ordinal: u64) -> FormatDetails {
self.source.format_details(version_ordinal)
}
fn is_access_units(&self) -> bool {
self.source.is_access_units()
}
fn stream<'a>(&'a self) -> Box<dyn Iterator<Item = ElementaryStreamChunk> + 'a> {
let mut timestamps = self.timestamps.clone();
Box::new(self.source.stream().map(move |mut chunk| {
match chunk.significance {
Significance::Video(VideoSignificance::Picture) => {
chunk.timestamp = timestamps.next();
}
_ => {}
};
chunk
}))
}
}
#[cfg(test)]
mod test {
use super::*;
use fidl::encoding::Decodable;
struct FakeStream {
data: Vec<u8>,
}
impl ElementaryStream for FakeStream {
fn format_details(&self, _version_ordinal: u64) -> FormatDetails {
Decodable::new_empty()
}
fn is_access_units(&self) -> bool {
true
}
fn stream<'a>(&'a self) -> Box<dyn Iterator<Item = ElementaryStreamChunk> + 'a> {
Box::new(self.data.chunks(20).map(|data| ElementaryStreamChunk {
start_access_unit: true,
known_end_access_unit: true,
data: data.to_vec(),
significance: Significance::Video(VideoSignificance::Picture),
timestamp: None,
}))
}
}
#[test]
fn chunks_are_capped() {
let stream = TimestampedStream {
source: FakeStream { data: (0..).take(100).collect() },
timestamps: 0..,
};
let chunk_size = 10;
for (i, capped_chunk) in stream.capped_chunks(chunk_size).enumerate() {
if i % 2 == 0 {
// This is the first half of a capped chunk.
assert!(capped_chunk.start_access_unit);
assert_eq!(capped_chunk.timestamp, Some(i as u64 / 2));
} else {
// This is the second half of a capped chunk.
assert!(capped_chunk.known_end_access_unit);
// No subchunks other than the first should have a timestamp; if there is a
// timestamp, we spend it on the first subchunk to keep it aligned.
assert!(capped_chunk.timestamp.is_none());
}
for j in 0..chunk_size {
assert_eq!(capped_chunk.data[j], (i * chunk_size + j) as u8);
}
}
}
}