Skip to content

Commit

Permalink
Switch to ffmpeg-next, cf. zmwangx/rust-ffmpeg#23
Browse files Browse the repository at this point in the history
  • Loading branch information
CL-Jeremy committed Aug 3, 2020
1 parent 963dcf9 commit dd8f944
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 75 deletions.
66 changes: 33 additions & 33 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions Cargo.toml
Expand Up @@ -33,18 +33,16 @@ wild = "2.0.4"
natord = "1.0.9"
quick-error = "1.2.3"

[dependencies.ffmpeg]
rev = "83ee1c4f5a6f09ce03671861f431e1f015095952"
[dependencies.ffmpeg-next]
rev = "f1ccd4e69641dbd2be98e3ef38e670b592a103c1"
optional = true
git = "https://github.com/kornelski/rust-ffmpeg.git"
default-features = false
features = ["ffmpeg4", "codec", "format", "filter", "software-resampling", "software-scaling"]
git = "https://github.com/zmwangx/rust-ffmpeg.git"

[features]
default = []
openmp = ["imagequant/openmp"]
openmp-static = ["imagequant/openmp-static"]
video = ["ffmpeg"]
video = ["ffmpeg-next"]
malloc = []

[lib]
Expand All @@ -57,3 +55,6 @@ opt-level = 3
[profile.release]
panic = "abort"
lto = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
87 changes: 52 additions & 35 deletions src/bin/ffmpeg_source.rs
@@ -1,3 +1,4 @@
extern crate ffmpeg_next as ffmpeg;
use crate::BinResult;
use gifski::Collector;
use imgref::*;
Expand All @@ -8,9 +9,9 @@ use std::path::Path;
pub struct FfmpegDecoder {
input_context: ffmpeg::format::context::Input,
frames: u64,
fps: f32,
speed: f32,
pts_frame_step: f64,
min_pts: f64,
time_base: f64,
}

impl Source for FfmpegDecoder {
Expand All @@ -29,65 +30,81 @@ impl FfmpegDecoder {
.map_err(|e| format!("Unable to open video file {}: {}", path.display(), e))?;
// take fps override into account
let stream = input_context.streams().best(ffmpeg::media::Type::Video).ok_or("The file has no video tracks")?;
let rate = stream.avg_frame_rate().numerator() as f64 / stream.avg_frame_rate().denominator() as f64;
let max_fps = rate as f32 * speed;
if fps > max_fps {
eprintln!("Target frame rate is at most {:.2}fps based on the average frame rate of input source.\n\
The specified target will net be reached. It will still be used to filter out higher-speed portions in the input.", max_fps=max_fps);
}
let frames = stream.frames() as u64;
let time_base = stream.time_base().numerator() as f64 / stream.time_base().denominator() as f64;
let frames = (stream.duration() as f64 * time_base as f64 * (fps / speed) as f64).ceil() as u64;
Ok(Self {
input_context,
frames: (frames as f64 / rate * match fps > max_fps { true => max_fps, false => fps } as f64 / speed as f64) as u64,
frames,
fps,
speed,
pts_frame_step: 1.0 / fps as f64,
min_pts: 0.0,
time_base,
})
}

pub fn collect_frames(&mut self, mut dest: Collector) -> BinResult<()> {
let (stream_index, mut decoder, mut converter, time_base) = {
let (stream_index, mut decoder, mut filter, time_base) = {
let stream = self.input_context.streams().best(ffmpeg::media::Type::Video).ok_or("The file has no video tracks")?;

let decoder = stream.codec().decoder().video().map_err(|e| format!("Unable to decode the codec used in the video: {}", e))?;

let converter = decoder.converter(ffmpeg::util::format::pixel::Pixel::RGBA)?;
(stream.index(), decoder, converter, stream.time_base())
let time_base = self.time_base / self.speed as f64;
let buffer_args = format!("width={}:height={}:pix_fmt={}:time_base={}:sar={}",
decoder.width(),
decoder.height(),
decoder.format().descriptor().unwrap().name(),
stream.time_base(),
(|sar: ffmpeg::util::rational::Rational| match sar.numerator() {
0 => "1".to_string(),
_ => format!("{}/{}", sar.numerator(), sar.denominator()),
})(decoder.aspect_ratio()),
);
let mut filter = ffmpeg::filter::Graph::new();
filter.add(&ffmpeg::filter::find("buffer").unwrap(), "in", &buffer_args)?;
filter.add(&ffmpeg::filter::find("buffersink").unwrap(), "out", "")?;
filter.output("in", 0)?.input("out", 0)?.parse(&format!("fps=fps={}:eof_action=pass,format=rgba", self.fps / self.speed)[..])?;
filter.validate()?;
(stream.index(), decoder, filter, time_base)
};

let mut i = 0;
for (s, packet) in self.input_context.packets() {
if s.index() != stream_index {
continue;
}
let mut vid_frame = ffmpeg::util::frame::video::Video::empty();
let decoded = decoder.decode(&packet, &mut vid_frame)?;
if !decoded || 0 == vid_frame.width() {
continue;
}

let mut rgba_frame = ffmpeg::util::frame::video::Video::empty();
converter.run(&vid_frame, &mut rgba_frame)?;

let mut ptsf = 0;
let mut vid_frame = ffmpeg::util::frame::Video::empty();
let mut filt_frame = ffmpeg::util::frame::Video::empty();
let mut add_frame = |rgba_frame: &ffmpeg::util::frame::Video, pts: f64, pos: i64| -> BinResult<()> {
let stride = rgba_frame.stride(0) as usize / 4;
let rgba_frame = ImgVec::new_stride(
rgba_frame.data(0).as_rgba().to_owned(),
rgba_frame.width() as usize,
rgba_frame.height() as usize,
stride,
);
Ok(dest.add_frame_rgba(pos as usize, rgba_frame, pts)?)
};

let timestamp = vid_frame.timestamp();
vid_frame.set_pts(timestamp);
let pts = vid_frame.pts().unwrap();
let ptsf = (pts as u64 * time_base.numerator() as u64) as f64 / f64::from(time_base.denominator()) / self.speed as f64;
for (s, packet) in self.input_context.packets() {
if s.index() != stream_index {
continue;
}
let decoded = decoder.decode(&packet, &mut vid_frame)?;
if !decoded || 0 == vid_frame.width() {
continue;
}

if ptsf >= self.min_pts {
dest.add_frame_rgba(i, rgba_frame, ptsf)?;
filter.get("in").unwrap().source().add(&vid_frame).unwrap();
while let Ok(..) = filter.get("out").unwrap().sink().frame(&mut filt_frame) {
ptsf = filt_frame.timestamp().unwrap();
add_frame(&filt_frame, ptsf as f64 * time_base, i)?;
i += 1;
self.min_pts += self.pts_frame_step;
}
}
// round EOF to least integral upper bound
let pts_final = ptsf + (1.5 / time_base / self.fps as f64) as i64;
filter.get("in").unwrap().source().close(pts_final).unwrap();
while let Ok(..) = filter.get("out").unwrap().sink().frame(&mut filt_frame) {
ptsf = filt_frame.timestamp().unwrap();
add_frame(&filt_frame, ptsf as f64 * time_base, i)?;
i += 1;
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion src/bin/gifski.rs
Expand Up @@ -8,7 +8,7 @@ static A: System = System;
#[macro_use] extern crate clap;

#[cfg(feature = "video")]
extern crate ffmpeg;
extern crate ffmpeg_next as ffmpeg;

use natord;
use wild;
Expand Down

0 comments on commit dd8f944

Please sign in to comment.