Skip to content

Commit

Permalink
Walkie-talkie service
Browse files Browse the repository at this point in the history
  • Loading branch information
rom1v committed Mar 8, 2013
1 parent d9e8483 commit 3059bc4
Show file tree
Hide file tree
Showing 9 changed files with 1,273 additions and 0 deletions.
9 changes: 9 additions & 0 deletions AndroidManifest.xml
Expand Up @@ -253,6 +253,15 @@
android:resource="@xml/contacts" />
</service>

<service android:name=".walkietalkie.WalkieTalkieService" android:exported="false">
<intent-filter>
<action android:name="org.servalproject.walkietalkie.START_SPEAKING" />
<action android:name="org.servalproject.walkietalkie.STOP_SPEAKING" />
<action android:name="org.servalproject.walkietalkie.START_LISTENING" />
<action android:name="org.servalproject.walkietalkie.STOP_LISTENING" />
</intent-filter>
</service>

<activity android:launchMode="singleTop"
android:name="WifiJammedActivity"></activity>

Expand Down
256 changes: 256 additions & 0 deletions src/org/servalproject/walkietalkie/AudioReceiver.java
@@ -0,0 +1,256 @@
package org.servalproject.walkietalkie;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.servalproject.servald.mdp.MeshPacket;
import org.servalproject.servald.mdp.MeshSocket;
import org.servalproject.servald.mdp.MeshSocketException;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.SystemClock;
import android.util.Log;

