mcu_hi3321_watch/application/audio/sample_audio_utils.c
2025-05-26 20:15:20 +08:00

571 lines
16 KiB
C

/*
* Copyright (c) CompanyNameMagicTag 2021-2021. All rights reserved.
* Description: sample audio utils
* Author: audio
* Create: 2021-07-27
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "securec.h"
#include "sample_audio_api.h"
#include "sample_audio_utils.h"
#ifndef ftell
#define ftell
#endif
#ifndef ftell
#define ftell
#endif
td_char *strcasestr(const td_char *, const td_char *);
static const td_char *g_samplerate_signs[] = {
"hz",
"khz",
"k_",
"k.",
};
static const td_char *g_channel_signs[] = {
"channel",
"ch_",
"ch.",
};
static const td_char *g_bit_signs[] = {
"bit",
};
static const td_char *find_sign_pos_in_line(const td_char *line, const td_char *sign)
{
td_char *sign_pos = strcasestr(line, sign);
td_char *num_pos = TD_NULL;
if (sign_pos == TD_NULL || sign_pos == line) {
return TD_NULL;
}
num_pos = sign_pos - 1;
while (num_pos >= line && (isdigit((td_s32)(*num_pos)) != 0 || *num_pos == '.')) {
num_pos--;
}
num_pos++;
return num_pos;
}
static double parse_sign_float_value(const td_char *line, const td_char *sign)
{
const td_char *num_pos = find_sign_pos_in_line(line, sign);
if (num_pos == TD_NULL) {
return 0.0;
}
return strtod(num_pos, TD_NULL);
}
static td_s32 parse_sign_int_value(const td_char *line, const td_char *sign)
{
const td_char *num_pos = find_sign_pos_in_line(line, sign);
if (num_pos == TD_NULL) {
return -1;
}
return (td_s32)strtol(num_pos, TD_NULL, 0);
}
static td_s32 parse_samplerate(const td_char *line)
{
const size_t sign_num = sizeof(g_samplerate_signs) / sizeof(g_samplerate_signs[0]);
td_u32 i;
td_s32 samplerate;
double vf;
for (i = 0; i < sign_num; i++) {
vf = parse_sign_float_value(line, g_samplerate_signs[i]);
if (strcasestr(g_samplerate_signs[i], "k") != TD_NULL) {
vf = vf * 1000; /* 1000 kHz to Hz */
}
samplerate = (td_s32)vf;
if (samplerate > 0) {
return samplerate;
}
}
return -1;
}
static td_s32 parse_sign(const td_char *line, const td_char **signs, td_u32 sign_num)
{
td_u32 i;
td_s32 v;
for (i = 0; i < sign_num; i++) {
v = parse_sign_int_value(line, signs[i]);
if (v > 0) {
return v;
}
}
return -1;
}
td_void parse_audio_params_from_file(const td_char *file, uapi_audio_pcm_format *pcm_format)
{
td_s32 channels;
td_s32 samplerate;
td_s32 bit_depth;
if (file == TD_NULL || pcm_format == TD_NULL) {
return;
}
channels = parse_sign(file, g_channel_signs, sizeof(g_channel_signs) / sizeof(g_channel_signs[0]));
samplerate = parse_samplerate(file);
bit_depth = parse_sign(file, g_bit_signs, sizeof(g_bit_signs) / sizeof(g_bit_signs[0]));
sap_printf("parsed from file = '%s': channels = %d, samplerate = %d, bit_depth = %d\n", file, channels, samplerate,
bit_depth);
if (channels <= 0 || samplerate <= 0 || bit_depth <= 0) {
return;
}
pcm_format->sample_rate = (td_u32)samplerate;
pcm_format->bit_depth = (td_u32)bit_depth;
pcm_format->channels = (td_u32)channels;
}
#define MAX_PATH_LEN 128
FILE *sample_audio_open_input_stream(const td_char *stream)
{
td_u32 i;
td_s32 ret;
FILE *file = TD_NULL;
const td_char *path_prefix[] = {
"/user/",
"/user/test/",
"/user/stream/",
"/music/",
"/music/test/",
"/music/stream/",
};
td_char path[MAX_PATH_LEN];
file = fopen(stream, "rb");
if (file != TD_NULL) {
return file;
}
for (i = 0; i < sizeof(path_prefix) / sizeof(path_prefix[0]); i++) {
ret = snprintf_s(path, sizeof(path) - 1, sizeof(path) - 1,
"%s%s", path_prefix[i], stream);
if (ret < 0) {
sap_err_log_fun(snprintf_s, ret);
return TD_NULL;
}
file = fopen(path, "rb");
if (file != TD_NULL) {
return file;
}
}
return TD_NULL;
}
td_u32 calc_pcm_frame_size(const uapi_audio_pcm_format *pcm_fmt)
{
size_t frame_size;
if (pcm_fmt == TD_NULL) {
return 0;
}
if (pcm_fmt->bit_depth == UAPI_AUDIO_BIT_DEPTH_16) {
frame_size = sizeof(td_s16);
} else {
frame_size = sizeof(td_s32);
}
return frame_size * pcm_fmt->channels * pcm_fmt->sample_per_frame;
}
static td_u32 pcm_sample_size(td_u32 channels, td_u32 bit_depth)
{
size_t depth_size;
switch (bit_depth) {
case UAPI_AUDIO_BIT_DEPTH_8:
depth_size = sizeof(td_s8);
break;
case UAPI_AUDIO_BIT_DEPTH_16:
depth_size = sizeof(td_s16);
break;
case UAPI_AUDIO_BIT_DEPTH_24:
case UAPI_AUDIO_BIT_DEPTH_32:
depth_size = sizeof(td_s32);
break;
default:
return 0;
}
return depth_size * channels;
}
/* how many bytes per frame */
td_u32 calc_audio_frame_size(const uapi_audio_frame *frame)
{
if (frame == TD_NULL) {
return 0;
}
if (frame->channels < UAPI_AUDIO_CHANNEL_1 || frame->channels > UAPI_AUDIO_CHANNEL_16) {
return 0;
}
return pcm_sample_size(frame->channels, frame->bit_depth) * frame->pcm_samples;
}
typedef struct {
const td_char *name;
uapi_acodec_id acodec_id;
} acodec_type_map;
static acodec_type_map g_acodec_type_map[] = {
{"pcm", UAPI_ACODEC_ID_PCM},
{"mp3", UAPI_ACODEC_ID_MP3},
{"aac", UAPI_ACODEC_ID_AAC},
{"sbc", UAPI_ACODEC_ID_SBC},
{"msbc", UAPI_ACODEC_ID_MSBC},
{"opus", UAPI_ACODEC_ID_OPUS},
{"flac", UAPI_ACODEC_ID_FLAC},
{"l2hc", UAPI_ACODEC_ID_L2HC},
{"lc3", UAPI_ACODEC_ID_LC3},
{"silk", UAPI_ACODEC_ID_SILK},
{"vorbis", UAPI_ACODEC_ID_VORBIS},
{"amrwb", UAPI_ACODEC_ID_AMRWB},
};
td_s32 sample_audio_get_acodec_id(const td_char *name, uapi_acodec_id *acodec_id)
{
td_u32 i;
const size_t num = sizeof(g_acodec_type_map) / sizeof(acodec_type_map);
for (i = 0; i < num; i++) {
if (strcasecmp(name, g_acodec_type_map[i].name) == 0) {
*acodec_id = g_acodec_type_map[i].acodec_id;
return EXT_SUCCESS;
}
}
*acodec_id = UAPI_ACODEC_ID_MAX;
return EXT_FAILURE;
}
#define I2S_MCLK_DEF 12288000 /* i2s mclk default 12.288M */
static uapi_audio_i2s_mclk_sel get_i2s_mclk_div(uapi_audio_sample_rate sample_rate)
{
const td_u32 clk_div = I2S_MCLK_DEF / (td_u32)sample_rate;
const struct {
uapi_audio_i2s_mclk_sel mclk_div_e;
td_u32 mclk_div;
} div_tab[] = {
{UAPI_AUDIO_I2S_MCLK_128_FS, 128}, /* 128 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_256_FS, 256}, /* 256 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_384_FS, 384}, /* 384 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_512_FS, 512}, /* 512 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_768_FS, 768}, /* 768 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_1024_FS, 1024}, /* 1024 = mclk / fs */
{UAPI_AUDIO_I2S_MCLK_1536_FS, 1536}, /* 1536 = mclk / fs */
};
sap_trace_log_u32(sample_rate);
sap_trace_log_u32(clk_div);
for (td_u32 i = 0; i < sizeof(div_tab) / sizeof(div_tab[0]); i++) {
if (div_tab[i].mclk_div == clk_div) {
return div_tab[i].mclk_div_e;
}
}
return UAPI_AUDIO_I2S_MCLK_MAX;
}
static uapi_audio_i2s_bclk_sel get_i2s_bclk_div(uapi_audio_channel channels, uapi_audio_bit_depth bit_depth,
uapi_audio_sample_rate sample_rate)
{
td_u32 bclk_hz = (td_u32)sample_rate * (td_u32)bit_depth * (td_u32)channels;
sap_trace_log_u32(bclk_hz);
return (uapi_audio_i2s_bclk_sel)(I2S_MCLK_DEF / bclk_hz);
}
static uapi_audio_i2s_mode get_i2s_mode(uapi_audio_channel channels)
{
uapi_audio_i2s_mode i2s_mode;
/* Set I2S mode according to input channels */
if (channels == UAPI_AUDIO_CHANNEL_1) {
sap_alert_log_info("i2s_pcm mode");
i2s_mode = UAPI_AUDIO_I2S_PCM_MODE;
} else if (channels == UAPI_AUDIO_CHANNEL_2) {
sap_alert_log_info("i2s_std mode");
i2s_mode = UAPI_AUDIO_I2S_STD_MODE;
} else {
sap_alert_log_info("i2s_tdm mode");
i2s_mode = UAPI_AUDIO_I2S_TDM_MODE;
}
sap_trace_log_u32(i2s_mode);
return i2s_mode;
}
td_void get_i2s_attr(uapi_audio_i2s_attr *i2s_attr, const uapi_audio_pcm_format *pcm_fmt, td_bool local_out_port)
{
i2s_attr->bit_depth = pcm_fmt->bit_depth;
i2s_attr->channels = pcm_fmt->channels;
/* Set I2S mode according to output channels */
i2s_attr->i2s_mode = get_i2s_mode(pcm_fmt->channels);
i2s_attr->mclk = get_i2s_mclk_div(pcm_fmt->sample_rate); /* The fs division from mclk */
if (local_out_port == TD_TRUE) { /* Local output port : SmartPA Speaker */
i2s_attr->master = SND_OUT_I2S_MST_DEF;
i2s_attr->pcm_sample_rise_edge = SND_OUT_I2S_DATA_EDGE_DEF;
i2s_attr->bclk = get_i2s_bclk_div(pcm_fmt->channels, SND_OUT_FRAME_MODE_WORD_LEN, pcm_fmt->sample_rate);
} else { /* Cat1 port */
i2s_attr->master = SIO_CAT1_I2S_MST_DEF;
i2s_attr->pcm_sample_rise_edge = SIO_CAT1_I2S_DATA_EDGE_DEF;
i2s_attr->pcm_delay_cycle = SIO_CAT1_I2S_PCM_DELAY_DEF;
i2s_attr->bclk = get_i2s_bclk_div(pcm_fmt->channels, SIO_CAT1_FRAME_MODE_WORD_LEN, pcm_fmt->sample_rate);
}
sap_trace_log_u32(i2s_attr->mclk);
sap_trace_log_u32(i2s_attr->bclk);
}
#define FLAC_HEADER_SIZE 4
#define ID3V2_HEADER_SIZE 10
/* Default magic bytes for ID3v2 header: "ID3" */
#define ID3V2_DEFAULT_MAGIC "ID3"
#define av_rb32(x) \
(((uint32_t)((const uint8_t*)(x))[0] << 24) | \
(((const uint8_t*)(x))[1] << 16) | \
(((const uint8_t*)(x))[2] << 8) | \
((const uint8_t*)(x))[3])
#define flac_tag(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
typedef union {
/* Define the struct bits */
struct {
unsigned int block_size : 24; /* 24 bits is metadata size */
unsigned int block_type : 7; /* 7 bits is metadata type */
unsigned int last : 1; /* 1 bits represent last metadata block */
} bits;
/* Define an unsigned member */
unsigned int u32;
} flac_metadata_header;
static td_bool ff_id3v2_match(const uint8_t *buf, const char *magic)
{
return buf[0] == magic[0] && /* 0 magic num */
buf[1] == magic[1] && /* 1 magic num */
buf[2] == magic[2] && /* 2 magic num */
buf[3] != 0xff && /* 3 magic num */
buf[4] != 0xff && /* 4 magic num */
(buf[6] & 0x80) == 0 && /* 6 magic num */
(buf[7] & 0x80) == 0 && /* 7 magic num */
(buf[8] & 0x80) == 0 && /* 8 magic num */
(buf[9] & 0x80) == 0; /* 9 magic num */
}
static td_u32 ff_id3v2_tag_len(const uint8_t *buf, td_u32 size)
{
td_u32 len;
len = ((buf[6] & 0x7f) << 21) + /* 6 represent sixth byte, 21 is shift num */
((buf[7] & 0x7f) << 14) + /* 7 represent seventh byte, 14 is shift num */
((buf[8] & 0x7f) << 7) + /* 8 represent eighth byte, 7 is shift num */
(buf[9] & 0x7f) + /* 9 represent ninth byte */
ID3V2_HEADER_SIZE;
if ((buf[5] & 0x10) != 0x0) { /* 5 represent fifth byte */
len += ID3V2_HEADER_SIZE;
}
sap_unused(size);
return len;
}
static td_void skip_id3v2_tag(FILE *in)
{
td_u32 tag_len;
td_u32 read_len;
td_u32 ret;
uint8_t header[ID3V2_HEADER_SIZE];
read_len = fread(header, 1, sizeof(header), in);
if (read_len != sizeof(header)) {
sap_printf("read failed!");
return;
}
if (!(ff_id3v2_match(header, ID3V2_DEFAULT_MAGIC))) {
rewind(in);
return;
}
tag_len = ff_id3v2_tag_len(header, sizeof(header));
tag_len -= ID3V2_HEADER_SIZE;
ret = (td_u32)fseek(in, tag_len, SEEK_CUR);
if (ret != 0) {
sap_printf("offset failed!");
}
}
static td_void skip_metadata(FILE *in)
{
td_u32 read_len;
td_u32 ret;
uint8_t header[FLAC_HEADER_SIZE];
flac_metadata_header metadata;
read_len = fread(header, sizeof(header), 1, in);
if (read_len != 1) {
sap_printf("read failed!");
return;
}
if (av_rb32(header) != flac_tag('f', 'L', 'a', 'C')) {
sap_printf("check fLaC header error");
return;
}
while (1) {
read_len = fread(header, sizeof(header), 1, in);
if (read_len != 1) {
sap_printf("read failed!");
return;
}
metadata.u32 = av_rb32(header);
ret = (td_u32)fseek(in, metadata.bits.block_size, SEEK_CUR);
if (ret != 0) {
sap_printf("offset failed!");
}
if (metadata.bits.last != 0x0) {
break;
}
}
}
td_void flac_skip_header(FILE *in)
{
skip_id3v2_tag(in);
skip_metadata(in);
}
#define MIN_ARG_NUM 2
#define INDEX_1 1
#define INDEX_2 2
static td_bool is_argv_end(const parg_state *ps, int argc, char *const argv[])
{
return (ps->optind >= argc || argv[ps->optind] == NULL) ? TD_TRUE : TD_FALSE;
}
static td_s32 match_short(parg_state *ps, int argc, char *const argv[], const char *optstring)
{
const char *p = strchr(optstring, *ps->nextchar);
if (p == NULL) {
ps->optopt = *ps->nextchar++;
return '?';
}
/* if no option argument, return option */
if (p[INDEX_1] != ':') {
return *ps->nextchar++;
}
/* if more characters, return as option argument */
if (ps->nextchar[INDEX_1] != '\0') {
ps->optarg = &ps->nextchar[1];
ps->nextchar = NULL;
return *p;
}
/* if option argument is optional, return option */
if (p[INDEX_2] == ':') {
return *ps->nextchar++;
}
/* option argument required, so return next argv element */
if (is_argv_end(ps, argc, argv)) {
ps->optopt = *ps->nextchar++;
return (optstring[0] == ':') ? ':' : '?';
}
ps->optarg = argv[ps->optind++];
ps->nextchar = NULL;
return *p;
}
td_void parg_init(parg_state *ps)
{
ps->optarg = NULL;
ps->optind = 1;
ps->optopt = '?';
ps->nextchar = NULL;
}
td_s32 parg_getopt(parg_state *ps, int argc, td_char *const argv[], const td_char *optstring)
{
if (ps == NULL || argv == NULL || optstring == NULL) {
return -1;
}
if (argc < MIN_ARG_NUM) {
return -1;
}
ps->optarg = NULL;
/* advance to next element if needed */
if (ps->nextchar == NULL || *ps->nextchar == '\0') {
if (is_argv_end(ps, argc, argv)) {
return -1;
}
ps->nextchar = argv[ps->optind++];
/* check for nonoption element (including '-') */
if (ps->nextchar[0] != '-' || ps->nextchar[1] == '\0') {
ps->optarg = ps->nextchar;
ps->nextchar = NULL;
return 1;
}
/* check for '--' */
if (ps->nextchar[INDEX_1] == '-') {
if (ps->nextchar[INDEX_2] == '\0') {
ps->nextchar = NULL;
return -1;
}
}
ps->nextchar++;
}
/* match nextchar */
return match_short(ps, argc, argv, optstring);
}