From 54f8f1418282d0fbdf806bdaa357206c16f0927d Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sun, 18 Jan 2026 16:26:21 +0100 Subject: [PATCH 01/13] libs: add flac recording --- libsl/CMakeLists.txt | 4 +- libsl/include/studiolink.h | 18 ++++ libsl/src/flac.c | 164 +++++++++++++++++++++++++++++ libsl/src/http/server.c | 10 ++ libsl/src/meters.c | 2 +- libsl/src/record.c | 207 +++++++++++++++++++++++++++++++++++++ libsl/src/ws.c | 2 +- 7 files changed, 404 insertions(+), 3 deletions(-) create mode 100644 libsl/src/flac.c create mode 100644 libsl/src/record.c diff --git a/libsl/CMakeLists.txt b/libsl/CMakeLists.txt index 5c5c03d..dbac49d 100644 --- a/libsl/CMakeLists.txt +++ b/libsl/CMakeLists.txt @@ -22,10 +22,12 @@ set(SRCS src/audio.c src/conf.c src/db.c + src/flac.c src/http/client.c src/http/server.c src/main.c src/meters.c + src/record.c src/tracks.c src/ws.c ) @@ -83,7 +85,7 @@ LIST(APPEND COMPILED_RESOURCES ${OUTPUT_FILE}) # # Main target object # -set(LINKLIBS baresip re ${LMDB_LIBRARIES} ${OPENH264_LIBRARIES} stdc++) +set(LINKLIBS baresip re FLAC ${LMDB_LIBRARIES} stdc++) if(WIN32) list(APPEND LINKLIBS winmm setupapi) diff --git a/libsl/include/studiolink.h b/libsl/include/studiolink.h index bb67cae..5bc077e 100644 --- a/libsl/include/studiolink.h +++ b/libsl/include/studiolink.h @@ -220,6 +220,24 @@ int sl_account_close(void); struct ua *sl_account_ua(void); +/****************************************************************************** + * record.c + */ +uint64_t sl_record_msecs(void); +void sl_record_toggle(const char *folder); +int sl_record_start(const char *folder); +void sl_record(struct auframe *af); +int sl_record_close(void); + + +/****************************************************************************** + * flac.c + */ +struct flac; +int sl_flac_init(struct flac **flacp, struct auframe *af, char *file); +int sl_flac_record(struct flac *flac, struct auframe *af, uint64_t offset); + + #ifdef __cplusplus } #endif diff --git a/libsl/src/flac.c b/libsl/src/flac.c new file mode 100644 index 0000000..e0510c0 --- /dev/null +++ b/libsl/src/flac.c @@ -0,0 +1,164 @@ +#include +#include +#include + +#include +#include +#include +#include + +struct flac { + FLAC__StreamEncoder *enc; + FLAC__StreamMetadata *m[2]; + FLAC__int32 *pcm; +}; + + +static void flac_destruct(void *arg) +{ + struct flac *flac = arg; + + mem_deref(flac->pcm); + FLAC__stream_encoder_finish(flac->enc); + FLAC__stream_encoder_delete(flac->enc); + FLAC__metadata_object_delete(flac->m[0]); + FLAC__metadata_object_delete(flac->m[1]); +} + + +int sl_flac_init(struct flac **flacp, struct auframe *af, char *file) +{ + + struct flac *flac; + FLAC__bool ret; + FLAC__StreamEncoderInitStatus init; + FLAC__StreamMetadata_VorbisComment_Entry entry; + + + if (!flacp || !af || !file) + return EINVAL; + + flac = mem_zalloc(sizeof(struct flac), flac_destruct); + if (!flac) + return ENOMEM; + + flac->pcm = mem_zalloc(af->sampc * sizeof(FLAC__int32), NULL); + + + flac->enc = FLAC__stream_encoder_new(); + if (!flac->enc) + return ENOMEM; + + ret = FLAC__stream_encoder_set_verify(flac->enc, true); + ret &= FLAC__stream_encoder_set_compression_level(flac->enc, 5); + ret &= FLAC__stream_encoder_set_channels(flac->enc, af->ch); + ret &= FLAC__stream_encoder_set_bits_per_sample(flac->enc, 16); + ret &= FLAC__stream_encoder_set_sample_rate(flac->enc, af->srate); + ret &= FLAC__stream_encoder_set_total_samples_estimate(flac->enc, 0); + + if (!ret) { + warning("record: FLAC__stream_encoder_set\n"); + return EINVAL; + } + + /* METADATA */ + flac->m[0] = + FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + flac->m[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + + ret = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( + &entry, "ENCODED_BY", "STUDIO LINK MIX"); + + ret &= FLAC__metadata_object_vorbiscomment_append_comment( + flac->m[0], entry, /*copy=*/false); + + if (!ret) { + warning("record: FLAC METADATA ERROR: out of memory or tag " + "error\n"); + return ENOMEM; + } + + flac->m[1]->length = 1234; /* padding length */ + + ret = FLAC__stream_encoder_set_metadata(flac->enc, flac->m, 2); + + if (!ret) { + warning("record: FLAC__stream_encoder_set_metadata\n"); + return ENOMEM; + } + + init = FLAC__stream_encoder_init_file(flac->enc, file, NULL, NULL); + + if (init != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + warning("record: FLAC ERROR: initializing encoder: %s\n", + FLAC__StreamEncoderInitStatusString[init]); + return ENOMEM; + } + + *flacp = flac; + + return 0; +} + + +int sl_flac_record(struct flac *flac, struct auframe *af, uint64_t offset) +{ + FLAC__StreamEncoderState state; + FLAC__bool ret; + + if (!flac || !af || !af->ch) + return EINVAL; + + if (offset > 24 * 3600 * 1000) { + warning("flac_record: ignoring high >24h offset (%llu)\n", + offset); + offset = 0; + } + + if (offset < 40 /*ms*/) + offset = 0; + + if (offset) { + info("flac_record: offset %llu id %u\n", offset, af->id); + memset(flac->pcm, 0, af->sampc * sizeof(FLAC__int32)); + uint64_t offsampc = af->srate * af->ch * offset / 1000; + + while (offsampc) { + ret = FLAC__stream_encoder_process_interleaved( + flac->enc, flac->pcm, + (uint32_t)af->sampc / af->ch); + if (!ret) + goto err; + + if (offsampc >= af->sampc) + offsampc -= af->sampc; + else { + ret = FLAC__stream_encoder_process_interleaved( + flac->enc, flac->pcm, + (uint32_t)offsampc / af->ch); + if (!ret) + goto err; + offsampc = 0; + } + } + } + + int16_t *sampv = (int16_t *)af->sampv; + + for (size_t i = 0; i < af->sampc; i++) { + flac->pcm[i] = sampv[i]; + } + + ret = FLAC__stream_encoder_process_interleaved( + flac->enc, flac->pcm, (uint32_t)af->sampc / af->ch); + if (ret) + return 0; + + +err: + state = FLAC__stream_encoder_get_state(flac->enc); + warning("record: FLAC ENCODE ERROR: %s\n", + FLAC__StreamEncoderStateString[state]); + + return EBADFD; +} diff --git a/libsl/src/http/server.c b/libsl/src/http/server.c index a00d2ee..10ed0de 100644 --- a/libsl/src/http/server.c +++ b/libsl/src/http/server.c @@ -344,6 +344,16 @@ static void http_req_handler(struct http_conn *conn, } + if (0 == pl_strcasecmp(&msg->path, "/api/v1/record") && + 0 == pl_strcasecmp(&msg->met, "POST")) { + + sl_record_toggle("/tmp"); + http_sreply(conn, 200, "OK", "text/html", "", 0); + + goto out; + } + + #ifndef RELEASE /* Default return OPTIONS - needed on dev for preflight CORS Check * @TODO: add release test */ diff --git a/libsl/src/meters.c b/libsl/src/meters.c index c870c5f..1dc9067 100644 --- a/libsl/src/meters.c +++ b/libsl/src/meters.c @@ -38,7 +38,7 @@ static void write_ws(void) p[0] = '\0'; /* Record time */ - re_snprintf(one_peak, sizeof(one_peak), "0 0 "); + re_snprintf(one_peak, sizeof(one_peak), "%llu ", sl_record_msecs()); strcat((char *)p, one_peak); for (i = 0; i < MAX_METERS; i++) { diff --git a/libsl/src/record.c b/libsl/src/record.c new file mode 100644 index 0000000..f7e7929 --- /dev/null +++ b/libsl/src/record.c @@ -0,0 +1,207 @@ +/** + * @file record.c generic recording + * + * Copyright (C) 2026 Sebastian Reimers + */ + +#include +#include +#include +#include +#include + + +static struct { + struct list tracks; + RE_ATOMIC bool run; + thrd_t thread; + struct aubuf *ab; + char *folder; + RE_ATOMIC uint64_t start_time; +} record = {.tracks = LIST_INIT, .run = false}; + +struct record_entry { + struct le le; + struct mbuf *mb; + size_t size; +}; + +enum { + SRATE = 48000, + CH = 1, + PTIME = 20, +}; + + +uint64_t sl_record_msecs(void) +{ + if (!re_atomic_rlx(&record.start_time)) + return 0; + + return tmr_jiffies() - re_atomic_rlx(&record.start_time); +} + + +struct track { + struct le le; + uint16_t id; + char file[512]; + uint64_t last; + struct flac *flac; +}; + + +static void track_destruct(void *arg) +{ + struct track *track = arg; + + list_unlink(&track->le); + mem_deref(track->flac); +} + + +static int record_track(struct auframe *af) +{ + struct le *le; + struct track *track = NULL; + uint64_t offset; + int err; + + LIST_FOREACH(&record.tracks, le) + { + struct track *t = le->data; + + if (t->id == af->id) { + track = t; + break; + } + } + + if (!track) { + track = mem_zalloc(sizeof(struct track), track_destruct); + if (!track) + return ENOMEM; + + track->id = af->id; + track->last = re_atomic_rlx(&record.start_time); + + /* TODO: add user->name */ + re_snprintf(track->file, sizeof(track->file), + "%s/audio_id%u.flac", record.folder, track->id); + + err = sl_flac_init(&track->flac, af, track->file); + if (err) { + mem_deref(track); + return err; + } + + list_append(&record.tracks, &track->le, track); + } + + offset = af->timestamp - track->last; + track->last = af->timestamp; + + sl_flac_record(track->flac, af, offset); + + return 0; +} + + +static int record_thread(void *arg) +{ + struct auframe af; + int err; + (void)arg; + + int16_t *sampv; + size_t sampc = SRATE * CH * PTIME / 1000; + + sampv = mem_zalloc(sampc * sizeof(int16_t), NULL); + if (!sampv) + return ENOMEM; + + auframe_init(&af, AUFMT_S16LE, sampv, sampc, SRATE, CH); + + re_atomic_rlx_set(&record.start_time, tmr_jiffies()); + + while (re_atomic_rlx(&record.run)) { + sys_msleep(4); + while (aubuf_cur_size(record.ab) > sampc) { + aubuf_read_auframe(record.ab, &af); + err = record_track(&af); + if (err) + goto out; + } + } + +out: + re_atomic_rlx_set(&record.start_time, 0); + mem_deref(sampv); + + return 0; +} + + +void sl_record(struct auframe *af) +{ + if (!re_atomic_rlx(&record.run) || !af->id) + return; + + af->timestamp = tmr_jiffies(); + aubuf_write_auframe(record.ab, af); +} + + +void sl_record_toggle(const char *folder) +{ + if (!re_atomic_rlx(&record.run)) + sl_record_start(folder); + else + sl_record_close(); + +} + + +int sl_record_start(const char *folder) +{ + int err; + + if (!folder) + return EINVAL; + + if (re_atomic_rlx(&record.run)) + return EALREADY; + + re_atomic_rlx_set(&record.start_time, 0); + str_dup(&record.folder, folder); + + err = aubuf_alloc(&record.ab, 0, 0); + if (err) { + return err; + } + + re_atomic_rlx_set(&record.run, true); + info("aumix: record started\n"); + + thread_create_name(&record.thread, "sl_record", record_thread, NULL); + + return 0; +} + + +int sl_record_close(void) +{ + if (!re_atomic_rlx(&record.run)) + return EINVAL; + + re_atomic_rlx_set(&record.run, false); + info("aumix: record close\n"); + thrd_join(record.thread, NULL); + + mem_deref(record.ab); + + record.folder = mem_deref(record.folder); + list_flush(&record.tracks); + + return 0; +} diff --git a/libsl/src/ws.c b/libsl/src/ws.c index 2940dcb..d189833 100644 --- a/libsl/src/ws.c +++ b/libsl/src/ws.c @@ -138,7 +138,7 @@ int sl_ws_init(void) return err; } - err = mutex_alloc(&wsl_lock); + err = mutex_alloc_tp(&wsl_lock, mtx_recursive); if (err) { warning("sl_ws_init: mutex_alloc failed\n"); mem_deref(ws); From a72853d76df8f6ed3f3a8edacd0af0ffd13d4432 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sun, 18 Jan 2026 16:26:35 +0100 Subject: [PATCH 02/13] webui: add recording --- webui/src/api.ts | 4 +++ webui/src/components/BottomActions.vue | 16 +++++----- webui/src/states/meters.ts | 41 +++++++++++++++++++++----- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/webui/src/api.ts b/webui/src/api.ts index 1dc2fe5..10330a8 100644 --- a/webui/src/api.ts +++ b/webui/src/api.ts @@ -32,6 +32,10 @@ export default { api_request('POST', '/track/mute?track=' + String(id), null); }, + record() { + api_request('POST', '/record', null); + }, + audio_device(track: number, mic: number, speaker: number) { api_request('PUT', '/audio/device?track=' + String(track), String(mic) + ";" + String(speaker)); }, diff --git a/webui/src/components/BottomActions.vue b/webui/src/components/BottomActions.vue index 4c52872..9ffd880 100644 --- a/webui/src/components/BottomActions.vue +++ b/webui/src/components/BottomActions.vue @@ -5,21 +5,19 @@ @click="api.track_mute(1)"> Mute - diff --git a/webui/src/states/meters.ts b/webui/src/states/meters.ts index b6899fc..45533ff 100644 --- a/webui/src/states/meters.ts +++ b/webui/src/states/meters.ts @@ -1,9 +1,4 @@ -interface Meters { - socket?: WebSocket - websocket(ws_host: string): void - /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ - update(message: any): void -} +import { Ref, ref } from 'vue' function iec_scale(db: number) { let def = 0.0; @@ -38,10 +33,26 @@ function update_meters(peak: string, index: number) { document.getElementById("level" + (index - 2))?.style.setProperty("--my-level", val + "% 0 0 0") } +interface Meters { + socket?: WebSocket + record_timer: Ref + record: Ref + websocket(ws_host: string): void + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + update(message: any): void +} + +function pad(num: number, size: number) { + const s = '0000' + num + return s.substring(s.length - size) +} + export const meters: Meters = { + record_timer: ref("0:00:00"), + record: ref(false), websocket(ws_host): void { this.socket = new WebSocket('ws://' + ws_host + '/ws/v1/meters') - this.socket.onerror = function() { + this.socket.onerror = function () { console.log('Websocket error') } this.socket.onmessage = (message) => { @@ -50,6 +61,22 @@ export const meters: Meters = { }, update(message): void { const peaks = message.data.split(" ") + let time = parseInt(peaks.shift()) + if (time) { + this.record.value = true + time = time / 1000 + const h = Math.floor(time / (60 * 60)) + time = time % (60 * 60) + const m = Math.floor(time / 60) + time = time % 60 + const s = Math.floor(time) + + this.record_timer.value = pad(h, 1) + ':' + pad(m, 2) + ':' + pad(s, 2) + } + else { + this.record.value = false + } + peaks.forEach(update_meters) } } From 46d7e6ad27cb07c5726bf5531168519e8872aa4e Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sun, 18 Jan 2026 17:14:59 +0100 Subject: [PATCH 03/13] mk: add flac and upgrade opus --- Makefile | 17 ++++++++++++++++- versions.mk | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index db604cb..04fdcac 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,9 @@ lmdb: third_party/lmdb .PHONY: libvpx libvpx: third_party/libvpx +.PHONY: flac +flac: third_party/flac + .PHONY: cacert cacert: third_party/cacert.pem @@ -100,7 +103,19 @@ third_party_dir: .PHONY: third_party third_party: third_party_dir libvpx openssl opus samplerate portaudio lmdb \ - cacert + flac cacert + + +third_party/flac: + $(HIDE)cd third_party && \ + wget ${FLAC_MIRROR}/flac-${FLAC_VERSION}.tar.xz && \ + tar -xvf flac-${FLAC_VERSION}.tar.xz && \ + mv flac-${FLAC_VERSION} flac && cd flac && \ + $(_ARCH_CONFIGURE) \ + --disable-ogg --enable-static --disable-cpplibs && \ + make -j4 && \ + cp src/libFLAC/.libs/libFLAC.a ../lib/ && \ + cp -a include/FLAC ../include third_party/libvpx: $(HIDE)cd third_party && \ diff --git a/versions.mk b/versions.mk index a6bb413..f071c80 100644 --- a/versions.mk +++ b/versions.mk @@ -1,7 +1,9 @@ OPENSSL_VERSION := 3.6.0 OPENSSL_MIRROR := https://www.openssl.org/source -OPUS_VERSION := 1.6 +OPUS_VERSION := 1.6.1 OPUS_MIRROR := https://downloads.xiph.org/releases/opus +FLAC_VERSION := 1.5.0 +FLAC_MIRROR := https://ftp.osuosl.org/pub/xiph/releases/flac VPX_VERSION := v1.15.2 VPX_MIRROR := https://chromium.googlesource.com/webm/libvpx PORTAUDIO_VERSION := master From 9bb97d3a740804af4ec34e09c18e0b6223fa4b02 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sun, 18 Jan 2026 17:27:02 +0100 Subject: [PATCH 04/13] cmake: add FindFLAC --- cmake/FindFLAC.cmake | 9 +++++++++ libsl/CMakeLists.txt | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 cmake/FindFLAC.cmake diff --git a/cmake/FindFLAC.cmake b/cmake/FindFLAC.cmake new file mode 100644 index 0000000..c465f89 --- /dev/null +++ b/cmake/FindFLAC.cmake @@ -0,0 +1,9 @@ +FIND_PATH(FLAC_INCLUDE_DIR FLAC/metadata.h HINTS include) + +FIND_LIBRARY(FLAC_LIBRARIES NAMES FLAC HINTS lib) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLAC DEFAULT_MSG FLAC_LIBRARIES FLAC_INCLUDE_DIR) + +# show the FLAC_INCLUDE_DIR and FLAC_LIBRARIES variables only in the advanced view +MARK_AS_ADVANCED(FLAC_INCLUDE_DIR FLAC_LIBRARIES) diff --git a/libsl/CMakeLists.txt b/libsl/CMakeLists.txt index dbac49d..6f351f9 100644 --- a/libsl/CMakeLists.txt +++ b/libsl/CMakeLists.txt @@ -11,6 +11,7 @@ project(sl LANGUAGES C) find_package(LMDB REQUIRED) +find_package(FLAC REQUIRED) ############################################################################## # @@ -85,7 +86,7 @@ LIST(APPEND COMPILED_RESOURCES ${OUTPUT_FILE}) # # Main target object # -set(LINKLIBS baresip re FLAC ${LMDB_LIBRARIES} stdc++) +set(LINKLIBS baresip re ${FLAC_LIBRARIES} ${LMDB_LIBRARIES} stdc++) if(WIN32) list(APPEND LINKLIBS winmm setupapi) From c0dc8954e50695c259b2e6cd4448bcc5949981a3 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sun, 18 Jan 2026 17:31:41 +0100 Subject: [PATCH 05/13] use EPROTO --- libsl/src/flac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsl/src/flac.c b/libsl/src/flac.c index e0510c0..9b8c637 100644 --- a/libsl/src/flac.c +++ b/libsl/src/flac.c @@ -160,5 +160,5 @@ int sl_flac_record(struct flac *flac, struct auframe *af, uint64_t offset) warning("record: FLAC ENCODE ERROR: %s\n", FLAC__StreamEncoderStateString[state]); - return EBADFD; + return EPROTO; } From d1a59075af9f06b7f27e0b03a618aca4452338e0 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Fri, 23 Jan 2026 14:20:12 +0100 Subject: [PATCH 06/13] some fixes --- libsl/src/audio.c | 8 +++++++- libsl/src/record.c | 13 +++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/libsl/src/audio.c b/libsl/src/audio.c index fc49aa7..b140654 100644 --- a/libsl/src/audio.c +++ b/libsl/src/audio.c @@ -456,7 +456,9 @@ static void driver_read_handler(struct auframe *af, void *arg) auconv_from_s16(AUFMT_FLOAT, sampv, af->sampv, af->sampc); sl_meter_process(0, sampv, af->sampc / CH); - aumix_source_put(a->mix_src, af->sampv, af->sampc); + af->id = 1; + + aumix_source_put_auframe(a->mix_src, af); } @@ -689,6 +691,10 @@ int sl_audio_init(void) } err = aumix_alloc(&aumix, SRATE, CH, PTIME); + if (err) + return err; + + aumix_recordh(aumix, sl_record); return err; } diff --git a/libsl/src/record.c b/libsl/src/record.c index f7e7929..2ae85b2 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -4,7 +4,6 @@ * Copyright (C) 2026 Sebastian Reimers */ -#include #include #include #include @@ -28,7 +27,7 @@ struct record_entry { enum { SRATE = 48000, - CH = 1, + CH = 2, PTIME = 20, }; @@ -85,16 +84,19 @@ static int record_track(struct auframe *af) track->id = af->id; track->last = re_atomic_rlx(&record.start_time); - /* TODO: add user->name */ + /* TODO: use track name */ re_snprintf(track->file, sizeof(track->file), "%s/audio_id%u.flac", record.folder, track->id); err = sl_flac_init(&track->flac, af, track->file); if (err) { + warning("sl_record: error flac_init (%m)\n", err); mem_deref(track); return err; } + info("sl_record: add track (%s)\n", track->file); + list_append(&record.tracks, &track->le, track); } @@ -158,7 +160,6 @@ void sl_record_toggle(const char *folder) sl_record_start(folder); else sl_record_close(); - } @@ -181,7 +182,7 @@ int sl_record_start(const char *folder) } re_atomic_rlx_set(&record.run, true); - info("aumix: record started\n"); + info("sl_record: started\n"); thread_create_name(&record.thread, "sl_record", record_thread, NULL); @@ -195,7 +196,7 @@ int sl_record_close(void) return EINVAL; re_atomic_rlx_set(&record.run, false); - info("aumix: record close\n"); + info("sl_record: close\n"); thrd_join(record.thread, NULL); mem_deref(record.ab); From 0b4b2b8992cf9b91556729c563be4af0ac3216d2 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Fri, 23 Jan 2026 15:47:39 +0100 Subject: [PATCH 07/13] wip --- libsl/include/studiolink.h | 24 +++++++++++++++++++++++- libsl/src/audio.c | 9 ++++++--- libsl/src/tracks.c | 38 +++++++------------------------------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/libsl/include/studiolink.h b/libsl/include/studiolink.h index 5bc077e..6df1ccb 100644 --- a/libsl/include/studiolink.h +++ b/libsl/include/studiolink.h @@ -147,7 +147,15 @@ void sl_ws_dummyh(const struct websock_hdr *hdr, struct mbuf *mb, void *arg); /****************************************************************************** * tracks.c */ -struct sl_track; +/* Local audio device track */ +struct sl_local { + struct slaudio *slaudio; +}; + +/* Remote audio call track */ +struct sl_remote { + struct call *call; +}; enum sl_track_type { SL_TRACK_REMOTE, SL_TRACK_LOCAL }; enum sl_track_status { SL_TRACK_INVALID = -1, @@ -160,6 +168,20 @@ enum sl_track_status { SL_TRACK_REMOTE_CALLING = 6, SL_TRACK_REMOTE_INCOMING = 7, }; +struct sl_track { + struct le le; + uint16_t id; + enum sl_track_type type; + char name[64]; + char error[128]; + enum sl_track_status status; + bool muted; + union + { + struct sl_local local; + struct sl_remote remote; + } u; +}; int sl_tracks_init(void); int sl_tracks_close(void); const struct list *sl_tracks(void); diff --git a/libsl/src/audio.c b/libsl/src/audio.c index b140654..18dbfd8 100644 --- a/libsl/src/audio.c +++ b/libsl/src/audio.c @@ -1,3 +1,4 @@ +#include #include enum { PTIME = 20, SRATE = 48000, CH = 2 }; @@ -24,7 +25,7 @@ struct amix { struct auplay_st *play; struct aumix_source *aumix_src; char *device; - uint16_t speaker_id; + struct sl_track *track; /* remote track */ }; struct ausrc_st { @@ -208,7 +209,7 @@ static void mix_readh(struct auframe *af, void *arg) return; amix->play->wh(af, amix->play->arg); - af->id = amix->speaker_id; + af->id = amix->track->id; #if 0 re_atomic_rlx_set(&amix->play->level, @@ -241,7 +242,9 @@ static int amix_alloc(struct amix **amixp, const char *device) goto out; } - struct pl *id = pl_alloc_str(device); + amix->track = sl_track_by_id(atoi(device)); + + struct pl *id = pl_alloc_str(amix->track->name); if (!id) { err = ENOMEM; goto out; diff --git a/libsl/src/tracks.c b/libsl/src/tracks.c index 53b12a1..89286ce 100644 --- a/libsl/src/tracks.c +++ b/libsl/src/tracks.c @@ -4,31 +4,6 @@ #define SL_MAX_TRACKS 16 -/* Local audio device track */ -struct local { - struct slaudio *slaudio; -}; - -/* Remote audio call track */ -struct remote { - struct call *call; -}; - -struct sl_track { - struct le le; - int id; - enum sl_track_type type; - char name[64]; - char error[128]; - enum sl_track_status status; - bool muted; - union - { - struct local local; - struct remote remote; - } u; -}; - static struct list tracks = LIST_INIT; /* TODO: refactor allow multiple local tracks */ @@ -264,13 +239,13 @@ int sl_track_dial(struct sl_track *track, struct pl *peer) track->status = SL_TRACK_REMOTE_CALLING; pl_strcpy(peer, track->name, sizeof(track->name)); - audio_set_devicename(call_audio(track->u.remote.call), track->name, - track->name); + char buf[ITOA_BUFSZ]; + char *id = str_itoa(track->id, buf, 10); + audio_set_devicename(call_audio(track->u.remote.call), id, id); out: - if (err) { + if (err) re_snprintf(track->error, sizeof(track->error), "%m", err); - } if (err == EINVAL) str_ncpy(track->error, "Invalid ID", sizeof(track->error)); @@ -354,8 +329,9 @@ static void call_incoming(struct call *call) track->u.remote.call = call; track->status = SL_TRACK_REMOTE_INCOMING; str_ncpy(track->name, call_peeruri(call), sizeof(track->name)); - - audio_set_devicename(call_audio(call), track->name, track->name); + char buf[ITOA_BUFSZ]; + char *id = str_itoa(track->id, buf, 10); + audio_set_devicename(call_audio(call), id, id); } From 3abace96cfde8facaca1b7db2057ecf42133eb1f Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 08:40:06 +0100 Subject: [PATCH 08/13] fix static flac compiling win32 --- libsl/src/flac.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libsl/src/flac.c b/libsl/src/flac.c index 9b8c637..41d82fd 100644 --- a/libsl/src/flac.c +++ b/libsl/src/flac.c @@ -1,3 +1,6 @@ +#ifdef WIN32 +#define FLAC__NO_DLL +#endif #include #include #include From 87b951731758ddcfeed3e63a7858c5f897ebcbed Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 09:42:44 +0100 Subject: [PATCH 09/13] improve folder handling --- libsl/include/studiolink.h | 4 +- libsl/src/http/server.c | 2 +- libsl/src/record.c | 91 ++++++++++++++++++++++++++++++++------ 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/libsl/include/studiolink.h b/libsl/include/studiolink.h index 6df1ccb..535446c 100644 --- a/libsl/include/studiolink.h +++ b/libsl/include/studiolink.h @@ -246,8 +246,8 @@ struct ua *sl_account_ua(void); * record.c */ uint64_t sl_record_msecs(void); -void sl_record_toggle(const char *folder); -int sl_record_start(const char *folder); +void sl_record_toggle(void); +int sl_record_start(void); void sl_record(struct auframe *af); int sl_record_close(void); diff --git a/libsl/src/http/server.c b/libsl/src/http/server.c index 10ed0de..5dbae47 100644 --- a/libsl/src/http/server.c +++ b/libsl/src/http/server.c @@ -347,7 +347,7 @@ static void http_req_handler(struct http_conn *conn, if (0 == pl_strcasecmp(&msg->path, "/api/v1/record") && 0 == pl_strcasecmp(&msg->met, "POST")) { - sl_record_toggle("/tmp"); + sl_record_toggle(); http_sreply(conn, 200, "OK", "text/html", "", 0); goto out; diff --git a/libsl/src/record.c b/libsl/src/record.c index 2ae85b2..6c785fd 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -3,7 +3,7 @@ * * Copyright (C) 2026 Sebastian Reimers */ - +#include #include #include #include @@ -15,7 +15,7 @@ static struct { RE_ATOMIC bool run; thrd_t thread; struct aubuf *ab; - char *folder; + char folder[256]; RE_ATOMIC uint64_t start_time; } record = {.tracks = LIST_INIT, .run = false}; @@ -84,9 +84,8 @@ static int record_track(struct auframe *af) track->id = af->id; track->last = re_atomic_rlx(&record.start_time); - /* TODO: use track name */ re_snprintf(track->file, sizeof(track->file), - "%s/audio_id%u.flac", record.folder, track->id); + "%s/track_%u.flac", record.folder, track->id); err = sl_flac_init(&track->flac, af, track->file); if (err) { @@ -154,33 +153,98 @@ void sl_record(struct auframe *af) } -void sl_record_toggle(const char *folder) +static void record_open_folder(void) +{ + char command[256] = {0}; + +#if defined(DARWIN) + re_snprintf(command, sizeof(command), "open %s", record.folder); +#elif defined(WIN32) + re_snprintf(command, sizeof(command), "explorer.exe %s", + record.folder); +#else + re_snprintf(command, sizeof(command), "xdg-open %s", record.folder); +#endif + system(command); +} + + +void sl_record_toggle(void) { if (!re_atomic_rlx(&record.run)) - sl_record_start(folder); + sl_record_start(); else sl_record_close(); } -int sl_record_start(const char *folder) +static int timestamp_print(struct re_printf *pf, const struct tm *tm) { - int err; + if (!tm) + return 0; - if (!folder) - return EINVAL; + return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d", + 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +static int folder_init(void) +{ + int err = 0; + char buf[256]; + char basefolder[256]; + +#ifdef WIN32 + char win32_path[MAX_PATH]; + + if (S_OK != + SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, win32_path)) { + return ENOENT; + } + str_ncpy(buf, win32_path, sizeof(buf)); +#else + err = fs_gethome(buf, sizeof(buf)); + if (err) + return err; +#endif + + (void)re_snprintf(basefolder, sizeof(basefolder), + "%s" DIR_SEP "studio-link", buf); + (void)fs_mkdir(basefolder, 0700); + + (void)re_snprintf(basefolder, sizeof(basefolder), + "%s" DIR_SEP "studio-link", buf); + + time_t tnow = time(0); + struct tm *tm = localtime(&tnow); + + (void)re_snprintf(record.folder, sizeof(record.folder), + "%s" DIR_SEP "%H", basefolder, timestamp_print, tm); + + return fs_mkdir(record.folder, 0700); +} + + +int sl_record_start(void) +{ + int err; if (re_atomic_rlx(&record.run)) return EALREADY; re_atomic_rlx_set(&record.start_time, 0); - str_dup(&record.folder, folder); - err = aubuf_alloc(&record.ab, 0, 0); + err = folder_init(); if (err) { + warning("sl_record_start: folder_init err %m\n", err); return err; } + err = aubuf_alloc(&record.ab, 0, 0); + if (err) + return err; + re_atomic_rlx_set(&record.run, true); info("sl_record: started\n"); @@ -201,8 +265,9 @@ int sl_record_close(void) mem_deref(record.ab); - record.folder = mem_deref(record.folder); list_flush(&record.tracks); + record_open_folder(); + return 0; } From e9e54b75d61c3e45199219c84a2819db6904a8aa Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 10:43:28 +0100 Subject: [PATCH 10/13] fix win32 folder handling --- libsl/src/record.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libsl/src/record.c b/libsl/src/record.c index 6c785fd..9c31cb5 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -3,6 +3,16 @@ * * Copyright (C) 2026 Sebastian Reimers */ +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include +#endif +#include #include #include #include @@ -191,7 +201,6 @@ static int timestamp_print(struct re_printf *pf, const struct tm *tm) static int folder_init(void) { - int err = 0; char buf[256]; char basefolder[256]; @@ -204,7 +213,7 @@ static int folder_init(void) } str_ncpy(buf, win32_path, sizeof(buf)); #else - err = fs_gethome(buf, sizeof(buf)); + int err = fs_gethome(buf, sizeof(buf)); if (err) return err; #endif From 7195e509064eb3059829caf83f9be3d3d71ddedc Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 10:56:14 +0100 Subject: [PATCH 11/13] fix localtime --- libsl/src/record.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/libsl/src/record.c b/libsl/src/record.c index 9c31cb5..8c6cd62 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -3,16 +3,6 @@ * * Copyright (C) 2026 Sebastian Reimers */ -#ifdef WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include -#include -#include -#endif -#include #include #include #include @@ -225,11 +215,15 @@ static int folder_init(void) (void)re_snprintf(basefolder, sizeof(basefolder), "%s" DIR_SEP "studio-link", buf); - time_t tnow = time(0); - struct tm *tm = localtime(&tnow); + struct timespec tspec; + struct tm tm; + + (void)clock_gettime(CLOCK_REALTIME, &tspec); + if (!localtime_r(&tspec.tv_sec, &tm)) + return EINVAL; (void)re_snprintf(record.folder, sizeof(record.folder), - "%s" DIR_SEP "%H", basefolder, timestamp_print, tm); + "%s" DIR_SEP "%H", basefolder, timestamp_print, &tm); return fs_mkdir(record.folder, 0700); } From 0042b65f6023fc649d8c3ddc7743704e3da0df75 Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 10:57:10 +0100 Subject: [PATCH 12/13] fixes --- libsl/src/record.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libsl/src/record.c b/libsl/src/record.c index 8c6cd62..df8bb83 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -3,6 +3,16 @@ * * Copyright (C) 2026 Sebastian Reimers */ +#ifdef WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include +#endif +#include #include #include #include From f7df04b16b7ac779a85f59c82701e3c10c85e51a Mon Sep 17 00:00:00 2001 From: Sebastian Reimers Date: Sat, 24 Jan 2026 11:01:42 +0100 Subject: [PATCH 13/13] fix mingw localtime_r --- libsl/src/record.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libsl/src/record.c b/libsl/src/record.c index df8bb83..20b34de 100644 --- a/libsl/src/record.c +++ b/libsl/src/record.c @@ -3,6 +3,10 @@ * * Copyright (C) 2026 Sebastian Reimers */ +#ifdef __MINGW32__ +#define _POSIX_C_SOURCE 200809L +#endif + #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN