audio_demo/alsa_dev.cpp
2025-02-14 08:58:27 +08:00

389 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "alsa_dev.h"
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
#include <cmath>
#define LOG(fmt,...) printf(fmt,##__VA_ARGS__)
#define LOGF(file, fmt,...) fprintf(file,fmt,##__VA_ARGS__)
namespace alsa {
//----------------------------------------------
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 最大/小音量db
#define MIN_DB (-10)
#define MAX_DB (10)
// 最大/小音量: 0: 静音; 100:最大音量
#define MUTE_VOLUME (0)
#define MAX_VOLUME (100)
static int vol_scaler_init(int *scaler, int mindb, int maxdb);
typedef struct VolumeCtlUnit
{
int scaler[MAX_VOLUME + 1]; // 音量表
int zeroDb; // 0db在scaler中的索引
VolumeCtlUnit() {
// 音量控制器初始化
zeroDb = vol_scaler_init(scaler, MIN_DB, MAX_DB);
}
} volume_ctl_unit_t;
static volume_ctl_unit_t kVolCtrlUnit;
static int vol_scaler_init(int *scaler, int mindb, int maxdb)
{
double tabdb[MAX_VOLUME + 1];
double tabf [MAX_VOLUME + 1];
int z, i;
for (i = 0; i < (MAX_VOLUME + 1); i++) {
// (mindb, maxdb)平均分成(MAX_VOLUME + 1)份
tabdb[i] = mindb + (maxdb - mindb) * i / (MAX_VOLUME + 1);
// dB = 20 * log(A1 / A2)当A1A2相等时db为0
// 这里以(1 << 14)作为原始声音振幅,得到调节后的振幅(A1),将A1存入音量表中
tabf [i] = pow(10.0, tabdb[i] / 20.0);
scaler[i] = (int)((1 << 14) * tabf[i]); // Q14 fix point
}
z = -mindb * (MAX_VOLUME + 1) / (maxdb - mindb);
z = MAX(z, 0 );
z = MIN(z, MAX_VOLUME);
scaler[0] = 0; // 音量表中0标识静音
scaler[z] = (1 << 14);// (mindb, maxdb)的中间值作为0db即不做增益处理
return z;
}
void vol_scaler_run(int16_t *buf, int n, int volume)
{
/* 简易版
while (n--) {
*buf = (*buf) * multiplier / 100.0;
*buf = std::max((int)*buf, -0x7fff);
*buf = std::min((int)*buf, 0x7fff);
buf++;
}
*/
int multiplier = kVolCtrlUnit.scaler[volume];
if (multiplier > (1 << 14)) {
int32_t v;
while (n--) {
v = ((int32_t)*buf * multiplier) >> 14;
v = MAX(v,-0x7fff);
v = MIN(v, 0x7fff);
*buf++ = (int16_t)v;
}
} else if (multiplier < (1 << 14)) {
while (n--) {
*buf = ((int32_t)*buf * multiplier) >> 14;
buf++;
}
}
}
//----------------------------------------------
struct ConfigPrivate {
Config config;
int sample_size;
snd_pcm_uframes_t buffer;
snd_pcm_uframes_t period;
};
static int set_swparams(snd_pcm_t *handle, ConfigPrivate &config);
static int set_hwparams(snd_pcm_t *handle, ConfigPrivate &config);
static int xrun_recovery(snd_pcm_t *handle, int err);
static void msleep(size_t ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
static void usleep(size_t us) {
std::this_thread::sleep_for(std::chrono::microseconds(us));
}
class AlsaDev::Private
{
public:
Private() {
memset(&_config, 0, sizeof(_config));
}
~Private() {
destory();
}
int init(snd_pcm_stream_t type) {
int err = 0;
if ((err = snd_pcm_open(&_handle, _config.config.device, type, 0)) < 0) {
LOG("Playback open error: %s\n", snd_strerror(err));
return err;
}
if ((err = set_hwparams(_handle, _config)) < 0) {
LOG("Setting of hwparams failed: %s\n", snd_strerror(err));
return err;
}
if ((err = set_swparams(_handle, _config)) < 0) {
LOG("Setting of swparams failed: %s\n", snd_strerror(err));
return err;
}
LOG("alsa param, period: %d, buffer: %d\n", _config.period, _config.buffer);
return err;
}
void destory() {
if (_handle) {
snd_pcm_drain(_handle);
snd_pcm_close(_handle);
snd_pcm_hw_free(_handle);
}
_handle = nullptr;
}
public:
ConfigPrivate _config;
snd_pcm_t *_handle = nullptr;
};
AlsaDev::AlsaDev() : d_ptr{new Private()} {
}
AlsaDev::~AlsaDev() {
if (d_ptr) delete d_ptr;
}
int AlsaDev::applyConfig(Config &config)
{
memcpy(&d_ptr->_config.config, &config, sizeof(config));
d_ptr->_config.sample_size = snd_pcm_format_size(d_ptr->_config.config.format, 1);
d_ptr->_config.period = d_ptr->_config.config.period_time * d_ptr->_config.config.rate / 1000000;
d_ptr->_config.buffer = d_ptr->_config.period * d_ptr->_config.config.buffer_time / d_ptr->_config.config.period_time;
return 0;
}
int AlsaDev::init(snd_pcm_stream_t type) {
return d_ptr->init(type);
}
void AlsaDev::destory() {
return d_ptr->destory();
}
const char * AlsaDev::configToString() {
static char str[1024];
memset(str, 0, 1024);
sprintf(str, "{\n"
"\t device: %s\n"
"\t format: %d\n"
"\t channels: %d\n"
"\t rate: %d\n"
"\t bytes_per_sample: %d\n"
"\t period: %d\n"
"\t buffer: %d\n"
"}", d_ptr->_config.config.device, d_ptr->_config.config.format,
d_ptr->_config.config.channels, d_ptr->_config.config.rate, d_ptr->_config.sample_size,
d_ptr->_config.period, d_ptr->_config.buffer);
return str;
}
int AlsaDev::getSampleSize() {
return d_ptr->_config.sample_size;
}
int AlsaDev::getFrameSize() {
return d_ptr->_config.sample_size * d_ptr->_config.config.channels;
}
int AlsaDev::getFrames() {
return d_ptr->_config.period;
}
int AlsaDev::getHwBufferSize() {
return d_ptr->_config.buffer;
}
int AlsaDev::write(uint8_t *data, size_t bytes)
{
int bytes_per_frame = snd_pcm_frames_to_bytes(d_ptr->_handle, 1);
snd_pcm_sframes_t frames = bytes / bytes_per_frame;
snd_pcm_sframes_t gotten = 0;
int ret;
// LOG("frames = %d\n", frames);
while (frames > 0) {
ret = snd_pcm_writei(d_ptr->_handle, data, frames);
// LOG("snd_pcm_writei %d, ret=%d\n", frames, ret);
if (ret < 0) {
if (ret == -EAGAIN) {
usleep(100);
errno = -EAGAIN;
break;
}
if (xrun_recovery(d_ptr->_handle, ret) < 0) {
LOG("write error: %s \n", strerror(ret));
break;
}
}
frames -= ret;
gotten += ret;
}
return gotten;
}
int AlsaDev::read(uint8_t *data, size_t bytes) {
int bytes_per_frame = snd_pcm_frames_to_bytes(d_ptr->_handle, 1);
snd_pcm_sframes_t frames = bytes / bytes_per_frame;
snd_pcm_sframes_t gotten = 0;
int ret;
while (frames > 0) {
ret = snd_pcm_readi(d_ptr->_handle, data, frames);
if (ret < 0) {
if (ret == -EAGAIN) {
usleep(100);
errno = -EAGAIN;
break;
}
if (xrun_recovery(d_ptr->_handle, ret) < 0) {
LOG("Read error: %s \n", strerror(ret));
break;
}
}
frames -= ret;
gotten += ret;
}
return gotten;
}
static int set_hwparams(snd_pcm_t *handle, ConfigPrivate &config)
{
snd_pcm_hw_params_t *params;
unsigned int rrate;
snd_pcm_uframes_t size;
int err, dir;
snd_pcm_hw_params_alloca(&params);
/* choose all parameters */
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) {
LOG("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
return err;
}
/* set the interleaved read/write format */
err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
LOG("Access type not available for playback: %s\n", snd_strerror(err));
return err;
}
/* set the sample format */
err = snd_pcm_hw_params_set_format(handle, params, config.config.format);
if (err < 0) {
LOG("Sample format not available for playback: %s\n", snd_strerror(err));
return err;
}
/* set the count of channels */
err = snd_pcm_hw_params_set_channels(handle, params, config.config.channels);
if (err < 0) {
LOG("Channels count (%u) not available for playbacks: %s\n", config.config.channels, snd_strerror(err));
return err;
}
/* set the stream rate */
rrate = config.config.rate;
err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
if (err < 0) {
LOG("Rate %uHz not available for playback: %s\n", config.config.rate, snd_strerror(err));
return err;
}
if (rrate != config.config.rate) {
LOG("Rate doesn't match (requested %uHz, get %iHz)\n", config.config.rate, err);
return -EINVAL;
}
/* set the buffer time */
err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &config.config.buffer_time, &dir);
if (err < 0) {
LOG("Unable to set buffer time %u for playback: %s\n", config.config.buffer_time, snd_strerror(err));
return err;
}
err = snd_pcm_hw_params_get_buffer_size(params, &size);
if (err < 0) {
LOG("Unable to get buffer size for playback: %s\n", snd_strerror(err));
return err;
}
config.buffer = size;
/* set the period time */
err = snd_pcm_hw_params_set_period_time_near(handle, params, &config.config.period_time, &dir);
if (err < 0) {
LOG("Unable to set period time %u for playback: %s\n", config.config.period_time, snd_strerror(err));
return err;
}
err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
if (err < 0) {
LOG("Unable to get period size for playback: %s\n", snd_strerror(err));
return err;
}
config.period = size;
/* write the parameters to device */
err = snd_pcm_hw_params(handle, params);
if (err < 0) {
printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
return err;
}
return 0;
}
static int set_swparams(snd_pcm_t *handle, ConfigPrivate &config)
{
int err; snd_pcm_sw_params_t *swparams;
snd_pcm_sw_params_alloca(&swparams);
/* get the current swparams */
err = snd_pcm_sw_params_current(handle, swparams);
if (err < 0) {
LOG("Unable to determine current swparams for playback: %s\n", snd_strerror(err));
return err;
}
/* start the transfer when the buffer is almost full: */
/* (buffer_size / avail_min) * avail_min */
err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (config.buffer / config.period) * config.period);
if (err < 0) {
printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err));
return err;
}
// LOGF(stderr, "---%d--\r\n", (config.buffer / config.period) * config.period);
err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, (config.buffer / config.period) * config.period);
/* write the parameters to the playback device */
err = snd_pcm_sw_params(handle, swparams);
if (err < 0) {
LOG("Unable to set sw params for playback: %s\n", snd_strerror(err));
return err;
}
return 0;
}
static int xrun_recovery(snd_pcm_t *handle, int err)
{
#if 0
if (err == -EPIPE) { /* under-run */
err = snd_pcm_prepare(handle);
if (err < 0)
LOG("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
return 0;
} else if (err == -ESTRPIPE) {
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
sleep(1); /* wait until the suspend flag is released */
if (err < 0) {
LOG("stream recovery----%d\n", __LINE__);
err = snd_pcm_prepare(handle);
if (err < 0)
LOG("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
}
return 0;
}
#endif
if (err == -EPIPE) {
err = snd_pcm_prepare(handle);
if (err < 0) {
LOG("Unable to prepare PCM device: %s\n", (char *)snd_strerror(err));
}
} else {
LOG("Error recovery to PCM device: %s\n", (char *)snd_strerror(err));
}
return err;
}
}