From e87c477b15510ec60366d02b1c0a306177569a15 Mon Sep 17 00:00:00 2001 From: kieran Date: Wed, 23 Oct 2024 14:48:03 +0100 Subject: [PATCH] init --- .gitignore | 2 + Cargo.lock | 334 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 16 +++ src/decode.rs | 129 +++++++++++++++++++ src/demux.rs | 318 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 25 ++++ src/main.rs | 3 + src/resample.rs | 75 +++++++++++ src/scale.rs | 95 ++++++++++++++ 9 files changed, 997 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/decode.rs create mode 100644 src/demux.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/resample.rs create mode 100644 src/scale.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40d9aca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4e19376 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,334 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" +dependencies = [ + "clang-sys", + "libc", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "ffmpeg-rs-raw" +version = "0.1.0" +dependencies = [ + "anyhow", + "ffmpeg-sys-the-third", + "libc", +] + +[[package]] +name = "ffmpeg-sys-the-third" +version = "2.1.0+ffmpeg-7.1" +source = "git+https://github.com/shssoichiro/ffmpeg-the-third.git?branch=master#814f8b9464dc250a7093415fdd72a27c6f9e7621" +dependencies = [ + "bindgen", + "cc", + "clang", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a73bc83 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ffmpeg-rs-raw" +version = "0.1.0" +edition = "2021" +repository = "https://git.v0l.io/Kieran/ffmpeg-rs-raw.git" +authors = [ + "Kieran" +] + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +anyhow = "1.0.91" +ffmpeg-sys-the-third = { git = "https://github.com/shssoichiro/ffmpeg-the-third.git", branch = "master", package = "ffmpeg-sys-the-third" } +libc = { version = "0.2.160" } diff --git a/src/decode.rs b/src/decode.rs new file mode 100644 index 0000000..e652fd7 --- /dev/null +++ b/src/decode.rs @@ -0,0 +1,129 @@ +use std::collections::HashMap; +use std::ffi::CStr; +use std::ptr; + +use anyhow::Error; +use ffmpeg_sys_the_third::AVPictureType::AV_PICTURE_TYPE_NONE; +use ffmpeg_sys_the_third::{ + av_buffer_alloc, av_frame_alloc, avcodec_alloc_context3, avcodec_find_decoder, + avcodec_free_context, avcodec_get_name, avcodec_open2, avcodec_parameters_to_context, + avcodec_receive_frame, avcodec_send_packet, AVCodec, AVCodecContext, AVFrame, AVMediaType, + AVPacket, AVStream, AVERROR, AVERROR_EOF, +}; +use libc::memcpy; + +struct CodecContext { + pub context: *mut AVCodecContext, + pub codec: *const AVCodec, +} + +impl Drop for CodecContext { + fn drop(&mut self) { + unsafe { + avcodec_free_context(&mut self.context); + self.codec = ptr::null_mut(); + self.context = ptr::null_mut(); + } + } +} + +pub struct Decoder { + codecs: HashMap, + pts: i64, +} + +unsafe impl Send for Decoder {} + +unsafe impl Sync for Decoder {} + +impl Decoder { + pub fn new() -> Self { + Self { + codecs: HashMap::new(), + pts: 0, + } + } + + pub unsafe fn decode_pkt( + &mut self, + pkt: *mut AVPacket, + stream: *mut AVStream, + ) -> Result, Error> { + let stream_index = (*pkt).stream_index; + assert_eq!( + stream_index, + (*stream).index, + "Passed stream reference does not match stream_index of packet" + ); + + let codec_par = (*stream).codecpar; + assert_ne!( + codec_par, + ptr::null_mut(), + "Codec parameters are missing from stream" + ); + + if let std::collections::hash_map::Entry::Vacant(e) = self.codecs.entry(stream_index) { + let codec = avcodec_find_decoder((*codec_par).codec_id); + if codec.is_null() { + return Err(Error::msg(format!( + "Failed to find codec: {}", + CStr::from_ptr(avcodec_get_name((*codec_par).codec_id)).to_str()? + ))); + } + let context = avcodec_alloc_context3(ptr::null()); + if context.is_null() { + return Err(Error::msg("Failed to alloc context")); + } + if avcodec_parameters_to_context(context, (*stream).codecpar) != 0 { + return Err(Error::msg("Failed to copy codec parameters to context")); + } + if avcodec_open2(context, codec, ptr::null_mut()) < 0 { + return Err(Error::msg("Failed to open codec")); + } + e.insert(CodecContext { context, codec }); + } + + if let Some(ctx) = self.codecs.get_mut(&stream_index) { + // subtitles don't need decoding, create a frame from the pkt data + if (*ctx.codec).type_ == AVMediaType::AVMEDIA_TYPE_SUBTITLE { + let frame = av_frame_alloc(); + (*frame).pts = (*pkt).pts; + (*frame).pkt_dts = (*pkt).dts; + (*frame).duration = (*pkt).duration; + (*frame).buf[0] = av_buffer_alloc((*pkt).size as usize); + (*frame).data[0] = (*(*frame).buf[0]).data; + (*frame).linesize[0] = (*pkt).size; + memcpy( + (*frame).data[0] as *mut libc::c_void, + (*pkt).data as *const libc::c_void, + (*pkt).size as usize, + ); + return Ok(vec![(frame, stream)]); + } + + let mut ret = avcodec_send_packet(ctx.context, pkt); + if ret < 0 { + return Err(Error::msg(format!("Failed to decode packet {}", ret))); + } + + let mut pkgs = Vec::new(); + while ret >= 0 { + let frame = av_frame_alloc(); + ret = avcodec_receive_frame(ctx.context, frame); + if ret < 0 { + if ret == AVERROR_EOF || ret == AVERROR(libc::EAGAIN) { + break; + } + return Err(Error::msg(format!("Failed to decode {}", ret))); + } + + (*frame).pict_type = AV_PICTURE_TYPE_NONE; // encoder prints warnings + pkgs.push((frame, stream)); + } + Ok(pkgs) + } else { + Ok(vec![]) + } + } +} diff --git a/src/demux.rs b/src/demux.rs new file mode 100644 index 0000000..a850527 --- /dev/null +++ b/src/demux.rs @@ -0,0 +1,318 @@ +use std::ffi::CStr; +use std::time::Instant; +use std::{ptr, slice}; + +use anyhow::Error; +use ffmpeg_sys_the_third::*; + +use crate::get_ffmpeg_error_msg; +use crate::return_ffmpeg_error; +use std::fmt::{Display, Formatter}; +use std::io::{Read, Write}; +use std::mem::transmute; + +unsafe extern "C" fn read_data( + opaque: *mut libc::c_void, + dst_buffer: *mut libc::c_uchar, + size: libc::c_int, +) -> libc::c_int { + let buffer: *mut Box = opaque.cast(); + /// we loop until there is enough data to fill [size] + let mut dst_slice: &mut [u8] = slice::from_raw_parts_mut(dst_buffer, size as usize); + let mut w_total = 0usize; + loop { + return match (*buffer).read(dst_slice) { + Ok(v) => { + w_total += v; + if w_total != size as usize { + dst_slice = &mut dst_slice[v..]; + continue; + } + size + } + Err(e) => { + eprintln!("read_data {}", e); + AVERROR_EOF + } + }; + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct DemuxerInfo { + pub bitrate: usize, + pub duration: f32, + pub channels: Vec, +} + +unsafe impl Send for DemuxerInfo {} +unsafe impl Sync for DemuxerInfo {} + +impl DemuxerInfo { + pub fn best_stream(&self, t: StreamChannelType) -> Option<&StreamInfoChannel> { + self.channels + .iter() + .filter(|a| a.channel_type == t) + .reduce(|acc, channel| { + if channel.best_metric() > acc.best_metric() { + channel + } else { + acc + } + }) + } + + pub fn best_video(&self) -> Option<&StreamInfoChannel> { + self.best_stream(StreamChannelType::Video) + } + + pub fn best_audio(&self) -> Option<&StreamInfoChannel> { + self.best_stream(StreamChannelType::Audio) + } + + pub fn best_subtitle(&self) -> Option<&StreamInfoChannel> { + self.best_stream(StreamChannelType::Subtitle) + } + + pub unsafe fn is_best_stream(&self, stream: *mut AVStream) -> bool { + match (*(*stream).codecpar).codec_type { + AVMediaType::AVMEDIA_TYPE_VIDEO => { + (*stream).index == self.best_video().map_or(usize::MAX, |r| r.index) as libc::c_int + } + AVMediaType::AVMEDIA_TYPE_AUDIO => { + (*stream).index == self.best_audio().map_or(usize::MAX, |r| r.index) as libc::c_int + } + AVMediaType::AVMEDIA_TYPE_SUBTITLE => { + (*stream).index + == self.best_subtitle().map_or(usize::MAX, |r| r.index) as libc::c_int + } + _ => false, + } + } +} + +impl Display for DemuxerInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Demuxer Info:")?; + for c in &self.channels { + write!(f, "\n{}", c)?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum StreamChannelType { + Video, + Audio, + Subtitle, +} + +impl Display for StreamChannelType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + StreamChannelType::Video => "video", + StreamChannelType::Audio => "audio", + StreamChannelType::Subtitle => "subtitle", + } + ) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct StreamInfoChannel { + pub index: usize, + pub channel_type: StreamChannelType, + pub codec: usize, + pub width: usize, + pub height: usize, + pub fps: f32, + pub sample_rate: usize, + pub format: usize, +} + +impl StreamInfoChannel { + pub fn best_metric(&self) -> f32 { + match self.channel_type { + StreamChannelType::Video => self.width as f32 * self.height as f32 * self.fps, + StreamChannelType::Audio => self.sample_rate as f32, + StreamChannelType::Subtitle => 999. - self.index as f32, + } + } +} + +impl Display for StreamInfoChannel { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let codec_name = unsafe { CStr::from_ptr(avcodec_get_name(transmute(self.codec as i32))) }; + write!( + f, + "{} #{}: codec={},size={}x{},fps={}", + self.channel_type, + self.index, + codec_name.to_str().unwrap(), + self.width, + self.height, + self.fps + ) + } +} + +pub struct Demuxer<'a> { + ctx: *mut AVFormatContext, + input: String, + started: Instant, + buffer: Option>, +} + +unsafe impl Send for Demuxer<'_> {} + +unsafe impl Sync for Demuxer<'_> {} + +impl Demuxer<'_> { + pub fn new(input: &str) -> Self { + unsafe { + let ps = avformat_alloc_context(); + Self { + ctx: ps, + input: input.to_string(), + started: Instant::now(), + buffer: None, + } + } + } + + pub fn new_custom_io(reader: T) -> Self { + unsafe { + let ps = avformat_alloc_context(); + (*ps).flags |= AVFMT_FLAG_CUSTOM_IO; + + let buffer = Box::new(reader); + Self { + ctx: ps, + input: String::new(), + started: Instant::now(), + buffer: Some(buffer), + } + } + } + + unsafe fn open_input(&mut self) -> libc::c_int { + if let Some(mut buffer) = self.buffer.take() { + const BUFFER_SIZE: usize = 4096; + let pb = avio_alloc_context( + av_mallocz(BUFFER_SIZE) as *mut libc::c_uchar, + BUFFER_SIZE as libc::c_int, + 0, + ptr::addr_of_mut!(buffer) as _, + Some(read_data), + None, + None, + ); + (*self.ctx).pb = pb; + avformat_open_input( + &mut self.ctx, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + } else { + avformat_open_input( + &mut self.ctx, + format!("{}\0", self.input).as_ptr() as *const libc::c_char, + ptr::null_mut(), + ptr::null_mut(), + ) + } + } + + pub unsafe fn probe_input(&mut self) -> Result { + let ret = self.open_input(); + return_ffmpeg_error!(ret); + + if avformat_find_stream_info(self.ctx, ptr::null_mut()) < 0 { + return Err(Error::msg("Could not find stream info")); + } + av_dump_format(self.ctx, 0, ptr::null_mut(), 0); + + let mut channel_infos = vec![]; + + for n in 0..(*self.ctx).nb_streams as usize { + let stream = *(*self.ctx).streams.add(n); + match (*(*stream).codecpar).codec_type { + AVMediaType::AVMEDIA_TYPE_VIDEO => { + channel_infos.push(StreamInfoChannel { + index: (*stream).index as usize, + codec: (*(*stream).codecpar).codec_id as usize, + channel_type: StreamChannelType::Video, + width: (*(*stream).codecpar).width as usize, + height: (*(*stream).codecpar).height as usize, + fps: av_q2d((*stream).avg_frame_rate) as f32, + format: (*(*stream).codecpar).format as usize, + sample_rate: 0, + }); + } + AVMediaType::AVMEDIA_TYPE_AUDIO => { + channel_infos.push(StreamInfoChannel { + index: (*stream).index as usize, + codec: (*(*stream).codecpar).codec_id as usize, + channel_type: StreamChannelType::Audio, + width: (*(*stream).codecpar).width as usize, + height: (*(*stream).codecpar).height as usize, + fps: 0.0, + format: (*(*stream).codecpar).format as usize, + sample_rate: (*(*stream).codecpar).sample_rate as usize, + }); + } + AVMediaType::AVMEDIA_TYPE_SUBTITLE => { + channel_infos.push(StreamInfoChannel { + index: (*stream).index as usize, + codec: (*(*stream).codecpar).codec_id as usize, + channel_type: StreamChannelType::Subtitle, + width: 0, + height: 0, + fps: 0.0, + format: 0, + sample_rate: 0, + }); + } + AVMediaType::AVMEDIA_TYPE_ATTACHMENT => {} + AVMediaType::AVMEDIA_TYPE_NB => {} + _ => {} + } + } + + let info = DemuxerInfo { + duration: (*self.ctx).duration as f32 / AV_TIME_BASE as f32, + bitrate: (*self.ctx).bit_rate as usize, + channels: channel_infos, + }; + Ok(info) + } + + pub unsafe fn get_packet(&mut self) -> Result<(*mut AVPacket, *mut AVStream), Error> { + let pkt: *mut AVPacket = av_packet_alloc(); + let ret = av_read_frame(self.ctx, pkt); + if ret == AVERROR_EOF { + return Ok((ptr::null_mut(), ptr::null_mut())); + } + if ret < 0 { + let msg = get_ffmpeg_error_msg(ret); + return Err(Error::msg(msg)); + } + let stream = *(*self.ctx).streams.add((*pkt).stream_index as usize); + let pkg = (pkt, stream); + Ok(pkg) + } +} + +impl Drop for Demuxer<'_> { + fn drop(&mut self) { + unsafe { + avformat_free_context(self.ctx); + self.ctx = ptr::null_mut(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c4d55c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,25 @@ +use ffmpeg_sys_the_third::av_make_error_string; +use std::ffi::CStr; + +mod decode; +mod demux; +mod resample; +mod scale; + +#[macro_export(crate)] +macro_rules! return_ffmpeg_error { + ($x:expr) => { + if $x < 0 { + return Err(Error::msg(get_ffmpeg_error_msg($x))); + } + }; +} + +fn get_ffmpeg_error_msg(ret: libc::c_int) -> String { + unsafe { + const BUF_SIZE: usize = 512; + let mut buf: [libc::c_char; BUF_SIZE] = [0; BUF_SIZE]; + av_make_error_string(buf.as_mut_ptr(), BUF_SIZE, ret); + String::from(CStr::from_ptr(buf.as_ptr()).to_str().unwrap()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/resample.rs b/src/resample.rs new file mode 100644 index 0000000..47181cd --- /dev/null +++ b/src/resample.rs @@ -0,0 +1,75 @@ +use crate::get_ffmpeg_error_msg; +use crate::return_ffmpeg_error; +use anyhow::Error; +use ffmpeg_sys_the_third::{ + av_channel_layout_default, av_frame_alloc, av_frame_copy_props, av_frame_free, + swr_alloc_set_opts2, swr_convert_frame, swr_init, AVChannelLayout, AVFrame, AVSampleFormat, + SwrContext, +}; +use libc::malloc; +use std::mem::transmute; +use std::ptr; + +pub struct Resample { + format: AVSampleFormat, + sample_rate: u32, + channels: usize, + ctx: *mut SwrContext, +} + +impl Resample { + pub fn new(format: AVSampleFormat, rate: u32, channels: usize) -> Self { + Self { + format, + channels, + sample_rate: rate, + ctx: ptr::null_mut(), + } + } + + unsafe fn setup_swr(&mut self, frame: *mut AVFrame) -> Result<(), Error> { + if !self.ctx.is_null() { + return Ok(()); + } + let layout = malloc(size_of::()) as *mut AVChannelLayout; + av_channel_layout_default(layout, self.channels as libc::c_int); + + let ret = swr_alloc_set_opts2( + &mut self.ctx, + layout, + self.format, + self.sample_rate as libc::c_int, + &(*frame).ch_layout, + transmute((*frame).format), + (*frame).sample_rate, + 0, + ptr::null_mut(), + ); + return_ffmpeg_error!(ret); + + let ret = swr_init(self.ctx); + return_ffmpeg_error!(ret); + + Ok(()) + } + + pub unsafe fn process_frame(&mut self, frame: *mut AVFrame) -> Result<*mut AVFrame, Error> { + self.setup_swr(frame)?; + + let mut out_frame = av_frame_alloc(); + av_frame_copy_props(out_frame, frame); + (*out_frame).sample_rate = self.sample_rate as libc::c_int; + (*out_frame).format = transmute(self.format); + (*out_frame).time_base = (*frame).time_base; + + av_channel_layout_default(&mut (*out_frame).ch_layout, self.channels as libc::c_int); + + let ret = swr_convert_frame(self.ctx, out_frame, frame); + if ret < 0 { + av_frame_free(&mut out_frame); + return Err(Error::msg(get_ffmpeg_error_msg(ret))); + } + + Ok(out_frame) + } +} diff --git a/src/scale.rs b/src/scale.rs new file mode 100644 index 0000000..79741d2 --- /dev/null +++ b/src/scale.rs @@ -0,0 +1,95 @@ +use crate::get_ffmpeg_error_msg; +use std::mem::transmute; +use std::ptr; + +use crate::return_ffmpeg_error; +use anyhow::Error; +use ffmpeg_sys_the_third::{ + av_frame_alloc, av_frame_copy_props, sws_freeContext, sws_getContext, sws_scale_frame, AVFrame, + AVPixelFormat, SwsContext, SWS_BILINEAR, +}; + +pub struct Scaler { + width: u16, + height: u16, + format: AVPixelFormat, + ctx: *mut SwsContext, +} + +unsafe impl Send for Scaler {} + +unsafe impl Sync for Scaler {} + +impl Drop for Scaler { + fn drop(&mut self) { + unsafe { + sws_freeContext(self.ctx); + self.ctx = ptr::null_mut(); + } + } +} + +impl Scaler { + pub fn new(format: AVPixelFormat) -> Self { + Self { + width: 0, + height: 0, + format, + ctx: ptr::null_mut(), + } + } + + unsafe fn setup_scaler( + &mut self, + frame: *const AVFrame, + width: u16, + height: u16, + ) -> Result<(), Error> { + if !self.ctx.is_null() && self.width == width && self.height == height { + return Ok(()); + } + + // clear previous context, before re-creating + if !self.ctx.is_null() { + sws_freeContext(self.ctx); + self.ctx = ptr::null_mut(); + } + + self.width = width; + self.height = height; + self.ctx = sws_getContext( + (*frame).width, + (*frame).height, + transmute((*frame).format), + self.width as libc::c_int, + self.height as libc::c_int, + transmute(self.format), + SWS_BILINEAR, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ); + if self.ctx.is_null() { + return Err(Error::msg("Failed to create scalar context")); + } + Ok(()) + } + + pub unsafe fn process_frame( + &mut self, + frame: *mut AVFrame, + width: u16, + height: u16, + ) -> Result<*mut AVFrame, Error> { + self.setup_scaler(frame, width, height)?; + + let dst_frame = av_frame_alloc(); + let ret = av_frame_copy_props(dst_frame, frame); + return_ffmpeg_error!(ret); + + let ret = sws_scale_frame(self.ctx, dst_frame, frame); + return_ffmpeg_error!(ret); + + Ok(dst_frame) + } +}