Add AudioCompression (ulaw)
This commit is contained in:
@@ -21,6 +21,9 @@ and streams audio. Room for improvement includes:
|
|||||||
At the time this project was forked from _Protect Baby Monitor_ there was no obvious open source solution for a
|
At the time this project was forked from _Protect Baby Monitor_ there was no obvious open source solution for a
|
||||||
baby monitor for Android in F-Droid.
|
baby monitor for Android in F-Droid.
|
||||||
|
|
||||||
|
# License information
|
||||||
|
_Child Monitor_ is licensed under the GPLv3. The Ulaw encoding / decoding code is licensed under the Apache License, Version 2.0 and taken from the Android Open Source Project.
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
Audio file originals from [freesound](https://freesound.org).
|
Audio file originals from [freesound](https://freesound.org).
|
||||||
This whole project originally created by [brarcher](https://github.com/brarcher/protect-baby-monitor).
|
This whole project originally created by [brarcher](https://github.com/brarcher/protect-baby-monitor).
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ package de.rochefort.childmonitor;
|
|||||||
|
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
|
|
||||||
|
import de.rochefort.childmonitor.audio.G711UCodec;
|
||||||
|
|
||||||
public class AudioCodecDefines {
|
public class AudioCodecDefines {
|
||||||
public static final int FREQUENCY = 11025;
|
public static final int FREQUENCY = 11025;
|
||||||
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
|
public static final G711UCodec CODEC = new G711UCodec();
|
||||||
public static final int CHANNEL_CONFIGURATION_IN = AudioFormat.CHANNEL_IN_MONO;
|
public static final int CHANNEL_CONFIGURATION_IN = AudioFormat.CHANNEL_IN_MONO;
|
||||||
public static final int CHANNEL_CONFIGURATION_OUT = AudioFormat.CHANNEL_OUT_MONO;
|
public static final int CHANNEL_CONFIGURATION_OUT = AudioFormat.CHANNEL_OUT_MONO;
|
||||||
|
|
||||||
|
|
||||||
private AudioCodecDefines() {
|
private AudioCodecDefines() {
|
||||||
throw new IllegalStateException("Do not instantiate!");
|
throw new IllegalStateException("Do not instantiate!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,5 @@
|
|||||||
package de.rochefort.childmonitor;
|
package de.rochefort.childmonitor;
|
||||||
|
|
||||||
public interface AudioListener {
|
public interface AudioListener {
|
||||||
void onAudio(byte[] audioBytes);
|
void onAudio(short[] audioBytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package de.rochefort.childmonitor;
|
package de.rochefort.childmonitor;
|
||||||
|
|
||||||
|
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@@ -31,6 +33,8 @@ import android.widget.TextView;
|
|||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.NotificationManagerCompat;
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
|
|
||||||
|
import de.rochefort.childmonitor.audio.G711UCodec;
|
||||||
|
|
||||||
public class ListenActivity extends Activity
|
public class ListenActivity extends Activity
|
||||||
{
|
{
|
||||||
final String TAG = "ChildMonitor";
|
final String TAG = "ChildMonitor";
|
||||||
@@ -69,21 +73,26 @@ public class ListenActivity extends Activity
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
final byte [] buffer = new byte[byteBufferSize];
|
final byte [] readBuffer = new byte[byteBufferSize];
|
||||||
|
final short [] decodedBuffer = new short[byteBufferSize*2];
|
||||||
|
|
||||||
while(socket.isConnected() && read != -1 && Thread.currentThread().isInterrupted() == false)
|
while(socket.isConnected() && read != -1 && Thread.currentThread().isInterrupted() == false)
|
||||||
{
|
{
|
||||||
read = is.read(buffer);
|
read = is.read(readBuffer);
|
||||||
|
int decoded = CODEC.decode(decodedBuffer, readBuffer, read, 0);
|
||||||
|
|
||||||
if(read > 0)
|
if(decoded > 0)
|
||||||
{
|
{
|
||||||
audioTrack.write(buffer, 0, read);
|
audioTrack.write(decodedBuffer, 0, decoded);
|
||||||
byte[] readBytes = new byte[read];
|
short[] decodedBytes = new short[decoded];
|
||||||
System.arraycopy(buffer, 0, readBytes, 0, read);
|
System.arraycopy(decodedBuffer, 0, decodedBytes, 0, decoded);
|
||||||
listener.onAudio(readBytes);
|
listener.onAudio(decodedBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Log.e(TAG, "Connection failed", e);
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
audioTrack.stop();
|
audioTrack.stop();
|
||||||
@@ -125,7 +134,7 @@ public class ListenActivity extends Activity
|
|||||||
|
|
||||||
final AudioListener listener = new AudioListener() {
|
final AudioListener listener = new AudioListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAudio(final byte[] audioData) {
|
public void onAudio(final short[] audioData) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package de.rochefort.childmonitor;
|
package de.rochefort.childmonitor;
|
||||||
|
|
||||||
|
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -53,13 +55,9 @@ public class MonitorActivity extends Activity {
|
|||||||
private int currentPort;
|
private int currentPort;
|
||||||
|
|
||||||
private void serviceConnection(Socket socket) {
|
private void serviceConnection(Socket socket) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(() -> {
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
final TextView statusText = (TextView) findViewById(R.id.textStatus);
|
final TextView statusText = (TextView) findViewById(R.id.textStatus);
|
||||||
statusText.setText(R.string.streaming);
|
statusText.setText(R.string.streaming);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final int frequency = AudioCodecDefines.FREQUENCY;
|
final int frequency = AudioCodecDefines.FREQUENCY;
|
||||||
@@ -75,21 +73,24 @@ public class MonitorActivity extends Activity {
|
|||||||
bufferSize
|
bufferSize
|
||||||
);
|
);
|
||||||
|
|
||||||
final int byteBufferSize = bufferSize*2;
|
final int pcmBufferSize = bufferSize*2;
|
||||||
final byte[] buffer = new byte[byteBufferSize];
|
final short[] pcmBuffer = new short[pcmBufferSize];
|
||||||
|
final byte[] ulawBuffer = new byte[pcmBufferSize];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
audioRecord.startRecording();
|
audioRecord.startRecording();
|
||||||
final OutputStream out = socket.getOutputStream();
|
final OutputStream out = socket.getOutputStream();
|
||||||
|
|
||||||
socket.setSendBufferSize(byteBufferSize);
|
socket.setSendBufferSize(pcmBufferSize);
|
||||||
Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize());
|
Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize());
|
||||||
|
|
||||||
while (socket.isConnected() && !Thread.currentThread().isInterrupted()) {
|
while (socket.isConnected() && !Thread.currentThread().isInterrupted()) {
|
||||||
final int read = audioRecord.read(buffer, 0, bufferSize);
|
final int read = audioRecord.read(pcmBuffer, 0, bufferSize);
|
||||||
out.write(buffer, 0, read);
|
int encoded = CODEC.encode(pcmBuffer, read, ulawBuffer, 0);
|
||||||
|
out.write(ulawBuffer, 0, encoded);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Connection failed", e);
|
Log.e(TAG, "Connection failed", e);
|
||||||
} finally {
|
} finally {
|
||||||
audioRecord.stop();
|
audioRecord.stop();
|
||||||
@@ -109,10 +110,7 @@ public class MonitorActivity extends Activity {
|
|||||||
final Object currentToken = new Object();
|
final Object currentToken = new Object();
|
||||||
connectionToken = currentToken;
|
connectionToken = currentToken;
|
||||||
|
|
||||||
new Thread(new Runnable() {
|
new Thread(() -> {
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
while(Objects.equals(connectionToken, currentToken)) {
|
while(Objects.equals(connectionToken, currentToken)) {
|
||||||
try (ServerSocket serverSocket = new ServerSocket(currentPort)) {
|
try (ServerSocket serverSocket = new ServerSocket(currentPort)) {
|
||||||
currentSocket = serverSocket;
|
currentSocket = serverSocket;
|
||||||
@@ -133,13 +131,12 @@ public class MonitorActivity extends Activity {
|
|||||||
unregisterService();
|
unregisterService();
|
||||||
serviceConnection(socket);
|
serviceConnection(socket);
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(Exception e) {
|
||||||
// Just in case
|
// Just in case
|
||||||
currentPort++;
|
currentPort++;
|
||||||
Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
|
Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
final TextView addressText = findViewById(R.id.address);
|
final TextView addressText = findViewById(R.id.address);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class VolumeView extends View {
|
|||||||
paint.setColor(Color.rgb(255, 127, 0));
|
paint.setColor(Color.rgb(255, 127, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAudioData(byte[] data) {
|
public void onAudioData(short[] data) {
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
double rel = data[i] / ((double)128);
|
double rel = data[i] / ((double)128);
|
||||||
@@ -94,7 +94,7 @@ public class VolumeView extends View {
|
|||||||
double margins = height * 0.1;
|
double margins = height * 0.1;
|
||||||
double graphHeight = height - 2*margins;
|
double graphHeight = height - 2*margins;
|
||||||
int leftMost = Math.max(0, volumeHistory.size() - width);
|
int leftMost = Math.max(0, volumeHistory.size() - width);
|
||||||
int yPrev = (int) (graphHeight - margins);
|
int yPrev = (int) (height - margins);
|
||||||
for (int i = leftMost; i < volumeHistory.size() && i - leftMost < width; i++) {
|
for (int i = leftMost; i < volumeHistory.size() && i - leftMost < width; i++) {
|
||||||
int xNext = i - leftMost;
|
int xNext = i - leftMost;
|
||||||
int yNext = (int) (margins + graphHeight - volumeHistory.get(i) / maxVolume * (graphHeight));
|
int yNext = (int) (margins + graphHeight - volumeHistory.get(i) / maxVolume * (graphHeight));
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* Taken from https://android.googlesource.com/platform/external/nist-sip/+/6f95fdeab4481188b6260041b41d1db12b101266/src/com/android/sip/media/G711UCodec.java
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.rochefort.childmonitor.audio;
|
||||||
|
/**
|
||||||
|
* G.711 codec. This class provides u-law conversion.
|
||||||
|
*/
|
||||||
|
public class G711UCodec {
|
||||||
|
// s00000001wxyz...s000wxyz
|
||||||
|
// s0000001wxyza...s001wxyz
|
||||||
|
// s000001wxyzab...s010wxyz
|
||||||
|
// s00001wxyzabc...s011wxyz
|
||||||
|
// s0001wxyzabcd...s100wxyz
|
||||||
|
// s001wxyzabcde...s101wxyz
|
||||||
|
// s01wxyzabcdef...s110wxyz
|
||||||
|
// s1wxyzabcdefg...s111wxyz
|
||||||
|
private static byte[] table13to8 = new byte[8192];
|
||||||
|
private static short[] table8to16 = new short[256];
|
||||||
|
static {
|
||||||
|
// b13 --> b8
|
||||||
|
for (int p = 1, q = 0; p <= 0x80; p <<= 1, q+=0x10) {
|
||||||
|
for (int i = 0, j = (p << 4) - 0x10; i < 16; i++, j += p) {
|
||||||
|
int v = (i + q) ^ 0x7F;
|
||||||
|
byte value1 = (byte) v;
|
||||||
|
byte value2 = (byte) (v + 128);
|
||||||
|
for (int m = j, e = j + p; m < e; m++) {
|
||||||
|
table13to8[m] = value1;
|
||||||
|
table13to8[8191 - m] = value2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// b8 --> b16
|
||||||
|
for (int q = 0; q <= 7; q++) {
|
||||||
|
for (int i = 0, m = (q << 4); i < 16; i++, m++) {
|
||||||
|
int v = (((i + 0x10) << q) - 0x10) << 3;
|
||||||
|
table8to16[m ^ 0x7F] = (short) v;
|
||||||
|
table8to16[(m ^ 0x7F) + 128] = (short) (65536 - v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int decode(short[] b16, byte[] ulaw, int count, int offset) {
|
||||||
|
for (int i = 0, j = offset; i < count; i++, j++) {
|
||||||
|
b16[i] = table8to16[ulaw[j] & 0xFF];
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
public int encode(short[] b16, int count, byte[] b8, int offset) {
|
||||||
|
for (int i = 0, j = offset; i < count; i++, j++) {
|
||||||
|
b8[j] = table13to8[(b16[i] >> 4) & 0x1FFF];
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
public int getSampleCount(int frameSize) {
|
||||||
|
return frameSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user