#1241 Adds user preference to enable/disable audio normalization prior to MP3 encoding/recording. (#1242)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-05-07 07:11:40 -04:00
committed by GitHub
parent f4ab46b79d
commit 459e77390a
6 changed files with 75 additions and 41 deletions

View File

@@ -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
*

View File

@@ -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<byte[]> convert(List<float[]> audioPackets)
{
List<byte[]> converted = new ArrayList<>();
audioPackets = AudioUtils.normalize(audioPackets);
if(mNormalizeAudio)
{
audioPackets = AudioUtils.normalize(audioPackets);
}
if(mResampler != null)
{

View File

@@ -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);
}
/**

View File

@@ -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<MP3Setting> mMP3SettingComboBox;
private ComboBox<InputAudioFormat> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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<byte[]> mp3Frames = converter.convert(audioSegment.getAudioBuffers());
for(byte[] mp3Frame: mp3Frames)
{