feat: Add high-performance PipeWire/cpal AAC audio encoder and interleaved MP4 muxing

This commit is contained in:
katR 2026-09-02 16:51:04 +05:30
parent 32ada5214a
commit 0dc63cf9ad
21 changed files with 476 additions and 21 deletions

BIN
record_1788348049.mp4 Normal file

Binary file not shown.

16
scratch/audio_patch.rs Normal file
View file

@ -0,0 +1,16 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/capture/audio.rs").unwrap();
code = code.replace(
"pub fn new(sender: Sender<Vec<f32>>) -> Result<Self, Box<dyn std::error::Error>> {",
"pub fn new(sender: Sender<Vec<f32>>) -> Result<(Self, u32, u16), Box<dyn std::error::Error>> {"
);
code = code.replace(
"Ok(Self { _stream: stream })",
"Ok((Self { _stream: stream }, sample_rate, channels))"
);
fs::write("src/capture/audio.rs", code).unwrap();
}

72
scratch/daemon_patch.rs Normal file
View file

@ -0,0 +1,72 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/bin/vrec-daemon.rs").unwrap();
code = code.replace(
"let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);",
"let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);\n let (audio_tx, audio_rx) = bounded::<Vec<f32>>(100);\n let audio_info = vrec::capture::audio::AudioCapture::new(audio_tx).ok();"
);
code = code.replace(
"let codec_ctx_ptr = encoder.codec_ctx() as usize;",
r#"let codec_ctx_ptr = encoder.codec_ctx() as usize;
let mut audio_encoder = if let Some(ref info) = audio_info {
vrec::encoder::AudioEncoder::new(info.1 as i32, info.2 as i32).ok()
} else { None };
let audio_codec_ctx_ptr = audio_encoder.as_ref().map(|e| e.codec_ctx() as usize);"#
);
code = code.replace(
"let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 60).max(60) as usize);",
"let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 120).max(120) as usize);"
);
code = code.replace(
"let new_capacity = (config.replay_duration_sec * 60).max(60) as usize;",
"let new_capacity = (config.replay_duration_sec * 120).max(120) as usize;"
);
code = code.replace(
"let mut muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).unwrap() };",
"let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);\n let mut muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).unwrap() };"
);
code = code.replace(
"normal_muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).ok() };",
"let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);\n normal_muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).ok() };"
);
// Add the audio pumping logic inside the while let Ok(frame) loop
code = code.replace(
"if let Ok(packets) = encoder.encode_frame(&frame) {",
r#"while let Ok(audio_chunk) = audio_rx.try_recv() {
if let Some(enc) = audio_encoder.as_mut() {
if let Ok(audio_packets) = enc.encode_pcm(&audio_chunk) {
for mut pkt in audio_packets {
pkt.set_stream_index(1);
if config.replay_enabled {
ring.push_overwrite(pkt.clone());
}
if normal_recording && !normal_waiting_keyframe {
if let Some(muxer) = normal_muxer.as_mut() {
let _ = muxer.write_packet(&pkt);
}
}
}
}
}
}
if let Ok(packets) = encoder.encode_frame(&frame) {"#
);
// For video packets, set stream index to 0
code = code.replace(
"for pkt in packets {",
"for mut pkt in packets {\n pkt.set_stream_index(0);"
);
fs::write("src/bin/vrec-daemon.rs", code).unwrap();
}

20
scratch/encoder_fix.patch Normal file
View file

