diff --git a/include/sound/compress_offload.h b/include/sound/compress_offload.h index 54e08a2..a21ddbc 100644 --- a/include/sound/compress_offload.h +++ b/include/sound/compress_offload.h @@ -21,7 +21,7 @@ #include #include #include -#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 2, 0) +#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 3, 0) struct snd_compressed_buffer { __u32 fragment_size; @@ -49,7 +49,8 @@ struct snd_compr_avail { enum snd_compr_direction { SND_COMPRESS_PLAYBACK = 0, - SND_COMPRESS_CAPTURE + SND_COMPRESS_CAPTURE, + SND_COMPRESS_ACCEL }; struct snd_compr_caps { @@ -79,6 +80,58 @@ struct snd_compr_metadata { __u32 value[8]; }__attribute__((packed, aligned(4))); + +/* flags for struct snd_compr_task */ +#define SND_COMPRESS_TFLG_NEW_STREAM (1<<0) /* mark for the new stream data */ + +/** + * struct snd_compr_task - task primitive for non-realtime operation + * @seqno: sequence number (task identifier) + * @origin_seqno: previous sequence number (task identifier) - for reuse + * @input_fd: data input file descriptor (dma-buf) + * @output_fd: data output file descriptor (dma-buf) + * @input_size: filled data in bytes (from caller, must not exceed fragment size) + * @flags: see SND_COMPRESS_TFLG_* defines + */ +struct snd_compr_task { + __u64 seqno; + __u64 origin_seqno; + int input_fd; + int output_fd; + __u64 input_size; + __u32 flags; + __u8 reserved[16]; +} __attribute__((packed, aligned(4))); + +/** + * enum snd_compr_state - task state + * @SND_COMPRESS_TASK_STATE_IDLE: task is not queued + * @SND_COMPRESS_TASK_STATE_ACTIVE: task is in the queue + * @SND_COMPRESS_TASK_STATE_FINISHED: task was processed, output is available + */ +enum snd_compr_state { + SND_COMPRESS_TASK_STATE_IDLE = 0, + SND_COMPRESS_TASK_STATE_ACTIVE, + SND_COMPRESS_TASK_STATE_FINISHED +}; + +/** + * struct snd_compr_task_status - task status + * @seqno: sequence number (task identifier) + * @input_size: filled data in bytes (from user space) + * @output_size: filled data in bytes (from driver) + * @output_flags: reserved for future (all zeros - from driver) + * @state: actual task state (SND_COMPRESS_TASK_STATE_*) + */ +struct snd_compr_task_status { + __u64 seqno; + __u64 input_size; + __u64 output_size; + __u32 output_flags; + __u8 state; + __u8 reserved[15]; +} __attribute__((packed, aligned(4))); + #define SNDRV_COMPRESS_IOCTL_VERSION _IOR('C', 0x00, int) #define SNDRV_COMPRESS_GET_CAPS _IOWR('C', 0x10, struct snd_compr_caps) #define SNDRV_COMPRESS_GET_CODEC_CAPS _IOWR('C', 0x11, struct snd_compr_codec_caps) @@ -98,4 +151,11 @@ struct snd_compr_metadata { #define SND_COMPR_TRIGGER_DRAIN 7 #define SND_COMPR_TRIGGER_NEXT_TRACK 8 #define SND_COMPR_TRIGGER_PARTIAL_DRAIN 9 + +#define SNDRV_COMPRESS_TASK_CREATE _IOWR('C', 0x60, struct snd_compr_task) +#define SNDRV_COMPRESS_TASK_FREE _IOW('C', 0x61, __u64) +#define SNDRV_COMPRESS_TASK_START _IOWR('C', 0x62, struct snd_compr_task) +#define SNDRV_COMPRESS_TASK_STOP _IOW('C', 0x63, __u64) +#define SNDRV_COMPRESS_TASK_STATUS _IOWR('C', 0x68, struct snd_compr_task_status) + #endif diff --git a/include/sound/compress_params.h b/include/sound/compress_params.h index 6844b97..49d71cb 100644 --- a/include/sound/compress_params.h +++ b/include/sound/compress_params.h @@ -253,8 +253,16 @@ union snd_codec_options { struct snd_dec_wma wma_d; struct snd_dec_alac alac_d; struct snd_dec_ape ape_d; + struct { + __u32 out_sample_rate; + } src_d; }__attribute__((packed, aligned(4))); +struct snd_codec_desc_src { + __u32 out_sample_rate_min; + __u32 out_sample_rate_max; +} __attribute__((packed, aligned(4))); + struct snd_codec_desc { __u32 max_ch; __u32 sample_rates[MAX_NUM_SAMPLE_RATES]; @@ -266,7 +274,12 @@ struct snd_codec_desc { __u32 modes; __u32 formats; __u32 min_buffer; - __u32 reserved[15]; + __u32 pcm_formats; + union { + __u32 u_space[6]; + struct snd_codec_desc_src src; + } __attribute__((packed, aligned(4))); + __u32 reserved[8]; }__attribute__((packed, aligned(4))); struct snd_codec { @@ -282,7 +295,8 @@ struct snd_codec { __u32 format; __u32 align; union snd_codec_options options; - __u32 reserved[3]; + __u32 pcm_format; + __u32 reserved[2]; }__attribute__((packed, aligned(4))); #endif diff --git a/include/tinycompress/compress_ops.h b/include/tinycompress/compress_ops.h index 2ee8d15..55bebfc 100644 --- a/include/tinycompress/compress_ops.h +++ b/include/tinycompress/compress_ops.h @@ -43,6 +43,11 @@ struct compress_ops { int (*is_compress_ready)(void *compress_data); const char *(*get_error)(void *compress_data); int (*set_codec_params)(void *compress_data, struct snd_codec *codec); + int (*task_create)(void *compress_data, struct compr_task *task); + int (*task_start)(void *compress_data, struct compr_task *task); + int (*task_stop)(void *compress_data, struct compr_task *task); + int (*task_free)(void *compress_data, struct compr_task *task); + int (*task_status)(void *compress_data, struct compr_task_status *status); }; #endif /* end of __COMPRESS_OPS_H__ */ diff --git a/include/tinycompress/tinycompress.h b/include/tinycompress/tinycompress.h index a8b4e58..254a829 100644 --- a/include/tinycompress/tinycompress.h +++ b/include/tinycompress/tinycompress.h @@ -82,6 +82,28 @@ struct compr_gapless_mdata { __u32 encoder_padding; }; +/* Same as struct snd_compr_task */ +struct compr_task { + __u64 seqno; + __u64 origin_seqno; + int input_fd; + int output_fd; + __u64 input_size; + __u32 flags; + __u8 reserved[16]; +}; + +/* Same as struct snd_compr_task_status */ +struct compr_task_status { + __u64 seqno; + __u64 input_size; + __u64 output_size; + __u32 output_flags; + __u8 state; + __u8 reserved[15]; +}; + +#define COMPRESS_ACCEL 0x40000000 #define COMPRESS_OUT 0x20000000 #define COMPRESS_IN 0x10000000 @@ -318,6 +340,12 @@ const char *compress_get_error(struct compress *compress); */ int compress_set_codec_params(struct compress *compress, struct snd_codec *codec); +int compress_task_create(struct compress *compress, struct compr_task *task); +int compress_task_start(struct compress *compress, struct compr_task *task); +int compress_task_stop(struct compress *compress, struct compr_task *task); +int compress_task_free(struct compress *compress, struct compr_task *task); +int compress_task_status(struct compress *compress, struct compr_task_status *status); + #if defined(__cplusplus) } // extern "C" #endif diff --git a/src/lib/compress.c b/src/lib/compress.c index 5a8fd04..ab9ff70 100644 --- a/src/lib/compress.c +++ b/src/lib/compress.c @@ -314,3 +314,28 @@ int compress_set_codec_params(struct compress *compress, struct snd_codec *codec { return compress->ops->set_codec_params(compress->data, codec); } + +int compress_task_create(struct compress *compress, struct compr_task *task) +{ + return compress->ops->task_create(compress->data, task); +} + +int compress_task_start(struct compress *compress, struct compr_task *task) +{ + return compress->ops->task_start(compress->data, task); +} + +int compress_task_stop(struct compress *compress, struct compr_task *task) +{ + return compress->ops->task_stop(compress->data, task); +} + +int compress_task_free(struct compress *compress, struct compr_task *task) +{ + return compress->ops->task_free(compress->data, task); +} + +int compress_task_status(struct compress *compress, struct compr_task_status *status) +{ + return compress->ops->task_status(compress->data, status); +} diff --git a/src/lib/compress_hw.c b/src/lib/compress_hw.c index 5aabae0..f1c7f53 100644 --- a/src/lib/compress_hw.c +++ b/src/lib/compress_hw.c @@ -163,16 +163,19 @@ static void *compress_hw_open_by_name(const char *name, compress->max_poll_wait_ms = DEFAULT_MAX_POLL_WAIT_MS; compress->flags = flags; - if (!((flags & COMPRESS_OUT) || (flags & COMPRESS_IN))) { + if (!((flags & COMPRESS_OUT) || (flags & COMPRESS_IN) || (flags & COMPRESS_ACCEL))) { oops(&bad_compress, EINVAL, "can't deduce device direction from given flags"); goto config_fail; } if (flags & COMPRESS_OUT) { compress->fd = open(fn, O_RDONLY); - } else { + } else if (flags & COMPRESS_IN) { compress->fd = open(fn, O_WRONLY); + } else { + compress->fd = open(fn, O_RDWR); } + if (compress->fd < 0) { oops(&bad_compress, errno, "cannot open device '%s'", fn); goto config_fail; @@ -598,6 +601,90 @@ static int compress_hw_set_codec_params(void *data, struct snd_codec *codec) return 0; } +static int compress_hw_task_create(void *data, struct compr_task *ctask) +{ + struct compress_hw_data *compress = (struct compress_hw_data *)data; + struct snd_compr_task task; + + if (!is_compress_hw_ready(compress)) + return oops(compress, ENODEV, "device not ready\n"); + + memcpy(&task, ctask, sizeof(task)); + + if (ioctl(compress->fd, SNDRV_COMPRESS_TASK_CREATE, &task)) + return oops(compress, errno, "cannot create task\n"); + + memcpy(ctask, &task, sizeof(task)); + + return 0; +} + +static int compress_hw_task_start(void *data, struct compr_task *ctask) +{ + struct compress_hw_data *compress = (struct compress_hw_data *)data; + struct snd_compr_task task; + + if (!is_compress_hw_ready(compress)) + return oops(compress, ENODEV, "device not ready\n"); + + memcpy(&task, ctask, sizeof(task)); + + if (ioctl(compress->fd, SNDRV_COMPRESS_TASK_START, &task)) + return oops(compress, errno, "cannot create task\n"); + + return 0; +} + +static int compress_hw_task_stop(void *data, struct compr_task *ctask) +{ + struct compress_hw_data *compress = (struct compress_hw_data *)data; + struct snd_compr_task task; + + if (!is_compress_hw_ready(compress)) + return oops(compress, ENODEV, "device not ready\n"); + + memcpy(&task, ctask, sizeof(task)); + + if (ioctl(compress->fd, SNDRV_COMPRESS_TASK_STOP, &task.seqno)) + return oops(compress, errno, "cannot create task\n"); + + return 0; +} + +static int compress_hw_task_free(void *data, struct compr_task *ctask) +{ + struct compress_hw_data *compress = (struct compress_hw_data *)data; + struct snd_compr_task task; + + if (!is_compress_hw_ready(compress)) + return oops(compress, ENODEV, "device not ready\n"); + + memcpy(&task, ctask, sizeof(task)); + + if (ioctl(compress->fd, SNDRV_COMPRESS_TASK_FREE, &task.seqno)) + return oops(compress, errno, "cannot create task\n"); + + return 0; +} + +static int compress_hw_task_status(void *data, struct compr_task_status *cstatus) +{ + struct compress_hw_data *compress = (struct compress_hw_data *)data; + struct snd_compr_task_status status; + + if (!is_compress_hw_ready(compress)) + return oops(compress, ENODEV, "device not ready\n"); + + memcpy(&status, cstatus, sizeof(status)); + + if (ioctl(compress->fd, SNDRV_COMPRESS_TASK_STATUS, &status)) + return oops(compress, errno, "cannot create task\n"); + + memcpy(cstatus, &status, sizeof(status)); + + return 0; +} + struct compress_ops compress_hw_ops = { .open_by_name = compress_hw_open_by_name, .close = compress_hw_close, @@ -621,5 +708,10 @@ struct compress_ops compress_hw_ops = { .is_compress_ready = is_compress_hw_ready, .get_error = compress_hw_get_error, .set_codec_params = compress_hw_set_codec_params, + .task_create = compress_hw_task_create, + .task_start = compress_hw_task_start, + .task_stop = compress_hw_task_stop, + .task_status = compress_hw_task_status, + .task_free = compress_hw_task_free, }; diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index 379247f..fa43f34 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -1,11 +1,14 @@ -bin_PROGRAMS = cplay crecord +bin_PROGRAMS = cplay crecord caccel cplay_SOURCES = cplay.c wave.c crecord_SOURCES = crecord.c wave.c +caccel_SOURCES = caccel.c wave.c alsa_wrap.c cplay_CFLAGS = -I$(top_srcdir)/include crecord_CFLAGS = -I$(top_srcdir)/include +caccel_CFLAGS = -I$(top_srcdir)/include cplay_LDADD = $(top_builddir)/src/lib/libtinycompress.la crecord_LDADD = $(top_builddir)/src/lib/libtinycompress.la +caccel_LDADD = $(top_builddir)/src/lib/libtinycompress.la -lasound diff --git a/src/utils/alsa_wrap.c b/src/utils/alsa_wrap.c new file mode 100644 index 0000000..683f8e4 --- /dev/null +++ b/src/utils/alsa_wrap.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: (LGPL-2.1-only OR BSD-3-Clause) + +#include + +snd_pcm_format_t alsa_snd_pcm_format_value(const char *name) +{ + return snd_pcm_format_value(name); +} + +int alsa_snd_pcm_format_physical_width(snd_pcm_format_t format) +{ + return snd_pcm_format_physical_width(format); +} + +int alsa_snd_pcm_format_width(snd_pcm_format_t format) +{ + return snd_pcm_format_width(format); +} + +int alsa_snd_pcm_format_linear(snd_pcm_format_t format) +{ + return snd_pcm_format_linear(format); +} + +int alsa_snd_pcm_format_signed(snd_pcm_format_t format) +{ + return snd_pcm_format_signed(format); +} diff --git a/src/utils/alsa_wrap.h b/src/utils/alsa_wrap.h new file mode 100644 index 0000000..56f8394 --- /dev/null +++ b/src/utils/alsa_wrap.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: (LGPL-2.1-only OR BSD-3-Clause) */ + +#ifndef __ALSA_WRAP_API_H +#define __ALSA_WRAP_API_H + +snd_pcm_format_t alsa_snd_pcm_format_value(const char *name); +int alsa_snd_pcm_format_physical_width(snd_pcm_format_t format); +int alsa_snd_pcm_format_width(snd_pcm_format_t format); +int alsa_snd_pcm_format_linear(snd_pcm_format_t format); +int alsa_snd_pcm_format_signed(snd_pcm_format_t format); +#endif + diff --git a/src/utils/caccel.c b/src/utils/caccel.c new file mode 100644 index 0000000..13eae17 --- /dev/null +++ b/src/utils/caccel.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: (LGPL-2.1-only OR BSD-3-Clause) +/* + * Copyright 2024 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sound/compress_params.h" +#include "sound/compress_offload.h" +#include "tinycompress/tinycompress.h" +#include "tinycompress/tinywave.h" + +#include "alsa_wrap.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#define DMA_BUF_SIZE 4096 +#define MAP_BUF_SIZE (512 * 1024) + +static int verbose; + +struct audio_info { + unsigned int card; + unsigned int device; + char *infile; + char *outfile; + unsigned int channels; + unsigned int in_rate; + unsigned short in_samplebits; + unsigned short in_blockalign; + unsigned int out_rate; + unsigned short out_samplebits; + unsigned short out_blockalign; + snd_pcm_format_t in_format; + snd_pcm_format_t out_format; + unsigned int in_dmabuf_size; +}; + +static void usage(void) +{ + fprintf(stderr, "usage: caccel [OPTIONS]\n" + "-c\tcard number\n" + "-d\tdevice node\n" + "-i\tinput wave file\n" + "-o\toutput wave file\n" + "-r\toutput rate\n" + "-f\toutput format\n" + "-b\tbuffer size\n" + "-v\tverbose mode\n" + "-h\tPrints this help list\n\n" + "Example:\n" + "\tcmemtomem -c 1 -d 2 -i input.wav -o output.wav\n" + "Valid codec: SRC\n"); +} + +static int parse_arguments(int argc, const char *argv[], struct audio_info *info) +{ + int c, option_index; + static const char short_options[] = "hvc:d:r:i:o:f:"; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"verbose", 0, 0, 'v'}, + {"card", 1, 0, 'c'}, + {"device", 1, 0, 'd'}, + {"inFile", 1, 0, 'i'}, + {"outFile", 1, 0, 'o'}, + {"outRate", 1, 0, 'r'}, + {"outFormat", 1, 0, 'f'}, + {0, 0, 0, 0} + }; + + if (argc < 3) + usage(); + + while ((c = getopt_long(argc, (char * const*)argv, short_options, + long_options, &option_index)) != -1) { + switch (c) { + case 'c': + info->card = strtol(optarg, NULL, 0); + break; + case 'd': + info->device = strtol(optarg, NULL, 0); + break; + case 'i': + info->infile = optarg; + break; + case 'o': + info->outfile = optarg; + break; + case 'r': + info->out_rate = strtol(optarg, NULL, 0); + break; + case 'f': + info->out_format = alsa_snd_pcm_format_value(optarg); + break; + case 'h': + usage(); + exit(EXIT_FAILURE); + case 'v': + verbose = 1; + break; + default: + fprintf(stderr, "Unknown Command -%c\n", c); + exit(EXIT_FAILURE); + } + } + + return 0; +} + +int main(int argc, const char *argv[]) +{ + struct wave_header in_header; + struct wave_header out_header; + struct audio_info info; + size_t read, written; + struct compr_task task = {}; + struct compr_task_status status = {}; + struct compress *compress; + struct compr_config config; + struct snd_codec codec; + FILE *fd_dst = NULL; + FILE *fd_src = NULL; + void *bufin_start; + void *bufout_start; + int length = 0; + int err = 0; + + verbose = 0; + memset(&info, 0, sizeof(struct audio_info)); + + info.out_format = SNDRV_PCM_FORMAT_S16_LE; + + if (parse_arguments(argc, argv, &info) != 0) + exit(EXIT_FAILURE); + + if (info.out_rate == 0) { + fprintf(stderr, "invalid output rate %d\n", info.out_rate); + exit(EXIT_FAILURE); + } + + fd_dst = fopen(info.outfile, "wb+"); + if (fd_dst <= 0) { + fprintf(stderr, "output file not found\n"); + goto err_dst_not_found; + } + + fd_src = fopen(info.infile, "r"); + if (fd_src <= 0) { + fprintf(stderr, "input file not found\n"); + goto err_src_not_found; + } + + read = fread(&in_header, 1, sizeof(in_header), fd_src); + if (read != sizeof(in_header)) { + fprintf(stderr, "Unable to read header\n"); + goto err_header_read; + } + + if (parse_wave_header(&in_header, &info.channels, &info.in_rate, + (unsigned int *)&info.in_format) == -1) { + fprintf(stderr, "Unable to parse header\n"); + goto err_header_read; + } + + info.in_blockalign = info.channels * in_header.fmt.samplebits / 8; + info.in_dmabuf_size = (DMA_BUF_SIZE / info.in_blockalign) * info.in_blockalign; + info.out_samplebits = alsa_snd_pcm_format_width(info.out_format); + + init_wave_header(&out_header, info.channels, info.out_rate, info.out_samplebits); + + written = fwrite(&out_header, 1, sizeof(out_header), fd_dst); + if (written != sizeof(out_header)) { + fprintf(stderr, "Error writing output file header: %s\n", + strerror(errno)); + goto err_header_read; + } + + codec.id = SND_AUDIOCODEC_PCM; + codec.ch_in = info.channels; + codec.ch_out = info.channels; + codec.format = info.in_format; + codec.sample_rate = info.in_rate; + codec.pcm_format = info.out_format; + codec.options.src_d.out_sample_rate = info.out_rate; + + config.codec = &codec; + compress = compress_open(info.card, info.device, COMPRESS_ACCEL, &config); + if (!compress || !is_compress_ready(compress)) { + fprintf(stderr, "Unable to open Compress device %d:%d\n", + info.card, info.device); + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto err_compress_open; + }; + + err = compress_task_create(compress, &task); + if (err < 0) { + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto err_task_create; + } + + bufin_start = mmap(NULL, MAP_BUF_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, task.input_fd, 0); + if (bufin_start == MAP_FAILED) { + fprintf(stderr, "Error mapping input buffer\n"); + goto err_mmap_in; + } + memset(bufin_start, 0, MAP_BUF_SIZE); + + bufout_start = mmap(NULL, MAP_BUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, + task.output_fd, 0); + if (bufout_start == MAP_FAILED) { + fprintf(stderr, "Error mapping output buffer\n"); + goto err_mmap_out; + } + memset(bufout_start, 0, MAP_BUF_SIZE); + + if (verbose) + printf("conversion is started\n"); + + status.seqno = task.seqno; + + do { + + read = fread(bufin_start, 1, info.in_dmabuf_size, fd_src); + if (read <= 0) + break; + + task.input_size = read; + + err = compress_task_start(compress, &task); + if (err < 0) { + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto err_process_err; + } + + err = compress_task_status(compress, &status); + if (err < 0) { + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto err_process_err; + } + + err = compress_task_stop(compress, &task); + if (err < 0) { + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); + goto err_process_err; + } + + written = fwrite(bufout_start, 1, status.output_size, fd_dst); + if (written != (size_t)status.output_size) { + fprintf(stderr, "Error writing output file: %s\n", + strerror(errno)); + goto err_process_err; + } + + } while (read > 0); + + fseek(fd_dst, 0L, SEEK_END); + length = ftell(fd_dst); + size_wave_header(&out_header, length - sizeof(out_header)); + fseek(fd_dst, 0L, SEEK_SET); + fwrite(&out_header, 1, sizeof(out_header), fd_dst); + + if (verbose) + printf("Conversion is finished\n"); + +err_process_err: + munmap(bufout_start, MAP_BUF_SIZE); +err_mmap_out: + munmap(bufin_start, MAP_BUF_SIZE); +err_mmap_in: + if (compress_task_free(compress, &task) < 0) + fprintf(stderr, "ERR: %s\n", compress_get_error(compress)); +err_task_create: + compress_close(compress); +err_compress_open: +err_header_read: + fclose(fd_src); +err_src_not_found: + fclose(fd_dst); +err_dst_not_found: + return err; +}