Android Question aLaw/uLaw PCM - compression, decompression - Code Available to all.

Jmu5667

Well-Known Member
Licensed User
Longtime User
Hello All

I have big ask her(if it works I will pay).

We have a new PTT app developed B4A (client) B4J (server). It works really well. We were looking at bandwidth considerations and would like to compress the PCM data before sending it.

I came across this http://thorntonzone.com/manuals/Compression/Fax, IBM MMR/MMSC/mmsc/uk/co/mmscomputing/sound/index.html (Items 1 & 3)

In you have the skills and are interested, please reply. I would prefer a code conversion, either java we can use in with our B4A, or indeed pure B4A, as I would intend to make it freely available to everyone, even at my own expense. I know there are some audio guys on the forum, so @stevel05 @tigrot have a look.

thanks for reading

John.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
Upvote 0

Jmu5667

Well-Known Member
Licensed User
Longtime User
It looks like both Android and Java support both a/law and u/law compression for audio streams. If you have written both ends yourself, it should be fairly straight forward to implement.

Android:
https://developer.android.com/reference/android/net/rtp/AudioCodec
https://developer.android.com/reference/android/net/rtp/AudioStream

Java:
https://docs.oracle.com/javase/8/docs/api/javax/sound/sampled/AudioFormat.Encoding.html


Hi steve

I am using the Audio Streamer from B4A audio lib 1.63, so I get the audio buffered data, can I compress that and the send it ?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
How are you sending the data?
 
Upvote 0

Jmu5667

Well-Known Member
Licensed User
Longtime User
How are you sending the data?

Yep

B4X:
Sub AudioStream_RecordBuffer (Data() As Byte)
  
   If sendingAudio Then
       Try
           Dim p As UDPPacket
          
           If Main.APPSET.callvolumes.data_micboostlevel > 0 Then
               Dim volAdjust As Float
               ' // we need to create the float val for the vol adjust
               volAdjust = (Main.APPSET.callvolumes.data_micboostlevel/10) + 1
               ' // adjust the volume
               Dim jo As JavaObject
               jo.InitializeContext
               p.Initialize(jo.RunMethod("adjustVolume", Array As Object(Data,volAdjust)),Main.APPSET.currentChannel.host,Main.APPSET.currentChannel.udp_speaker)
           Else
               p.Initialize(Data,Main.APPSET.currentChannel.host,Main.APPSET.currentChannel.udp_speaker)
           End If
           ' // send the audio to the PTT server
           udpSckTalk.Send(p)
       Catch
           mod_functions.writelog("svc_data(), AudioStream_RecordBuffer, error " & LastException.Message)
           StopSendingAudio
       End Try
   End If
  
End Sub



#If Java

public byte[] adjustVolume(byte[] audioSamples, float volume)       
   {
        byte[] array = new byte[audioSamples.length];
        for (int i = 0; i < array.length; i+=2)
           {
               // convert byte pair to int
               short buf1 = audioSamples[i+1];
               short buf2 = audioSamples[i];

               buf1 = (short) ((buf1 & 0xff) << 8);
               buf2 = (short) (buf2 & 0xff);

               short res= (short) (buf1 | buf2);
               res = (short) (res * volume);
              
               // convert back
               array[i] = (byte) res;
               array[i+1] = (byte) (res >> 8);
           }
        return array;
   }

#End If
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
If you are not using the audio streaming available in Android, it may be easier to zip it to a file and send that. Otherwise you will be relying on two third party libraries (if they are available).
 
Upvote 0

Jmu5667

Well-Known Member
Licensed User
Longtime User
If you are not using the audio streaming available in Android, it may be easier to zip it to a file and send that. Otherwise you will be relying on two third party libraries (if they are available).

Ok, thanks steve, I thought they may have been a java routine to call to compress the buffer, then send it, and when the client receives the buffer, decompress it, then play it.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Ok, thanks steve, I thought they may have been a java routine to call to compress the buffer, then send it, and when the client receives the buffer, decompress it, then play it.
If you look at this post (https://stackoverflow.com/a/23603738), the class CMG711 encodes/decodes aLaw. You could either duplicate the code in B4A/B4J or create a wrapper for it with inline Java.
 
Upvote 0

Jmu5667

Well-Known Member
Licensed User
Longtime User
Ok, as promised. Credits to @OliverA

aLaw Class

B4X:
'Based on source found at https://stackoverflow.com/a/23603738, by Juan Pedro Martinez
'
Sub Class_Globals
   'decompress table constants
   Private aLawDecompressTable() As Short = Array As 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 cClip As Int = 32635
   Private aLawCompressTable() As Byte = Array As 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 )
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize

