init
This commit is contained in:
52
lib_audio_dsp/test/pipeline/app_pipeline/app_dsp.c
Normal file
52
lib_audio_dsp/test/pipeline/app_pipeline/app_dsp.c
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include "app_dsp.h"
|
||||
#include "stdbool.h"
|
||||
#include "xcore/chanend.h"
|
||||
#include "xcore/parallel.h"
|
||||
|
||||
#include <stages/adsp_pipeline.h>
|
||||
#include <adsp_generated_auto.h>
|
||||
|
||||
#if SEND_TEST_CONTROL_COMMANDS
|
||||
#include "run_control_cmds.h"
|
||||
#endif
|
||||
|
||||
static adsp_pipeline_t * m_dsp;
|
||||
|
||||
// send data to dsp
|
||||
void app_dsp_source(int32_t** data) {
|
||||
adsp_pipeline_source(m_dsp, data);
|
||||
}
|
||||
|
||||
// read output
|
||||
void app_dsp_sink(int32_t** data) {
|
||||
adsp_pipeline_sink(m_dsp, data);
|
||||
}
|
||||
|
||||
DECLARE_JOB(dsp_control_thread, (chanend_t, module_instance_t*, size_t));
|
||||
|
||||
void dsp_control_thread(chanend_t c_control, module_instance_t* modules, size_t num_modules)
|
||||
{
|
||||
chan_in_word(c_control);
|
||||
#if SEND_TEST_CONTROL_COMMANDS
|
||||
send_control_cmds(m_dsp, c_control);
|
||||
#endif
|
||||
chan_out_word(c_control, 0);
|
||||
}
|
||||
|
||||
// do dsp
|
||||
void app_dsp_main(chanend_t c_control) {
|
||||
m_dsp = adsp_auto_pipeline_init();
|
||||
|
||||
PAR_JOBS(
|
||||
PJOB(adsp_auto_pipeline_main, (m_dsp)),
|
||||
PJOB(dsp_control_thread, (c_control, m_dsp->modules, m_dsp->n_modules))
|
||||
);
|
||||
}
|
||||
|
||||
int app_dsp_frame_size() {
|
||||
while(!m_dsp);
|
||||
return m_dsp->input_mux.chan_cfg[0].frame_size;
|
||||
}
|
||||
28
lib_audio_dsp/test/pipeline/app_pipeline/app_dsp.h
Normal file
28
lib_audio_dsp/test/pipeline/app_pipeline/app_dsp.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <xccompat.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_CHANNELS (4)
|
||||
// XC safe wrapper for adsp
|
||||
|
||||
void app_dsp_init(void);
|
||||
|
||||
// send data to dsp
|
||||
void app_dsp_source(int32_t** data);
|
||||
|
||||
// read output
|
||||
void app_dsp_sink(int32_t** data);
|
||||
|
||||
// do dsp
|
||||
void app_dsp_main(chanend c_control);
|
||||
|
||||
// get frame size
|
||||
int app_dsp_frame_size();
|
||||
|
||||
// TODO control
|
||||
|
||||
|
||||
86
lib_audio_dsp/test/pipeline/app_pipeline/args_parsing.c
Normal file
86
lib_audio_dsp/test/pipeline/app_pipeline/args_parsing.c
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "fileio_task.h"
|
||||
|
||||
static void show_usage()
|
||||
{
|
||||
puts(
|
||||
"args: Specify in a single line in args.txt file.\n\n"
|
||||
" -i input wav file (eg. input.wav)\n\n"
|
||||
" -o output wav file (eg. -o output.wav)\n\n"
|
||||
" -n no. of output channels (eg. -o 2. If not specified, set to be same as input channels)\n\n"
|
||||
" -h Show this usage message and abort\n\n"
|
||||
" -t Input/output offset, number of samples to throw out at boot to account for pipeline length\n\n"
|
||||
);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void parse_one_arg(char *arg_part_1, test_config_t *test_config)
|
||||
{
|
||||
char *arg_part_2 = strtok(NULL, " ");
|
||||
|
||||
if(strcmp(arg_part_1, "-h") == 0)
|
||||
{
|
||||
show_usage();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(arg_part_2 != NULL);
|
||||
}
|
||||
|
||||
if(strcmp(arg_part_1, "-n") == 0)
|
||||
{
|
||||
test_config->num_output_channels = atoi(arg_part_2);
|
||||
}
|
||||
else if(strcmp(arg_part_1, "-t") == 0)
|
||||
{
|
||||
test_config->num_discard_frames = atoi(arg_part_2);
|
||||
}
|
||||
else if(strcmp(arg_part_1, "-i") == 0)
|
||||
{
|
||||
test_config->input_filename = malloc(strlen(arg_part_2)+1);
|
||||
strcpy(test_config->input_filename, arg_part_2);
|
||||
}
|
||||
else if(strcmp(arg_part_1, "-o") == 0)
|
||||
{
|
||||
test_config->output_filename = malloc(strlen(arg_part_2)+1);
|
||||
strcpy(test_config->output_filename, arg_part_2);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Error: Invalid argument %s\n", arg_part_1);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void parse_args(const char *args_file, test_config_t *test_config)
|
||||
{
|
||||
FILE *fp = fopen(args_file, "r");
|
||||
|
||||
// Get the length of the line first
|
||||
int count = 0;
|
||||
int ch = fgetc(fp);
|
||||
while((ch != '\n') && (ch != EOF)) {
|
||||
count += 1;
|
||||
ch = fgetc(fp);
|
||||
}
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
char *args = malloc((count+1)*sizeof(char));
|
||||
// Read the full line
|
||||
assert(fgets(args, count+1, fp) != NULL);
|
||||
printf("args = %s\n", args);
|
||||
|
||||
char *pch;
|
||||
pch = strtok(args, " ");
|
||||
while(pch != NULL)
|
||||
{
|
||||
parse_one_arg(pch, test_config);
|
||||
pch = strtok(NULL, " ");
|
||||
}
|
||||
}
|
||||
12
lib_audio_dsp/test/pipeline/app_pipeline/dspt_main.h
Normal file
12
lib_audio_dsp/test/pipeline/app_pipeline/dspt_main.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#pragma once
|
||||
|
||||
#include <xcore/chanend.h>
|
||||
#include <xcore/channel.h>
|
||||
#include <xcore/parallel.h>
|
||||
#include <stddef.h>
|
||||
#include "stages/adsp_module.h"
|
||||
|
||||
DECLARE_JOB(dsp_control_thread, (chanend_t, module_instance_t **, size_t));
|
||||
DECLARE_JOB(dsp_thread, (chanend_t, chanend_t, module_instance_t**, size_t));
|
||||
93
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/fileio.c
Normal file
93
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/fileio.c
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include "fileio.h"
|
||||
|
||||
int file_open(file_t *fp, const char* name, const char *mode) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
fp->xscope_file = xscope_open_file(name, (char*)mode);
|
||||
#else
|
||||
if(!strcmp(mode, "rb")) {
|
||||
fp->file = open(name, O_RDONLY);
|
||||
if(fp->file == -1) {return -1;}
|
||||
}
|
||||
else if(!strcmp(mode, "wb")) {
|
||||
fp->file = open(name, O_WRONLY|O_CREAT, 0644);
|
||||
if(fp->file == -1) {return -1;}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert((0) && "invalid file open mode specified. Only 'rb' and 'wb' modes supported");
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void file_seek(file_t *fp, long int offset, int origin) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
xscope_fseek(&fp->xscope_file, offset, origin);
|
||||
#else
|
||||
lseek(fp->file, offset, origin);
|
||||
#endif
|
||||
}
|
||||
|
||||
int get_current_file_offset(file_t *fp) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
int current_offset = xscope_ftell(&fp->xscope_file);
|
||||
#else
|
||||
int current_offset = lseek(fp->file, 0, SEEK_CUR);
|
||||
#endif
|
||||
return current_offset;
|
||||
}
|
||||
|
||||
int get_file_size(file_t *fp) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
//find the current offset in the file
|
||||
int current_offset = xscope_ftell(&fp->xscope_file);
|
||||
//go to the end
|
||||
xscope_fseek(&fp->xscope_file, 0, SEEK_END);
|
||||
//get offset which will be file size
|
||||
int size = xscope_ftell(&fp->xscope_file);
|
||||
//return back to the original offset
|
||||
xscope_fseek(&fp->xscope_file, current_offset, SEEK_SET);
|
||||
#else
|
||||
//find the current offset in the file
|
||||
int current_offset = lseek(fp->file, 0, SEEK_CUR);
|
||||
//get file size
|
||||
int size = lseek(fp->file, 0, SEEK_END);
|
||||
//go back to original offset
|
||||
lseek(fp->file, current_offset, SEEK_SET);
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
void file_read(file_t *fp, void *buf, size_t count) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
xscope_fread(&fp->xscope_file, (uint8_t*)buf, count);
|
||||
#else
|
||||
read(fp->file, buf, count);
|
||||
#endif
|
||||
}
|
||||
|
||||
void file_write(file_t *fp, void *buf, size_t count) {
|
||||
#if TEST_WAV_XSCOPE
|
||||
xscope_fwrite(&fp->xscope_file, (uint8_t*)buf, count);
|
||||
#else
|
||||
write(fp->file, buf, count);
|
||||
#endif
|
||||
}
|
||||
|
||||
void file_close(file_t *fp) {
|
||||
#if !TEST_WAV_XSCOPE
|
||||
close(fp->file);
|
||||
#else
|
||||
//files are closed by a single call to xscope_close_all_files()
|
||||
#endif
|
||||
}
|
||||
|
||||
void shutdown_session() {
|
||||
//Needed for XSCOPE_ID_HOST_QUIT in xscope_close_all_files()
|
||||
#if TEST_WAV_XSCOPE
|
||||
xscope_close_all_files();
|
||||
#endif
|
||||
}
|
||||
31
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/fileio.h
Normal file
31
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/fileio.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#if TEST_WAV_XSCOPE
|
||||
#include "xscope_io_device.h"
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef union {
|
||||
int file;
|
||||
#if TEST_WAV_XSCOPE
|
||||
xscope_file_t xscope_file;
|
||||
#endif
|
||||
}file_t;
|
||||
|
||||
|
||||
int file_open(file_t *fp, const char* name, const char *mode);
|
||||
void file_read(file_t *fp, void *buf, size_t count);
|
||||
void file_write(file_t *fp, void *buf, size_t count);
|
||||
void file_seek(file_t *fp, long int offset, int origin);
|
||||
void file_close(file_t *fp);
|
||||
void shutdown_session(); //Needed for XSCOPE_ID_HOST_QUIT in xscope_close_all_files()
|
||||
|
||||
int get_current_file_offset(file_t *fp);
|
||||
int get_file_size(file_t *fp);
|
||||
135
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/wav_utils.c
Normal file
135
lib_audio_dsp/test/pipeline/app_pipeline/file_utils/wav_utils.c
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2022-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include "fileio.h"
|
||||
#include "wav_utils.h"
|
||||
|
||||
#define RIFF_SECTION_SIZE (12)
|
||||
#define FMT_SUBCHUNK_MIN_SIZE (24)
|
||||
#define EXTENDED_FMT_GUID_SIZE (16)
|
||||
static const char wav_default_header[WAV_HEADER_BYTES] = {
|
||||
0x52, 0x49, 0x46, 0x46,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x57, 0x41, 0x56, 0x45,
|
||||
0x66, 0x6d, 0x74, 0x20,
|
||||
0x10, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x64, 0x61, 0x74, 0x61,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
int get_wav_header_details(file_t *input_file, wav_header *s, unsigned *header_size){
|
||||
//Assume file is already open here. First rewind.
|
||||
file_seek(input_file, 0, SEEK_SET);
|
||||
//read riff header section (12 bytes)
|
||||
file_read(input_file, (uint8_t*)(&s->riff_header[0]), RIFF_SECTION_SIZE);
|
||||
if(memcmp(s->riff_header, "RIFF", sizeof(s->riff_header)) != 0)
|
||||
{
|
||||
printf("Error: couldn't find RIFF: 0x%x, 0x%x, 0x%x, 0x%x\n", s->riff_header[0], s->riff_header[1], s->riff_header[2], s->riff_header[3]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(memcmp(s->wave_header, "WAVE", sizeof(s->wave_header)) != 0)
|
||||
{
|
||||
printf("couldn't find WAVE:, 0x%x, 0x%x, 0x%x, 0x%x\n", s->wave_header[0], s->wave_header[1], s->wave_header[2], s->wave_header[3]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
file_read(input_file, (uint8_t*)&s->fmt_header[0], FMT_SUBCHUNK_MIN_SIZE);
|
||||
if(memcmp(s->fmt_header, "fmt ", sizeof(s->fmt_header)) != 0)
|
||||
{
|
||||
printf("Error: couldn't find fmt: 0x%x, 0x%x, 0x%x, 0x%x\n", s->fmt_header[0], s->fmt_header[1], s->fmt_header[2], s->fmt_header[3]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned fmt_subchunk_actual_size = s->fmt_chunk_size + sizeof(s->fmt_header) + sizeof(s->fmt_chunk_size); //fmt_chunk_size doesn't include the fmt_header(4) and size(4) bytes
|
||||
unsigned fmt_subchunk_remaining_size = fmt_subchunk_actual_size - FMT_SUBCHUNK_MIN_SIZE;
|
||||
|
||||
if(s->audio_format == (short)0xfffe)
|
||||
{
|
||||
//seek to the end of fmt subchunk and rewind 16bytes to the beginning of GUID
|
||||
file_seek(input_file, fmt_subchunk_remaining_size - EXTENDED_FMT_GUID_SIZE, SEEK_CUR);
|
||||
//The first 2 bytes of GUID is the audio_format.
|
||||
file_read(input_file, (uint8_t *)&s->audio_format, sizeof(s->audio_format));
|
||||
//skip the rest of GUID
|
||||
file_seek(input_file, EXTENDED_FMT_GUID_SIZE - sizeof(s->audio_format), SEEK_CUR);
|
||||
}
|
||||
else
|
||||
{
|
||||
//go to the end of fmt subchunk
|
||||
file_seek(input_file, fmt_subchunk_remaining_size, SEEK_CUR);
|
||||
}
|
||||
if(s->audio_format != 1)
|
||||
{
|
||||
printf("Error: audio format(%d) is not PCM\n", s->audio_format);
|
||||
return 1;
|
||||
}
|
||||
|
||||
//read header (4 bytes) for the next subchunk
|
||||
file_read(input_file, (uint8_t*)&s->data_header[0], sizeof(s->data_header));
|
||||
//if next subchunk is fact, read subchunk size and skip it
|
||||
if(memcmp(s->data_header, "fact", sizeof(s->data_header)) == 0)
|
||||
{
|
||||
uint32_t chunksize;
|
||||
file_read(input_file, (uint8_t *)&chunksize, sizeof(s->data_bytes));
|
||||
file_seek(input_file, chunksize, SEEK_CUR);
|
||||
file_read(input_file, (uint8_t*)(&s->data_header[0]), sizeof(s->data_header));
|
||||
}
|
||||
//only thing expected at this point is the 'data' subchunk. Throw error if not found.
|
||||
if(memcmp(s->data_header, "data", sizeof(s->data_header)) != 0)
|
||||
{
|
||||
printf("Error: couldn't find data: 0x%x, 0x%x, 0x%x, 0x%x\n", s->data_header[0], s->data_header[1], s->data_header[2], s->data_header[3]);
|
||||
return 1;
|
||||
}
|
||||
//read data subchunk size.
|
||||
file_read(input_file, (uint8_t *)&s->data_bytes, sizeof(s->data_bytes));
|
||||
*header_size = get_current_file_offset(input_file); //total file size should be header_size + data_bytes
|
||||
//No need to close file - handled by caller
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wav_form_header(wav_header *header,
|
||||
short audio_format,
|
||||
short num_channels,
|
||||
int sample_rate,
|
||||
short bit_depth,
|
||||
int num_frames){
|
||||
memcpy((char*)header, wav_default_header, WAV_HEADER_BYTES);
|
||||
|
||||
header->audio_format = audio_format;
|
||||
header->num_channels = num_channels;
|
||||
header->sample_rate = sample_rate;
|
||||
header->bit_depth = bit_depth;
|
||||
|
||||
header->byte_rate = sample_rate*bit_depth*num_channels/8;
|
||||
|
||||
header->sample_alignment = num_channels* (bit_depth/8);
|
||||
int data_bytes = num_frames * num_channels * (bit_depth/8);
|
||||
header->data_bytes = data_bytes;
|
||||
header->wav_size = data_bytes + WAV_HEADER_BYTES - 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned wav_get_num_bytes_per_frame(const wav_header *s){
|
||||
int bytes_per_sample = s->bit_depth/CHAR_BIT;
|
||||
return (unsigned)(bytes_per_sample * s->num_channels);
|
||||
}
|
||||
|
||||
int wav_get_num_frames(const wav_header *s){
|
||||
unsigned bytes_per_frame = wav_get_num_bytes_per_frame(s);
|
||||
return s->data_bytes / bytes_per_frame;
|
||||
}
|
||||
|
||||
long wav_get_frame_start(const wav_header *s, unsigned frame_number, uint32_t wavheader_size){
|
||||
return wavheader_size + frame_number * wav_get_num_bytes_per_frame(s);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2022-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define WAV_HEADER_BYTES 44
|
||||
|
||||
typedef struct wav_header {
|
||||
// RIFF Header
|
||||
char riff_header[4]; // Should be "RIFF"
|
||||
int wav_size; // File size - 8 = data_bytes + WAV_HEADER_BYTES - 8
|
||||
char wave_header[4]; // Should be "WAVE"
|
||||
|
||||
// Format Subsection
|
||||
char fmt_header[4]; // Should be "fmt "
|
||||
int fmt_chunk_size; // Size of the rest of this subchunk
|
||||
short audio_format;
|
||||
short num_channels;
|
||||
int sample_rate;
|
||||
int byte_rate; // sample_rate * num_channels * (bit_depth/8)
|
||||
short sample_alignment; // num_channels * (bit_depth/8)
|
||||
short bit_depth; // bits per sample
|
||||
|
||||
// Data Subsection
|
||||
char data_header[4]; // Should be "data"
|
||||
int data_bytes; // frame count * num_channels * (bit_depth/8)
|
||||
} wav_header;
|
||||
|
||||
int get_wav_header_details(file_t *input_file, wav_header *s, unsigned *header_size);
|
||||
|
||||
int wav_form_header(wav_header *header,
|
||||
short audio_format,
|
||||
short num_channels,
|
||||
int sample_rate,
|
||||
short bit_depth,
|
||||
int num_frames);
|
||||
|
||||
unsigned wav_get_num_bytes_per_frame(const wav_header *s);
|
||||
|
||||
int wav_get_num_frames(const wav_header *s);
|
||||
|
||||
long wav_get_frame_start(const wav_header *s, unsigned frame_number, uint32_t wavheader_size);
|
||||
|
||||
234
lib_audio_dsp/test/pipeline/app_pipeline/fileio_task.c
Normal file
234
lib_audio_dsp/test/pipeline/app_pipeline/fileio_task.c
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <xcore/assert.h>
|
||||
#include <xcore/channel.h>
|
||||
#include <xcore/chanend.h>
|
||||
#include <xcore/select.h>
|
||||
#include "fileio.h"
|
||||
#include "wav_utils.h"
|
||||
#include "app_dsp.h"
|
||||
#include "fileio_task.h"
|
||||
#include "adsp_generated_auto.h"
|
||||
|
||||
|
||||
#define FILEREAD_CHUNK_SIZE (1024)
|
||||
|
||||
// Token to start control operations
|
||||
#define START_CONTROL_TOKEN (0x12345678)
|
||||
|
||||
/// @brief Read a chunk of data from the input file.
|
||||
/// Takes care of different bit-depths and reads the data in left justified 32bit format.
|
||||
/// @param input_file Input file handle
|
||||
/// @param input Buffer to read into
|
||||
/// @param input_header_struct wav header structure
|
||||
static void read_input_frame(file_t *input_file, int32_t *input, wav_header *input_header_struct)
|
||||
{
|
||||
unsigned bytes_per_frame = wav_get_num_bytes_per_frame(input_header_struct);
|
||||
|
||||
if(input_header_struct->bit_depth == 32)
|
||||
{
|
||||
// Read directly to input
|
||||
file_read (input_file, (uint8_t*)&input[0], bytes_per_frame * FILEREAD_CHUNK_SIZE); // Read in FILEREAD_CHUNK_SIZE chunks otherwise it's really slow
|
||||
}
|
||||
else if(input_header_struct->bit_depth == 16)
|
||||
{
|
||||
int16_t input16[ FILEREAD_CHUNK_SIZE * MAX_CHANNELS];
|
||||
file_read (input_file, (uint8_t*)&input16[0], bytes_per_frame * FILEREAD_CHUNK_SIZE);
|
||||
for(int i=0; i<FILEREAD_CHUNK_SIZE*input_header_struct->num_channels; i++)
|
||||
{
|
||||
input[i] = input16[i] << 16;
|
||||
}
|
||||
}
|
||||
else if(input_header_struct->bit_depth == 24)
|
||||
{
|
||||
uint8_t input8[FILEREAD_CHUNK_SIZE * MAX_CHANNELS * 3];
|
||||
uint8_t *inptr = input8;
|
||||
file_read (input_file, (uint8_t*)&input8[0], bytes_per_frame * FILEREAD_CHUNK_SIZE);
|
||||
for(int i=0; i<FILEREAD_CHUNK_SIZE; i++)
|
||||
{
|
||||
for(int ch=0; ch<input_header_struct->num_channels; ch++)
|
||||
{
|
||||
int32_t temp = 0;
|
||||
for(int b=0; b<3; b++)
|
||||
{
|
||||
uint32_t val = (uint32_t)(*inptr++);
|
||||
temp = temp | ((uint32_t)val << ((b+1)*8));
|
||||
}
|
||||
input[i*input_header_struct->num_channels + ch] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: Unsupported bit depth %d\n", input_header_struct->bit_depth);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void prepare_input(int32_t** dsp_input, int32_t* data, int n_ch, int frame_size) {
|
||||
for(int f = 0; f < frame_size; ++f) {
|
||||
for(int ch=0; ch < n_ch; ++ch) {
|
||||
dsp_input[ch][f] = *data;
|
||||
++data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void prepare_output(int32_t* data, int32_t** dsp_input, int n_ch, int frame_size) {
|
||||
for(int f = 0; f < frame_size; ++f) {
|
||||
for(int ch=0; ch < n_ch; ++ch) {
|
||||
*data = dsp_input[ch][f];
|
||||
++data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Task responsible for sending data read from a wav file to the pipeline and writing the output received
|
||||
/// from the pipeline to another file. File operations are done using xscope_fileio functions.
|
||||
/// @param c_control Channel used for control operations.
|
||||
void fileio_task(chanend_t c_control)
|
||||
{
|
||||
printf("In test app!\n");
|
||||
test_config_t test_config = {0};
|
||||
parse_args("args.txt", &test_config);
|
||||
|
||||
printf("After parse_args: Input file %s, Output file %s, num channels %d\n", test_config.input_filename, test_config.output_filename, test_config.num_output_channels);
|
||||
|
||||
assert(test_config.input_filename != NULL);
|
||||
assert(test_config.output_filename != NULL);
|
||||
|
||||
|
||||
file_t input_file, output_file;
|
||||
int ret = file_open(&input_file, test_config.input_filename, "rb");
|
||||
assert((!ret) && "Failed to open file");
|
||||
|
||||
ret = file_open(&output_file, test_config.output_filename, "wb");
|
||||
assert((!ret) && "Failed to open file");
|
||||
|
||||
wav_header input_header_struct, output_header_struct;
|
||||
unsigned input_header_size;
|
||||
if(get_wav_header_details(&input_file, &input_header_struct, &input_header_size) != 0){
|
||||
printf("error in get_wav_header_details()\n");
|
||||
_Exit(1);
|
||||
}
|
||||
|
||||
if(test_config.num_output_channels == 0)
|
||||
{
|
||||
test_config.num_output_channels = input_header_struct.num_channels;
|
||||
}
|
||||
|
||||
assert(input_header_struct.num_channels <= MAX_CHANNELS);
|
||||
assert(test_config.num_output_channels <= MAX_CHANNELS);
|
||||
|
||||
unsigned frame_count = wav_get_num_frames(&input_header_struct);
|
||||
// Calculate number of frames in the wav file
|
||||
unsigned block_count = frame_count / (FILEREAD_CHUNK_SIZE);
|
||||
xassert(0 != block_count);
|
||||
|
||||
wav_form_header(&output_header_struct,
|
||||
input_header_struct.audio_format,
|
||||
test_config.num_output_channels,
|
||||
input_header_struct.sample_rate,
|
||||
32, // Output always 32 bits
|
||||
block_count*FILEREAD_CHUNK_SIZE);
|
||||
|
||||
file_write(&output_file, (uint8_t*)(&output_header_struct), WAV_HEADER_BYTES);
|
||||
|
||||
unsigned bytes_per_frame = wav_get_num_bytes_per_frame(&input_header_struct);
|
||||
|
||||
printf("Num input channels = %d\n", input_header_struct.num_channels);
|
||||
printf("Num output channels = %d\n", test_config.num_output_channels);
|
||||
printf("Discard frames = %d\n", test_config.num_discard_frames);
|
||||
printf("bytes_per_frame = %d\n", bytes_per_frame);
|
||||
printf("Block count = %d\n", block_count);
|
||||
printf("Frame size = %d\n", app_dsp_frame_size());
|
||||
|
||||
int32_t input[ FILEREAD_CHUNK_SIZE * MAX_CHANNELS] = {0}; // Array for storing interleaved input read from wav file
|
||||
int32_t output[FILEREAD_CHUNK_SIZE * MAX_CHANNELS] = {0};
|
||||
xassert(FILEREAD_CHUNK_SIZE % app_dsp_frame_size() ==0); // frame size must be a factor of read size
|
||||
|
||||
int32_t** dsp_input = malloc(sizeof(int32_t*) * input_header_struct.num_channels);
|
||||
for(int i = 0; i < input_header_struct.num_channels; ++i) {
|
||||
dsp_input[i] = calloc(app_dsp_frame_size(), sizeof(int32_t));
|
||||
}
|
||||
int32_t** dsp_output = malloc(sizeof(int32_t*) * test_config.num_output_channels);
|
||||
for(int i = 0; i < test_config.num_output_channels; ++i) {
|
||||
dsp_output[i] = calloc(app_dsp_frame_size(), sizeof(int32_t));
|
||||
}
|
||||
|
||||
|
||||
// Send a token to indicate that the control parameters, if any, can be sent
|
||||
chan_out_word(c_control, START_CONTROL_TOKEN);
|
||||
const int pretest_zero_count = 64000; // MUST MATCH test_stages.py !!!!!
|
||||
for(int i = 0; i < pretest_zero_count/app_dsp_frame_size(); ++i) {
|
||||
// push data through pipeline so the control works.
|
||||
// the amount of data is fixed so that it matches
|
||||
// the test.
|
||||
app_dsp_source(dsp_input);
|
||||
app_dsp_sink(dsp_output);
|
||||
}
|
||||
// check that control is done.
|
||||
SELECT_RES(CASE_THEN(c_control, success), DEFAULT_THEN(error)) {
|
||||
success:
|
||||
chan_in_word(c_control);
|
||||
break;
|
||||
error:
|
||||
printf("Error Control should be done by now\n");
|
||||
_Exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
int discard = test_config.num_discard_frames;
|
||||
for(int i=0; i<block_count; i++)
|
||||
{
|
||||
printf("block %d\n", i);
|
||||
read_input_frame(&input_file, input, &input_header_struct);
|
||||
|
||||
int32_t* block_input = input;
|
||||
int32_t* block_output = output;
|
||||
|
||||
// 1. discard initial outputs which will be 0
|
||||
for(int d = 0; d < discard; d++) {
|
||||
prepare_input(dsp_input, block_input, input_header_struct.num_channels, app_dsp_frame_size());
|
||||
app_dsp_source(dsp_input);
|
||||
block_input += input_header_struct.num_channels * app_dsp_frame_size();
|
||||
|
||||
// discard these outputs
|
||||
app_dsp_sink(dsp_output);
|
||||
}
|
||||
|
||||
// 2. source and sink pipeline
|
||||
int loop_n = FILEREAD_CHUNK_SIZE/app_dsp_frame_size();//- test_config.num_discard_frames;
|
||||
for(int j=0; j < loop_n; j++)
|
||||
{
|
||||
prepare_input(dsp_input, block_input, input_header_struct.num_channels, app_dsp_frame_size());
|
||||
app_dsp_source(dsp_input);
|
||||
app_dsp_sink(dsp_output);
|
||||
prepare_output(block_output, dsp_output, test_config.num_output_channels, app_dsp_frame_size());
|
||||
|
||||
block_input += input_header_struct.num_channels * app_dsp_frame_size();
|
||||
block_output += test_config.num_output_channels * app_dsp_frame_size();
|
||||
}
|
||||
|
||||
// 3. read only, should empty the pipeline
|
||||
for(int d = 0; d < discard; d++) {
|
||||
app_dsp_sink(dsp_output);
|
||||
prepare_output(block_output, dsp_output, test_config.num_output_channels, app_dsp_frame_size());
|
||||
block_output += test_config.num_output_channels * app_dsp_frame_size();
|
||||
}
|
||||
|
||||
file_write(&output_file, (uint8_t*)&output[0], test_config.num_output_channels * FILEREAD_CHUNK_SIZE * sizeof(int32_t));
|
||||
discard = 0; // synchronisation complete
|
||||
}
|
||||
file_close(&input_file);
|
||||
file_close(&output_file);
|
||||
shutdown_session();
|
||||
adsp_auto_print_thread_max_ticks();
|
||||
|
||||
printf("DONE\n");
|
||||
_Exit(0);
|
||||
}
|
||||
14
lib_audio_dsp/test/pipeline/app_pipeline/fileio_task.h
Normal file
14
lib_audio_dsp/test/pipeline/app_pipeline/fileio_task.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#pragma once
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *input_filename;
|
||||
char *output_filename;
|
||||
int num_discard_frames;
|
||||
int num_output_channels;
|
||||
}test_config_t;
|
||||
|
||||
void parse_args(const char *args_file, test_config_t *test_config);
|
||||
|
||||
25
lib_audio_dsp/test/pipeline/app_pipeline/main.c
Normal file
25
lib_audio_dsp/test/pipeline/app_pipeline/main.c
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <xcore/assert.h>
|
||||
#include <xcore/parallel.h>
|
||||
#include <xcore/channel.h>
|
||||
#include <xcore/chanend.h>
|
||||
|
||||
DECLARE_JOB(fileio_task, (chanend_t));
|
||||
DECLARE_JOB(app_dsp_main, (chanend_t));
|
||||
|
||||
int main_c()
|
||||
{
|
||||
channel_t c_control = chan_alloc();
|
||||
|
||||
PAR_JOBS(
|
||||
PJOB(fileio_task, (c_control.end_a)),
|
||||
PJOB(app_dsp_main, (c_control.end_b))
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
29
lib_audio_dsp/test/pipeline/app_pipeline/main.xc
Normal file
29
lib_audio_dsp/test/pipeline/app_pipeline/main.xc
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include <platform.h>
|
||||
#include <xs1.h>
|
||||
#include <xscope.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __XC__
|
||||
#define chanend_t chanend
|
||||
#else
|
||||
#include <xcore/chanend.h>
|
||||
#endif
|
||||
#include "xscope_io_device.h"
|
||||
|
||||
extern int main_c();
|
||||
|
||||
int main (void)
|
||||
{
|
||||
chan xscope_chan;
|
||||
par
|
||||
{
|
||||
xscope_host_data(xscope_chan);
|
||||
on tile[0]: {
|
||||
xscope_io_init(xscope_chan);
|
||||
exit(main_c());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
93
lib_audio_dsp/test/pipeline/app_pipeline/run_control_cmds.c
Normal file
93
lib_audio_dsp/test/pipeline/app_pipeline/run_control_cmds.c
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#if SEND_TEST_CONTROL_COMMANDS
|
||||
|
||||
#include <string.h>
|
||||
#include "adsp_control.h"
|
||||
#include "adsp_instance_id_auto.h"
|
||||
#include "xcore/hwtimer.h"
|
||||
#include <xcore/assert.h>
|
||||
#include <debug_print.h>
|
||||
|
||||
#include "run_control_cmds.h"
|
||||
|
||||
#include "control_test_params.h"
|
||||
#include "print.h"
|
||||
#define CONTROL_COMMAND_TIMEOUT_TICKS 1000000 // one tick is 10ns
|
||||
#define CONTROL_COMMAND_DELAY_TICKS 1000 // one tick is 10ns
|
||||
|
||||
void send_control_cmds(adsp_pipeline_t * m_dsp, chanend_t c_control) {
|
||||
|
||||
adsp_stage_control_cmd_t cmd;
|
||||
int8_t payload_buf[CMD_PAYLOAD_MAX_SIZE];
|
||||
cmd.instance_id = control_stage_index;
|
||||
uint8_t values_write[CMD_PAYLOAD_MAX_SIZE];
|
||||
uint8_t values_read[CMD_PAYLOAD_MAX_SIZE];
|
||||
|
||||
adsp_controller_t ctrl;
|
||||
adsp_controller_init(&ctrl, m_dsp);
|
||||
|
||||
// Iterate through all the commands in the stage config
|
||||
for (int cmd_idx = 0; cmd_idx<CMD_TOTAL_NUM; cmd_idx++)
|
||||
{
|
||||
// Fill up the command fields
|
||||
cmd.cmd_id = control_config[cmd_idx].cmd_id;
|
||||
cmd.payload_len = control_config[cmd_idx].cmd_size;
|
||||
cmd.payload = payload_buf;
|
||||
memset(cmd.payload, 0, cmd.payload_len);
|
||||
|
||||
// Write control command to the stage
|
||||
memcpy(cmd.payload, control_config[cmd_idx].payload, cmd.payload_len);
|
||||
|
||||
// Save the payload values for the final check
|
||||
memcpy(values_write, cmd.payload, cmd.payload_len);
|
||||
|
||||
adsp_control_status_t ret = ADSP_CONTROL_BUSY;
|
||||
uint32_t time_start = get_reference_time();
|
||||
|
||||
// Write the data
|
||||
do {
|
||||
ret = adsp_write_module_config(&ctrl, &cmd);
|
||||
// Assert if operation is taking too long
|
||||
if (get_reference_time() > time_start + CONTROL_COMMAND_TIMEOUT_TICKS) {
|
||||
xassert(0 && "Timer expired while writing control command");
|
||||
}
|
||||
}while(ret == ADSP_CONTROL_BUSY);
|
||||
xassert(ret == ADSP_CONTROL_SUCCESS);
|
||||
|
||||
memset(cmd.payload, 0, cmd.payload_len);
|
||||
|
||||
// Add a delay of 10 ms to address the bug described in https://xmosjira.atlassian.net/browse/LCD-257
|
||||
hwtimer_t t_delay = hwtimer_alloc();
|
||||
hwtimer_delay(t_delay, CONTROL_COMMAND_DELAY_TICKS);
|
||||
hwtimer_free(t_delay);
|
||||
|
||||
// Read back the written data
|
||||
ret = ADSP_CONTROL_BUSY;
|
||||
time_start = get_reference_time();
|
||||
do {
|
||||
ret = adsp_read_module_config(&ctrl, &cmd);
|
||||
// Assert if operation is taking too long
|
||||
if (get_reference_time() > time_start + CONTROL_COMMAND_TIMEOUT_TICKS) {
|
||||
xassert(0 && "Timer expired while reading control command");
|
||||
}
|
||||
}while(ret == ADSP_CONTROL_BUSY);
|
||||
|
||||
xassert(ret == ADSP_CONTROL_SUCCESS);
|
||||
|
||||
memcpy(values_read, cmd.payload, cmd.payload_len);
|
||||
|
||||
// Check that read and written values match
|
||||
for(int i=0; i<cmd.payload_len; i++)
|
||||
{
|
||||
if(values_read[i] != values_write[i])
|
||||
{
|
||||
debug_printf("Command %d: mismatch at index %d. Expected %d, found %d\n", cmd.cmd_id, i, values_write[i], values_read[i]);
|
||||
xassert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
#include "adsp_pipeline.h"
|
||||
|
||||
// Function to write and read back control parameters.
|
||||
// Only the commands included in the stage config are used.
|
||||
void send_control_cmds(adsp_pipeline_t * m_dsp, chanend_t c_control);
|
||||
Reference in New Issue
Block a user