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
|
||||
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
|
||||
Audio file originals from [freesound](https://freesound.org).
|
||||
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 de.rochefort.childmonitor.audio.G711UCodec;
|
||||
|
||||
public class AudioCodecDefines {
|
||||
public static final int FREQUENCY = 11025;
|
||||
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_OUT = AudioFormat.CHANNEL_OUT_MONO;
|
||||
|
||||
|
||||
private AudioCodecDefines() {
|
||||
throw new IllegalStateException("Do not instantiate!");
|
||||
}
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
package de.rochefort.childmonitor;
|
||||
|
||||
public interface AudioListener {
|
||||
void onAudio(byte[] audioBytes);
|
||||
void onAudio(short[] audioBytes);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package de.rochefort.childmonitor;
|
||||
|
||||
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
@@ -31,6 +33,8 @@ import android.widget.TextView;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
|
||||
import de.rochefort.childmonitor.audio.G711UCodec;
|
||||
|
||||
public class ListenActivity extends Activity
|
||||
{
|
||||
final String TAG = "ChildMonitor";
|
||||
@@ -69,21 +73,26 @@ public class ListenActivity extends Activity
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
byte[] readBytes = new byte[read];
|
||||
System.arraycopy(buffer, 0, readBytes, 0, read);
|
||||
listener.onAudio(readBytes);
|
||||
audioTrack.write(decodedBuffer, 0, decoded);
|
||||
short[] decodedBytes = new short[decoded];
|
||||
System.arraycopy(decodedBuffer, 0, decodedBytes, 0, decoded);
|
||||
listener.onAudio(decodedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e(TAG, "Connection failed", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
audioTrack.stop();
|
||||
@@ -125,7 +134,7 @@ public class ListenActivity extends Activity
|
||||
|
||||
final AudioListener listener = new AudioListener() {
|
||||
@Override
|
||||
public void onAudio(final byte[] audioData) {
|
||||
public void onAudio(final short[] audioData) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package de.rochefort.childmonitor;
|
||||
|
||||
import static de.rochefort.childmonitor.AudioCodecDefines.CODEC;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
@@ -53,13 +55,9 @@ public class MonitorActivity extends Activity {
|
||||
private int currentPort;
|
||||
|
||||
private void serviceConnection(Socket socket) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
runOnUiThread(() -> {
|
||||
final TextView statusText = (TextView) findViewById(R.id.textStatus);
|
||||
statusText.setText(R.string.streaming);
|
||||
}
|
||||
});
|
||||
|
||||
final int frequency = AudioCodecDefines.FREQUENCY;
|
||||
@@ -75,21 +73,24 @@ public class MonitorActivity extends Activity {
|
||||
bufferSize
|
||||
);
|
||||
|
||||
final int byteBufferSize = bufferSize*2;
|
||||
final byte[] buffer = new byte[byteBufferSize];
|
||||
final int pcmBufferSize = bufferSize*2;
|
||||
final short[] pcmBuffer = new short[pcmBufferSize];
|
||||
final byte[] ulawBuffer = new byte[pcmBufferSize];
|
||||
|
||||
try {
|
||||
audioRecord.startRecording();
|
||||
final OutputStream out = socket.getOutputStream();
|
||||
|
||||
socket.setSendBufferSize(byteBufferSize);
|
||||
socket.setSendBufferSize(pcmBufferSize);
|
||||
Log.d(TAG, "Socket send buffer size: " + socket.getSendBufferSize());
|
||||
|
||||
while (socket.isConnected() && !Thread.currentThread().isInterrupted()) {
|
||||
final int read = audioRecord.read(buffer, 0, bufferSize);
|
||||
out.write(buffer, 0, read);
|
||||
final int read = audioRecord.read(pcmBuffer, 0, bufferSize);
|
||||
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);
|
||||
} finally {
|
||||
audioRecord.stop();
|
||||
@@ -109,10 +110,7 @@ public class MonitorActivity extends Activity {
|
||||
final Object currentToken = new Object();
|
||||
connectionToken = currentToken;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
new Thread(() -> {
|
||||
while(Objects.equals(connectionToken, currentToken)) {
|
||||
try (ServerSocket serverSocket = new ServerSocket(currentPort)) {
|
||||
currentSocket = serverSocket;
|
||||
@@ -133,13 +131,12 @@ public class MonitorActivity extends Activity {
|
||||
unregisterService();
|
||||
serviceConnection(socket);
|
||||
}
|
||||
} catch(IOException e) {
|
||||
} catch(Exception e) {
|
||||
// Just in case
|
||||
currentPort++;
|
||||
Log.e(TAG, "Failed to open server socket. Port increased to " + currentPort, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
final TextView addressText = findViewById(R.id.address);
|
||||
|
||||
@@ -56,7 +56,7 @@ public class VolumeView extends View {
|
||||
paint.setColor(Color.rgb(255, 127, 0));
|
||||
}
|
||||
|
||||
public void onAudioData(byte[] data) {
|
||||
public void onAudioData(short[] data) {
|
||||
double sum = 0;
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
double rel = data[i] / ((double)128);
|
||||
@@ -94,7 +94,7 @@ public class VolumeView extends View {
|
||||
double margins = height * 0.1;
|
||||
double graphHeight = height - 2*margins;
|
||||
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++) {
|
||||
int xNext = i - leftMost;
|
||||
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