#1170 resolves issue with MP3 encoded audio raspy. Adds auto-clipping of PCM audio prior to encoding and adds 4x new 32-bit input sample rate selections to choose from, in addition to original 4x 16-bit input formats. (#1171)

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
Denny Sheirer
2022-03-16 05:00:07 -04:00
committed by GitHub
parent 90bc321aca
commit 7749fd10f1
23 changed files with 245 additions and 183 deletions

View File

@@ -24,112 +24,107 @@ import javax.sound.sampled.DataLine;
import javax.sound.sampled.Line;
import javax.sound.sampled.SourceDataLine;
/**
* Audio formats
*/
public class AudioFormats
{
public static final AudioFormat.Encoding IMBE_ENCODING =
new AudioFormat.Encoding( "IMBE" );
public static final boolean LITTLE_ENDIAN = false;
public static final boolean BIG_ENDIAN = true;
public static final float IMBE_FRAME_RATE = 50;
public static final float IMBE_SAMPLE_RATE = 50;
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;
public static final int ONE_CHANNEL = 1;
public static final int TWO_CHANNELS = 2;
public static final int PCM_SAMPLE_SIZE_BITS = 16;
public static final int PCM_FRAME_SIZE_BYTES_MONO = 2;
public static final int PCM_FRAME_SIZE_BYTES_STEREO = 4;
public static AudioFormat IMBE_AUDIO_FORMAT =
new AudioFormat( IMBE_ENCODING,
IMBE_SAMPLE_RATE,
IMBE_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
IMBE_FRAME_SIZE_BYTES,
IMBE_FRAME_RATE,
LITTLE_ENDIAN );
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_8_KHZ_RATE,
LITTLE_ENDIAN );
public static final int PCM_SAMPLE_SIZE_16_BITS = 16;
public static final int PCM_SAMPLE_SIZE_32_BITS = 32;
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_MONO = 2;
public static final int PCM_FRAME_SIZE_BYTES_32_BIT_MONO = 4;
public static final int PCM_FRAME_SIZE_BYTES_16_BIT_STEREO = 4;
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,
public static AudioFormat PCM_SIGNED_8000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_16000_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_16_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_16_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_16000_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_16_KHZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_16_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_22050_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_22050_HZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_22050_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_22050_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_22050_HZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_22050_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_44100_HZ_16_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_44100_HZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_16_BIT_MONO,
PCM_44100_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_44100_HZ_32_BIT_MONO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_44100_HZ_RATE,
PCM_SAMPLE_SIZE_32_BITS,
ONE_CHANNEL,
PCM_FRAME_SIZE_BYTES_32_BIT_MONO,
PCM_44100_HZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_8000_HZ_16BITS_STEREO = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_8_KHZ_RATE,
PCM_SAMPLE_SIZE_16_BITS,
TWO_CHANNELS,
PCM_FRAME_SIZE_BYTES_STEREO,
PCM_FRAME_SIZE_BYTES_16_BIT_STEREO,
PCM_8_KHZ_RATE,
LITTLE_ENDIAN );
public static AudioFormat PCM_SIGNED_48KHZ_16BITS_MONO =
new AudioFormat( AudioFormat.Encoding.PCM_SIGNED,
PCM_48_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
ONE_CHANNEL,
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_48_KHZ_RATE,
PCM_SAMPLE_SIZE_BITS,
TWO_CHANNELS,
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_8_KHZ_16BITS_MONO);
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16_BIT_MONO);
/**
* Source Data Line Info for a 48 kHz, 16-bits signed PCM two channels
*/
public static final Line.Info STEREO_SOURCE_DATALINE_INFO =
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8KHZ_16BITS_STEREO);
new DataLine.Info( SourceDataLine.class, PCM_SIGNED_8000_HZ_16BITS_STEREO);
}

View File

@@ -18,8 +18,8 @@
*/
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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.util.ThreadPool;
@@ -73,13 +73,13 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
* The last audio packet's metadata is automatically attached to the closed audio recording when it is enqueued for
* broadcast. That metadata will be updated on the remote server once the audio recording is opened for streaming.
*/
public AudioStreamingBroadcaster(T broadcastConfiguration, AudioSampleRate audioSampleRate, MP3Setting mp3Setting)
public AudioStreamingBroadcaster(T broadcastConfiguration, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
{
super(broadcastConfiguration);
mDelay = getBroadcastConfiguration().getDelay();
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
audioSampleRate, mp3Setting);
inputAudioFormat, mp3Setting);
}
public void dispose()

View File

