]> git.nihav.org Git - nihav-encoder.git/commitdiff
implement volume adjustment for output audio streams
authorKostya Shishkov <kostya.shishkov@gmail.com>
Fri, 13 Feb 2026 20:00:00 +0000 (21:00 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Fri, 13 Feb 2026 20:00:00 +0000 (21:00 +0100)
src/acvt.rs
src/transcoder.rs

index 903c8cfe80703a041cebb04b1b69b552adf3b624..43e18c2d6faa592ef87e0771cbae655721c0f292 100644 (file)
@@ -41,7 +41,35 @@ fn copy_audio<T:Clone+Copy>(dst: &mut [T], dstride: usize,
     }
 }
 
-impl<T:Clone+Copy+From<u8>> AudioQueue<T> {
+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<T:Clone+Copy+From<u8>+ApplyVolume> AudioQueue<T> {
     fn new(channels: usize, rec_size: usize, ileaved: bool) -> Self {
         Self {
             start:      0,
@@ -63,7 +91,7 @@ impl<T:Clone+Copy+From<u8>> AudioQueue<T> {
     }
     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<T>) {
+    fn read(&mut self, src: &NAAudioBuffer<T>, vol: f32) {
         let mut to_copy = src.get_length();
         if self.ileaved {
             to_copy *= self.channels;
@@ -85,13 +113,36 @@ impl<T:Clone+Copy+From<u8>> AudioQueue<T> {
                                     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<T:Clone+Copy+From<u8>> AudioQueue<T> {
         }
         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<T>) {
@@ -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
index a8e0c9b6cabd8cc9a08246aeb8d8dc524318c638..f40092bd075d39271167079401410337f118b5b9 100644 (file)
@@ -83,6 +83,7 @@ pub struct OutputStreamOptions {
     pub enc_params:     EncodeParameters,
     pub enc_name:       String,
     pub enc_opts:       Vec<OptionArgs>,
+    pub volume:         f32,
 }
 
 pub struct DecodeContext {
@@ -101,6 +102,7 @@ pub struct AudioEncodeContext {
     pub cvt:        Option<AudioConverter>,
     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::<f32>() {
+                            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!(),