diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 379247f..9c83362 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 probes_demux.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/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..b11f702 --- /dev/null +++ b/src/utils/probes_demux.c @@ -0,0 +1,403 @@ +// 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 + +#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 */ +#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 { + 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; +} + +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); + uint32_t file_index; + int i; + + i = get_buffer_file_free(p->files); + if (i == -1) { + fprintf(stderr, "error: too many buffers\n"); + exit(0); + } + + /* 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, p->files[i].path); + + if (!audio && p->log_to_stdout) { + p->files[i].fd = stdout; + } else { + 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", + 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; + + 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; +} + +/** + * 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) +{ + uint32_t i; + + for (i = 0; i < FILES_LIMIT; i++) { + if (!p->files[i].fd) + continue; + + 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; + + clock_gettime(CLOCK_MONOTONIC, &now); + + 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); + } +} + +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 %#x\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; + clock_gettime(CLOCK_MONOTONIC, + &p->files[file].last_write); + } + 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..00f3ecf --- /dev/null +++ b/src/utils/probes_demux.h @@ -0,0 +1,31 @@ +// 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); + +void parser_close_idle_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 new file mode 100644 index 0000000..359ff84 --- /dev/null +++ b/src/utils/sofprobeclient.c @@ -0,0 +1,371 @@ +/* + * 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 "probes_demux.h" + +static int verbose; +static int parser_debug; +static FILE *finfo; + +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 struct dma_frame_parser *parser; + +static void usage(void) +{ + 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" + "-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" + "-R\tSpecify the sample rate (default %u)\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\n" + "\tsofprobeclient -c 1 -d 2\n", + DEFAULT_CHANNELS, DEFAULT_RATE); + + 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 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; + char *buffer; + 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: + 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); + + memset(&codec, 0, sizeof(codec)); + memset(&config, 0, sizeof(config)); + 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); + return; + } + 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)); + return; + }; + + 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, "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 probe data 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 (parser_debug && (unsigned int)read != size) { + fprintf(stderr, "We read %d, DSP sent %d\n", + size, read); + } + + if (read > 0) { + int remaining = read; + char *src = buffer; + + total_read += read; + + /* 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; + } + + parser_close_idle_files(parser); + + 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)); + } + + /* Finalize any open wave files */ + finalize_wave_files(parser); + + if (verbose) + fprintf(finfo, "%s: exit success\n", __func__); + + free(buffer); + compress_close(compress); + + return; +buf_exit: + free(buffer); +comp_exit: + compress_close(compress); + + if (verbose) + fprintf(finfo, "%s: exit failure\n", __func__); + + exit(EXIT_FAILURE); +} + +static void sig_handler(int signum __attribute__ ((unused))) +{ + /* Finalize wave files on signal */ + if (parser) + finalize_wave_files(parser); + + _exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + unsigned long buffer_size = 8192; + 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; + + if (signal(SIGINT, sig_handler) == SIG_ERR) { + fprintf(stderr, "Error registering signal handler\n"); + exit(EXIT_FAILURE); + } + + /* 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; + finfo = stderr; + + while ((c = getopt(argc, argv, "hvDl:R:C:F: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 'D': + parser_debug = 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; + default: + exit(EXIT_FAILURE); + } + } + + 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); +}