go-audio / wav

Battle tested Wav decoder/encoder
Apache License 2.0
309 stars 46 forks source link

How to write G.711u (ulaw / mulaw) to wav? #29

Closed grokify closed 1 year ago

grokify commented 3 years ago

Issue

I have a 8Khz 8-bit PCM ulaw byte slice that I would like to convert to a WAV file. When I try to convert it using this library, I get audio that is loud and choppy, sounding a bit like the following issue.

https://github.com/go-audio/audio/issues/16

I'm using an Encoder as follows:

wav := NewEncoder(f, 8000, 8, 1, 1)

I'm not quite sure how to convert the raw byte slice into an audio.IntBuffer so I'm doing the following, which results in the poor quality audio.

func BytesToInts(bytes []byte) []int {
    ints := []int{}
    for _, b := range bytes {
        ints = append(ints, int(b))
    }
    return ints
}

func UlawByteSliceToIntBuffer(bytes []byte) *audio.IntBuffer {
    return &audio.IntBuffer{
        Format: &audio.Format{
            NumChannels: 1,
            SampleRate:  8000},
        SourceBitDepth: 8,
        Data:           BytesToInts(bytes)}
}

Any tips on how to get this library to work for this?

Alternative 1 - Works

As an alternative, the following raw approach results in good sounding audio where I can write out the bytes without transformation.

var wavHeaderThin = []byte{0x52, 0x49, 0x46, 0x46, 0x62, 0xb8, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20,
    0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x80, 0x3e, 0x00, 0x00,
    0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x61, 0x63, 0x74, 0x04, 0x00, 0x00, 0x00, 0xc5, 0x5b,
    0x00, 0x00, 0x64, 0x61, 0x74, 0x61}

func WriteFileWavFromUlaw(filename string, ulawBytes []byte) error {
    f, err := os.Create(filename)
    if err != nil {
        return err
    }
        defer f.Close()
    _, err = WriteWavFromUlaw(f, ulawBytes)
    return err
}

func WriteWavFromUlaw(w io.Writer, ulawBytes []byte) (n int, err error) {
    n1, err := w.Write(wavHeaderThin)
    if err != nil {
        return n, err
    }
    n += n1
    count := len(ulawBytes)
    n2, err := w.Write([]byte{
        byte(count % 256),
        byte((count >> 8) % 256),
        byte((count >> 16) % 256),
        byte((count >> 24) % 256)})
    if err != nil {
        return n, err
    }
    n += n2
    n3, err := w.Write(ulawBytes)
    if err != nil {
        return n, err
    }
    return n + n3, nil
}

I've added this here for easy reuse:

https://github.com/grokify/simplego/blob/master/audio/ulaw/ulaw.go

Alternative 2 - Works

$ ffmpeg -f mulaw -ar 8000 -i input.ulaw output.wav
vlad-tokarev commented 1 year ago

wav := NewEncoder(f, 8000, 8, 1, 1)

The problem lies with the last argument.

TL;DR: Change it to: wav := NewEncoder(f, 8000, 8, 1, 7)

Details:

WAV format allows storage of audio data in various encodings, such as raw data, compressed formats, etc. Therefore, you should explicitly set the µ-law compression format in the header. This ensures that media players recognize and play back the audio correctly.

In your example, passing "1" means "uncompressed pulse code modulated samples." This is incorrect since your data is actually encoded in a different, compressed format.

Although the original author of the issue may no longer need assistance due to the passage of time, I thought it would be helpful to present the solution for future readers.

I also suggest closing this issue. @mattetti

petercsiba commented 1 year ago

FWIW for my use-case encoding the Twilio Stream websocket audio/x-mulaw on my local M2 Mac, I had to have different encoder params, than the input ones (otherwise ended up 2x slower)

    inputBuffer := &audio.IntBuffer{
        Data: intData,
        Format: &audio.Format{
            SampleRate:  8000,
            NumChannels: 1,
        },
        SourceBitDepth: 8,
    }

       // When using (f, 8000, 8, 1, 7), it ended up 2x slower idk why
        wav := NewEncoder(f, 16000, 16, 1, 7)
linnv commented 11 months ago

wav := NewEncoder(f, 8000, 8, 1, 1)

The problem lies with the last argument.

TL;DR: Change it to: wav := NewEncoder(f, 8000, 8, 1, 7)

Details:

WAV format allows storage of audio data in various encodings, such as raw data, compressed formats, etc. Therefore, you should explicitly set the µ-law compression format in the header. This ensures that media players recognize and play back the audio correctly.

In your example, passing "1" means "uncompressed pulse code modulated samples." This is incorrect since your data is actually encoded in a different, compressed format.

Although the original author of the issue may no longer need assistance due to the passage of time, I thought it would be helpful to present the solution for future readers.

I also suggest closing this issue. @mattetti

it works a WavAudioFormat value of 6 or 7 would indicate that the audio data is stored in the A-law or μ-law format, which are forms of compressed PCM commonly used in telephony.