├── .travis.yml ├── LICENSE ├── README.md ├── alsa.go ├── alsa_test.go ├── reader_thread.c └── reader_thread.h /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.6" 4 | addons: 5 | apt: 6 | packages: 7 | - libasound2-dev 8 | before_install: 9 | - go get github.com/axw/gocov/gocov 10 | - go get github.com/mattn/goveralls 11 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 12 | script: 13 | - $HOME/gopath/bin/goveralls -service=travis-ci 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2021 ecobee. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of ecobee nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go ALSA bindings 2 | 3 | These bindings allow capture and playback of audio via 4 | [ALSA](http://www.alsa-project.org/) using the 5 | [alsa-lib](http://www.alsa-project.org/alsa-doc/alsa-lib/pcm.html) library. 6 | 7 | [![Build Status](https://travis-ci.org/cocoonlife/goalsa.svg)](https://travis-ci.org/cocoonlife/goalsa) 8 | 9 | [![Coverage Status](https://coveralls.io/repos/cocoonlife/goalsa/badge.svg?branch=master&service=github)](https://coveralls.io/github/cocoonlife/goalsa?branch=master) 10 | 11 | ### Installation 12 | 13 | go get github.com/cocoonlife/goalsa 14 | 15 | ### Status 16 | 17 | The code has support for capture and playback with various parameters 18 | however it is only quite lightly tested so it is likely that bugs remain. 19 | Playback in particular has not been very well tested. 20 | -------------------------------------------------------------------------------- /alsa.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Cocoon Labs Ltd. 2 | // 3 | // See LICENSE file for terms and conditions. 4 | 5 | // Package alsa provides Go bindings to the ALSA library. 6 | package alsa 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | "reflect" 12 | "runtime" 13 | "unsafe" 14 | ) 15 | 16 | /* 17 | #cgo LDFLAGS: -lasound 18 | #include "reader_thread.h" 19 | #include 20 | #include 21 | */ 22 | import "C" 23 | 24 | // Format is the type used for specifying sample formats. 25 | type Format C.snd_pcm_format_t 26 | 27 | // The range of sample formats supported by ALSA. 28 | const ( 29 | FormatS8 = C.SND_PCM_FORMAT_S8 30 | FormatU8 = C.SND_PCM_FORMAT_U8 31 | FormatS16LE = C.SND_PCM_FORMAT_S16_LE 32 | FormatS16BE = C.SND_PCM_FORMAT_S16_BE 33 | FormatU16LE = C.SND_PCM_FORMAT_U16_LE 34 | FormatU16BE = C.SND_PCM_FORMAT_U16_BE 35 | FormatS24LE = C.SND_PCM_FORMAT_S24_LE 36 | FormatS24BE = C.SND_PCM_FORMAT_S24_BE 37 | FormatU24LE = C.SND_PCM_FORMAT_U24_LE 38 | FormatU24BE = C.SND_PCM_FORMAT_U24_BE 39 | FormatS32LE = C.SND_PCM_FORMAT_S32_LE 40 | FormatS32BE = C.SND_PCM_FORMAT_S32_BE 41 | FormatU32LE = C.SND_PCM_FORMAT_U32_LE 42 | FormatU32BE = C.SND_PCM_FORMAT_U32_BE 43 | FormatFloatLE = C.SND_PCM_FORMAT_FLOAT_LE 44 | FormatFloatBE = C.SND_PCM_FORMAT_FLOAT_BE 45 | FormatFloat64LE = C.SND_PCM_FORMAT_FLOAT64_LE 46 | FormatFloat64BE = C.SND_PCM_FORMAT_FLOAT64_BE 47 | ) 48 | 49 | var ( 50 | // ErrOverrun signals an overrun error 51 | ErrOverrun = errors.New("overrun") 52 | // ErrUnderrun signals an underrun error 53 | ErrUnderrun = errors.New("underrun") 54 | ) 55 | 56 | // BufferParams specifies the buffer parameters of a device. 57 | type BufferParams struct { 58 | BufferFrames int 59 | PeriodFrames int 60 | Periods int 61 | } 62 | 63 | type device struct { 64 | h *C.snd_pcm_t 65 | Channels int 66 | Format Format 67 | Rate int 68 | BufferParams BufferParams 69 | frames int 70 | readerThread *C.reader_thread_state 71 | } 72 | 73 | func createError(errorMsg string, errorCode C.int) (err error) { 74 | strError := C.GoString(C.snd_strerror(errorCode)) 75 | err = fmt.Errorf("%s: %s", errorMsg, strError) 76 | return 77 | } 78 | 79 | func (d *device) createDevice(deviceName string, channels int, format Format, rate int, playback bool, bufferParams BufferParams) (err error) { 80 | deviceCString := C.CString(deviceName) 81 | defer C.free(unsafe.Pointer(deviceCString)) 82 | var ret C.int 83 | if playback { 84 | ret = C.snd_pcm_open(&d.h, deviceCString, C.SND_PCM_STREAM_PLAYBACK, 0) 85 | } else { 86 | ret = C.snd_pcm_open(&d.h, deviceCString, C.SND_PCM_STREAM_CAPTURE, 0) 87 | } 88 | if ret < 0 { 89 | return fmt.Errorf("could not open ALSA device %s", deviceName) 90 | } 91 | runtime.SetFinalizer(d, (*device).Close) 92 | var hwParams *C.snd_pcm_hw_params_t 93 | ret = C.snd_pcm_hw_params_malloc(&hwParams) 94 | if ret < 0 { 95 | return createError("could not alloc hw params", ret) 96 | } 97 | defer C.snd_pcm_hw_params_free(hwParams) 98 | ret = C.snd_pcm_hw_params_any(d.h, hwParams) 99 | if ret < 0 { 100 | return createError("could not set default hw params", ret) 101 | } 102 | ret = C.snd_pcm_hw_params_set_access(d.h, hwParams, C.SND_PCM_ACCESS_RW_INTERLEAVED) 103 | if ret < 0 { 104 | return createError("could not set access params", ret) 105 | } 106 | ret = C.snd_pcm_hw_params_set_format(d.h, hwParams, C.snd_pcm_format_t(format)) 107 | if ret < 0 { 108 | return createError("could not set format params", ret) 109 | } 110 | ret = C.snd_pcm_hw_params_set_channels(d.h, hwParams, C.uint(channels)) 111 | if ret < 0 { 112 | return createError("could not set channels params", ret) 113 | } 114 | ret = C.snd_pcm_hw_params_set_rate(d.h, hwParams, C.uint(rate), 0) 115 | if ret < 0 { 116 | return createError("could not set rate params", ret) 117 | } 118 | var bufferSize = C.snd_pcm_uframes_t(bufferParams.BufferFrames) 119 | if bufferParams.BufferFrames == 0 { 120 | // Default buffer size: max buffer size 121 | ret = C.snd_pcm_hw_params_get_buffer_size_max(hwParams, &bufferSize) 122 | if ret < 0 { 123 | return createError("could not get buffer size", ret) 124 | } 125 | } 126 | ret = C.snd_pcm_hw_params_set_buffer_size_near(d.h, hwParams, &bufferSize) 127 | if ret < 0 { 128 | return createError("could not set buffer size", ret) 129 | } 130 | // Default period size: 1/8 of a second 131 | var periodFrames = C.snd_pcm_uframes_t(rate / 8) 132 | if bufferParams.PeriodFrames > 0 { 133 | periodFrames = C.snd_pcm_uframes_t(bufferParams.PeriodFrames) 134 | } else if bufferParams.Periods > 0 { 135 | periodFrames = C.snd_pcm_uframes_t(int(bufferSize) / bufferParams.Periods) 136 | } 137 | ret = C.snd_pcm_hw_params_set_period_size_near(d.h, hwParams, &periodFrames, nil) 138 | if ret < 0 { 139 | return createError("could not set period size", ret) 140 | } 141 | var periods = C.uint(0) 142 | ret = C.snd_pcm_hw_params_get_periods(hwParams, &periods, nil) 143 | if ret < 0 { 144 | return createError("could not get periods", ret) 145 | } 146 | ret = C.snd_pcm_hw_params(d.h, hwParams) 147 | if ret < 0 { 148 | return createError("could not set hw params", ret) 149 | } 150 | d.frames = int(periodFrames) 151 | d.Channels = channels 152 | d.Format = format 153 | d.Rate = rate 154 | d.BufferParams.BufferFrames = int(bufferSize) 155 | d.BufferParams.PeriodFrames = int(periodFrames) 156 | d.BufferParams.Periods = int(periods) 157 | return 158 | } 159 | 160 | // Close closes a device and frees the resources associated with it. 161 | func (d *device) Close() { 162 | if d.h != nil { 163 | C.snd_pcm_drain(d.h) 164 | C.snd_pcm_close(d.h) 165 | d.h = nil 166 | } 167 | if d.readerThread != nil { 168 | C.reader_thread_stop(d.readerThread) 169 | d.readerThread = nil 170 | } 171 | runtime.SetFinalizer(d, nil) 172 | } 173 | 174 | func (d device) formatSampleSize() (s int) { 175 | switch d.Format { 176 | case FormatS8, FormatU8: 177 | return 1 178 | case FormatS16LE, FormatS16BE, FormatU16LE, FormatU16BE: 179 | return 2 180 | case FormatS24LE, FormatS24BE, FormatU24LE, FormatU24BE, FormatS32LE, FormatS32BE, FormatU32LE, FormatU32BE, FormatFloatLE, FormatFloatBE: 181 | return 4 182 | case FormatFloat64LE, FormatFloat64BE: 183 | return 8 184 | } 185 | panic("unsupported format") 186 | } 187 | 188 | // CaptureDevice is an ALSA device configured to record audio. 189 | type CaptureDevice struct { 190 | device 191 | } 192 | 193 | // NewCaptureDevice creates a new CaptureDevice object. 194 | func NewCaptureDevice(deviceName string, channels int, format Format, rate int, bufferParams BufferParams) (c *CaptureDevice, err error) { 195 | c = new(CaptureDevice) 196 | err = c.createDevice(deviceName, channels, format, rate, false, bufferParams) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return c, nil 201 | } 202 | 203 | func (c *CaptureDevice) StartReadThread() error { 204 | if c.readerThread != nil { 205 | return errors.New("Reader thread already running") 206 | } 207 | periodBytes := C.int(c.formatSampleSize() * c.Channels * c.BufferParams.PeriodFrames) 208 | // Alocate a 1 second buffer 209 | nbuf := C.int(c.Rate / c.BufferParams.PeriodFrames) 210 | c.readerThread = C.reader_thread_start(c.h, periodBytes, C.int(c.BufferParams.PeriodFrames), nbuf) 211 | if c.readerThread == nil { 212 | return fmt.Errorf("C.reader_thread_start: %s", C.GoString(C.reader_thread_error)) 213 | } 214 | return nil 215 | } 216 | 217 | // Read reads samples into a buffer and returns the amount read. 218 | func (c *CaptureDevice) Read(buffer interface{}) (samples int, err error) { 219 | bufferType := reflect.TypeOf(buffer) 220 | if !(bufferType.Kind() == reflect.Array || 221 | bufferType.Kind() == reflect.Slice) { 222 | return 0, errors.New("Read requires an array type") 223 | } 224 | 225 | sizeError := errors.New("Read requires a matching sample size") 226 | switch bufferType.Elem().Kind() { 227 | case reflect.Int8: 228 | if c.formatSampleSize() != 1 { 229 | return 0, sizeError 230 | } 231 | case reflect.Int16: 232 | if c.formatSampleSize() != 2 { 233 | return 0, sizeError 234 | } 235 | case reflect.Int32, reflect.Float32: 236 | if c.formatSampleSize() != 4 { 237 | return 0, sizeError 238 | } 239 | case reflect.Float64: 240 | if c.formatSampleSize() != 8 { 241 | return 0, sizeError 242 | } 243 | default: 244 | return 0, errors.New("Read does not support this format") 245 | } 246 | 247 | val := reflect.ValueOf(buffer) 248 | length := val.Len() 249 | sliceData := val.Slice(0, length) 250 | 251 | frames := length / c.Channels 252 | bufPtr := unsafe.Pointer(sliceData.Index(0).Addr().Pointer()) 253 | 254 | if c.readerThread != nil { 255 | if frames != c.BufferParams.PeriodFrames { 256 | return 0, errors.New("buffer size must match period") 257 | } 258 | rc := C.reader_thread_poll(c.readerThread, bufPtr) 259 | if rc == 1 { 260 | return 0, ErrOverrun 261 | } else if rc != 0 { 262 | return 0, fmt.Errorf("read error: %s", C.GoString(C.reader_thread_error)) 263 | } 264 | samples = frames * c.Channels 265 | } else { 266 | ret := C.snd_pcm_readi(c.h, bufPtr, C.snd_pcm_uframes_t(frames)) 267 | 268 | if ret == -C.EPIPE { 269 | C.snd_pcm_prepare(c.h) 270 | return 0, ErrOverrun 271 | } else if ret < 0 { 272 | return 0, createError("read error", C.int(ret)) 273 | } 274 | samples = int(ret) * c.Channels 275 | } 276 | return 277 | } 278 | 279 | // PlaybackDevice is an ALSA device configured to playback audio. 280 | type PlaybackDevice struct { 281 | device 282 | } 283 | 284 | // NewPlaybackDevice creates a new PlaybackDevice object. 285 | func NewPlaybackDevice(deviceName string, channels int, format Format, rate int, bufferParams BufferParams) (p *PlaybackDevice, err error) { 286 | p = new(PlaybackDevice) 287 | err = p.createDevice(deviceName, channels, format, rate, true, bufferParams) 288 | if err != nil { 289 | return nil, err 290 | } 291 | return p, nil 292 | } 293 | 294 | // Write writes a buffer of data to a playback device. 295 | func (p *PlaybackDevice) Write(buffer interface{}) (samples int, err error) { 296 | bufferType := reflect.TypeOf(buffer) 297 | if !(bufferType.Kind() == reflect.Array || 298 | bufferType.Kind() == reflect.Slice) { 299 | return 0, errors.New("Write requires an array type") 300 | } 301 | 302 | sizeError := errors.New("Write requires a matching sample size") 303 | switch bufferType.Elem().Kind() { 304 | case reflect.Int8: 305 | if p.formatSampleSize() != 1 { 306 | return 0, sizeError 307 | } 308 | case reflect.Int16: 309 | if p.formatSampleSize() != 2 { 310 | return 0, sizeError 311 | } 312 | case reflect.Int32, reflect.Float32: 313 | if p.formatSampleSize() != 4 { 314 | return 0, sizeError 315 | } 316 | case reflect.Float64: 317 | if p.formatSampleSize() != 8 { 318 | return 0, sizeError 319 | } 320 | default: 321 | return 0, errors.New("Write does not support this format") 322 | } 323 | 324 | val := reflect.ValueOf(buffer) 325 | length := val.Len() 326 | sliceData := val.Slice(0, length) 327 | 328 | var frames = C.snd_pcm_uframes_t(length / p.Channels) 329 | bufPtr := unsafe.Pointer(sliceData.Index(0).Addr().Pointer()) 330 | 331 | ret := C.snd_pcm_writei(p.h, bufPtr, frames) 332 | if ret == -C.EPIPE { 333 | C.snd_pcm_prepare(p.h) 334 | return 0, ErrUnderrun 335 | } else if ret < 0 { 336 | return 0, createError("write error", C.int(ret)) 337 | } 338 | samples = int(ret) * p.Channels 339 | return 340 | } 341 | -------------------------------------------------------------------------------- /alsa_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Cocoon Labs Ltd. 2 | // 3 | // See LICENSE file for terms and conditions. 4 | 5 | package alsa 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/cocoonlife/testify/assert" 11 | ) 12 | 13 | func TestCapture(t *testing.T) { 14 | a := assert.New(t) 15 | 16 | c, err := NewCaptureDevice("nonexistent", 1, FormatS16LE, 44100, 17 | BufferParams{}) 18 | 19 | a.Equal(c, (*CaptureDevice)(nil), "capture device is nil") 20 | a.Error(err, "no device error") 21 | 22 | c, err = NewCaptureDevice("null", 1, FormatS32LE, 0, BufferParams{}) 23 | 24 | a.Equal(c, (*CaptureDevice)(nil), "capture device is nil") 25 | a.Error(err, "bad rate error") 26 | 27 | c, err = NewCaptureDevice("null", 1, FormatS32LE, 44100, BufferParams{}) 28 | 29 | a.NoError(err, "created capture device") 30 | 31 | b1 := make([]int8, 100) 32 | samples, err := c.Read(b1) 33 | 34 | a.Error(err, "wrong type error") 35 | a.Equal(samples, 0, "no samples read") 36 | 37 | b2 := make([]int16, 200) 38 | samples, err = c.Read(b2) 39 | 40 | a.Error(err, "wrong type error") 41 | a.Equal(samples, 0, "no samples read") 42 | 43 | b3 := make([]float64, 50) 44 | samples, err = c.Read(b3) 45 | 46 | a.Error(err, "wrong type error") 47 | a.Equal(samples, 0, "no samples read") 48 | 49 | b4 := make([]int32, 200) 50 | samples, err = c.Read(b4) 51 | 52 | a.NoError(err, "read samples ok") 53 | a.Equal(len(b2), samples, "correct number of samples read") 54 | 55 | c.Close() 56 | } 57 | 58 | func TestPlayback(t *testing.T) { 59 | a := assert.New(t) 60 | 61 | p, err := NewPlaybackDevice("nonexistent", 1, FormatS16LE, 44100, 62 | BufferParams{}) 63 | 64 | a.Equal(p, (*PlaybackDevice)(nil), "playback device is nil") 65 | a.Error(err, "no device error") 66 | 67 | p, err = NewPlaybackDevice("null", 0, FormatS32LE, 44100, 68 | BufferParams{}) 69 | 70 | a.Equal(p, (*PlaybackDevice)(nil), "playback device is nil") 71 | a.Error(err, "bad channels error") 72 | 73 | p, err = NewPlaybackDevice("null", 1, FormatS32LE, 44100, 74 | BufferParams{}) 75 | 76 | a.NoError(err, "created playback device") 77 | 78 | b1 := make([]int8, 100) 79 | frames, err := p.Write(b1) 80 | 81 | a.Error(err, "wrong type error") 82 | a.Equal(frames, 0, "no frames written") 83 | 84 | b2 := make([]int16, 100) 85 | frames, err = p.Write(b2) 86 | 87 | a.Error(err, "wrong type error") 88 | a.Equal(frames, 0, "no frames written") 89 | 90 | b3 := make([]float64, 100) 91 | frames, err = p.Write(b3) 92 | 93 | a.Error(err, "wrong type error") 94 | a.Equal(frames, 0, "no frames written") 95 | 96 | b4 := make([]int32, 100) 97 | frames, err = p.Write(b4) 98 | 99 | a.NoError(err, "buffer written ok") 100 | a.Equal(frames, 100, "100 frames written") 101 | 102 | p.Close() 103 | } 104 | -------------------------------------------------------------------------------- /reader_thread.c: -------------------------------------------------------------------------------- 1 | // In a process with a mixture of IO and CPU bound goroutines 2 | // (e.g. reading from alsa while compressing a jpeg snapshot) the IO bound 3 | // thread can experience very high latencies as it gets stuck behind the cpu 4 | // bound tasks. On a fully loaded machine this can exceed the maximum 5 | // buffer size supported by our alsa interfaces (~250ms). 6 | // The golang runtime/scheduler does not provide any mechanism to avoid this, 7 | // with the suggested solution being "install more/faster cpus" 8 | // 9 | // Workaround this by creating a high proirity C thread to read into a larger 10 | // buffer. The end-end latency may still be relatively poor with large amounts 11 | // of jitter, but at least we won't be dropping audio. 12 | // For simplicitly this buffer uses a fixed block size equal to the 13 | // period of the underlying alsa device 14 | // 15 | // In theory this could probably be lockless. However that is hard. 16 | // Hopefully the thunk down to C code before taking the lock in _poll 17 | // is sufficient to isolate us from the terrible golang scheduler latency 18 | 19 | // for pthread_setname_np 20 | #define _GNU_SOURCE 21 | #include 22 | #include 23 | 24 | #include "reader_thread.h" 25 | 26 | struct reader_thread_state_s { 27 | pthread_mutex_t mu; 28 | pthread_cond_t cond; 29 | snd_pcm_t *h; 30 | pthread_t tid; 31 | char *buf; 32 | int head_offset; 33 | int tail_offset; 34 | int period_frames; 35 | int period_bytes; 36 | int buf_len; 37 | bool stop; 38 | bool overrun; 39 | bool error; 40 | }; 41 | 42 | const char *reader_thread_error = "no error"; 43 | 44 | static void *reader_thread_loop(void *arg) 45 | { 46 | reader_thread_state *s = (reader_thread_state *)arg; 47 | // Enable realtime priority for this thread 48 | struct sched_param sched_param; 49 | sched_param.sched_priority = sched_get_priority_max(SCHED_FIFO); 50 | pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched_param); 51 | pthread_setname_np(pthread_self(), "goalsa_reader"); 52 | 53 | pthread_mutex_lock(&s->mu); 54 | while (!s->stop) { 55 | char *ptr = s->buf + s->head_offset; 56 | int next_offset = s->head_offset + s->period_bytes; 57 | // The last buffer may still be in use 58 | if (next_offset >= s->buf_len) { 59 | next_offset = 0; 60 | } 61 | // If the reader just can't keep up then we may see this anyway 62 | if (next_offset == s->tail_offset) { 63 | s->overrun = true; 64 | } 65 | pthread_mutex_unlock(&s->mu); 66 | int rc = snd_pcm_readi(s->h, ptr, s->period_frames); 67 | pthread_mutex_lock(&s->mu); 68 | if (rc == -EPIPE) { 69 | fprintf(stderr, "realtime alsa overrun"); 70 | s->overrun = true; 71 | snd_pcm_prepare(s->h); 72 | } else if (rc < 0) { 73 | reader_thread_error = "snd_pcm_readi"; 74 | s->error = true; 75 | s->stop = true; 76 | } else if (s->overrun) { 77 | // Drop data while waiting for _poll to clear overrun 78 | } else { 79 | if (s->head_offset == s->tail_offset) { 80 | pthread_cond_signal(&s->cond); 81 | } 82 | s->head_offset = next_offset; 83 | } 84 | } 85 | pthread_mutex_unlock(&s->mu); 86 | 87 | return NULL; 88 | } 89 | 90 | // Copy one block (period_bytes) of audio data into buf 91 | // Returns 0 on success, 1 on overrun, -1 on error. 92 | int reader_thread_poll(reader_thread_state *s, void *buf) 93 | { 94 | int ret = -1; 95 | void *src = NULL; 96 | if (!buf) { 97 | reader_thread_error = "null buffer"; 98 | return -1; 99 | } 100 | pthread_mutex_lock(&s->mu); 101 | while (s->head_offset == s->tail_offset && !(s->overrun || s->error)) { 102 | pthread_cond_wait(&s->cond, &s->mu); 103 | } 104 | if (s->error) { 105 | ret = -1; 106 | } else if (s->overrun) { 107 | ret = 1; 108 | s->overrun = false; 109 | s->tail_offset = s->head_offset; 110 | } else { // success 111 | ret = 0; 112 | src = s->buf + s->tail_offset; 113 | s->tail_offset += s->period_bytes; 114 | if (s->tail_offset >= s->buf_len) { 115 | s->tail_offset = 0; 116 | } 117 | } 118 | pthread_mutex_unlock(&s->mu); 119 | if (ret == 0) { 120 | memcpy(buf, src, s->period_bytes); 121 | } 122 | return ret; 123 | } 124 | 125 | reader_thread_state *reader_thread_start(snd_pcm_t *h, int bytes, int frames, int bufcount) 126 | { 127 | reader_thread_state *s; 128 | pthread_attr_t attr; 129 | 130 | s = malloc(sizeof(*s)); 131 | if (!s) { 132 | reader_thread_error = "malloc failed (state)"; 133 | return NULL; 134 | } 135 | memset(s, 0, sizeof(*s)); 136 | pthread_mutex_init(&s->mu, NULL); 137 | pthread_cond_init(&s->cond, NULL); 138 | 139 | s->h = h; 140 | s->period_frames = frames; 141 | s->period_bytes = bytes; 142 | s->buf_len = bytes * bufcount; 143 | s->buf = malloc(s->buf_len); 144 | if (!s->buf) { 145 | reader_thread_error = "malloc failed (buf)"; 146 | goto error; 147 | } 148 | 149 | int rc = pthread_create(&s->tid, NULL, reader_thread_loop, s); 150 | if (rc) { 151 | reader_thread_error = "pthread_create failed"; 152 | goto error; 153 | } 154 | 155 | return s; 156 | 157 | error: 158 | free(s->buf); 159 | free(s); 160 | return NULL; 161 | } 162 | 163 | void reader_thread_stop(reader_thread_state *s) 164 | { 165 | if (!s) { 166 | return; 167 | } 168 | 169 | s->stop = true; 170 | pthread_join(s->tid, NULL); 171 | free(s->buf); 172 | free(s); 173 | } 174 | -------------------------------------------------------------------------------- /reader_thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct reader_thread_state_s reader_thread_state; 6 | 7 | extern const char *reader_thread_error; 8 | 9 | reader_thread_state *reader_thread_start(snd_pcm_t *h, int bytes, int frames, int bufcount); 10 | void reader_thread_stop(reader_thread_state *s); 11 | int reader_thread_poll(reader_thread_state *s, void *buf); 12 | 13 | --------------------------------------------------------------------------------