ReviewOS

also looking at this

home-lang/home

feat(media): add bounded PCM capture, owned snapshots and A/V streaming

#917
Merged chris wants to merge feat/pcm-av-streaming into main
11 files +683 -0
packages/audio/src/io/native_capture.cadded+77-0
Changes to packages/audio/src/io/native_capture.c
@@ -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>
9struct 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};
17static 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}
21void 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}
27int 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;
40fail:
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}
45int 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}