Skip to content

Commit

Permalink
More segfault solving and other minor fixes
Browse files Browse the repository at this point in the history
Co-authored-by: Vítor Vasconcellos <[email protected]>
  • Loading branch information
fogodev and HeavenVolkoff committed May 2, 2024
1 parent 9feeb5a commit 872592a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 92 deletions.
46 changes: 26 additions & 20 deletions crates/ffmpeg/src/codec_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,22 @@ impl FFmpegCodecContext {

fn tag(&self) -> Option<String> {
if self.as_ref().codec_tag != 0 {
CString::new(vec![0; AV_FOURCC_MAX_STRING_SIZE as usize])
.ok()
.map(|buffer| {
let tag = unsafe {
CString::from_raw(av_fourcc_make_string(
buffer.into_raw(),
self.as_ref().codec_tag,
))
};
String::from_utf8_lossy(tag.as_bytes()).to_string()
})
CString::new(vec![
0;
usize::try_from(AV_FOURCC_MAX_STRING_SIZE).expect(
"AV_FOURCC_MAX_STRING_SIZE is 32, must fit in an usize"
)
])
.ok()
.map(|buffer| {
let tag = unsafe {
CString::from_raw(av_fourcc_make_string(
buffer.into_raw(),
self.as_ref().codec_tag,
))
};
String::from_utf8_lossy(tag.as_bytes()).to_string()
})
} else {
None
}
Expand All @@ -163,11 +168,12 @@ impl FFmpegCodecContext {
| AVMediaType::AVMEDIA_TYPE_SUBTITLE
| AVMediaType::AVMEDIA_TYPE_ATTACHMENT => ctx.bit_rate,
AVMediaType::AVMEDIA_TYPE_AUDIO => {
let bits_per_sample = unsafe { av_get_bits_per_sample(ctx.codec_id) };
let bits_per_sample = i64::from(unsafe { av_get_bits_per_sample(ctx.codec_id) });
if bits_per_sample != 0 {
let bit_rate = ctx.sample_rate as i64 * ctx.ch_layout.nb_channels as i64;
if bit_rate <= std::i64::MAX / bits_per_sample as i64 {
return bit_rate * (bits_per_sample as i64);
let bit_rate =
i64::from(ctx.sample_rate) * i64::from(ctx.ch_layout.nb_channels);
if bit_rate <= std::i64::MAX / bits_per_sample {
return bit_rate * (bits_per_sample);
}
}
ctx.bit_rate
Expand Down Expand Up @@ -276,8 +282,8 @@ impl FFmpegCodecContext {
(None, None)
} else {
let mut display_aspect_ratio = AVRational { num: 0, den: 0 };
let num = (width * ctx.sample_aspect_ratio.num) as i64;
let den = (height * ctx.sample_aspect_ratio.den) as i64;
let num = i64::from(width * ctx.sample_aspect_ratio.num);
let den = i64::from(height * ctx.sample_aspect_ratio.den);
let max = 1024 * 1024;
unsafe {
av_reduce(
Expand All @@ -296,13 +302,13 @@ impl FFmpegCodecContext {
};

let mut properties = vec![];
if ctx.properties & (FF_CODEC_PROPERTY_LOSSLESS as u32) != 0 {
if ctx.properties & (FF_CODEC_PROPERTY_LOSSLESS.unsigned_abs()) != 0 {
properties.push("Closed Captions".to_string());
}
if ctx.properties & (FF_CODEC_PROPERTY_CLOSED_CAPTIONS as u32) != 0 {
if ctx.properties & (FF_CODEC_PROPERTY_CLOSED_CAPTIONS.unsigned_abs()) != 0 {
properties.push("Film Grain".to_string());
}
if ctx.properties & (FF_CODEC_PROPERTY_FILM_GRAIN as u32) != 0 {
if ctx.properties & (FF_CODEC_PROPERTY_FILM_GRAIN.unsigned_abs()) != 0 {
properties.push("lossless".to_string());
}

Expand Down
6 changes: 5 additions & 1 deletion crates/ffmpeg/src/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ impl FFmpegDict {
}

pub(crate) fn get(&self, key: &CStr) -> Option<String> {
unsafe { av_dict_get(self.dict, key.as_ptr(), ptr::null(), 0).as_ref() }
if self.dict.is_null() {
return None;
}

unsafe { av_dict_get(self.dict, key.as_ptr(), ptr::null(), AV_DICT_MATCH_CASE).as_ref() }
.and_then(|entry| unsafe { entry.value.as_ref() })
.map(|value| {
let cstr = unsafe { CStr::from_ptr(value) };
Expand Down
41 changes: 17 additions & 24 deletions crates/ffmpeg/src/filter_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ impl<'a> FFmpegFilterGraph {
size: Option<ThumbnailSize>,
time_base: &AVRational,
codec_ctx: &FFmpegCodecContext,
aspect_ratio: &AVRational,
rotation_angle: f64,
interlaced_frame: bool,
pixel_aspect_ratio: &AVRational,
maintain_aspect_ratio: bool,
) -> Result<(Self, &'a mut AVFilterContext, &'a mut AVFilterContext), Error> {
let mut filter_graph = Self::new()?;
Expand All @@ -50,6 +50,7 @@ impl<'a> FFmpegFilterGraph {
"video_size={}x{}:pix_fmt={}:time_base={}/{}:pixel_aspect={}/{}",
codec_ctx.as_ref().width,
codec_ctx.as_ref().height,
// AVPixelFormat is an i32 enum, so it's safe to cast it to i32
codec_ctx.as_ref().pix_fmt as i32,
time_base.num,
time_base.den,
Expand Down Expand Up @@ -97,7 +98,7 @@ impl<'a> FFmpegFilterGraph {
CString::new(thumb_scale_filter_args(
size,
codec_ctx,
aspect_ratio,
pixel_aspect_ratio,
maintain_aspect_ratio,
)?)?
.as_c_str(),
Expand Down Expand Up @@ -247,48 +248,40 @@ impl Drop for FFmpegFilterGraph {
fn thumb_scale_filter_args(
size: Option<ThumbnailSize>,
codec_ctx: &FFmpegCodecContext,
aspect_ratio: &AVRational,
pixel_aspect_ratio: &AVRational,
maintain_aspect_ratio: bool,
) -> Result<String, Error> {
let (mut width, mut height) = match size {
Some(ThumbnailSize::Dimensions { width, height }) => (width as i32, height as i32),
Some(ThumbnailSize::Size(width)) => (width as i32, -1),
let (width, height) = match size {
Some(ThumbnailSize::Dimensions { width, height }) => (width, Some(height)),
Some(ThumbnailSize::Size(width)) => (width, None),
None => return Ok("w=0:h=0".to_string()),
};

if width <= 0 {
width = -1;
}

if height <= 0 {
height = -1;
}

let mut scale = String::new();

if width != -1 && height != -1 {
if let Some(height) = height {
scale.push_str(&format!("w={}:h={}", width, height));
if maintain_aspect_ratio {
scale.push_str(":force_original_aspect_ratio=decrease");
}
} else if !maintain_aspect_ratio {
let size = if width == -1 { height } else { width };
scale.push_str(&format!("w={}:h={}", size, size));
scale.push_str(&format!("w={}:h={}", width, width));
} else {
let size = if height == -1 { width } else { height };
width = codec_ctx.as_ref().width;
height = codec_ctx.as_ref().height;
let size = width;
let mut width = codec_ctx.as_ref().width.unsigned_abs();
let mut height = codec_ctx.as_ref().height.unsigned_abs();

// if the pixel aspect ratio is defined and is not 1, we have an anamorphic stream
if aspect_ratio.num != 0 && aspect_ratio.num != aspect_ratio.den {
width = width * aspect_ratio.num / aspect_ratio.den;
if pixel_aspect_ratio.num != 0 && pixel_aspect_ratio.num != pixel_aspect_ratio.den {
width = (width * pixel_aspect_ratio.num.unsigned_abs())
/ pixel_aspect_ratio.den.unsigned_abs();

if size != 0 {
if height > width {
width = width * size / height;
width = (width * size) / height;
height = size;
} else {
height = height * size / width;
height = (height * size) / width;
width = size;
}
}
Expand Down
105 changes: 70 additions & 35 deletions crates/ffmpeg/src/format_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,41 @@ impl FFmpegFormatContext {
return None;
}

let ms = (duration % (AV_TIME_BASE as i64)).abs();
TimeDelta::new(duration / (AV_TIME_BASE as i64), (ms * 1000) as u32)
let av_time_base = i64::from(AV_TIME_BASE);
let ms = u32::try_from((duration % av_time_base).unsigned_abs())
.expect("we're taking modulo from 1_000_000 so it fits into u32");

TimeDelta::new(duration / av_time_base, ms * 1000)
}

pub(crate) fn stream(&self, index: u32) -> Option<&mut AVStream> {
let streams = self.as_ref().streams;
if streams.is_null() {
return None;
}

let Ok(index) = isize::try_from(index) else {
return None;
};

unsafe { self.as_ref().streams.as_ref() }
.and_then(|streams| unsafe { streams.offset(index).as_mut() })
unsafe { (*(streams.offset(index))).as_mut() }
}

pub(crate) fn get_stream_rotation_angle(&self, index: u32) -> f64 {
let Some(stream) = self.stream(index) else {
return 0.0;
};

/*
* This side data contains a 3x3 transformation matrix describing an affine transformation
* that needs to be applied to the decoded video frames for correct presentation.
*
* See libavutil/display.h for a detailed description of the data.
* https://github.com/FFmpeg/FFmpeg/blob/n6.1.1/libavutil/display.h#L32-L71
*
* The pointer conversion is due to the fact that av_stream_get_side_data is a generic function that has no prior
* knowledge of the type of the side data it is retrieving.
*/
#[allow(clippy::cast_ptr_alignment)]
let matrix = (unsafe {
av_stream_get_side_data(
Expand Down Expand Up @@ -125,9 +142,7 @@ impl FFmpegFormatContext {
let mut embedded_data_streams = vec![];

'outer: for stream_idx in 0..self.as_ref().nb_streams {
let Some(stream) = unsafe { self.as_ref().streams.as_ref() }
.and_then(|streams| unsafe { streams.offset(stream_idx as isize).as_ref() })
else {
let Some(stream) = self.stream(stream_idx) else {
continue;
};

Expand Down Expand Up @@ -202,24 +217,34 @@ impl FFmpegFormatContext {
return None;
}

let _secs = start_time / (AV_TIME_BASE as i64);
let ms = (start_time % (AV_TIME_BASE as i64)).abs();
let av_time_base = i64::from(AV_TIME_BASE);

let secs = start_time / av_time_base;
let ms = u32::try_from((start_time % av_time_base).unsigned_abs())
.expect("we're taking modulo from 1_000_000 so it fits into u32");

TimeDelta::new(start_time / (AV_TIME_BASE as i64), (ms * 1000) as u32)
TimeDelta::new(secs, ms * 1000)
}

fn bit_rate(&self) -> i64 {
self.as_ref().bit_rate
}

fn chapters(&self) -> Vec<MediaChapter> {
unsafe { self.as_ref().chapters.as_ref() }
.map(|chapters| {
(0..self.as_ref().nb_chapters)
let chapters_ptr = self.as_ref().chapters;

let Ok(num_chapters) = isize::try_from(self.as_ref().nb_chapters) else {
return vec![];
};

(!chapters_ptr.is_null())
.then(|| {
(0..num_chapters)
.filter_map(|id| {
unsafe { chapters.offset(id as isize).as_ref() }.map(|chapter| {
unsafe { (*(chapters_ptr.offset(id))).as_ref() }.map(|chapter| {
MediaChapter {
id,
// Note: id is guaranteed to be a valid u32 because it was calculated from a u32
id: id as u32,
start: chapter.start,
end: chapter.end,
time_base_num: chapter.time_base.num,
Expand All @@ -237,28 +262,36 @@ impl FFmpegFormatContext {

fn programs(&self) -> Vec<MediaProgram> {
let mut visited_streams: HashSet<u32> = HashSet::new();
let mut programs = unsafe { self.as_ref().programs.as_ref() }
.map(|programs| {
(0..self.as_ref().nb_programs)
let programs_ptr = self.as_ref().programs;

let mut programs = (!programs_ptr.is_null())
.then(|| {
let Ok(num_programs) = isize::try_from(self.as_ref().nb_programs) else {
return vec![];
};

(0..num_programs)
.filter_map(|id| {
unsafe { programs.offset(id as isize).as_ref() }.map(|program| {
unsafe { (*(programs_ptr.offset(id))).as_ref() }.map(|program| {
let (metadata, name) =
extract_name_and_convert_metadata(program.metadata);

let streams = (0..program.nb_stream_indexes)
let streams = (0..num_programs)
.filter_map(|index| {
unsafe { program.stream_index.offset(index as isize).as_ref() }
.and_then(|stream_index| {
unsafe { program.stream_index.offset(index).as_ref() }.and_then(
|stream_index| {
self.stream(*stream_index).map(|stream| {
visited_streams.insert(*stream_index);
(&*stream).into()
})
})
},
)
})
.collect::<Vec<MediaStream>>();

MediaProgram {
id,
// Note: id is guaranteed to be a valid u32 because it was calculated from a u32
id: id as u32,
name,
streams,
metadata,
Expand All @@ -274,20 +307,22 @@ impl FFmpegFormatContext {
.filter_map(|i| self.stream(i).map(|stream| (&*stream).into()))
.collect::<Vec<MediaStream>>();
if !unvisited_streams.is_empty() {
// Create an empty program to hold unvisited streams if there are any
programs.push(MediaProgram {
id: programs.len() as u32,
name: Some("No Program".to_string()),
streams: unvisited_streams,
metadata: MediaMetadata::default(),
});
if let Ok(id) = u32::try_from(programs.len()) {
// Create an empty program to hold unvisited streams if there are any
programs.push(MediaProgram {
id,
name: Some("No Program".to_string()),
streams: unvisited_streams,
metadata: MediaMetadata::default(),
});
}
}

programs
}

fn metadata(&self) -> Option<MediaMetadata> {
unsafe { self.as_ref().metadata.as_mut() }
unsafe { (*(self.0)).metadata.as_mut() }
.map(|metadata| FFmpegDict::new(Some(metadata)).into())
}
}
Expand Down Expand Up @@ -326,8 +361,8 @@ impl From<&AVStream> for MediaStream {
!= 0
{
let mut display_aspect_ratio = AVRational { num: 0, den: 0 };
let num = (codecpar.width * codecpar.sample_aspect_ratio.num) as i64;
let den = (codecpar.height * codecpar.sample_aspect_ratio.den) as i64;
let num = i64::from(codecpar.width * codecpar.sample_aspect_ratio.num);
let den = i64::from(codecpar.height * codecpar.sample_aspect_ratio.den);
let max = 1024 * 1024;
unsafe {
av_reduce(
Expand Down Expand Up @@ -387,7 +422,7 @@ impl From<&AVStream> for MediaStream {
});

MediaStream {
id: stream.id as u32,
id: stream.id.unsigned_abs(),
name,
codec,
aspect_ratio_num: aspect_ratio.num,
Expand Down
Loading

0 comments on commit 872592a

Please sign in to comment.