5

I googled this issue for last 2 weeks and wasn't able to find an algorithm or solution. I have some short .wav file but it has MULAW compression and python doesn't seem to have function inside wave.py that can successfully decompresses it. So I've taken upon myself to build a decoder in python.

I've found some info about MULAW in basic elements:

  1. Wikipedia
  2. A-law u-Law comparison
  3. Some c-esc codec library

So I need some guidance, since I don't know how to approach getting from signed short integer to a full wave signal. This is my initial thought from what I've gathered so far:


So from wiki I've got a equation for u-law compression and decompression :

compression : compression

decompression : enter image description here

So judging by compression equation, it looks like the output is limited to a float range of -1 to +1 , and with signed short integer from –32,768 to 32,767 so it looks like I would need to convert it from short int to float in specific range.

Now, to be honest, I've heard of quantisation before, but I am not sure if I should first try and dequantize and then decompress or in the other way, or even if in this case it is the same thing... the tutorials/documentation can be a bit of tricky with terminology.

The wave file I am working with is supposed to contain 'A' sound like for speech synthesis, I could probably verify success by comparing 2 waveforms in some audio software and custom wave analyzer but I would really like to diminish trial and error section of this process.

So what I've had in mind:

u = 0xff
data_chunk = b'\xe7\xe7' # -6169
data_to_r1 = unpack('h',data_chunk)[0]/0xffff # I suspect this is wrong,
#                                             # but I don't know what else

u_law = ( -1 if data_chunk<0 else 1 )*( pow( 1+u, abs(data_to_r1)) -1 )/u   

So is there some sort of algorithm or crucial steps I would need to take in form of first: decompression, second: quantisation : third ?
Since everything I find on google is how to read a .wav PCM-modulated file type, not how to manage it if wild compression arises.

user3666197
  • 1
  • 6
  • 50
  • 92
Danilo
  • 1,017
  • 13
  • 32

3 Answers3

3

So, after scouring the google the solution was found in github ( go figure ). I've searched for many many algorithms and found 1 that is within bounds of error for lossy compression. Which is for u law for positive values from 30 -> 1 and for negative values from -32 -> -1

To be honest i think this solution is adequate but not quite per equation per say, but it is best solution for now. This code is transcribed to python directly from gcc9108 audio codec

def uLaw_d(i8bit):
    bias = 33
    sign = pos = 0
    decoded = 0

    i8bit = ~i8bit
    if i8bit&0x80:
        i8bit &= ~(1<<7)
        sign = -1

    pos = ( (i8bit&0xf0) >> 4 ) + 5
    decoded = ((1 << pos) | ((i8bit & 0x0F) << (pos - 4)) | (1 << (pos - 5))) - bias
    return decoded if sign else ~decoded

def uLaw_e(i16bit):
    MAX = 0x1fff
    BIAS = 33
    mask = 0x1000
    sign = lsb = 0
    pos = 12 

    if i16bit < 0:
        i16bit = -i16bit
        sign = 0x80

    i16bit += BIAS

    if ( i16bit>MAX ): i16bit = MAX 

    for x in reversed(range(pos)):
        if i16bit&mask != mask and pos>=5:
            pos = x
            break

    lsb = ( i16bit>>(pos-4) )&0xf
    return ( ~( sign | ( pos<<4 ) | lsb ) )

With test:

print( 'normal :\t{0}\t|\t{0:2X}\t:\t{0:016b}'.format(0xff) )
print( 'encoded:\t{0}\t|\t{0:2X}\t:\t{0:016b}'.format(uLaw_e(0xff)) )
print( 'decoded:\t{0}\t|\t{0:2X}\t:\t{0:016b}'.format(uLaw_d(uLaw_e(0xff))) )

and output:

normal :    255     |   FF  :   0000000011111111
encoded:    -179    |   -B3 :   -000000010110011
decoded:    263     |   107 :   0000000100000111

And as you can see 263-255 = 8 which is within bounds. When i tried to implement seeemmmm method described in G.711 ,that kind user Oliver Charlesworth suggested that i look in to , the decoded value for maximum in data was -8036 which is close to the maximum of uLaw spec, but i couldn't reverse engineer decoding function to get binary equivalent of function from wikipedia.

Lastly, i must say that i am currently disappointed that python library doesn't support all kind of compression algorithms since it is not just a tool that people use, it is also a resource python consumers learn from since most of data for further dive into code isn't readily available or understandable.


EDIT

After decoding the data and writing wav file via wave.py i've successfully succeeded to write a new raw linear PCM file. This works... even though i was sceptical at first.


EDIT 2: ::> you can find real solution oncompressions.py


Danilo
  • 1,017
  • 13
  • 32
  • hey, I want to decode a ulaw byte stream into normal wave file, but not sure how to use your solution. I have a buffer of ulaw encoded bytes. Any help would be really appriciated. – Umesh Chaudhary Apr 19 '23 at 19:51
  • @UmeshChaudhary at [link](https://github.com/coderByNeed/BOB404/blob/master/audio/compressions.py) you have full file with implementation. But in short, you would read each byte in byte stream according to file structure. If for example **ulaw** is compressed from 2 byte integers, then each pair is equal to 1 floating number value that represents amplitude. You can consider `map` functionality in python and just pass in the byte stream (with minor adjustments to byte size of amplitude value). Result should be buffer of floating number types. I hope this helps. – Danilo Apr 20 '23 at 17:58
3

I find this helpful for converting to/from ulaw with numpy arrays.

import audioop

def numpy_audioop_helper(x, xdtype, func, width, ydtype):
    '''helper function for using audioop buffer conversion in numpy'''
    xi = np.asanyarray(x).astype(xdtype)
    if np.any(x != xi):
        xinfo = np.iinfo(xdtype)
        raise ValueError("input must be %s [%d..%d]" % (xdtype, xinfo.min, xinfo.max))
    y = np.frombuffer(func(xi.tobytes(), width), dtype=ydtype)
    return y.reshape(xi.shape)

def audioop_ulaw_compress(x):
    return numpy_audioop_helper(x, np.int16, audioop.lin2ulaw, 2, np.uint8)

def audioop_ulaw_expand(x):
    return numpy_audioop_helper(x, np.uint8, audioop.ulaw2lin, 2, np.int16)
Greg Allen
  • 575
  • 5
  • 14
  • to be honest. Never used numpy or pycharm or anything that is extending python. The reason why i needed to do this isn't the conversion or data handling. It is mp3/wav compression ( among other ) flag recognition. Python has some bugs with limited flags, if the file is written with generic, standard and wide used flags python has no issue loading it, but if the file is written with other it raises an error...so you would need to read binary structure and decompress ulaw by yourself then. – Danilo Jul 10 '19 at 08:42
2

Python actually supports decoding u-Law out of the box:

audioop.ulaw2lin(fragment, width)

Convert sound fragments in u-LAW encoding to linearly encoded sound fragments. u-LAW encoding always uses 8 bits samples, so width refers only to the sample width of the output fragment here.

https://docs.python.org/3/library/audioop.html#audioop.ulaw2lin

  • it does but only if file you would like to read has a simple compression flag. I got mp3 file with compression flag for mulaw, but python wouldn't recognize it. So it doesn't work on everything. – Danilo Jun 28 '19 at 10:57