feat: Add high-performance PipeWire/cpal AAC audio encoder and interleaved MP4 muxing
This commit is contained in:
parent
32ada5214a
commit
0dc63cf9ad
21 changed files with 476 additions and 21 deletions
BIN
record_1788348049.mp4
Normal file
BIN
record_1788348049.mp4
Normal file
Binary file not shown.
16
scratch/audio_patch.rs
Normal file
16
scratch/audio_patch.rs
Normal 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
72
scratch/daemon_patch.rs
Normal 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
20
scratch/encoder_fix.patch
Normal 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
29
scratch/fix.patch
Normal 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
25
scratch/fix_audio.patch
Normal 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
33
scratch/muxer_patch.rs
Normal 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();
|
||||
}
|
||||
9
scratch/patch_audio_timebase.rs
Normal file
9
scratch/patch_audio_timebase.rs
Normal 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
43
scratch/patch_enc.rs
Normal 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();
|
||||
}
|
||||
13
scratch/patch_muxer_timebase.rs
Normal file
13
scratch/patch_muxer_timebase.rs
Normal 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
17
scratch/ring_patch.rs
Normal 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
1
scratch/test.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
fn main() { }
|
||||
11
scratch/test_aac.rs
Normal file
11
scratch/test_aac.rs
Normal 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
11
src/bin/test-aac.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
51
src/capture/audio.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
0
src/capture/audio.rs.obOCNTS
Normal file
0
src/capture/audio.rs.obOCNTS
Normal file
51
src/capture/audio.rs.orig
Normal file
51
src/capture/audio.rs.orig
Normal 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 })
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
17
src/muxer.rs
17
src/muxer.rs
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue