also looking at this
feat(media): add bounded PCM capture, owned snapshots and A/V streaming
#917
11 files
+683
-0
| @@ -0,0 +1,77 @@ | ||
| 1 | /* Header-checked Linux ALSA input. No subprocess, FFmpeg or unbounded queue. */ | |
| 2 | #define _POSIX_C_SOURCE 200809L | |
| 3 | #include "native_capture.h" | |
| 4 | #include <alsa/asoundlib.h> | |
| 5 | #include <stdlib.h> | |
| 6 | #include <errno.h> | |
| 7 | #include <time.h> | |
| 8 | #include <limits.h> | |
| 9 | struct home_audio_capture { | |
| 10 | snd_pcm_t *pcm; | |
| 11 | snd_pcm_status_t *status; | |
| 12 | home_capture_config config; | |
| 13 | snd_pcm_uframes_t buffer_frames; | |
| 14 | uint64_t anchor,samples; | |
| 15 | int started,anchored; | |
| 16 | }; | |
| 17 | static uint64_t now_ns(clockid_t clock) { | |
| 18 | struct timespec ts;if(clock_gettime(clock,&ts)!=0)return 0; | |
| 19 | return (uint64_t)ts.tv_sec*1000000000u+(uint64_t)ts.tv_nsec; | |
| 20 | } | |
| 21 | void home_audio_close(home_audio_capture *s) { | |
| 22 | if(!s)return; | |
| 23 | if(s->pcm){snd_pcm_drop(s->pcm);snd_pcm_close(s->pcm);} | |
| 24 | if(s->status)snd_pcm_status_free(s->status); | |
| 25 | free(s); | |
| 26 | } | |
| 27 | int home_audio_open(const home_capture_config *cfg,const char *device,home_audio_capture **out) { | |
| 28 | if(!out)return -1; | |
| 29 | *out=NULL; | |
| 30 | if(!cfg || !device || !device[0] || cfg->sample_rate<8000 || cfg->sample_rate>192000 || cfg->channels<1 || cfg->channels>2 || cfg->block_frames<1 || cfg->block_frames>4096)return -1; | |
| 31 | home_audio_capture *s=calloc(1,sizeof(*s));if(!s)return -2;s->config=*cfg; | |
| 32 | snd_pcm_hw_params_t *hw=NULL;snd_pcm_sw_params_t *sw=NULL; | |
| 33 | if(snd_pcm_open(&s->pcm,device,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK)<0)goto fail; | |
| 34 | if(snd_pcm_hw_params_malloc(&hw)<0 || snd_pcm_sw_params_malloc(&sw)<0 || snd_pcm_status_malloc(&s->status)<0)goto fail; | |
| 35 | snd_pcm_uframes_t period=cfg->block_frames,buffer=cfg->sample_rate/5; | |
| 36 | if(snd_pcm_hw_params_any(s->pcm,hw)<0 || snd_pcm_hw_params_set_access(s->pcm,hw,SND_PCM_ACCESS_RW_INTERLEAVED)<0 || snd_pcm_hw_params_set_format(s->pcm,hw,SND_PCM_FORMAT_S16_LE)<0 || snd_pcm_hw_params_set_channels(s->pcm,hw,cfg->channels)<0 || snd_pcm_hw_params_set_rate(s->pcm,hw,cfg->sample_rate,0)<0 || snd_pcm_hw_params_set_period_size_near(s->pcm,hw,&period,NULL)<0 || snd_pcm_hw_params_set_buffer_size_near(s->pcm,hw,&buffer)<0 || snd_pcm_hw_params(s->pcm,hw)<0)goto fail; | |
| 37 | s->buffer_frames=buffer; | |
| 38 | if(snd_pcm_sw_params_current(s->pcm,sw)<0 || snd_pcm_sw_params_set_tstamp_mode(s->pcm,sw,SND_PCM_TSTAMP_ENABLE)<0 || snd_pcm_sw_params_set_tstamp_type(s->pcm,sw,SND_PCM_TSTAMP_TYPE_MONOTONIC)<0 || snd_pcm_sw_params(s->pcm,sw)<0 || snd_pcm_prepare(s->pcm)<0)goto fail; | |
| 39 | snd_pcm_hw_params_free(hw);snd_pcm_sw_params_free(sw);*out=s;return 0; | |
| 40 | fail: | |
| 41 | if(hw)snd_pcm_hw_params_free(hw); | |
| 42 | if(sw)snd_pcm_sw_params_free(sw); | |
| 43 | home_audio_close(s);return -3; | |
| 44 | } | |
| 45 | int home_audio_read(home_audio_capture *s,uint8_t *data,uint32_t capacity,uint32_t timeout_ms,uint32_t *frames,uint64_t *timestamp,int64_t *error_ns) { | |
| 46 | if(!s || !data || !frames || !timestamp || !error_ns || capacity<s->config.block_frames || timeout_ms>1000)return -1; | |
| 47 | *frames=0;*timestamp=0;*error_ns=0; | |
| 48 | if(!s->started){if(snd_pcm_start(s->pcm)<0)return -3;s->started=1;} | |
| 49 | const uint64_t deadline=now_ns(CLOCK_MONOTONIC)+(uint64_t)timeout_ms*1000000; | |
| 50 | snd_pcm_sframes_t n; | |
| 51 | for(;;) { | |
| 52 | n=snd_pcm_readi(s->pcm,data,s->config.block_frames); | |
| 53 | if(n>0)break; | |
| 54 | if(n<0 && n!=-EAGAIN && n!=-EINTR)return -3; | |
| 55 | uint64_t now=now_ns(CLOCK_MONOTONIC);if(now>=deadline)return 1; | |
| 56 | int wait=(int)((deadline-now+999999)/1000000); | |
| 57 | int rc=snd_pcm_wait(s->pcm,wait);if(rc<0 && rc!=-EINTR)return -3; | |
| 58 | if(rc==0)return 1; | |
| 59 | } | |
| 60 | if(snd_pcm_status(s->pcm,s->status)<0 || snd_pcm_status_get_state(s->status)!=SND_PCM_STATE_RUNNING)return -3; | |
| 61 | snd_htimestamp_t stamp;snd_pcm_status_get_htstamp(s->status,&stamp); | |
| 62 | const uint64_t mono=now_ns(CLOCK_MONOTONIC),boot=now_ns(CLOCK_BOOTTIME); | |
| 63 | const snd_pcm_uframes_t available=snd_pcm_status_get_avail(s->status); | |
| 64 | if(!mono || boot<mono || available>s->buffer_frames)return -3; | |
| 65 | const uint64_t status_ns=(uint64_t)stamp.tv_sec*1000000000u+(uint64_t)stamp.tv_nsec; | |
| 66 | const uint64_t backlog=((uint64_t)available+(uint64_t)n)*1000000000u/s->config.sample_rate; | |
| 67 | if(status_ns<backlog)return -3; | |
| 68 | const uint64_t measured=status_ns-backlog+(boot-mono); | |
| 69 | if(!s->anchored){s->anchor=measured;s->anchored=1;} | |
| 70 | __uint128_t value=(__uint128_t)s->anchor+(__uint128_t)s->samples*1000000000u/s->config.sample_rate; | |
| 71 | if(value>UINT64_MAX || s->samples>UINT64_MAX-(uint64_t)n)return -3; | |
| 72 | const uint64_t nominal=(uint64_t)value; | |
| 73 | uint64_t delta=measured>nominal ? measured-nominal : nominal-measured; | |
| 74 | if(delta>100000000u)return -3; | |
| 75 | *error_ns=measured>=nominal ? (int64_t)delta : -(int64_t)delta; | |
| 76 | *timestamp=nominal;*frames=(uint32_t)n;s->samples+=(uint64_t)n;return 0; | |
| 77 | } | |