use ffmpeg_sys_next::{
av_packet_alloc, av_packet_free, av_packet_unref, av_read_frame,
avformat_seek_file, AVPacket, AVERROR, EAGAIN,
AV_PKT_FLAG_CORRUPT, AV_PKT_FLAG_KEY,
};
use std::iter::FusedIterator;
use crate::core::context::AVFormatContextBox;
use crate::core::stream_info::StreamInfo;
use crate::error::{DemuxingError, OpenInputError, PacketScannerError, Result};
#[derive(Debug, Clone)]
pub struct PacketInfo {
stream_index: usize,
pts: Option<i64>,
dts: Option<i64>,
duration: i64,
size: usize,
pos: i64,
is_keyframe: bool,
is_corrupt: bool,
is_video: bool,
is_audio: bool,
}
impl PacketInfo {
pub fn stream_index(&self) -> usize {
self.stream_index
}
pub fn pts(&self) -> Option<i64> {
self.pts
}
pub fn dts(&self) -> Option<i64> {
self.dts
}
pub fn duration(&self) -> i64 {
self.duration
}
pub fn size(&self) -> usize {
self.size
}
pub fn pos(&self) -> i64 {
self.pos
}
pub fn is_keyframe(&self) -> bool {
self.is_keyframe
}
pub fn is_corrupt(&self) -> bool {
self.is_corrupt
}
pub fn is_video(&self) -> bool {
self.is_video
}
pub fn is_audio(&self) -> bool {
self.is_audio
}
}
pub struct PacketScanner {
fmt_ctx_box: AVFormatContextBox,
pkt: *mut AVPacket,
streams: Vec<StreamInfo>,
}
unsafe impl Send for PacketScanner {}
impl PacketScanner {
pub fn open(url: impl Into<String>) -> Result<Self> {
let fmt_ctx_box = crate::core::stream_info::init_format_context(url)?;
let streams = unsafe { crate::core::stream_info::extract_stream_infos(&fmt_ctx_box)? };
unsafe {
let pkt = av_packet_alloc();
if pkt.is_null() {
return Err(OpenInputError::OutOfMemory.into());
}
Ok(Self { fmt_ctx_box, pkt, streams })
}
}
pub fn seek(&mut self, timestamp_us: i64) -> Result<()> {
unsafe {
let ret = avformat_seek_file(
self.fmt_ctx_box.fmt_ctx,
-1,
i64::MIN,
timestamp_us,
timestamp_us,
0,
);
if ret < 0 {
return Err(
PacketScannerError::SeekError(DemuxingError::from(ret)).into()
);
}
}
Ok(())
}
pub fn next_packet(&mut self) -> Result<Option<PacketInfo>> {
const MAX_EAGAIN_RETRIES: u32 = 500;
unsafe {
av_packet_unref(self.pkt);
let mut eagain_retries: u32 = 0;
loop {
let ret = av_read_frame(self.fmt_ctx_box.fmt_ctx, self.pkt);
if ret == AVERROR(EAGAIN) {
eagain_retries += 1;
if eagain_retries > MAX_EAGAIN_RETRIES {
return Err(
PacketScannerError::ReadError(DemuxingError::from(ret)).into()
);
}
std::thread::sleep(std::time::Duration::from_millis(10));
continue;
}
if ret < 0 {
if ret == ffmpeg_sys_next::AVERROR_EOF {
return Ok(None);
}
return Err(
PacketScannerError::ReadError(DemuxingError::from(ret)).into()
);
}
break;
}
let pkt = &*self.pkt;
let pts = if pkt.pts == ffmpeg_sys_next::AV_NOPTS_VALUE {
None
} else {
Some(pkt.pts)
};
let dts = if pkt.dts == ffmpeg_sys_next::AV_NOPTS_VALUE {
None
} else {
Some(pkt.dts)
};
let stream_index = pkt.stream_index.max(0) as usize;
let (is_video, is_audio) = self.streams.get(stream_index)
.map(|s| (s.is_video(), s.is_audio()))
.unwrap_or((false, false));
Ok(Some(PacketInfo {
stream_index,
pts,
dts,
duration: pkt.duration,
size: { debug_assert!(pkt.size >= 0, "negative pkt.size: {}", pkt.size); pkt.size.max(0) as usize },
pos: pkt.pos,
is_keyframe: (pkt.flags & AV_PKT_FLAG_KEY) != 0,
is_corrupt: (pkt.flags & AV_PKT_FLAG_CORRUPT) != 0,
is_video,
is_audio,
}))
}
}
pub fn streams(&self) -> &[StreamInfo] {
&self.streams
}
pub fn video_stream(&self) -> Option<&StreamInfo> {
self.streams.iter().find(|s| s.is_video())
}
pub fn audio_stream(&self) -> Option<&StreamInfo> {
self.streams.iter().find(|s| s.is_audio())
}
pub fn stream_for_packet(&self, packet: &PacketInfo) -> Option<&StreamInfo> {
self.streams.get(packet.stream_index())
}
pub fn packets(&mut self) -> PacketIter<'_> {
PacketIter { scanner: self, done: false }
}
}
impl Drop for PacketScanner {
fn drop(&mut self) {
unsafe {
if !self.pkt.is_null() {
av_packet_free(&mut self.pkt);
}
}
}
}
pub struct PacketIter<'a> {
scanner: &'a mut PacketScanner,
done: bool,
}
impl<'a> Iterator for PacketIter<'a> {
type Item = Result<PacketInfo>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
match self.scanner.next_packet() {
Ok(Some(info)) => Some(Ok(info)),
Ok(None) => {
self.done = true;
None
}
Err(e) => {
self.done = true;
Some(Err(e))
}
}
}
}
impl<'a> FusedIterator for PacketIter<'a> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_open_not_found() {
let result = PacketScanner::open("not_found.mp4");
assert!(result.is_err());
}
#[test]
fn test_scan_packets() {
let mut scanner = PacketScanner::open("test.mp4").unwrap();
let mut count = 0;
let mut keyframes = 0;
for packet in scanner.packets() {
let info = packet.unwrap();
count += 1;
if info.is_keyframe() {
keyframes += 1;
}
}
assert!(count > 0, "expected at least one packet");
assert!(keyframes > 0, "expected at least one keyframe");
println!("total packets: {}, keyframes: {}", count, keyframes);
}
#[test]
fn test_seek_and_read() {
let mut scanner = PacketScanner::open("test.mp4").unwrap();
scanner.seek(1_000_000).unwrap();
let packet = scanner.next_packet().unwrap();
assert!(packet.is_some(), "expected a packet after seeking");
}
#[test]
fn test_streams() {
let scanner = PacketScanner::open("test.mp4").unwrap();
let streams = scanner.streams();
assert!(!streams.is_empty(), "expected at least one stream");
assert_eq!(streams.len(), 2, "test.mp4 should have 2 streams (video + audio)");
}
#[test]
fn test_video_stream() {
let scanner = PacketScanner::open("test.mp4").unwrap();
let video = scanner.video_stream();
assert!(video.is_some(), "expected a video stream");
assert!(video.unwrap().is_video());
}
#[test]
fn test_audio_stream() {
let scanner = PacketScanner::open("test.mp4").unwrap();
let audio = scanner.audio_stream();
assert!(audio.is_some(), "expected an audio stream");
assert!(audio.unwrap().is_audio());
}
#[test]
fn test_stream_for_packet() {
let mut scanner = PacketScanner::open("test.mp4").unwrap();
let packet = scanner.next_packet().unwrap();
assert!(packet.is_some(), "expected at least one packet");
let info = packet.unwrap();
let stream = scanner.stream_for_packet(&info);
assert!(stream.is_some(), "stream_for_packet should return Some for valid packet");
}
}