mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#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:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user