#include "alsa_dev.h" #include #include #include #include #include #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; } }