From 0f37e0951e6cdc657f68821b26fe3974a426bf5a Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 30 Mar 2026 23:45:38 +0300 Subject: [PATCH 1/6] utils: add sofprobeclient binary Add sofprobeclient, a SOF probe client for compressed audio capture based on crecord. Built from its own source file with the same libtinycompress linkage as cplay and crecord. At this phase the sofprobeclient.c is almost exact copy of crecord.c. Only the reference to the command name have been updated from "crecord" to "sofprobeclient". This is a staring point for SOF probe debugging tool that uses compressed audio capture device for routing logs and audio form defined probe points out of the DSP subsystem. --- src/utils/Makefile.am | 5 +- src/utils/sofprobeclient.c | 499 +++++++++++++++++++++++++++++++++++++ 2 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 src/utils/sofprobeclient.c diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 379247f..94647d7 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,11 +1,14 @@ -bin_PROGRAMS = cplay crecord +bin_PROGRAMS = cplay crecord sofprobeclient cplay_SOURCES = cplay.c wave.c crecord_SOURCES = crecord.c wave.c +sofprobeclient_SOURCES = sofprobeclient.c wave.c cplay_CFLAGS = -I$(top_srcdir)/include crecord_CFLAGS = -I$(top_srcdir)/include +sofprobeclient_CFLAGS = -I$(top_srcdir)/include cplay_LDADD = $(top_builddir)/src/lib/libtinycompress.la crecord_LDADD = $(top_builddir)/src/lib/libtinycompress.la +sofprobeclient_LDADD = $(top_builddir)/src/lib/libtinycompress.la diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c new file mode 100644 index 0000000..a417321 --- /dev/null +++ b/src/utils/sofprobeclient.c @@ -0,0 +1,499 @@ +/* + * This file is provided under a dual BSD/LGPLv2.1 license. When using or + * redistributing this file, you may do so under either license. + * + * BSD LICENSE + * + * sofprobeclient - SOF probe client for compress audio capture + * Copyright (c) 2011-2012, Intel Corporation + * Copyright (c) 2013-2014, Wolfson Microelectronic Ltd. + * All rights reserved. + * + * Author: Vinod Koul + * Author: Charles Keepax + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * LGPL LICENSE + * + * sofprobeclient - SOF probe client for compress audio capture + * Copyright (c) 2011-2012, Intel Corporation + * Copyright (c) 2013-2014, Wolfson Microelectronic Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to + * the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define __force +#define __bitwise +#define __user +#include "sound/compress_params.h" +#include "sound/compress_offload.h" +#include "tinycompress/tinycompress.h" +#include "tinycompress/tinywave.h" + +static int verbose; +static int file; +static FILE *finfo; +static bool streamed; + +static const unsigned int DEFAULT_CHANNELS = 1; +static const unsigned int DEFAULT_RATE = 44100; +static const unsigned int DEFAULT_FORMAT = SNDRV_PCM_FORMAT_S16_LE; +static const unsigned int DEFAULT_CODEC_ID = SND_AUDIOCODEC_PCM; + +static const struct { + const char *name; + unsigned int id; +} codec_ids[] = { + { "PCM", SND_AUDIOCODEC_PCM }, + { "MP3", SND_AUDIOCODEC_MP3 }, + { "AMR", SND_AUDIOCODEC_AMR }, + { "AMRWB", SND_AUDIOCODEC_AMRWB }, + { "AMRWBPLUS", SND_AUDIOCODEC_AMRWBPLUS }, + { "AAC", SND_AUDIOCODEC_AAC }, + { "WMA", SND_AUDIOCODEC_WMA }, + { "REAL", SND_AUDIOCODEC_REAL }, + { "VORBIS", SND_AUDIOCODEC_VORBIS }, + { "FLAC", SND_AUDIOCODEC_FLAC }, + { "IEC61937", SND_AUDIOCODEC_IEC61937 }, + { "G723_1", SND_AUDIOCODEC_G723_1 }, + { "G729", SND_AUDIOCODEC_G729 }, +/* BESPOKE isn't defined on older kernels */ +#ifdef SND_AUDIOCODEC_BESPOKE + { "BESPOKE", SND_AUDIOCODEC_BESPOKE }, +#endif +}; +#define CREC_NUM_CODEC_IDS (sizeof(codec_ids) / sizeof(codec_ids[0])) + +static const char *codec_name_from_id(unsigned int id) +{ + static char hexname[12]; + int i; + + for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) { + if (codec_ids[i].id == id) + return codec_ids[i].name; + } + + snprintf(hexname, sizeof(hexname), "0x%x", id); + return hexname; /* a static is safe because we're single-threaded */ +} + +static void usage(void) +{ + int i; + + fprintf(stderr, "usage: sofprobeclient [OPTIONS] [filename.wav]\n" + "-c\tcard number\n" + "-d\tdevice node\n" + "-b\tbuffer size\n" + "-f\tfragments\n" + "-v\tverbose mode\n" + "-l\tlength of record in seconds\n" + "-h\tPrints this help list\n\n" + "-C\tSpecify the number of channels (default %u)\n" + "-R\tSpecify the sample rate (default %u)\n" + "-F\tSpecify the format: S16_LE, S32_LE (default S16_LE)\n" + "-I\tSpecify codec ID (default %s)\n\n" + "If filename.wav is not given the output is written to stdout\n" + "Only PCM data can be written to a WAV file.\n\n" + "Example:\n" + "\tsofprobeclient -c 1 -d 2 test.wav\n" + "\tsofprobeclient -f 5 test.wav\n" + "\tsofprobeclient -I BESPOKE >raw.bin\n\n" + "Valid codec IDs:\n", + DEFAULT_CHANNELS, DEFAULT_RATE, + codec_name_from_id(DEFAULT_CODEC_ID)); + + for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) + fprintf(stderr, "%s%c", codec_ids[i].name, + ((i + 1) % 8) ? ' ' : '\n'); + + fprintf(stderr, "\nor the value in decimal or hex\n"); + + exit(EXIT_FAILURE); +} + +static int print_time(struct compress *compress) +{ + unsigned int avail; + struct timespec tstamp; + + if (compress_get_hpointer(compress, &avail, &tstamp) != 0) { + fprintf(stderr, "Error querying timestamp\n"); + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + return -1; + } else { + fprintf(finfo, "DSP recorded %jd.%jd\n", + (intmax_t)tstamp.tv_sec, (intmax_t)tstamp.tv_nsec*1000); + } + return 0; +} + +static int finish_record(void) +{ + struct wave_header header; + int ret; + size_t nread, written; + + if (!file) + return -ENOENT; + + /* can't rewind if streaming to stdout */ + if (streamed) + return 0; + + /* Get amount of data written to file */ + ret = lseek(file, 0, SEEK_END); + if (ret < 0) + return -errno; + + written = ret; + if (written < sizeof(header)) + return -ENOENT; + written -= sizeof(header); + + /* Sync file header from file */ + ret = lseek(file, 0, SEEK_SET); + if (ret < 0) + return -errno; + + nread = read(file, &header, sizeof(header)); + if (nread != sizeof(header)) + return -errno; + + /* Update file header */ + ret = lseek(file, 0, SEEK_SET); + if (ret < 0) + return -errno; + + size_wave_header(&header, written); + + written = write(file, &header, sizeof(header)); + if (written != sizeof(header)) + return -errno; + + return 0; +} + +static void capture_samples(char *name, unsigned int card, unsigned int device, + unsigned long buffer_size, unsigned int frag, + unsigned int length, unsigned int rate, + unsigned int channels, unsigned int format, + unsigned int codec_id) +{ + struct compr_config config; + struct snd_codec codec; + struct compress *compress; + struct wave_header header; + char *buffer; + size_t written; + int read, ret; + unsigned int size, total_read = 0; + unsigned int samplebits; + + switch (format) { + case SNDRV_PCM_FORMAT_S32_LE: + samplebits = 32; + break; + default: + samplebits = 16; + break; + } + + /* Convert length from seconds to bytes */ + length = length * rate * (samplebits / 8) * channels; + + if (verbose) + fprintf(finfo, "%s: entry, reading %u bytes\n", __func__, length); + if (!name) { + file = STDOUT_FILENO; + } else { + file = open(name, O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (file == -1) { + fprintf(stderr, "Unable to open file '%s'\n", name); + exit(EXIT_FAILURE); + } + } + + /* Write a header, will update with size once record is complete */ + if (!streamed) { + init_wave_header(&header, channels, rate, samplebits); + written = write(file, &header, sizeof(header)); + if (written != sizeof(header)) { + fprintf(stderr, "Error writing output file header: %s\n", + strerror(errno)); + goto file_exit; + } + } + + memset(&codec, 0, sizeof(codec)); + memset(&config, 0, sizeof(config)); + codec.id = codec_id; + codec.ch_in = channels; + codec.ch_out = channels; + codec.sample_rate = rate; + if (!codec.sample_rate) { + fprintf(stderr, "invalid sample rate %d\n", rate); + goto file_exit; + } + codec.format = format; + if ((buffer_size != 0) && (frag != 0)) { + config.fragment_size = buffer_size/frag; + config.fragments = frag; + } + config.codec = &codec; + + compress = compress_open(card, device, COMPRESS_OUT, &config); + if (!compress || !is_compress_ready(compress)) { + fprintf(stderr, "Unable to open Compress device %d:%d\n", + card, device); + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto file_exit; + }; + + if (verbose) + fprintf(finfo, "%s: Opened compress device\n", __func__); + + size = config.fragments * config.fragment_size; + buffer = malloc(size); + if (!buffer) { + fprintf(stderr, "Unable to allocate %d bytes\n", size); + goto comp_exit; + } + + fprintf(finfo, "Recording file %s On Card %u device %u, with buffer of %u bytes\n", + name, card, device, size); + fprintf(finfo, "Codec %u Format %u Channels %u, %u Hz\n", + codec.id, codec.format, codec.ch_out, rate); + + compress_start(compress); + + if (verbose) + fprintf(finfo, "%s: Capturing audio NOW!!!\n", __func__); + + do { + read = compress_read(compress, buffer, size); + if (read < 0) { + fprintf(stderr, "Error reading sample\n"); + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto buf_exit; + } + if ((unsigned int)read != size) { + fprintf(stderr, "We read %d, DSP sent %d\n", + size, read); + } + + if (read > 0) { + total_read += read; + + written = write(file, buffer, read); + if (written != (size_t)read) { + fprintf(stderr, "Error writing output file: %s\n", + strerror(errno)); + goto buf_exit; + } + if (verbose) { + print_time(compress); + fprintf(finfo, "%s: read %d\n", __func__, read); + } + } + } while (!length || total_read < length); + + ret = compress_stop(compress); + if (ret < 0) { + fprintf(stderr, "Error closing stream\n"); + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + } + + ret = finish_record(); + if (ret < 0) { + fprintf(stderr, "Failed to finish header: %s\n", strerror(ret)); + goto buf_exit; + } + + if (verbose) + fprintf(finfo, "%s: exit success\n", __func__); + + free(buffer); + close(file); + file = 0; + + compress_close(compress); + + return; +buf_exit: + free(buffer); +comp_exit: + compress_close(compress); +file_exit: + close(file); + + if (verbose) + fprintf(finfo, "%s: exit failure\n", __func__); + + exit(EXIT_FAILURE); +} + +static void sig_handler(int signum __attribute__ ((unused))) +{ + finish_record(); + + if (file) + close(file); + + _exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + char *file; + unsigned long buffer_size = 0; + int c, i; + unsigned int card = 0, device = 0, frag = 0, length = 0; + unsigned int rate = DEFAULT_RATE, channels = DEFAULT_CHANNELS; + unsigned int format = DEFAULT_FORMAT; + unsigned int codec_id = DEFAULT_CODEC_ID; + + if (signal(SIGINT, sig_handler) == SIG_ERR) { + fprintf(stderr, "Error registering signal handler\n"); + exit(EXIT_FAILURE); + } + + if (argc < 1) + usage(); + + verbose = 0; + while ((c = getopt(argc, argv, "hvl:R:C:F:I:b:f:c:d:")) != -1) { + switch (c) { + case 'h': + usage(); + break; + case 'b': + buffer_size = strtol(optarg, NULL, 0); + break; + case 'f': + frag = strtol(optarg, NULL, 10); + break; + case 'c': + card = strtol(optarg, NULL, 10); + break; + case 'd': + device = strtol(optarg, NULL, 10); + break; + case 'v': + verbose = 1; + break; + case 'l': + length = strtol(optarg, NULL, 10); + break; + case 'R': + rate = strtol(optarg, NULL, 10); + break; + case 'C': + channels = strtol(optarg, NULL, 10); + break; + case 'F': + if (strcmp(optarg, "S16_LE") == 0) { + format = SNDRV_PCM_FORMAT_S16_LE; + } else if (strcmp(optarg, "S32_LE") == 0) { + format = SNDRV_PCM_FORMAT_S32_LE; + } else { + fprintf(stderr, "Unrecognised format: %s\n", + optarg); + usage(); + } + break; + case 'I': + if (optarg[0] == '0') { + codec_id = strtol(optarg, NULL, 0); + } else { + for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) { + if (strcmp(optarg, + codec_ids[i].name) == 0) { + codec_id = codec_ids[i].id; + break; + } + } + + if (i == CREC_NUM_CODEC_IDS) { + fprintf(stderr, "Unrecognised ID: %s\n", + optarg); + usage(); + } + } + break; + default: + exit(EXIT_FAILURE); + } + } + + if (optind >= argc) { + file = NULL; + finfo = fopen("/dev/null", "w"); + streamed = true; + } else if (codec_id == SND_AUDIOCODEC_PCM) { + file = argv[optind]; + finfo = stdout; + streamed = false; + } else { + fprintf(stderr, "ERROR: Only PCM can be written to a WAV file\n"); + exit(EXIT_FAILURE); + } + + capture_samples(file, card, device, buffer_size, frag, length, + rate, channels, format, codec_id); + + fprintf(finfo, "Finish capturing... Close Normally\n"); + + exit(EXIT_SUCCESS); +} From d38ad4fa8fb46a516edbf4f76d7edbddf8bb4a92 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 31 Mar 2026 00:20:49 +0300 Subject: [PATCH 2/6] utils: sofprobeclient: set SOF probe defaults Change default parameters to match typical SOF probe usage: card 3, device 0, buffer 8192, 4 fragments, S32_LE format, 48000 Hz sample rate, and 4 channels. All parameters remain overridable via command line options. --- src/utils/sofprobeclient.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c index a417321..754084d 100644 --- a/src/utils/sofprobeclient.c +++ b/src/utils/sofprobeclient.c @@ -84,9 +84,9 @@ static int file; static FILE *finfo; static bool streamed; -static const unsigned int DEFAULT_CHANNELS = 1; -static const unsigned int DEFAULT_RATE = 44100; -static const unsigned int DEFAULT_FORMAT = SNDRV_PCM_FORMAT_S16_LE; +static const unsigned int DEFAULT_CHANNELS = 4; +static const unsigned int DEFAULT_RATE = 48000; +static const unsigned int DEFAULT_FORMAT = SNDRV_PCM_FORMAT_S32_LE; static const unsigned int DEFAULT_CODEC_ID = SND_AUDIOCODEC_PCM; static const struct { @@ -397,9 +397,9 @@ static void sig_handler(int signum __attribute__ ((unused))) int main(int argc, char **argv) { char *file; - unsigned long buffer_size = 0; + unsigned long buffer_size = 8192; int c, i; - unsigned int card = 0, device = 0, frag = 0, length = 0; + unsigned int card = 3, device = 0, frag = 4, length = 0; unsigned int rate = DEFAULT_RATE, channels = DEFAULT_CHANNELS; unsigned int format = DEFAULT_FORMAT; unsigned int codec_id = DEFAULT_CODEC_ID; From d795c4abb8abd20dbfa78fbd0fd4b27f492a5791 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 31 Mar 2026 00:47:33 +0300 Subject: [PATCH 3/6] utils: sofprobeclient: embed SOF probe parser for real-time demux Integrate the sof-probes demux engine directly into sofprobeclient so captured compressed data is parsed in real time instead of being written to a raw file. Log output from non-audio probe points is printed to stdout (equivalent to sof-probes -l). Audio probe data is extracted into buffer_.wav files in the current directory. The file output parameter is removed; sofprobeclient no longer acts as a generic recorder. New files copied from SOF v2.9 sources tools/probes/ with include paths adjusted for standalone tinycompress build: probes_demux.c / probes_demux.h - probe stream parser probes_wave.h - WAV header definitions probe_dma_frame.h - DMA frame packet format --- src/utils/Makefile.am | 2 +- src/utils/probe_dma_frame.h | 92 ++++++++++ src/utils/probes_demux.c | 338 ++++++++++++++++++++++++++++++++++++ src/utils/probes_demux.h | 29 ++++ src/utils/probes_wave.h | 45 +++++ src/utils/sofprobeclient.c | 262 +++++++--------------------- 6 files changed, 565 insertions(+), 203 deletions(-) create mode 100644 src/utils/probe_dma_frame.h create mode 100644 src/utils/probes_demux.c create mode 100644 src/utils/probes_demux.h create mode 100644 src/utils/probes_wave.h diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 94647d7..9c83362 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -2,7 +2,7 @@ bin_PROGRAMS = cplay crecord sofprobeclient cplay_SOURCES = cplay.c wave.c crecord_SOURCES = crecord.c wave.c -sofprobeclient_SOURCES = sofprobeclient.c wave.c +sofprobeclient_SOURCES = sofprobeclient.c probes_demux.c cplay_CFLAGS = -I$(top_srcdir)/include crecord_CFLAGS = -I$(top_srcdir)/include diff --git a/src/utils/probe_dma_frame.h b/src/utils/probe_dma_frame.h new file mode 100644 index 0000000..1b617f2 --- /dev/null +++ b/src/utils/probe_dma_frame.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + * + * Local copy of ipc/probe_dma_frame.h with MASK defined inline + * so it can be built without SOF RTOS headers. + */ + +#ifndef __PROBE_DMA_FRAME_H__ +#define __PROBE_DMA_FRAME_H__ + +#include + +#ifndef MASK +#define MASK(b_hi, b_lo) \ + (((1ULL << ((b_hi) - (b_lo) + 1ULL)) - 1ULL) << (b_lo)) +#endif + +/** + * Header for data packets sent via compressed PCM from extraction probes + */ +struct probe_data_packet { + uint32_t sync_word; /**< PROBE_EXTRACT_SYNC_WORD */ + uint32_t buffer_id; /**< Buffer ID from which data was extracted */ + uint32_t format; /**< Encoded data format */ + uint32_t timestamp_low; /**< Low 32 bits of timestamp in us */ + uint32_t timestamp_high; /**< High 32 bits of timestamp in us */ + uint32_t data_size_bytes; /**< Size of following audio data */ + uint8_t data[]; /**< Audio data extracted from buffer */ +} __attribute__((packed, aligned(4))); + +#define PROBE_EXTRACT_SYNC_WORD 0xBABEBEBA + +/** + * \brief Definitions of shifts and masks for format encoding in probe + * extraction stream + * + * Audio format from extraction probes is encoded as 32 bit value. Following + * graphic explains encoding. + * + * A|BBBB|CCCC|DDDD|EEEEE|FF|GG|H|I|J|XXXXXXX + * A - 1 bit - Specifies Type Encoding - 1 for Standard encoding + * B - 4 bits - Specify Standard Type - 0 for Audio + * C - 4 bits - Specify Audio format - 0 for PCM + * D - 4 bits - Specify Sample Rate - value enumerating standard sample rates: + * 8000 Hz = 0x0 + * 11025 Hz = 0x1 + * 12000 Hz = 0x2 + * 16000 Hz = 0x3 + * 22050 Hz = 0x4 + * 24000 Hz = 0x5 + * 32000 Hz = 0x6 + * 44100 Hz = 0x7 + * 48000 Hz = 0x8 + * 64000 Hz = 0x9 + * 88200 Hz = 0xA + * 96000 Hz = 0xB + * 128000 Hz = 0xC + * 176400 Hz = 0xD + * 192000 Hz = 0xE + * none of the above = 0xF + * E - 5 bits - Specify Number of Channels minus 1 + * F - 2 bits - Specify Sample Size, number of valid sample bytes minus 1 + * G - 2 bits - Specify Container Size, number of container bytes minus 1 + * H - 1 bit - Specifies Sample Format - 0 for Integer, 1 for Floating point + * I - 1 bit - Specifies Sample Endianness - 0 for LE + * J - 1 bit - Specifies Interleaving - 1 for Sample Interleaving + */ + +#define PROBE_SHIFT_FMT_TYPE 31 +#define PROBE_SHIFT_STANDARD_TYPE 27 +#define PROBE_SHIFT_AUDIO_FMT 23 +#define PROBE_SHIFT_SAMPLE_RATE 19 +#define PROBE_SHIFT_NB_CHANNELS 14 +#define PROBE_SHIFT_SAMPLE_SIZE 12 +#define PROBE_SHIFT_CONTAINER_SIZE 10 +#define PROBE_SHIFT_SAMPLE_FMT 9 +#define PROBE_SHIFT_SAMPLE_END 8 +#define PROBE_SHIFT_INTERLEAVING_ST 7 + +#define PROBE_MASK_FMT_TYPE MASK(31, 31) +#define PROBE_MASK_STANDARD_TYPE MASK(30, 27) +#define PROBE_MASK_AUDIO_FMT MASK(26, 23) +#define PROBE_MASK_SAMPLE_RATE MASK(22, 19) +#define PROBE_MASK_NB_CHANNELS MASK(18, 14) +#define PROBE_MASK_SAMPLE_SIZE MASK(13, 12) +#define PROBE_MASK_CONTAINER_SIZE MASK(11, 10) +#define PROBE_MASK_SAMPLE_FMT MASK(9, 9) +#define PROBE_MASK_SAMPLE_END MASK(8, 8) +#define PROBE_MASK_INTERLEAVING_ST MASK(7, 7) + +#endif /* __PROBE_DMA_FRAME_H__ */ diff --git a/src/utils/probes_demux.c b/src/utils/probes_demux.c new file mode 100644 index 0000000..7d51d3c --- /dev/null +++ b/src/utils/probes_demux.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Adrian Bonislawski +// Jyri Sarha (restructured and moved to this file) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "probe_dma_frame.h" + +#include "probes_wave.h" + +#define APP_NAME "sof-probes" + +#define PACKET_MAX_SIZE 4096 /**< Size limit for probe data packet */ +#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 */ + +struct wave_files { + FILE *fd; + uint32_t buffer_id; + uint32_t fmt; + uint32_t size; + struct wave header; +}; + +enum p_state { + READY = 0, /**< At this stage app is looking for a SYNC word */ + SYNC, /**< SYNC received, copying data */ + CHECK /**< Check crc and save packet if valid */ +}; + +struct dma_frame_parser { + bool log_to_stdout; + enum p_state state; + struct probe_data_packet *packet; + size_t packet_size; + uint8_t *w_ptr; /* Write pointer to copy data to */ + uint32_t total_data_to_copy; /* Total bytes left to copy */ + int start; /* Start of unfilled data */ + int len; /* Data buffer fill level */ + uint8_t data[DATA_READ_LIMIT]; + struct wave_files files[FILES_LIMIT]; +}; + +static uint32_t sample_rate[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, + 48000, 64000, 88200, 96000, 128000, 176400, 192000 +}; + +int get_buffer_file(struct wave_files *files, uint32_t buffer_id) +{ + int i; + + for (i = 0; i < FILES_LIMIT; i++) { + if (files[i].fd != NULL && files[i].buffer_id == buffer_id) + return i; + } + return -1; +} + +int get_buffer_file_free(struct wave_files *files) +{ + int i; + + for (i = 0; i < FILES_LIMIT; i++) { + if (files[i].fd == NULL) + return i; + } + return -1; +} + +bool is_audio_format(uint32_t format) +{ + return (format & PROBE_MASK_FMT_TYPE) != 0 && (format & PROBE_MASK_AUDIO_FMT) == 0; +} + +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]; + int i; + + i = get_buffer_file_free(p->files); + if (i == -1) { + fprintf(stderr, "error: too many buffers\n"); + exit(0); + } + + sprintf(path, "buffer_%d.%s", buffer_id, audio ? "wav" : "bin"); + + fprintf(stderr, "%s:\t Creating file %s\n", APP_NAME, path); + + if (!audio && p->log_to_stdout) { + p->files[i].fd = stdout; + } else { + p->files[i].fd = fopen(path, "wb"); + if (!p->files[i].fd) { + fprintf(stderr, "error: unable to create file %s, error %d\n", + path, errno); + exit(0); + } + } + + p->files[i].buffer_id = buffer_id; + p->files[i].fmt = format; + + if (!audio) + return i; + + p->files[i].header.riff.chunk_id = HEADER_RIFF; + p->files[i].header.riff.format = HEADER_WAVE; + p->files[i].header.fmt.subchunk_id = HEADER_FMT; + p->files[i].header.fmt.subchunk_size = 16; + p->files[i].header.fmt.audio_format = 1; + p->files[i].header.fmt.num_channels = ((format & PROBE_MASK_NB_CHANNELS) >> PROBE_SHIFT_NB_CHANNELS) + 1; + p->files[i].header.fmt.sample_rate = sample_rate[(format & PROBE_MASK_SAMPLE_RATE) >> PROBE_SHIFT_SAMPLE_RATE]; + p->files[i].header.fmt.bits_per_sample = (((format & PROBE_MASK_CONTAINER_SIZE) >> PROBE_SHIFT_CONTAINER_SIZE) + 1) * 8; + p->files[i].header.fmt.byte_rate = p->files[i].header.fmt.sample_rate * + p->files[i].header.fmt.num_channels * + p->files[i].header.fmt.bits_per_sample / 8; + p->files[i].header.fmt.block_align = p->files[i].header.fmt.num_channels * + p->files[i].header.fmt.bits_per_sample / 8; + p->files[i].header.data.subchunk_id = HEADER_DATA; + + fwrite(&p->files[i].header, sizeof(struct wave), 1, p->files[i].fd); + + return i; +} + +void finalize_wave_files(struct dma_frame_parser *p) +{ + struct wave_files *files = p->files; + uint32_t i, chunk_size; + + /* 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)) + continue; + + if (files[i].fd) { + chunk_size = files[i].size + sizeof(struct wave) - + offsetof(struct riff_chunk, format); + + 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); + + fclose(files[i].fd); + } + } +} + +int validate_data_packet(struct probe_data_packet *packet) +{ + uint64_t *checksump; + uint64_t sum; + + sum = (uint32_t) (packet->sync_word + + packet->buffer_id + + packet->format + + packet->timestamp_high + + packet->timestamp_low + + packet->data_size_bytes); + + checksump = (uint64_t *) (packet->data + packet->data_size_bytes); + + if (sum != *checksump) { + fprintf(stderr, "Checksum error 0x%016" PRIx64 " != 0x%016" PRIx64 "\n", + sum, *checksump); + return -EINVAL; + } + + return 0; +} + +int process_sync(struct dma_frame_parser *p) +{ + struct probe_data_packet *temp_packet; + + /* request to copy data_size from probe packet and 64-bit checksum */ + p->total_data_to_copy = p->packet->data_size_bytes + sizeof(uint64_t); + + if (sizeof(struct probe_data_packet) + p->total_data_to_copy > + p->packet_size) { + p->packet_size = sizeof(struct probe_data_packet) + + p->total_data_to_copy; + + temp_packet = realloc(p->packet, p->packet_size); + + if (!temp_packet) + return -ENOMEM; + + p->packet = temp_packet; + } + + p->w_ptr = (uint8_t *)p->packet->data; + + return 0; +} + +struct dma_frame_parser *parser_init(void) +{ + struct dma_frame_parser *p = malloc(sizeof(*p)); + if (!p) { + fprintf(stderr, "error: allocation failed, err %d\n", + errno); + return NULL; + } + memset(p, 0, sizeof(*p)); + p->packet = malloc(PACKET_MAX_SIZE); + if (!p) { + fprintf(stderr, "error: allocation failed, err %d\n", + errno); + free(p); + return NULL; + } + memset(p->packet, 0, PACKET_MAX_SIZE); + p->packet_size = PACKET_MAX_SIZE; + return p; +} + +void parser_free(struct dma_frame_parser *p) +{ + free(p->packet); + free(p); +} + +void parser_log_to_stdout(struct dma_frame_parser *p) +{ + p->log_to_stdout = true; +} + +void parser_fetch_free_buffer(struct dma_frame_parser *p, uint8_t **d, size_t *len) +{ + *d = &p->data[p->start]; + *len = sizeof(p->data) - p->start; +} + +int parser_parse_data(struct dma_frame_parser *p, size_t d_len) +{ + uint i = 0; + + p->len = p->start + d_len; + /* processing all loaded bytes */ + while (i < p->len) { + if (p->total_data_to_copy == 0) { + switch (p->state) { + case READY: + /* check for SYNC */ + if (p->len - i < sizeof(p->packet->sync_word)) { + p->start = p->len - i; + memmove(&p->data[0], &p->data[i], p->start); + i += p->start; + } else if (*((uint32_t *)&p->data[i]) == + PROBE_EXTRACT_SYNC_WORD) { + memset(p->packet, 0, p->packet_size); + /* request to copy full data packet */ + p->total_data_to_copy = + sizeof(struct probe_data_packet); + p->w_ptr = (uint8_t *)p->packet; + p->state = SYNC; + p->start = 0; + } else { + i++; + } + break; + case SYNC: + /* SYNC -> CHECK */ + if (process_sync(p) < 0) { + fprintf(stderr, "OOM, quitting\n"); + return -ENOMEM; + } + p->state = CHECK; + break; + case CHECK: + /* CHECK -> READY */ + /* find corresponding file and save data if valid */ + if (validate_data_packet(p->packet) == 0) { + int file = get_buffer_file(p->files, + p->packet->buffer_id); + + if (file < 0) + file = init_wave(p, p->packet->buffer_id, + p->packet->format); + + if (file < 0) { + fprintf(stderr, + "unable to open file for %u\n", + p->packet->buffer_id); + return -EIO; + } + + fwrite(p->packet->data, 1, + p->packet->data_size_bytes, + p->files[file].fd); + p->files[file].size += p->packet->data_size_bytes; + } + p->state = READY; + break; + } + } + /* data copying section */ + if (p->total_data_to_copy > 0) { + uint data_to_copy; + + /* check if there is enough bytes loaded */ + /* or copy partially if not */ + if (i + p->total_data_to_copy > p->len) { + data_to_copy = p->len - i; + p->total_data_to_copy -= data_to_copy; + } else { + data_to_copy = p->total_data_to_copy; + p->total_data_to_copy = 0; + } + memcpy(p->w_ptr, &p->data[i], data_to_copy); + p->w_ptr += data_to_copy; + i += data_to_copy; + } + } + return 0; +} diff --git a/src/utils/probes_demux.h b/src/utils/probes_demux.h new file mode 100644 index 0000000..93aa891 --- /dev/null +++ b/src/utils/probes_demux.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Jyri Sarha +// + +#ifndef _PROBES_DEMUX_H_ +#define _PROBES_DEMUX_H_ + +#include +#include +#include + +struct dma_frame_parser; + +struct dma_frame_parser *parser_init(void); + +void parser_log_to_stdout(struct dma_frame_parser *p); + +void parser_free(struct dma_frame_parser *p); + +void parser_fetch_free_buffer(struct dma_frame_parser *p, uint8_t **d, size_t *len); + +int parser_parse_data(struct dma_frame_parser *p, size_t d_len); + +void finalize_wave_files(struct dma_frame_parser *p); + +#endif diff --git a/src/utils/probes_wave.h b/src/utils/probes_wave.h new file mode 100644 index 0000000..d5a1b70 --- /dev/null +++ b/src/utils/probes_wave.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2019 Intel Corporation. All rights reserved. + * + * Author: Adrian Bonislawski + */ + +#ifndef __WAVE_H__ +#define __WAVE_H__ + +#define HEADER_RIFF 0x46464952 /**< ASCII "RIFF" */ +#define HEADER_WAVE 0x45564157 /**< ASCII "WAVE" */ +#define HEADER_FMT 0x20746d66 /**< ASCII "fmt " */ +#define HEADER_DATA 0x61746164 /**< ASCII "data" */ + +struct riff_chunk { + uint32_t chunk_id; + uint32_t chunk_size; + uint32_t format; +}; + +struct fmt_subchunk { + uint32_t subchunk_id; + uint32_t subchunk_size; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +}; + +struct data_subchunk { + uint32_t subchunk_id; + uint32_t subchunk_size; + uint32_t data[]; +}; + +struct wave { + struct riff_chunk riff; + struct fmt_subchunk fmt; + struct data_subchunk data; +}; + +#endif /* __WAVE_H__ */ diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c index 754084d..e5b4aea 100644 --- a/src/utils/sofprobeclient.c +++ b/src/utils/sofprobeclient.c @@ -77,87 +77,40 @@ #include "sound/compress_params.h" #include "sound/compress_offload.h" #include "tinycompress/tinycompress.h" -#include "tinycompress/tinywave.h" + +#include "probes_demux.h" static int verbose; -static int file; static FILE *finfo; -static bool streamed; static const unsigned int DEFAULT_CHANNELS = 4; static const unsigned int DEFAULT_RATE = 48000; static const unsigned int DEFAULT_FORMAT = SNDRV_PCM_FORMAT_S32_LE; static const unsigned int DEFAULT_CODEC_ID = SND_AUDIOCODEC_PCM; -static const struct { - const char *name; - unsigned int id; -} codec_ids[] = { - { "PCM", SND_AUDIOCODEC_PCM }, - { "MP3", SND_AUDIOCODEC_MP3 }, - { "AMR", SND_AUDIOCODEC_AMR }, - { "AMRWB", SND_AUDIOCODEC_AMRWB }, - { "AMRWBPLUS", SND_AUDIOCODEC_AMRWBPLUS }, - { "AAC", SND_AUDIOCODEC_AAC }, - { "WMA", SND_AUDIOCODEC_WMA }, - { "REAL", SND_AUDIOCODEC_REAL }, - { "VORBIS", SND_AUDIOCODEC_VORBIS }, - { "FLAC", SND_AUDIOCODEC_FLAC }, - { "IEC61937", SND_AUDIOCODEC_IEC61937 }, - { "G723_1", SND_AUDIOCODEC_G723_1 }, - { "G729", SND_AUDIOCODEC_G729 }, -/* BESPOKE isn't defined on older kernels */ -#ifdef SND_AUDIOCODEC_BESPOKE - { "BESPOKE", SND_AUDIOCODEC_BESPOKE }, -#endif -}; -#define CREC_NUM_CODEC_IDS (sizeof(codec_ids) / sizeof(codec_ids[0])) - -static const char *codec_name_from_id(unsigned int id) -{ - static char hexname[12]; - int i; - - for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) { - if (codec_ids[i].id == id) - return codec_ids[i].name; - } - - snprintf(hexname, sizeof(hexname), "0x%x", id); - return hexname; /* a static is safe because we're single-threaded */ -} +static struct dma_frame_parser *parser; static void usage(void) { - int i; - - fprintf(stderr, "usage: sofprobeclient [OPTIONS] [filename.wav]\n" - "-c\tcard number\n" - "-d\tdevice node\n" - "-b\tbuffer size\n" - "-f\tfragments\n" + fprintf(stderr, "usage: sofprobeclient [OPTIONS]\n" + "-c\tcard number (default 3)\n" + "-d\tdevice node (default 0)\n" + "-b\tbuffer size (default 8192)\n" + "-f\tfragments (default 4)\n" "-v\tverbose mode\n" - "-l\tlength of record in seconds\n" + "-l\tlength of record in seconds (0 = unlimited)\n" "-h\tPrints this help list\n\n" "-C\tSpecify the number of channels (default %u)\n" "-R\tSpecify the sample rate (default %u)\n" - "-F\tSpecify the format: S16_LE, S32_LE (default S16_LE)\n" - "-I\tSpecify codec ID (default %s)\n\n" - "If filename.wav is not given the output is written to stdout\n" - "Only PCM data can be written to a WAV file.\n\n" + "-F\tSpecify the format: S16_LE, S32_LE (default S32_LE)\n\n" + "Captured probe data is parsed in real time.\n" + "Log output (non-audio probes) is written to stdout.\n" + "Audio probe data is written to buffer_.wav files\n" + "in the current directory.\n\n" "Example:\n" - "\tsofprobeclient -c 1 -d 2 test.wav\n" - "\tsofprobeclient -f 5 test.wav\n" - "\tsofprobeclient -I BESPOKE >raw.bin\n\n" - "Valid codec IDs:\n", - DEFAULT_CHANNELS, DEFAULT_RATE, - codec_name_from_id(DEFAULT_CODEC_ID)); - - for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) - fprintf(stderr, "%s%c", codec_ids[i].name, - ((i + 1) % 8) ? ' ' : '\n'); - - fprintf(stderr, "\nor the value in decimal or hex\n"); + "\tsofprobeclient\n" + "\tsofprobeclient -c 1 -d 2\n", + DEFAULT_CHANNELS, DEFAULT_RATE); exit(EXIT_FAILURE); } @@ -178,67 +131,20 @@ static int print_time(struct compress *compress) return 0; } -static int finish_record(void) -{ - struct wave_header header; - int ret; - size_t nread, written; - - if (!file) - return -ENOENT; - - /* can't rewind if streaming to stdout */ - if (streamed) - return 0; - - /* Get amount of data written to file */ - ret = lseek(file, 0, SEEK_END); - if (ret < 0) - return -errno; - - written = ret; - if (written < sizeof(header)) - return -ENOENT; - written -= sizeof(header); - - /* Sync file header from file */ - ret = lseek(file, 0, SEEK_SET); - if (ret < 0) - return -errno; - - nread = read(file, &header, sizeof(header)); - if (nread != sizeof(header)) - return -errno; - - /* Update file header */ - ret = lseek(file, 0, SEEK_SET); - if (ret < 0) - return -errno; - - size_wave_header(&header, written); - - written = write(file, &header, sizeof(header)); - if (written != sizeof(header)) - return -errno; - - return 0; -} - -static void capture_samples(char *name, unsigned int card, unsigned int device, - unsigned long buffer_size, unsigned int frag, - unsigned int length, unsigned int rate, - unsigned int channels, unsigned int format, - unsigned int codec_id) +static void capture_and_parse(unsigned int card, unsigned int device, + unsigned long buffer_size, unsigned int frag, + unsigned int length, unsigned int rate, + unsigned int channels, unsigned int format) { struct compr_config config; struct snd_codec codec; struct compress *compress; - struct wave_header header; char *buffer; - size_t written; int read, ret; unsigned int size, total_read = 0; unsigned int samplebits; + uint8_t *parse_buf; + size_t parse_len; switch (format) { case SNDRV_PCM_FORMAT_S32_LE: @@ -254,37 +160,16 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, if (verbose) fprintf(finfo, "%s: entry, reading %u bytes\n", __func__, length); - if (!name) { - file = STDOUT_FILENO; - } else { - file = open(name, O_RDWR | O_CREAT, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (file == -1) { - fprintf(stderr, "Unable to open file '%s'\n", name); - exit(EXIT_FAILURE); - } - } - - /* Write a header, will update with size once record is complete */ - if (!streamed) { - init_wave_header(&header, channels, rate, samplebits); - written = write(file, &header, sizeof(header)); - if (written != sizeof(header)) { - fprintf(stderr, "Error writing output file header: %s\n", - strerror(errno)); - goto file_exit; - } - } memset(&codec, 0, sizeof(codec)); memset(&config, 0, sizeof(config)); - codec.id = codec_id; + codec.id = DEFAULT_CODEC_ID; codec.ch_in = channels; codec.ch_out = channels; codec.sample_rate = rate; if (!codec.sample_rate) { fprintf(stderr, "invalid sample rate %d\n", rate); - goto file_exit; + return; } codec.format = format; if ((buffer_size != 0) && (frag != 0)) { @@ -298,7 +183,7 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, fprintf(stderr, "Unable to open Compress device %d:%d\n", card, device); fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); - goto file_exit; + return; }; if (verbose) @@ -311,15 +196,15 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, goto comp_exit; } - fprintf(finfo, "Recording file %s On Card %u device %u, with buffer of %u bytes\n", - name, card, device, size); + fprintf(finfo, "Capturing probes on Card %u device %u, buffer %u bytes\n", + card, device, size); fprintf(finfo, "Codec %u Format %u Channels %u, %u Hz\n", codec.id, codec.format, codec.ch_out, rate); compress_start(compress); if (verbose) - fprintf(finfo, "%s: Capturing audio NOW!!!\n", __func__); + fprintf(finfo, "%s: Capturing probe data NOW!!!\n", __func__); do { read = compress_read(compress, buffer, size); @@ -336,12 +221,20 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, if (read > 0) { total_read += read; - written = write(file, buffer, read); - if (written != (size_t)read) { - fprintf(stderr, "Error writing output file: %s\n", - strerror(errno)); + /* Feed captured data into the probe parser */ + parser_fetch_free_buffer(parser, &parse_buf, &parse_len); + if ((size_t)read > parse_len) { + fprintf(stderr, "Warning: read %d > parser buffer %zu, truncating\n", + read, parse_len); + read = parse_len; + } + memcpy(parse_buf, buffer, read); + ret = parser_parse_data(parser, read); + if (ret < 0) { + fprintf(stderr, "Parser error %d, stopping\n", ret); goto buf_exit; } + if (verbose) { print_time(compress); fprintf(finfo, "%s: read %d\n", __func__, read); @@ -355,19 +248,13 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); } - ret = finish_record(); - if (ret < 0) { - fprintf(stderr, "Failed to finish header: %s\n", strerror(ret)); - goto buf_exit; - } + /* Finalize any open wave files */ + finalize_wave_files(parser); if (verbose) fprintf(finfo, "%s: exit success\n", __func__); free(buffer); - close(file); - file = 0; - compress_close(compress); return; @@ -375,8 +262,6 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, free(buffer); comp_exit: compress_close(compress); -file_exit: - close(file); if (verbose) fprintf(finfo, "%s: exit failure\n", __func__); @@ -386,34 +271,38 @@ static void capture_samples(char *name, unsigned int card, unsigned int device, static void sig_handler(int signum __attribute__ ((unused))) { - finish_record(); - - if (file) - close(file); + /* Finalize wave files on signal */ + if (parser) + finalize_wave_files(parser); _exit(EXIT_FAILURE); } int main(int argc, char **argv) { - char *file; unsigned long buffer_size = 8192; - int c, i; + int c; unsigned int card = 3, device = 0, frag = 4, length = 0; unsigned int rate = DEFAULT_RATE, channels = DEFAULT_CHANNELS; unsigned int format = DEFAULT_FORMAT; - unsigned int codec_id = DEFAULT_CODEC_ID; if (signal(SIGINT, sig_handler) == SIG_ERR) { fprintf(stderr, "Error registering signal handler\n"); exit(EXIT_FAILURE); } - if (argc < 1) - usage(); + /* Initialize the probe parser with log-to-stdout */ + parser = parser_init(); + if (!parser) { + fprintf(stderr, "Failed to initialize probe parser\n"); + exit(EXIT_FAILURE); + } + parser_log_to_stdout(parser); verbose = 0; - while ((c = getopt(argc, argv, "hvl:R:C:F:I:b:f:c:d:")) != -1) { + finfo = stderr; + + while ((c = getopt(argc, argv, "hvl:R:C:F:b:f:c:d:")) != -1) { switch (c) { case 'h': usage(); @@ -453,47 +342,16 @@ int main(int argc, char **argv) usage(); } break; - case 'I': - if (optarg[0] == '0') { - codec_id = strtol(optarg, NULL, 0); - } else { - for (i = 0; i < CREC_NUM_CODEC_IDS; ++i) { - if (strcmp(optarg, - codec_ids[i].name) == 0) { - codec_id = codec_ids[i].id; - break; - } - } - - if (i == CREC_NUM_CODEC_IDS) { - fprintf(stderr, "Unrecognised ID: %s\n", - optarg); - usage(); - } - } - break; default: exit(EXIT_FAILURE); } } - if (optind >= argc) { - file = NULL; - finfo = fopen("/dev/null", "w"); - streamed = true; - } else if (codec_id == SND_AUDIOCODEC_PCM) { - file = argv[optind]; - finfo = stdout; - streamed = false; - } else { - fprintf(stderr, "ERROR: Only PCM can be written to a WAV file\n"); - exit(EXIT_FAILURE); - } - - capture_samples(file, card, device, buffer_size, frag, length, - rate, channels, format, codec_id); + capture_and_parse(card, device, buffer_size, frag, length, + rate, channels, format); fprintf(finfo, "Finish capturing... Close Normally\n"); + parser_free(parser); exit(EXIT_SUCCESS); } From df9eedf877647392fa03562676fc97b3b179af6c Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 31 Mar 2026 01:12:16 +0300 Subject: [PATCH 4/6] utils: sofprobeclient: fix parser buffer overflow on large reads The compress device may return more data than the parser's internal buffer (DATA_READ_LIMIT, 4096 bytes) can accept in one call. Feed captured data in a loop, copying only as much as parser_fetch_free_buffer() reports available per iteration. This fixes truncated probe packets that caused checksum errors and missing log lines. --- src/utils/sofprobeclient.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c index e5b4aea..6e10b44 100644 --- a/src/utils/sofprobeclient.c +++ b/src/utils/sofprobeclient.c @@ -219,20 +219,27 @@ static void capture_and_parse(unsigned int card, unsigned int device, } if (read > 0) { + int remaining = read; + char *src = buffer; + total_read += read; - /* Feed captured data into the probe parser */ - parser_fetch_free_buffer(parser, &parse_buf, &parse_len); - if ((size_t)read > parse_len) { - fprintf(stderr, "Warning: read %d > parser buffer %zu, truncating\n", - read, parse_len); - read = parse_len; - } - memcpy(parse_buf, buffer, read); - ret = parser_parse_data(parser, read); - if (ret < 0) { - fprintf(stderr, "Parser error %d, stopping\n", ret); - goto buf_exit; + /* Feed captured data to the parser in chunks + * that fit its internal buffer. + */ + while (remaining > 0) { + int chunk; + + parser_fetch_free_buffer(parser, &parse_buf, &parse_len); + chunk = remaining < (int)parse_len ? remaining : (int)parse_len; + memcpy(parse_buf, src, chunk); + ret = parser_parse_data(parser, chunk); + if (ret < 0) { + fprintf(stderr, "Parser error %d, stopping\n", ret); + goto buf_exit; + } + src += chunk; + remaining -= chunk; } if (verbose) { From f65454a995b091fd2feaf7a0063a6ea2692c1eaf Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 31 Mar 2026 01:19:02 +0300 Subject: [PATCH 5/6] utils: sofprobeclient: add -D flag for parser debug messages Move the "We read N, DSP sent M" diagnostic behind a new -D (parser debug) flag so it does not clutter normal output. --- src/utils/sofprobeclient.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c index 6e10b44..60f4007 100644 --- a/src/utils/sofprobeclient.c +++ b/src/utils/sofprobeclient.c @@ -81,6 +81,7 @@ #include "probes_demux.h" static int verbose; +static int parser_debug; static FILE *finfo; static const unsigned int DEFAULT_CHANNELS = 4; @@ -98,6 +99,7 @@ static void usage(void) "-b\tbuffer size (default 8192)\n" "-f\tfragments (default 4)\n" "-v\tverbose mode\n" + "-D\tenable parser debug messages\n" "-l\tlength of record in seconds (0 = unlimited)\n" "-h\tPrints this help list\n\n" "-C\tSpecify the number of channels (default %u)\n" @@ -213,7 +215,7 @@ static void capture_and_parse(unsigned int card, unsigned int device, fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); goto buf_exit; } - if ((unsigned int)read != size) { + if (parser_debug && (unsigned int)read != size) { fprintf(stderr, "We read %d, DSP sent %d\n", size, read); } @@ -309,7 +311,7 @@ int main(int argc, char **argv) verbose = 0; finfo = stderr; - while ((c = getopt(argc, argv, "hvl:R:C:F:b:f:c:d:")) != -1) { + while ((c = getopt(argc, argv, "hvDl:R:C:F:b:f:c:d:")) != -1) { switch (c) { case 'h': usage(); @@ -329,6 +331,9 @@ int main(int argc, char **argv) case 'v': verbose = 1; break; + case 'D': + parser_debug = 1; + break; case 'l': length = strtol(optarg, NULL, 10); break; From 9464e497984f533a8e4840f297c6c4279f03e701 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Tue, 31 Mar 2026 18:05:52 +0300 Subject: [PATCH 6/6] utils: probes_demux: auto-close idle audio files and avoid overwrites Audio capture files (buffer_.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_-1.wav, buffer_-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. --- src/utils/probes_demux.c | 113 +++++++++++++++++++++++++++++-------- src/utils/probes_demux.h | 2 + src/utils/sofprobeclient.c | 2 + 3 files changed, 93 insertions(+), 24 deletions(-) diff --git a/src/utils/probes_demux.c b/src/utils/probes_demux.c index 7d51d3c..b11f702 100644 --- a/src/utils/probes_demux.c +++ b/src/utils/probes_demux.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "probe_dma_frame.h" @@ -26,13 +27,16 @@ #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; } diff --git a/src/utils/probes_demux.h b/src/utils/probes_demux.h index 93aa891..00f3ecf 100644 --- a/src/utils/probes_demux.h +++ b/src/utils/probes_demux.h @@ -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 diff --git a/src/utils/sofprobeclient.c b/src/utils/sofprobeclient.c index 60f4007..359ff84 100644 --- a/src/utils/sofprobeclient.c +++ b/src/utils/sofprobeclient.c @@ -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);