Forráskód Böngészése

Add volume normalisation support

Sasha Hilton 7 éve
szülő
commit
f8db550e5e
6 módosított fájl, 91 hozzáadás és 10 törlés
  1. 1 0
      Cargo.lock
  2. 1 0
      playback/Cargo.toml
  3. 4 0
      playback/src/config.rs
  4. 1 0
      playback/src/lib.rs
  5. 77 9
      playback/src/player.rs
  6. 7 1
      src/main.rs

+ 1 - 0
Cargo.lock

@@ -413,6 +413,7 @@ name = "librespot-playback"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
  "alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)",
  "alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)",
+ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",

+ 1 - 0
playback/Cargo.toml

@@ -13,6 +13,7 @@ path = "../metadata"
 [dependencies]
 [dependencies]
 futures = "0.1.8"
 futures = "0.1.8"
 log = "0.3.5"
 log = "0.3.5"
+byteorder = "1.2.1"
 
 
 alsa            = { git = "https://github.com/plietar/rust-alsa", optional = true }
 alsa            = { git = "https://github.com/plietar/rust-alsa", optional = true }
 portaudio-rs    = { version = "0.3.0", optional = true }
 portaudio-rs    = { version = "0.3.0", optional = true }

+ 4 - 0
playback/src/config.rs

@@ -30,6 +30,8 @@ pub struct PlayerConfig {
     pub bitrate: Bitrate,
     pub bitrate: Bitrate,
     pub onstart: Option<String>,
     pub onstart: Option<String>,
     pub onstop: Option<String>,
     pub onstop: Option<String>,
+    pub normalisation: bool,
+    pub normalisation_pregain: f32,
 }
 }
 
 
 impl Default for PlayerConfig {
 impl Default for PlayerConfig {
@@ -38,6 +40,8 @@ impl Default for PlayerConfig {
             bitrate: Bitrate::default(),
             bitrate: Bitrate::default(),
             onstart: None,
             onstart: None,
             onstop: None,
             onstop: None,
+            normalisation: false,
+            normalisation_pregain: 0.0,
         }
         }
     }
     }
 }
 }

+ 1 - 0
playback/src/lib.rs

@@ -1,6 +1,7 @@
 #[macro_use] extern crate log;
 #[macro_use] extern crate log;
 
 
 extern crate futures;
 extern crate futures;
+extern crate byteorder;
 
 
 #[cfg(feature = "alsa-backend")]
 #[cfg(feature = "alsa-backend")]
 extern crate alsa;
 extern crate alsa;

+ 77 - 9
playback/src/player.rs

@@ -1,3 +1,4 @@
+use byteorder::{LittleEndian, ReadBytesExt};
 use futures::sync::oneshot;
 use futures::sync::oneshot;
 use futures::{future, Future};
 use futures::{future, Future};
 use std;
 use std;
@@ -43,6 +44,51 @@ enum PlayerCommand {
     Seek(u32),
     Seek(u32),
 }
 }
 
 
