From 04e1e7382e82c7540a7dd923213a1a0665814977 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Fri, 13 Feb 2026 21:00:00 +0100 Subject: [PATCH] implement volume adjustment for output audio streams --- src/acvt.rs | 80 ++++++++++++++++++++++++++++++++++++++++++----- src/transcoder.rs | 27 +++++++++++----- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/acvt.rs b/src/acvt.rs index 903c8cf..43e18c2 100644 --- a/src/acvt.rs +++ b/src/acvt.rs @@ -41,7 +41,35 @@ fn copy_audio(dst: &mut [T], dstride: usize, } } -impl> AudioQueue { +trait ApplyVolume { + fn apply_volume(&mut self, vol: f32); +} + +impl ApplyVolume for u8 { + fn apply_volume(&mut self, vol: f32) { + *self = (f32::from(*self) * vol).clamp(0.0, 255.0) as u8; + } +} + +impl ApplyVolume for i16 { + fn apply_volume(&mut self, vol: f32) { + *self = (f32::from(*self) * vol).clamp(-32768.0, 32767.0) as i16; + } +} + +impl ApplyVolume for i32 { + fn apply_volume(&mut self, vol: f32) { + *self = (f64::from(*self) * f64::from(vol)).clamp(-2147483648.0, 2147483647.0) as i32; + } +} + +impl ApplyVolume for f32 { + fn apply_volume(&mut self, vol: f32) { + *self *= vol; + } +} + +impl+ApplyVolume> AudioQueue { fn new(channels: usize, rec_size: usize, ileaved: bool) -> Self { Self { start: 0, @@ -63,7 +91,7 @@ impl> AudioQueue { } fn get_cur_avail(&self) -> usize { self.stride - self.end } fn get_potentially_avail(&self) -> usize { self.stride - self.get_cur_size() } - fn read(&mut self, src: &NAAudioBuffer) { + fn read(&mut self, src: &NAAudioBuffer, vol: f32) { let mut to_copy = src.get_length(); if self.ileaved { to_copy *= self.channels; @@ -85,13 +113,36 @@ impl> AudioQueue { src.get_data().chunks(src.get_stride()))) { dst[..old_len].copy_from_slice(&old[self.start..self.end]); dst[old_len..][..new_len].copy_from_slice(&new[..new_len]); + if vol != 1.0 { + for el in dst[old_len..][..new_len].iter_mut() { + el.apply_volume(vol); + } + } } } else { new_buf[..old_len].copy_from_slice(&self.data[self.start..self.end]); copy_audio(&mut new_buf[old_len..], 1, src.get_data(), src.get_stride(), new_len, self.channels); + if vol != 1.0 { + for el in new_buf[old_len..][..new_len].iter_mut() { + el.apply_volume(vol); + } + } } } else { copy_audio(&mut new_buf, if !self.ileaved { new_stride } else { 1 }, src.get_data(), src.get_stride(), new_len, self.channels); + if vol != 1.0 { + if self.ileaved { + for el in new_buf[..new_len].iter_mut() { + el.apply_volume(vol); + } + } else { + for channel in new_buf.chunks_exact_mut(new_stride) { + for el in channel[self.end..][..new_len / self.channels].iter_mut() { + el.apply_volume(vol); + } + } + } + } } self.data = new_buf; self.stride = new_stride; @@ -102,6 +153,19 @@ impl> AudioQueue { } copy_audio(&mut self.data[self.end..], if !self.ileaved { self.stride } else { 1 }, src.get_data(), src.get_stride(), to_copy, self.channels); + if vol != 1.0 { + if self.ileaved { + for el in self.data[self.end..][..to_copy].iter_mut() { + el.apply_volume(vol); + } + } else { + for channel in self.data.chunks_exact_mut(self.stride) { + for el in channel[self.end..][..to_copy / self.channels].iter_mut() { + el.apply_volume(vol); + } + } + } + } self.end += to_copy; } fn write(&mut self, dbuf: &mut NAAudioBuffer) { @@ -188,18 +252,18 @@ impl AudioConverter { resampler, } } - pub fn queue_frame(&mut self, buf: NABufferType, tinfo: NATimeInfo) -> bool { + pub fn queue_frame(&mut self, buf: NABufferType, tinfo: NATimeInfo, vol: f32) -> bool { let ret = self.resampler.convert_audio_frame(&buf); if let Ok(dbuf) = ret { if self.apts.is_none() && tinfo.get_pts().is_some() { self.apts = tinfo.get_pts(); } match (&mut self.queue, dbuf) { - (AudioDataType::U8(ref mut queue), NABufferType::AudioU8(ref buf)) => queue.read(buf), - (AudioDataType::I16(ref mut queue), NABufferType::AudioI16(ref buf)) => queue.read(buf), - (AudioDataType::I32(ref mut queue), NABufferType::AudioI32(ref buf)) => queue.read(buf), - (AudioDataType::F32(ref mut queue), NABufferType::AudioF32(ref buf)) => queue.read(buf), - (AudioDataType::Packed(ref mut queue), NABufferType::AudioPacked(ref buf)) => queue.read(buf), + (AudioDataType::U8(ref mut queue), NABufferType::AudioU8(ref buf)) => queue.read(buf, vol), + (AudioDataType::I16(ref mut queue), NABufferType::AudioI16(ref buf)) => queue.read(buf, vol), + (AudioDataType::I32(ref mut queue), NABufferType::AudioI32(ref buf)) => queue.read(buf, vol), + (AudioDataType::F32(ref mut queue), NABufferType::AudioF32(ref buf)) => queue.read(buf, vol), + (AudioDataType::Packed(ref mut queue), NABufferType::AudioPacked(ref buf)) => queue.read(buf, vol), _ => unimplemented!(), }; true diff --git a/src/transcoder.rs b/src/transcoder.rs index a8e0c9b..f40092b 100644 --- a/src/transcoder.rs +++ b/src/transcoder.rs @@ -83,6 +83,7 @@ pub struct OutputStreamOptions { pub enc_params: EncodeParameters, pub enc_name: String, pub enc_opts: Vec, + pub volume: f32, } pub struct DecodeContext { @@ -101,6 +102,7 @@ pub struct AudioEncodeContext { pub cvt: Option, pub sainfo: NAAudioInfo, pub dainfo: NAAudioInfo, + pub vol: f32, } // todo better channel map generation @@ -130,7 +132,7 @@ impl EncoderInterface for AudioEncodeContext { let cbuf = if let NABufferType::None = buf { buf } else if let Some(ref mut acvt) = self.cvt { - if !acvt.queue_frame(buf, frm.get_time_information()) { + if !acvt.queue_frame(buf, frm.get_time_information(), self.vol) { println!("error converting audio for stream {}", dst_id); return Ok(false); } @@ -450,7 +452,7 @@ impl Transcoder { let sidx = if let Some(idx) = self.ostr_opts.iter().position(|el| el.id == streamno) { idx } else { - self.ostr_opts.push(OutputStreamOptions {id: streamno, enc_name: String::new(), enc_params: EncodeParameters::default(), enc_opts: Vec::new() }); + self.ostr_opts.push(OutputStreamOptions {id: streamno, enc_name: String::new(), enc_params: EncodeParameters::default(), enc_opts: Vec::new(), volume: 1.0 }); self.ostr_opts.len() - 1 }; let ostr = &mut self.ostr_opts[sidx]; @@ -648,6 +650,17 @@ impl Transcoder { println!("invalid quality value"); } }, + "volume" => { + if let Ok(val) = oval[1].parse::() { + if (0.0..=1024.0).contains(&val) { + ostr.volume = val; + } else { + println!("invalid volume"); + } + } else { + println!("invalid volume"); + } + }, _ => { ostr.enc_opts.push(OptionArgs{ name: oval[0].to_string(), value: Some(oval[1].to_string()) }); }, @@ -871,7 +884,7 @@ impl Transcoder { (NACodecTypeInfo::Audio(sainfo), NACodecTypeInfo::Audio(dainfo)) => { let icodec = istr.get_info().get_name(); if (sainfo == dainfo) && (icodec != "pcm" || oopts.enc_name.as_str() == "pcm") { - Box::new(AudioEncodeContext { encoder, cvt: None, sainfo: *sainfo, dainfo: *dainfo }) + Box::new(AudioEncodeContext { encoder, cvt: None, sainfo: *sainfo, dainfo: *dainfo, vol: oopts.volume }) } else { let dchmap = if let Some(ret) = generate_channel_map(dainfo) { ret @@ -879,7 +892,7 @@ impl Transcoder { return RegisterResult::Failed; }; let acvt = AudioConverter::new(sainfo, dainfo, dchmap); - Box::new(AudioEncodeContext { encoder, cvt: Some(acvt), sainfo: *sainfo, dainfo: *dainfo }) + Box::new(AudioEncodeContext { encoder, cvt: Some(acvt), sainfo: *sainfo, dainfo: *dainfo, vol: oopts.volume }) } }, _ => unreachable!(), @@ -894,7 +907,7 @@ println!("encoder {} is not supported by output (expected {})", istr.id, istr.ge out_sm.add_stream((*istr).clone()); self.encoders.push(OutputMode::Copy(out_id)); } else { - let mut oopts = OutputStreamOptions {id: out_id, enc_name: cname.to_owned(), enc_params: EncodeParameters::default(), enc_opts: Vec::new() }; + let mut oopts = OutputStreamOptions {id: out_id, enc_name: cname.to_owned(), enc_params: EncodeParameters::default(), enc_opts: Vec::new(), volume: 1.0 }; if let Some(ref profile) = self.profile { match istr.get_media_type() { @@ -1003,7 +1016,7 @@ println!("encoder {} is not supported by output (expected {})", istr.id, istr.ge } } if sainfo == &dainfo { - Box::new(AudioEncodeContext { encoder, cvt: None, sainfo: *sainfo, dainfo }) + Box::new(AudioEncodeContext { encoder, cvt: None, sainfo: *sainfo, dainfo, vol: oopts.volume }) } else { let dchmap = if let Some(ret) = generate_channel_map(&dainfo) { ret @@ -1011,7 +1024,7 @@ println!("encoder {} is not supported by output (expected {})", istr.id, istr.ge return RegisterResult::Failed; }; let acvt = AudioConverter::new(sainfo, &dainfo, dchmap); - Box::new(AudioEncodeContext { encoder, cvt: Some(acvt), sainfo: *sainfo, dainfo }) + Box::new(AudioEncodeContext { encoder, cvt: Some(acvt), sainfo: *sainfo, dainfo, vol: oopts.volume }) } }, _ => unreachable!(), -- 2.39.5