package de.tu.darmstadt.seemoo.ansian.model; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.util.Log; import de.tu.darmstadt.seemoo.ansian.model.filter.FirFilter; /** * <h1>AnSiAn - Audio Sink</h1> * * Module: AudioSink.java Description: This class implements the interface to * the systems audio API. It will run in a separate thread and buffer incoming * sample packets in a blocking queue. Input packets are demodulated (real) * signals. This class will decimate the incoming sample rate according to the * audio rate. * * @author Dennis Mantz * * Copyright (C) 2014 Dennis Mantz License: * http://www.gnu.org/licenses/gpl.html GPL version 2 or higher * * This library 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 2 of the License, or (at * your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ public class AudioSink extends Thread { private AudioTrack audioTrack = null; // AudioTrack object that is used to // pass audio samples to the Android // system private boolean stopRequested = true; private ArrayBlockingQueue<SamplePacket> queue = null; // Queue that // holds // incoming // samples private int packetSize; // packet size of the incoming sample packets private int sampleRate; // audio sample rate of the AudioSink private static final int QUEUE_SIZE = 5; // This results in a double buffer. // see Scheduler... private static final String LOGTAG = "AudioSink"; private FirFilter audioFilter1 = null; // Filter used to decimate the // incoming signal rate private FirFilter audioFilter2 = null; // Cascaded filter for high incoming // signal rates private SamplePacket tmpAudioSamples; // tmp buffer for audio filters. // private long oldTimestamp = System.currentTimeMillis(); /** * Constructor. Will create a new AudioSink. * * @param packetSize * size of the incoming packets * @param sampleRate * sample rate of the audio signal */ public AudioSink(int packetSize, int sampleRate) { this.packetSize = packetSize; this.sampleRate = sampleRate; setPriority(MAX_PRIORITY); // Create the queues and fill them with queue = new ArrayBlockingQueue<SamplePacket>(QUEUE_SIZE); // Create an instance of the AudioTrack class: int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) * 3; this.audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM); // Create the audio filters: this.audioFilter1 = FirFilter.createLowPass(2, 1, 1, 0.1f, 0.15f, 30); Log.d(LOGTAG, "constructor: created audio filter 1 with " + audioFilter1.getNumberOfTaps() + " Taps."); this.audioFilter2 = FirFilter.createLowPass(4, 1, 1, 0.1f, 0.1f, 30); Log.d(LOGTAG, "constructor: created audio filter 2 with " + audioFilter2.getNumberOfTaps() + " Taps."); this.tmpAudioSamples = new SamplePacket(packetSize); } /** * Starts the thread */ @Override public synchronized void start() { stopRequested = false; super.start(); } /** * Stops the thread */ public void stopSink() { stopRequested = true; } /** * @return size of the packets that are offered by getPacketBuffer() */ public int getPacketSize() { return packetSize; } /** * Enqueues a packet buffer for being played on the audio track. * * @param packet * the packet buffer from getPacketBuffer() filled with samples * @return true if success, false if error */ public boolean enqueuePacket(SamplePacket packet) { if (packet == null) { Log.e(LOGTAG, "enqueuePacket: Packet is null."); return false; } if (!queue.offer(packet)) { Log.e(LOGTAG, "enqueuePacket: Queue is full."); return false; } return true; } @Override public void run() { SamplePacket packet; SamplePacket filteredPacket; SamplePacket tempPacket = new SamplePacket(packetSize); float[] floatPacket; short[] shortPacket = new short[packetSize]; Log.i(LOGTAG, "AudioSink started. (Thread: " + this.getName() + ")"); // start audio playback: audioTrack.play(); // Continuously write the data from the queue to the audio track: while (!stopRequested) { try { // Get the next packet from the queue packet = queue.poll(1000, TimeUnit.MILLISECONDS); if (packet == null) { // Log.d(LOGTAG, "run: Queue is empty. skip this round"); continue; } // apply audio filter (decimation) if (packet.getSampleRate() > this.sampleRate) { applyAudioFilter(packet, tempPacket); filteredPacket = tempPacket; } else filteredPacket = packet; // Convert doubles to shorts [expect doubles to be in [-1...1] floatPacket = filteredPacket.getRe(); for (int i = 0; i < filteredPacket.size(); i++) { shortPacket[i] = (short) (floatPacket[i] * 32767); } // Write it to the audioTrack: if (audioTrack.write(shortPacket, 0, filteredPacket.size()) != filteredPacket.size()) { Log.e(LOGTAG, "run: write() returned with error! stop"); stopRequested = true; } // long timestamp = System.currentTimeMillis(); // Log.d(LOGTAG, "timeBetweenAudioWrites: " + (timestamp - // oldTimestamp)); // oldTimestamp = timestamp; } catch (InterruptedException e) { Log.e(LOGTAG, "run: Interrupted while polling from queue. stop"); stopRequested = true; } } // stop audio playback: audioTrack.stop(); this.stopRequested = true; Log.i(LOGTAG, "AudioSink stopped. (Thread: " + this.getName() + ")"); } /** * Will filter the real array contained in input and decimate them to the * audio rate. * * @param input * incoming (unfiltered) samples at the incoming rate (quadrature * rate) * @param output * outgoing (filtered, decimated) samples at audio rate */ public void applyAudioFilter(SamplePacket input, SamplePacket output) { // if we need a decimation of 8: apply first and second filter (decimate // to input_rate/8) if (input.getSampleRate() / sampleRate == 8) { // apply first filter (decimate to input_rate/2) tmpAudioSamples.setSize(0); // mark buffer as empty if (audioFilter1.filterReal(input, tmpAudioSamples, 0, input.size()) < input.size()) { Log.e(LOGTAG, "applyAudioFilter: [audioFilter1] could not filter all samples from input packet."); } // apply second filter (decimate to input_rate/8) output.setSize(0); if (audioFilter2.filterReal(tmpAudioSamples, output, 0, tmpAudioSamples.size()) < tmpAudioSamples.size()) { Log.e(LOGTAG, "applyAudioFilter: [audioFilter2] could not filter all samples from input packet."); } } else if (input.getSampleRate() / sampleRate == 2) { // apply first filter (decimate to input_rate/2 ) output.setSize(0); if (audioFilter1.filterReal(input, output, 0, input.size()) < input.size()) { Log.e(LOGTAG, "applyAudioFilter: [audioFilter1] could not filter all samples from input packet."); } } else Log.e(LOGTAG, "applyAudioFilter: incoming sample rate is not supported!"); } }