+#[derive(Clone, Copy, Debug)]
+struct NormalisationData {
+    track_gain_db: f32,
+    track_peak: f32,
+    album_gain_db: f32,
+    album_peak: f32,
+}
+
+impl NormalisationData {
+    fn new<T: Read + Seek>(file: &mut AudioDecrypt<T>) -> NormalisationData {
+        file.seek(SeekFrom::Start(144)).unwrap();
+
+        let track_gain_db: f32 = file.read_f32::<LittleEndian>().unwrap();
+        let track_peak: f32 = file.read_f32::<LittleEndian>().unwrap();
+        let album_gain_db: f32 = file.read_f32::<LittleEndian>().unwrap();
+        let album_peak: f32 = file.read_f32::<LittleEndian>().unwrap();
+
+        NormalisationData {
+            track_gain_db: track_gain_db,
+            track_peak: track_peak,
+            album_gain_db: album_gain_db,
+            album_peak: album_peak,
+        }
+    }
+
+    fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
+        let mut normalisation_factor: f32 = 1.0;
+
+        debug!("Normalization Data: {:?}", data);
+
+        if config.normalisation {
+            normalisation_factor = f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0);
+
+            if normalisation_factor * data.track_peak > 1.0 {
+                warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
+                normalisation_factor = 1.0 / data.track_peak;
+            }
+
+            debug!("Applied normalization factor: {}", normalisation_factor);
+        }
+
+        normalisation_factor
+    }
+}
+
 impl Player {
 impl Player {
     pub fn new<F>(config: PlayerConfig, session: Session,
     pub fn new<F>(config: PlayerConfig, session: Session,
                   audio_filter: Option<Box<AudioFilter + Send>>,
                   audio_filter: Option<Box<AudioFilter + Send>>,
@@ -123,10 +169,12 @@ enum PlayerState {
     Paused {
     Paused {
         decoder: Decoder,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
         end_of_track: oneshot::Sender<()>,
+        normalisation_factor: f32,
     },
     },
     Playing {
     Playing {
         decoder: Decoder,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
         end_of_track: oneshot::Sender<()>,
+        normalisation_factor: f32,
     },
     },
 
 
     Invalid,
     Invalid,
@@ -168,10 +216,11 @@ impl PlayerState {
     fn paused_to_playing(&mut self) {
     fn paused_to_playing(&mut self) {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
         match ::std::mem::replace(self, Invalid) {
-            Paused { decoder, end_of_track } => {
+            Paused { decoder, end_of_track, normalisation_factor } => {
                 *self = Playing {
                 *self = Playing {
                     decoder: decoder,
                     decoder: decoder,
                     end_of_track: end_of_track,
                     end_of_track: end_of_track,
+                    normalisation_factor: normalisation_factor,
                 };
                 };
             }
             }
             _ => panic!("invalid state"),
             _ => panic!("invalid state"),
@@ -181,10 +230,11 @@ impl PlayerState {
     fn playing_to_paused(&mut self) {
     fn playing_to_paused(&mut self) {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
         match ::std::mem::replace(self, Invalid) {
-            Playing { decoder, end_of_track } => {
+            Playing { decoder, end_of_track, normalisation_factor } => {
                 *self = Paused {
                 *self = Paused {
                     decoder: decoder,
                     decoder: decoder,
                     end_of_track: end_of_track,
                     end_of_track: end_of_track,
+                    normalisation_factor: normalisation_factor,
                 };
                 };
             }
             }
             _ => panic!("invalid state"),
             _ => panic!("invalid state"),
@@ -228,14 +278,17 @@ impl PlayerInternal {
             }
             }
 
 
             if self.sink_running {
             if self.sink_running {
-                let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state {
+                let mut current_normalisation_factor: f32 = 1.0;
+
+                let packet = if let PlayerState::Playing { ref mut decoder, normalisation_factor, .. } = self.state {
+                    current_normalisation_factor = normalisation_factor;
                     Some(decoder.next_packet().expect("Vorbis error"))
                     Some(decoder.next_packet().expect("Vorbis error"))
                 } else {
                 } else {
                     None
                     None
                 };
                 };
 
 
                 if let Some(packet) = packet {
                 if let Some(packet) = packet {
-                    self.handle_packet(packet);
+                    self.handle_packet(packet, current_normalisation_factor);
                 }
                 }
             }
             }
         }
         }
@@ -259,13 +312,19 @@ impl PlayerInternal {
         self.sink_running = false;
         self.sink_running = false;
     }
     }
 
 
-    fn handle_packet(&mut self, packet: Option<VorbisPacket>) {
+    fn handle_packet(&mut self, packet: Option<VorbisPacket>, normalisation_factor: f32) {
         match packet {
         match packet {
             Some(mut packet) => {
             Some(mut packet) => {
                 if let Some(ref editor) = self.audio_filter {
                 if let Some(ref editor) = self.audio_filter {
                     editor.modify_stream(&mut packet.data_mut())
                     editor.modify_stream(&mut packet.data_mut())
                 };
                 };
 
 
+                if self.config.normalisation && normalisation_factor != 1.0 {
+                    for x in packet.data_mut().iter_mut() {
+                        *x = (*x as f32 * normalisation_factor) as i16;
+                    }
+                }
+
                 if let Err(err) = self.sink.write(&packet.data()) {
                 if let Err(err) = self.sink.write(&packet.data()) {
                     error!("Could not write audio: {}", err);
                     error!("Could not write audio: {}", err);
                     self.stop_sink();
                     self.stop_sink();
@@ -291,7 +350,7 @@ impl PlayerInternal {
                 }
                 }
 
 
                 match self.load_track(track_id, position as i64) {
                 match self.load_track(track_id, position as i64) {
-                    Some(decoder) => {
+                    Some((decoder, normalisation_factor)) => {
                         if play {
                         if play {
                             if !self.state.is_playing() {
                             if !self.state.is_playing() {
                                 self.run_onstart();
                                 self.run_onstart();
@@ -301,6 +360,7 @@ impl PlayerInternal {
                             self.state = PlayerState::Playing {
                             self.state = PlayerState::Playing {
                                 decoder: decoder,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                                 end_of_track: end_of_track,
+                                normalisation_factor: normalisation_factor,
                             };
                             };
                         } else {
                         } else {
                             if self.state.is_playing() {
                             if self.state.is_playing() {
@@ -310,6 +370,7 @@ impl PlayerInternal {
                             self.state = PlayerState::Paused {
                             self.state = PlayerState::Paused {
                                 decoder: decoder,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                                 end_of_track: end_of_track,
+                                normalisation_factor: normalisation_factor,
                             };
                             };
                         }
                         }
                     }
                     }
@@ -402,7 +463,7 @@ impl PlayerInternal {
         }
         }
     }
     }
 
 
-    fn load_track(&self, track_id: SpotifyId, position: i64) -> Option<Decoder> {
+    fn load_track(&self, track_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
         let track = Track::get(&self.session, track_id).wait().unwrap();
         let track = Track::get(&self.session, track_id).wait().unwrap();
 
 
         info!("Loading track \"{}\"", track.name);
         info!("Loading track \"{}\"", track.name);
@@ -432,7 +493,14 @@ impl PlayerInternal {
         let key = self.session.audio_key().request(track.id, file_id).wait().unwrap();
         let key = self.session.audio_key().request(track.id, file_id).wait().unwrap();
 
 
         let encrypted_file = AudioFile::open(&self.session, file_id).wait().unwrap();
         let encrypted_file = AudioFile::open(&self.session, file_id).wait().unwrap();
-        let audio_file = Subfile::new(AudioDecrypt::new(key, encrypted_file), 0xa7);
+
+        let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
+
+        let normalisation_data = NormalisationData::new(&mut decrypted_file);
+
+        let normalisation_factor: f32 = NormalisationData::get_factor(&self.config, normalisation_data);
+
+        let audio_file = Subfile::new(decrypted_file, 0xa7);
 
 
         let mut decoder = VorbisDecoder::new(audio_file).unwrap();
         let mut decoder = VorbisDecoder::new(audio_file).unwrap();
 
 
@@ -443,7 +511,7 @@ impl PlayerInternal {
 
 
         info!("Track \"{}\" loaded", track.name);
         info!("Track \"{}\" loaded", track.name);
 
 
-        Some(decoder)
+        Some((decoder, normalisation_factor))
     }
     }
 }
 }
 
 

+ 7 - 1
src/main.rs

@@ -102,7 +102,9 @@ fn setup(args: &[String]) -> Setup {
         .optopt("", "device", "Audio device to use. Use '?' to list options if using portaudio", "DEVICE")
         .optopt("", "device", "Audio device to use. Use '?' to list options if using portaudio", "DEVICE")
         .optopt("", "mixer", "Mixer to use", "MIXER")
         .optopt("", "mixer", "Mixer to use", "MIXER")
         .optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
         .optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
-        .optopt("", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT");
+        .optopt("", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT")
+        .optflag("", "enable-volume-normalisation", "Play all tracks at the same volume")
+        .optopt("", "normalisation-pregain", "Pregain (dB) applied by volume normalisation", "PREGAIN");
 
 
     let matches = match opts.parse(&args[1..]) {
     let matches = match opts.parse(&args[1..]) {
         Ok(m) => m,
         Ok(m) => m,
@@ -187,6 +189,10 @@ fn setup(args: &[String]) -> Setup {
             bitrate: bitrate,
             bitrate: bitrate,
             onstart: matches.opt_str("onstart"),
             onstart: matches.opt_str("onstart"),
             onstop: matches.opt_str("onstop"),
             onstop: matches.opt_str("onstop"),
+            normalisation: matches.opt_present("enable-volume-normalisation"),
+            normalisation_pregain: matches.opt_str("normalisation-pregain")
+                .map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
+                .unwrap_or(PlayerConfig::default().normalisation_pregain),
         }
         }
     };
     };