use std::{
num::{NonZeroU8, NonZeroUsize},
path::Path,
};
use anyhow::{Result, anyhow, bail};
use av1_grain::v_frame::{
chroma::ChromaSubsampling,
frame::{Frame as VFrame, FrameBuilder},
pixel::Pixel as VPixel,
};
use ffmpeg::{
Rational, Stream,
codec::{decoder, packet},
format::{self, context::Input},
frame, media,
};
pub struct BitstreamReader {
input_ctx: Input,
decoder: decoder::Video,
video_details: VideoDetails,
frameno: usize,
end_of_stream: bool,
eof_sent: bool,
}
impl BitstreamReader {
pub fn open<P: AsRef<Path>>(input: P) -> Result<Self> {
ffmpeg::init()?;
let input_ctx = format::input(&input)?;
let input = input_ctx
.streams()
.best(media::Type::Video)
.ok_or_else(|| anyhow!("Could not find video stream"))?;
let mut decoder = ffmpeg::codec::context::Context::from_parameters(input.parameters())?
.decoder()
.video()?;
decoder.set_parameters(input.parameters())?;
let video_details = VideoDetails {
width: NonZeroUsize::new(decoder.width() as usize).expect("cannot be zero"),
height: NonZeroUsize::new(decoder.height() as usize).expect("cannot be zero"),
bit_depth: unsafe {
match decoder.format() {
format::pixel::Pixel::YUV420P
| format::pixel::Pixel::YUV422P
| format::pixel::Pixel::YUV444P => NonZeroU8::new_unchecked(8),
format::pixel::Pixel::YUV420P10LE
| format::pixel::Pixel::YUV422P10LE
| format::pixel::Pixel::YUV444P10LE => NonZeroU8::new_unchecked(10),
format::pixel::Pixel::YUV420P12LE
| format::pixel::Pixel::YUV422P12LE
| format::pixel::Pixel::YUV444P12LE => NonZeroU8::new_unchecked(12),
_ => {
bail!("Unsupported pixel format {:?}", decoder.format());
}
}
},
chroma_sampling: match decoder.format() {
format::pixel::Pixel::YUV420P
| format::pixel::Pixel::YUV420P10LE
| format::pixel::Pixel::YUV420P12LE => ChromaSubsampling::Yuv420,
format::pixel::Pixel::YUV422P
| format::pixel::Pixel::YUV422P10LE
| format::pixel::Pixel::YUV422P12LE => ChromaSubsampling::Yuv422,
format::pixel::Pixel::YUV444P
| format::pixel::Pixel::YUV444P10LE
| format::pixel::Pixel::YUV444P12LE => ChromaSubsampling::Yuv444,
_ => {
bail!("Unsupported pixel format {:?}", decoder.format());
}
},
frame_rate: input.avg_frame_rate(),
};
Ok(Self {
input_ctx,
decoder,
video_details,
frameno: 0usize,
end_of_stream: false,
eof_sent: false,
})
}
pub fn get_video_stream(&'_ self) -> Result<Stream<'_>> {
Ok(self
.input_ctx
.streams()
.best(media::Type::Video)
.ok_or(ffmpeg::Error::StreamNotFound)?)
}
pub const fn input(&mut self) -> &mut Input {
&mut self.input_ctx
}
#[must_use]
pub const fn get_video_details(&self) -> &VideoDetails {
&self.video_details
}
pub fn get_frame<T: VPixel>(&mut self) -> Result<Option<VFrame<T>>> {
loop {
let packet = self
.input_ctx
.packets()
.find_map(Result::ok)
.map(|(_, packet)| packet);
let mut packet = if let Some(packet) = packet {
packet
} else {
self.end_of_stream = true;
packet::Packet::empty()
};
if self.end_of_stream && !self.eof_sent {
let _ = self.decoder.send_eof();
self.eof_sent = true;
}
if self.end_of_stream || packet.stream() == self.get_video_stream()?.index() {
let mut decoded = frame::Video::new(
self.decoder.format(),
self.video_details.width.get() as u32,
self.video_details.height.get() as u32,
);
packet.set_pts(Some(self.frameno as i64));
packet.set_dts(Some(self.frameno as i64));
if !self.end_of_stream {
let _ = self.decoder.send_packet(&packet);
}
if self.decoder.receive_frame(&mut decoded).is_ok() {
let mut f: VFrame<T> = FrameBuilder::new(
self.video_details.width,
self.video_details.height,
self.video_details.chroma_sampling,
self.video_details.bit_depth,
)
.build()?;
f.y_plane.copy_from_u8_slice(decoded.data(0))?;
f.u_plane
.as_mut()
.expect("has chroma")
.copy_from_u8_slice(decoded.data(1))?;
f.v_plane
.as_mut()
.expect("has chroma")
.copy_from_u8_slice(decoded.data(2))?;
self.frameno += 1;
return Ok(Some(f));
} else if self.end_of_stream {
return Ok(None);
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct VideoDetails {
pub width: NonZeroUsize,
pub height: NonZeroUsize,
pub bit_depth: NonZeroU8,
pub chroma_sampling: ChromaSubsampling,
pub frame_rate: Rational,
}