]> git.alsa-project.org Git - tinycompress.git/commitdiff
utils: probes_demux: auto-close idle audio files and avoid overwrites
authorJyri Sarha <jyri.sarha@linux.intel.com>
Tue, 31 Mar 2026 15:05:52 +0000 (18:05 +0300)
committerJaroslav Kysela <perex@perex.cz>
Fri, 29 May 2026 12:04:53 +0000 (14:04 +0200)
Audio capture files (buffer_<id>.wav) are now automatically closed
after 200 ms of inactivity, so completed recordings can be accessed
while sofprobeclient is still running.

If the same probe point becomes active again, a new file is created
with an incrementing index (buffer_<id>-1.wav, buffer_<id>-2.wav, ...)
rather than overwriting the previous capture.  The index is chosen by
probing the filesystem, so existing files from earlier runs are never
clobbered either.

Buffer IDs in file names and log messages are printed in hexadecimal
(e.g. buffer_0x1a.wav) to match firmware conventions.

Closes: https://github.com/alsa-project/tinycompress/pull/35
Signed-off-by: Jyri Sarha <jyri.sarha@linux.intel.com>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
src/utils/probes_demux.c
src/utils/probes_demux.h
src/utils/sofprobeclient.c

index 7d51d3c50d6cc8ebc476cc6a65740d7521b6729e..b11f7029aa68e22298f21a610c6af95f78be09cd 100644 (file)
@@ -14,6 +14,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "probe_dma_frame.h"
 #define DATA_READ_LIMIT 4096   /**< Data limit for file read */
 #define FILES_LIMIT    32      /**< Maximum num of probe output files */
 #define FILE_PATH_LIMIT 128    /**< Path limit for probe output files */
+#define IDLE_TIMEOUT_MS 200    /**< Close idle audio files after this many ms */
 
 struct wave_files {
        FILE *fd;
        uint32_t buffer_id;
        uint32_t fmt;
        uint32_t size;
+       char path[FILE_PATH_LIMIT];
        struct wave header;
+       struct timespec last_write;
 };
 
 enum p_state {
@@ -86,10 +90,21 @@ bool is_audio_format(uint32_t format)
        return (format & PROBE_MASK_FMT_TYPE) != 0 && (format & PROBE_MASK_AUDIO_FMT) == 0;
 }
 
+static void make_buffer_path(char *path, uint32_t buffer_id,
+                            uint32_t file_index, bool audio)
+{
+       const char *ext = audio ? "wav" : "bin";
+
+       if (file_index == 0)
+               sprintf(path, "buffer_%#x.%s", buffer_id, ext);
+       else
+               sprintf(path, "buffer_%#x-%d.%s", buffer_id, file_index, ext);
+}
+
 int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format)
 {
        bool audio = is_audio_format(format);
-       char path[FILE_PATH_LIMIT];
+       uint32_t file_index;
        int i;
 
        i = get_buffer_file_free(p->files);
@@ -98,23 +113,29 @@ int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format)
                exit(0);
        }
 
-       sprintf(path, "buffer_%d.%s", buffer_id, audio ? "wav" : "bin");
+       /* Find a filename that does not collide with existing files */
+       for (file_index = 0; ; file_index++) {
+               make_buffer_path(p->files[i].path, buffer_id, file_index, audio);
+               if (access(p->files[i].path, F_OK) != 0)
+                       break;
+       }
 
-       fprintf(stderr, "%s:\t Creating file %s\n", APP_NAME, path);
+       fprintf(stderr, "%s:\t Creating file %s\n", APP_NAME, p->files[i].path);
 
        if (!audio && p->log_to_stdout) {
                p->files[i].fd = stdout;
        } else {
-               p->files[i].fd = fopen(path, "wb");
+               p->files[i].fd = fopen(p->files[i].path, "wb");
                if (!p->files[i].fd) {
                        fprintf(stderr, "error: unable to create file %s, error %d\n",
-                               path, errno);
+                               p->files[i].path, errno);
                        exit(0);
                }
        }
 
        p->files[i].buffer_id = buffer_id;
        p->files[i].fmt = format;
+       clock_gettime(CLOCK_MONOTONIC, &p->files[i].last_write);
 
        if (!audio)
                return i;
@@ -139,31 +160,73 @@ int init_wave(struct dma_frame_parser *p, uint32_t buffer_id, uint32_t format)
        return i;
 }
 
