/******************************************************************************* * sdr-trunk * Copyright (C) 2014-2018 Dennis Sheirer * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. * If not, see * ******************************************************************************/ package io.github.dsheirer.sample.buffer; import io.github.dsheirer.sample.Listener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.LinkedTransferQueue; /** * Delay buffer/queue for reusable complex buffers with support for pre-loading new buffer listeners (ie channels) * with delayed sample buffers and broadcasting of incoming sample buffers to a list of listeners. * * This class is designed to compensate/account for any delays in processing of a control channel and subsequent * channel grants so that when a new channel is allocated, that channel can be pre-loaded with time delayed sample * buffers that start as close as possible to the channel grant timestamp, in order to avoid cutting off the * beginning of a channel grant transmission. * * New channel listeners are enqueued with a request timestamp and as new buffers arrive, the new channel listeners * are preloaded with delayed buffers from the queue and then added to an internal broadcaster so that they * receive all subsequent buffers as normal. * * This class is designed to be controlled by the sample producer thread for adding new listeners and pre-loading * delayed buffers. Any listeners that are added to this class are expected to implement a non-blocking receive method * so as not to delay the stream of sample buffers. Channel listeners are expected to implement buffer queue processing * on another thread. */ public class ReusableComplexDelayBuffer implements Listener { private final static Logger mLog = LoggerFactory.getLogger(ReusableComplexDelayBuffer.class); private ReusableBufferBroadcaster mBroadcaster = new ReusableBufferBroadcaster<>(); private LinkedTransferQueue mActionRequestQueue = new LinkedTransferQueue<>(); private ActionRequest mActionRequest; private ReusableComplexBuffer[] mDelayBuffer; private int mDelayBufferPointer = 0; private long mBufferDuration; /** * Creates a new delay buffer with the specified delay length and the buffer sample rate. * * @param size of the delay queue * @param bufferDuration in milliseconds for each complex buffer processed by this instance */ public ReusableComplexDelayBuffer(int size, long bufferDuration) { mDelayBuffer = new ReusableComplexBuffer[size]; mBufferDuration = bufferDuration; } /** * Prepares this instance for disposal by releasing all stored sample buffers. */ public void dispose() { clearBuffer(); } public void clear() { //Submit a clear buffer request to be processed upon the next buffer that arrives mActionRequestQueue.offer(new ActionRequest()); } /** * Clears any delayed/enqueued sample buffers. */ private void clearBuffer() { for(int x = 0; x < mDelayBuffer.length; x++) { if(mDelayBuffer[x] != null) { mDelayBuffer[x].decrementUserCount(); mDelayBuffer[x] = null; } } mDelayBufferPointer = 0; } @Override public synchronized void receive(ReusableComplexBuffer reusableComplexBuffer) { mActionRequest = mActionRequestQueue.poll(); while(mActionRequest != null) { switch(mActionRequest.getAction()) { case ADD_USER: processNewListener(mActionRequest); break; case REMOVE_USER: mBroadcaster.removeListener(mActionRequest.getListener()); break; case CLEAR_BUFFER: clearBuffer(); break; } mActionRequest = mActionRequestQueue.poll(); } //Increment user count before we hand it to the broadcaster so that we can keep it as a copy for the //delay buffer reusableComplexBuffer.incrementUserCount(); mBroadcaster.receive(reusableComplexBuffer); //Decrement the user count on the oldest buffer before we replace it in the delay queue if(mDelayBuffer[mDelayBufferPointer] != null) { mDelayBuffer[mDelayBufferPointer].decrementUserCount(); } //Store the new buffer in the delay queue and increment the pointer mDelayBuffer[mDelayBufferPointer++] = reusableComplexBuffer; //Wrap the delay buffer pointer as needed if(mDelayBufferPointer >= mDelayBuffer.length) { mDelayBufferPointer = 0; } } /** * Indicates if any listeners are registered with this delay buffer */ public boolean hasListeners() { return mBroadcaster.hasListeners(); } /** * Processes any newly added listeners by checking all buffers in the delay queue and pre-loading the * listeners with any buffers that occur on or after the listener's requested start timestamp. */ private void processNewListener(ActionRequest listenerToAdd) { ReusableComplexBuffer bufferToEvaluate; int pointer = mDelayBufferPointer; int preloadedBufferCount = 0; for(int x = 0; x < mDelayBuffer.length; x++) { bufferToEvaluate = mDelayBuffer[pointer]; if(bufferToEvaluate != null && (bufferToEvaluate.getTimestamp() >= listenerToAdd.getTimestamp() || bufferToEvaluate.getTimestamp() + mBufferDuration >= listenerToAdd.getTimestamp())) { bufferToEvaluate.incrementUserCount(); listenerToAdd.getListener().receive(bufferToEvaluate); preloadedBufferCount++; } pointer++; if(pointer >= mDelayBuffer.length) { pointer = 0; } } mBroadcaster.addListener(listenerToAdd.getListener()); } /** * Adds the listener to receive a copy of each buffer. The listener will be preloaded with delayed buffers * that are at or after the specified timestamp. * * @param listener to add * @param timestamp of the oldest sample buffers to preload to the listener */ public void addListener(Listener listener, long timestamp) { mActionRequestQueue.add(new ActionRequest(listener, timestamp)); } /** * Removes the listener from receiving sample buffers */ public void removeListener(Listener listener) { mActionRequestQueue.add(new ActionRequest(listener)); } private enum Action{ADD_USER, REMOVE_USER, CLEAR_BUFFER}; /** * Actions that must be completed on the incoming sample stream thread. */ public class ActionRequest { private Action mAction; private Listener mListener; private long mTimestamp; /** * Creates an add listener request * @param listener to add * @param timestamp for the preloading buffers to the listener from the delay queue */ public ActionRequest(Listener listener, long timestamp) { mAction = Action.ADD_USER; mListener = listener; mTimestamp = timestamp; } /** * Creates a remove listener request * @param listener */ public ActionRequest(Listener listener) { mAction = Action.REMOVE_USER; mListener = listener; } /** * Creates a clearBuffer buffer request */ public ActionRequest() { mAction = Action.CLEAR_BUFFER; } public Action getAction() { return mAction; } /** * Listener to be added/removed */ public Listener getListener() { return mListener; } /** * Requested timestamp for buffers to be preloaded. */ public long getTimestamp() { return mTimestamp; } } }