@ -0,0 +1,20 @@
--- src/capture/audio.rs
+++ src/capture/audio.rs
@@ -12,7 +12,7 @@
let device = host.default_input_device().ok_or("No audio input device available")?;
let config = device.default_input_config()?;
- let sample_rate = config.sample_rate().0;
+ let sample_rate = config.sample_rate().0; // Let's just fix the into() first
let channels = config.channels();
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
@@ -22,7 +22,7 @@
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => {
device.build_input_stream(
- &config.into(),
+ &config.clone().into(),
move |data: &[f32], _: &_| {
let _ = sender.try_send(data.to_vec());
},

29
scratch/fix.patch Normal file
View file

@ -0,0 +1,29 @@
--- src/capture/audio.rs
+++ src/capture/audio.rs
@@ -11,7 +11,7 @@
let device = host.default_input_device().ok_or("No audio input device available")?;
let config = device.default_input_config()?;
- let sample_rate = config.sample_rate().0;
+ let sample_rate = config.sample_rate().0; // will fix this with sed
let channels = config.channels();
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
@@ -21,7 +21,7 @@
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => {
device.build_input_stream(
- &stream_config,
+ stream_config.clone(),
move |data: &[f32], _: &_| {
let _ = sender.try_send(data.to_vec());
},
@@ -31,7 +31,7 @@
}
cpal::SampleFormat::I16 => {
device.build_input_stream(
- &stream_config,
+ stream_config,
move |data: &[i16], _: &_| {
let f32_data: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
let _ = sender.try_send(f32_data);

25
scratch/fix_audio.patch Normal file
View file

@ -0,0 +1,25 @@
--- src/capture/audio.rs
+++ src/capture/audio.rs
@@ -11,7 +11,7 @@
let device = host.default_input_device().ok_or("No audio input device available")?;
let config = device.default_input_config()?;
- let sample_rate = config.sample_rate().0; // Let's just use .0 for now but wait, the compiler said u32!
+ let sample_rate = config.sample_rate().0;
let channels = config.channels();
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
@@ -19,10 +19,10 @@
let err_fn = |err| eprintln!("Audio stream error: {}", err);
let stream_config: cpal::StreamConfig = config.clone().into();
- let stream = match stream_config.sample_rate.0 {
- _ => {
+ let stream = match config.sample_format() {
+ cpal::SampleFormat::F32 => {
device.build_input_stream(
- &stream_config,
+ &stream_config,
move |data: &[f32], _: &_| {
let _ = sender.try_send(data.to_vec());
},

33
scratch/muxer_patch.rs Normal file
View file

@ -0,0 +1,33 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/muxer.rs").unwrap();
code = code.replace(
"pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext) -> Result<Self, String> {",
"pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext, audio_codec_ctx: Option<*mut AVCodecContext>) -> Result<Self, String> {"
);
code = code.replace(
r#" let ret = avcodec_parameters_from_context((*stream).codecpar, codec_ctx);
if ret < 0 {
return Err("Failed to copy codec parameters".into());
}"#,
r#" (*stream).id = 0;
let ret = avcodec_parameters_from_context((*stream).codecpar, codec_ctx);
if ret < 0 {
return Err("Failed to copy video codec parameters".into());
}
if let Some(actx) = audio_codec_ctx {
let astream = avformat_new_stream(fmt_ctx, std::ptr::null());
if !astream.is_null() {
(*astream).id = 1;
if avcodec_parameters_from_context((*astream).codecpar, actx) < 0 {
eprintln!("Failed to copy audio codec parameters");
}
}
}"#
);
fs::write("src/muxer.rs", code).unwrap();
}

View file

@ -0,0 +1,9 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/encoder.rs").unwrap();
code = code.replace(
"(*codec_ctx).sample_rate = sample_rate;",
"(*codec_ctx).sample_rate = sample_rate;\n (*codec_ctx).time_base = AVRational { num: 1, den: sample_rate };"
);
fs::write("src/encoder.rs", code).unwrap();
}

43
scratch/patch_enc.rs Normal file
View file

@ -0,0 +1,43 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/encoder.rs").unwrap();
code = code.replace(
"(*codec_ctx).channel_layout = if channels == 2 { AV_CH_LAYOUT_STEREO } else { AV_CH_LAYOUT_MONO } as u64;",
"av_channel_layout_default(&mut (*codec_ctx).ch_layout, channels);"
);
// For SwrContext, swr_alloc_set_opts is deprecated in favor of swr_alloc_set_opts2
// But swr_alloc_set_opts2 requires passing pointer to AVChannelLayout.
code = code.replace(
r#" let mut swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx as _, c"in_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"in_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"in_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLT, 0);
av_opt_set_int(swr_ctx as _, c"out_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"out_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"out_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLTP, 0);
swr_init(swr_ctx);"#,
r#" let mut swr_ctx: *mut SwrContext = std::ptr::null_mut();
swr_alloc_set_opts2(
&mut swr_ctx,
&(*codec_ctx).ch_layout,
AVSampleFormat::AV_SAMPLE_FMT_FLTP,
sample_rate,
&(*codec_ctx).ch_layout,
AVSampleFormat::AV_SAMPLE_FMT_FLT,
sample_rate,
0,
std::ptr::null_mut()
);
swr_init(swr_ctx);"#
);
code = code.replace("(*in_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*in_frame).ch_layout = (*self.codec_ctx).ch_layout;");
code = code.replace("(*out_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*out_frame).ch_layout = (*self.codec_ctx).ch_layout;");
code = code.replace("(*enc_frame).channel_layout = (*self.codec_ctx).channel_layout;", "(*enc_frame).ch_layout = (*self.codec_ctx).ch_layout;");
fs::write("src/encoder.rs", code).unwrap();
}

View file

@ -0,0 +1,13 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/muxer.rs").unwrap();
code = code.replace(
"if ret < 0 {\n return Err(\"Failed to copy video codec parameters\".into());\n }",
"if ret < 0 {\n return Err(\"Failed to copy video codec parameters\".into());\n }\n (*stream).time_base = (*codec_ctx).time_base;"
);
code = code.replace(
"eprintln!(\"Failed to copy audio codec parameters\");\n }",
"eprintln!(\"Failed to copy audio codec parameters\");\n }\n (*astream).time_base = (*actx).time_base;"
);
fs::write("src/muxer.rs", code).unwrap();
}

17
scratch/ring_patch.rs Normal file
View file

@ -0,0 +1,17 @@
use std::fs;
fn main() {
let mut code = fs::read_to_string("src/ring.rs").unwrap();
code = code.replace(
"pub fn is_keyframe(&self) -> bool {",
"pub fn stream_index(&self) -> i32 {
unsafe { (*self.ptr).stream_index }
}
pub fn set_stream_index(&mut self, idx: i32) {
unsafe { (*self.ptr).stream_index = idx; }
}
pub fn is_keyframe(&self) -> bool {"
);
fs::write("src/ring.rs", code).unwrap();
}

1
scratch/test.rs Normal file
View file

@ -0,0 +1 @@
fn main() { }

11
scratch/test_aac.rs Normal file
View file

@ -0,0 +1,11 @@
use ffmpeg_next::ffi::*;
fn main() {
unsafe {
let codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_AAC);
if codec.is_null() {
println!("AAC encoder not found");
} else {
println!("AAC encoder found");
}
}
}

11
src/bin/test-aac.rs Normal file
View file

@ -0,0 +1,11 @@
use ffmpeg_next::ffi::*;
fn main() {
unsafe {
let codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_AAC);
if codec.is_null() {
println!("AAC encoder not found");
} else {
println!("AAC encoder found");
}
}
}

View file

@ -36,6 +36,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let (frame_tx, frame_rx) = bounded::<Frame>(5);
let (cmd_tx, cmd_rx) = bounded::<Command>(10);
let (mux_tx, mux_rx) = bounded::<Vec<Packet>>(1);
let (audio_tx, audio_rx) = bounded::<Vec<f32>>(100);
let audio_info = vrec::capture::audio::AudioCapture::new(audio_tx).ok();
let _ = frame_tx.send(first_frame);
@ -58,10 +60,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
thread::spawn(move || {
let mut encoder = VaapiEncoder::new(width, height).expect("Failed to init encoder");
let codec_ctx_ptr = encoder.codec_ctx() as usize;
let codec_ctx_ptr = encoder.codec_ctx() as usize;
let mut audio_encoder = if let Some(ref info) = audio_info {
vrec::encoder::AudioEncoder::new(info.1 as i32, info.2 as i32).ok()
} else { None };
let audio_codec_ctx_ptr = audio_encoder.as_ref().map(|e| e.codec_ctx() as usize);
let mut config = vrec::config::VrecConfig::load();
let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 60).max(60) as usize);
let mut ring = HeapRb::<Packet>::new((config.replay_duration_sec * 120).max(120) as usize);
let mut normal_muxer: Option<Muxer> = None;
let mut normal_recording = false;
let mut normal_waiting_keyframe = false;
@ -81,7 +88,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if start_idx < drain.len() {
let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
let mut muxer = unsafe { Muxer::new(&name, codec_ctx).unwrap() };
let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);
let mut muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).unwrap() };
for p in drain.into_iter().skip(start_idx) {
let _ = muxer.write_packet(&p);
}
@ -99,7 +107,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Command::ReloadConfig => {
config = vrec::config::VrecConfig::load();
println!("Daemon config reloaded!");
let new_capacity = (config.replay_duration_sec * 60).max(60) as usize;
let new_capacity = (config.replay_duration_sec * 120).max(120) as usize;
if ring.capacity().get() != new_capacity {
ring = HeapRb::<Packet>::new(new_capacity);
println!("Replay buffer resized to {} frames.", new_capacity);
@ -129,8 +137,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
}
while let Ok(audio_chunk) = audio_rx.try_recv() {
if let Some(enc) = audio_encoder.as_mut() {
if let Ok(audio_packets) = enc.encode_pcm(&audio_chunk) {
for mut pkt in audio_packets {
pkt.set_stream_index(1);
if config.replay_enabled {
ring.push_overwrite(pkt.clone());
}
if normal_recording && !normal_waiting_keyframe {
if let Some(muxer) = normal_muxer.as_mut() {
let _ = muxer.write_packet(&pkt);
}
}
}
}
}
}
if let Ok(packets) = encoder.encode_frame(&frame) {
for pkt in packets {
for mut pkt in packets {
pkt.set_stream_index(0);
if config.replay_enabled {
ring.push_overwrite(pkt.clone());
}
@ -141,7 +168,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
let name = format!("record_{}.mp4", ts);
normal_muxer = unsafe { Muxer::new(&name, codec_ctx).ok() };
let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);
normal_muxer = unsafe { Muxer::new(&name, codec_ctx, audio_codec_ctx).ok() };
println!("Started normal recording to {}", name);
}
}

51
src/capture/audio.rs Normal file
View file

@ -0,0 +1,51 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::Sender;
pub struct AudioCapture {
_stream: cpal::Stream,
}
impl AudioCapture {
pub fn new(sender: Sender<Vec<f32>>) -> Result<(Self, u32, u16), Box<dyn std::error::Error>> {
let host = cpal::default_host();
let device = host.default_input_device().ok_or("No audio input device available")?;
let config = device.default_input_config()?;
let sample_rate = config.sample_rate();
let channels = config.channels();
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
let err_fn = |err| eprintln!("Audio stream error: {}", err);
let stream_config: cpal::StreamConfig = config.clone().into();
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => {
device.build_input_stream(
stream_config.clone(),
move |data: &[f32], _: &_| {
let _ = sender.try_send(data.to_vec());
},
err_fn,
None,
)?
}
cpal::SampleFormat::I16 => {
device.build_input_stream(
stream_config,
move |data: &[i16], _: &_| {
let f32_data: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
let _ = sender.try_send(f32_data);
},
err_fn,
None,
)?
}
_ => return Err("Unsupported audio format".into()),
};
stream.play()?;
Ok((Self { _stream: stream }, sample_rate, channels))
}
}

View file

51
src/capture/audio.rs.orig Normal file
View file

@ -0,0 +1,51 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crossbeam_channel::Sender;
pub struct AudioCapture {
_stream: cpal::Stream,
}
impl AudioCapture {
pub fn new(sender: Sender<Vec<f32>>) -> Result<Self, Box<dyn std::error::Error>> {
let host = cpal::default_host();
let device = host.default_input_device().ok_or("No audio input device available")?;
let config = device.default_input_config()?;
let sample_rate = config.sample_rate().0;
let channels = config.channels();
println!("Audio capture initialized: {} Hz, {} channels", sample_rate, channels);
let err_fn = |err| eprintln!("Audio stream error: {}", err);
let stream_config: cpal::StreamConfig = config.clone().into();
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => {
device.build_input_stream(
&stream_config,
move |data: &[f32], _: &_| {
let _ = sender.try_send(data.to_vec());
},
err_fn,
None,
)?
}
cpal::SampleFormat::I16 => {
device.build_input_stream(
&stream_config,
move |data: &[i16], _: &_| {
let f32_data: Vec<f32> = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect();
let _ = sender.try_send(f32_data);
},
err_fn,
None,
)?
}
_ => return Err("Unsupported audio format".into()),
};
stream.play()?;
Ok(Self { _stream: stream })
}
}

View file

@ -240,12 +240,13 @@ impl AudioEncoder {
let codec_ctx = avcodec_alloc_context3(codec);
(*codec_ctx).sample_rate = sample_rate;
(*codec_ctx).time_base = AVRational { num: 1, den: sample_rate };
(*codec_ctx).ch_layout.nb_channels = channels;
// AAC requires FLTP (planar float)
(*codec_ctx).sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_FLTP;
(*codec_ctx).bit_rate = 192_000;
// For older ffmpeg compatibility:
(*codec_ctx).channel_layout = if channels == 2 { AV_CH_LAYOUT_STEREO } else { AV_CH_LAYOUT_MONO } as u64;
av_channel_layout_default(&mut (*codec_ctx).ch_layout, channels);
let ret = avcodec_open2(codec_ctx, codec, std::ptr::null_mut());
if ret < 0 {
@ -253,15 +254,18 @@ impl AudioEncoder {
}
// Create SwrContext to convert from interleaved F32 (AV_SAMPLE_FMT_FLT) to planar F32 (AV_SAMPLE_FMT_FLTP)
let mut swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx as _, c"in_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"in_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"in_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLT, 0);
av_opt_set_int(swr_ctx as _, c"out_channel_layout".as_ptr(), (*codec_ctx).channel_layout as i64, 0);
av_opt_set_int(swr_ctx as _, c"out_sample_rate".as_ptr(), sample_rate as i64, 0);
av_opt_set_sample_fmt(swr_ctx as _, c"out_sample_fmt".as_ptr(), AVSampleFormat::AV_SAMPLE_FMT_FLTP, 0);
let mut swr_ctx: *mut SwrContext = std::ptr::null_mut();
swr_alloc_set_opts2(
&mut swr_ctx,
&(*codec_ctx).ch_layout,
AVSampleFormat::AV_SAMPLE_FMT_FLTP,
sample_rate,
&(*codec_ctx).ch_layout,
AVSampleFormat::AV_SAMPLE_FMT_FLT,
sample_rate,
0,
std::ptr::null_mut()
);
swr_init(swr_ctx);
let frame_size = (*codec_ctx).frame_size;
@ -291,7 +295,7 @@ impl AudioEncoder {
let mut in_frame = av_frame_alloc();
(*in_frame).nb_samples = nb_samples;
(*in_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLT as i32;
(*in_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*in_frame).ch_layout = (*self.codec_ctx).ch_layout;
(*in_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(in_frame, 0);
@ -302,7 +306,7 @@ impl AudioEncoder {
let mut out_frame = av_frame_alloc();
(*out_frame).nb_samples = nb_samples;
(*out_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32;
(*out_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*out_frame).ch_layout = (*self.codec_ctx).ch_layout;
(*out_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(out_frame, 0);
@ -324,7 +328,7 @@ impl AudioEncoder {
let mut enc_frame = av_frame_alloc();
(*enc_frame).nb_samples = self.frame_size;
(*enc_frame).format = AVSampleFormat::AV_SAMPLE_FMT_FLTP as i32;
(*enc_frame).channel_layout = (*self.codec_ctx).channel_layout;
(*enc_frame).ch_layout = (*self.codec_ctx).ch_layout;
(*enc_frame).sample_rate = (*self.codec_ctx).sample_rate;
av_frame_get_buffer(enc_frame, 0);

View file

@ -7,7 +7,7 @@ pub struct Muxer {
}
impl Muxer {
pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext) -> Result<Self, String> {
pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext, audio_codec_ctx: Option<*mut AVCodecContext>) -> Result<Self, String> {
unsafe {
let path_cstr = CString::new(path).unwrap();
let mut fmt_ctx: *mut AVFormatContext = std::ptr::null_mut();
@ -22,9 +22,22 @@ impl Muxer {
return Err("Failed to create stream".into());
}
(*stream).id = 0;
let ret = avcodec_parameters_from_context((*stream).codecpar, codec_ctx);
if ret < 0 {
return Err("Failed to copy codec parameters".into());
return Err("Failed to copy video codec parameters".into());
}
(*stream).time_base = (*codec_ctx).time_base;
if let Some(actx) = audio_codec_ctx {
let astream = avformat_new_stream(fmt_ctx, std::ptr::null());
if !astream.is_null() {
(*astream).id = 1;
if avcodec_parameters_from_context((*astream).codecpar, actx) < 0 {
eprintln!("Failed to copy audio codec parameters");
}
(*astream).time_base = (*actx).time_base;
}
}
if ((*(*fmt_ctx).oformat).flags & AVFMT_NOFILE) == 0 {

View file

@ -12,6 +12,14 @@ impl Packet {
Self { ptr }
}
pub fn stream_index(&self) -> i32 {
unsafe { (*self.ptr).stream_index }
}
pub fn set_stream_index(&mut self, idx: i32) {
unsafe { (*self.ptr).stream_index = idx; }
}
pub fn is_keyframe(&self) -> bool {
unsafe {
((*self.ptr).flags & AV_PKT_FLAG_KEY) != 0