Issue encoding and decoding an audio recording to G711 ( PCMU - uLaw) format
android, audiorecord, audiotrack, codec, pcm
Solution
Ok guys, finally I resolved for myself the problem encoding/decoding audio. It's been a annoying task during the last week. The main problem of my code was the encoding was well done but the decoding wasn't so I was working around it and modify these class with the help of other resources and I've created my own encoding/decoding methods (and these are working like a charm!!!).
Other important decision was to change the encoding format. Now I am using alaw, and not anymore ulaw. The only reason why I did this change is because programmatically is easier to implement alaw than ulaw.
Also I had to play a lot with the parameters as a buffers sizes, etc etc.
I will submit my code and I hope that someone of you could save so much time using my references.
private int port=50005;
private int sampleRate = 8000; //44100;
private int channelConfig = AudioFormat.CHANNEL_IN_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
public void startStreamingEncoding() {
Thread streamThread = new Thread(new Runnable() {
@Override
public void run() {
try {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
DatagramSocket socket = new DatagramSocket();
byte[] buffer = new byte[4096];
DatagramPacket packet;
final InetAddress destination = InetAddress.getByName(ip_receiver);
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,audioFormat, minBufSize * 10);
recorder.startRecording();
/////Encoding:
CMG711 encoder = new CMG711();
byte[] outBuffer = new byte[4096];
int read, encoded;
File sdCard = Environment.getExternalStorageDirectory();
FileOutputStream out = new FileOutputStream( new File( sdCard ,"audio-bernard.raw" ));
while(status == true) {
//reading data from MIC into buffer
read = recorder.read(buffer, 0, buffer.length);
Log.d(getTag(), "read: "+read );
//Encoding:
encoded = encoder.encode(buffer,0, read, outBuffer);
//putting buffer in the packet
packet = new DatagramPacket (outBuffer, encoded, destination,port);
out.write( outBuffer, 0, encoded );
socket.send(packet);
}
} catch(UnknownHostException e) {
Log.e("VS", "UnknownHostException");
} catch (IOException e) {
e.printStackTrace();
Log.e("VS", "IOException");
}
}
});
streamThread.start();
}
And for the receiver and player class or method:
private int port=50005;
private int sampleRate = 8000 ;//44100;
private int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int minBufSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
public void playerAudioDecodingBernard()
{
Thread thrd = new Thread(new Runnable() {
@Override
public void run()
{
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufSize * 10,
AudioTrack.MODE_STREAM);
CMG711 decoder = new CMG711();
try
{
DatagramSocket sock = new DatagramSocket(port);
byte[] buf = new byte[4096];
int frame = 0;
while(true)
{
DatagramPacket pack = new DatagramPacket(buf, 4096);
sock.receive(pack);
//Decoding:
int size = pack.getLength();
//Log.d( "Player", "Player: "+ size +", "+pack.getLength() + ", "+pack.getOffset() );
byte[] byteArray = new byte[size*2];
decoder.decode(pack.getData(), 0, size, byteArray);
track.write(byteArray, 0, byteArray.length);
if( frame++ > 3 )
track.play();
}
}
catch (SocketException se)
{
Log.e("Error", "SocketException: " + se.toString());
}
catch (IOException ie)
{
Log.e("Error", "IOException" + ie.toString());
}
} // end run
});
thrd.start();
}
And this one is the class o encode/decoding in alaw format:
public class CMG711
{
/** decompress table constants */
private static short aLawDecompressTable[] = new short[]
{ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344, -328, -376,
-360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624,
3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120,
1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 };
private final static int cClip = 32635;
private static byte aLawCompressTable[] = new byte[]
{ 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 };
public int encode( byte[] src, int offset, int len, byte[] res )
{
int j = offset;
int count = len / 2;
short sample = 0;
for ( int i = 0; i < count; i++ )
{
sample = (short) ( ( ( src[j++] & 0xff ) | ( src[j++] ) << 8 ) );
res[i] = linearToALawSample( sample );
}
return count;
}
private byte linearToALawSample( short sample )
{
int sign;
int exponent;
int mantissa;
int s;
sign = ( ( ~sample ) >> 8 ) & 0x80;
if ( !( sign == 0x80 ) )
{
sample = (short) -sample;
}
if ( sample > cClip )
{
sample = cClip;
}
if ( sample >= 256 )
{
exponent = (int) aLawCompressTable[( sample >> 8 ) & 0x7F];
mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F;
s = ( exponent << 4 ) | mantissa;
}
else
{
s = sample >> 4;
}
s ^= ( sign ^ 0x55 );
return (byte) s;
}
public void decode( byte[] src, int offset, int len, byte[] res )
{
int j = 0;
for ( int i = 0; i < len; i++ )
{
short s = aLawDecompressTable[src[i + offset] & 0xff];
res[j++] = (byte) s;
res[j++] = (byte) ( s >> 8 );
}
}
}
Hope to be useful for someone of you! Thanks anyway for the help received, specially to bonnyz.
Problem
There isn't too much info about apply this codec when we need to streaming audio. Without apply the codec, my code work like a charm establishing a communication between 2 devices but I need encode/decode in that format because I will need streaming with the server and not between two devices (I am testing this code using 2 devices). I am looking for the chance if anyone of your could see where is the key of my problem. I've tried different configurations of the input parameters. Maybe, the codecs that I am using are wrong (I took them from one project with Apache license.) This values are set in the recorder-sender as in the player-receiver device: ``` private int port=50005; private int sampleRate = 8000 ;//44100; private int channelConfig = AudioFormat.CHANNEL_OUT_MONO; private int audioFormat = AudioFormat.ENCODING_PCM_16BIT; int minBufSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); ``` Note: CHANNEL_OUT_MONO in the player and CHANNEL_IN_MONO in the recorder item. And these are my methods: ``` public void startStreamingEncoding() { Thread streamThread = new Thread(new Runnable() { @Override public void run() { try { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); DatagramSocket socket = new DatagramSocket(); short[] buffer = new short[minBufSize]; DatagramPacket packet; final InetAddress destination = InetAddress.getByName(ip_receiver); recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,audioFormat,minBufSize*10); recorder.startRecording(); /////Encoding: Encoder encoder = new G711UCodec(); byte[] outBuffer = new byte[minBufSize]; while(status == true) { //reading data from MIC into buffer minBufSize = recorder.read(buffer, 0, buffer.length); //Encoding: encoder.encode(buffer, minBufSize, outBuffer, 0); //putting buffer in the packet packet = new DatagramPacket (outBuffer, outBuffer.length, destination,port); socket.send(packet); } } catch(UnknownHostException e) { Log.e("VS", "UnknownHostException"); } catch (IOException e) { e.printStackTrace(); Log.e("VS", "IOException"); } } }); streamThread.start(); } ``` And the method to play and decoding the stream: ``` public void playerAudioDecoding() { Thread thrd = new Thread(new Runnable() { @Override public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufSize, AudioTrack.MODE_STREAM); track.play(); Decoder decoder = new G711UCodec(); try { DatagramSocket sock = new DatagramSocket(port); byte[] buf = new byte[minBufSize]; while(true) { DatagramPacket pack = new DatagramPacket(buf, minBufSize); sock.receive(pack); //Decoding: int size = pack.getData().length; short[] shortArray = new short[size]; decoder.decode(shortArray, pack.getData(), minBufSize, 0); byte[] array = MyShortToByte(shortArray); track.write(array, 0, array.length); } } catch (SocketException se) { Log.e("Error", "SocketException: " + se.toString()); } catch (IOException ie) { Log.e("Error", "IOException" + ie.toString()); } } // end run }); thrd.start(); } ``` And it is the codec class that I am using with Apache license: ``` public class G711UCodec implements Encoder, Decoder { // 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[] b8, int count, int offset) { for (int i = 0, j = offset; i < count; i++, j++) { b16[i] = table8to16[b8[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; } ``` } Really, I don't know what it happen; If I change the sampleRate to 4000 I can recognice my voice and some words but there is a lot echo. And i repeat, if disable the encoding/decoding process and make the streaming in PCM, the quality is fantastic. Let see if anybody could help me and thanks in advance.