End Sub

Public Sub Encode(audio() As Byte) As Byte()
   Dim length As Int = audio.Length
   If length = 0 Then Return Array As Byte()
   Dim encoded(length/2) As Byte
       
   Dim offset As Int = 0
   Dim count As Int = length / 2
   Dim sample As Short = 0
   Dim byte1 As Short
   Dim byte2 As Short

   Dim i As Int
   For i = 0 To count - 1
       byte1 = Bit.And(audio(offset), 0xff)
       offset = offset + 1
       byte2 = Bit.ShiftLeft(audio(offset), 8)
       offset = offset + 1
       sample = Bit.Or( byte1, byte2 )
       encoded(i) = linearToALawSample( sample )
   Next
   Return encoded
End Sub

Private Sub linearToALawSample(sample As Short ) As Byte
   Dim sign As Int
   Dim exponent As Int
   Dim mantissa As Int
   Dim s As Int

   sign = Bit.And(Bit.ShiftRight(Bit.Not(sample) , 8), 0x80)
   If (sign <> 0x80)  Then sample = -sample
   If ( sample > cClip ) Then sample = cClip
   If ( sample >= 256 ) Then
       exponent = aLawCompressTable(Bit.And( Bit.ShiftRight(sample , 8 ) , 0x7F))
       mantissa = Bit.And( Bit.ShiftRight(sample , ( exponent + 3 ) ) , 0x0F)
       s = Bit.Or(Bit.ShiftLeft(exponent,4), mantissa)
   Else
       s = Bit.ShiftRight(sample,4)
   End If
   s = Bit.XOR(s , Bit.XOR( sign , 0x55 ))
   Return s
End Sub

Public Sub Decode(audio() As Byte) As Byte()
   Dim length As Int = audio.Length
   If length = 0 Then Return Array As Byte()
   Dim decoded(length*2) As Byte
   Dim offset As Int = 0
   Dim i As Int
   Dim s As Short
   For i = 0 To length -1
       s = aLawDecompressTable(Bit.And(audio(i), 0xff))
       decoded(offset) = s
       offset = offset + 1
       decoded(offset) = Bit.ShiftRight(s, 8)
       offset = offset + 1
   Next
   
   Return decoded
End Sub

Usage

B4X:
' // process globals
Public aLaw                       As aLawCodec
Public audioStream                As AudioStreamer

' // encoding
Sub AudioStream_RecordBuffer (Data() As Byte)

          Dim p As UDPPacket         
          p.Initialize(aLaw.Encode(Data),Main.APPSET.currentChannel.host,Main.APPSET.currentChannel.udp_speaker)
          ' // send the audio to the PTT server
          udpSckTalk.Send(p)
   
End Sub

' // decoding

Sub udpSocket_PacketArrived (Packet As UDPPacket)

       Dim data(Packet.Length) As Byte
       bc.ArrayCopy(Packet.Data,Packet.Offset,data,0,Packet.Length)
       data = aLaw.Decode(data)                  
        ' // play the received audio data
       audioStream.Write(data)
       
   End If
       
End Sub

Hope this is of value to others. And again thanks to @OliverA for the B4X conversion.

Regards

John.
 
Upvote 0

Addo

Well-Known Member
Licensed User
i have tried this i got into a problem

at this line

B4X:
Sub UDPAUD_PacketArrived (Packet As UDPPacket)


Dim data(Packet.Length) As Byte
bcaudio.ArrayCopy(Packet.Data,Packet.Offset,data,0,Packet.Length)
data = myConverterB4X.Decode(data)
audioStream.Write(data)


alawb4x_vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv0 (java line: 80)
java.lang.NullPointerException: Attempt to read from null array
at com.app.voip.alawb4x._vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv0(alawb4x.java:80)
at com.app.voip.emod._udpaud_packetarrived(emod.java:2105)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
at anywheresoftware.b4a.BA$2.run(BA.java:370)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:7007)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
java.lang.RuntimeException: java.lang.NullPointerException: Attempt to read from null array
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:233)
at anywheresoftware.b4a.BA$2.run(BA.java:370)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:7007)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
Caused by: java.lang.NullPointerException: Attempt to read from null array
at com.app.voip.alawb4x._vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv0(alawb4x.java:80)
at com.app.voip.emod._udpaud_packetarrived(emod.java:2105)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:196)
... 9 more
 
Upvote 0
Top