library for reading and resampling audio files
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

517 lines
12 KiB

/*
* Copyright (C) 2019-2020 Alexandros Theodotou <alex at zrythm dot org>
*
* This file is part of libaudec
*
* libaudec is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libaudec is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libaudec. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright (C) 2011-2013 Robin Gareus <robin@gareus.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <samplerate.h>
#include "ad_plugin.h"
AudecLogLevel ad_log_level =
AUDEC_LOG_LEVEL_ERROR;
audec_log_fn_t log_fn = NULL;
#define UNUSED(x) (void)(x)
int ad_eval_null(const char *f) { UNUSED(f); return -1; }
void * ad_open_null(const char *f, AudecInfo *n) { UNUSED(f); UNUSED(n); return NULL; }
int ad_close_null(void *x) { UNUSED(x); return -1; }
int ad_info_null(void *x, AudecInfo *n) { UNUSED(x); UNUSED(n); return -1; }
int64_t ad_seek_null(void *x, int64_t p) { UNUSED(x); UNUSED(p); return -1; }
ssize_t ad_read_null(void *x, float*d, size_t s) { UNUSED(x); UNUSED(d); UNUSED(s); return -1;}
typedef struct adecoder
{
/** Decoder backend plugin. */
ad_plugin const * plugin;
/** Backend data, such as SF file. */
void * data;
/* Log function. */
audec_log_fn_t log_fn;
} adecoder;
/* samplecat api */
void audec_init() { /* global init */ }
static ad_plugin const *
choose_backend (
const char * fn)
{
int max, val;
ad_plugin const * plugin = NULL;
max = 0;
val = adp_get_sndfile()->eval(fn);
if (val > max)
{
max = val;
plugin = adp_get_sndfile();
}
#if 0
val = adp_get_ffmpeg()->eval(fn);
if (val > max)
{
max = val;
plugin = adp_get_ffmpeg();
}
#endif
val = adp_get_minimp3()->eval(fn);
if (val > max)
{
max = val;
plugin = adp_get_minimp3();
}
return plugin;
}
AudecHandle *
audec_open (
const char * filename,
AudecInfo * nfo)
{
adecoder * decoder =
calloc (1, sizeof (adecoder));
audec_clear_nfo (nfo);
decoder->plugin = choose_backend (filename);
if (!decoder->plugin)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"fatal: no decoder backend available");
free(decoder);
return NULL;
}
decoder->data = decoder->plugin->open (filename, nfo);
if (!decoder->data)
{
free (decoder);
return NULL;
}
return (AudecHandle *) decoder;
}
int
audec_info (
AudecHandle * handle,
AudecInfo * nfo)
{
adecoder * decoder = (adecoder *) handle;
if (!decoder)
return -1;
return decoder->plugin->info (decoder->data, nfo);
}
int
audec_close (
AudecHandle * handle)
{
adecoder * decoder = (adecoder*) handle;
if (!decoder)
return -1;
int ret = decoder->plugin->close (decoder->data);
free (decoder);
return ret;
}
int64_t
audec_seek (
AudecHandle * handle,
int64_t pos)
{
adecoder * decoder = (adecoder*) handle;
if (!decoder) return -1;
return decoder->plugin->seek (decoder->data, pos);
}
/**
* Returns the size of the buffer that must be
* allocated to load the file with the given
* sample rate.
*/
static ssize_t
get_buf_size_for_sample_rate (
AudecInfo * nfo,
unsigned int sample_rate)
{
if (sample_rate == nfo->sample_rate)
{
return nfo->frames * nfo->channels;
}
else
{
double resample_ratio =
(double) sample_rate / nfo->sample_rate;
if (fabs (resample_ratio - 1.0) < 1e-20)
{
/* no sample rate change needed */
return
(ssize_t) nfo->frames *
(ssize_t) nfo->channels;
}
if (src_is_valid_ratio (resample_ratio) == 0)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"Sample rate change out of valid "
"range.");
return -1;
}
return
(ssize_t)
((double) nfo->frames * resample_ratio *
(double) nfo->channels);
}
}
typedef struct SrcCallbackData
{
float * in_frames;
size_t num_in_frames;
} SrcCallbackData;
static long
src_cb (
SrcCallbackData * data,
float ** audio)
{
*audio = &(data->in_frames[0]);
return data->num_in_frames;
}
ssize_t
audec_read (
AudecHandle * handle,
float ** out,
int sample_rate)
{
adecoder *decoder = (adecoder*) handle;
if (!decoder)
return -1;
if (*out != NULL)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"Please set 'out' to NULL before calling "
"audec_read()");
return -1;
}
/* get the info */
AudecInfo nfo;
audec_info ((AudecHandle *) decoder, &nfo);
/* read the frames */
size_t in_len =
(size_t) (nfo.frames * nfo.channels);
float * in = malloc (in_len * sizeof (float));
ssize_t ret = /* note: includes channels */
decoder->plugin->read (
decoder->data, in, in_len);
/* verify that the frames were all read */
if (ret != (ssize_t) in_len)
{
dbg (
AUDEC_LOG_LEVEL_DEBUG,
"Number of read in frames %zu not equal to "
"given buf size %zd",
ret, in_len);
}
if (ret > (ssize_t) in_len)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"Number of read in frames %zu greater than "
"given buf size %zd",
ret, in_len);
free (in);
return -1;
}
/* resample if required */
if (sample_rate > 0 &&
sample_rate != (int) nfo.sample_rate)
{
ssize_t out_buf_size =
get_buf_size_for_sample_rate (
&nfo, (unsigned int) sample_rate);
if (out_buf_size < 0)
{
free (in);
return -1;
}
else
{
int err;
SrcCallbackData data = {
.in_frames = in,
.num_in_frames = nfo.frames,
};
SRC_STATE * state =
src_callback_new (
(src_callback_t) src_cb,
SRC_SINC_BEST_QUALITY,
(int) nfo.channels, &err, &data);
if (!state)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"Failed to create a src callback: "
"%s", src_strerror (err));
free (in);
return -1;
}
double resample_ratio =
(1.0 * (double) sample_rate) /
nfo.sample_rate;
*out =
malloc (
(size_t) out_buf_size *
sizeof (float));
/* start reading */
ssize_t frames_read = -1;
ssize_t total_read = 0;
ssize_t frames_to_read = 0;
ssize_t num_out_frames =
(out_buf_size / (ssize_t) nfo.channels);
do
{
frames_to_read =
MIN (
6000,
num_out_frames - total_read);
frames_read =
src_callback_read (
state, resample_ratio,
frames_to_read,
&((*out)[total_read * nfo.channels]));
/* handle errors */
int err_ret =
src_error (state);
if (err_ret)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"An error occurred during "
"resampling: %s",
src_strerror (err_ret));
src_delete (state);
free (in);
free (*out);
*out = NULL;
return -1;
}
total_read += frames_read;
if (frames_read == -1)
break;
} while (frames_read > 0);
src_delete (state);
free (in);
if (total_read != num_out_frames)
{
dbg (
AUDEC_LOG_LEVEL_INFO,
"Total frames read (%zu) and out "
"frames expected (%zu) do not match",
total_read, num_out_frames);
}
if (frames_read == -1)
{
dbg (
AUDEC_LOG_LEVEL_ERROR,
"An error has occurred in "
"resampling: frames read == -1");
free (*out);
*out = NULL;
return -1;
}
ret = total_read;
dbg (
AUDEC_LOG_LEVEL_INFO,
"%zu frames read after resampling "
"(out buffer size %zu)",
total_read, out_buf_size);
}
}
else
{
*out = in;
ret = nfo.frames;
dbg (
AUDEC_LOG_LEVEL_INFO,
"No resampling done, returning %" PRIi64 " frames "
"(out buffer size %zu)",
nfo.frames, ret);
}
return ret;
}
/*
* side-effects: allocates buffer
*/
ssize_t
audec_read_mono_dbl (
void * sf,
AudecInfo * nfo,
double * d,
size_t len,
int sample_rate)
{
unsigned int c,f;
unsigned int chn = nfo->channels;
if (len<1) return 0;
static float *buf = NULL;
len = audec_read (sf, &buf, sample_rate);
for (f=0;f< (len/chn);f++) {
double val=0.0;
for (c=0;c<chn;c++) {
val+=buf[f*chn + c];
}
d[f]= val/chn;
}
return len/chn;
}
int
audec_finfo (
const char * filename, AudecInfo *nfo)
{
audec_clear_nfo (nfo);
void * sf = audec_open (filename, nfo);
return audec_close(sf) ? 1 : 0;
}
void
audec_clear_nfo (
AudecInfo *nfo)
{
memset (nfo, 0, sizeof (AudecInfo));
}
void
audec_free_nfo (
AudecInfo *nfo)
{
if (nfo->meta_data)
free (nfo->meta_data);
}
void
audec_dump_info (
AudecLogLevel dbglvl,
AudecInfo * nfo)
{
dbg(dbglvl, "sample_rate: %u", nfo->sample_rate);
dbg(dbglvl, "channels: %u", nfo->channels);
dbg(dbglvl, "length: %"PRIi64" ms", nfo->length);
dbg(dbglvl, "frames: %"PRIi64, nfo->frames);
dbg(dbglvl, "bit_rate: %d", nfo->bit_rate);
dbg(dbglvl, "bit_depth: %d", nfo->bit_depth);
dbg(dbglvl, "channels: %u", nfo->channels);
dbg(dbglvl, "meta-data: %s", nfo->meta_data?nfo->meta_data:"-");
}
void
ad_log (
const char * func,
AudecLogLevel level,
const char * format,
...)
{
va_list args;
va_start (args, format);
if (log_fn)
{
log_fn (level, format, args);
}
else if (level <= ad_log_level)
{
fprintf (stderr, "%s(): ", func);
vfprintf (stderr, format, args);
fprintf (stderr, "\n");
}
va_end (args);
}
void
audec_set_log_func (
audec_log_fn_t _log_fn)
{
log_fn = _log_fn;
}
void
audec_set_log_level (
AudecLogLevel lvl)
{
ad_log_level = lvl;
}