From 459e77390a055f2361790734d4a82b63f375250b Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Sat, 7 May 2022 07:11:40 -0400 Subject: [PATCH] #1241 Adds user preference to enable/disable audio normalization prior to MP3 encoding/recording. (#1242) Co-authored-by: Dennis Sheirer --- .../audio/broadcast/BroadcastFactory.java | 22 ------------- .../audio/convert/MP3AudioConverter.java | 23 +++++++++----- .../audio/convert/MP3SilenceGenerator.java | 7 ++--- .../preference/mp3/MP3PreferenceEditor.java | 24 +++++++++++++- .../preference/mp3/MP3Preference.java | 31 +++++++++++++++++-- .../dsheirer/record/AudioSegmentRecorder.java | 9 +++--- 6 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java index a28562dc..28bd6199 100644 --- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java +++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java @@ -30,10 +30,8 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1AudioBroadcast import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2AudioStreamingBroadcaster; import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration; -import io.github.dsheirer.audio.convert.IAudioConverter; import io.github.dsheirer.audio.convert.ISilenceGenerator; import io.github.dsheirer.audio.convert.InputAudioFormat; -import io.github.dsheirer.audio.convert.MP3AudioConverter; import io.github.dsheirer.audio.convert.MP3Setting; import io.github.dsheirer.audio.convert.MP3SilenceGenerator; import io.github.dsheirer.preference.UserPreferences; @@ -88,26 +86,6 @@ public class BroadcastFactory return null; } - /** - * Creates an audio convert to convert from 8 kHz PCM audio to the specified format - * - * @param configuration containing the requested output audio format - * @return audio convert or null - */ - public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration, InputAudioFormat inputAudioFormat, - MP3Setting mp3Setting) - { - switch(configuration.getBroadcastFormat()) - { - case MP3: - return new MP3AudioConverter(inputAudioFormat, mp3Setting); - default: - mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name()); - } - - return null; - } - /** * Creates a broadcastAudio configuration for the specified server type and format * diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java b/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java index 2420c5f4..9108e4c2 100644 --- a/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java +++ b/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java @@ -21,17 +21,16 @@ package io.github.dsheirer.audio.convert; import io.github.dsheirer.audio.AudioUtils; import io.github.dsheirer.dsp.filter.resample.RealResampler; import io.github.dsheirer.sample.ConversionUtils; -import net.sourceforge.lame.lowlevel.LameEncoder; -import net.sourceforge.lame.mp3.Lame; -import org.apache.commons.math3.util.FastMath; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import net.sourceforge.lame.lowlevel.LameEncoder; +import net.sourceforge.lame.mp3.Lame; +import org.apache.commons.math3.util.FastMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Converts PCM audio packets to Mono, MP3 encoded audio @@ -46,16 +45,20 @@ public class MP3AudioConverter implements IAudioConverter private byte[] mOutputFramesBuffer; private InputAudioFormat mInputAudioFormat; + private boolean mNormalizeAudio; + /** * Constructs an instance. * * @param inputAudioFormat for the desired input sample rate and bit size (resampled from default 8 kHz as needed) * @param setting to configure the LAME encoder + * @param normalizeAudio to normalize the audio gain prior to encoding */ - public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting) + public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio) { mInputAudioFormat = inputAudioFormat; mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting); + mNormalizeAudio = normalizeAudio; //Resampling is only required if desired input sample rate is not system default of 8kHz if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000) @@ -74,7 +77,11 @@ public class MP3AudioConverter implements IAudioConverter public List convert(List audioPackets) { List converted = new ArrayList<>(); - audioPackets = AudioUtils.normalize(audioPackets); + + if(mNormalizeAudio) + { + audioPackets = AudioUtils.normalize(audioPackets); + } if(mResampler != null) { diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java b/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java index ff1f5b85..35fb06a1 100644 --- a/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java +++ b/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java @@ -18,11 +18,10 @@ */ package io.github.dsheirer.audio.convert; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MP3SilenceGenerator implements ISilenceGenerator { @@ -40,7 +39,7 @@ public class MP3SilenceGenerator implements ISilenceGenerator public MP3SilenceGenerator(InputAudioFormat inputAudioFormat, MP3Setting setting) { mInputAudioFormat = inputAudioFormat; - mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting); + mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting, false); } /** diff --git a/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java index 66f6b74e..f4995601 100644 --- a/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java +++ b/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java @@ -25,6 +25,7 @@ import io.github.dsheirer.preference.UserPreferences; import io.github.dsheirer.preference.mp3.MP3Preference; import javafx.geometry.HPos; import javafx.geometry.Insets; +import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; @@ -44,7 +45,12 @@ public class MP3PreferenceEditor extends HBox private GridPane mEditorPane; private ComboBox mMP3SettingComboBox; private ComboBox mAudioSampleRateComboBox; + private CheckBox mNormalizeAudioCheckBox; + /** + * Constructs an instance + * @param userPreferences + */ public MP3PreferenceEditor(UserPreferences userPreferences) { mMP3Preference = userPreferences.getMP3Preference(); @@ -65,6 +71,9 @@ public class MP3PreferenceEditor extends HBox Label topLabel = new Label("MP3 Encoder Preferences"); mEditorPane.add(topLabel, 0, row++, 2, 1); + + mEditorPane.add(getNormalizeAudioCheckBox(), 1, row++); + Label label = new Label("(LAME) Encoder Setting:"); GridPane.setHalignment(label, HPos.RIGHT); mEditorPane.add(label, 0, row); @@ -76,7 +85,7 @@ public class MP3PreferenceEditor extends HBox mEditorPane.add(getAudioSampleRateComboBox(), 1, row++); Label notice = new Label("Note: sdrtrunk default 8 kHz audio rate is resampled to input sample rate before MP3 encoding"); - mEditorPane.add(notice, 0, row, 2, 1); + mEditorPane.add(notice, 0, row++, 2, 1); } return mEditorPane; @@ -109,4 +118,17 @@ public class MP3PreferenceEditor extends HBox return mAudioSampleRateComboBox; } + + private CheckBox getNormalizeAudioCheckBox() + { + if(mNormalizeAudioCheckBox == null) + { + mNormalizeAudioCheckBox = new CheckBox("Normalize Audio Before Encoding"); + mNormalizeAudioCheckBox.setSelected(mMP3Preference.isNormalizeAudioBeforeEncode()); + mNormalizeAudioCheckBox.onActionProperty().set(event -> + mMP3Preference.setNormalizeAudioBeforeEncode(getNormalizeAudioCheckBox().isSelected())); + } + + return mNormalizeAudioCheckBox; + } } diff --git a/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java b/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java index 4b6788a2..18ce7037 100644 --- a/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java +++ b/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java @@ -24,11 +24,10 @@ import io.github.dsheirer.audio.convert.MP3Setting; import io.github.dsheirer.preference.Preference; import io.github.dsheirer.preference.PreferenceType; import io.github.dsheirer.sample.Listener; +import java.util.prefs.Preferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.prefs.Preferences; - /** * User preferences for playlists */ @@ -36,11 +35,14 @@ public class MP3Preference extends Preference { private static final String PREFERENCE_KEY_AUDIO_MP3_SETTING = "audio.mp3.setting"; private static final String PREFERENCE_KEY_AUDIO_MP3_SAMPLE_RATE = "audio.mp3.sample.rate"; + private static final String PREFERENCE_KEY_AUDIO_MP3_NORMALIZE_BEFORE_ENCODE = "audio.mp3.normalize.before.encode"; private final static Logger mLog = LoggerFactory.getLogger(MP3Preference.class); private Preferences mPreferences = Preferences.userNodeForPackage(MP3Preference.class); private InputAudioFormat mInputAudioFormat; private MP3Setting mMP3Setting; + private Boolean mNormalizeAudio; + /** * Constructs this preference with an update listener * @param updateListener to receive notifications whenever these preferences change @@ -128,4 +130,29 @@ public class MP3Preference extends Preference mPreferences.put(PREFERENCE_KEY_AUDIO_MP3_SAMPLE_RATE, mInputAudioFormat.name()); notifyPreferenceUpdated(); } + + /** + * Indicates if the user prefers to normalize audio before encoding to MP3 + * @return true to normalize + */ + public boolean isNormalizeAudioBeforeEncode() + { + if(mNormalizeAudio == null) + { + mNormalizeAudio = mPreferences.getBoolean(PREFERENCE_KEY_AUDIO_MP3_NORMALIZE_BEFORE_ENCODE, false); + } + + return mNormalizeAudio; + } + + /** + * Sets the preferenced to normalize audio before encoding to MP3. + * @param normalizeAudio true to normalize + */ + public void setNormalizeAudioBeforeEncode(boolean normalizeAudio) + { + mNormalizeAudio = normalizeAudio; + mPreferences.putBoolean(PREFERENCE_KEY_AUDIO_MP3_NORMALIZE_BEFORE_ENCODE, normalizeAudio); + notifyPreferenceUpdated(); + } } diff --git a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java index fd611d99..5139b4dd 100644 --- a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java +++ b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java @@ -29,9 +29,6 @@ import io.github.dsheirer.record.wave.AudioMetadata; import io.github.dsheirer.record.wave.AudioMetadataUtils; import io.github.dsheirer.record.wave.WaveWriter; import io.github.dsheirer.sample.ConversionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -39,6 +36,8 @@ import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Recording utility for audio segments @@ -96,7 +95,9 @@ public class AudioSegmentRecorder InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate(); MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting(); - MP3AudioConverter converter = new MP3AudioConverter(inputAudioFormat, mp3Setting); + boolean normalizeAudio = userPreferences.getMP3Preference().isNormalizeAudioBeforeEncode(); + + MP3AudioConverter converter = new MP3AudioConverter(inputAudioFormat, mp3Setting, normalizeAudio); List mp3Frames = converter.convert(audioSegment.getAudioBuffers()); for(byte[] mp3Frame: mp3Frames) {