/**
* Receive audio packets, bufferize and mix them, then play.
*
* @author Romain Vimont (®om) <rom@rom1v.com>
*
*/
public class AudioReceiver {

private final ExecutorService bufferizerExecutor = Executors.newSingleThreadExecutor();
private final ExecutorService playerExecutor = Executors.newSingleThreadExecutor();

private static final String TAG = "AudioReceiver";

public static final Compression COMPRESSION = AudioSender.COMPRESSION;

public static final int PACKET_SIZE = AudioSender.PACKET_SIZE;
public static final int HEADER_SIZE = AudioSender.HEADER_SIZE;
public static final int PAYLOAD_SIZE = AudioSender.PAYLOAD_SIZE;

private static final int RATE = AudioSender.RATE; // Hz
private static final int CHANNEL = AudioFormat.CHANNEL_OUT_MONO;
private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
private static final int PLAYER_BUFFER_SIZE = AudioTrack
.getMinBufferSize(RATE, CHANNEL, FORMAT) * 2;

private static final int BUFFER_MS = 1000;
private static final int DELAY_IN_MS = 100;
private static final int DELAY_IN_SAMPLES = Mixer.toSamples(DELAY_IN_MS, RATE);

private MeshSocket socket;
private int port;

private Bufferizer bufferizer;
private Player player;
private Future<?> bufferizerFuture;
private Future<?> playerFuture;

public AudioReceiver(int port) {
this.port = port;
}

public synchronized void start() {
Mixer mixer = new Mixer(RATE, BUFFER_MS, DELAY_IN_SAMPLES);

bufferizer = new Bufferizer(mixer);
bufferizerFuture = bufferizerExecutor.submit(bufferizer);

player = new Player(mixer);
playerFuture = playerExecutor.submit(player);
}

public synchronized void stop() {
if (isRunning()) {
bufferizerFuture.cancel(true);
bufferizer.stopSocket();
bufferizer = null;
playerFuture.cancel(true);
player.stopMixer();
player = null;
}
}

private synchronized boolean isRunning() {
return bufferizer != null;
}

private class Bufferizer implements Runnable {

private Mixer mixer;

private volatile boolean stopped;

Bufferizer(Mixer mixer) {
this.mixer = mixer;
}

public synchronized void stopSocket() {
stopped = true;
if (socket != null) {
/* close socket for unblocking receive */
socket.close();
}
}

@Override
public void run() {
try {
int attempts = 0;
do {
try {
synchronized (this) {
if (stopped) {
return;
}
/* try to initialize mesh socket */
socket = new MeshSocket(port);
}
} catch (MeshSocketException e) {
/*
* attempt failed, maybe because servald is not started yet (State becomes
* "on" *before* servald is really on, need to fix it upstream on batphone)
*/

if (++attempts >= 5) {
/* definitively fail after 5 tries */
throw e;
}

Log.e(TAG, "Receiver socket creation failed, retrying...", e);

/* retry after 1.5 s */
try {
Thread.sleep(1500);
} catch (InterruptedException ie) {
/* do nothing, but sleep() is interrupted */
}
}
} while (socket == null);

byte[] buf = new byte[PACKET_SIZE];
byte[] writeBuf = new byte[COMPRESSION.ratio * PAYLOAD_SIZE];
MeshPacket packet = new MeshPacket(buf, PACKET_SIZE);

while (!stopped) {
try {
socket.receive(packet);

int seq = (buf[0] & 0xff) << 8 | buf[1] & 0xff;
int timestamp = (buf[2] & 0xff) << 24 | (buf[3] & 0xff) << 16
| (buf[4] & 0xff) << 8 | buf[5] & 0xff;
int ssrc = (buf[6] & 0xff) << 24 | (buf[7] & 0xff) << 16
| (buf[8] & 0xff) << 8 | buf[9] & 0xff;
Log.i(TAG, "ssrc=" + ssrc + ", " + buf[6] + ":" + buf[7] + ":" + buf[8]
+ ":" + buf[9]);

int writeBufLength = decompress(buf, writeBuf, HEADER_SIZE,
packet.getLength() - HEADER_SIZE);
int written = mixer.write(ssrc, timestamp, writeBuf, 0, writeBufLength);
Log.i(TAG, "(" + ssrc + ") Packet " + seq + "[" + packet.getBuf().length
+ "] " + written);

} catch (IOException e) {
if (!stopped) {
Log.e(TAG, "Cannot receive data", e);
}
}
}
} catch (MeshSocketException e) {
Log.e(TAG, "Cannot create receiver socket", e);
} finally {

}
}
};

private class Player implements Runnable {

private Mixer mixer;

private AudioTrack audioTrack;

private volatile boolean stopped;

Player(Mixer mixer) {
this.mixer = mixer;
}

public synchronized void stopMixer() {
stopped = true;
mixer.close();
}

@Override
public void run() {
try {
synchronized (this) {
if (stopped) {
return;
}
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RATE, CHANNEL, FORMAT,
PLAYER_BUFFER_SIZE, AudioTrack.MODE_STREAM);
}

byte[] buf = new byte[PAYLOAD_SIZE];

while (!stopped) {
int read;
synchronized (mixer) {
read = mixer.read(buf, 0, buf.length);

if (stopped || read == 0) {
return;
}

Log.i(TAG, "mixerPlayer.read() : " + SystemClock.elapsedRealtime() + " ["
+ read + "]");
mixer.move(read);
}
audioTrack.write(buf, 0, read);
/* play and stop after playing this packet (unless another call play() again) */
audioTrack.play();
audioTrack.stop();
}
} finally {
if (audioTrack == null) {
audioTrack.release();
}
}
}
};

private static int decompress(byte[] buf, byte[] writeBuf, int bufOffset, int bufPayloadLength) {
switch (COMPRESSION) {
case NONE:
System.arraycopy(buf, bufOffset, writeBuf, 0, bufPayloadLength);
return bufPayloadLength;
case TO_8_BITS:
/* convert 8 bits to 16 bits */
for (int i = 0; i < bufPayloadLength; i++) {
/* recreate lower bits read 8 bits (little endian) */
writeBuf[2 * i + 1] = buf[bufOffset + i];
}
return bufPayloadLength * 2;
case A_LAW:
for (int i = 0; i < bufPayloadLength; i++) {
byte alaw = buf[i + bufOffset];
int sample = G711.decodeALaw(alaw);
byte msb = (byte) (sample >> 8);
byte lsb = (byte) sample;
writeBuf[2 * i + 1] = msb;
writeBuf[2 * i] = lsb;
}
return bufPayloadLength * 2;
default:
throw new UnsupportedOperationException(COMPRESSION + " not implemented");
}
}

}

0 comments on commit 3059bc4

Please sign in to comment.