+/**
+ * Update WAV header sizes and close a single audio file.
+ * Safe to call on non-audio, NULL-fd, or stdout entries (no-ops).
+ */
+static void close_wave_file(struct wave_files *f, bool log)
+{
+       uint32_t chunk_size;
+
+       if (!f->fd || f->fd == stdout) {
+               f->fd = NULL;
+               return;
+       }
+
+       if (log)
+               fprintf(stderr, "%s:\t Closing idle file %s\n",
+                       APP_NAME, f->path);
+
+       if (is_audio_format(f->fmt)) {
+               chunk_size = f->size + sizeof(struct wave) -
+                            offsetof(struct riff_chunk, format);
+
+               fseek(f->fd, sizeof(uint32_t), SEEK_SET);
+               fwrite(&chunk_size, sizeof(uint32_t), 1, f->fd);
+               fseek(f->fd, sizeof(struct wave) -
+                     offsetof(struct data_subchunk, subchunk_size),
+                     SEEK_SET);
+               fwrite(&f->size, sizeof(uint32_t), 1, f->fd);
+       }
+
+       fclose(f->fd);
+       f->fd = NULL;
+}
+
 void finalize_wave_files(struct dma_frame_parser *p)
 {
-       struct wave_files *files = p->files;
-       uint32_t i, chunk_size;
+       uint32_t i;
 
-       /* fill the header at the beginning of each file */
-       /* and close all opened files */
-       /* check wave struct to understand the offsets */
        for (i = 0; i < FILES_LIMIT; i++) {
-               if (!is_audio_format(files[i].fmt))
+               if (!p->files[i].fd)
                        continue;
 
-               if (files[i].fd) {
-                       chunk_size = files[i].size + sizeof(struct wave) -
-                                    offsetof(struct riff_chunk, format);
+               close_wave_file(&p->files[i], false);
+       }
+}
+
+void parser_close_idle_files(struct dma_frame_parser *p)
+{
+       struct timespec now;
+       long long elapsed_ms;
+       uint32_t i;
 
-                       fseek(files[i].fd, sizeof(uint32_t), SEEK_SET);
-                       fwrite(&chunk_size, sizeof(uint32_t), 1, files[i].fd);
-                       fseek(files[i].fd, sizeof(struct wave) -
-                             offsetof(struct data_subchunk, subchunk_size),
-                             SEEK_SET);
-                       fwrite(&files[i].size, sizeof(uint32_t), 1, files[i].fd);
+       clock_gettime(CLOCK_MONOTONIC, &now);
 
-                       fclose(files[i].fd);
-               }
+       for (i = 0; i < FILES_LIMIT; i++) {
+               if (!p->files[i].fd)
+                       continue;
+
+               /* Only idle-close audio files */
+               if (!is_audio_format(p->files[i].fmt))
+                       continue;
+
+               elapsed_ms =
+                       (now.tv_sec - p->files[i].last_write.tv_sec) * 1000LL +
+                       (now.tv_nsec - p->files[i].last_write.tv_nsec) / 1000000LL;
+
+               if (elapsed_ms >= IDLE_TIMEOUT_MS)
+                       close_wave_file(&p->files[i], true);
        }
 }
 
@@ -302,7 +365,7 @@ int parser_parse_data(struct dma_frame_parser *p, size_t d_len)
 
                                        if (file < 0) {
                                                fprintf(stderr,
-                                                       "unable to open file for %u\n",
+                                                       "unable to open file for %#x\n",
                                                        p->packet->buffer_id);
                                                return -EIO;
                                        }
@@ -311,7 +374,9 @@ int parser_parse_data(struct dma_frame_parser *p, size_t d_len)
                                               p->packet->data_size_bytes,
                                               p->files[file].fd);
                                        p->files[file].size += p->packet->data_size_bytes;
-                                       }
+                                       clock_gettime(CLOCK_MONOTONIC,
+                                                     &p->files[file].last_write);
+                               }
                                p->state = READY;
                                break;
                        }
index 93aa89119a5e61cea598bc98f33b2b4da532d115..00f3ecfe44065e426b19a576739e743e0e89adc0 100644 (file)
@@ -26,4 +26,6 @@ int parser_parse_data(struct dma_frame_parser *p, size_t d_len);
 
 void finalize_wave_files(struct dma_frame_parser *p);
 
+void parser_close_idle_files(struct dma_frame_parser *p);
+
 #endif
index 60f40070724a5ee8fcfeddd515b2ce00e18f761d..359ff8494f7df2fef1380e3687d2b9a038dd9a00 100644 (file)
@@ -244,6 +244,8 @@ static void capture_and_parse(unsigned int card, unsigned int device,
                                remaining -= chunk;
                        }
 
+                       parser_close_idle_files(parser);
+
                        if (verbose) {
                                print_time(compress);
                                fprintf(finfo, "%s: read %d\n", __func__, read);