ecobee / goalsa

Go bindings for audio capture and playback with ALSA and libasound
BSD 3-Clause "New" or "Revised" License
44 stars 14 forks source link

Underrun every second time calling PlaybackDevice.Write(buffer) #6

Open ratzupaltuff opened 3 weeks ago

ratzupaltuff commented 3 weeks ago

Hi, thanks for your great library! Unfortunately I am unable to play audio every second time I call the Write method. It plays fine the first time and fails with an underrun every second time. When adjusting the Buffer properties nothing changes.

My code:

const (
    sampleRate = 44100
    frequency  = 1760
    format   = alsa.FormatS8
    channels = 1

        // I tried many different combinations, I got the same behavior with every combination
    bufferFrames = periodFrames * periods
    periodFrames = sampleRate * 0.1
    periods      =  4
)

type AlsaAudioDevice struct {
    playbackDevice *alsa.PlaybackDevice
    sampleRate     int
    frequency      int
    format         alsa.Format
    channels       int
}

func NewAudioDevice(bluetoothMacAddr BTMAC) (*AlsaAudioDevice, error) {
    bufferParams := alsa.BufferParams{
        BufferFrames: bufferFrames,
        PeriodFrames: periodFrames,
        Periods:      periods,
    }
    p, err := alsa.NewPlaybackDevice("bluealsa:SRV=org.bluealsa,DEV="+string(bluetoothMacAddr)+",PROFILE=a2dp",
        channels, format, sampleRate,
        bufferParams)
    if err != nil {
        return nil, err
    }

    return &AlsaAudioDevice{playbackDevice: p}, nil
}

func (ad AlsaAudioDevice) CloseAudioDevice() {
    ad.playbackDevice.Close()
}

func (ad AlsaAudioDevice) PlayBeep(duration time.Duration) error {
    milliseconds := int(duration.Milliseconds())
    numSamples := milliseconds * sampleRate / 1000
    samples := make([]int8, numSamples*channels)

        // generate sine wave
    for i := 0; i < numSamples; i++ {
        // maxInt-1 because the max negative int is one less
        sample := int8((math.MaxInt8 - 1) * math.Sin(2*math.Pi*frequency*float64(i)/sampleRate))
        switch channels {
        case 1:
            samples[i] = sample
        case 2:
            samples[2*i] = sample   // Left channel
            samples[2*i+1] = sample // Right channel
        }
    }

    if ad.playbackDevice == nil {return errors.New("Could not access playback device")}
    samplesPlayed, err := ad.playbackDevice.Write(samples)
    if err == alsa.ErrUnderrun {
        // triggers every second time i call the Write(samples) method
        return err
    }
    if err != nil {
        return err
    }
    return nil
}

If I run PlayBeep(100*time.Millisecond) it works the first time and fails the second time. The same is true if I generate longer sine wave beeps. What can I do to fix this problem or at least debug it further? Thanks a lot!

ratzupaltuff commented 3 weeks ago

It fails in this section in the Write(buffer) method:

(Part of the library code)

ret := C.snd_pcm_writei(p.h, bufPtr, frames)
if ret == -C.EPIPE { // this returns true every second time calling the method
    C.snd_pcm_prepare(p.h) // this probably fixes the playback for the third call
    return 0, ErrUnderrun