/******************************************************************************* * sdrtrunk * Copyright (C) 2014-2016 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see * ******************************************************************************/ package audio.broadcast; import audio.AudioPacket; import audio.IAudioPacketListener; import audio.convert.IAudioConverter; import controller.ThreadPoolManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public abstract class Broadcaster implements IAudioPacketListener, Listener { private final static Logger mLog = LoggerFactory.getLogger( Broadcaster.class ); private ThreadPoolManager mThreadPoolManager; private ScheduledFuture mScheduledTask; private AudioQueueProcessor mAudioQueueProcessor; protected LinkedTransferQueue mAudioQueue = new LinkedTransferQueue<>(); private List> mBroadcastStateListeners = new CopyOnWriteArrayList<>(); private BroadcastState mBroadcastState = BroadcastState.READY; private BroadcastConfiguration mBroadcastConfiguration; private IAudioConverter mAudioConverter; private boolean mEnabled = true; /** * Broadcaster for sending audio to a remote broadcast server. */ public Broadcaster(ThreadPoolManager threadPoolManager, BroadcastConfiguration broadcastConfiguration, IAudioConverter audioConverter) { mThreadPoolManager = threadPoolManager; mBroadcastConfiguration = broadcastConfiguration; mAudioConverter = audioConverter; mAudioQueueProcessor = new AudioQueueProcessor(); } /** * Broadcast configuration used by this broadcaster */ public BroadcastConfiguration getBroadcastConfiguration() { return mBroadcastConfiguration; } /** * IAudioPacketListener interface method */ @Override public Listener getAudioPacketListener() { return this; } /** * Audio converter used by this broadcaster to convert PCM audio packets to the desired output format. */ protected IAudioConverter getAudioConverter() { return mAudioConverter; } /** * Commands the broadcaster to broadcast any queued audio packets. This method is invoked by a scheduled thread * pool task to periodically command the sub-class to broadcast queued audio packets. */ protected abstract void broadcast(); /** * Disconnects the broadcaster and halts all broadcasting. */ public void stop() { mEnabled = false; stopProcessor(); } /** * Primary audio packet input method. Audio packets are queued and then processed by the underlying broadcast * handler for broadcast to the remote server. */ public void receive(AudioPacket packet) { if(mEnabled) { startProcessor(); mAudioQueue.add(packet); } } /** * Starts the audio queue processor. The audio packet queue is serviced every 100 milliseconds. Subsequent calls * to this method once running are ignored. */ private void startProcessor() { if(mEnabled && mScheduledTask == null) { if(mThreadPoolManager != null) { mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING, mAudioQueueProcessor, 250, TimeUnit.MILLISECONDS ); } } } /** * Stops the audio queue processor */ private void stopProcessor() { if(mThreadPoolManager != null && mScheduledTask != null) { mThreadPoolManager.cancel(mScheduledTask); } } /** * Pauses or unpauses the broadcast */ public void setPaused(boolean paused) { if(!getBroadcastState().isErrorState()) { if(paused) { setBroadcastState(BroadcastState.PAUSED); } else { setBroadcastState(BroadcastState.READY); } } } /** * Registers the listener to receive broadcast state changes */ public void addListener(Listener listener) { mBroadcastStateListeners.add(listener); } /** * Removes the listener from receiving broadcast state changes */ public void removeListener(Listener listener) { mBroadcastStateListeners.remove(listener); } /** * Sets the state of the broadcast connection */ protected void setBroadcastState(BroadcastState state) { if(mBroadcastState != state) { mLog.debug("Changing State to: " + state); mBroadcastState = state; for(Listener listener: mBroadcastStateListeners) { listener.receive(state); } } } /** * Current state of the broadcast connection */ public BroadcastState getBroadcastState() { return mBroadcastState; } public boolean connected() { return getBroadcastState() == BroadcastState.CONNECTED; } /** * Indicates if this broadcaster can connect and is not currently in an error state or a connected state. */ public boolean canConnect() { BroadcastState state = getBroadcastState(); return state != BroadcastState.CONNECTED && !state.isErrorState(); } /** * Audio packet queue processor. Runs via a scheduled thread pool task as a timing processor to instruct * sub-classes to broadcast queued audio packets. Internal state monitor ensures that subsequent invocations of * the broadcast method do not start until the previous invocation completes. */ public class AudioQueueProcessor implements Runnable { private AtomicBoolean mProcessing = new AtomicBoolean(); @Override public void run() { if(mProcessing.compareAndSet(false, true)) { broadcast(); mProcessing.set(false); } } } }