Hardware mixer for alsa

This commit is contained in:
badaix 2020-04-19 22:26:20 +02:00
parent bfbd9e05a7
commit 4ce69d4bfb
2 changed files with 227 additions and 20 deletions

View file

@ -28,15 +28,187 @@ using namespace std;
static constexpr auto LOG_TAG = "Alsa";
AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), handle_(nullptr)
AlsaPlayer::AlsaPlayer(const PcmDevice& pcmDevice, std::shared_ptr<Stream> stream) : Player(pcmDevice, stream), handle_(nullptr), ctl_(nullptr)
{
}
// typedef enum
// {
// AUDIO_VOLUME_SET,
// AUDIO_VOLUME_GET,
// } audio_volume_action;
// int audio_volume(audio_volume_action action, long& outvol)
// {
// static const char* mix_name = "Master";
// static const char* card = "default";
// static int mix_index = 0;
// long pmin, pmax;
// long get_vol, set_vol;
// float f_multi;
// if (action == AUDIO_VOLUME_GET)
// {
// if (snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &outvol) < 0)
// {
// snd_mixer_close(handle);
// return -6;
// }
// LOG(INFO, LOG_TAG) << "Get volume " << outvol << " with status " << ret << "\n";
// // make the value bound to 100
// outvol -= minv;
// maxv -= minv;
// minv = 0;
// outvol = 100 * outvol / maxv; // make the value bound from 0 to 100
// int val;
// if (snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &val) == 0)
// LOG(INFO, LOG_TAG) << "switch: " << val << "\n";
// }
// // else if (action == AUDIO_VOLUME_SET)
// // {
// // if (*outvol < 0 || *outvol > VOLUME_BOUND) // out of bounds
// // return -7;
// // *outvol = (*outvol * (maxv - minv) / (100 - 1)) + minv;
// // if (snd_mixer_selem_set_playback_volume(elem, 0, *outvol) < 0)
// // {
// // snd_mixer_close(handle);
// // return -8;
// // }
// // if (snd_mixer_selem_set_playback_volume(elem, 1, *outvol) < 0)
// // {
// // snd_mixer_close(handle);
// // return -9;
// // }
// // fprintf(stderr, "Set volume %i with status %i\n", *outvol, ret);
// // }
// snd_mixer_close(handle);
// return 0;
// }
void AlsaPlayer::setVolume(double volume)
{
int err = 0;
snd_mixer_elem_t* elem(nullptr);
snd_mixer_t* mixer(nullptr);
try
{
openMixer(&elem, &mixer);
// make the value bound to 100
long minv, maxv;
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &minv, &maxv)) < 0)
throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
LOG(INFO, LOG_TAG) << "Mixer volume range [" << minv << ", " << maxv << "]\n";
volume = volume * (maxv - minv) + minv;
std::cerr << "vol: " << volume << "\n";
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, volume)) < 0)
throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, volume)) < 0)
throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
}
catch (const std::exception& e)
{
LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
}
if (mixer != nullptr)
snd_mixer_close(mixer);
}
bool AlsaPlayer::getVolume(double& volume, bool& muted)
{
long vol;
int err = 0;
snd_mixer_elem_t* elem(nullptr);
snd_mixer_t* mixer(nullptr);
try
{
openMixer(&elem, &mixer);
if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &vol)) < 0)
throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
std::cerr << "vol: " << vol << "\n";
// make the value bound to 100
long minv, maxv;
if ((err = snd_mixer_selem_get_playback_volume_range(elem, &minv, &maxv)) < 0)
throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
LOG(INFO, LOG_TAG) << "Mixer volume range [" << minv << ", " << maxv << "]\n";
vol -= minv;
maxv = maxv - minv;
volume = 100. * static_cast<double>(vol) / static_cast<double>(maxv);
int val;
if ((err = snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &val)) < 0)
return false;
muted = (val == 0);
if (mixer != nullptr)
snd_mixer_close(mixer);
return true;
}
catch (const std::exception& e)
{
if (mixer != nullptr)
snd_mixer_close(mixer);
LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
return false;
}
}
void AlsaPlayer::openMixer(snd_mixer_elem_t** elem, snd_mixer_t** mixer)
{
snd_mixer_selem_id_t* sid;
long minv_, maxv_;
snd_mixer_selem_id_alloca(&sid);
std::string mix_name = "Master";
int mix_index = 0;
// sets simple-mixer index and name
snd_mixer_selem_id_set_index(sid, mix_index);
snd_mixer_selem_id_set_name(sid, mix_name.c_str());
int err;
if ((err = snd_mixer_open(mixer, 0)) < 0)
throw SnapException(std::string("Failed to open mixer, error: ") + snd_strerror(err));
if ((err = snd_mixer_attach(*mixer, pcmDevice_.name.c_str())) < 0)
throw SnapException("Failed to attach mixer to " + pcmDevice_.name + ", error: " + snd_strerror(err));
if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0)
throw SnapException(std::string("Failed to register selem, error: ") + snd_strerror(err));
if ((err = snd_mixer_load(*mixer)) < 0)
throw SnapException(std::string("Failed to load mixer, error: ") + snd_strerror(err));
*elem = snd_mixer_find_selem(*mixer, sid);
if (!elem)
throw SnapException(std::string("Failed to find selem, error: ") + snd_strerror(err));
}
void AlsaPlayer::initMixer()
{
int err;
if ((err = snd_ctl_open(&ctl_, pcmDevice_.name.c_str(), SND_CTL_READONLY)) < 0)
throw SnapException("Can't open control for " + pcmDevice_.name + ", error: " + snd_strerror(err));
if ((err = snd_ctl_subscribe_events(ctl_, 1)) < 0)
throw SnapException("Can't subscribe for events for " + pcmDevice_.name + ", error: " + snd_strerror(err));
fd_ = (pollfd*)malloc(sizeof(struct pollfd));
snd_ctl_poll_descriptors(ctl_, fd_, 1);
}
void AlsaPlayer::initAlsa()
{
unsigned int tmp, rate;
int pcm, channels;
int err, channels;
snd_pcm_hw_params_t* params;
const SampleFormat& format = stream_->getFormat();
@ -44,8 +216,8 @@ void AlsaPlayer::initAlsa()
channels = format.channels();
/* Open the PCM device in playback mode */
if ((pcm = snd_pcm_open(&handle_, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
throw SnapException("Can't open " + pcmDevice_.name + " PCM device: " + snd_strerror(pcm));
if ((err = snd_pcm_open(&handle_, pcmDevice_.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
throw SnapException("Can't open " + pcmDevice_.name + ", error: " + snd_strerror(err));
/* struct snd_pcm_playback_info_t pinfo;
if ( (pcm = snd_pcm_playback_info( pcm_handle, &pinfo )) < 0 )
@ -55,12 +227,12 @@ void AlsaPlayer::initAlsa()
/* Allocate parameters object and fill it with default values*/
snd_pcm_hw_params_alloca(&params);
if ((pcm = snd_pcm_hw_params_any(handle_, params)) < 0)
throw SnapException("Can't fill params: " + string(snd_strerror(pcm)));
if ((err = snd_pcm_hw_params_any(handle_, params)) < 0)
throw SnapException("Can't fill params: " + string(snd_strerror(err)));
/* Set parameters */
if ((pcm = snd_pcm_hw_params_set_access(handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
throw SnapException("Can't set interleaved mode: " + string(snd_strerror(pcm)));
if ((err = snd_pcm_hw_params_set_access(handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
throw SnapException("Can't set interleaved mode: " + string(snd_strerror(err)));
snd_pcm_format_t snd_pcm_format;
if (format.bits() == 8)
@ -74,8 +246,8 @@ void AlsaPlayer::initAlsa()
else
throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits()));
pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
if (pcm == -EINVAL)
err = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
if (err == -EINVAL)
{
if (snd_pcm_format == SND_PCM_FORMAT_S24_LE)
{
@ -88,12 +260,11 @@ void AlsaPlayer::initAlsa()
}
}
pcm = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
if (pcm < 0)
err = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
if (err < 0)
{
cerr << "error: " << pcm << "\n";
stringstream ss;
ss << "Can't set format: " << string(snd_strerror(pcm)) << ", supported: ";
ss << "Can't set format: " << string(snd_strerror(err)) << ", supported: ";
for (int format = 0; format <= (int)SND_PCM_FORMAT_LAST; format++)
{
snd_pcm_format_t snd_pcm_format = static_cast<snd_pcm_format_t>(format);
@ -103,11 +274,11 @@ void AlsaPlayer::initAlsa()
throw SnapException(ss.str());
}
if ((pcm = snd_pcm_hw_params_set_channels(handle_, params, channels)) < 0)
throw SnapException("Can't set channels number: " + string(snd_strerror(pcm)));
if ((err = snd_pcm_hw_params_set_channels(handle_, params, channels)) < 0)
throw SnapException("Can't set channels number: " + string(snd_strerror(err)));
if ((pcm = snd_pcm_hw_params_set_rate_near(handle_, params, &rate, nullptr)) < 0)
throw SnapException("Can't set rate: " + string(snd_strerror(pcm)));
if ((err = snd_pcm_hw_params_set_rate_near(handle_, params, &rate, nullptr)) < 0)
throw SnapException("Can't set rate: " + string(snd_strerror(err)));
unsigned int period_time;
snd_pcm_hw_params_get_period_time_max(params, &period_time, nullptr);
@ -124,8 +295,8 @@ void AlsaPlayer::initAlsa()
// LOG(ERROR, LOG_TAG) << "Unable to set buffer size " << (long int)periodsize << ": " << snd_strerror(pcm) << "\n";
/* Write parameters */
if ((pcm = snd_pcm_hw_params(handle_, params)) < 0)
throw SnapException("Can't set hardware parameters: " + string(snd_strerror(pcm)));
if ((err = snd_pcm_hw_params(handle_, params)) < 0)
throw SnapException("Can't set hardware parameters: " + string(snd_strerror(err)));
/* Resume information */
LOG(DEBUG, LOG_TAG) << "PCM name: " << snd_pcm_name(handle_) << "\n";
@ -151,6 +322,8 @@ void AlsaPlayer::initAlsa()
snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_);
// snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
snd_pcm_sw_params(handle_, swparams);
initMixer();
}
@ -162,6 +335,11 @@ void AlsaPlayer::uninitAlsa()
snd_pcm_close(handle_);
handle_ = nullptr;
}
if (ctl_ != nullptr)
{
snd_ctl_close(ctl_);
ctl_ = nullptr;
}
}
@ -210,6 +388,27 @@ void AlsaPlayer::worker()
continue;
}
auto err = poll(fd_, 1, 0);
if (err > 0)
{
unsigned short revents;
snd_ctl_poll_descriptors_revents(ctl_, fd_, 1, &revents);
if (revents & POLLIN)
{
snd_ctl_event_t* event;
snd_ctl_event_alloca(&event);
if ((snd_ctl_read(ctl_, event) >= 0) && (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM))
{
LOG(INFO, LOG_TAG) << "event\n";
double volume;
bool muted;
if (getVolume(volume, muted))
LOG(INFO, LOG_TAG) << "Volume: " << volume << ", muted: " << muted << "\n";
}
}
}
int wait_result = snd_pcm_wait(handle_, 100);
if (wait_result == -EPIPE)
{

View file

@ -39,6 +39,7 @@ public:
/// List the system's audio output devices
static std::vector<PcmDevice> pcm_list(void);
void setVolume(double volume) override;
protected:
void worker() override;
@ -46,8 +47,15 @@ protected:
private:
void initAlsa();
void uninitAlsa();
void initMixer();
bool getVolume(double& volume, bool& muted);
void openMixer(snd_mixer_elem_t** elem, snd_mixer_t** mixer);
snd_pcm_t* handle_;
snd_ctl_t* ctl_;
pollfd* fd_;
std::vector<char> buffer_;
snd_pcm_uframes_t frames_;
};