portaudio-go icon indicating copy to clipboard operation
portaudio-go copied to clipboard

Error when callback twice

Open JianleiZhang opened this issue 7 years ago • 0 comments

package main

import (
	"io"
	"log"
	"os"
	"sync"
	"unsafe"

	"github.com/xlab/portaudio-go/portaudio"
	"github.com/youpy/go-wav"
)

const (
	samplesPerChannel = 2048
)

func main() {
	if err := portaudio.Initialize(); paError(err) {
		log.Fatalln("PortAudio init error:", paErrorText(err))
	}

	defer func() {
		if err := portaudio.Terminate(); paError(err) {
			log.Println("PortAudio term error:", paErrorText(err))
		}
	}()
	play("./t.wav")
	play("./t.wav")
}

func play(filePath string) {
	file, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := wav.NewReader(file)
	info, err := reader.Format()
	if err != nil {
		log.Fatal(err)
	}

	samplesOut := make(chan [][]float32)

	go func() {
		var frame [][]float32
		for {
			samples, err := reader.ReadSamples()
			if err == io.EOF {
				samplesOut <- frame
				close(samplesOut)
				break
			}

			for _, sample := range samples {
				frame = append(frame, []float32{float32(reader.FloatValue(sample, 0))})
				if len(frame)%samplesPerChannel == 0 {
					samplesOut <- frame
					frame = [][]float32{}
				}
			}
		}
	}()

	var wg sync.WaitGroup
	var stream *portaudio.Stream
	callback := paCallback(&wg, int(info.NumChannels), samplesOut)
	if err := portaudio.OpenDefaultStream(&stream, 0, int32(info.NumChannels), portaudio.PaFloat32, float64(info.SampleRate), samplesPerChannel, callback, nil); paError(err) {
		log.Fatalln("PortAudio error:", paErrorText(err))
	}
	defer func() {
		if err := portaudio.CloseStream(stream); paError(err) {
			log.Println("[WARN] PortAudio error:", paErrorText(err))
		}
	}()

	if err := portaudio.StartStream(stream); paError(err) {
		log.Fatalln("PortAudio error:", paErrorText(err))
	}
	defer func() {
		if err := portaudio.StopStream(stream); paError(err) {
			log.Fatalln("[WARN] PortAudio error:", paErrorText(err))
		}
	}()

	wg.Wait()
}

func paError(err portaudio.Error) bool {
	return portaudio.ErrorCode(err) != portaudio.PaNoError
}

func paErrorText(err portaudio.Error) string {
	return portaudio.GetErrorText(err)
}

func paCallback(wg *sync.WaitGroup, channels int, samples <-chan [][]float32) portaudio.StreamCallback {
	wg.Add(1)
	return func(_ unsafe.Pointer, output unsafe.Pointer, sampleCount uint, _ *portaudio.StreamCallbackTimeInfo, _ portaudio.StreamCallbackFlags, _ unsafe.Pointer) int32 {

		const (
			statusContinue = int32(portaudio.PaContinue)
			statusComplete = int32(portaudio.PaComplete)
		)

		frame, ok := <-samples
		if !ok {
			wg.Done()
			return statusComplete
		}
		if len(frame) > int(sampleCount) {
			frame = frame[:sampleCount]
		}

		var idx int
		out := (*(*[1 << 32]float32)(unsafe.Pointer(output)))[:int(sampleCount)*channels]
		for _, sample := range frame {
			if len(sample) > channels {
				sample = sample[:channels]
			}
			for i := range sample {
				out[idx] = sample[i]
				idx++
			}
		}

		return statusContinue
	}
}

panic: sync: negative WaitGroup counter

JianleiZhang avatar Mar 08 '18 03:03 JianleiZhang