From 5e472ba4c97bb3537c4454fab25565aa3e6d6e9d Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 12 Nov 2019 21:24:08 +1030 Subject: [PATCH 1/5] pcm: refactored to be general not alsa only Addition of new structs and helper functions for passing around pcm clips/buffers and their formats so that we don't have to import and rely on yobert/alsa code. Updated any commands and alsa package to use refactored code. --- cmd/audio-netsender/main.go | 72 ++++--- codec/pcm/pcm.go | 240 ++++++++++++++++++----- codec/pcm/pcm_test.go | 26 ++- device/alsa/alsa.go | 38 ++-- exp/pcm/resample/resample.go | 25 ++- exp/pcm/stereo-to-mono/stereo-to-mono.go | 23 ++- 6 files changed, 291 insertions(+), 133 deletions(-) diff --git a/cmd/audio-netsender/main.go b/cmd/audio-netsender/main.go index ac9e662d..4987cc71 100644 --- a/cmd/audio-netsender/main.go +++ b/cmd/audio-netsender/main.go @@ -46,7 +46,7 @@ import ( "sync" "time" - "github.com/yobert/alsa" + yalsa "github.com/yobert/alsa" "bitbucket.org/ausocean/av/codec/pcm" "bitbucket.org/ausocean/iot/pi/netsender" @@ -78,11 +78,11 @@ type audioClient struct { parameters // internals - dev *alsa.Device // audio input device - ab alsa.Buffer // ALSA's buffer - rb *ring.Buffer // our buffer - ns *netsender.Sender // our NetSender - vs int // our "var sum" to track var changes + dev *yalsa.Device // audio input device + clip pcm.Clip // Clip to contain the recording. + rb *ring.Buffer // our buffer + ns *netsender.Sender // our NetSender + vs int // our "var sum" to track var changes } type parameters struct { @@ -132,12 +132,26 @@ func main() { // Open the requested audio device. err = ac.open() if err != nil { - log.Log(logger.Fatal, "alsa.open failed", "error", err.Error()) + log.Log(logger.Fatal, "yalsa.open failed", "error", err.Error()) } // Capture audio in periods of ac.period seconds, and buffer rbDuration seconds in total. - ac.ab = ac.dev.NewBufferDuration(time.Second * time.Duration(ac.period)) - recSize := (((len(ac.ab.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate + ab := ac.dev.NewBufferDuration(time.Second * time.Duration(ac.period)) + sf, err := pcm.SFFromString(ab.Format.SampleFormat.String()) + if err != nil { + log.Log(logger.Error, err.Error()) + } + cf := pcm.ClipFormat{ + SFormat: sf, + Channels: ab.Format.Channels, + Rate: ab.Format.Rate, + } + ac.clip = pcm.Clip{ + Format: cf, + Data: ab.Data, + } + + recSize := (((len(ac.clip.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate rbLen := rbDuration / ac.period ac.rb = ring.NewBuffer(rbLen, recSize, rbTimeout) @@ -217,11 +231,11 @@ func (ac *audioClient) open() error { } log.Log(logger.Debug, "opening", "source", ac.source) - cards, err := alsa.OpenCards() + cards, err := yalsa.OpenCards() if err != nil { return err } - defer alsa.CloseCards(cards) + defer yalsa.CloseCards(cards) for _, card := range cards { devices, err := card.Devices() @@ -229,7 +243,7 @@ func (ac *audioClient) open() error { return err } for _, dev := range devices { - if dev.Type != alsa.PCM || !dev.Record { + if dev.Type != yalsa.PCM || !dev.Record { continue } if dev.Title == ac.source || ac.source == "" { @@ -287,12 +301,12 @@ func (ac *audioClient) open() error { log.Log(logger.Debug, "sample rate set", "rate", defaultFrameRate) } - var fmt alsa.FormatType + var fmt yalsa.FormatType switch ac.bits { case 16: - fmt = alsa.S16_LE + fmt = yalsa.S16_LE case 32: - fmt = alsa.S32_LE + fmt = yalsa.S32_LE default: return errors.New("unsupported sample bits") } @@ -318,7 +332,7 @@ func (ac *audioClient) open() error { // Re-opens the device and tries again if ASLA returns an error. // Spends a lot of time sleeping in Paused mode. // ToDo: Currently, reading audio and writing to the ringbuffer are synchronous. -// Need a way to asynchronously read from the ALSA buffer, i.e., _while_ it is recording to avoid any gaps. +// Need a way to asynchronously read from the clip, i.e., _while_ it is recording to avoid any gaps. func (ac *audioClient) input() { for { ac.mu.Lock() @@ -330,14 +344,14 @@ func (ac *audioClient) input() { } log.Log(logger.Debug, "recording audio for period", "seconds", ac.period) ac.mu.Lock() - err := ac.dev.Read(ac.ab.Data) + err := ac.dev.Read(ac.clip.Data) ac.mu.Unlock() if err != nil { log.Log(logger.Debug, "device.Read failed", "error", err.Error()) ac.mu.Lock() err = ac.open() // re-open if err != nil { - log.Log(logger.Fatal, "alsa.open failed", "error", err.Error()) + log.Log(logger.Fatal, "yalsa.open failed", "error", err.Error()) } ac.mu.Unlock() continue @@ -372,7 +386,7 @@ func (ac *audioClient) input() { // This function also handles NetReceiver configuration requests and updating of NetReceiver vars. func (ac *audioClient) output() { // Calculate the size of the output data based on wanted channels and rate. - outLen := (((len(ac.ab.Data) / ac.ab.Format.Channels) * ac.channels) / ac.ab.Format.Rate) * ac.rate + outLen := (((len(ac.clip.Data) / ac.clip.Format.Channels) * ac.channels) / ac.clip.Format.Rate) * ac.rate buf := make([]byte, outLen) mime := "audio/x-wav;codec=pcm;rate=" + strconv.Itoa(ac.rate) + ";channels=" + strconv.Itoa(ac.channels) + ";bits=" + strconv.Itoa(ac.bits) @@ -509,9 +523,9 @@ func read(rb *ring.Buffer, buf []byte) (int, error) { return n, nil } -// formatBuffer returns an ALSA buffer that has the recording data from the ac's original ALSA buffer but stored +// formatBuffer returns a Clip that has the recording data from the ac's original Clip but stored // in the desired format specified by the ac's parameters. -func (ac *audioClient) formatBuffer() alsa.Buffer { +func (ac *audioClient) formatBuffer() pcm.Clip { var err error ac.mu.Lock() wantChannels := ac.channels @@ -519,17 +533,17 @@ func (ac *audioClient) formatBuffer() alsa.Buffer { ac.mu.Unlock() // If nothing needs to be changed, return the original. - if ac.ab.Format.Channels == wantChannels && ac.ab.Format.Rate == wantRate { - return ac.ab + if ac.clip.Format.Channels == wantChannels && ac.clip.Format.Rate == wantRate { + return ac.clip } - formatted := alsa.Buffer{Format: ac.ab.Format} + formatted := pcm.Clip{Format: ac.clip.Format} bufCopied := false - if ac.ab.Format.Channels != wantChannels { + if ac.clip.Format.Channels != wantChannels { // Convert channels. - if ac.ab.Format.Channels == 2 && wantChannels == 1 { - if formatted, err = pcm.StereoToMono(ac.ab); err != nil { + if ac.clip.Format.Channels == 2 && wantChannels == 1 { + if formatted, err = pcm.StereoToMono(ac.clip); err != nil { log.Log(logger.Warning, "channel conversion failed, audio has remained stereo", "error", err.Error()) } else { formatted.Format.Channels = 1 @@ -538,13 +552,13 @@ func (ac *audioClient) formatBuffer() alsa.Buffer { } } - if ac.ab.Format.Rate != wantRate { + if ac.clip.Format.Rate != wantRate { // Convert rate. if bufCopied { formatted, err = pcm.Resample(formatted, wantRate) } else { - formatted, err = pcm.Resample(ac.ab, wantRate) + formatted, err = pcm.Resample(ac.clip, wantRate) } if err != nil { log.Log(logger.Warning, "rate conversion failed, audio has remained original rate", "error", err.Error()) diff --git a/codec/pcm/pcm.go b/codec/pcm/pcm.go index 8093401e..99877eb9 100644 --- a/codec/pcm/pcm.go +++ b/codec/pcm/pcm.go @@ -32,105 +32,151 @@ import ( "encoding/binary" "fmt" - "github.com/yobert/alsa" + "github.com/pkg/errors" ) -// Resample takes alsa.Buffer b and resamples the pcm audio data to 'rate' Hz and returns an alsa.Buffer with the resampled data. +// SampleFormat is the format that a PCM Clip's samples can be in. +type SampleFormat int + +// Used to represent an unknown format. +const ( + Unknown SampleFormat = -1 +) + +// Common sample formats that are used. +const ( + S8 SampleFormat = iota + U8 + S16_LE + S16_BE + U16_LE + U16_BE + S24_LE + S24_BE + U24_LE + U24_BE + S32_LE + S32_BE + U32_LE + U32_BE + FLOAT_LE + FLOAT_BE + FLOAT64_LE + FLOAT64_BE + // There are many more: + // https://linux.die.net/man/1/arecord + // https://trac.ffmpeg.org/wiki/audio%20types +) + +// ClipFormat contains the format for a PCM Clip. +type ClipFormat struct { + SFormat SampleFormat + Rate int + Channels int +} + +// Clip contains a clip of PCM data and the format that it is in. +type Clip struct { + Format ClipFormat + Data []byte +} + +// Resample takes Clip c and resamples the pcm audio data to 'rate' Hz and returns a Clip with the resampled data. // Notes: -// - Currently only downsampling is implemented and b's rate must be divisible by 'rate' or an error will occur. -// - If the number of bytes in b.Data is not divisible by the decimation factor (ratioFrom), the remaining bytes will +// - Currently only downsampling is implemented and c's rate must be divisible by 'rate' or an error will occur. +// - If the number of bytes in c.Data is not divisible by the decimation factor (ratioFrom), the remaining bytes will // not be included in the result. Eg. input of length 480002 downsampling 6:1 will result in output length 80000. -func Resample(b alsa.Buffer, rate int) (alsa.Buffer, error) { - if b.Format.Rate == rate { - return b, nil +func Resample(c Clip, rate int) (Clip, error) { + if c.Format.Rate == rate { + return c, nil } - if b.Format.Rate < 0 { - return alsa.Buffer{}, fmt.Errorf("Unable to convert from: %v Hz", b.Format.Rate) + if c.Format.Rate < 0 { + return Clip{}, fmt.Errorf("Unable to convert from: %v Hz", c.Format.Rate) } if rate < 0 { - return alsa.Buffer{}, fmt.Errorf("Unable to convert to: %v Hz", rate) + return Clip{}, fmt.Errorf("Unable to convert to: %v Hz", rate) } // The number of bytes in a sample. var sampleLen int - switch b.Format.SampleFormat { - case alsa.S32_LE: - sampleLen = 4 * b.Format.Channels - case alsa.S16_LE: - sampleLen = 2 * b.Format.Channels + switch c.Format.SFormat { + case S32_LE: + sampleLen = 4 * c.Format.Channels + case S16_LE: + sampleLen = 2 * c.Format.Channels default: - return alsa.Buffer{}, fmt.Errorf("Unhandled ALSA format: %v", b.Format.SampleFormat) + return Clip{}, fmt.Errorf("Unhandled ALSA format: %v", c.Format.SFormat) } - inPcmLen := len(b.Data) + inPcmLen := len(c.Data) // Calculate sample rate ratio ratioFrom:ratioTo. - rateGcd := gcd(rate, b.Format.Rate) - ratioFrom := b.Format.Rate / rateGcd + rateGcd := gcd(rate, c.Format.Rate) + ratioFrom := c.Format.Rate / rateGcd ratioTo := rate / rateGcd // ratioTo = 1 is the only number that will result in an even sampling. if ratioTo != 1 { - return alsa.Buffer{}, fmt.Errorf("unhandled from:to rate ratio %v:%v: 'to' must be 1", ratioFrom, ratioTo) + return Clip{}, fmt.Errorf("unhandled from:to rate ratio %v:%v: 'to' must be 1", ratioFrom, ratioTo) } newLen := inPcmLen / ratioFrom resampled := make([]byte, 0, newLen) - // For each new sample to be generated, loop through the respective 'ratioFrom' samples in 'b.Data' to add them + // For each new sample to be generated, loop through the respective 'ratioFrom' samples in 'c.Data' to add them // up and average them. The result is the new sample. bAvg := make([]byte, sampleLen) for i := 0; i < newLen/sampleLen; i++ { var sum int for j := 0; j < ratioFrom; j++ { - switch b.Format.SampleFormat { - case alsa.S32_LE: - sum += int(int32(binary.LittleEndian.Uint32(b.Data[(i*ratioFrom*sampleLen)+(j*sampleLen) : (i*ratioFrom*sampleLen)+((j+1)*sampleLen)]))) - case alsa.S16_LE: - sum += int(int16(binary.LittleEndian.Uint16(b.Data[(i*ratioFrom*sampleLen)+(j*sampleLen) : (i*ratioFrom*sampleLen)+((j+1)*sampleLen)]))) + switch c.Format.SFormat { + case S32_LE: + sum += int(int32(binary.LittleEndian.Uint32(c.Data[(i*ratioFrom*sampleLen)+(j*sampleLen) : (i*ratioFrom*sampleLen)+((j+1)*sampleLen)]))) + case S16_LE: + sum += int(int16(binary.LittleEndian.Uint16(c.Data[(i*ratioFrom*sampleLen)+(j*sampleLen) : (i*ratioFrom*sampleLen)+((j+1)*sampleLen)]))) } } avg := sum / ratioFrom - switch b.Format.SampleFormat { - case alsa.S32_LE: + switch c.Format.SFormat { + case S32_LE: binary.LittleEndian.PutUint32(bAvg, uint32(avg)) - case alsa.S16_LE: + case S16_LE: binary.LittleEndian.PutUint16(bAvg, uint16(avg)) } resampled = append(resampled, bAvg...) } - // Return a new alsa.Buffer with resampled data. - return alsa.Buffer{ - Format: alsa.BufferFormat{ - Channels: b.Format.Channels, - SampleFormat: b.Format.SampleFormat, - Rate: rate, + // Return a new Clip with resampled data. + return Clip{ + Format: ClipFormat{ + Channels: c.Format.Channels, + SFormat: c.Format.SFormat, + Rate: rate, }, Data: resampled, }, nil } // StereoToMono returns raw mono audio data generated from only the left channel from -// the given stereo recording (ALSA buffer) -func StereoToMono(b alsa.Buffer) (alsa.Buffer, error) { - if b.Format.Channels == 1 { - return b, nil +// the given stereo Clip +func StereoToMono(c Clip) (Clip, error) { + if c.Format.Channels == 1 { + return c, nil } - if b.Format.Channels != 2 { - return alsa.Buffer{}, fmt.Errorf("Audio is not stereo or mono, it has %v channels", b.Format.Channels) + if c.Format.Channels != 2 { + return Clip{}, fmt.Errorf("Audio is not stereo or mono, it has %v channels", c.Format.Channels) } var stereoSampleBytes int - switch b.Format.SampleFormat { - case alsa.S32_LE: + switch c.Format.SFormat { + case S32_LE: stereoSampleBytes = 8 - case alsa.S16_LE: + case S16_LE: stereoSampleBytes = 4 default: - return alsa.Buffer{}, fmt.Errorf("Unhandled ALSA format %v", b.Format.SampleFormat) + return Clip{}, fmt.Errorf("Unhandled sample format %v", c.Format.SFormat) } - recLength := len(b.Data) + recLength := len(c.Data) mono := make([]byte, recLength/2) // Convert to mono: for each byte in the stereo recording, if it's in the first half of a stereo sample @@ -138,17 +184,17 @@ func StereoToMono(b alsa.Buffer) (alsa.Buffer, error) { var inc int for i := 0; i < recLength; i++ { if i%stereoSampleBytes < stereoSampleBytes/2 { - mono[inc] = b.Data[i] + mono[inc] = c.Data[i] inc++ } } - // Return a new alsa.Buffer with resampled data. - return alsa.Buffer{ - Format: alsa.BufferFormat{ - Channels: 1, - SampleFormat: b.Format.SampleFormat, - Rate: b.Format.Rate, + // Return a new Clip with resampled data. + return Clip{ + Format: ClipFormat{ + Channels: 1, + SFormat: c.Format.SFormat, + Rate: c.Format.Rate, }, Data: mono, }, nil @@ -162,3 +208,91 @@ func gcd(a, b int) int { } return a } + +// String returns the string representation of a SampleFormat. +func (f SampleFormat) String() string { + switch f { + case S8: + return "S8" + case U8: + return "U8" + case S16_LE: + return "S16_LE" + case S16_BE: + return "S16_BE" + case U16_LE: + return "U16_LE" + case U16_BE: + return "U16_BE" + case S24_LE: + return "S24_LE" + case S24_BE: + return "S24_BE" + case U24_LE: + return "U24_LE" + case U24_BE: + return "U24_BE" + case S32_LE: + return "S32_LE" + case S32_BE: + return "S32_BE" + case U32_LE: + return "U32_LE" + case U32_BE: + return "U32_BE" + case FLOAT_LE: + return "FLOAT_LE" + case FLOAT_BE: + return "FLOAT_BE" + case FLOAT64_LE: + return "FLOAT64_LE" + case FLOAT64_BE: + return "FLOAT64_BE" + default: + return fmt.Sprintf("Invalid FormatType (%d)", f) + } +} + +// SFFromString takes a string representing a sample format and returns the corresponding SampleFormat. +func SFFromString(s string) (SampleFormat, error) { + switch s { + case "S8": + return S8, nil + case "U8": + return U8, nil + case "S16_LE": + return S16_LE, nil + case "S16_BE": + return S16_BE, nil + case "U16_LE": + return U16_LE, nil + case "U16_BE": + return U16_BE, nil + case "S24_LE": + return S24_LE, nil + case "S24_BE": + return S24_BE, nil + case "U24_LE": + return U24_LE, nil + case "U24_BE": + return U24_BE, nil + case "S32_LE": + return S32_LE, nil + case "S32_BE": + return S32_BE, nil + case "U32_LE": + return U32_LE, nil + case "U32_BE": + return U32_BE, nil + case "FLOAT_LE": + return FLOAT_LE, nil + case "FLOAT_BE": + return FLOAT_BE, nil + case "FLOAT64_LE": + return FLOAT64_LE, nil + case "FLOAT64_BE": + return FLOAT64_BE, nil + default: + return Unknown, errors.Errorf("Unknown FormatType (%d)", s) + } +} diff --git a/codec/pcm/pcm_test.go b/codec/pcm/pcm_test.go index 1aa1b9d2..e2324121 100644 --- a/codec/pcm/pcm_test.go +++ b/codec/pcm/pcm_test.go @@ -31,8 +31,6 @@ import ( "io/ioutil" "log" "testing" - - "github.com/yobert/alsa" ) // TestResample tests the Resample function using a pcm file that contains audio of a freq. sweep. @@ -47,19 +45,19 @@ func TestResample(t *testing.T) { log.Fatal(err) } - format := alsa.BufferFormat{ - Channels: 1, - Rate: 48000, - SampleFormat: alsa.S16_LE, + format := ClipFormat{ + Channels: 1, + Rate: 48000, + SFormat: S16_LE, } - buf := alsa.Buffer{ + clip := Clip{ Format: format, Data: inPcm, } // Resample pcm. - resampled, err := Resample(buf, 8000) + resampled, err := Resample(clip, 8000) if err != nil { log.Fatal(err) } @@ -88,19 +86,19 @@ func TestStereoToMono(t *testing.T) { log.Fatal(err) } - format := alsa.BufferFormat{ - Channels: 2, - Rate: 44100, - SampleFormat: alsa.S16_LE, + format := ClipFormat{ + Channels: 2, + Rate: 44100, + SFormat: S16_LE, } - buf := alsa.Buffer{ + clip := Clip{ Format: format, Data: inPcm, } // Convert audio. - mono, err := StereoToMono(buf) + mono, err := StereoToMono(clip) if err != nil { log.Fatal(err) } diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 6c634556..b37090e7 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -68,7 +68,7 @@ type ALSA struct { mu sync.Mutex // Provides synchronisation when changing modes concurrently. title string // Name of audio title, or empty for the default title. dev *yalsa.Device // ALSA device's Audio input device. - ab yalsa.Buffer // ALSA device's buffer. + clip pcm.Clip // Clip to contain the recording. rb *ring.Buffer // Our buffer. chunkSize int // This is the number of bytes that will be stored in rb at a time. Config // Configuration parameters for this device. @@ -133,10 +133,24 @@ func (d *ALSA) Set(c config.Config) error { } // Setup the device to record with desired period. - d.ab = d.dev.NewBufferDuration(time.Duration(d.RecPeriod * float64(time.Second))) + ab := d.dev.NewBufferDuration(time.Duration(d.RecPeriod * float64(time.Second))) + sf, err := pcm.SFFromString(ab.Format.SampleFormat.String()) + if err != nil { + d.l.Log(logger.Error, pkg+err.Error()) + return err + } + cf := pcm.ClipFormat{ + SFormat: sf, + Channels: ab.Format.Channels, + Rate: ab.Format.Rate, + } + d.clip = pcm.Clip{ + Format: cf, + Data: ab.Data, + } // Account for channel conversion. - chunkSize := float64(len(d.ab.Data) / d.dev.BufferFormat().Channels * d.Channels) + chunkSize := float64(len(d.clip.Data) / d.dev.BufferFormat().Channels * d.Channels) // Account for resampling. chunkSize = (chunkSize / float64(d.dev.BufferFormat().Rate)) * float64(d.SampleRate) @@ -372,7 +386,7 @@ func (d *ALSA) input() { // Read from audio device. d.l.Log(logger.Debug, pkg+"recording audio for period", "seconds", d.RecPeriod) - err := d.dev.Read(d.ab.Data) + err := d.dev.Read(d.clip.Data) if err != nil { d.l.Log(logger.Debug, pkg+"read failed", "error", err.Error()) err = d.open() // re-open @@ -414,26 +428,26 @@ func (d *ALSA) Read(p []byte) (int, error) { } // formatBuffer returns audio that has been converted to the desired format. -func (d *ALSA) formatBuffer() yalsa.Buffer { +func (d *ALSA) formatBuffer() pcm.Clip { var err error // If nothing needs to be changed, return the original. - if d.ab.Format.Channels == d.Channels && d.ab.Format.Rate == d.SampleRate { - return d.ab + if d.clip.Format.Channels == d.Channels && d.clip.Format.Rate == d.SampleRate { + return d.clip } - var formatted yalsa.Buffer - if d.ab.Format.Channels != d.Channels { + var formatted pcm.Clip + if d.clip.Format.Channels != d.Channels { // Convert channels. // TODO(Trek): Make this work for conversions other than stereo to mono. - if d.ab.Format.Channels == 2 && d.Channels == 1 { - formatted, err = pcm.StereoToMono(d.ab) + if d.clip.Format.Channels == 2 && d.Channels == 1 { + formatted, err = pcm.StereoToMono(d.clip) if err != nil { d.l.Log(logger.Fatal, pkg+"channel conversion failed", "error", err.Error()) } } } - if d.ab.Format.Rate != d.SampleRate { + if d.clip.Format.Rate != d.SampleRate { // Convert rate. formatted, err = pcm.Resample(formatted, d.SampleRate) if err != nil { diff --git a/exp/pcm/resample/resample.go b/exp/pcm/resample/resample.go index f7f5342e..00e9da28 100644 --- a/exp/pcm/resample/resample.go +++ b/exp/pcm/resample/resample.go @@ -32,7 +32,6 @@ import ( "log" "bitbucket.org/ausocean/av/codec/pcm" - "github.com/yobert/alsa" ) // This program accepts an input pcm file and outputs a resampled pcm file. @@ -43,7 +42,7 @@ func main() { var from = *flag.Int("from", 48000, "sample rate of input file") var to = *flag.Int("to", 8000, "sample rate of output file") var channels = *flag.Int("ch", 1, "number of channels in input file") - var sf = *flag.String("sf", "S16_LE", "sample format of input audio, eg. S16_LE") + var SFString = *flag.String("sf", "S16_LE", "sample format of input audio, eg. S16_LE") flag.Parse() // Read pcm. @@ -53,29 +52,29 @@ func main() { } fmt.Println("Read", len(inPcm), "bytes from file", inPath) - var sampleFormat alsa.FormatType - switch sf { + var sf pcm.SampleFormat + switch SFString { case "S32_LE": - sampleFormat = alsa.S32_LE + sf = pcm.S32_LE case "S16_LE": - sampleFormat = alsa.S16_LE + sf = pcm.S16_LE default: - log.Fatalf("Unhandled ALSA format: %v", sf) + log.Fatalf("Unhandled ALSA format: %v", SFString) } - format := alsa.BufferFormat{ - Channels: channels, - Rate: from, - SampleFormat: sampleFormat, + format := pcm.ClipFormat{ + Channels: channels, + Rate: from, + SFormat: sf, } - buf := alsa.Buffer{ + clip := pcm.Clip{ Format: format, Data: inPcm, } // Resample audio. - resampled, err := pcm.Resample(buf, to) + resampled, err := pcm.Resample(clip, to) if err != nil { log.Fatal(err) } diff --git a/exp/pcm/stereo-to-mono/stereo-to-mono.go b/exp/pcm/stereo-to-mono/stereo-to-mono.go index 729caa96..dca08d7f 100644 --- a/exp/pcm/stereo-to-mono/stereo-to-mono.go +++ b/exp/pcm/stereo-to-mono/stereo-to-mono.go @@ -32,7 +32,6 @@ import ( "log" "bitbucket.org/ausocean/av/codec/pcm" - "github.com/yobert/alsa" ) // This program accepts an input pcm file and outputs a resampled pcm file. @@ -40,7 +39,7 @@ import ( func main() { var inPath = *flag.String("in", "data.pcm", "file path of input data") var outPath = *flag.String("out", "mono.pcm", "file path of output") - var sf = *flag.String("sf", "S16_LE", "sample format of input audio, eg. S16_LE") + var SFString = *flag.String("sf", "S16_LE", "sample format of input audio, eg. S16_LE") flag.Parse() // Read pcm. @@ -50,28 +49,28 @@ func main() { } fmt.Println("Read", len(inPcm), "bytes from file", inPath) - var sampleFormat alsa.FormatType - switch sf { + var sf pcm.SampleFormat + switch SFString { case "S32_LE": - sampleFormat = alsa.S32_LE + sf = pcm.S32_LE case "S16_LE": - sampleFormat = alsa.S16_LE + sf = pcm.S16_LE default: - log.Fatalf("Unhandled ALSA format: %v", sf) + log.Fatalf("Unhandled sample format: %v", SFString) } - format := alsa.BufferFormat{ - Channels: 2, - SampleFormat: sampleFormat, + format := pcm.ClipFormat{ + Channels: 2, + SFormat: sf, } - buf := alsa.Buffer{ + clip := pcm.Clip{ Format: format, Data: inPcm, } // Convert audio. - mono, err := pcm.StereoToMono(buf) + mono, err := pcm.StereoToMono(clip) if err != nil { log.Fatal(err) } From 678245c63492c4d88f1640f80947a27af71380fc Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Nov 2019 12:30:48 +1030 Subject: [PATCH 2/5] pcm: fix string format --- codec/pcm/pcm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codec/pcm/pcm.go b/codec/pcm/pcm.go index 99877eb9..9e8393f9 100644 --- a/codec/pcm/pcm.go +++ b/codec/pcm/pcm.go @@ -293,6 +293,6 @@ func SFFromString(s string) (SampleFormat, error) { case "FLOAT64_BE": return FLOAT64_BE, nil default: - return Unknown, errors.Errorf("Unknown FormatType (%d)", s) + return Unknown, errors.Errorf("Unknown FormatType (%s)", s) } } From 3a7c2c5c5ef298823ab305637d43fc51e5ca8e76 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Nov 2019 14:00:52 +1030 Subject: [PATCH 3/5] pcm: removed unused sample formats --- codec/pcm/pcm.go | 88 +++--------------------------------------------- 1 file changed, 4 insertions(+), 84 deletions(-) diff --git a/codec/pcm/pcm.go b/codec/pcm/pcm.go index 9e8393f9..5500c97e 100644 --- a/codec/pcm/pcm.go +++ b/codec/pcm/pcm.go @@ -43,26 +43,10 @@ const ( Unknown SampleFormat = -1 ) -// Common sample formats that are used. +// Sample formats that we use. const ( - S8 SampleFormat = iota - U8 - S16_LE - S16_BE - U16_LE - U16_BE - S24_LE - S24_BE - U24_LE - U24_BE + S16_LE SampleFormat = iota S32_LE - S32_BE - U32_LE - U32_BE - FLOAT_LE - FLOAT_BE - FLOAT64_LE - FLOAT64_BE // There are many more: // https://linux.die.net/man/1/arecord // https://trac.ffmpeg.org/wiki/audio%20types @@ -212,87 +196,23 @@ func gcd(a, b int) int { // String returns the string representation of a SampleFormat. func (f SampleFormat) String() string { switch f { - case S8: - return "S8" - case U8: - return "U8" case S16_LE: return "S16_LE" - case S16_BE: - return "S16_BE" - case U16_LE: - return "U16_LE" - case U16_BE: - return "U16_BE" - case S24_LE: - return "S24_LE" - case S24_BE: - return "S24_BE" - case U24_LE: - return "U24_LE" - case U24_BE: - return "U24_BE" case S32_LE: return "S32_LE" - case S32_BE: - return "S32_BE" - case U32_LE: - return "U32_LE" - case U32_BE: - return "U32_BE" - case FLOAT_LE: - return "FLOAT_LE" - case FLOAT_BE: - return "FLOAT_BE" - case FLOAT64_LE: - return "FLOAT64_LE" - case FLOAT64_BE: - return "FLOAT64_BE" default: - return fmt.Sprintf("Invalid FormatType (%d)", f) + return "Unknown" } } // SFFromString takes a string representing a sample format and returns the corresponding SampleFormat. func SFFromString(s string) (SampleFormat, error) { switch s { - case "S8": - return S8, nil - case "U8": - return U8, nil case "S16_LE": return S16_LE, nil - case "S16_BE": - return S16_BE, nil - case "U16_LE": - return U16_LE, nil - case "U16_BE": - return U16_BE, nil - case "S24_LE": - return S24_LE, nil - case "S24_BE": - return S24_BE, nil - case "U24_LE": - return U24_LE, nil - case "U24_BE": - return U24_BE, nil case "S32_LE": return S32_LE, nil - case "S32_BE": - return S32_BE, nil - case "U32_LE": - return U32_LE, nil - case "U32_BE": - return U32_BE, nil - case "FLOAT_LE": - return FLOAT_LE, nil - case "FLOAT_BE": - return FLOAT_BE, nil - case "FLOAT64_LE": - return FLOAT64_LE, nil - case "FLOAT64_BE": - return FLOAT64_BE, nil default: - return Unknown, errors.Errorf("Unknown FormatType (%s)", s) + return Unknown, errors.Errorf("unknown sample format (%s)", s) } } From 796a3b9a9772d1db336ba67adfe7c91aa018c6a7 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Nov 2019 16:54:41 +1030 Subject: [PATCH 4/5] pcm: changed term clip to buffer --- cmd/audio-netsender/main.go | 42 +++++++++++----------- codec/pcm/pcm.go | 44 ++++++++++++------------ codec/pcm/pcm_test.go | 12 +++---- device/alsa/alsa.go | 26 +++++++------- exp/pcm/resample/resample.go | 6 ++-- exp/pcm/stereo-to-mono/stereo-to-mono.go | 6 ++-- 6 files changed, 68 insertions(+), 68 deletions(-) diff --git a/cmd/audio-netsender/main.go b/cmd/audio-netsender/main.go index 4987cc71..142c4441 100644 --- a/cmd/audio-netsender/main.go +++ b/cmd/audio-netsender/main.go @@ -78,11 +78,11 @@ type audioClient struct { parameters // internals - dev *yalsa.Device // audio input device - clip pcm.Clip // Clip to contain the recording. - rb *ring.Buffer // our buffer - ns *netsender.Sender // our NetSender - vs int // our "var sum" to track var changes + dev *yalsa.Device // audio input device + buf pcm.Buffer // Buffer to contain the recording. + rb *ring.Buffer // our buffer + ns *netsender.Sender // our NetSender + vs int // our "var sum" to track var changes } type parameters struct { @@ -141,17 +141,17 @@ func main() { if err != nil { log.Log(logger.Error, err.Error()) } - cf := pcm.ClipFormat{ + cf := pcm.BufferFormat{ SFormat: sf, Channels: ab.Format.Channels, Rate: ab.Format.Rate, } - ac.clip = pcm.Clip{ + ac.buf = pcm.Buffer{ Format: cf, Data: ab.Data, } - recSize := (((len(ac.clip.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate + recSize := (((len(ac.buf.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate rbLen := rbDuration / ac.period ac.rb = ring.NewBuffer(rbLen, recSize, rbTimeout) @@ -332,7 +332,7 @@ func (ac *audioClient) open() error { // Re-opens the device and tries again if ASLA returns an error. // Spends a lot of time sleeping in Paused mode. // ToDo: Currently, reading audio and writing to the ringbuffer are synchronous. -// Need a way to asynchronously read from the clip, i.e., _while_ it is recording to avoid any gaps. +// Need a way to asynchronously read from the buf, i.e., _while_ it is recording to avoid any gaps. func (ac *audioClient) input() { for { ac.mu.Lock() @@ -344,7 +344,7 @@ func (ac *audioClient) input() { } log.Log(logger.Debug, "recording audio for period", "seconds", ac.period) ac.mu.Lock() - err := ac.dev.Read(ac.clip.Data) + err := ac.dev.Read(ac.buf.Data) ac.mu.Unlock() if err != nil { log.Log(logger.Debug, "device.Read failed", "error", err.Error()) @@ -386,7 +386,7 @@ func (ac *audioClient) input() { // This function also handles NetReceiver configuration requests and updating of NetReceiver vars. func (ac *audioClient) output() { // Calculate the size of the output data based on wanted channels and rate. - outLen := (((len(ac.clip.Data) / ac.clip.Format.Channels) * ac.channels) / ac.clip.Format.Rate) * ac.rate + outLen := (((len(ac.buf.Data) / ac.buf.Format.Channels) * ac.channels) / ac.buf.Format.Rate) * ac.rate buf := make([]byte, outLen) mime := "audio/x-wav;codec=pcm;rate=" + strconv.Itoa(ac.rate) + ";channels=" + strconv.Itoa(ac.channels) + ";bits=" + strconv.Itoa(ac.bits) @@ -523,9 +523,9 @@ func read(rb *ring.Buffer, buf []byte) (int, error) { return n, nil } -// formatBuffer returns a Clip that has the recording data from the ac's original Clip but stored +// formatBuffer returns a Buffer that has the recording data from the ac's original Buffer but stored // in the desired format specified by the ac's parameters. -func (ac *audioClient) formatBuffer() pcm.Clip { +func (ac *audioClient) formatBuffer() pcm.Buffer { var err error ac.mu.Lock() wantChannels := ac.channels @@ -533,17 +533,17 @@ func (ac *audioClient) formatBuffer() pcm.Clip { ac.mu.Unlock() // If nothing needs to be changed, return the original. - if ac.clip.Format.Channels == wantChannels && ac.clip.Format.Rate == wantRate { - return ac.clip + if ac.buf.Format.Channels == wantChannels && ac.buf.Format.Rate == wantRate { + return ac.buf } - formatted := pcm.Clip{Format: ac.clip.Format} + formatted := pcm.Buffer{Format: ac.buf.Format} bufCopied := false - if ac.clip.Format.Channels != wantChannels { + if ac.buf.Format.Channels != wantChannels { // Convert channels. - if ac.clip.Format.Channels == 2 && wantChannels == 1 { - if formatted, err = pcm.StereoToMono(ac.clip); err != nil { + if ac.buf.Format.Channels == 2 && wantChannels == 1 { + if formatted, err = pcm.StereoToMono(ac.buf); err != nil { log.Log(logger.Warning, "channel conversion failed, audio has remained stereo", "error", err.Error()) } else { formatted.Format.Channels = 1 @@ -552,13 +552,13 @@ func (ac *audioClient) formatBuffer() pcm.Clip { } } - if ac.clip.Format.Rate != wantRate { + if ac.buf.Format.Rate != wantRate { // Convert rate. if bufCopied { formatted, err = pcm.Resample(formatted, wantRate) } else { - formatted, err = pcm.Resample(ac.clip, wantRate) + formatted, err = pcm.Resample(ac.buf, wantRate) } if err != nil { log.Log(logger.Warning, "rate conversion failed, audio has remained original rate", "error", err.Error()) diff --git a/codec/pcm/pcm.go b/codec/pcm/pcm.go index 5500c97e..9c0b8c6e 100644 --- a/codec/pcm/pcm.go +++ b/codec/pcm/pcm.go @@ -35,7 +35,7 @@ import ( "github.com/pkg/errors" ) -// SampleFormat is the format that a PCM Clip's samples can be in. +// SampleFormat is the format that a PCM Buffer's samples can be in. type SampleFormat int // Used to represent an unknown format. @@ -52,33 +52,33 @@ const ( // https://trac.ffmpeg.org/wiki/audio%20types ) -// ClipFormat contains the format for a PCM Clip. -type ClipFormat struct { +// BufferFormat contains the format for a PCM Buffer. +type BufferFormat struct { SFormat SampleFormat Rate int Channels int } -// Clip contains a clip of PCM data and the format that it is in. -type Clip struct { - Format ClipFormat +// Buffer contains a buffer of PCM data and the format that it is in. +type Buffer struct { + Format BufferFormat Data []byte } -// Resample takes Clip c and resamples the pcm audio data to 'rate' Hz and returns a Clip with the resampled data. +// Resample takes Buffer c and resamples the pcm audio data to 'rate' Hz and returns a Buffer with the resampled data. // Notes: // - Currently only downsampling is implemented and c's rate must be divisible by 'rate' or an error will occur. // - If the number of bytes in c.Data is not divisible by the decimation factor (ratioFrom), the remaining bytes will // not be included in the result. Eg. input of length 480002 downsampling 6:1 will result in output length 80000. -func Resample(c Clip, rate int) (Clip, error) { +func Resample(c Buffer, rate int) (Buffer, error) { if c.Format.Rate == rate { return c, nil } if c.Format.Rate < 0 { - return Clip{}, fmt.Errorf("Unable to convert from: %v Hz", c.Format.Rate) + return Buffer{}, fmt.Errorf("Unable to convert from: %v Hz", c.Format.Rate) } if rate < 0 { - return Clip{}, fmt.Errorf("Unable to convert to: %v Hz", rate) + return Buffer{}, fmt.Errorf("Unable to convert to: %v Hz", rate) } // The number of bytes in a sample. @@ -89,7 +89,7 @@ func Resample(c Clip, rate int) (Clip, error) { case S16_LE: sampleLen = 2 * c.Format.Channels default: - return Clip{}, fmt.Errorf("Unhandled ALSA format: %v", c.Format.SFormat) + return Buffer{}, fmt.Errorf("Unhandled ALSA format: %v", c.Format.SFormat) } inPcmLen := len(c.Data) @@ -100,7 +100,7 @@ func Resample(c Clip, rate int) (Clip, error) { // ratioTo = 1 is the only number that will result in an even sampling. if ratioTo != 1 { - return Clip{}, fmt.Errorf("unhandled from:to rate ratio %v:%v: 'to' must be 1", ratioFrom, ratioTo) + return Buffer{}, fmt.Errorf("unhandled from:to rate ratio %v:%v: 'to' must be 1", ratioFrom, ratioTo) } newLen := inPcmLen / ratioFrom @@ -129,9 +129,9 @@ func Resample(c Clip, rate int) (Clip, error) { resampled = append(resampled, bAvg...) } - // Return a new Clip with resampled data. - return Clip{ - Format: ClipFormat{ + // Return a new Buffer with resampled data. + return Buffer{ + Format: BufferFormat{ Channels: c.Format.Channels, SFormat: c.Format.SFormat, Rate: rate, @@ -141,13 +141,13 @@ func Resample(c Clip, rate int) (Clip, error) { } // StereoToMono returns raw mono audio data generated from only the left channel from -// the given stereo Clip -func StereoToMono(c Clip) (Clip, error) { +// the given stereo Buffer +func StereoToMono(c Buffer) (Buffer, error) { if c.Format.Channels == 1 { return c, nil } if c.Format.Channels != 2 { - return Clip{}, fmt.Errorf("Audio is not stereo or mono, it has %v channels", c.Format.Channels) + return Buffer{}, fmt.Errorf("Audio is not stereo or mono, it has %v channels", c.Format.Channels) } var stereoSampleBytes int @@ -157,7 +157,7 @@ func StereoToMono(c Clip) (Clip, error) { case S16_LE: stereoSampleBytes = 4 default: - return Clip{}, fmt.Errorf("Unhandled sample format %v", c.Format.SFormat) + return Buffer{}, fmt.Errorf("Unhandled sample format %v", c.Format.SFormat) } recLength := len(c.Data) @@ -173,9 +173,9 @@ func StereoToMono(c Clip) (Clip, error) { } } - // Return a new Clip with resampled data. - return Clip{ - Format: ClipFormat{ + // Return a new Buffer with resampled data. + return Buffer{ + Format: BufferFormat{ Channels: 1, SFormat: c.Format.SFormat, Rate: c.Format.Rate, diff --git a/codec/pcm/pcm_test.go b/codec/pcm/pcm_test.go index e2324121..8e9cf891 100644 --- a/codec/pcm/pcm_test.go +++ b/codec/pcm/pcm_test.go @@ -45,19 +45,19 @@ func TestResample(t *testing.T) { log.Fatal(err) } - format := ClipFormat{ + format := BufferFormat{ Channels: 1, Rate: 48000, SFormat: S16_LE, } - clip := Clip{ + buf := Buffer{ Format: format, Data: inPcm, } // Resample pcm. - resampled, err := Resample(clip, 8000) + resampled, err := Resample(buf, 8000) if err != nil { log.Fatal(err) } @@ -86,19 +86,19 @@ func TestStereoToMono(t *testing.T) { log.Fatal(err) } - format := ClipFormat{ + format := BufferFormat{ Channels: 2, Rate: 44100, SFormat: S16_LE, } - clip := Clip{ + buf := Buffer{ Format: format, Data: inPcm, } // Convert audio. - mono, err := StereoToMono(clip) + mono, err := StereoToMono(buf) if err != nil { log.Fatal(err) } diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index b37090e7..47f614bc 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -68,7 +68,7 @@ type ALSA struct { mu sync.Mutex // Provides synchronisation when changing modes concurrently. title string // Name of audio title, or empty for the default title. dev *yalsa.Device // ALSA device's Audio input device. - clip pcm.Clip // Clip to contain the recording. + buf pcm.Buffer // Buffer to contain the recording. rb *ring.Buffer // Our buffer. chunkSize int // This is the number of bytes that will be stored in rb at a time. Config // Configuration parameters for this device. @@ -139,18 +139,18 @@ func (d *ALSA) Set(c config.Config) error { d.l.Log(logger.Error, pkg+err.Error()) return err } - cf := pcm.ClipFormat{ + cf := pcm.BufferFormat{ SFormat: sf, Channels: ab.Format.Channels, Rate: ab.Format.Rate, } - d.clip = pcm.Clip{ + d.buf = pcm.Buffer{ Format: cf, Data: ab.Data, } // Account for channel conversion. - chunkSize := float64(len(d.clip.Data) / d.dev.BufferFormat().Channels * d.Channels) + chunkSize := float64(len(d.buf.Data) / d.dev.BufferFormat().Channels * d.Channels) // Account for resampling. chunkSize = (chunkSize / float64(d.dev.BufferFormat().Rate)) * float64(d.SampleRate) @@ -386,7 +386,7 @@ func (d *ALSA) input() { // Read from audio device. d.l.Log(logger.Debug, pkg+"recording audio for period", "seconds", d.RecPeriod) - err := d.dev.Read(d.clip.Data) + err := d.dev.Read(d.buf.Data) if err != nil { d.l.Log(logger.Debug, pkg+"read failed", "error", err.Error()) err = d.open() // re-open @@ -428,26 +428,26 @@ func (d *ALSA) Read(p []byte) (int, error) { } // formatBuffer returns audio that has been converted to the desired format. -func (d *ALSA) formatBuffer() pcm.Clip { +func (d *ALSA) formatBuffer() pcm.Buffer { var err error // If nothing needs to be changed, return the original. - if d.clip.Format.Channels == d.Channels && d.clip.Format.Rate == d.SampleRate { - return d.clip + if d.buf.Format.Channels == d.Channels && d.buf.Format.Rate == d.SampleRate { + return d.buf } - var formatted pcm.Clip - if d.clip.Format.Channels != d.Channels { + var formatted pcm.Buffer + if d.buf.Format.Channels != d.Channels { // Convert channels. // TODO(Trek): Make this work for conversions other than stereo to mono. - if d.clip.Format.Channels == 2 && d.Channels == 1 { - formatted, err = pcm.StereoToMono(d.clip) + if d.buf.Format.Channels == 2 && d.Channels == 1 { + formatted, err = pcm.StereoToMono(d.buf) if err != nil { d.l.Log(logger.Fatal, pkg+"channel conversion failed", "error", err.Error()) } } } - if d.clip.Format.Rate != d.SampleRate { + if d.buf.Format.Rate != d.SampleRate { // Convert rate. formatted, err = pcm.Resample(formatted, d.SampleRate) if err != nil { diff --git a/exp/pcm/resample/resample.go b/exp/pcm/resample/resample.go index 00e9da28..6c7106b6 100644 --- a/exp/pcm/resample/resample.go +++ b/exp/pcm/resample/resample.go @@ -62,19 +62,19 @@ func main() { log.Fatalf("Unhandled ALSA format: %v", SFString) } - format := pcm.ClipFormat{ + format := pcm.BufferFormat{ Channels: channels, Rate: from, SFormat: sf, } - clip := pcm.Clip{ + buf := pcm.Buffer{ Format: format, Data: inPcm, } // Resample audio. - resampled, err := pcm.Resample(clip, to) + resampled, err := pcm.Resample(buf, to) if err != nil { log.Fatal(err) } diff --git a/exp/pcm/stereo-to-mono/stereo-to-mono.go b/exp/pcm/stereo-to-mono/stereo-to-mono.go index dca08d7f..84700737 100644 --- a/exp/pcm/stereo-to-mono/stereo-to-mono.go +++ b/exp/pcm/stereo-to-mono/stereo-to-mono.go @@ -59,18 +59,18 @@ func main() { log.Fatalf("Unhandled sample format: %v", SFString) } - format := pcm.ClipFormat{ + format := pcm.BufferFormat{ Channels: 2, SFormat: sf, } - clip := pcm.Clip{ + buf := pcm.Buffer{ Format: format, Data: inPcm, } // Convert audio. - mono, err := pcm.StereoToMono(clip) + mono, err := pcm.StereoToMono(buf) if err != nil { log.Fatal(err) } From f2c9cc588195f38fd5acbf8b699072cb412219bd Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 13 Nov 2019 17:11:35 +1030 Subject: [PATCH 5/5] alsa: renamed ALSA.buf to ALSA.pb since there are two buffers Also elaborated on the difference in the comments. --- cmd/audio-netsender/main.go | 28 ++++++++++++++-------------- device/alsa/alsa.go | 22 +++++++++++----------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/audio-netsender/main.go b/cmd/audio-netsender/main.go index 142c4441..ade701aa 100644 --- a/cmd/audio-netsender/main.go +++ b/cmd/audio-netsender/main.go @@ -79,8 +79,8 @@ type audioClient struct { // internals dev *yalsa.Device // audio input device - buf pcm.Buffer // Buffer to contain the recording. - rb *ring.Buffer // our buffer + pb pcm.Buffer // Buffer to contain the direct audio from ALSA. + rb *ring.Buffer // Ring buffer to contain processed audio ready to be read. ns *netsender.Sender // our NetSender vs int // our "var sum" to track var changes } @@ -146,12 +146,12 @@ func main() { Channels: ab.Format.Channels, Rate: ab.Format.Rate, } - ac.buf = pcm.Buffer{ + ac.pb = pcm.Buffer{ Format: cf, Data: ab.Data, } - recSize := (((len(ac.buf.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate + recSize := (((len(ac.pb.Data) / ac.dev.BufferFormat().Channels) * ac.channels) / ac.dev.BufferFormat().Rate) * ac.rate rbLen := rbDuration / ac.period ac.rb = ring.NewBuffer(rbLen, recSize, rbTimeout) @@ -344,7 +344,7 @@ func (ac *audioClient) input() { } log.Log(logger.Debug, "recording audio for period", "seconds", ac.period) ac.mu.Lock() - err := ac.dev.Read(ac.buf.Data) + err := ac.dev.Read(ac.pb.Data) ac.mu.Unlock() if err != nil { log.Log(logger.Debug, "device.Read failed", "error", err.Error()) @@ -386,7 +386,7 @@ func (ac *audioClient) input() { // This function also handles NetReceiver configuration requests and updating of NetReceiver vars. func (ac *audioClient) output() { // Calculate the size of the output data based on wanted channels and rate. - outLen := (((len(ac.buf.Data) / ac.buf.Format.Channels) * ac.channels) / ac.buf.Format.Rate) * ac.rate + outLen := (((len(ac.pb.Data) / ac.pb.Format.Channels) * ac.channels) / ac.pb.Format.Rate) * ac.rate buf := make([]byte, outLen) mime := "audio/x-wav;codec=pcm;rate=" + strconv.Itoa(ac.rate) + ";channels=" + strconv.Itoa(ac.channels) + ";bits=" + strconv.Itoa(ac.bits) @@ -533,17 +533,17 @@ func (ac *audioClient) formatBuffer() pcm.Buffer { ac.mu.Unlock() // If nothing needs to be changed, return the original. - if ac.buf.Format.Channels == wantChannels && ac.buf.Format.Rate == wantRate { - return ac.buf + if ac.pb.Format.Channels == wantChannels && ac.pb.Format.Rate == wantRate { + return ac.pb } - formatted := pcm.Buffer{Format: ac.buf.Format} + formatted := pcm.Buffer{Format: ac.pb.Format} bufCopied := false - if ac.buf.Format.Channels != wantChannels { + if ac.pb.Format.Channels != wantChannels { // Convert channels. - if ac.buf.Format.Channels == 2 && wantChannels == 1 { - if formatted, err = pcm.StereoToMono(ac.buf); err != nil { + if ac.pb.Format.Channels == 2 && wantChannels == 1 { + if formatted, err = pcm.StereoToMono(ac.pb); err != nil { log.Log(logger.Warning, "channel conversion failed, audio has remained stereo", "error", err.Error()) } else { formatted.Format.Channels = 1 @@ -552,13 +552,13 @@ func (ac *audioClient) formatBuffer() pcm.Buffer { } } - if ac.buf.Format.Rate != wantRate { + if ac.pb.Format.Rate != wantRate { // Convert rate. if bufCopied { formatted, err = pcm.Resample(formatted, wantRate) } else { - formatted, err = pcm.Resample(ac.buf, wantRate) + formatted, err = pcm.Resample(ac.pb, wantRate) } if err != nil { log.Log(logger.Warning, "rate conversion failed, audio has remained original rate", "error", err.Error()) diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 47f614bc..f04af950 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -68,8 +68,8 @@ type ALSA struct { mu sync.Mutex // Provides synchronisation when changing modes concurrently. title string // Name of audio title, or empty for the default title. dev *yalsa.Device // ALSA device's Audio input device. - buf pcm.Buffer // Buffer to contain the recording. - rb *ring.Buffer // Our buffer. + pb pcm.Buffer // Buffer to contain the direct audio from ALSA. + rb *ring.Buffer // Ring buffer to contain processed audio ready to be read. chunkSize int // This is the number of bytes that will be stored in rb at a time. Config // Configuration parameters for this device. } @@ -144,13 +144,13 @@ func (d *ALSA) Set(c config.Config) error { Channels: ab.Format.Channels, Rate: ab.Format.Rate, } - d.buf = pcm.Buffer{ + d.pb = pcm.Buffer{ Format: cf, Data: ab.Data, } // Account for channel conversion. - chunkSize := float64(len(d.buf.Data) / d.dev.BufferFormat().Channels * d.Channels) + chunkSize := float64(len(d.pb.Data) / d.dev.BufferFormat().Channels * d.Channels) // Account for resampling. chunkSize = (chunkSize / float64(d.dev.BufferFormat().Rate)) * float64(d.SampleRate) @@ -386,7 +386,7 @@ func (d *ALSA) input() { // Read from audio device. d.l.Log(logger.Debug, pkg+"recording audio for period", "seconds", d.RecPeriod) - err := d.dev.Read(d.buf.Data) + err := d.dev.Read(d.pb.Data) if err != nil { d.l.Log(logger.Debug, pkg+"read failed", "error", err.Error()) err = d.open() // re-open @@ -432,22 +432,22 @@ func (d *ALSA) formatBuffer() pcm.Buffer { var err error // If nothing needs to be changed, return the original. - if d.buf.Format.Channels == d.Channels && d.buf.Format.Rate == d.SampleRate { - return d.buf + if d.pb.Format.Channels == d.Channels && d.pb.Format.Rate == d.SampleRate { + return d.pb } var formatted pcm.Buffer - if d.buf.Format.Channels != d.Channels { + if d.pb.Format.Channels != d.Channels { // Convert channels. // TODO(Trek): Make this work for conversions other than stereo to mono. - if d.buf.Format.Channels == 2 && d.Channels == 1 { - formatted, err = pcm.StereoToMono(d.buf) + if d.pb.Format.Channels == 2 && d.Channels == 1 { + formatted, err = pcm.StereoToMono(d.pb) if err != nil { d.l.Log(logger.Fatal, pkg+"channel conversion failed", "error", err.Error()) } } } - if d.buf.Format.Rate != d.SampleRate { + if d.pb.Format.Rate != d.SampleRate { // Convert rate. formatted, err = pcm.Resample(formatted, d.SampleRate) if err != nil {