Compare commits

..

No commits in common. "bdc65868e76f241bb88e89c69464156c80bea51a" and "ef14631f71dd6b791c32505ea9ea4bf24a2ed757" have entirely different histories.

38 changed files with 735 additions and 1122 deletions

View File

@ -23,49 +23,30 @@ env:
jobs: jobs:
build-test-lint-linux: build-test-lint-linux:
name: Linux - FFmpeg ${{ matrix.ffmpeg.version }} - build, test and lint name: Linux - FFmpeg ${{ matrix.ffmpeg_version }} - build, test and lint
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: jrottenberg/ffmpeg:${{ matrix.ffmpeg_version }}-ubuntu
strategy: strategy:
matrix: matrix:
ffmpeg: ffmpeg_version:
- version: "4.2" - "4.2"
file: "ffmpeg-4.2-linux-gcc.tar.xz" - "4.3"
- version: "4.3" - "4.4"
file: "ffmpeg-4.3-linux-gcc.tar.xz" - "5.0"
- version: "4.4" - "5.1"
file: "ffmpeg-4.4-linux-clang-default.tar.xz" - "6.0"
lib_subdir: "amd64" - "6.1"
- version: "5.1" - "7.0"
file: "ffmpeg-5.1-linux-clang-default.tar.xz" - "7.1"
lib_subdir: "amd64"
- version: "6.1"
file: "ffmpeg-6.1-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "7.0"
file: "ffmpeg-7.0-linux-clang-default.tar.xz"
lib_subdir: "amd64"
- version: "7.1"
file: "ffmpeg-7.1-linux-clang-default.tar.xz"
lib_subdir: "amd64"
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get update apt-get update
sudo apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends clang curl pkg-config
libc++1 libva2 libva-drm2 libva-x11-2 libvdpau1 libxv1
- name: Download FFmpeg
shell: bash
run: |
mkdir ffmpeg-libs
curl -L "https://sourceforge.net/projects/avbuild/files/linux/${{ matrix.ffmpeg.file }}/download" \
| tar xJf - --strip 1 -C ffmpeg-libs
echo "PKG_CONFIG_PATH=$PWD/ffmpeg-libs/lib/${{ matrix.ffmpeg.lib_subdir }}/pkgconfig" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$PWD/ffmpeg-libs/lib/${{ matrix.ffmpeg.lib_subdir }}" >> "$GITHUB_ENV"
- name: Install Rust stable with clippy and rustfmt - name: Install Rust stable with clippy and rustfmt
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@ -74,7 +55,7 @@ jobs:
with: with:
prefix-key: "v3-rust" prefix-key: "v3-rust"
# Only save cache for one FFmpeg version # Only save cache for one FFmpeg version
save-if: ${{ matrix.ffmpeg.version == '7.1' }} save-if: ${{ matrix.ffmpeg_version == '7.1' }}
- name: Check format - name: Check format
run: cargo fmt -- --check run: cargo fmt -- --check
@ -185,18 +166,14 @@ jobs:
msrv: msrv:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: jrottenberg/ffmpeg:7.0-ubuntu
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Download FFmpeg - name: Install dependencies
shell: bash
run: | run: |
mkdir ffmpeg-libs apt-get update
curl -L "https://sourceforge.net/projects/avbuild/files/linux/ffmpeg-7.1-linux-clang-lite.tar.xz/download" \ apt-get install -y --no-install-recommends clang curl pkg-config
| tar xJf - --strip 1 -C ffmpeg-libs
echo "PKG_CONFIG_PATH=$PWD/ffmpeg-libs/lib/amd64/pkgconfig" >> "$GITHUB_ENV"
echo "LD_LIBRARY_PATH=$PWD/ffmpeg-libs" >> "$GITHUB_ENV"
# rust-version from Cargo.toml # rust-version from Cargo.toml
- name: Install Rust 1.65.0 - name: Install Rust 1.65.0
uses: dtolnay/rust-toolchain@1.65.0 uses: dtolnay/rust-toolchain@1.65.0

View File

@ -58,6 +58,7 @@ codec = ["ffmpeg-sys-the-third/avcodec"]
device = ["ffmpeg-sys-the-third/avdevice", "format"] device = ["ffmpeg-sys-the-third/avdevice", "format"]
filter = ["ffmpeg-sys-the-third/avfilter"] filter = ["ffmpeg-sys-the-third/avfilter"]
format = ["ffmpeg-sys-the-third/avformat", "codec"] format = ["ffmpeg-sys-the-third/avformat", "codec"]
resampling = ["ffmpeg-sys-the-third/avresample"]
postprocessing = ["ffmpeg-sys-the-third/postproc"] postprocessing = ["ffmpeg-sys-the-third/postproc"]
software-resampling = ["ffmpeg-sys-the-third/swresample"] software-resampling = ["ffmpeg-sys-the-third/swresample"]
software-scaling = ["ffmpeg-sys-the-third/swscale", "codec"] software-scaling = ["ffmpeg-sys-the-third/swscale", "codec"]

View File

