blob: 8525b2b81b25007ceb5847797cb8b69f643e4e85 [file] [log] [blame]
/*
* Copyright (c) 2018, Intel Corporation
* All rights reserved.
*
* 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 the 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.
*
* Author(s): Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
* Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
*/
/* file component for reading/writing pcm samples to/from a file */
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
#include <inttypes.h>
#include <sof/sof.h>
#include <sof/lock.h>
#include <sof/list.h>
#include <sof/stream.h>
#include <sof/work.h>
#include <sof/clock.h>
#include <sof/audio/component.h>
#include <sof/audio/format.h>
#include <sof/audio/pipeline.h>
#include <uapi/ipc.h>
#include "host/common_test.h"
#include "host/file.h"
static inline void buffer_check_wrap_32(int32_t **ptr, int32_t *end,
size_t size)
{
if (*ptr >= end)
*ptr = (int32_t *)((size_t)*ptr - size);
}
static inline void buffer_check_wrap_16(int16_t **ptr, int16_t *end,
size_t size)
{
if (*ptr >= end)
*ptr = (int16_t *)((size_t)*ptr - size);
}
/*
* Read 32-bit samples from file
* currently only supports txt files
*/
static int read_samples_32(struct comp_dev *dev, struct comp_buffer *sink,
int n, int fmt, int nch)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int32_t *dest = (int32_t *)sink->w_ptr;
int32_t sample;
int n_samples = 0;
int i, n_wrap, n_min, ret;
while (n > 0) {
n_wrap = (int32_t *)sink->end_addr - dest;
/* check for buffer wrap and copy to the end of the buffer */
n_min = (n < n_wrap) ? n : n_wrap;
while (n_min > 0) {
n -= nch;
n_min -= nch;
/* copy sample per channel */
for (i = 0; i < nch; i++) {
/* read sample from file */
switch (cd->fs.f_format) {
/* text input file */
case FILE_TEXT:
if (fmt == SOF_IPC_FRAME_S32_LE)
ret = fscanf(cd->fs.rfh, "%d",
dest);
/* mask bits if 24-bit samples */
if (fmt == SOF_IPC_FRAME_S24_4LE) {
ret = fscanf(cd->fs.rfh, "%d",
&sample);
*dest = sample & 0x00ffffff;
}
/* quit if eof is reached */
if (ret == EOF) {
cd->fs.reached_eof = 1;
goto quit;
}
break;
/* raw input file */
default:
if (fmt == SOF_IPC_FRAME_S32_LE)
ret = fread(dest,
sizeof(int32_t),
1, cd->fs.rfh);
/* mask bits if 24-bit samples */
if (fmt == SOF_IPC_FRAME_S24_4LE) {
ret = fread(&sample,
sizeof(int32_t),
1, cd->fs.rfh);
*dest = sample & 0x00ffffff;
}
/* quit if eof is reached */
if (ret != 1) {
cd->fs.reached_eof = 1;
goto quit;
}
break;
}
dest++;
n_samples++;
}
}
/* check for buffer wrap and update pointer */
buffer_check_wrap_32(&dest, sink->end_addr,
sink->size);
}
quit:
return n_samples;
}
/*
* Read 16-bit samples from file
* currently only supports txt files
*/
static int read_samples_16(struct comp_dev *dev, struct comp_buffer *sink,
int n, int nch)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int16_t *dest = (int16_t *)sink->w_ptr;
int i, n_wrap, n_min, ret;
int n_samples = 0;
/* copy samples */
while (n > 0) {
n_wrap = (int16_t *)sink->end_addr - dest;
/* check for buffer wrap and copy to the end of the buffer */
n_min = (n < n_wrap) ? n : n_wrap;
while (n_min > 0) {
n -= nch;
n_min -= nch;
/* copy sample per channel */
for (i = 0; i < nch; i++) {
/* read sample from file */
ret = fscanf(cd->fs.rfh, "%hd", dest);
switch (cd->fs.f_format) {
/* text input file */
case FILE_TEXT:
ret = fscanf(cd->fs.rfh, "%hd", dest);
if (ret == EOF) {
cd->fs.reached_eof = 1;
goto quit;
}
break;
/* rw pcm input file */
default:
ret = fread(dest, sizeof(int16_t), 1,
cd->fs.rfh);
if (ret != 1) {
cd->fs.reached_eof = 1;
goto quit;
}
break;
}
dest++;
n_samples++;
}
}
/* check for buffer wrap and update pointer */
buffer_check_wrap_16(&dest, sink->end_addr,
sink->size);
}
quit:
return n_samples;
}
/*
* Write 16-bit samples from file
* currently only supports txt files
*/
static int write_samples_16(struct comp_dev *dev, struct comp_buffer *source,
int n, int nch)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int16_t *src = (int16_t *)source->r_ptr;
int i, n_wrap, n_min, ret;
int n_samples = 0;
/* copy samples */
while (n > 0) {
n_wrap = (int16_t *)source->end_addr - src;
/* check for buffer wrap and copy to the end of the buffer */
n_min = (n < n_wrap) ? n : n_wrap;
while (n_min > 0) {
n -= nch;
n_min -= nch;
/* copy sample per channel */
for (i = 0; i < nch; i++) {
switch (cd->fs.f_format) {
/* text output file */
case FILE_TEXT:
ret = fprintf(cd->fs.wfh,
"%d\n", *src);
if (ret < 0)
goto quit;
break;
/* raw pcm output file */
default:
ret = fwrite(src,
sizeof(int16_t),
1, cd->fs.wfh);
if (ret != 1)
goto quit;
break;
}
src++;
n_samples++;
}
}
/* check for buffer wrap and update pointer */
buffer_check_wrap_16(&src, source->end_addr,
source->size);
}
quit:
return n_samples;
}
/*
* Write 32-bit samples from file
* currently only supports txt files
*/
static int write_samples_32(struct comp_dev *dev, struct comp_buffer *source,
int n, int fmt, int nch)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int32_t *src = (int32_t *)source->r_ptr;
int i, n_wrap, n_min, ret;
int n_samples = 0;
int32_t sample;
/* copy samples */
while (n > 0) {
n_wrap = (int32_t *)source->end_addr - src;
/* check for buffer wrap and copy to the end of the buffer */
n_min = (n < n_wrap) ? n : n_wrap;
while (n_min > 0) {
n -= nch;
n_min -= nch;
/* copy sample per channel */
for (i = 0; i < nch; i++) {
switch (cd->fs.f_format) {
/* text output file */
case FILE_TEXT:
if (fmt == SOF_IPC_FRAME_S32_LE)
ret = fprintf(cd->fs.wfh,
"%d\n", *src);
if (fmt == SOF_IPC_FRAME_S24_4LE) {
sample = *src << 8;
ret = fprintf(cd->fs.wfh,
"%d\n",
sample >> 8);
}
if (ret < 0)
goto quit;
break;
/* raw pcm output file */
default:
if (fmt == SOF_IPC_FRAME_S32_LE)
ret = fwrite(src,
sizeof(int32_t),
1, cd->fs.wfh);
if (fmt == SOF_IPC_FRAME_S24_4LE) {
sample = *src << 8;
sample >>= 8;
ret = fwrite(&sample,
sizeof(int32_t),
1, cd->fs.wfh);
}
if (ret != 1)
goto quit;
break;
}
/* increment read pointer */
src++;
/* increment number of samples written */
n_samples++;
}
}
/* check for buffer wrap and update pointer */
buffer_check_wrap_32(&src, source->end_addr,
source->size);
}
quit:
return n_samples;
}
/* function for processing 32-bit samples */
static int file_s32_default(struct comp_dev *dev, struct comp_buffer *sink,
struct comp_buffer *source, uint32_t frames)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int nch = dev->params.channels;
int n_samples = 0;
switch (cd->fs.mode) {
case FILE_READ:
/* read samples */
n_samples = read_samples_32(dev, sink, frames * nch,
SOF_IPC_FRAME_S32_LE, nch);
break;
case FILE_WRITE:
/* write samples */
n_samples = write_samples_32(dev, source, frames * nch,
SOF_IPC_FRAME_S32_LE, nch);
break;
default:
/* TODO: duplex mode */
break;
}
cd->fs.n += n_samples;
return n_samples;
}
/* function for processing 16-bit samples */
static int file_s16(struct comp_dev *dev, struct comp_buffer *sink,
struct comp_buffer *source, uint32_t frames)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int nch = dev->params.channels;
int n_samples = 0;
switch (cd->fs.mode) {
case FILE_READ:
/* read samples */
n_samples = read_samples_16(dev, sink, frames * nch, nch);
break;
case FILE_WRITE:
/* write samples */
n_samples = write_samples_16(dev, source, frames * nch, nch);
break;
default:
/* TODO: duplex mode */
break;
}
cd->fs.n += n_samples;
return n_samples;
}
/* function for processing 24-bit samples */
static int file_s24(struct comp_dev *dev, struct comp_buffer *sink,
struct comp_buffer *source, uint32_t frames)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
int nch = dev->params.channels;
int n_samples = 0;
switch (cd->fs.mode) {
case FILE_READ:
/* read samples */
n_samples = read_samples_32(dev, sink, frames * nch,
SOF_IPC_FRAME_S24_4LE, nch);
break;
case FILE_WRITE:
/* write samples */
n_samples = write_samples_32(dev, source, frames * nch,
SOF_IPC_FRAME_S24_4LE, nch);
break;
default:
/* TODO: duplex mode */
break;
}
cd->fs.n += n_samples;
return n_samples;
}
static enum file_format get_file_format(char *filename)
{
char *ext = strrchr(filename, '.');
if (!strcmp(ext, ".txt"))
return FILE_TEXT;
return FILE_RAW;
}
static struct comp_dev *file_new(struct sof_ipc_comp *comp)
{
struct comp_dev *dev;
struct sof_ipc_comp_file *file;
struct sof_ipc_comp_file *ipc_file =
(struct sof_ipc_comp_file *)comp;
struct file_comp_data *cd;
/* allocate memory for file comp */
dev = malloc(COMP_SIZE(struct sof_ipc_comp_file));
if (!dev)
return NULL;
/* copy file comp config */
file = (struct sof_ipc_comp_file *)&dev->comp;
memcpy(file, ipc_file, sizeof(struct sof_ipc_comp_file));
/* allocate memory for file comp data */
cd = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*cd));
if (!cd) {
free(dev);
return NULL;
}
comp_set_drvdata(dev, cd);
/* default function for processing samples */
cd->file_func = file_s32_default;
/* get filename from IPC and open file */
cd->fs.fn = strdup(ipc_file->fn);
/* set file format */
cd->fs.f_format = get_file_format(cd->fs.fn);
/* set file comp mode */
cd->fs.mode = ipc_file->mode;
/* open file handle(s) depending on mode */
switch (cd->fs.mode) {
case FILE_READ:
cd->fs.rfh = fopen(cd->fs.fn, "r");
if (!cd->fs.rfh) {
fprintf(stderr, "error: opening file %s\n", cd->fs.fn);
free(cd);
free(dev);
return NULL;
}
break;
case FILE_WRITE:
cd->fs.wfh = fopen(cd->fs.fn, "w");
if (!cd->fs.wfh) {
fprintf(stderr, "error: opening file %s\n", cd->fs.fn);
free(cd);
free(dev);
return NULL;
}
break;
default:
/* TODO: duplex mode */
break;
}
cd->fs.reached_eof = 0;
cd->fs.n = 0;
dev->state = COMP_STATE_READY;
return dev;
}
static void file_free(struct comp_dev *dev)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
if (cd->fs.mode == FILE_READ)
fclose(cd->fs.rfh);
else
fclose(cd->fs.wfh);
free(cd->fs.fn);
free(cd);
free(dev);
debug_print("free file component\n");
}
/* set component audio stream parameters */
static int file_params(struct comp_dev *dev)
{
struct file_comp_data *cd = comp_get_drvdata(dev);
struct sof_ipc_comp_config *config = COMP_GET_CONFIG(dev);
/* for file endpoint set the following from topology config */
if (cd->fs.mode == FILE_WRITE) {
dev->params.frame_fmt = config->frame_fmt;
if (dev->params.frame_fmt == SOF_IPC_FRAME_S16_LE)
dev->params.sample_container_bytes = 2;
else
dev->params.sample_container_bytes = 4;
}
/* Need to compute this in non-host endpoint */
dev->frame_bytes =
dev->params.sample_container_bytes * dev->params.channels;
/* calculate period size based on config */
cd->period_bytes = dev->frames * dev->frame_bytes;
/* File to sink supports only S32_LE/S16_LE/S24_4LE PCM formats */
if (config->frame_fmt != SOF_IPC_FRAME_S32_LE &&
config->frame_fmt != SOF_IPC_FRAME_S24_4LE &&
config->frame_fmt != SOF_IPC_FRAME_S16_LE)
return -EINVAL;
return 0;
}
static int fr_cmd(struct comp_dev *dev, struct sof_ipc_ctrl_data *cdata)
{
return -EINVAL;
}
static int file_trigger(struct comp_dev *dev, int cmd)
{
return comp_set_state(dev, cmd);
}
/* used to pass standard and bespoke commands (with data) to component */
static int file_cmd(struct comp_dev *dev, int cmd, void *data)
{
struct sof_ipc_ctrl_data *cdata = data;
int ret = 0;
switch (cmd) {
case COMP_CMD_SET_DATA:
ret = fr_cmd(dev, cdata);
break;
default:
break;
}
return ret;
}
/*
* copy and process stream samples
* returns the number of bytes copied
*/
static int file_copy(struct comp_dev *dev)
{
struct comp_buffer *buffer;
struct file_comp_data *cd = comp_get_drvdata(dev);
int ret = 0, bytes;
switch (cd->fs.mode) {
case FILE_READ:
/* file component sink buffer */
buffer = list_first_item(&dev->bsink_list, struct comp_buffer,
source_list);
/* test sink has enough free frames */
if (buffer->free >= cd->period_bytes && !cd->fs.reached_eof) {
/* read PCM samples from file */
ret = cd->file_func(dev, buffer, NULL, dev->frames);
/* update sink buffer pointers */
bytes = dev->params.sample_container_bytes;
if (ret > 0)
comp_update_buffer_produce(buffer,
ret * bytes);
}
break;
case FILE_WRITE:
/* file component source buffer */
buffer = list_first_item(&dev->bsource_list,
struct comp_buffer, sink_list);
/* test source has enough free frames */
if (buffer->avail >= cd->period_bytes) {
/* write PCM samples into file */
ret = cd->file_func(dev, NULL, buffer, dev->frames);
/* update source buffer pointers */
bytes = dev->params.sample_container_bytes;
if (ret > 0)
comp_update_buffer_consume(buffer,
ret * bytes);
}
break;
default:
/* TODO: duplex mode */
break;
}
return ret;
}
static int file_prepare(struct comp_dev *dev)
{
struct sof_ipc_comp_config *config = COMP_GET_CONFIG(dev);
struct comp_buffer *buffer = NULL;
struct file_comp_data *cd = comp_get_drvdata(dev);
int ret = 0, periods;
/* file component sink/source buffer period count */
switch (cd->fs.mode) {
case FILE_READ:
buffer = list_first_item(&dev->bsink_list, struct comp_buffer,
source_list);
periods = config->periods_sink;
break;
case FILE_WRITE:
buffer = list_first_item(&dev->bsource_list,
struct comp_buffer, sink_list);
periods = config->periods_source;
break;
default:
/* TODO: duplex mode */
break;
}
if (!buffer) {
fprintf(stderr, "error: no sink/source buffer\n");
return -EINVAL;
}
/* set downstream buffer size */
switch (config->frame_fmt) {
case(SOF_IPC_FRAME_S16_LE):
ret = buffer_set_size(buffer, dev->frames * 2 *
periods * dev->params.channels);
if (ret < 0) {
fprintf(stderr, "error: file buffer size set\n");
return ret;
}
buffer_reset_pos(buffer);
/* set file function */
cd->file_func = file_s16;
break;
case(SOF_IPC_FRAME_S24_4LE):
ret = buffer_set_size(buffer, dev->frames * 4 *
periods * dev->params.channels);
if (ret < 0) {
fprintf(stderr, "error: file buffer size set\n");
return ret;
}
buffer_reset_pos(buffer);
/* set file function */
cd->file_func = file_s24;
break;
case(SOF_IPC_FRAME_S32_LE):
ret = buffer_set_size(buffer, dev->frames * 4 *
periods * dev->params.channels);
if (ret < 0) {
fprintf(stderr, "error: file buffer size set\n");
return ret;
}
buffer_reset_pos(buffer);
break;
default:
return -EINVAL;
}
dev->state = COMP_STATE_PREPARE;
return ret;
}
static int file_reset(struct comp_dev *dev)
{
dev->state = COMP_STATE_INIT;
return 0;
}
struct comp_driver comp_file = {
.type = SOF_COMP_FILEREAD,
.ops = {
.new = file_new,
.free = file_free,
.params = file_params,
.cmd = file_cmd,
.trigger = file_trigger,
.copy = file_copy,
.prepare = file_prepare,
.reset = file_reset,
},
};
void sys_comp_file_init(void)
{
comp_register(&comp_file);
}