diff --git a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java
index d2e993ca..f6b6457b 100644
--- a/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java
+++ b/src/main/java/io/github/dsheirer/alias/action/beep/BeepAction.java
@@ -1,7 +1,6 @@
/*
- * ******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,7 +14,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- * *****************************************************************************
+ * ****************************************************************************
*/
package io.github.dsheirer.alias.action.beep;
@@ -72,7 +71,7 @@ public class BeepAction extends RecurringAction
@Override
public void performAction(Alias alias, IMessage message )
{
- DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
+ DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO);
if(!AudioSystem.isLineSupported(info))
{
@@ -83,7 +82,7 @@ public class BeepAction extends RecurringAction
try
{
Clip clip = (Clip)AudioSystem.getLine(info);
- clip.open(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, mToneBytes, 0, mToneBytes.length);
+ clip.open(AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO, mToneBytes, 0, mToneBytes.length);
clip.start();
}
catch(Exception e)
diff --git a/src/main/java/io/github/dsheirer/audio/AudioFormats.java b/src/main/java/io/github/dsheirer/audio/AudioFormats.java
index c6cbed82..ea901d46 100644
--- a/src/main/java/io/github/dsheirer/audio/AudioFormats.java
+++ b/src/main/java/io/github/dsheirer/audio/AudioFormats.java
@@ -1,3 +1,22 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
package io.github.dsheirer.audio;
import javax.sound.sampled.AudioFormat;
@@ -15,8 +34,12 @@ public class AudioFormats
public static final float IMBE_FRAME_RATE = 50;
public static final float IMBE_SAMPLE_RATE = 50;
- public static final float PCM_8KHZ_RATE = 8000;
- public static final float PCM_48KHZ_RATE = 48000;
+
+ public static final float PCM_8_KHZ_RATE = 8000;
+ public static final float PCM_16_KHZ_RATE = 16000;
+ public static final float PCM_22050_HZ_RATE = 22050;
+ public static final float PCM_44100_HZ_RATE = 44100;
+ public static final float PCM_48_KHZ_RATE = 48000;
public static final int IMBE_FRAME_SIZE_BYTES = 18;
public static final int IMBE_SAMPLE_SIZE_BITS = 144;
@@ -35,47 +58,74 @@ public class AudioFormats
IMBE_FRAME_RATE,
LITTLE_ENDIAN );
- public static AudioFormat PCM_SIGNED_8KHZ_16BITS_MONO =
- new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
- PCM_8KHZ_RATE,
+ public static AudioFormat PCM_SIGNED_8_KHZ_16BITS_MONO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
- PCM_FRAME_SIZE_BYTES_MONO,
- PCM_8KHZ_RATE,
+ PCM_FRAME_SIZE_BYTES_MONO,
+ PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
+ public static AudioFormat PCM_SIGNED_16_KHZ_16BITS_MONO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_16_KHZ_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ PCM_FRAME_SIZE_BYTES_MONO,
+ PCM_16_KHZ_RATE,
+ LITTLE_ENDIAN );
+
+ public static AudioFormat PCM_SIGNED_22050_HZ_16BITS_MONO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_22050_HZ_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ PCM_FRAME_SIZE_BYTES_MONO,
+ PCM_22050_HZ_RATE,
+ LITTLE_ENDIAN );
+
+ public static AudioFormat PCM_SIGNED_44100_HZ_16BITS_MONO =
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_44100_HZ_RATE,
+ PCM_SAMPLE_SIZE_BITS,
+ ONE_CHANNEL,
+ PCM_FRAME_SIZE_BYTES_MONO,
+ PCM_44100_HZ_RATE,
+ LITTLE_ENDIAN );
+
public static AudioFormat PCM_SIGNED_8KHZ_16BITS_STEREO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
- PCM_8KHZ_RATE,
+ PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_STEREO,
- PCM_8KHZ_RATE,
+ PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
- new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
- PCM_48KHZ_RATE,
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_48_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
- PCM_FRAME_SIZE_BYTES_MONO,
- PCM_48KHZ_RATE,
+ PCM_FRAME_SIZE_BYTES_MONO,
+ PCM_48_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_STEREO =
- new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
- PCM_48KHZ_RATE,
+ new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
+ PCM_48_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
- PCM_FRAME_SIZE_BYTES_STEREO,
- PCM_48KHZ_RATE,
+ PCM_FRAME_SIZE_BYTES_STEREO,
+ PCM_48_KHZ_RATE,
LITTLE_ENDIAN );
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM one channel
*/
public static final Line.Info MONO_SOURCE_DATALINE_INFO =
- new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_MONO);
+ new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8_KHZ_16BITS_MONO);
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
diff --git a/src/main/java/io/github/dsheirer/audio/AudioUtils.java b/src/main/java/io/github/dsheirer/audio/AudioUtils.java
index a42f6784..0f0dd223 100644
--- a/src/main/java/io/github/dsheirer/audio/AudioUtils.java
+++ b/src/main/java/io/github/dsheirer/audio/AudioUtils.java
@@ -1,18 +1,21 @@
-/*******************************************************************************
- * sdr-trunk
- * Copyright (C) 2014-2018 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
- * later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see
- *
- ******************************************************************************/
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio;
import io.github.dsheirer.sample.ConversionUtils;
@@ -75,5 +78,4 @@ public class AudioUtils
return stream.toByteArray();
}
-
}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
index 05641152..289d22e3 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingBroadcaster.java
@@ -1,27 +1,26 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2020 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
+import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;
import org.apache.commons.math3.util.FastMath;
@@ -31,6 +30,7 @@ import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledFuture;
@@ -73,12 +73,13 @@ public abstract class AudioStreamingBroadcaster 0)
{
- broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
+ List finalSilence = mSilenceGenerator.generate(mFinalSilencePadding);
+
+ for(byte[] silence: finalSilence)
+ {
+ broadcastAudio(silence);
+ }
+
mFinalSilencePadding = 0;
}
@@ -312,7 +319,11 @@ public abstract class AudioStreamingBroadcaster silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS);
+ for(byte[] silence: silenceFrames)
+ {
+ broadcastAudio(silence);
+ }
}
}
catch(Throwable t)
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java
index 352860ac..864d30e9 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/AudioStreamingManager.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -150,7 +150,7 @@ public class AudioStreamingManager implements Listener
try
{
- AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
+ AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
IdentifierCollection identifierCollectionCopy =
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
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 ad663354..f584d8d7 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastFactory.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2019 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
@@ -33,10 +30,13 @@ 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.AudioSampleRate;
import io.github.dsheirer.audio.convert.IAudioConverter;
import io.github.dsheirer.audio.convert.ISilenceGenerator;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,42 +44,44 @@ public class BroadcastFactory
{
private final static Logger mLog = LoggerFactory.getLogger(BroadcastFactory.class);
- public static final int MP3_MONO_16_KHZ_BITRATE = 16;
- public static final boolean MP3_CONSTANT_BITRATE = false;
-
/**
* Creates an audio streaming broadcaster for the configuration
*
* @param configuration describing the server and audio types
* @return configured broadcaster or null
*/
- public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel)
+ public static AbstractAudioBroadcaster getBroadcaster(BroadcastConfiguration configuration, AliasModel aliasModel,
+ UserPreferences userPreferences)
{
if(configuration != null)
{
- IAudioConverter converter = getAudioConverter(configuration);
+ AudioSampleRate audioSampleRate = userPreferences.getMP3Preference().getAudioSampleRate();
+ MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
- if(converter != null)
+ switch(configuration.getBroadcastServerType())
{
- switch(configuration.getBroadcastServerType())
- {
- case BROADCASTIFY_CALL:
- return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration, aliasModel);
- case BROADCASTIFY:
- return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration, aliasModel);
- case ICECAST_TCP:
- return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, aliasModel);
- case ICECAST_HTTP:
- return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, aliasModel);
- case SHOUTCAST_V1:
- return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, aliasModel);
- case SHOUTCAST_V2:
- return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration, aliasModel);
- case UNKNOWN:
- default:
- mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
- break;
- }
+ case BROADCASTIFY_CALL:
+ return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
+ audioSampleRate, mp3Setting, aliasModel);
+ case BROADCASTIFY:
+ return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
+ audioSampleRate, mp3Setting, aliasModel);
+ case ICECAST_TCP:
+ return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, audioSampleRate,
+ mp3Setting, aliasModel);
+ case ICECAST_HTTP:
+ return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, audioSampleRate,
+ mp3Setting, aliasModel);
+ case SHOUTCAST_V1:
+ return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, audioSampleRate,
+ mp3Setting, aliasModel);
+ case SHOUTCAST_V2:
+ return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
+ audioSampleRate, mp3Setting, aliasModel);
+ case UNKNOWN:
+ default:
+ mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
+ break;
}
}
@@ -92,12 +94,13 @@ public class BroadcastFactory
* @param configuration containing the requested output audio format
* @return audio convert or null
*/
- public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration)
+ public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting)
{
switch(configuration.getBroadcastFormat())
{
case MP3:
- return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
+ return new MP3AudioConverter(audioSampleRate, mp3Setting);
default:
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
}
@@ -137,12 +140,12 @@ public class BroadcastFactory
return null;
}
- public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
+ public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, AudioSampleRate audioSampleRate, MP3Setting mp3Setting)
{
switch(format)
{
case MP3:
- return new MP3SilenceGenerator();
+ return new MP3SilenceGenerator(audioSampleRate, mp3Setting);
default:
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
"] can't create silence generator");
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
index c04d9665..025042e4 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/BroadcastModel.java
@@ -1,23 +1,20 @@
/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
- * * ******************************************************************************
- * * Copyright (C) 2014-2020 Dennis Sheirer
- * *
- * * This program is free software: you can redistribute it and/or modify
- * * it under the terms of the GNU General Public License as published by
- * * the Free Software Foundation, either version 3 of the License, or
- * * (at your option) any later version.
- * *
- * * This program is distributed in the hope that it will be useful,
- * * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * * GNU General Public License for more details.
- * *
- * * You should have received a copy of the GNU General Public License
- * * along with this program. If not, see
- * * *****************************************************************************
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
*/
package io.github.dsheirer.audio.broadcast;
@@ -34,7 +31,7 @@ import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.swing.ImageIcon;
+import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.io.IOException;
import java.nio.file.DirectoryStream;
@@ -80,6 +77,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener mBroadcastEventBroadcaster = new Broadcaster<>();
private BroadcastEventListener mBroadcastEventListener = new BroadcastEventListener();
+ private UserPreferences mUserPreferences;
/**
* Model for managing Broadcast configurations and any associated broadcaster instances.
@@ -88,6 +86,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
+import io.github.dsheirer.audio.convert.MP3Setting;
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
{
private IBroadcastMetadataUpdater mMetadataUpdater;
private AliasModel mAliasModel;
- public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AliasModel aliasModel)
+ public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting, AliasModel aliasModel)
{
- super(broadcastConfiguration);
+ super(broadcastConfiguration, audioSampleRate, mp3Setting);
mAliasModel = aliasModel;
}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
index 6530410f..da41d895 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
@@ -20,7 +20,9 @@ package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
+import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
@@ -68,9 +70,10 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
- public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AliasModel aliasModel)
+ public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting, AliasModel aliasModel)
{
- super(configuration, aliasModel);
+ super(configuration, audioSampleRate, mp3Setting, aliasModel);
}
/**
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
index 9e8dc9e6..009ec9bd 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
@@ -21,7 +21,9 @@ package io.github.dsheirer.audio.broadcast.icecast;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
+import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
@@ -66,9 +68,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
- public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AliasModel aliasModel)
+ public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting, AliasModel aliasModel)
{
- super(configuration, aliasModel);
+ super(configuration, audioSampleRate, mp3Setting, aliasModel);
}
/**
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
index 7fe52d40..ff9da137 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v1/ShoutcastV1AudioBroadcaster.java
@@ -22,6 +22,8 @@ import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioStreamingBroadcaster;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.broadcast.IBroadcastMetadataUpdater;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
+import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.buffer.IoBuffer;
@@ -61,9 +63,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
*
* @param configuration for the Shoutcast stream
*/
- public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AliasModel aliasModel)
+ public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting, AliasModel aliasModel)
{
- super(configuration);
+ super(configuration, audioSampleRate, mp3Setting);
mAliasModel = aliasModel;
}
diff --git a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
index 474b6071..b37cee14 100644
--- a/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
+++ b/src/main/java/io/github/dsheirer/audio/broadcast/shoutcast/v2/ShoutcastV2AudioStreamingBroadcaster.java
@@ -40,6 +40,8 @@ import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageF
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMessageType;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxMetadata;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ultravox.UltravoxProtocolFactory;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
+import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.identifier.IdentifierClass;
@@ -91,9 +93,10 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
*
* @param configuration for the Shoutcast V2 stream
*/
- public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, AliasModel aliasModel)
+ public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, AudioSampleRate audioSampleRate,
+ MP3Setting mp3Setting, AliasModel aliasModel)
{
- super(configuration);
+ super(configuration, audioSampleRate, mp3Setting);
mAliasModel = aliasModel;
}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/AudioSampleRate.java b/src/main/java/io/github/dsheirer/audio/convert/AudioSampleRate.java
new file mode 100644
index 00000000..c6eef10b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/AudioSampleRate.java
@@ -0,0 +1,79 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+import io.github.dsheirer.audio.AudioFormats;
+
+import javax.sound.sampled.AudioFormat;
+
+/**
+ * Enumeration of (input) audio sample rates for mono audio.
+ */
+public enum AudioSampleRate
+{
+ SR_8000(AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO, "8000 Hz (no resample / default)"),
+ SR_16000(AudioFormats.PCM_SIGNED_16_KHZ_16BITS_MONO, "16000 Hz"),
+ SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16BITS_MONO, "22050 Hz"),
+ SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16BITS_MONO, "44100 Hz");
+
+ private AudioFormat mAudioFormat;
+ private String mLabel;
+
+ /**
+ * Constructs an instance
+ * @param audioFormat for the specified sample rate
+ */
+ AudioSampleRate(AudioFormat audioFormat, String label)
+ {
+ mAudioFormat = audioFormat;
+ mLabel = label;
+ }
+
+ /**
+ * Default sample rate
+ */
+ public static AudioSampleRate getDefault()
+ {
+ return SR_8000;
+ }
+
+ /**
+ * Audio format for the specified sample rate entry
+ */
+ public AudioFormat getAudioFormat()
+ {
+ return mAudioFormat;
+ }
+
+ /**
+ * Sample rate for the format
+ */
+ public double getSampleRate()
+ {
+ return getAudioFormat().getSampleRate();
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/ChannelMode.java b/src/main/java/io/github/dsheirer/audio/convert/ChannelMode.java
new file mode 100644
index 00000000..6da12acf
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/ChannelMode.java
@@ -0,0 +1,62 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+/**
+ * MP3 Channel Mode
+ *
+ * see: http://www.mp3-tech.org/programmer/frame_header.html
+ */
+public enum ChannelMode
+{
+ STEREO("STEREO"),
+ JOINT_STEREO("JOINT STEREO"),
+ DUAL_CHANNEL("DUAL CHANNEL"),
+ MONO("MONO");
+
+ private String mLabel;
+
+ ChannelMode(String label)
+ {
+ mLabel = label;
+ }
+
+ public static ChannelMode fromValue(int value)
+ {
+ switch(value)
+ {
+ case 0:
+ return STEREO;
+ case 1:
+ return JOINT_STEREO;
+ case 2:
+ return DUAL_CHANNEL;
+ case 3:
+ default:
+ return MONO;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/IAudioConverter.java b/src/main/java/io/github/dsheirer/audio/convert/IAudioConverter.java
index 52ddb94f..95e103c4 100644
--- a/src/main/java/io/github/dsheirer/audio/convert/IAudioConverter.java
+++ b/src/main/java/io/github/dsheirer/audio/convert/IAudioConverter.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,8 +14,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.convert;
import java.util.List;
@@ -25,11 +25,11 @@ public interface IAudioConverter
/**
* Converts the PCM audio packets to converted audio format. May produce partial audio frame data.
*/
- public byte[] convert(List audioBuffers);
+ List convert(List audioBuffers);
/**
* Finalizes audio conversion by fully converting any partial frames left in the buffer and returning the
* remaining bytes to produce a full frame.
*/
- public byte[] flush();
+ List flush();
}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/ISilenceGenerator.java b/src/main/java/io/github/dsheirer/audio/convert/ISilenceGenerator.java
index 42682944..eddfd389 100644
--- a/src/main/java/io/github/dsheirer/audio/convert/ISilenceGenerator.java
+++ b/src/main/java/io/github/dsheirer/audio/convert/ISilenceGenerator.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,11 +14,13 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.convert;
+import java.util.List;
+
public interface ISilenceGenerator
{
- byte[] generate(long duration);
+ List generate(long duration);
}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/LameFactory.java b/src/main/java/io/github/dsheirer/audio/convert/LameFactory.java
new file mode 100644
index 00000000..4e418f5b
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/LameFactory.java
@@ -0,0 +1,65 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+import io.github.dsheirer.dsp.filter.resample.RealResampler;
+import net.sourceforge.lame.lowlevel.LameEncoder;
+import net.sourceforge.lame.mp3.MPEGMode;
+
+/**
+ * Factory class to create and configure LAME MP3 encoder
+ */
+public class LameFactory
+{
+ /**
+ * Creates a LAME MP3 encoder using the specified input sample rate and MP3 settings
+ * @param input sample rate
+ * @param setting to apply to the encoder
+ * @return lame encoder
+ */
+ public static LameEncoder getLameEncoder(AudioSampleRate input, MP3Setting setting)
+ {
+ return switch(setting)
+ {
+ case CBR_16 -> new LameEncoder(input.getAudioFormat(), 16, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
+ case CBR_32 -> new LameEncoder(input.getAudioFormat(), 32, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, false);
+ case ABR_56 -> new LameEncoder(input.getAudioFormat(), 56, MPEGMode.MONO, LameEncoder.DEFAULT_QUALITY, true);
+ case VBR_5 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 5, true);
+ case VBR_7 -> new LameEncoder(input.getAudioFormat(), LameEncoder.DEFAULT_BITRATE, MPEGMode.MONO, 7, true);
+ default -> throw new IllegalArgumentException("Unrecognized MP3 setting: " + setting);
+ };
+ }
+
+ /**
+ * Creates a resampler to resample from 8 kHz default audio sample rate to the specified sample rate.
+ * @param sampleRate to resample to
+ * @return resampler.
+ */
+ public static RealResampler getResampler(AudioSampleRate sampleRate)
+ {
+ return switch(sampleRate)
+ {
+ case SR_16000 -> new RealResampler(8000, 16000, 4096, 512);
+ case SR_22050 -> new RealResampler(8000, 22050, 4096, 512);
+ case SR_44100 -> new RealResampler(8000, 44100, 4096, 512);
+ default -> throw new IllegalArgumentException("Unrecognized sample rate for resampling: " + sampleRate);
+ };
+ }
+}
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 d90d71a1..52f1c267 100644
--- a/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3AudioConverter.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,21 +14,22 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.convert;
-import io.github.dsheirer.audio.AudioFormats;
-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 net.sourceforge.lame.mp3.MPEGMode;
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;
public class MP3AudioConverter implements IAudioConverter
@@ -36,91 +37,89 @@ public class MP3AudioConverter implements IAudioConverter
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
public static final int AUDIO_QUALITY = Lame.QUALITY_LOW;
private LameEncoder mEncoder;
- private ByteArrayOutputStream mMP3Stream = new ByteArrayOutputStream();
- private byte[] mMP3Buffer;
+ private RealResampler mResampler;
+ private ByteArrayOutputStream mMP3EncodedFramesStream = new ByteArrayOutputStream();
+ private byte[] mOutputFramesBuffer;
/**
- * Converts PCM 8kHz 16-bit Little Endian audio packets to Mono, Low Quality MP3 compressed audio.
+ * Converts PCM 16-bit Little Endian audio packets to Mono, MP3 compressed audio.
*
- * @param bitRate for converted MP3 audio
- * @param variableBitRate (VBR) true or false for constant bit rate (CBR)
+ * @param audioSampleRate for the desired input sample rate (resampled from default 8 kHz as needed)
+ * @param setting to configure the LAME encoder
*/
- public MP3AudioConverter(int bitRate, boolean variableBitRate)
+ public MP3AudioConverter(AudioSampleRate audioSampleRate, MP3Setting setting)
{
- mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
- bitRate, MPEGMode.MONO, AUDIO_QUALITY, variableBitRate);
+ mEncoder = LameFactory.getLameEncoder(audioSampleRate, setting);
- mMP3Buffer = new byte[mEncoder.getPCMBufferSize()];
+ if(audioSampleRate != AudioSampleRate.SR_8000)
+ {
+ mResampler = LameFactory.getResampler(audioSampleRate);
+ }
+
+ mOutputFramesBuffer = new byte[mEncoder.getPCMBufferSize()];
+ }
+
+ public List convert(List audioPackets)
+ {
+ List converted = new ArrayList<>();
+
+ if(mResampler != null)
+ {
+ audioPackets = mResampler.resample(audioPackets);
+ }
+
+ for(int x = 0; x < audioPackets.size(); x++)
+ {
+ byte[] bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
+ int bytesToEncodePointer = 0;
+
+ int inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length);
+ int outputChunkSize = 0;
+
+ try
+ {
+ while(bytesToEncodePointer < bytesToEncode.length)
+ {
+ outputChunkSize = mEncoder.encodeBuffer(bytesToEncode, bytesToEncodePointer, inputChunkSize, mOutputFramesBuffer);
+ bytesToEncodePointer += inputChunkSize;
+ inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length - bytesToEncodePointer);
+
+ if(outputChunkSize > 0)
+ {
+ converted.add(Arrays.copyOf(mOutputFramesBuffer, outputChunkSize));
+ }
+ }
+ }
+ catch(Exception e)
+ {
+ mLog.error("There was an error converting audio to MP3: " + e.getMessage());
+ }
+ }
+
+ int finalChunkSize = mEncoder.encodeFinish(mOutputFramesBuffer);
+
+ if(finalChunkSize > 0)
+ {
+ converted.add(Arrays.copyOf(mOutputFramesBuffer, finalChunkSize));
+ }
+
+ return converted;
}
@Override
- public byte[] convert(List audioPackets)
- {
- mMP3Stream.reset();
-
- byte[] pcmBytes = AudioUtils.convertTo16BitSamples(audioPackets);
-
- int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
-
- int mp3BufferSize = 0;
-
- int pcmBytesPosition = 0;
-
- try
- {
- while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
- {
- pcmBytesPosition += pcmBufferSize;
- pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
- mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
- }
-
- return mMP3Stream.toByteArray();
- }
- catch(Exception e)
- {
- mLog.error("There was an error converting audio to MP3: " + e.getMessage());
- return new byte[0];
- }
- }
-
- public byte[] convertAudio(List audioBuffers)
- {
- mMP3Stream.reset();
-
- byte[] pcmBytes = AudioUtils.convert(audioBuffers);
-
- int pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length);
-
- int mp3BufferSize = 0;
-
- int pcmBytesPosition = 0;
-
- try
- {
- while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
- {
- pcmBytesPosition += pcmBufferSize;
- pcmBufferSize = FastMath.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
- mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
- }
-
- return mMP3Stream.toByteArray();
- }
- catch(Exception e)
- {
- mLog.error("There was an error converting audio to MP3: " + e.getMessage());
- return new byte[0];
- }
- }
-
- @Override
- public byte[] flush()
+ public List flush()
{
byte[] lastPartialFrame = new byte[mEncoder.getMP3BufferSize()];
int length = mEncoder.encodeFinish(lastPartialFrame);
- return Arrays.copyOf(lastPartialFrame, length);
+ byte[] frame = Arrays.copyOf(lastPartialFrame, length);
+
+ if(frame.length == 0)
+ {
+ return Collections.emptyList();
+ }
+
+ return Collections.singletonList(frame);
}
}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3BitRate.java b/src/main/java/io/github/dsheirer/audio/convert/MP3BitRate.java
new file mode 100644
index 00000000..34ae29f8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3BitRate.java
@@ -0,0 +1,55 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+public class MP3BitRate
+{
+ private final static int[] MPEG_1_L3 = new int[]{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320};
+ private final static int[] MPEG_2_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
+ private final static int[] MPEG_2_5_L3 = new int[]{0, 8, 16, 24, 32, 40, 48, 56, 64};
+
+ public static int getLayer3BitRate(MPEGVersion version, int value)
+ {
+ switch(version)
+ {
+ case V_1:
+ if(0 <= value && value < MPEG_1_L3.length)
+ {
+ return MPEG_1_L3[value];
+ }
+ break;
+ case V_2:
+ if(0 <= value && value < MPEG_2_L3.length)
+ {
+ return MPEG_2_L3[value];
+ }
+ break;
+ case V_2_5:
+ if(0 <= value && value < MPEG_2_5_L3.length)
+ {
+ return MPEG_2_5_L3[value];
+ }
+ break;
+ default:
+ }
+
+ return 0;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3FrameInspector.java b/src/main/java/io/github/dsheirer/audio/convert/MP3FrameInspector.java
index ecd5af31..b3e397ac 100644
--- a/src/main/java/io/github/dsheirer/audio/convert/MP3FrameInspector.java
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3FrameInspector.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,73 +14,53 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+
public class MP3FrameInspector
{
private final static Logger mLog = LoggerFactory.getLogger(MP3FrameInspector.class);
- public static void inspect(byte[] frames)
+ public static void inspect(List frames)
{
- if(frames == null || frames.length == 0)
+ if(frames.isEmpty())
{
+ mLog.info("No Frame Data!");
return;
}
- if(frames.length % 144 != 0)
+ for(byte[] frame: frames)
{
- log("Frame byte array not multiple of 144 byte frame size - length:" + frames.length, frames);
- return;
- }
+ StringBuilder sb = new StringBuilder();
+ int frameCounter = 0;
- //Check for erroneous frame sync
- for(int x = 0; x < frames.length - 1; x++)
- {
- if(frames[x] == (byte)0xFF && (frames[x + 1] & 0xE0) == 0xE0 && (x % 144) != 0)
+ for(int x = 0; x < frame.length; x++)
{
- log("Bad frame sync detected at byte " + x + " in frame " + (x / 144), frames);
- return;
- }
- }
+ if(MP3Header.isValid(frame, x))
+ {
+ sb.append("\n").append(++frameCounter).append(": ");
+ sb.append(MP3Header.inspect(frame, x)).append(" - ");
+ }
- //Check MP3 headers
-
-// log("Frames ...", frames);
- }
-
- private static void log(String message, byte[] frames)
- {
- StringBuilder sb = new StringBuilder();
- sb.append(message).append("\n");
-
- int frameCounter = 0;
- sb.append(frameCounter++).append(" ");
-
- for(int x = 0; x < frames.length; x++)
- {
- if(x > 0 && x % 144 == 0)
- {
- sb.append("\n");
- sb.append(frameCounter++).append(" ");
+ sb.append(String.format("%02X ", frame[x]));
}
- sb.append(String.format("%02X ", frames[x]));
+ mLog.info(sb.toString());
}
-
- mLog.info(sb.toString());
}
public static void main(String[] args)
{
mLog.info("Starting ...");
- MP3SilenceGenerator gen = new MP3SilenceGenerator();
+ MP3SilenceGenerator gen = new MP3SilenceGenerator(AudioSampleRate.SR_8000, MP3Setting.getDefault());
- byte[] audio = gen.generate(173);
+ List audio = gen.generate(173);
inspect(audio);
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3Header.java b/src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
new file mode 100644
index 00000000..123d6ef8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
@@ -0,0 +1,115 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+/**
+ * Utility class for inspecting/parsing MP3 frames
+ */
+public class MP3Header
+{
+ private MP3Header() {}
+
+ /**
+ * Indicates if the byte array at the offset has a valid MP3 frame sync and is at least 4 bytes long
+ * @param frame array of bytes
+ * @param offset into the array
+ * @return true if the first two offset bytes contain a frame sync and length from offset is at least 4
+ */
+ public static boolean isValid(byte[] frame, int offset)
+ {
+ return frame.length >= (offset + 4) && hasSync(frame, offset);
+ }
+
+ /**
+ * Indicates if the byte array at the offset has a valid MP3 frame sync
+ * @param frame array of bytes
+ * @param offset into the array
+ * @return true if the first two offset bytes contain a frame sync
+ */
+ public static boolean hasSync(byte[] frame, int offset)
+ {
+ return (frame[offset] & 0xFF) == 0xFF && (frame[offset + 1] & 0xE0) == 0xE0;
+ }
+
+ /**
+ * MPEG version for the frame
+ */
+ public static MPEGVersion getMPEGVersion(byte[] frame, int offset)
+ {
+ return MPEGVersion.fromValue((frame[offset + 1] & 0x18) >> 3);
+ }
+
+ /**
+ * MPEG layer for the frame. Normally layer 3 for audio.
+ */
+ public static MPEGLayer getMPEGLayer(byte[] frame, int offset)
+ {
+ return MPEGLayer.fromValue((frame[offset + 1] & 0x6) >> 1);
+ }
+
+ /**
+ * MP3 bit rate
+ */
+ public static int getBitRate(byte[] frame, int offset)
+ {
+ return MP3BitRate.getLayer3BitRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0xF0) >> 4);
+ }
+
+ /**
+ * Input audio sample rate.
+ */
+ public static int getSampleRate(byte[] frame, int offset)
+ {
+ return MP3SampleRate.getSampleRate(getMPEGVersion(frame, offset), (frame[offset + 2] & 0x0C) >> 2);
+ }
+
+ /**
+ * Channel mode
+ */
+ public static ChannelMode getChannelMode(byte[] frame, int offset)
+ {
+ return ChannelMode.fromValue((frame[offset + 3] & 0xC0) >> 6);
+ }
+
+ /**
+ * Inspects the frame bytes and provides details about the header.
+ */
+ public static String inspect(byte[] frame, int offset)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MP3 FRAME");
+
+ if(!isValid(frame, offset))
+ {
+ sb.append(" INVALID");
+ return sb.toString();
+ }
+
+ sb.append(" VER:").append(getMPEGVersion(frame, offset));
+ sb.append(" MODE:").append(getChannelMode(frame, offset));
+ sb.append(" ").append(getMPEGLayer(frame, offset));
+ sb.append(" BR:").append(getBitRate(frame, offset));
+ sb.append(" SR:").append(getSampleRate(frame, offset));
+
+ //TODO: add MONO/STEREO
+
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3SampleRate.java b/src/main/java/io/github/dsheirer/audio/convert/MP3SampleRate.java
new file mode 100644
index 00000000..2915e983
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3SampleRate.java
@@ -0,0 +1,55 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+public class MP3SampleRate
+{
+ private final static int[] MPEG_1 = new int[]{44100, 48000, 32000};
+ private final static int[] MPEG_2 = new int[]{22050, 24000, 16000};
+ private final static int[] MPEG2_5 = new int[]{11025, 12000, 8000};
+
+ public static int getSampleRate(MPEGVersion version, int value)
+ {
+ switch(version)
+ {
+ case V_1:
+ if(0 <= value && value < MPEG_1.length)
+ {
+ return MPEG_1[value];
+ }
+ break;
+ case V_2:
+ if(0 <= value && value < MPEG_2.length)
+ {
+ return MPEG_2[value];
+ }
+ break;
+ case V_2_5:
+ if(0 <= value && value < MPEG2_5.length)
+ {
+ return MPEG2_5[value];
+ }
+ break;
+ default:
+ }
+
+ return 0;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MP3Setting.java b/src/main/java/io/github/dsheirer/audio/convert/MP3Setting.java
new file mode 100644
index 00000000..0c6b04df
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3Setting.java
@@ -0,0 +1,87 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+import net.sourceforge.lame.mp3.Lame;
+
+/**
+ * MP3 encoder settings.
+ *
+ * A curated list of LAME audio encoder settings that are optimized for mono-channel voice that is originally
+ * sampled at 8,000 Hz.
+ *
+ * See: https://svn.code.sf.net/p/lame/svn/trunk/lame/doc/html/usage.html
+ */
+public enum MP3Setting
+{
+ CBR_16(false, 16, "CBR - Constant Bit Rate 16 kbps (default)"),
+ CBR_32(false, 32, "CBR - Constant Bit Rate 32 kbps"),
+ ABR_56(true, 56, "ABR - Average Bit Rate 56 kbps"),
+ VBR_5(true, Lame.QUALITY_MIDDLE, "VBR - Middle Quality (5)"),
+ VBR_7(true, Lame.QUALITY_LOW, "VBR - Low Quality (7)");
+
+ private boolean mVariableBitRate;
+ private int mSetting;
+ private String mLabel;
+
+ /**
+ * Constructs an instance
+ * @param variableBitRate flag
+ * @param setting for the entry
+ * @param label to display
+ */
+ MP3Setting(boolean variableBitRate, int setting, String label)
+ {
+ mVariableBitRate = variableBitRate;
+ mSetting = setting;
+ mLabel = label;
+ }
+
+ /**
+ * Default setting
+ */
+ public static MP3Setting getDefault()
+ {
+ return MP3Setting.CBR_16;
+ }
+
+ /**
+ * Indicates if the entry is Constant Bit Rate (CBR), or Average/Variable Bit Rate (ABR or VBR)
+ * @return true if CBR
+ */
+ public boolean isVariableBitRate()
+ {
+ return mVariableBitRate;
+ }
+
+ /**
+ * LAME encoder quality value
+ */
+ public int getSetting()
+ {
+ return mSetting;
+ }
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
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 8efecc4f..12ff3df5 100644
--- a/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java
+++ b/src/main/java/io/github/dsheirer/audio/convert/MP3SilenceGenerator.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2016 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,15 +14,14 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class MP3SilenceGenerator implements ISilenceGenerator
@@ -31,47 +30,42 @@ public class MP3SilenceGenerator implements ISilenceGenerator
public static final int MP3_BIT_RATE = 16;
public static final boolean CONSTANT_BIT_RATE = false;
- private MP3AudioConverter mGenerator = new MP3AudioConverter(MP3_BIT_RATE, CONSTANT_BIT_RATE);
+ private MP3AudioConverter mMP3AudioConverter;
+ private AudioSampleRate mAudioSampleRate;
private byte[] mPreviousPartialFrameData;
/**
* Generates MP3 audio silence frames
*/
- public MP3SilenceGenerator()
+ public MP3SilenceGenerator(AudioSampleRate audioSampleRate, MP3Setting setting)
{
+ mAudioSampleRate = audioSampleRate;
+ mMP3AudioConverter = new MP3AudioConverter(audioSampleRate, setting);
}
- public byte[] generate(long duration)
+ /**
+ * Generates silence frames
+ * @param duration_ms in milliseconds
+ * @return
+ */
+ public List generate(long duration_ms)
{
- int length = (int)(duration * 8); //8000 Hz sample rate
+ double duration_secs = (double)duration_ms / 1000.0;
+
+ //We generate silence at 8000 kHz, because it gets resampled by the silence generator to the target rate
+ int length = (int)(duration_secs * AudioSampleRate.SR_8000.getSampleRate());
List silenceBuffers = new ArrayList<>();
- silenceBuffers.add(new float[length]);
- byte[] frameData = mGenerator.convert(silenceBuffers);
- frameData = merge(mPreviousPartialFrameData, frameData);
-
- if(frameData != null && frameData.length > 0)
+ int added = 0;
+ while(added < length)
{
- if(frameData.length < 144)
- {
- mPreviousPartialFrameData = frameData;
- return null;
- }
- else if((frameData.length % 144) == 0)
- {
- mPreviousPartialFrameData = null;
- return frameData;
- }
- else
- {
- int integralFrameLength = (int)(frameData.length / 144) * 144;
- mPreviousPartialFrameData = Arrays.copyOfRange(frameData, integralFrameLength, frameData.length);
- return Arrays.copyOf(frameData, integralFrameLength);
- }
+ int chunk = Math.min(length - added, 256);
+ silenceBuffers.add(new float[chunk]);
+ added += chunk;
}
- return null;
+ return mMP3AudioConverter.convert(silenceBuffers);
}
private static byte[] merge(byte[] a, byte[] b)
@@ -100,19 +94,24 @@ public class MP3SilenceGenerator implements ISilenceGenerator
{
mLog.debug("Starting ...");
- for(long x = 243; x < 500; x ++)
+ MP3SilenceGenerator generator = new MP3SilenceGenerator(AudioSampleRate.SR_44100, MP3Setting.VBR_7);
+
+ int x = 100;
+
+// for(; x < 1000; x += 10)
+// {
+ List silenceFrames = generator.generate(x);
+
+ mLog.debug("Generated [" + silenceFrames.size() + "] silence frames");
+
+ for(byte[] silence: silenceFrames)
{
- MP3SilenceGenerator generator = new MP3SilenceGenerator();
-
- byte[] silence = generator.generate(x);
-
- if(silence != null && silence.length > 0)
- {
- mLog.debug("Silence:" + x + " Length:" + silence.length);
- MP3FrameInspector.inspect(silence);
- }
+ mLog.debug("Silence:" + x + " Length:" + silence.length);
}
+ MP3FrameInspector.inspect(silenceFrames);
+// }
+
mLog.debug("Finished");
}
}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MPEGLayer.java b/src/main/java/io/github/dsheirer/audio/convert/MPEGLayer.java
new file mode 100644
index 00000000..a8d8d7fe
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MPEGLayer.java
@@ -0,0 +1,62 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+/**
+ * MPEG Layer
+ *
+ * see: http://www.mp3-tech.org/programmer/frame_header.html
+ */
+public enum MPEGLayer
+{
+ RESERVED("LAYER-RESERVED"),
+ LAYER3("LAYER-3"),
+ LAYER2("LAYER-2"),
+ LAYER1("LAYER-1");
+
+ private String mLabel;
+
+ MPEGLayer(String label)
+ {
+ mLabel = label;
+ }
+
+ public static MPEGLayer fromValue(int value)
+ {
+ switch(value)
+ {
+ case 1:
+ return LAYER3;
+ case 2:
+ return LAYER2;
+ case 3:
+ return LAYER1;
+ default:
+ case 0:
+ return RESERVED;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/convert/MPEGVersion.java b/src/main/java/io/github/dsheirer/audio/convert/MPEGVersion.java
new file mode 100644
index 00000000..eda010be
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/audio/convert/MPEGVersion.java
@@ -0,0 +1,61 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.audio.convert;
+
+/**
+ * MPEG Version
+ *
+ * see: http://www.mp3-tech.org/programmer/frame_header.html
+ */
+public enum MPEGVersion
+{
+ V_2_5("2.5"),
+ RESERVED("RESERVED"),
+ V_2("2"),
+ V_1("1");
+
+ private String mLabel;
+
+ MPEGVersion(String label)
+ {
+ mLabel = label;
+ }
+
+ public static MPEGVersion fromValue(int value)
+ {
+ switch(value)
+ {
+ case 0:
+ return V_2_5;
+ case 2:
+ return V_2;
+ case 3:
+ return V_1;
+ default:
+ return RESERVED;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return mLabel;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java b/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java
index ac6972f3..a5ad453d 100644
--- a/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java
+++ b/src/main/java/io/github/dsheirer/audio/playback/MonoAudioOutput.java
@@ -1,6 +1,6 @@
-/*******************************************************************************
- * sdrtrunk
- * Copyright (C) 2014-2017 Dennis Sheirer
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,8 +14,8 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
- *
- ******************************************************************************/
+ * ****************************************************************************
+ */
package io.github.dsheirer.audio.playback;
import io.github.dsheirer.audio.AudioFormats;
@@ -36,7 +36,7 @@ public class MonoAudioOutput extends AudioOutput
public MonoAudioOutput(Mixer mixer, UserPreferences userPreferences)
{
- super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
+ super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO,
AudioFormats.MONO_SOURCE_DATALINE_INFO, BUFFER_SIZE, userPreferences);
}
diff --git a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
index 5a5f1771..d0eed7ef 100644
--- a/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
+++ b/src/main/java/io/github/dsheirer/dsp/filter/resample/RealResampler.java
@@ -26,6 +26,8 @@ import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
/**
* Real sample buffer resampler
@@ -54,14 +56,59 @@ public class RealResampler
mBufferManager = new BufferManager(bufferSize, outputArrayLength);
}
+ /**
+ * Resample factor (output rate / input rate)
+ */
+ public double getResampleFactor()
+ {
+ return mResampleFactor;
+ }
+
+ /**
+ * Resamples all of the audio packets
+ * @param audioPackets to resample
+ * @return resampled audio packets
+ */
+ public List resample(List audioPackets)
+ {
+ List resampled = new ArrayList<>();
+
+ mResampledListener = resampledAudio -> resampled.add(resampledAudio);
+
+ for(int x = 0; x < audioPackets.size(); x++)
+ {
+ if(x == audioPackets.size() - 1)
+ {
+ resample(audioPackets.get(x), true);
+ }
+ else
+ {
+ resample(audioPackets.get(x));
+ }
+ }
+
+ mResampledListener = null;
+ return resampled;
+ }
+
/**
* Primary input method to the resampler
* @param samples to resample
*/
public void resample(float[] samples)
+ {
+ resample(samples, false);
+ }
+
+ /**
+ * Primary input method to the resampler
+ * @param samples to resample
+ * @param lastBatch set to true if this is the last set of samples
+ */
+ public void resample(float[] samples, boolean lastBatch)
{
mBufferManager.load(samples);
- mResampler.process(mResampleFactor, mBufferManager, false);
+ mResampler.process(mResampleFactor, mBufferManager, lastBatch);
}
/**
diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java
index 7baf12bc..5ec247de 100644
--- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java
+++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorFactory.java
@@ -23,6 +23,7 @@ import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreference
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor;
+import io.github.dsheirer.gui.preference.mp3.MP3PreferenceEditor;
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
import io.github.dsheirer.gui.preference.tuner.TunerPreferenceEditor;
@@ -40,6 +41,8 @@ public class PreferenceEditorFactory
{
case AUDIO_DUPLICATE_CALL_DETECTION:
return new DuplicateCallPreferenceEditor(userPreferences);
+ case AUDIO_MP3:
+ return new MP3PreferenceEditor(userPreferences);
case AUDIO_OUTPUT:
return new PlaybackPreferenceEditor(userPreferences);
case AUDIO_RECORD:
diff --git a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java
index de98f775..7c8d8761 100644
--- a/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java
+++ b/src/main/java/io/github/dsheirer/gui/preference/PreferenceEditorType.java
@@ -27,6 +27,7 @@ public enum PreferenceEditorType
CHANNEL_EVENT("Channel Events"),
DIRECTORY("Directories"),
JMBE_LIBRARY("JMBE Audio Library"),
+ AUDIO_MP3("MP3"),
AUDIO_RECORD("Record"),
AUDIO_OUTPUT("Output/Tones"),
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
diff --git a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java
index 78e12781..24062113 100644
--- a/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java
+++ b/src/main/java/io/github/dsheirer/gui/preference/UserPreferencesEditor.java
@@ -168,6 +168,7 @@ public class UserPreferencesEditor extends BorderPane
TreeItem audioItem = new TreeItem<>("Audio");
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
+ audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3));
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_OUTPUT));
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_RECORD));
treeRoot.getChildren().add(audioItem);
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
new file mode 100644
index 00000000..21d9aef8
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/gui/preference/mp3/MP3PreferenceEditor.java
@@ -0,0 +1,112 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.gui.preference.mp3;
+
+import io.github.dsheirer.audio.convert.AudioSampleRate;
+import io.github.dsheirer.audio.convert.MP3Setting;
+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.ComboBox;
+import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Preference settings for MP3
+ */
+public class MP3PreferenceEditor extends HBox
+{
+ private final static Logger mLog = LoggerFactory.getLogger(MP3PreferenceEditor.class);
+ private MP3Preference mMP3Preference;
+ private GridPane mEditorPane;
+ private ComboBox mMP3SettingComboBox;
+ private ComboBox mAudioSampleRateComboBox;
+
+ public MP3PreferenceEditor(UserPreferences userPreferences)
+ {
+ mMP3Preference = userPreferences.getMP3Preference();
+ HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
+ getChildren().add(getEditorPane());
+ }
+
+ private GridPane getEditorPane()
+ {
+ if(mEditorPane == null)
+ {
+ mEditorPane = new GridPane();
+ mEditorPane.setPadding(new Insets(10, 10, 10, 10));
+ mEditorPane.setHgap(10);
+ mEditorPane.setVgap(10);
+
+ int row = 0;
+ Label topLabel = new Label("MP3 Encoder Preferences");
+ mEditorPane.add(topLabel, 0, row++, 2, 1);
+
+ Label label = new Label("(LAME) Encoder Setting:");
+ GridPane.setHalignment(label, HPos.RIGHT);
+ mEditorPane.add(label, 0, row);
+ mEditorPane.add(getMP3SettingComboBox(), 1, row++);
+
+ Label sampleRateLabel = new Label("Input Audio Sample Rate:");
+ GridPane.setHalignment(sampleRateLabel, HPos.RIGHT);
+ mEditorPane.add(sampleRateLabel, 0, row);
+ 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);
+ }
+
+ return mEditorPane;
+ }
+
+ private ComboBox getMP3SettingComboBox()
+ {
+ if(mMP3SettingComboBox == null)
+ {
+ mMP3SettingComboBox = new ComboBox<>();
+ mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
+ mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
+ mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
+ .addListener((observable, oldValue, newValue) -> mMP3Preference.setMP3Setting(newValue));
+ }
+
+ return mMP3SettingComboBox;
+ }
+
+ private ComboBox getAudioSampleRateComboBox()
+ {
+ if(mAudioSampleRateComboBox == null)
+ {
+ mAudioSampleRateComboBox = new ComboBox<>();
+ mAudioSampleRateComboBox.getItems().addAll(AudioSampleRate.values());
+ mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
+ mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
+ .addListener((observable, oldValue, newValue) -> mMP3Preference.setAudioSampleRate(newValue));
+ }
+
+ return mAudioSampleRateComboBox;
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java b/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java
index b2d9c706..91c09ac2 100644
--- a/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java
+++ b/src/main/java/io/github/dsheirer/gui/preference/playback/PlaybackPreferenceEditor.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,10 +19,7 @@
package io.github.dsheirer.gui.preference.playback;
-import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.audio.AudioFormats;
-import io.github.dsheirer.eventbus.MyEventBus;
-import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.playback.PlaybackPreference;
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
@@ -410,7 +407,7 @@ public class PlaybackPreferenceEditor extends HBox
byte[] bytes = buffer.array();
- DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
+ DataLine.Info info = new DataLine.Info(Clip.class, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO);
if(!AudioSystem.isLineSupported(info))
{
@@ -421,7 +418,7 @@ public class PlaybackPreferenceEditor extends HBox
try
{
Clip clip = (Clip)AudioSystem.getLine(info);
- clip.open(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, bytes, 0, bytes.length);
+ clip.open(AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO, bytes, 0, bytes.length);
clip.start();
}
catch(Exception e)
diff --git a/src/main/java/io/github/dsheirer/preference/PreferenceType.java b/src/main/java/io/github/dsheirer/preference/PreferenceType.java
index d9309dea..355ff838 100644
--- a/src/main/java/io/github/dsheirer/preference/PreferenceType.java
+++ b/src/main/java/io/github/dsheirer/preference/PreferenceType.java
@@ -29,6 +29,7 @@ public enum PreferenceType
DIRECTORY,
DUPLICATE_CALL_DETECTION,
JMBE_LIBRARY,
+ MP3,
MULTI_FREQUENCY,
PLAYLIST,
PLAYBACK,
diff --git a/src/main/java/io/github/dsheirer/preference/UserPreferences.java b/src/main/java/io/github/dsheirer/preference/UserPreferences.java
index bc247a07..3f10b316 100644
--- a/src/main/java/io/github/dsheirer/preference/UserPreferences.java
+++ b/src/main/java/io/github/dsheirer/preference/UserPreferences.java
@@ -27,6 +27,7 @@ import io.github.dsheirer.preference.duplicate.DuplicateCallDetectionPreference;
import io.github.dsheirer.preference.event.DecodeEventPreference;
import io.github.dsheirer.preference.identifier.TalkgroupFormatPreference;
import io.github.dsheirer.preference.javafx.JavaFxPreferences;
+import io.github.dsheirer.preference.mp3.MP3Preference;
import io.github.dsheirer.preference.playback.PlaybackPreference;
import io.github.dsheirer.preference.playlist.PlaylistPreference;
import io.github.dsheirer.preference.radioreference.RadioReferencePreference;
@@ -60,6 +61,7 @@ public class UserPreferences implements Listener
private DirectoryPreference mDirectoryPreference;
private DuplicateCallDetectionPreference mDuplicateCallDetectionPreference;
private JmbeLibraryPreference mJmbeLibraryPreference;
+ private MP3Preference mMP3Preference;
private PlaybackPreference mPlaybackPreference;
private PlaylistPreference mPlaylistPreference;
private RadioReferencePreference mRadioReferencePreference;
@@ -155,6 +157,14 @@ public class UserPreferences implements Listener
return mRecordPreference;
}
+ /**
+ * MP3 preferences
+ */
+ public MP3Preference getMP3Preference()
+ {
+ return mMP3Preference;
+ }
+
/**
* Identifier preferences
*/
@@ -199,6 +209,7 @@ public class UserPreferences implements Listener
mDirectoryPreference = new DirectoryPreference(this::receive);
mDuplicateCallDetectionPreference = new DuplicateCallDetectionPreference(this::receive);
mJmbeLibraryPreference = new JmbeLibraryPreference(this::receive);
+ mMP3Preference = new MP3Preference(this::receive);
mPlaybackPreference = new PlaybackPreference(this::receive);
mPlaylistPreference = new PlaylistPreference(this::receive, mDirectoryPreference);
mRadioReferencePreference = new RadioReferencePreference(this::receive);
diff --git a/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java b/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java
new file mode 100644
index 00000000..355350a3
--- /dev/null
+++ b/src/main/java/io/github/dsheirer/preference/mp3/MP3Preference.java
@@ -0,0 +1,131 @@
+/*
+ * *****************************************************************************
+ * Copyright (C) 2014-2022 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ * ****************************************************************************
+ */
+
+package io.github.dsheirer.preference.mp3;
+
+import io.github.dsheirer.audio.convert.AudioSampleRate;
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.prefs.Preferences;
+
+/**
+ * User preferences for playlists
+ */
+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 final static Logger mLog = LoggerFactory.getLogger(MP3Preference.class);
+ private Preferences mPreferences = Preferences.userNodeForPackage(MP3Preference.class);
+ private AudioSampleRate mAudioSampleRate;
+ private MP3Setting mMP3Setting;
+
+ /**
+ * Constructs this preference with an update listener
+ * @param updateListener to receive notifications whenever these preferences change
+ */
+ public MP3Preference(Listener updateListener)
+ {
+ super(updateListener);
+ }
+
+ @Override
+ public PreferenceType getPreferenceType()
+ {
+ return PreferenceType.MP3;
+ }
+
+
+ /**
+ * MP3 Setting
+ */
+ public MP3Setting getMP3Setting()
+ {
+ if(mMP3Setting == null)
+ {
+ try
+ {
+ String setting = mPreferences.get(PREFERENCE_KEY_AUDIO_MP3_SETTING, MP3Setting.getDefault().name());
+ mMP3Setting = MP3Setting.valueOf(setting);
+ }
+ catch(Exception e)
+ {
+ mLog.error("Error parsing mp3 setting preference", e);
+ }
+
+ if(mMP3Setting == null)
+ {
+ mMP3Setting = MP3Setting.getDefault();
+ }
+ }
+
+ return mMP3Setting;
+ }
+
+ /**
+ * Sets the MP3 setting preference
+ */
+ public void setMP3Setting(MP3Setting mp3Setting)
+ {
+ mMP3Setting = mp3Setting;
+ mPreferences.put(PREFERENCE_KEY_AUDIO_MP3_SETTING, mp3Setting.name());
+ notifyPreferenceUpdated();
+ }
+
+ /**
+ * Preferred audio sample rate for input to the LAME MP3 encoder
+ */
+ public AudioSampleRate getAudioSampleRate()
+ {
+ if(mAudioSampleRate == null)
+ {
+ try
+ {
+ String rate = mPreferences.get(PREFERENCE_KEY_AUDIO_MP3_SAMPLE_RATE, AudioSampleRate.getDefault().name());
+ mAudioSampleRate = AudioSampleRate.valueOf(rate);
+ }
+ catch(Exception e)
+ {
+ mLog.error("Error parsing mp3 setting preference", e);
+ }
+
+ if(mAudioSampleRate == null)
+ {
+ mAudioSampleRate = AudioSampleRate.getDefault();
+ }
+ }
+
+ return mAudioSampleRate;
+ }
+
+ /**
+ * Sets the input Audio Sample Rate preference
+ */
+ public void setAudioSampleRate(AudioSampleRate audioSampleRate)
+ {
+ mAudioSampleRate = audioSampleRate;
+ mPreferences.put(PREFERENCE_KEY_AUDIO_MP3_SAMPLE_RATE, mAudioSampleRate.name());
+ notifyPreferenceUpdated();
+ }
+}
diff --git a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java
index 2120d1df..05e14018 100644
--- a/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java
+++ b/src/main/java/io/github/dsheirer/record/AudioRecordingManager.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -152,7 +152,7 @@ public class AudioRecordingManager implements Listener
try
{
- AudioSegmentRecorder.record(audioSegment, path, recordFormat);
+ AudioSegmentRecorder.record(audioSegment, path, recordFormat, mUserPreferences);
}
catch(IOException ioe)
{
diff --git a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java
index ca9fb1f2..bbbce446 100644
--- a/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java
+++ b/src/main/java/io/github/dsheirer/record/AudioSegmentRecorder.java
@@ -1,6 +1,6 @@
/*
* *****************************************************************************
- * Copyright (C) 2014-2020 Dennis Sheirer
+ * Copyright (C) 2014-2022 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,7 +21,10 @@ package io.github.dsheirer.record;
import io.github.dsheirer.audio.AudioFormats;
import io.github.dsheirer.audio.AudioSegment;
+import io.github.dsheirer.audio.convert.AudioSampleRate;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
+import io.github.dsheirer.audio.convert.MP3Setting;
+import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.record.wave.AudioMetadata;
import io.github.dsheirer.record.wave.AudioMetadataUtils;
import io.github.dsheirer.record.wave.WaveWriter;
@@ -34,6 +37,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
+import java.util.List;
import java.util.Map;
/**
@@ -53,12 +57,13 @@ public class AudioSegmentRecorder
* @param recordFormat to use (WAVE, MP3)
* @throws IOException on any errors
*/
- public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat) throws IOException
+ public static void record(AudioSegment audioSegment, Path path, RecordFormat recordFormat,
+ UserPreferences userPreferences) throws IOException
{
switch(recordFormat)
{
case MP3:
- recordMP3(audioSegment, path);
+ recordMP3(audioSegment, path, userPreferences);
break;
case WAVE:
recordWAVE(audioSegment, path);
@@ -74,7 +79,7 @@ public class AudioSegmentRecorder
* @param path for the recording
* @throws IOException on any errors
*/
- public static void recordMP3(AudioSegment audioSegment, Path path) throws IOException
+ public static void recordMP3(AudioSegment audioSegment, Path path, UserPreferences userPreferences) throws IOException
{
if(audioSegment.hasAudio())
{
@@ -88,15 +93,24 @@ public class AudioSegmentRecorder
outputStream.write(id3Bytes);
//Convert audio to MP3 and write to file
- MP3AudioConverter converter = new MP3AudioConverter(MP3_BIT_RATE, CONSTANT_BIT_RATE);
- byte[] mp3 = converter.convertAudio(audioSegment.getAudioBuffers());
- outputStream.write(mp3);
+ AudioSampleRate audioSampleRate = userPreferences.getMP3Preference().getAudioSampleRate();
+ MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
- byte[] lastFrame = converter.flush();
-
- if(lastFrame != null && lastFrame.length > 0)
+ MP3AudioConverter converter = new MP3AudioConverter(audioSampleRate, mp3Setting);
+ List mp3Frames = converter.convert(audioSegment.getAudioBuffers());
+ for(byte[] mp3Frame: mp3Frames)
{
- outputStream.write(lastFrame);
+ outputStream.write(mp3Frame);
+ }
+
+ List lastFrames = converter.flush();
+
+ if(!lastFrames.isEmpty())
+ {
+ for(byte[] lastFrame: lastFrames)
+ {
+ outputStream.write(lastFrame);
+ }
}
outputStream.flush();
@@ -114,7 +128,7 @@ public class AudioSegmentRecorder
{
if(audioSegment.hasAudio())
{
- WaveWriter writer = new WaveWriter(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO, path);
+ WaveWriter writer = new WaveWriter(AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO, path);
for(float[] audioBuffer: audioSegment.getAudioBuffers())
{
diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java
index 2379ede3..7de642ee 100644
--- a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java
+++ b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java
@@ -76,7 +76,7 @@ public class MixerManager
if(channel == MixerChannel.MONO)
{
DataLine.Info info = new DataLine.Info(TargetDataLine.class,
- AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
+ AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO);
TargetDataLine dataLine;
@@ -86,7 +86,7 @@ public class MixerManager
if(dataLine != null)
{
- return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
+ return new RealMixerSource(dataLine, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO,
new RealShortAdapter());
}
}
@@ -307,7 +307,7 @@ public class MixerManager
boolean stereoSupported = mixer.isLineSupported(stereoInfo);
DataLine.Info monoInfo = new DataLine.Info(TargetDataLine.class,
- AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
+ AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO);
boolean monoSupported = mixer.isLineSupported(monoInfo);
@@ -341,7 +341,7 @@ public class MixerManager
boolean stereoSupported = mixer.isLineSupported(stereoInfo);
- DataLine.Info monoInfo = new DataLine.Info(SourceDataLine.class, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
+ DataLine.Info monoInfo = new DataLine.Info(SourceDataLine.class, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO);
boolean monoSupported = mixer.isLineSupported(monoInfo);