@ -4,26 +4,19 @@
This is a fork of the abandoned [ffmpeg-next](https://crates.io/crates/ffmpeg-next) crate which is a fork of the abandoned [ffmpeg](https://crates.io/crates/ffmpeg) crate. This is a fork of the abandoned [ffmpeg-next](https://crates.io/crates/ffmpeg-next) crate which is a fork of the abandoned [ffmpeg](https://crates.io/crates/ffmpeg) crate.
Currently supported FFmpeg versions: 4.2 - 7.1. Currently supported FFmpeg versions: 4.x, 5.x, 6.x.
Versions that are considered [old and unmaintained](https://ffmpeg.org/olddownload.html) by FFmpeg like 5.0 or 6.0 usually work, but are not actively tested during development. Build instructions can be found on the [wiki](https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building).
## Usage Documentation:
Build instructions can be found on the [wiki](https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-building). API documentation for this crate can be found on [docs.rs](https://docs.rs/ffmpeg-the-third/). - [docs.rs](https://docs.rs/ffmpeg-the-third/);
- [FFmpeg user manual](https://ffmpeg.org/ffmpeg-all.html);
- [FFmpeg Doxygen](https://ffmpeg.org/doxygen/trunk/).
_See [CHANGELOG.md](CHANGELOG.md) for information on version upgrades._ _See [CHANGELOG.md](CHANGELOG.md) for other information on version upgrades._
### FFmpeg documentation **If you have significant, demonstrable experience in Rust and multimedia-related programming, please let me know, I'll be more than happy to invite you as a collaborator.**
- [FFmpeg user manual](https://ffmpeg.org/ffmpeg-all.html)
- [FFmpeg Doxygen](https://ffmpeg.org/doxygen/trunk/)
## Contributing
Issues and PRs are welcome.
If you have significant, demonstrable experience in Rust and multimedia-related programming, please let me know, I'll be more than happy to invite you as a collaborator.
## Minimum supported Rust version (MSRV) ## Minimum supported Rust version (MSRV)

View File

@ -5,7 +5,7 @@ use std::env;
fn main() { fn main() {
ffmpeg::init().unwrap(); ffmpeg::init().unwrap();
match ffmpeg::format::input(env::args().nth(1).expect("missing input file name")) { match ffmpeg::format::input(&env::args().nth(1).expect("missing input file name")) {
Ok(ictx) => { Ok(ictx) => {
println!("Nb chapters: {}", ictx.nb_chapters()); println!("Nb chapters: {}", ictx.nb_chapters());
@ -20,7 +20,7 @@ fn main() {
} }
} }
let mut octx = ffmpeg::format::output("test.mkv").expect("Couldn't open test file"); let mut octx = ffmpeg::format::output(&"test.mkv").expect("Couldn't open test file");
for chapter in ictx.chapters() { for chapter in ictx.chapters() {
let title = match chapter.metadata().get("title") { let title = match chapter.metadata().get("title") {

View File

@ -20,7 +20,7 @@ fn main() {
println!("\t profiles: none"); println!("\t profiles: none");
} }
if let Some(video) = codec.video() { if let Ok(video) = codec.video() {
if let Some(rates) = video.rates() { if let Some(rates) = video.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>()); println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else { } else {
@ -34,7 +34,7 @@ fn main() {
} }
} }
if let Some(audio) = codec.audio() { if let Ok(audio) = codec.audio() {
if let Some(rates) = audio.rates() { if let Some(rates) = audio.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>()); println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else { } else {
@ -71,7 +71,7 @@ fn main() {
println!("\t profiles: {:?}", profiles.collect::<Vec<_>>()); println!("\t profiles: {:?}", profiles.collect::<Vec<_>>());
} }
if let Some(video) = codec.video() { if let Ok(video) = codec.video() {
if let Some(rates) = video.rates() { if let Some(rates) = video.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>()); println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else { } else {
@ -85,7 +85,7 @@ fn main() {
} }
} }
if let Some(audio) = codec.audio() { if let Ok(audio) = codec.audio() {
if let Some(rates) = audio.rates() { if let Some(rates) = audio.rates() {
println!("\t rates: {:?}", rates.collect::<Vec<_>>()); println!("\t rates: {:?}", rates.collect::<Vec<_>>());
} else { } else {

View File

@ -11,7 +11,7 @@ use std::io::prelude::*;
fn main() -> Result<(), ffmpeg::Error> { fn main() -> Result<(), ffmpeg::Error> {
ffmpeg::init().unwrap(); ffmpeg::init().unwrap();
if let Ok(mut ictx) = input(env::args().nth(1).expect("Cannot open file.")) { if let Ok(mut ictx) = input(&env::args().nth(1).expect("Cannot open file.")) {
let input = ictx let input = ictx
.streams() .streams()
.best(Type::Video) .best(Type::Video)

View File

@ -5,7 +5,7 @@ use std::env;
fn main() -> Result<(), ffmpeg::Error> { fn main() -> Result<(), ffmpeg::Error> {
ffmpeg::init().unwrap(); ffmpeg::init().unwrap();
match ffmpeg::format::input(env::args().nth(1).expect("missing file")) { match ffmpeg::format::input(&env::args().nth(1).expect("missing file")) {
Ok(context) => { Ok(context) => {
for (k, v) in context.metadata().iter() { for (k, v) in context.metadata().iter() {
println!("{k}: {v}"); println!("{k}: {v}");

View File

@ -72,7 +72,7 @@ struct Transcoder {
fn transcoder<P: AsRef<Path>>( fn transcoder<P: AsRef<Path>>(
ictx: &mut format::context::Input, ictx: &mut format::context::Input,
octx: &mut format::context::Output, octx: &mut format::context::Output,
path: P, path: &P,
filter_spec: &str, filter_spec: &str,
) -> Result<Transcoder, ffmpeg::Error> { ) -> Result<Transcoder, ffmpeg::Error> {
let input = ictx let input = ictx
@ -83,8 +83,7 @@ fn transcoder<P: AsRef<Path>>(
let mut decoder = context.decoder().audio()?; let mut decoder = context.decoder().audio()?;
let codec = ffmpeg::encoder::find(octx.format().codec(path, media::Type::Audio)) let codec = ffmpeg::encoder::find(octx.format().codec(path, media::Type::Audio))
.expect("failed to find encoder") .expect("failed to find encoder")
.audio() .audio()?;
.expect("encoder is not audio encoder");
let global = octx let global = octx
.format() .format()
.flags() .flags()

View File

@ -58,6 +58,7 @@ avcodec = []
avdevice = ["avformat"] avdevice = ["avformat"]
avfilter = [] avfilter = []
avformat = ["avcodec"] avformat = ["avcodec"]
avresample = []
postproc = [] postproc = []
swresample = [] swresample = []
swscale = [] swscale = []

View File

@ -20,7 +20,6 @@ In addition to feature flags declared in `Cargo.toml`, this crate performs vario
- "ffmpeg_6_0" - "ffmpeg_6_0"
- "ffmpeg_6_1" - "ffmpeg_6_1"
- "ffmpeg_7_0" - "ffmpeg_7_0"
- "ffmpeg_7_1"
- `avcodec_version_greater_than_<x>_<y>`, e.g., `avcodec_version_greater_than_58_90`. The name should be self-explanatory. - `avcodec_version_greater_than_<x>_<y>`, e.g., `avcodec_version_greater_than_58_90`. The name should be self-explanatory.

View File

@ -22,7 +22,6 @@ struct Library {
optional: bool, optional: bool,
features: &'static [AVFeature], features: &'static [AVFeature],
headers: &'static [AVHeader], headers: &'static [AVHeader],
min_major_version: u64,
} }
impl Library { impl Library {
@ -30,14 +29,12 @@ impl Library {
name: &'static str, name: &'static str,
features: &'static [AVFeature], features: &'static [AVFeature],
headers: &'static [AVHeader], headers: &'static [AVHeader],
min_version: u64,
) -> Self { ) -> Self {
Self { Self {
name, name,
optional: false, optional: false,
features, features,
headers, headers,
min_major_version: min_version,
} }
} }
@ -45,14 +42,12 @@ impl Library {
name: &'static str, name: &'static str,
features: &'static [AVFeature], features: &'static [AVFeature],
headers: &'static [AVHeader], headers: &'static [AVHeader],
min_version: u64,
) -> Self { ) -> Self {
Self { Self {
name, name,
optional: true, optional: true,
features, features,
headers, headers,
min_major_version: min_version,
} }
} }
@ -66,14 +61,15 @@ impl Library {
} }
static LIBRARIES: &[Library] = &[ static LIBRARIES: &[Library] = &[
Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS, 50), Library::required("avutil", AVUTIL_FEATURES, AVUTIL_HEADERS),
Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS, 50), Library::optional("avcodec", AVCODEC_FEATURES, AVCODEC_HEADERS),
Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS, 50), Library::optional("avformat", AVFORMAT_FEATURES, AVFORMAT_HEADERS),
Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS, 50), Library::optional("avdevice", AVDEVICE_FEATURES, AVDEVICE_HEADERS),
Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS, 0), Library::optional("avfilter", AVFILTER_FEATURES, AVFILTER_HEADERS),
Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS, 0), Library::optional("avresample", AVRESAMPLE_FEATURES, AVRESAMPLE_HEADERS),
Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS, 0), Library::optional("swscale", SWSCALE_FEATURES, SWSCALE_HEADERS),
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS, 50), Library::optional("swresample", SWRESAMPLE_FEATURES, SWRESAMPLE_HEADERS),
Library::optional("postproc", POSTPROC_FEATURES, POSTPROC_HEADERS),
]; ];
#[derive(Debug)] #[derive(Debug)]
@ -220,6 +216,8 @@ static AVFILTER_FEATURES: &[AVFeature] = &[
AVFeature::new("LINK_PUBLIC"), AVFeature::new("LINK_PUBLIC"),
]; ];
static AVRESAMPLE_FEATURES: &[AVFeature] = &[];
static SWSCALE_FEATURES: &[AVFeature] = &[]; static SWSCALE_FEATURES: &[AVFeature] = &[];
static SWRESAMPLE_FEATURES: &[AVFeature] = &[]; static SWRESAMPLE_FEATURES: &[AVFeature] = &[];
@ -304,6 +302,7 @@ static AVFILTER_HEADERS: &[AVHeader] = &[
AVHeader::new("buffersrc.h"), AVHeader::new("buffersrc.h"),
AVHeader::new("avfilter.h"), AVHeader::new("avfilter.h"),
]; ];
static AVRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("avresample.h")];
static SWSCALE_HEADERS: &[AVHeader] = &[AVHeader::new("swscale.h")]; static SWSCALE_HEADERS: &[AVHeader] = &[AVHeader::new("swscale.h")];
static SWRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("swresample.h")]; static SWRESAMPLE_HEADERS: &[AVHeader] = &[AVHeader::new("swresample.h")];
static POSTPROC_HEADERS: &[AVHeader] = &[AVHeader::new("postprocess.h")]; static POSTPROC_HEADERS: &[AVHeader] = &[AVHeader::new("postprocess.h")];
@ -563,8 +562,14 @@ fn build(out_dir: &Path, ffmpeg_version: &str) -> io::Result<PathBuf> {
// the binary using ffmpeg-sys cannot be redistributed // the binary using ffmpeg-sys cannot be redistributed
configure.switch("BUILD_LICENSE_NONFREE", "nonfree"); configure.switch("BUILD_LICENSE_NONFREE", "nonfree");
let ffmpeg_major_version: u32 = get_major_version(ffmpeg_version);
// configure building libraries based on features // configure building libraries based on features
for lib in LIBRARIES.iter().filter(|lib| lib.optional) { for lib in LIBRARIES
.iter()
.filter(|lib| lib.optional)
.filter(|lib| !(lib.name == "avresample" && ffmpeg_major_version >= 5))
{
configure.switch(&lib.name.to_uppercase(), lib.name); configure.switch(&lib.name.to_uppercase(), lib.name);
} }
@ -774,22 +779,22 @@ fn check_features(include_paths: &[PathBuf]) {
} }
} }
for lib in enabled_libraries() { let version_check_info = [("avcodec", 57, 62, 0, 101)];
let ver = if let Some(v) = versions.get(&lib.name) { for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in
v &version_check_info
} else { {
continue; let libversion = *versions
}; .get(lib)
for major in lib.min_major_version..=(*ver).0 { .expect("Unable to find the version for lib{lib}");
for minor in 0..=135 {
if *ver >= (major, minor) { for version_major in begin_version_major..end_version_major {
for version_minor in begin_version_minor..end_version_minor {
if libversion >= (version_major, version_minor) {
println!( println!(
r#"cargo:rustc-cfg=feature="{}_version_greater_than_{major}_{minor}""#, r#"cargo:rustc-cfg=feature="{lib}_version_greater_than_{version_major}_{version_minor}""#
lib.name,
); );
println!( println!(
r#"cargo:{}_version_greater_than_{major}_{minor}=true"#, r#"cargo:{lib}_version_greater_than_{version_major}_{version_minor}=true"#
lib.name,
); );
} }
} }
@ -966,6 +971,7 @@ fn main() {
.allowlist_file(r#".*[/\\]libavformat[/\\].*"#) .allowlist_file(r#".*[/\\]libavformat[/\\].*"#)
.allowlist_file(r#".*[/\\]libavdevice[/\\].*"#) .allowlist_file(r#".*[/\\]libavdevice[/\\].*"#)
.allowlist_file(r#".*[/\\]libavfilter[/\\].*"#) .allowlist_file(r#".*[/\\]libavfilter[/\\].*"#)
.allowlist_file(r#".*[/\\]libavresample[/\\].*"#)
.allowlist_file(r#".*[/\\]libswscale[/\\].*"#) .allowlist_file(r#".*[/\\]libswscale[/\\].*"#)
.allowlist_file(r#".*[/\\]libswresample[/\\].*"#) .allowlist_file(r#".*[/\\]libswresample[/\\].*"#)
.allowlist_file(r#".*[/\\]libpostproc[/\\].*"#) .allowlist_file(r#".*[/\\]libpostproc[/\\].*"#)

215
src/codec/audio.rs Normal file
View File

@ -0,0 +1,215 @@
use std::ops::Deref;
use super::codec::Codec;
use crate::ffi::*;
use crate::format;
#[cfg(not(feature = "ffmpeg_7_0"))]
use crate::ChannelLayoutMask;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Audio {
codec: Codec,
}
impl Audio {
pub unsafe fn new(codec: Codec) -> Audio {
Audio { codec }
}
}
impl Audio {
pub fn rates(&self) -> Option<RateIter> {
unsafe {
if (*self.as_ptr()).supported_samplerates.is_null() {
None
} else {
Some(RateIter::new((*self.codec.as_ptr()).supported_samplerates))
}
}
}
pub fn formats(&self) -> Option<FormatIter> {
unsafe {
if (*self.codec.as_ptr()).sample_fmts.is_null() {
None
} else {
Some(FormatIter::new((*self.codec.as_ptr()).sample_fmts))
}
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub fn channel_layouts(&self) -> Option<ChannelLayoutMaskIter> {
unsafe {
if (*self.codec.as_ptr()).channel_layouts.is_null() {
None
} else {
Some(ChannelLayoutMaskIter::new(
(*self.codec.as_ptr()).channel_layouts,
))
}
}
}
#[cfg(feature = "ffmpeg_5_1")]
pub fn ch_layouts(&self) -> Option<ChannelLayoutIter> {
unsafe { ChannelLayoutIter::from_raw((*self.codec.as_ptr()).ch_layouts) }
}
}
impl Deref for Audio {
type Target = Codec;
fn deref(&self) -> &Self::Target {
&self.codec
}
}
pub struct RateIter {
ptr: *const i32,
}
impl RateIter {
pub fn new(ptr: *const i32) -> Self {
RateIter { ptr }
}
}
impl Iterator for RateIter {
type Item = i32;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == 0 {
return None;
}
let rate = *self.ptr;
self.ptr = self.ptr.offset(1);
Some(rate)
}
}
}
pub struct FormatIter {
ptr: *const AVSampleFormat,
}
impl FormatIter {
pub fn new(ptr: *const AVSampleFormat) -> Self {
FormatIter { ptr }
}
}
impl Iterator for FormatIter {
type Item = format::Sample;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == AVSampleFormat::AV_SAMPLE_FMT_NONE {
return None;
}
let format = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(format)
}
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub struct ChannelLayoutMaskIter {
ptr: *const u64,
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl ChannelLayoutMaskIter {
pub fn new(ptr: *const u64) -> Self {
ChannelLayoutMaskIter { ptr }
}
pub fn best(self, max: i32) -> ChannelLayoutMask {
self.fold(ChannelLayoutMask::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl Iterator for ChannelLayoutMaskIter {
type Item = ChannelLayoutMask;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == 0 {
return None;
}
let layout = ChannelLayoutMask::from_bits_truncate(*self.ptr);
self.ptr = self.ptr.offset(1);
Some(layout)
}
}
}
#[cfg(feature = "ffmpeg_5_1")]
pub use ch_layout::ChannelLayoutIter;
#[cfg(feature = "ffmpeg_5_1")]
mod ch_layout {
use super::*;
use crate::ChannelLayout;
pub struct ChannelLayoutIter<'a> {
next: &'a AVChannelLayout,
}
impl<'a> ChannelLayoutIter<'a> {
pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option<Self> {
ptr.as_ref().map(|next| Self { next })
}
}
impl<'a> ChannelLayoutIter<'a> {
pub fn best(self, max: u32) -> ChannelLayout<'a> {
self.fold(ChannelLayout::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
impl<'a> Iterator for ChannelLayoutIter<'a> {
type Item = ChannelLayout<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let curr = self.next;
if *curr == zeroed_layout() {
return None;
}
// SAFETY: We trust that there is always an initialized layout up until
// the zeroed-out AVChannelLayout, which signals the end of iteration.
self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap();
Some(ChannelLayout::from(curr))
}
}
}
// TODO: Remove this with a const variable when zeroed() is const (1.75.0)
unsafe fn zeroed_layout() -> AVChannelLayout {
std::mem::zeroed()
}
}

View File

@ -1,58 +1,30 @@
use std::marker::PhantomData; use super::{Audio, Capabilities, Id, Profile, Video};
use std::ptr::NonNull;
use super::config::{
FrameRateIter, PixelFormatIter, SampleFormatIter, SampleRateIter, TerminatedPtrIter,
};
use super::descriptor::{CodecDescriptor, CodecDescriptorIter};
use super::profile::ProfileIter;
use super::{Capabilities, Id};
use crate::ffi::*; use crate::ffi::*;
use crate::{media, utils}; use crate::{media, utils, Error};
#[cfg(feature = "ffmpeg_7_1")]
use crate::codec::config::{ColorRangeIter, ColorSpaceIter, Supported};
pub fn list_descriptors() -> CodecDescriptorIter {
CodecDescriptorIter::new()
}
pub type Audio = Codec<AudioType>;
pub type Video = Codec<VideoType>;
#[derive(PartialEq, Eq, Copy, Clone)] #[derive(PartialEq, Eq, Copy, Clone)]
pub struct Codec<Type = UnknownType> { pub struct Codec {
ptr: NonNull<AVCodec>, ptr: *mut AVCodec,
_marker: PhantomData<Type>,
} }
#[derive(PartialEq, Eq, Copy, Clone)] unsafe impl Send for Codec {}
pub struct UnknownType; unsafe impl Sync for Codec {}
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct AudioType;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct VideoType;
unsafe impl<T> Send for Codec<T> {} impl Codec {
unsafe impl<T> Sync for Codec<T> {} pub unsafe fn wrap(ptr: *mut AVCodec) -> Self {
Codec { ptr }
}
impl Codec<UnknownType> { pub unsafe fn as_ptr(&self) -> *const AVCodec {
/// Create a new reference to a codec from a raw pointer. self.ptr as *const _
/// }
/// Returns `None` if `ptr` is null.
pub unsafe fn from_raw(ptr: *const AVCodec) -> Option<Self> { pub unsafe fn as_mut_ptr(&mut self) -> *mut AVCodec {
NonNull::new(ptr as *mut _).map(|ptr| Self { self.ptr
ptr,
_marker: PhantomData,
})
} }
} }
impl<T> Codec<T> { impl Codec {
pub fn as_ptr(&self) -> *const AVCodec {
self.ptr.as_ptr()
}
pub fn is_encoder(&self) -> bool { pub fn is_encoder(&self) -> bool {
unsafe { av_codec_is_encoder(self.as_ptr()) != 0 } unsafe { av_codec_is_encoder(self.as_ptr()) != 0 }
} }
@ -81,14 +53,13 @@ impl<T> Codec<T> {
self.medium() == media::Type::Video self.medium() == media::Type::Video
} }
pub fn video(self) -> Option<Video> { pub fn video(self) -> Result<Video, Error> {
unsafe {
if self.medium() == media::Type::Video { if self.medium() == media::Type::Video {
Some(Codec { Ok(Video::new(self))
ptr: self.ptr,
_marker: PhantomData,
})
} else { } else {
None Err(Error::InvalidData)
}
} }
} }
@ -96,14 +67,13 @@ impl<T> Codec<T> {
self.medium() == media::Type::Audio self.medium() == media::Type::Audio
} }
pub fn audio(self) -> Option<Audio> { pub fn audio(self) -> Result<Audio, Error> {
unsafe {
if self.medium() == media::Type::Audio { if self.medium() == media::Type::Audio {
Some(Codec { Ok(Audio::new(self))
ptr: self.ptr,
_marker: PhantomData,
})
} else { } else {
None Err(Error::InvalidData)
}
} }
} }
@ -124,217 +94,32 @@ impl<T> Codec<T> {
} }
} }
} }
pub fn descriptor(self) -> Option<CodecDescriptor> {
unsafe {
let ptr = avcodec_descriptor_get(self.id().into());
CodecDescriptor::from_raw(ptr)
} }
pub struct ProfileIter {
id: Id,
ptr: *const AVProfile,
}
impl ProfileIter {
pub fn new(id: Id, ptr: *const AVProfile) -> Self {
ProfileIter { id, ptr }
} }
} }
impl Codec<AudioType> { impl Iterator for ProfileIter {
/// Checks if the given sample rate is supported by this audio codec. type Item = Profile;
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_rate(self, rate: libc::c_int) -> bool {
self.supported_rates().supports(rate)
}
/// Returns a [`Supported`] representing the supported sample rates.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_rates(self) -> Supported<SampleRateIter<'static>> {
use super::config::supported_sample_rates;
supported_sample_rates(self, None).expect("audio codec returns supported sample rates")
}
pub fn rates(&self) -> Option<SampleRateIter> {
unsafe { SampleRateIter::from_raw((*self.as_ptr()).supported_samplerates) }
}
/// Checks if the given sample format is supported by this audio codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_format(self, format: crate::format::Sample) -> bool {
self.supported_formats().supports(format)
}
/// Returns a [`Supported`] representing the supported sample formats.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_formats(self) -> Supported<SampleFormatIter<'static>> {
use super::config::supported_sample_formats;
supported_sample_formats(self, None).expect("audio codec returns supported sample formats")
}
pub fn formats(&self) -> Option<SampleFormatIter> {
unsafe { SampleFormatIter::from_raw((*self.as_ptr()).sample_fmts) }
}
#[cfg(not(feature = "ffmpeg_7_0"))]
pub fn channel_layouts(&self) -> Option<ChannelLayoutMaskIter> {
unsafe { ChannelLayoutMaskIter::from_raw((*self.as_ptr()).channel_layouts) }
}
#[cfg(feature = "ffmpeg_5_1")]
pub fn ch_layouts(&self) -> Option<ChannelLayoutIter> {
unsafe { ChannelLayoutIter::from_raw((*self.as_ptr()).ch_layouts) }
}
}
impl Codec<VideoType> {
/// Checks if the given frame rate is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_rate(self, rate: crate::Rational) -> bool {
self.supported_rates().supports(rate)
}
/// Returns a [`Supported`] representing the supported frame rates.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_rates(self) -> Supported<FrameRateIter<'static>> {
use crate::codec::config::supported_frame_rates;
supported_frame_rates(self, None).expect("video codec returns supported frame rates")
}
pub fn rates(&self) -> Option<FrameRateIter> {
unsafe { FrameRateIter::from_raw((*self.as_ptr()).supported_framerates) }
}
/// Checks if the given pixel format is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_format(self, format: crate::format::Pixel) -> bool {
self.supported_formats().supports(format)
}
/// Returns a [`Supported`] representing the supported pixel formats.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_formats(self) -> Supported<PixelFormatIter<'static>> {
use crate::codec::config::supported_pixel_formats;
supported_pixel_formats(self, None).expect("video codec returns supported pixel formats")
}
pub fn formats(&self) -> Option<PixelFormatIter> {
unsafe { PixelFormatIter::from_raw((*self.as_ptr()).pix_fmts) }
}
/// Checks if the given color space is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_color_space(self, space: crate::color::Space) -> bool {
self.supported_color_spaces().supports(space)
}
/// Returns a [`Supported`] representing the supported color spaces.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_color_spaces(self) -> Supported<ColorSpaceIter<'static>> {
use crate::codec::config::supported_color_spaces;
supported_color_spaces(self, None).expect("video codec returns supported color spaces")
}
/// Checks if the given color range is supported by this video codec.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supports_color_range(self, range: crate::color::Range) -> bool {
self.supported_color_ranges().supports(range)
}
/// Returns a [`Supported`] representing the supported color ranges.
#[cfg(feature = "ffmpeg_7_1")]
pub fn supported_color_ranges(self) -> Supported<ColorRangeIter<'static>> {
use crate::codec::config::supported_color_ranges;
supported_color_ranges(self, None).expect("video codec returns supported color ranges")
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
use crate::ChannelLayoutMask;
#[cfg(not(feature = "ffmpeg_7_0"))]
pub struct ChannelLayoutMaskIter {
ptr: NonNull<u64>,
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl ChannelLayoutMaskIter {
pub fn from_raw(ptr: *const u64) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
pub fn best(self, max: i32) -> ChannelLayoutMask {
self.fold(ChannelLayoutMask::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
#[cfg(not(feature = "ffmpeg_7_0"))]
impl Iterator for ChannelLayoutMaskIter {
type Item = ChannelLayoutMask;
fn next(&mut self) -> Option<<Self as Iterator>::Item> { fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe { unsafe {
let ptr = self.ptr.as_ptr(); if (*self.ptr).profile == FF_PROFILE_UNKNOWN {
if *ptr == 0 {
return None; return None;
} }
let layout = ChannelLayoutMask::from_bits_truncate(*ptr); let profile = Profile::from((self.id, (*self.ptr).profile));
self.ptr = NonNull::new_unchecked(ptr.add(1)); self.ptr = self.ptr.offset(1);
Some(layout) Some(profile)
} }
} }
} }
#[cfg(feature = "ffmpeg_5_1")]
pub use ch_layout::ChannelLayoutIter;
#[cfg(feature = "ffmpeg_5_1")]
mod ch_layout {
use super::*;
use crate::ChannelLayout;
pub struct ChannelLayoutIter<'a> {
next: &'a AVChannelLayout,
}
impl<'a> ChannelLayoutIter<'a> {
pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option<Self> {
ptr.as_ref().map(|next| Self { next })
}
}
impl<'a> ChannelLayoutIter<'a> {
pub fn best(self, max: u32) -> ChannelLayout<'a> {
self.fold(ChannelLayout::MONO, |acc, cur| {
if cur.channels() > acc.channels() && cur.channels() <= max {
cur
} else {
acc
}
})
}
}
impl<'a> Iterator for ChannelLayoutIter<'a> {
type Item = ChannelLayout<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let curr = self.next;
if *curr == zeroed_layout() {
return None;
}
// SAFETY: We trust that there is always an initialized layout up until
// the zeroed-out AVChannelLayout, which signals the end of iteration.
self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap();
Some(ChannelLayout::from(curr))
}
}
}
// TODO: Remove this with a const variable when zeroed() is const (1.75.0)
unsafe fn zeroed_layout() -> AVChannelLayout {
std::mem::zeroed()
}
}

View File

@ -1,397 +0,0 @@
use std::ptr::NonNull;
#[cfg(feature = "ffmpeg_7_1")]
use crate::codec::Context;
#[cfg(feature = "ffmpeg_7_1")]
use crate::ffi::*;
#[cfg(feature = "ffmpeg_7_1")]
use crate::Codec;
#[cfg(feature = "ffmpeg_7_1")]
use crate::Error;
#[cfg(feature = "ffmpeg_7_1")]
#[derive(Debug, Clone)]
pub enum Supported<I> {
All,
Specific(I),
}
#[cfg(feature = "ffmpeg_7_1")]
impl<T, I> Supported<I>
where
T: PartialEq,
I: Iterator<Item = T>,
{
/// Check if all possible configuration values are supported.
///
/// # Example
///
/// ```
/// use ffmpeg_the_third::codec::{encoder, Id};
///
/// let codec = encoder::find(Id::VP9)
/// .expect("Can find a VP9 encoder")
/// .video()
/// .unwrap();
///
/// let supported = codec.supported_rates();
/// assert!(supported.all())
/// ```
pub fn all(&self) -> bool {
matches!(self, Supported::All)
}
/// Check if a specific configuration value is supported.
///
/// # Example
///
/// ```
/// use ffmpeg_the_third::codec::{decoder, Id};
/// use ffmpeg_the_third::format::sample::{Sample, Type};
///
/// let codec = decoder::find(Id::MP3)
/// .expect("Can find an MP3 decoder")
/// .audio()
/// .unwrap();
///
/// let supported = codec.supported_formats();
/// assert!(supported.supports(Sample::F32(Type::Planar)));
/// ```
pub fn supports(self, t: T) -> bool {
match self {
Supported::All => true,
Supported::Specific(mut iter) => iter.any(|elem| elem == t),
}
}
}
#[cfg(feature = "ffmpeg_7_1")]
fn supported<WrapperType, AVType, CodecType, I>(
codec: Codec<CodecType>,
ctx: Option<&Context>,
cfg: AVCodecConfig,
) -> Result<Supported<I>, Error>
where
I: TerminatedPtrIter<AVType, WrapperType>,
AVType: Into<WrapperType>,
{
let mut out_ptr: *const libc::c_void = std::ptr::null();
unsafe {
let avctx = ctx.map_or(std::ptr::null(), |ctx| ctx.as_ptr());
let ret = avcodec_get_supported_config(
avctx,
codec.as_ptr(),
cfg,
0, // flags: unused as of 7.1, set to zero
&mut out_ptr,
std::ptr::null_mut(), // out_num_configs: optional, we don't support it currently
);
if ret < 0 {
return Err(Error::from(ret));
}
match NonNull::new(out_ptr as *mut _) {
// non-nullptr -> Specific list of values is supported.
Some(ptr) => Ok(Supported::Specific(I::from_ptr(ptr))),
// nullptr -> Everything is supported
None => Ok(Supported::All),
}
}
}
/// Pointer-based iterator, stepped through via pointer arithmetic and ended
/// when a dereferenced pointer equals a terminator value.
pub(crate) trait TerminatedPtrIter<AVType, WrapperType>:
Sized + Iterator<Item = WrapperType>
{
/// Create a new iterator from a non-null pointer to any value in the iteration.
///
/// # Safety
///
/// `ptr` and all following pointers must be dereferenceable until the terminator is reached.
unsafe fn from_ptr(ptr: NonNull<AVType>) -> Self;
/// Create a new iterator from a pointer to any value in the iteration.
///
/// Returns `None` if `ptr` is null. See also [`from_ptr`][TerminatedPtrIter::from_ptr].
///
/// # Safety
///
/// See [`from_ptr`][TerminatedPtrIter::from_ptr].
unsafe fn from_raw(ptr: *const AVType) -> Option<Self> {
unsafe { NonNull::new(ptr as *mut _).map(|ptr| Self::from_ptr(ptr)) }
}
}
macro_rules! impl_config_iter {
(
$fn_name:ident,
$codec_cfg:expr,
$iter:ident,
$ty:ty,
$av_ty:ty,
$terminator:expr
) => {
impl_config_iter_fn!($fn_name, $iter, $codec_cfg);
impl_config_iter_struct!($iter, $av_ty);
impl_config_iter_traits!($iter, $ty, $av_ty, $terminator);
};
}
macro_rules! impl_config_iter_struct {
($iter:ident, $av_ty:ty) => {
#[derive(Debug, Clone)]
pub struct $iter<'a> {
next: std::ptr::NonNull<$av_ty>,
_marker: std::marker::PhantomData<&'a $av_ty>,
}
};
}
macro_rules! impl_config_iter_fn {
($fn_name:ident, $iter:ident, $codec_cfg:expr) => {
/// Low-level function interacting with the FFmpeg API via
/// `avcodec_get_supported_config()`. Consider using one of the convenience methods
/// on the codecs or codec contexts instead.
#[cfg(feature = "ffmpeg_7_1")]
pub fn $fn_name<T>(
codec: Codec<T>,
ctx: Option<&Context>,
) -> Result<Supported<$iter>, Error> {
supported(codec, ctx, $codec_cfg)
}
};
}
macro_rules! impl_config_iter_traits {
($iter:ident, $ty:ty, $av_ty:ty, $terminator:expr) => {
impl<'a> TerminatedPtrIter<$av_ty, $ty> for $iter<'a> {
unsafe fn from_ptr(ptr: std::ptr::NonNull<$av_ty>) -> Self {
Self {
next: ptr,
_marker: std::marker::PhantomData,
}
}
}
// We make sure that this is true by not incrementing self.ptr after the
// terminator has been reached.
impl<'a> std::iter::FusedIterator for $iter<'a> {}
// TODO: Maybe add ExactSizeIterator? This would require using the out_num_configs
// parameter and storing it inside $iter. Not sure it's too important unless
// many people want to use .collect() or something else that benefits from
// ExactSizeIterator.
impl<'a> Iterator for $iter<'a> {
type Item = $ty;
fn next(&mut self) -> Option<Self::Item> {
// SAFETY: The FFmpeg API guarantees that the pointer is safe to deref and
// increment until the terminator is reached.
unsafe {
let curr = self.next.as_ptr();
if *curr == $terminator {
return None;
}
// TODO: Replace with the following if MSRV >= 1.80:
// self.next = NonNull::from(self.next).add(1).as_ref();
self.next = std::ptr::NonNull::new_unchecked(curr.add(1));
Some((*curr).into())
}
}
}
};
}
impl_config_iter!(
supported_pixel_formats,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_PIX_FORMAT,
PixelFormatIter,
crate::format::Pixel,
crate::ffi::AVPixelFormat,
crate::ffi::AVPixelFormat::AV_PIX_FMT_NONE
);
impl_config_iter!(
supported_frame_rates,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_FRAME_RATE,
FrameRateIter,
crate::Rational,
crate::ffi::AVRational,
crate::ffi::AVRational { num: 0, den: 0 }
);
impl_config_iter!(
supported_sample_rates,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_RATE,
SampleRateIter,
libc::c_int,
libc::c_int,
0 as libc::c_int
);
impl_config_iter!(
supported_sample_formats,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_FORMAT,
SampleFormatIter,
crate::format::Sample,
crate::ffi::AVSampleFormat,
crate::ffi::AVSampleFormat::AV_SAMPLE_FMT_NONE
);
#[cfg(feature = "ffmpeg_7_1")]
impl_config_iter!(
supported_color_ranges,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_RANGE,
ColorRangeIter,
crate::color::Range,
crate::ffi::AVColorRange,
crate::ffi::AVColorRange::AVCOL_RANGE_UNSPECIFIED
);
#[cfg(feature = "ffmpeg_7_1")]
impl_config_iter!(
supported_color_spaces,
crate::ffi::AVCodecConfig::AV_CODEC_CONFIG_COLOR_SPACE,
ColorSpaceIter,
crate::color::Space,
crate::ffi::AVColorSpace,
crate::ffi::AVColorSpace::AVCOL_SPC_UNSPECIFIED
);
#[cfg(test)]
#[cfg(feature = "ffmpeg_7_1")]
mod test {
use super::*;
use crate::codec::{decoder, encoder, Compliance, Id};
use crate::color::Range;
use crate::format::Pixel;
use crate::Rational;
// These tests can fail if the FFmpeg build does not contain the required de/encoder.
// TODO: Check if tests can be hidden behind feature flags.
#[test]
fn audio_decoder() {
let codec = decoder::find(Id::MP3).expect("can find mp3 decoder");
// Audio decoder does not have color ranges
assert!(supported_color_ranges(codec, None).is_err());
let format_iter = match supported_sample_formats(codec, None) {
Ok(Supported::Specific(f)) => f,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for format in format_iter {
println!("format: {format:#?}");
}
}
#[test]
fn audio_encoder() {
let codec = encoder::find(Id::OPUS).expect("can find opus encoder");
// looks like every codec returns Supported::All for color space.
// might change in a future FFmpeg release
assert!(matches!(
supported_color_spaces(codec, None),
Ok(Supported::All)
));
let format_iter = match supported_sample_formats(codec, None) {
Ok(Supported::Specific(f)) => f,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for format in format_iter {
println!("format: {format:#?}");
}
}
#[test]
fn video_decoder() {
let codec = decoder::find(Id::H264).expect("can find H264 decoder");
assert!(supported_sample_rates(codec, None).is_err());
assert!(matches!(
supported_color_spaces(codec, None),
Ok(Supported::All)
));
}
#[test]
fn video_encoder() {
let codec = encoder::find(Id::VP9).expect("can find VP9 encoder");
let color_ranges = match supported_color_ranges(codec, None) {
Ok(Supported::Specific(c)) => c,
sup => panic!("Should be Supported::Specific, got {sup:#?}"),
};
for range in color_ranges {
println!("{range:#?}");
}
assert!(matches!(
supported_pixel_formats(codec, None),
Ok(Supported::Specific(_))
));
assert!(matches!(
supported_frame_rates(codec, None),
Ok(Supported::All)
));
}
#[test]
fn supports() {
let codec = encoder::find(Id::FFV1).expect("can find FFV1 encoder");
assert!(supported_color_ranges(codec, None)
.expect("can check color range support")
.supports(Range::MPEG));
assert!(!supported_pixel_formats(codec, None)
.expect("can check color range support")
.supports(Pixel::GRAY16));
assert!(supported_frame_rates(codec, None)
.expect("can check frame rate support")
.supports(Rational(123, 456)));
supported_sample_formats(codec, None)
.expect_err("can NOT check sample format support (video codec)");
}
#[test]
fn with_context() {
let codec = encoder::find(Id::MJPEG).expect("can find MJPEG encoder");
let mut ctx = unsafe {
let avctx = crate::ffi::avcodec_alloc_context3(codec.as_ptr());
crate::codec::Context::wrap(avctx, None)
};
ctx.compliance(Compliance::Strict);
assert!(!supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
.expect("can check color range support")
.supports(Range::MPEG));
ctx.compliance(Compliance::Unofficial);
// Note that we check for NOT supported above, and YES supported here
// MJPEG encoder only supports MPEG color range if compliance is
// Unofficial or lower (less strict)
assert!(supported_color_ranges(ctx.codec().unwrap(), Some(&ctx))
.expect("can check color range support")
.supports(Range::MPEG));
}
}

View File

@ -62,7 +62,13 @@ impl Context {
} }
pub fn codec(&self) -> Option<Codec> { pub fn codec(&self) -> Option<Codec> {
unsafe { Codec::from_raw((*self.as_ptr()).codec) } unsafe {
if (*self.as_ptr()).codec.is_null() {
None
} else {
Some(Codec::wrap((*self.as_ptr()).codec as *mut _))
}
}
} }
pub fn medium(&self) -> media::Type { pub fn medium(&self) -> media::Type {

View File

@ -18,7 +18,7 @@ impl Decoder {
} }
} }
pub fn open_as<T, D: traits::Decoder<T>>(mut self, codec: D) -> Result<Opened, Error> { pub fn open_as<D: traits::Decoder>(mut self, codec: D) -> Result<Opened, Error> {
unsafe { unsafe {
if let Some(codec) = codec.decoder() { if let Some(codec) = codec.decoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) { match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -31,7 +31,7 @@ impl Decoder {
} }
} }
pub fn open_as_with<T, D: traits::Decoder<T>>( pub fn open_as_with<D: traits::Decoder>(
mut self, mut self,
codec: D, codec: D,
options: Dictionary, options: Dictionary,

View File

@ -34,16 +34,25 @@ pub fn new() -> Decoder {
pub fn find(id: Id) -> Option<Codec> { pub fn find(id: Id) -> Option<Codec> {
unsafe { unsafe {
let ptr = avcodec_find_decoder(id.into()); let ptr = avcodec_find_decoder(id.into()) as *mut AVCodec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
pub fn find_by_name(name: &str) -> Option<Codec> { pub fn find_by_name(name: &str) -> Option<Codec> {
unsafe { unsafe {
let name = CString::new(name).unwrap(); let name = CString::new(name).unwrap();
let ptr = avcodec_find_decoder_by_name(name.as_ptr()); let ptr = avcodec_find_decoder_by_name(name.as_ptr()) as *mut AVCodec;
Codec::from_raw(ptr) if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }

View File

@ -1,158 +0,0 @@
use std::ffi::CStr;
use std::ptr::NonNull;
use std::str::from_utf8_unchecked;
use crate::ffi::*;
use crate::media;
use super::profile::ProfileIter;
use super::{CodecProperties, Id};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodecDescriptor {
ptr: NonNull<AVCodecDescriptor>,
}
impl CodecDescriptor {
pub unsafe fn from_raw(ptr: *const AVCodecDescriptor) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
pub fn as_ptr(self) -> *const AVCodecDescriptor {
self.ptr.as_ptr()
}
pub fn id(self) -> Id {
unsafe { (*self.as_ptr()).id.into() }
}
pub fn kind(self) -> media::Type {
unsafe { (*self.as_ptr()).type_.into() }
}
pub fn name(self) -> &'static str {
unsafe { from_utf8_unchecked(CStr::from_ptr((*self.as_ptr()).name).to_bytes()) }
}
pub fn description(self) -> Option<&'static str> {
unsafe {
let long_name = (*self.as_ptr()).long_name;
if long_name.is_null() {
None
} else {
Some(from_utf8_unchecked(CStr::from_ptr(long_name).to_bytes()))
}
}
}
pub fn props(self) -> CodecProperties {
unsafe { CodecProperties::from_bits_truncate((*self.as_ptr()).props) }
}
pub fn mime_types(self) -> Option<MimeTypeIter> {
unsafe { MimeTypeIter::from_raw((*self.as_ptr()).mime_types) }
}
pub fn profiles(self) -> Option<ProfileIter> {
unsafe {
if (*self.as_ptr()).profiles.is_null() {
None
} else {
Some(ProfileIter::new(self.id(), (*self.as_ptr()).profiles))
}
}
}
}
pub struct CodecDescriptorIter {
ptr: *const AVCodecDescriptor,
}
impl CodecDescriptorIter {
pub fn new() -> Self {
Self {
ptr: std::ptr::null(),
}
}
}
impl Default for CodecDescriptorIter {
fn default() -> Self {
Self::new()
}
}
impl Iterator for CodecDescriptorIter {
type Item = CodecDescriptor;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = avcodec_descriptor_next(self.ptr);
if let Some(desc) = CodecDescriptor::from_raw(next) {
self.ptr = next;
Some(desc)
} else {
None
}
}
}
}
pub struct MimeTypeIter {
ptr: NonNull<*const libc::c_char>,
}
impl MimeTypeIter {
pub unsafe fn from_raw(ptr: *const *const libc::c_char) -> Option<Self> {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr })
}
}
impl Iterator for MimeTypeIter {
type Item = &'static str;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let next = *self.ptr.as_ptr();
if next.is_null() {
return None;
}
self.ptr = NonNull::new_unchecked(self.ptr.as_ptr().add(1));
Some(from_utf8_unchecked(CStr::from_ptr(next).to_bytes()))
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::decoder::find;
#[test]
fn descriptor() {
let targa = find(Id::TARGA).expect("can find targa decoder");
let desc = targa.descriptor().expect("targa has descriptor");
assert_eq!(desc.id(), Id::TARGA);
assert_eq!(desc.kind(), media::Type::Video);
assert_eq!(desc.name(), "targa");
// --enable-small will remove all `long_name`s. So this can either be null/None
// or the correct description
assert!(matches!(
desc.description(),
None | Some("Truevision Targa image")
));
let props = desc.props();
assert!(
props.contains(CodecProperties::INTRA_ONLY)
&& props.contains(CodecProperties::LOSSLESS)
);
let mut mime_types = desc.mime_types().expect("has mime types");
assert_eq!(mime_types.next(), Some("image/x-targa"));
assert_eq!(mime_types.next(), Some("image/x-tga"));
assert_eq!(mime_types.next(), None);
}
}

View File

@ -30,7 +30,7 @@ impl Audio {
} }
} }
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe { unsafe {
if let Some(codec) = codec.encoder() { if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) { match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -57,7 +57,7 @@ impl Audio {
} }
} }
pub fn open_as_with<T, E: traits::Encoder<T>>( pub fn open_as_with<E: traits::Encoder>(
mut self, mut self,
codec: E, codec: E,
options: Dictionary, options: Dictionary,

View File

@ -37,15 +37,25 @@ pub fn new() -> Encoder {
pub fn find(id: Id) -> Option<Codec> { pub fn find(id: Id) -> Option<Codec> {
unsafe { unsafe {
let ptr = avcodec_find_encoder(id.into()); let ptr = avcodec_find_encoder(id.into()) as *mut AVCodec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
pub fn find_by_name(name: &str) -> Option<Codec> { pub fn find_by_name(name: &str) -> Option<Codec> {
unsafe { unsafe {
let name = CString::new(name).unwrap(); let name = CString::new(name).unwrap();
let ptr = avcodec_find_encoder_by_name(name.as_ptr()); let ptr = avcodec_find_encoder_by_name(name.as_ptr()) as *mut AVCodec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }

View File

@ -20,7 +20,7 @@ impl Subtitle {
} }
} }
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe { unsafe {
if let Some(codec) = codec.encoder() { if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) { match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -33,7 +33,7 @@ impl Subtitle {
} }
} }
pub fn open_as_with<T, E: traits::Encoder<T>>( pub fn open_as_with<E: traits::Encoder>(
mut self, mut self,
codec: E, codec: E,
options: Dictionary, options: Dictionary,

View File

@ -27,7 +27,7 @@ impl Video {
} }
#[inline] #[inline]
pub fn open_as<T, E: traits::Encoder<T>>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> {
unsafe { unsafe {
if let Some(codec) = codec.encoder() { if let Some(codec) = codec.encoder() {
match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) { match avcodec_open2(self.as_mut_ptr(), codec.as_ptr(), ptr::null_mut()) {
@ -56,7 +56,7 @@ impl Video {
} }
#[inline] #[inline]
pub fn open_as_with<T, E: traits::Encoder<T>>( pub fn open_as_with<E: traits::Encoder>(
mut self, mut self,
codec: E, codec: E,
options: Dictionary, options: Dictionary,

View File

@ -11,13 +11,8 @@ pub mod subtitle;
#[cfg(not(feature = "ffmpeg_5_0"))] #[cfg(not(feature = "ffmpeg_5_0"))]
pub mod picture; pub mod picture;
pub mod descriptor;
pub use self::descriptor::CodecDescriptor;
pub mod discard; pub mod discard;
pub mod config;
pub mod context; pub mod context;
pub use self::context::Context; pub use self::context::Context;
@ -25,11 +20,16 @@ pub mod capabilities;
pub use self::capabilities::Capabilities; pub use self::capabilities::Capabilities;
pub mod codec; pub mod codec;
pub use self::codec::{Audio, Codec, Video};
pub mod parameters; pub mod parameters;
pub use self::parameters::Parameters; pub use self::parameters::Parameters;
pub mod video;
pub use self::video::Video;
pub mod audio;
pub use self::audio::Audio;
pub mod audio_service; pub mod audio_service;
pub mod field_order; pub mod field_order;
@ -42,9 +42,6 @@ pub use self::debug::Debug;
pub mod profile; pub mod profile;
pub use self::profile::Profile; pub use self::profile::Profile;
pub mod props;
pub use self::props::CodecProperties;
pub mod threading; pub mod threading;
pub mod decoder; pub mod decoder;

View File

@ -387,31 +387,3 @@ impl From<Profile> for c_int {
} }
} }
} }
pub struct ProfileIter {
id: Id,
ptr: *const AVProfile,
}
impl ProfileIter {
pub fn new(id: Id, ptr: *const AVProfile) -> Self {
ProfileIter { id, ptr }
}
}
impl Iterator for ProfileIter {
type Item = Profile;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if (*self.ptr).profile == FF_PROFILE_UNKNOWN {
return None;
}
let profile = Profile::from((self.id, (*self.ptr).profile));
self.ptr = self.ptr.offset(1);
Some(profile)
}
}
}

View File

@ -1,17 +0,0 @@
use crate::ffi::*;
use libc::c_int;
bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct CodecProperties: c_int {
const INTRA_ONLY = AV_CODEC_PROP_INTRA_ONLY;
const LOSSY = AV_CODEC_PROP_LOSSY;
const LOSSLESS = AV_CODEC_PROP_LOSSLESS;
const REORDER = AV_CODEC_PROP_REORDER;
#[cfg(feature = "ffmpeg_6_1")]
const FIELDS = AV_CODEC_PROP_FIELDS;
const BITMAP_SUB = AV_CODEC_PROP_BITMAP_SUB;
const TEXT_SUB = AV_CODEC_PROP_TEXT_SUB;
}
}

View File

@ -1,26 +1,25 @@
use super::codec::UnknownType;
use super::{decoder, encoder}; use super::{decoder, encoder};
use crate::codec::Id; use crate::codec::{Audio, Id, Video};
use crate::Codec; use crate::Codec;
pub trait Decoder<T> { pub trait Decoder {
fn decoder(self) -> Option<Codec<T>>; fn decoder(self) -> Option<Codec>;
} }
impl<'a> Decoder<UnknownType> for &'a str { impl<'a> Decoder for &'a str {
fn decoder(self) -> Option<Codec<UnknownType>> { fn decoder(self) -> Option<Codec> {
decoder::find_by_name(self) decoder::find_by_name(self)
} }
} }
impl Decoder<UnknownType> for Id { impl Decoder for Id {
fn decoder(self) -> Option<Codec<UnknownType>> { fn decoder(self) -> Option<Codec> {
decoder::find(self) decoder::find(self)
} }
} }
impl<T> Decoder<T> for Codec<T> { impl Decoder for Codec {
fn decoder(self) -> Option<Codec<T>> { fn decoder(self) -> Option<Codec> {
if self.is_decoder() { if self.is_decoder() {
Some(self) Some(self)
} else { } else {
@ -29,30 +28,50 @@ impl<T> Decoder<T> for Codec<T> {
} }
} }
impl<T> Decoder<T> for Option<Codec<T>> { impl Decoder for Option<Codec> {
fn decoder(self) -> Option<Codec<T>> { fn decoder(self) -> Option<Codec> {
self.and_then(|c| c.decoder()) self.and_then(|c| c.decoder())
} }
} }
pub trait Encoder<T> { impl Decoder for Audio {
fn encoder(self) -> Option<Codec<T>>; fn decoder(self) -> Option<Codec> {
if self.is_decoder() {
Some(*self)
} else {
None
}
}
} }
impl<'a> Encoder<UnknownType> for &'a str { impl Decoder for Video {
fn encoder(self) -> Option<Codec<UnknownType>> { fn decoder(self) -> Option<Codec> {
if self.is_decoder() {
Some(*self)
} else {
None
}
}
}
pub trait Encoder {
fn encoder(self) -> Option<Codec>;
}
impl<'a> Encoder for &'a str {
fn encoder(self) -> Option<Codec> {
encoder::find_by_name(self) encoder::find_by_name(self)
} }
} }
impl Encoder<UnknownType> for Id { impl Encoder for Id {
fn encoder(self) -> Option<Codec<UnknownType>> { fn encoder(self) -> Option<Codec> {
encoder::find(self) encoder::find(self)
} }
} }
impl<T> Encoder<T> for Codec<T> { impl Encoder for Codec {
fn encoder(self) -> Option<Codec<T>> { fn encoder(self) -> Option<Codec> {
if self.is_encoder() { if self.is_encoder() {
Some(self) Some(self)
} else { } else {
@ -61,8 +80,28 @@ impl<T> Encoder<T> for Codec<T> {
} }
} }
impl<T> Encoder<T> for Option<Codec<T>> { impl Encoder for Option<Codec> {
fn encoder(self) -> Option<Codec<T>> { fn encoder(self) -> Option<Codec> {
self.and_then(|c| c.encoder()) self.and_then(|c| c.encoder())
} }
} }
impl Encoder for Audio {
fn encoder(self) -> Option<Codec> {
if self.is_encoder() {
Some(*self)
} else {
None
}
}
}
impl Encoder for Video {
fn encoder(self) -> Option<Codec> {
if self.is_encoder() {
Some(*self)
} else {
None
}
}
}

100
src/codec/video.rs Normal file
View File

@ -0,0 +1,100 @@
use std::ops::Deref;
use super::codec::Codec;
use crate::ffi::*;
use crate::{format, Rational};
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct Video {
codec: Codec,
}
impl Video {
pub unsafe fn new(codec: Codec) -> Video {
Video { codec }
}
}
impl Video {
pub fn rates(&self) -> Option<RateIter> {
unsafe {
if (*self.codec.as_ptr()).supported_framerates.is_null() {
None
} else {
Some(RateIter::new((*self.codec.as_ptr()).supported_framerates))
}
}
}
pub fn formats(&self) -> Option<FormatIter> {
unsafe {
if (*self.codec.as_ptr()).pix_fmts.is_null() {
None
} else {
Some(FormatIter::new((*self.codec.as_ptr()).pix_fmts))
}
}
}
}
impl Deref for Video {
type Target = Codec;
fn deref(&self) -> &Self::Target {
&self.codec
}
}
pub struct RateIter {
ptr: *const AVRational,
}
impl RateIter {
pub fn new(ptr: *const AVRational) -> Self {
RateIter { ptr }
}
}
impl Iterator for RateIter {
type Item = Rational;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if (*self.ptr).num == 0 && (*self.ptr).den == 0 {
return None;
}
let rate = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(rate)
}
}
}
pub struct FormatIter {
ptr: *const AVPixelFormat,
}
impl FormatIter {
pub fn new(ptr: *const AVPixelFormat) -> Self {
FormatIter { ptr }
}
}
impl Iterator for FormatIter {
type Item = format::Pixel;
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe {
if *self.ptr == AVPixelFormat::AV_PIX_FMT_NONE {
return None;
}
let format = (*self.ptr).into();
self.ptr = self.ptr.offset(1);
Some(format)
}
}
}

View File

@ -3,60 +3,50 @@ use std::ptr;
use crate::ffi::*; use crate::ffi::*;
use crate::format; use crate::format;
pub struct AudioIter(*const AVInputFormat); pub struct AudioIter(*mut AVInputFormat);
impl Iterator for AudioIter { impl Iterator for AudioIter {
type Item = format::Input; type Item = format::Input;
fn next(&mut self) -> Option<<Self as Iterator>::Item> { fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe { unsafe {
let inner = self.0; let ptr = av_input_audio_device_next(self.0) as *mut AVInputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here if ptr.is_null() && !self.0.is_null() {
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_input_audio_device_next(inner);
if let Some(input) = format::Input::from_raw(ptr) {
self.0 = ptr;
Some(input)
} else {
None None
} else {
self.0 = ptr;
Some(format::Input::wrap(ptr))
} }
} }
} }
} }
pub fn audio() -> AudioIter { pub fn audio() -> AudioIter {
AudioIter(ptr::null()) AudioIter(ptr::null_mut())
} }
pub struct VideoIter(*const AVInputFormat); pub struct VideoIter(*mut AVInputFormat);
impl Iterator for VideoIter { impl Iterator for VideoIter {
type Item = format::Input; type Item = format::Input;
fn next(&mut self) -> Option<<Self as Iterator>::Item> { fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe { unsafe {
let inner = self.0; let ptr = av_input_video_device_next(self.0) as *mut AVInputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here if ptr.is_null() && !self.0.is_null() {
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_input_video_device_next(inner);
if let Some(input) = format::Input::from_raw(ptr) {
self.0 = ptr;
Some(input)
} else {
None None
} else {
self.0 = ptr;
Some(format::Input::wrap(ptr))
} }
} }
} }
} }
pub fn video() -> VideoIter { pub fn video() -> VideoIter {
VideoIter(ptr::null()) VideoIter(ptr::null_mut())
} }

View File

@ -3,26 +3,21 @@ use std::ptr;
use crate::ffi::*; use crate::ffi::*;
use crate::format; use crate::format;
pub struct AudioIter(*const AVOutputFormat); pub struct AudioIter(*mut AVOutputFormat);
impl Iterator for AudioIter { impl Iterator for AudioIter {
type Item = format::Output; type Item = format::Output;
fn next(&mut self) -> Option<<Self as Iterator>::Item> { fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe { unsafe {
let inner = self.0; let ptr = av_output_audio_device_next(self.0) as *mut AVOutputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here if ptr.is_null() && !self.0.is_null() {
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_output_audio_device_next(inner);
if let Some(output) = format::Output::from_raw(ptr) {
self.0 = ptr;
Some(output)
} else {
None None
} else {
self.0 = ptr as *mut AVOutputFormat;
Some(format::Output::wrap(ptr))
} }
} }
} }
@ -32,26 +27,21 @@ pub fn audio() -> AudioIter {
AudioIter(ptr::null_mut()) AudioIter(ptr::null_mut())
} }
pub struct VideoIter(*const AVOutputFormat); pub struct VideoIter(*mut AVOutputFormat);
impl Iterator for VideoIter { impl Iterator for VideoIter {
type Item = format::Output; type Item = format::Output;
fn next(&mut self) -> Option<<Self as Iterator>::Item> { fn next(&mut self) -> Option<<Self as Iterator>::Item> {
unsafe { unsafe {
let inner = self.0; let ptr = av_output_video_device_next(self.0) as *mut AVOutputFormat;
// Pre-5.0 FFmpeg uses a non-const pointer here if ptr.is_null() && !self.0.is_null() {
#[cfg(not(feature = "ffmpeg_5_0"))]
let inner = inner as *mut _;
let ptr = av_output_video_device_next(inner);
if let Some(output) = format::Output::from_raw(ptr) {
self.0 = ptr;
Some(output)
} else {
None None
} else {
self.0 = ptr as *mut AVOutputFormat;
Some(format::Output::wrap(ptr))
} }
} }
} }

View File

@ -1,10 +1,11 @@
use std::ffi::CString; use std::ffi::CString;
use std::mem; use std::mem;
use std::ops::{Bound, Deref, DerefMut, RangeBounds}; use std::ops::{Deref, DerefMut};
use super::common::Context; use super::common::Context;
use super::destructor; use super::destructor;
use crate::ffi::*; use crate::ffi::*;
use crate::util::range::Range;
#[cfg(not(feature = "ffmpeg_5_0"))] #[cfg(not(feature = "ffmpeg_5_0"))]
use crate::Codec; use crate::Codec;
use crate::{format, Error, Packet, Stream}; use crate::{format, Error, Packet, Stream};
@ -35,14 +36,19 @@ impl Input {
impl Input { impl Input {
pub fn format(&self) -> format::Input { pub fn format(&self) -> format::Input {
unsafe { format::Input::from_raw((*self.as_ptr()).iformat).expect("iformat is non-null") } unsafe { format::Input::wrap((*self.as_ptr()).iformat as *mut AVInputFormat) }
} }
#[cfg(not(feature = "ffmpeg_5_0"))] #[cfg(not(feature = "ffmpeg_5_0"))]
pub fn video_codec(&self) -> Option<Codec> { pub fn video_codec(&self) -> Option<Codec> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).video_codec; let ptr = (*self.as_ptr()).video_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
@ -50,7 +56,12 @@ impl Input {
pub fn audio_codec(&self) -> Option<Codec> { pub fn audio_codec(&self) -> Option<Codec> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).audio_codec; let ptr = (*self.as_ptr()).audio_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
@ -58,7 +69,12 @@ impl Input {
pub fn subtitle_codec(&self) -> Option<Codec> { pub fn subtitle_codec(&self) -> Option<Codec> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).subtitle_codec; let ptr = (*self.as_ptr()).subtitle_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
@ -66,7 +82,12 @@ impl Input {
pub fn data_codec(&self) -> Option<Codec> { pub fn data_codec(&self) -> Option<Codec> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).data_codec; let ptr = (*self.as_ptr()).data_codec;
Codec::from_raw(ptr)
if ptr.is_null() {
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }
@ -96,21 +117,16 @@ impl Input {
} }
} }
pub fn seek<R: RangeBounds<i64>>(&mut self, ts: i64, range: R) -> Result<(), Error> { pub fn seek<R: Range<i64>>(&mut self, ts: i64, range: R) -> Result<(), Error> {
unsafe { unsafe {
let start = match range.start_bound().cloned() { match avformat_seek_file(
Bound::Included(i) => i, self.as_mut_ptr(),
Bound::Excluded(i) => i.saturating_add(1), -1,
Bound::Unbounded => i64::MIN, range.start().cloned().unwrap_or(i64::MIN),
}; ts,
range.end().cloned().unwrap_or(i64::MAX),
let end = match range.end_bound().cloned() { 0,
Bound::Included(i) => i, ) {
Bound::Excluded(i) => i.saturating_sub(1),
Bound::Unbounded => i64::MAX,
};
match avformat_seek_file(self.as_mut_ptr(), -1, start, ts, end, 0) {
s if s >= 0 => Ok(()), s if s >= 0 => Ok(()),
e => Err(Error::from(e)), e => Err(Error::from(e)),
} }

View File

@ -35,7 +35,7 @@ impl Output {
impl Output { impl Output {
pub fn format(&self) -> format::Output { pub fn format(&self) -> format::Output {
unsafe { format::Output::from_raw((*self.as_ptr()).oformat).expect("oformat is non-null") } unsafe { format::Output::wrap((*self.as_ptr()).oformat as *mut AVOutputFormat) }
} }
pub fn write_header(&mut self) -> Result<(), Error> { pub fn write_header(&mut self) -> Result<(), Error> {
@ -68,7 +68,7 @@ impl Output {
} }
} }
pub fn add_stream<T, E: traits::Encoder<T>>(&mut self, codec: E) -> Result<StreamMut, Error> { pub fn add_stream<E: traits::Encoder>(&mut self, codec: E) -> Result<StreamMut, Error> {
unsafe { unsafe {
let codec = codec.encoder(); let codec = codec.encoder();
let codec = codec.map_or(ptr::null(), |c| c.as_ptr()); let codec = codec.map_or(ptr::null(), |c| c.as_ptr());

View File

@ -1,37 +1,34 @@
use std::ptr::NonNull;
use crate::ffi::*; use crate::ffi::*;
use crate::utils; use crate::utils;
use super::Flags;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Input { pub struct Input {
ptr: NonNull<AVInputFormat>, ptr: *mut AVInputFormat,
} }
impl Input { impl Input {
pub unsafe fn from_raw(ptr: *const AVInputFormat) -> Option<Self> { pub unsafe fn wrap(ptr: *mut AVInputFormat) -> Self {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr }) Input { ptr }
} }
pub fn as_ptr(self) -> *const AVInputFormat { pub unsafe fn as_ptr(&self) -> *const AVInputFormat {
self.ptr.as_ptr() self.ptr as *const _
} }
pub fn name(self) -> &'static str { pub unsafe fn as_mut_ptr(&mut self) -> *mut AVInputFormat {
self.ptr
}
}
impl Input {
pub fn name(&self) -> &str {
unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) } unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) }
} }
pub fn description(self) -> &'static str { pub fn description(&self) -> &str {
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") } unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
} }
pub fn flags(self) -> Flags { pub fn extensions(&self) -> Vec<&str> {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
pub fn extensions(self) -> Vec<&'static str> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).extensions; let ptr = (*self.as_ptr()).extensions;
@ -43,7 +40,7 @@ impl Input {
} }
} }
pub fn mime_types(self) -> Vec<&'static str> { pub fn mime_types(&self) -> Vec<&str> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).mime_type; let ptr = (*self.as_ptr()).mime_type;

View File

@ -26,7 +26,11 @@ impl Iterator for DemuxerIter {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
unsafe { unsafe {
let next = av_demuxer_iterate(&mut self.ptr); let next = av_demuxer_iterate(&mut self.ptr);
Input::from_raw(next) if next.is_null() {
None
} else {
Some(Input::wrap(next as _))
}
} }
} }
} }
@ -53,7 +57,11 @@ impl Iterator for MuxerIter {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
unsafe { unsafe {
let next = av_muxer_iterate(&mut self.ptr); let next = av_muxer_iterate(&mut self.ptr);
Output::from_raw(next) if next.is_null() {
None
} else {
Some(Output::wrap(next as _))
}
} }
} }
} }

View File

@ -1,39 +1,40 @@
use std::path::Path; use std::path::Path;
use std::ffi::CString; use std::ffi::CString;
use std::ptr::{self, NonNull}; use std::ptr;
use super::Flags; use super::Flags;
use crate::ffi::*; use crate::ffi::*;
use crate::{codec, media, utils}; use crate::{codec, media, utils};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Output { pub struct Output {
ptr: NonNull<AVOutputFormat>, ptr: *mut AVOutputFormat,
} }
impl Output { impl Output {
pub unsafe fn from_raw(ptr: *const AVOutputFormat) -> Option<Self> { pub unsafe fn wrap(ptr: *mut AVOutputFormat) -> Self {
NonNull::new(ptr as *mut _).map(|ptr| Self { ptr }) Output { ptr }
} }
pub fn as_ptr(self) -> *const AVOutputFormat { pub unsafe fn as_ptr(&self) -> *const AVOutputFormat {
self.ptr.as_ptr() self.ptr as *const _
} }
pub fn name(self) -> &'static str { pub unsafe fn as_mut_ptr(&mut self) -> *mut AVOutputFormat {
self.ptr
}
}
impl Output {
pub fn name(&self) -> &str {
unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) } unsafe { utils::str_from_c_ptr((*self.as_ptr()).name) }
} }
pub fn description(self) -> &'static str { pub fn description(&self) -> &str {
unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") } unsafe { utils::optional_str_from_c_ptr((*self.as_ptr()).long_name).unwrap_or("") }
} }
pub fn flags(self) -> Flags { pub fn extensions(&self) -> Vec<&str> {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
pub fn extensions(self) -> Vec<&'static str> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).extensions; let ptr = (*self.as_ptr()).extensions;
@ -45,7 +46,7 @@ impl Output {
} }
} }
pub fn mime_types(self) -> Vec<&'static str> { pub fn mime_types(&self) -> Vec<&str> {
unsafe { unsafe {
let ptr = (*self.as_ptr()).mime_type; let ptr = (*self.as_ptr()).mime_type;
@ -57,9 +58,9 @@ impl Output {
} }
} }
pub fn codec<P: AsRef<Path>>(self, path: P, kind: media::Type) -> codec::Id { pub fn codec<P: AsRef<Path>>(&self, path: &P, kind: media::Type) -> codec::Id {
// XXX: use to_cstring when stable // XXX: use to_cstring when stable
let path = CString::new(path.as_ref().to_str().unwrap()).unwrap(); let path = CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap();
unsafe { unsafe {
codec::Id::from(av_guess_codec( codec::Id::from(av_guess_codec(
@ -71,4 +72,8 @@ impl Output {
)) ))
} }
} }
pub fn flags(&self) -> Flags {
unsafe { Flags::from_bits_truncate((*self.as_ptr()).flags) }
}
} }

View File

@ -23,6 +23,27 @@ use crate::ffi::*;
use crate::utils; use crate::utils;
use crate::{Dictionary, Error}; use crate::{Dictionary, Error};
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_all() {
unsafe {
av_register_all();
}
}
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_input(mut format: Input) {
unsafe {
av_register_input_format(format.as_mut_ptr());
}
}
#[cfg(not(feature = "ffmpeg_5_0"))]
pub fn register_output(mut format: Output) {
unsafe {
av_register_output_format(format.as_mut_ptr());
}
}
pub fn version() -> u32 { pub fn version() -> u32 {
unsafe { avformat_version() } unsafe { avformat_version() }
} }
@ -36,11 +57,11 @@ pub fn license() -> &'static str {
} }
// XXX: use to_cstring when stable // XXX: use to_cstring when stable
fn from_path<P: AsRef<Path>>(path: P) -> CString { fn from_path<P: AsRef<Path>>(path: &P) -> CString {
CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap() CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
} }
pub fn input<P: AsRef<Path>>(path: P) -> Result<context::Input, Error> { pub fn input<P: AsRef<Path>>(path: &P) -> Result<context::Input, Error> {
unsafe { unsafe {
let mut ps = ptr::null_mut(); let mut ps = ptr::null_mut();
let path = from_path(path); let path = from_path(path);
@ -60,7 +81,7 @@ pub fn input<P: AsRef<Path>>(path: P) -> Result<context::Input, Error> {
} }
pub fn input_with_dictionary<P: AsRef<Path>>( pub fn input_with_dictionary<P: AsRef<Path>>(
path: P, path: &P,
options: Dictionary, options: Dictionary,
) -> Result<context::Input, Error> { ) -> Result<context::Input, Error> {
unsafe { unsafe {
@ -85,7 +106,10 @@ pub fn input_with_dictionary<P: AsRef<Path>>(
} }
} }
pub fn input_with_interrupt<P: AsRef<Path>, F>(path: P, closure: F) -> Result<context::Input, Error> pub fn input_with_interrupt<P: AsRef<Path>, F>(
path: &P,
closure: F,
) -> Result<context::Input, Error>
where where
F: FnMut() -> bool, F: FnMut() -> bool,
{ {
@ -108,7 +132,7 @@ where
} }
} }
pub fn output<P: AsRef<Path>>(path: P) -> Result<context::Output, Error> { pub fn output<P: AsRef<Path>>(path: &P) -> Result<context::Output, Error> {
unsafe { unsafe {
let mut ps = ptr::null_mut(); let mut ps = ptr::null_mut();
let path = from_path(path); let path = from_path(path);
@ -124,7 +148,10 @@ pub fn output<P: AsRef<Path>>(path: P) -> Result<context::Output, Error> {
} }
} }
pub fn output_with<P: AsRef<Path>>(path: P, options: Dictionary) -> Result<context::Output, Error> { pub fn output_with<P: AsRef<Path>>(
path: &P,
options: Dictionary,
) -> Result<context::Output, Error> {
unsafe { unsafe {
let mut ps = ptr::null_mut(); let mut ps = ptr::null_mut();
let path = from_path(path); let path = from_path(path);
@ -153,7 +180,7 @@ pub fn output_with<P: AsRef<Path>>(path: P, options: Dictionary) -> Result<conte
} }
} }
pub fn output_as<P: AsRef<Path>>(path: P, format: &str) -> Result<context::Output, Error> { pub fn output_as<P: AsRef<Path>>(path: &P, format: &str) -> Result<context::Output, Error> {
unsafe { unsafe {
let mut ps = ptr::null_mut(); let mut ps = ptr::null_mut();
let path = from_path(path); let path = from_path(path);
@ -176,7 +203,7 @@ pub fn output_as<P: AsRef<Path>>(path: P, format: &str) -> Result<context::Outpu
} }
pub fn output_as_with<P: AsRef<Path>>( pub fn output_as_with<P: AsRef<Path>>(
path: P, path: &P,
format: &str, format: &str,
options: Dictionary, options: Dictionary,
) -> Result<context::Output, Error> { ) -> Result<context::Output, Error> {

View File

@ -83,6 +83,11 @@ fn init_error() {
util::error::register_all(); util::error::register_all();
} }
#[cfg(all(feature = "format", not(feature = "ffmpeg_5_0")))]
fn init_format() {
format::register_all();
}
#[cfg(not(feature = "format"))] #[cfg(not(feature = "format"))]
fn init_format() {} fn init_format() {}
@ -104,6 +109,8 @@ fn init_filter() {}
pub fn init() -> Result<(), Error> { pub fn init() -> Result<(), Error> {
init_error(); init_error();
#[cfg(not(feature = "ffmpeg_5_0"))]
init_format();
init_device(); init_device();
#[cfg(not(feature = "ffmpeg_5_0"))] #[cfg(not(feature = "ffmpeg_5_0"))]
init_filter(); init_filter();

View File

@ -12,6 +12,7 @@ pub mod mathematics;
pub mod media; pub mod media;
pub mod option; pub mod option;
pub mod picture; pub mod picture;
pub mod range;
pub mod rational; pub mod rational;
pub mod time; pub mod time;

35
src/util/range.rs Normal file
View File

@ -0,0 +1,35 @@
use std::ops;
pub trait Range<T> {
fn start(&self) -> Option<&T> {
None
}
fn end(&self) -> Option<&T> {
None
}
}
impl<T> Range<T> for ops::Range<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
impl<T> Range<T> for ops::RangeTo<T> {
fn end(&self) -> Option<&T> {
Some(&self.end)
}
}
impl<T> Range<T> for ops::RangeFrom<T> {
fn start(&self) -> Option<&T> {
Some(&self.start)
}
}
impl<T> Range<T> for ops::RangeFull {}