@@ -30,9 +30,9 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.audio.convert.MP3SilenceGenerator;
@@ -55,29 +55,29 @@ public class BroadcastFactory
{
if(configuration != null)
{
AudioSampleRate audioSampleRate = userPreferences.getMP3Preference().getAudioSampleRate();
InputAudioFormat inputAudioFormat = userPreferences.getMP3Preference().getAudioSampleRate();
MP3Setting mp3Setting = userPreferences.getMP3Preference().getMP3Setting();
switch(configuration.getBroadcastServerType())
{
case BROADCASTIFY_CALL:
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
audioSampleRate, mp3Setting, aliasModel);
inputAudioFormat, mp3Setting, aliasModel);
case BROADCASTIFY:
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
audioSampleRate, mp3Setting, aliasModel);
inputAudioFormat, mp3Setting, aliasModel);
case ICECAST_TCP:
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, audioSampleRate,
return new IcecastTCPAudioBroadcaster((IcecastTCPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case ICECAST_HTTP:
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, audioSampleRate,
return new IcecastHTTPAudioBroadcaster((IcecastHTTPConfiguration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case SHOUTCAST_V1:
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, audioSampleRate,
return new ShoutcastV1AudioBroadcaster((ShoutcastV1Configuration) configuration, inputAudioFormat,
mp3Setting, aliasModel);
case SHOUTCAST_V2:
return new ShoutcastV2AudioStreamingBroadcaster((ShoutcastV2Configuration) configuration,
audioSampleRate, mp3Setting, aliasModel);
inputAudioFormat, mp3Setting, aliasModel);
case UNKNOWN:
default:
mLog.info("Unrecognized broadcastAudio configuration: " + configuration.getBroadcastFormat().name());
@@ -94,13 +94,13 @@ public class BroadcastFactory
* @param configuration containing the requested output audio format
* @return audio convert or null
*/
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration, AudioSampleRate audioSampleRate,
public static IAudioConverter getAudioConverter(BroadcastConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting)
{
switch(configuration.getBroadcastFormat())
{
case MP3:
return new MP3AudioConverter(audioSampleRate, mp3Setting);
return new MP3AudioConverter(inputAudioFormat, mp3Setting);
default:
mLog.info("Unrecognized broadcastAudio format: " + configuration.getBroadcastFormat().name());
}
@@ -140,12 +140,12 @@ public class BroadcastFactory
return null;
}
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, AudioSampleRate audioSampleRate, MP3Setting mp3Setting)
public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format, InputAudioFormat inputAudioFormat, MP3Setting mp3Setting)
{
switch(format)
{
case MP3:
return new MP3SilenceGenerator(audioSampleRate, mp3Setting);
return new MP3SilenceGenerator(inputAudioFormat, mp3Setting);
default:
throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
"] can't create silence generator");

View File

@@ -25,7 +25,7 @@ import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
import io.github.dsheirer.audio.broadcast.AudioRecording;
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
import io.github.dsheirer.audio.broadcast.BroadcastState;
import io.github.dsheirer.audio.convert.AudioSampleRate;
import io.github.dsheirer.audio.convert.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
import io.github.dsheirer.identifier.Form;
@@ -83,7 +83,7 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
* @param config to use
* @param aliasModel for access to aliases
*/
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, AudioSampleRate audioSampleRate,
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(config);

View File

@@ -22,7 +22,7 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
@@ -30,10 +30,10 @@ public abstract class IcecastAudioBroadcaster extends AudioStreamingBroadcaster
private IBroadcastMetadataUpdater mMetadataUpdater;
private AliasModel mAliasModel;
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, AudioSampleRate audioSampleRate,
public IcecastAudioBroadcaster(BroadcastConfiguration broadcastConfiguration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(broadcastConfiguration, audioSampleRate, mp3Setting);
super(broadcastConfiguration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
}

View File

@@ -20,7 +20,7 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.properties.SystemProperties;
@@ -70,10 +70,10 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, AudioSampleRate audioSampleRate,
public IcecastHTTPAudioBroadcaster(IcecastHTTPConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, audioSampleRate, mp3Setting, aliasModel);
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
}
/**

View File

@@ -21,7 +21,7 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3AudioConverter;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.properties.SystemProperties;
@@ -68,10 +68,10 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
*
* @param configuration for the Icecast stream
*/
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, AudioSampleRate audioSampleRate,
public IcecastTCPAudioBroadcaster(IcecastTCPConfiguration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, audioSampleRate, mp3Setting, aliasModel);
super(configuration, inputAudioFormat, mp3Setting, aliasModel);
}
/**

View File

@@ -22,7 +22,7 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.util.ThreadPool;
import org.apache.mina.core.RuntimeIoException;
@@ -63,10 +63,10 @@ public class ShoutcastV1AudioBroadcaster extends AudioStreamingBroadcaster
*
* @param configuration for the Shoutcast stream
*/
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, AudioSampleRate audioSampleRate,
public ShoutcastV1AudioBroadcaster(ShoutcastV1Configuration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, audioSampleRate, mp3Setting);
super(configuration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
}

View File

@@ -40,7 +40,7 @@ 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.InputAudioFormat;
import io.github.dsheirer.audio.convert.MP3Setting;
import io.github.dsheirer.identifier.Form;
import io.github.dsheirer.identifier.Identifier;
@@ -93,10 +93,10 @@ public class ShoutcastV2AudioStreamingBroadcaster extends AudioStreamingBroadcas
*
* @param configuration for the Shoutcast V2 stream
*/
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, AudioSampleRate audioSampleRate,
public ShoutcastV2AudioStreamingBroadcaster(ShoutcastV2Configuration configuration, InputAudioFormat inputAudioFormat,
MP3Setting mp3Setting, AliasModel aliasModel)
{
super(configuration, audioSampleRate, mp3Setting);
super(configuration, inputAudioFormat, mp3Setting);
mAliasModel = aliasModel;
}

View File

@@ -24,14 +24,19 @@ import io.github.dsheirer.audio.AudioFormats;
import javax.sound.sampled.AudioFormat;
/**
* Enumeration of (input) audio sample rates for mono audio.
* Enumeration of MP3 input audio formats for mono audio.
*/
public enum AudioSampleRate
public enum InputAudioFormat
{
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");
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz"),
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
SR_32_16000(AudioFormats.PCM_SIGNED_16000_HZ_32_BIT_MONO, "32-Bit 16000 Hz"),
SR_32_22050(AudioFormats.PCM_SIGNED_22050_HZ_32_BIT_MONO, "32-Bit 22050 Hz"),
SR_32_44100(AudioFormats.PCM_SIGNED_44100_HZ_32_BIT_MONO, "32-Bit 44100 Hz");
private AudioFormat mAudioFormat;
private String mLabel;
@@ -40,7 +45,7 @@ public enum AudioSampleRate
* Constructs an instance
* @param audioFormat for the specified sample rate
*/
AudioSampleRate(AudioFormat audioFormat, String label)
InputAudioFormat(AudioFormat audioFormat, String label)
{
mAudioFormat = audioFormat;
mLabel = label;
@@ -49,9 +54,9 @@ public enum AudioSampleRate
/**
* Default sample rate
*/
public static AudioSampleRate getDefault()
public static InputAudioFormat getDefault()
{
return SR_8000;
return SR_22050;
}
/**

View File

@@ -34,7 +34,7 @@ public class LameFactory
* @param setting to apply to the encoder
* @return lame encoder
*/
public static LameEncoder getLameEncoder(AudioSampleRate input, MP3Setting setting)
public static LameEncoder getLameEncoder(InputAudioFormat input, MP3Setting setting)
{
return switch(setting)
{
@@ -52,13 +52,13 @@ public class LameFactory
* @param sampleRate to resample to
* @return resampler.
*/
public static RealResampler getResampler(AudioSampleRate sampleRate)
public static RealResampler getResampler(InputAudioFormat 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);
case SR_16000, SR_32_16000 -> new RealResampler(8000, 16000, 4096, 512);
case SR_22050, SR_32_22050 -> new RealResampler(8000, 22050, 4096, 512);
case SR_44100, SR_32_44100 -> new RealResampler(8000, 44100, 4096, 512);
default -> throw new IllegalArgumentException("Unrecognized sample rate for resampling: " + sampleRate);
};
}

View File

@@ -32,6 +32,9 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Converts PCM audio packets to Mono, MP3 encoded audio
*/
public class MP3AudioConverter implements IAudioConverter
{
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
@@ -40,25 +43,33 @@ public class MP3AudioConverter implements IAudioConverter
private RealResampler mResampler;
private ByteArrayOutputStream mMP3EncodedFramesStream = new ByteArrayOutputStream();
private byte[] mOutputFramesBuffer;
private InputAudioFormat mInputAudioFormat;
/**
* Converts PCM 16-bit Little Endian audio packets to Mono, MP3 compressed audio.
* Constructs an instance.
*
* @param audioSampleRate for the desired input sample rate (resampled from default 8 kHz as needed)
* @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
*/
public MP3AudioConverter(AudioSampleRate audioSampleRate, MP3Setting setting)
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting)
{
mEncoder = LameFactory.getLameEncoder(audioSampleRate, setting);
mInputAudioFormat = inputAudioFormat;
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
if(audioSampleRate != AudioSampleRate.SR_8000)
//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)
{
mResampler = LameFactory.getResampler(audioSampleRate);
mResampler = LameFactory.getResampler(inputAudioFormat);
}
mOutputFramesBuffer = new byte[mEncoder.getPCMBufferSize()];
}
/**
* Converts the list of PCM audio packets to MP3 encoded.
* @param audioPackets of PCM audio sampled at 8 kHz
* @return encoded MP3 audio
*/
public List<byte[]> convert(List<float[]> audioPackets)
{
List<byte[]> converted = new ArrayList<>();
@@ -70,7 +81,17 @@ public class MP3AudioConverter implements IAudioConverter
for(int x = 0; x < audioPackets.size(); x++)
{
byte[] bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
byte[] bytesToEncode = null;
if(mInputAudioFormat.getAudioFormat().getSampleSizeInBits() == 16)
{
bytesToEncode = ConversionUtils.convertToSigned16BitSamples(audioPackets.get(x)).array();
}
else
{
bytesToEncode = ConversionUtils.convertToSigned32BitSamples(audioPackets.get(x)).array();
}
int bytesToEncodePointer = 0;
int inputChunkSize = FastMath.min(mOutputFramesBuffer.length, bytesToEncode.length);

View File

@@ -58,7 +58,7 @@ public class MP3FrameInspector
public static void main(String[] args)
{
mLog.info("Starting ...");
MP3SilenceGenerator gen = new MP3SilenceGenerator(AudioSampleRate.SR_8000, MP3Setting.getDefault());
MP3SilenceGenerator gen = new MP3SilenceGenerator(InputAudioFormat.SR_8000, MP3Setting.getDefault());
List<byte[]> audio = gen.generate(173);

View File

@@ -31,16 +31,16 @@ public class MP3SilenceGenerator implements ISilenceGenerator
public static final boolean CONSTANT_BIT_RATE = false;
private MP3AudioConverter mMP3AudioConverter;
private AudioSampleRate mAudioSampleRate;
private InputAudioFormat mInputAudioFormat;
private byte[] mPreviousPartialFrameData;
/**
* Generates MP3 audio silence frames
*/
public MP3SilenceGenerator(AudioSampleRate audioSampleRate, MP3Setting setting)
public MP3SilenceGenerator(InputAudioFormat inputAudioFormat, MP3Setting setting)
{
mAudioSampleRate = audioSampleRate;
mMP3AudioConverter = new MP3AudioConverter(audioSampleRate, setting);
mInputAudioFormat = inputAudioFormat;
mMP3AudioConverter = new MP3AudioConverter(inputAudioFormat, setting);
}
/**
@@ -53,7 +53,7 @@ public class MP3SilenceGenerator implements ISilenceGenerator
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());
int length = (int)(duration_secs * InputAudioFormat.SR_8000.getSampleRate());
List<float[]> silenceBuffers = new ArrayList<>();
@@ -94,7 +94,7 @@ public class MP3SilenceGenerator implements ISilenceGenerator
{
mLog.debug("Starting ...");
MP3SilenceGenerator generator = new MP3SilenceGenerator(AudioSampleRate.SR_44100, MP3Setting.VBR_7);
MP3SilenceGenerator generator = new MP3SilenceGenerator(InputAudioFormat.SR_44100, MP3Setting.VBR_7);
int x = 100;

View File

@@ -36,7 +36,7 @@ public class MonoAudioOutput extends AudioOutput
public MonoAudioOutput(Mixer mixer, UserPreferences userPreferences)
{
super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8_KHZ_16BITS_MONO,
super(mixer, MixerChannel.MONO, AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO,
AudioFormats.MONO_SOURCE_DATALINE_INFO, BUFFER_SIZE, userPreferences);
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
* ****************************************************************************
*/
package io.github.dsheirer.audio.playback;
import io.github.dsheirer.audio.AudioFormats;
@@ -36,7 +36,7 @@ public class StereoAudioOutput extends AudioOutput
public StereoAudioOutput(Mixer mixer, MixerChannel channel, UserPreferences userPreferences)
{
super(mixer, channel, AudioFormats.PCM_SIGNED_8KHZ_16BITS_STEREO, AudioFormats.STEREO_SOURCE_DATALINE_INFO,
super(mixer, channel, AudioFormats.PCM_SIGNED_8000_HZ_16BITS_STEREO, AudioFormats.STEREO_SOURCE_DATALINE_INFO,
BUFFER_SIZE, userPreferences);
}