Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stuttering in realtime stream #31

Closed
gitchander opened this issue Nov 19, 2019 · 0 comments
Closed

Stuttering in realtime stream #31

gitchander opened this issue Nov 19, 2019 · 0 comments

Comments

@gitchander
Copy link

How I can play real time stream without stuttering?
It is work code. Sometimes it leads to an error in the method Write (Output underflowed).
Thanks in advance for your help!

package main

import (
	"fmt"
	"log"
	"math"
	"sync"
	"time"

	"github.com/gordonklaus/portaudio"
)

func main() {

	params := Params{
		SamplesPerSecond: 8000,
		SamplesPerPacket: 2000,
	}

	quit := make(chan struct{})
	packets := generate(quit, params)

	var wg sync.WaitGroup
	wg.Add(1)
	go playPortaudio(&wg, packets, params)
	time.Sleep(10 * time.Second)
	close(quit)
	wg.Wait()
}

func playPortaudio(wg *sync.WaitGroup, packets <-chan []int16, params Params) {
	defer wg.Done()

	err := portaudio.Initialize()
	checkError(err)

	defer portaudio.Terminate()

	var (
		channels = 1

		numberInputChannels  = 0
		numberOutputChannels = channels
		sampleRate           = float64(params.SamplesPerSecond)
		framesPerBuffer      = params.SamplesPerPacket * channels
		out                  = make([]int16, framesPerBuffer)
	)

	stream, err := portaudio.OpenDefaultStream(
		numberInputChannels,
		numberOutputChannels,
		sampleRate,
		framesPerBuffer,
		&out,
	)
	checkError(err)

	err = stream.Start()
	checkError(err)

	for packet := range packets {
		copy(out, packet)

		err = stream.Write()
		if err != nil {
			fmt.Println("ERROR:", err)
		}
	}
}

func checkError(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func playPrintln(wg *sync.WaitGroup, packets <-chan []int16) {
	defer wg.Done()
	for packet := range packets {
		fmt.Println(len(packet))
	}
}

type Params struct {
	SamplesPerSecond int // sampleRate
	SamplesPerPacket int
}

func generate(quit <-chan struct{}, params Params) <-chan []int16 {
	packets := make(chan []int16)
	go func() {
		gen(quit, packets, params)
		close(packets)
	}()
	return packets
}

func gen(quit <-chan struct{}, packets chan<- []int16, params Params) {

	ts := NewToneSampler(
		Garmonica{
			Amplitude: float64(math.MaxInt16) * 0.7,
			Frequency: 1000,
			Phase:     0,
		},
		params.SamplesPerSecond,
	)

	t := time.Now()

	//sampleDuration := time.Second / time.Duration(params.SamplesPerSecond)
	packetDuration := (time.Second * time.Duration(params.SamplesPerPacket)) / time.Duration(params.SamplesPerSecond)

	//fmt.Println("packet duration:", packetDuration)

	for {
		packet := make([]int16, params.SamplesPerPacket)
		for i := range packet {
			packet[i] = int16(ts.NextSample())
		}

		select {
		case <-quit:
			return
		case packets <- packet:
		}

		// calc sleep
		t = t.Add(packetDuration)
		d := t.Sub(time.Now())
		if d > 0 {
			time.Sleep(d)
		}
	}
}

type Sampler interface {
	NextSample() float64
}

// amplitude * sin( 2 * pi * frequency * t + phase)
// w = 2 * pi * frequency
// amplitude * sin( w * t + phase)

type Garmonica struct {
	Amplitude, Frequency, Phase float64
}

type ToneSampler struct {
	g  Garmonica
	w  float64 // 2 * pi * frequency
	t  time.Duration
	dt time.Duration // sampleDuration
}

var _ Sampler = &ToneSampler{}

// sampleRate, samplesPerSecond
func NewToneSampler(g Garmonica, sampleRate int) *ToneSampler {

	w := 2.0 * math.Pi * g.Frequency
	sampleDuration := time.Second / time.Duration(sampleRate)

	return &ToneSampler{
		g:  g,
		w:  w,
		t:  0,
		dt: sampleDuration,
	}
}

func (p *ToneSampler) Reset() {
	p.t = 0
}

func (p *ToneSampler) NextSample() float64 {
	angle := p.w*p.t.Seconds() + p.g.Phase
	sample := p.g.Amplitude * math.Sin(angle)
	p.t += p.dt
	return sample
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant