mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-10 23:57:25 -05:00
#939 Adds MP3 encoder preferences to the User Preferences dialog and provides user control over sample rate and MP3 encoder settings. MP3 preferences are global and affect both recording and streaming. (#1159)
Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* *****************************************************************************
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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)
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* 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;
|
||||
|
||||
import io.github.dsheirer.sample.ConversionUtils;
|
||||
@@ -75,5 +78,4 @@ public class AudioUtils
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<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)
|
||||
public AudioStreamingBroadcaster(T broadcastConfiguration, AudioSampleRate audioSampleRate, MP3Setting mp3Setting)
|
||||
{
|
||||
super(broadcastConfiguration);
|
||||
mDelay = getBroadcastConfiguration().getDelay();
|
||||
mMaximumRecordingAge = getBroadcastConfiguration().getMaximumRecordingAge();
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
|
||||
mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat(),
|
||||
audioSampleRate, mp3Setting);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
@@ -279,7 +280,13 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
{
|
||||
if(mFinalSilencePadding > 0)
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
|
||||
List<byte[]> finalSilence = mSilenceGenerator.generate(mFinalSilencePadding);
|
||||
|
||||
for(byte[] silence: finalSilence)
|
||||
{
|
||||
broadcastAudio(silence);
|
||||
}
|
||||
|
||||
mFinalSilencePadding = 0;
|
||||
}
|
||||
|
||||
@@ -312,7 +319,11 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
}
|
||||
else
|
||||
{
|
||||
broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
|
||||
List<byte[]> silenceFrames = mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS);
|
||||
for(byte[] silence: silenceFrames)
|
||||
{
|
||||
broadcastAudio(silence);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Throwable t)
|
||||
|
||||
@@ -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<AudioSegment>
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3);
|
||||
AudioSegmentRecorder.record(audioSegment, path, RecordFormat.MP3, mUserPreferences);
|
||||
IdentifierCollection identifierCollectionCopy =
|
||||
new IdentifierCollection(audioSegment.getIdentifierCollection().getIdentifiers());
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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");
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* 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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<Audio
|
||||
private AliasModel mAliasModel;
|
||||
private Broadcaster<BroadcastEvent> 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<Audio
|
||||
{
|
||||
mAliasModel = aliasModel;
|
||||
mIconModel = iconModel;
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Monitor to remove temporary recording files that have been streamed by all audio broadcasters
|
||||
ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecordingDeletionMonitor(), 15l, 15l, TimeUnit.SECONDS);
|
||||
@@ -346,7 +345,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
deleteBroadcaster(configuredBroadcast);
|
||||
}
|
||||
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration, mAliasModel);
|
||||
AbstractAudioBroadcaster audioBroadcaster = BroadcastFactory.getBroadcaster(broadcastConfiguration,
|
||||
mAliasModel, mUserPreferences);
|
||||
|
||||
if(audioBroadcaster != null)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,8 @@ 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.MP3Setting;
|
||||
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
@@ -81,7 +83,8 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
||||
* @param config to use
|
||||
* @param aliasModel for access to aliases
|
||||
*/
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, AliasModel aliasModel)
|
||||
public BroadcastifyCallBroadcaster(BroadcastifyCallConfiguration config, AudioSampleRate audioSampleRate,
|
||||
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||
{
|
||||
super(config);
|
||||
}
|
||||
|
||||
@@ -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,23 +14,26 @@
|
||||
*
|
||||
* 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.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<float[]> audioBuffers);
|
||||
List<byte[]> convert(List<float[]> 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<byte[]> flush();
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISilenceGenerator
|
||||
{
|
||||
byte[] generate(long duration);
|
||||
List<byte[]> generate(long duration);
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<byte[]> convert(List<float[]> audioPackets)
|
||||
{
|
||||
List<byte[]> 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<float[]> 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<float[]> 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<byte[]> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<byte[]> 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<byte[]> audio = gen.generate(173);
|
||||
|
||||
inspect(audio);
|
||||
|
||||
|
||||
115
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
115
src/main/java/io/github/dsheirer/audio/convert/MP3Header.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
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<byte[]> 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<float[]> 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<byte[]> 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<float[]> resample(List<float[]> audioPackets)
|
||||
{
|
||||
List<float[]> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -168,6 +168,7 @@ public class UserPreferencesEditor extends BorderPane
|
||||
|
||||
TreeItem<String> 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);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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<MP3Setting> mMP3SettingComboBox;
|
||||
private ComboBox<AudioSampleRate> 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<MP3Setting> 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<AudioSampleRate> 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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -29,6 +29,7 @@ public enum PreferenceType
|
||||
DIRECTORY,
|
||||
DUPLICATE_CALL_DETECTION,
|
||||
JMBE_LIBRARY,
|
||||
MP3,
|
||||
MULTI_FREQUENCY,
|
||||
PLAYLIST,
|
||||
PLAYBACK,
|
||||
|
||||
@@ -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<PreferenceType>
|
||||
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<PreferenceType>
|
||||
return mRecordPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* MP3 preferences
|
||||
*/
|
||||
public MP3Preference getMP3Preference()
|
||||
{
|
||||
return mMP3Preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier preferences
|
||||
*/
|
||||
@@ -199,6 +209,7 @@ public class UserPreferences implements Listener<PreferenceType>
|
||||
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);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
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<PreferenceType> 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();
|
||||
}
|
||||
}
|
||||
@@ -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<AudioSegment>
|
||||
|
||||
try
|
||||
{
|
||||
AudioSegmentRecorder.record(audioSegment, path, recordFormat);
|
||||
AudioSegmentRecorder.record(audioSegment, path, recordFormat, mUserPreferences);
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
|
||||
@@ -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<byte[]> mp3Frames = converter.convert(audioSegment.getAudioBuffers());
|
||||
for(byte[] mp3Frame: mp3Frames)
|
||||
{
|
||||
outputStream.write(lastFrame);
|
||||
outputStream.write(mp3Frame);
|
||||
}
|
||||
|
||||
List<byte[]> 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())
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user