389 lines
12 KiB
C++
389 lines
12 KiB
C++
#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),当A1,A2相等时,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(¶ms);
|
||
/* 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;
|
||
}
|
||
|
||
} |