From 0dc63cf9ad7727b230eb6c9298aafdd9b035125c Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Wed, 2 Sep 2026 16:51:04 +0530 Subject: [PATCH] feat: Add high-performance PipeWire/cpal AAC audio encoder and interleaved MP4 muxing --- record_1788348049.mp4 | Bin 0 -> 6513 bytes scratch/audio_patch.rs | 16 +++++++ scratch/daemon_patch.rs | 72 ++++++++++++++++++++++++++++++++ scratch/encoder_fix.patch | 20 +++++++++ scratch/fix.patch | 29 +++++++++++++ scratch/fix_audio.patch | 25 +++++++++++ scratch/muxer_patch.rs | 33 +++++++++++++++ scratch/patch_audio_timebase.rs | 9 ++++ scratch/patch_enc.rs | 43 +++++++++++++++++++ scratch/patch_muxer_timebase.rs | 13 ++++++ scratch/ring_patch.rs | 17 ++++++++ scratch/test.rs | 1 + scratch/test_aac.rs | 11 +++++ src/bin/test-aac.rs | 11 +++++ src/bin/vrec-daemon.rs | 40 +++++++++++++++--- src/capture/audio.rs | 51 ++++++++++++++++++++++ src/capture/audio.rs.obOCNTS | 0 src/capture/audio.rs.orig | 51 ++++++++++++++++++++++ src/encoder.rs | 30 +++++++------ src/muxer.rs | 17 +++++++- src/ring.rs | 8 ++++ 21 files changed, 476 insertions(+), 21 deletions(-) create mode 100644 record_1788348049.mp4 create mode 100644 scratch/audio_patch.rs create mode 100644 scratch/daemon_patch.rs create mode 100644 scratch/encoder_fix.patch create mode 100644 scratch/fix.patch create mode 100644 scratch/fix_audio.patch create mode 100644 scratch/muxer_patch.rs create mode 100644 scratch/patch_audio_timebase.rs create mode 100644 scratch/patch_enc.rs create mode 100644 scratch/patch_muxer_timebase.rs create mode 100644 scratch/ring_patch.rs create mode 100644 scratch/test.rs create mode 100644 scratch/test_aac.rs create mode 100644 src/bin/test-aac.rs create mode 100644 src/capture/audio.rs create mode 100644 src/capture/audio.rs.obOCNTS create mode 100644 src/capture/audio.rs.orig diff --git a/record_1788348049.mp4 b/record_1788348049.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..80652d0491887b1d1c937f748b072152c98ace2e GIT binary patch literal 6513 zcmeHMYe*DP6h1TSW42baT6;?EirP{sZkb6WtQhoAie6@v)acB*OFO&U?zHs!u@zQW zKPrD^Wcnj8D2jrpKq@OLqKGm`=&^!?zrr-gpw~GwyE-Nn24&D3=6?6gIrpA>=euW@ zxpx4-;*@vz-9euOzyj4+=J25^yX4Qa0|4g?h$290pCs@y-cP9(Of%2Uax7%V_p|~S zuouF+X=AxI^v1c~7UTjj&Emmu(e>Vam6xa6kDakSYB5Jvn!il0XsR1^a-Xf|ep?0g z8n_Z}tzY`GI5V|m1uKT5cDn^&q90&P1YmSGK=K8EltzGYSf-)R76w>EZ4V+aDVk{7 z$h!U$1|}e_P;9Md*G(1nUowT`{lCRx(G5%<&RQRbVQQ+cwmJ7kI^++%&s?8U;&yl! z?8>{py+(@K`z-CDY7_=|G- z+OPYhDw#5ZPpc2DGoUk|GoUk|GoUk|Gw>%EU>~Vltx@_`>%V`ur>z$4-c8Eeb%Fr1 z@O69mtgy*yN+s(lHGJ}ZgYSR2OKkU^qVL}SD&~-+1l2x5hM@Y=gn&qGNXT#67Vub9omm}} zD?O;656Z#V;!vAYbbyaFi8iKUNDLzdOHidkGNgrS*5D+h1YjOUEoSie|7*w3J_9i7 zSAfrbeH6i5DF+mO<1-vU1;rAS74AtV&N(bJiP%KU_&BsSBr$s$+NxAjzm0N$`~~RS z8TC+K=MoV&S(5GLQ9oNmOcbTeWM7c@W_Jexki! hX1moS$~YtYC}0|XE1@716iWG!Gk>) -> Result> {", + "pub fn new(sender: Sender>) -> Result<(Self, u32, u16), Box> {" + ); + + code = code.replace( + "Ok(Self { _stream: stream })", + "Ok((Self { _stream: stream }, sample_rate, channels))" + ); + + fs::write("src/capture/audio.rs", code).unwrap(); +} diff --git a/scratch/daemon_patch.rs b/scratch/daemon_patch.rs new file mode 100644 index 0000000..0e966ef --- /dev/null +++ b/scratch/daemon_patch.rs @@ -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::>(1);", + "let (mux_tx, mux_rx) = bounded::>(1);\n let (audio_tx, audio_rx) = bounded::>(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::::new((config.replay_duration_sec * 60).max(60) as usize);", + "let mut ring = HeapRb::::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(); +} diff --git a/scratch/encoder_fix.patch b/scratch/encoder_fix.patch new file mode 100644 index 0000000..dc82984 --- /dev/null +++ b/scratch/encoder_fix.patch @@ -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()); + }, diff --git a/scratch/fix.patch b/scratch/fix.patch new file mode 100644 index 0000000..cfbbd8a --- /dev/null +++ b/scratch/fix.patch @@ -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 = data.iter().map(|&s| s as f32 / i16::MAX as f32).collect(); + let _ = sender.try_send(f32_data); diff --git a/scratch/fix_audio.patch b/scratch/fix_audio.patch new file mode 100644 index 0000000..01eb7ab --- /dev/null +++ b/scratch/fix_audio.patch @@ -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()); + }, diff --git a/scratch/muxer_patch.rs b/scratch/muxer_patch.rs new file mode 100644 index 0000000..40d2381 --- /dev/null +++ b/scratch/muxer_patch.rs @@ -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 {", + "pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext, audio_codec_ctx: Option<*mut AVCodecContext>) -> Result {" + ); + + 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(); +} diff --git a/scratch/patch_audio_timebase.rs b/scratch/patch_audio_timebase.rs new file mode 100644 index 0000000..b6c02a4 --- /dev/null +++ b/scratch/patch_audio_timebase.rs @@ -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(); +} diff --git a/scratch/patch_enc.rs b/scratch/patch_enc.rs new file mode 100644 index 0000000..fa9b311 --- /dev/null +++ b/scratch/patch_enc.rs @@ -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(); +} diff --git a/scratch/patch_muxer_timebase.rs b/scratch/patch_muxer_timebase.rs new file mode 100644 index 0000000..efc174b --- /dev/null +++ b/scratch/patch_muxer_timebase.rs @@ -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(); +} diff --git a/scratch/ring_patch.rs b/scratch/ring_patch.rs new file mode 100644 index 0000000..989de6d --- /dev/null +++ b/scratch/ring_patch.rs @@ -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(); +} diff --git a/scratch/test.rs b/scratch/test.rs new file mode 100644 index 0000000..45590d8 --- /dev/null +++ b/scratch/test.rs @@ -0,0 +1 @@ +fn main() { } diff --git a/scratch/test_aac.rs b/scratch/test_aac.rs new file mode 100644 index 0000000..f94c9a2 --- /dev/null +++ b/scratch/test_aac.rs @@ -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"); + } + } +} diff --git a/src/bin/test-aac.rs b/src/bin/test-aac.rs new file mode 100644 index 0000000..f94c9a2 --- /dev/null +++ b/src/bin/test-aac.rs @@ -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"); + } + } +} diff --git a/src/bin/vrec-daemon.rs b/src/bin/vrec-daemon.rs index 401687a..dcc320d 100644 --- a/src/bin/vrec-daemon.rs +++ b/src/bin/vrec-daemon.rs @@ -36,6 +36,8 @@ async fn main() -> Result<(), Box> { let (frame_tx, frame_rx) = bounded::(5); let (cmd_tx, cmd_rx) = bounded::(10); let (mux_tx, mux_rx) = bounded::>(1); + let (audio_tx, audio_rx) = bounded::>(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> { 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::::new((config.replay_duration_sec * 60).max(60) as usize); + let mut ring = HeapRb::::new((config.replay_duration_sec * 120).max(120) as usize); let mut normal_muxer: Option = None; let mut normal_recording = false; let mut normal_waiting_keyframe = false; @@ -81,7 +88,8 @@ async fn main() -> Result<(), Box> { 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> { 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::::new(new_capacity); println!("Replay buffer resized to {} frames.", new_capacity); @@ -129,8 +137,27 @@ async fn main() -> Result<(), Box> { } } + 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> { 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); } } diff --git a/src/capture/audio.rs b/src/capture/audio.rs new file mode 100644 index 0000000..4b11c54 --- /dev/null +++ b/src/capture/audio.rs @@ -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>) -> Result<(Self, u32, u16), Box> { + 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 = 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)) + } +} diff --git a/src/capture/audio.rs.obOCNTS b/src/capture/audio.rs.obOCNTS new file mode 100644 index 0000000..e69de29 diff --git a/src/capture/audio.rs.orig b/src/capture/audio.rs.orig new file mode 100644 index 0000000..356e4a7 --- /dev/null +++ b/src/capture/audio.rs.orig @@ -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>) -> Result> { + 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 = 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 }) + } +} diff --git a/src/encoder.rs b/src/encoder.rs index 0985fa7..ed258ec 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -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); diff --git a/src/muxer.rs b/src/muxer.rs index 7d687ae..f0895f7 100644 --- a/src/muxer.rs +++ b/src/muxer.rs @@ -7,7 +7,7 @@ pub struct Muxer { } impl Muxer { - pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext) -> Result { + pub unsafe fn new(path: &str, codec_ctx: *mut AVCodecContext, audio_codec_ctx: Option<*mut AVCodecContext>) -> Result { 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 { diff --git a/src/ring.rs b/src/ring.rs index 384c69a..8f96b35 100644 --- a/src/ring.rs +++ b/src/ring.rs @@ -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