Refactor crate::codec (#78)

* Refactor AVCodec wrapper

* Implement new config interface

* Move ProfileIter to codec::profile

* Add CodecDescriptor
This commit is contained in:
FreezyLemon 2024-11-03 01:31:30 +01:00 committed by GitHub
parent f1e978b58d
commit f1c6b442b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 929 additions and 509 deletions

View File

@ -20,7 +20,7 @@ fn main() {
println!("\t profiles: none"); println!("\t profiles: none");
} }
if let Ok(video) = codec.video() { if let Some(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 Ok(audio) = codec.audio() { if let Some(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 Ok(video) = codec.video() { if let Some(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 Ok(audio) = codec.audio() { if let Some(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

@ -83,7 +83,8 @@ 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

@ -1,215 +0,0 @@
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,30 +1,58 @@
use super::{Audio, Capabilities, Id, Profile, Video}; use std::marker::PhantomData;
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, Error}; use crate::{media, utils};
#[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 { pub struct Codec<Type = UnknownType> {
ptr: *mut AVCodec, ptr: NonNull<AVCodec>,
_marker: PhantomData<Type>,
} }
unsafe impl Send for Codec {} #[derive(PartialEq, Eq, Copy, Clone)]
unsafe impl Sync for Codec {} pub struct UnknownType;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct AudioType;
#[derive(PartialEq, Eq, Copy, Clone)]
pub struct VideoType;
impl Codec { unsafe impl<T> Send for Codec<T> {}
pub unsafe fn wrap(ptr: *mut AVCodec) -> Self { unsafe impl<T> Sync for Codec<T> {}
Codec { ptr }
}
pub unsafe fn as_ptr(&self) -> *const AVCodec { impl Codec<UnknownType> {
self.ptr as *const _ /// Create a new reference to a codec from a raw pointer.
} ///
/// Returns `None` if `ptr` is null.
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVCodec { pub unsafe fn from_raw(ptr: *const AVCodec) -> Option<Self> {
self.ptr NonNull::new(ptr as *mut _).map(|ptr| Self {
ptr,
_marker: PhantomData,
})
} }
} }
impl Codec { impl<T> Codec<T> {
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 }
} }
@ -53,13 +81,14 @@ impl Codec {
self.medium() == media::Type::Video self.medium() == media::Type::Video
} }
pub fn video(self) -> Result<Video, Error> { pub fn video(self) -> Option<Video> {
unsafe { if self.medium() == media::Type::Video {
if self.medium() == media::Type::Video { Some(Codec {
Ok(Video::new(self)) ptr: self.ptr,
} else { _marker: PhantomData,
Err(Error::InvalidData) })
} } else {
None
} }
} }
@ -67,13 +96,14 @@ impl Codec {
self.medium() == media::Type::Audio self.medium() == media::Type::Audio
} }
pub fn audio(self) -> Result<Audio, Error> { pub fn audio(self) -> Option<Audio> {
unsafe { if self.medium() == media::Type::Audio {
if self.medium() == media::Type::Audio { Some(Codec {
Ok(Audio::new(self)) ptr: self.ptr,
} else { _marker: PhantomData,
Err(Error::InvalidData) })
} } else {
None
} }
} }
@ -94,32 +124,217 @@ impl Codec {
} }
} }
} }
}
pub struct ProfileIter { pub fn descriptor(self) -> Option<CodecDescriptor> {
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 { unsafe {
if (*self.ptr).profile == FF_PROFILE_UNKNOWN { let ptr = avcodec_descriptor_get(self.id().into());
return None; CodecDescriptor::from_raw(ptr)
}
let profile = Profile::from((self.id, (*self.ptr).profile));
self.ptr = self.ptr.offset(1);
Some(profile)
} }
} }
} }
impl Codec<AudioType> {
/// Checks if the given sample rate is supported by this audio codec.
#[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> {
unsafe {
let ptr = self.ptr.as_ptr();
if *ptr == 0 {
return None;
}
let layout = ChannelLayoutMask::from_bits_truncate(*ptr);
self.ptr = NonNull::new_unchecked(ptr.add(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()
}
}

397
src/codec/config.rs Normal file
View File

@ -0,0 +1,397 @@
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::{encoder, Id};
/// use ffmpeg_the_third::format::sample::{Sample, Type};
///
/// let codec = encoder::find(Id::MP3)
/// .expect("Can find an MP3 encoder")
/// .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::MP3).expect("can find mp3 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::VP9).expect("can find VP9 encoder");
assert!(supported_color_ranges(codec, None)
.expect("can check color range support")
.supports(Range::JPEG));
assert!(!supported_pixel_formats(codec, None)
.expect("can check color range support")
.supports(Pixel::BGR8));
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,13 +62,7 @@ impl Context {
} }
pub fn codec(&self) -> Option<Codec> { pub fn codec(&self) -> Option<Codec> {
unsafe { unsafe { Codec::from_raw((*self.as_ptr()).codec) }
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<D: traits::Decoder>(mut self, codec: D) -> Result<Opened, Error> { pub fn open_as<T, D: traits::Decoder<T>>(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<D: traits::Decoder>( pub fn open_as_with<T, D: traits::Decoder<T>>(
mut self, mut self,
codec: D, codec: D,
options: Dictionary, options: Dictionary,

View File

@ -34,25 +34,16 @@ 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()) as *mut AVCodec; let ptr = avcodec_find_decoder(id.into());
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()) as *mut AVCodec; let ptr = avcodec_find_decoder_by_name(name.as_ptr());
if ptr.is_null() { Codec::from_raw(ptr)
None
} else {
Some(Codec::wrap(ptr))
}
} }
} }

158
src/codec/descriptor.rs Normal file
View File

@ -0,0 +1,158 @@
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<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<T, E: traits::Encoder<T>>(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<E: traits::Encoder>( pub fn open_as_with<T, E: traits::Encoder<T>>(
mut self, mut self,
codec: E, codec: E,
options: Dictionary, options: Dictionary,

View File

@ -37,25 +37,15 @@ 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()) as *mut AVCodec; let ptr = avcodec_find_encoder(id.into());
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()) as *mut AVCodec; let ptr = avcodec_find_encoder_by_name(name.as_ptr());
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<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<T, E: traits::Encoder<T>>(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<E: traits::Encoder>( pub fn open_as_with<T, E: traits::Encoder<T>>(
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<E: traits::Encoder>(mut self, codec: E) -> Result<Encoder, Error> { pub fn open_as<T, E: traits::Encoder<T>>(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<E: traits::Encoder>( pub fn open_as_with<T, E: traits::Encoder<T>>(
mut self, mut self,
codec: E, codec: E,
options: Dictionary, options: Dictionary,

View File

@ -11,8 +11,13 @@ 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;
@ -20,16 +25,11 @@ 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,6 +42,9 @@ 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,3 +387,31 @@ 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)
}
}
}

17
src/codec/props.rs Normal file
View File

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

View File

@ -1,100 +0,0 @@
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

@ -42,12 +42,7 @@ impl Input {
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))
}
} }
} }
@ -55,12 +50,7 @@ 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))
}
} }
} }
@ -68,12 +58,7 @@ 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))
}
} }
} }
@ -81,12 +66,7 @@ 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))
}
} }
} }

View File

@ -68,7 +68,7 @@ impl Output {
} }
} }
pub fn add_stream<E: traits::Encoder>(&mut self, codec: E) -> Result<StreamMut, Error> { pub fn add_stream<T, E: traits::Encoder<T>>(&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());