mirror of
https://github.com/Motion-Project/motion.git
synced 2026-06-11 15:24:42 -04:00
963 lines
29 KiB
C++
963 lines
29 KiB
C++
/*
|
|
* This file is part of Motion.
|
|
*
|
|
* Motion 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.
|
|
*
|
|
* Motion 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 Motion. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#include "motion.hpp"
|
|
#include "util.hpp"
|
|
#include "conf.hpp"
|
|
#include "logger.hpp"
|
|
#include "sound.hpp"
|
|
|
|
static void *sound_handler(void *arg)
|
|
{
|
|
((cls_sound *)arg)->handler();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void cls_sound::init_values()
|
|
{
|
|
#ifdef HAVE_FFTW3
|
|
snd_info->snd_fftw->bin_max = 0;
|
|
snd_info->snd_fftw->bin_min = 0;
|
|
snd_info->snd_fftw->bin_size = 0;
|
|
snd_info->snd_fftw->ff_in = NULL;
|
|
snd_info->snd_fftw->ff_out = NULL;
|
|
snd_info->snd_fftw->ff_plan = NULL;
|
|
#endif
|
|
snd_info->sample_rate = 0;
|
|
snd_info->channels = 0;
|
|
snd_info->vol_count = 0;
|
|
snd_info->vol_max = 0;
|
|
snd_info->vol_min = 9999;
|
|
snd_info->buffer = NULL;
|
|
snd_info->buffer_size = 0;
|
|
snd_info->pulse_server = "";
|
|
}
|
|
|
|
void cls_sound::init_alerts(ctx_snd_alert *tmp_alert)
|
|
{
|
|
tmp_alert->alert_id = 0;
|
|
tmp_alert->alert_nm = "";
|
|
tmp_alert->freq_high = 10000;
|
|
tmp_alert->freq_low = 0;
|
|
tmp_alert->volume_count = 0;
|
|
tmp_alert->volume_level = 0;
|
|
tmp_alert->trigger_count = 0;
|
|
tmp_alert->trigger_threshold = 10;
|
|
tmp_alert->trigger_duration = 10;
|
|
clock_gettime(CLOCK_MONOTONIC, &tmp_alert->trigger_time);
|
|
|
|
}
|
|
|
|
void cls_sound::edit_alerts()
|
|
{
|
|
std::list<ctx_snd_alert>::iterator it_a0;
|
|
std::list<ctx_snd_alert>::iterator it_a1;
|
|
int indx, id_chk, id_cnt;
|
|
bool validids;
|
|
|
|
validids = true;
|
|
for (it_a0=snd_info->alerts.begin(); it_a0!=snd_info->alerts.end(); it_a0++) {
|
|
id_chk = it_a0->alert_id;
|
|
id_cnt = 0;
|
|
for (it_a1=snd_info->alerts.begin(); it_a1!=snd_info->alerts.end(); it_a1++) {
|
|
if (id_chk == it_a1->alert_id) {
|
|
id_cnt++;
|
|
}
|
|
}
|
|
if (id_cnt > 1) {
|
|
validids = false;
|
|
}
|
|
}
|
|
|
|
if (validids == false) {
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Sound alert ids must be unique.");
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Creating new sound alert ids.");
|
|
indx = 0;
|
|
for (it_a0=snd_info->alerts.begin(); it_a0!=snd_info->alerts.end(); it_a0++) {
|
|
it_a0->alert_id = indx;
|
|
indx++;
|
|
}
|
|
}
|
|
|
|
for (it_a0=snd_info->alerts.begin(); it_a0!=snd_info->alerts.end(); it_a0++) {
|
|
if (it_a0->alert_nm == "") {
|
|
it_a0->alert_nm = "sound_alert" + std::to_string(it_a0->alert_id);
|
|
}
|
|
if (it_a0->volume_level < snd_info->vol_min) {
|
|
snd_info->vol_min = it_a0->volume_level;
|
|
}
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Sound Alert Parameters:");
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " alert_id: %d",it_a0->alert_id);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " alert_nm %s",it_a0->alert_nm.c_str());
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " freq_low: %.4f",it_a0->freq_low);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " freq_high: %.4f",it_a0->freq_high);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " volume_count: %d",it_a0->volume_count);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " volume_level: %d",it_a0->volume_level);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " trigger_threshold: %d",it_a0->trigger_threshold);
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, " trigger_duration: %d",it_a0->trigger_duration);
|
|
}
|
|
|
|
}
|
|
|
|
void cls_sound::load_alerts()
|
|
{
|
|
ctx_snd_alert tmp_alert;
|
|
std::list<std::string> parm_val;
|
|
std::list<std::string>::iterator it_a;
|
|
ctx_params *tmp_params;
|
|
ctx_params_item *itm;
|
|
int indx;
|
|
|
|
cfg->edit_get("snd_alerts", parm_val, PARM_CAT_18);
|
|
|
|
tmp_params = new ctx_params;
|
|
for (it_a=parm_val.begin(); it_a!=parm_val.end(); it_a++) {
|
|
util_parms_parse(tmp_params,"snd_alerts", it_a->c_str());
|
|
init_alerts(&tmp_alert);
|
|
for (indx=0;indx<tmp_params->params_cnt;indx++) {
|
|
itm = &tmp_params->params_array[indx];
|
|
if (itm->param_name == "alert_id") {
|
|
tmp_alert.alert_id = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "alert_nm") {
|
|
tmp_alert.alert_nm = itm->param_value;
|
|
}
|
|
if (itm->param_name == "freq_low") {
|
|
tmp_alert.freq_low = mtof(itm->param_value);
|
|
}
|
|
if (itm->param_name == "freq_high") {
|
|
tmp_alert.freq_high = mtof(itm->param_value);
|
|
}
|
|
if (itm->param_name == "volume_count") {
|
|
tmp_alert.volume_count = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "volume_level") {
|
|
tmp_alert.volume_level = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "trigger_threshold") {
|
|
tmp_alert.trigger_threshold = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "trigger_duration") {
|
|
tmp_alert.trigger_duration = mtoi(itm->param_value);
|
|
}
|
|
}
|
|
snd_info->alerts.push_back(tmp_alert);
|
|
}
|
|
|
|
mydelete(tmp_params);
|
|
|
|
edit_alerts();
|
|
}
|
|
|
|
void cls_sound::load_params()
|
|
{
|
|
int indx;
|
|
ctx_params_item *itm;
|
|
|
|
util_parms_parse(snd_info->params,"snd_params", cfg->snd_params);
|
|
|
|
util_parms_add_default(snd_info->params,"source","alsa");
|
|
util_parms_add_default(snd_info->params,"channels","1");
|
|
util_parms_add_default(snd_info->params,"frames","2048");
|
|
util_parms_add_default(snd_info->params,"sample_rate","44100");
|
|
|
|
for (indx=0;indx<snd_info->params->params_cnt;indx++) {
|
|
itm = &snd_info->params->params_array[indx];
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s : %s"
|
|
,itm->param_name.c_str(),itm->param_value.c_str());
|
|
}
|
|
|
|
for (indx=0;indx<snd_info->params->params_cnt;indx++) {
|
|
itm = &snd_info->params->params_array[indx];
|
|
if (itm->param_name == "source") {
|
|
snd_info->source = itm->param_value;
|
|
}
|
|
if (itm->param_name == "channels") {
|
|
snd_info->channels = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "frames") {
|
|
snd_info->frames = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "sample_rate") {
|
|
snd_info->sample_rate = mtoi(itm->param_value);
|
|
}
|
|
if (itm->param_name == "pulse_server") {
|
|
snd_info->pulse_server = itm->param_value;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_ALSA /************ Start ALSA *******************/
|
|
|
|
void cls_sound::alsa_list_subdev()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
int indx, retcd, cnt;
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Card %i(%s): %s [%s]")
|
|
, alsa->card_id, alsa->device_nm.c_str()
|
|
, snd_ctl_card_info_get_id(alsa->card_info)
|
|
, snd_ctl_card_info_get_name(alsa->card_info));
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _(" Device %i (%s,%d): %s [%s]")
|
|
, alsa->device_id, alsa->device_nm.c_str()
|
|
, alsa->device_id
|
|
, snd_pcm_info_get_id(alsa->pcm_info)
|
|
, snd_pcm_info_get_name(alsa->pcm_info));
|
|
|
|
cnt = (int)snd_pcm_info_get_subdevices_count(alsa->pcm_info);
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _(" Subdevices: %i/%i")
|
|
, snd_pcm_info_get_subdevices_avail(alsa->pcm_info),cnt);
|
|
|
|
for (indx=0; indx<cnt; indx++) {
|
|
snd_pcm_info_set_subdevice(alsa->pcm_info, (uint)indx);
|
|
retcd = snd_ctl_pcm_info(alsa->ctl_hdl, alsa->pcm_info);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("control digital audio playback info (%i): %s")
|
|
, alsa->card_id, snd_strerror(retcd));
|
|
} else {
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO
|
|
, _(" Subdevice #%i: %s"), indx
|
|
, snd_pcm_info_get_subdevice_name(alsa->pcm_info));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void cls_sound::alsa_list_card()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
int retcd;
|
|
|
|
retcd = snd_ctl_card_info(alsa->ctl_hdl, alsa->card_info);
|
|
if (retcd < 0) {
|
|
snd_ctl_close(alsa->ctl_hdl);
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("control hardware info (%i): %s")
|
|
, alsa->card_id, snd_strerror(retcd));
|
|
return;
|
|
}
|
|
|
|
alsa->device_id = -1;
|
|
retcd = snd_ctl_pcm_next_device(alsa->ctl_hdl, &alsa->device_id);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, _("snd_ctl_pcm_next_device"));
|
|
return;
|
|
}
|
|
|
|
while (alsa->device_id >= 0) {
|
|
snd_pcm_info_set_device(alsa->pcm_info, (uint)alsa->device_id);
|
|
snd_pcm_info_set_subdevice(alsa->pcm_info, 0);
|
|
snd_pcm_info_set_stream(alsa->pcm_info, SND_PCM_STREAM_CAPTURE);
|
|
retcd = snd_ctl_pcm_info(alsa->ctl_hdl, alsa->pcm_info);
|
|
if (retcd == 0) {
|
|
alsa_list_subdev();
|
|
} else if (retcd != -ENOENT){
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("control digital audio info (%i): %s")
|
|
, alsa->card_id, snd_strerror(retcd));
|
|
}
|
|
retcd = snd_ctl_pcm_next_device(alsa->ctl_hdl, &alsa->device_id);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, _("snd_ctl_pcm_next_device"));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void cls_sound::alsa_list()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
int retcd;
|
|
|
|
if (device_status == STATUS_CLOSED) {
|
|
return;
|
|
}
|
|
|
|
snd_ctl_card_info_alloca(&alsa->card_info);
|
|
snd_pcm_info_alloca(&alsa->pcm_info);
|
|
|
|
alsa->card_id = -1;
|
|
retcd = snd_card_next(&alsa->card_id);
|
|
if ((retcd < 0) || (alsa->card_id == -1)) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("no soundcards found..."));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Devices"));
|
|
|
|
while (alsa->card_id >= 0) {
|
|
alsa->device_nm="hw:"+std::to_string(alsa->card_id);
|
|
retcd = snd_ctl_open(&alsa->ctl_hdl, alsa->device_nm.c_str(), 0);
|
|
if (retcd == 0) {
|
|
alsa_list_card();
|
|
snd_ctl_close(alsa->ctl_hdl);
|
|
} else {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, _("control open (%i): %s")
|
|
, alsa->card_id, snd_strerror(retcd));
|
|
}
|
|
snd_card_next(&alsa->card_id);
|
|
}
|
|
|
|
}
|
|
|
|
void cls_sound::alsa_start()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
snd_pcm_hw_params_t *hw_params;
|
|
snd_pcm_uframes_t frames_per;
|
|
snd_pcm_format_t actl_sndfmt;
|
|
unsigned int actl_rate, smpl_rate;
|
|
int retcd;
|
|
|
|
frames_per = (uint)snd_info->frames;
|
|
smpl_rate = (unsigned int)snd_info->sample_rate;
|
|
|
|
retcd = snd_pcm_open(&alsa->pcm_dev
|
|
, cfg->snd_device.c_str(), SND_PCM_STREAM_CAPTURE, 0);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_open device %s (%s)")
|
|
, cfg->snd_device.c_str(), snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_malloc(&hw_params);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_malloc(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_any(alsa->pcm_dev, hw_params);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_any(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_set_access(alsa->pcm_dev
|
|
, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_set_access(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_set_format(alsa->pcm_dev
|
|
, hw_params, SND_PCM_FORMAT_S16_LE);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_set_format(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_set_rate_near(alsa->pcm_dev
|
|
, hw_params, &smpl_rate, 0);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_set_rate_near(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_set_channels(alsa->pcm_dev
|
|
, hw_params, (uint)snd_info->channels);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_set_channels(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_set_period_size_near(alsa->pcm_dev
|
|
, hw_params, &frames_per, NULL);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_set_period_size_near(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params(alsa->pcm_dev, hw_params);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_prepare(alsa->pcm_dev);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_prepare(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
/* get actual parms selected */
|
|
retcd = snd_pcm_hw_params_get_format(hw_params, &actl_sndfmt);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_get_format(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_get_rate(hw_params, &actl_rate, NULL);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_get_rate(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
retcd = snd_pcm_hw_params_get_period_size(hw_params, &frames_per, NULL);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: snd_pcm_hw_params_get_period_size(%s)")
|
|
, snd_strerror (retcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
snd_pcm_hw_params_free(hw_params);
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Actual rate %hu"), actl_rate);
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Actual frames per %lu"), frames_per);
|
|
if (actl_sndfmt <= 5) {
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Sound format 16"));
|
|
} else if (actl_sndfmt <= 9 ) {
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Sound format 24"));
|
|
} else {
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Sound format 32"));
|
|
}
|
|
|
|
/*************************************************************/
|
|
/** allocate and initialize the sound buffers */
|
|
/*************************************************************/
|
|
snd_info->frames = (int)frames_per;
|
|
snd_info->buffer_size = snd_info->frames * 2;
|
|
snd_info->buffer = (int16_t*)mymalloc(
|
|
(uint)snd_info->buffer_size * sizeof(int16_t));
|
|
memset(snd_info->buffer, 0x00
|
|
, (uint)snd_info->buffer_size * sizeof(int16_t));
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started.");
|
|
device_status =STATUS_OPENED;
|
|
|
|
}
|
|
|
|
void cls_sound::alsa_init()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
|
|
if (snd_info->source != "alsa") {
|
|
return;
|
|
}
|
|
|
|
alsa->pcm_dev = NULL;
|
|
alsa->pcm_info = NULL;
|
|
alsa->card_id = -1;
|
|
alsa->card_info = NULL;
|
|
alsa->pcm_dev = NULL;
|
|
alsa->ctl_hdl = NULL;
|
|
alsa->card_info = NULL;
|
|
alsa->card_id = 0;
|
|
alsa->pcm_info = NULL;
|
|
alsa->device_nm = "";
|
|
alsa->device_id = 0;
|
|
|
|
alsa_list();
|
|
alsa_start();
|
|
}
|
|
|
|
void cls_sound::alsa_capture()
|
|
{
|
|
ctx_snd_alsa *alsa = snd_info->snd_alsa;
|
|
long int retcd;
|
|
|
|
if (snd_info->source != "alsa") {
|
|
return;
|
|
}
|
|
retcd = snd_pcm_readi(alsa->pcm_dev
|
|
, snd_info->buffer, (uint)snd_info->frames);
|
|
if (retcd != snd_info->frames) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("error: read from audio interface failed (%s)")
|
|
, snd_strerror((int)retcd));
|
|
device_status = STATUS_CLOSED;
|
|
}
|
|
}
|
|
|
|
void cls_sound::alsa_cleanup()
|
|
{
|
|
if (snd_info->source != "alsa") {
|
|
return;
|
|
}
|
|
if (snd_info->snd_alsa->pcm_dev != NULL) {
|
|
snd_pcm_close(snd_info->snd_alsa->pcm_dev);
|
|
snd_config_update_free_global();
|
|
}
|
|
}
|
|
|
|
#endif /************ End ALSA *******************/
|
|
|
|
#ifdef HAVE_PULSE /************ Start PULSE *******************/
|
|
|
|
void cls_sound::pulse_init()
|
|
{
|
|
pa_sample_spec specs;
|
|
int errcd;
|
|
|
|
if (snd_info->source != "pulse") {
|
|
return;
|
|
}
|
|
|
|
specs.format = PA_SAMPLE_S16LE;
|
|
specs.rate = (uint32_t)snd_info->sample_rate;
|
|
specs.channels = (uint8_t)snd_info->channels;
|
|
|
|
snd_info->snd_pulse->dev = NULL;
|
|
snd_info->snd_pulse->dev = pa_simple_new(
|
|
(snd_info->pulse_server=="" ? NULL : snd_info->pulse_server.c_str())
|
|
, "motion", PA_STREAM_RECORD
|
|
, (cfg->snd_device=="" ? NULL : cfg->snd_device.c_str())
|
|
, "motion", &specs, NULL, NULL, &errcd);
|
|
if (snd_info->snd_pulse->dev == NULL) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Error opening pulse (%s)")
|
|
, pa_strerror(errcd));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
snd_info->buffer_size = snd_info->frames * 2;
|
|
snd_info->buffer = (int16_t*)mymalloc(
|
|
(uint)snd_info->buffer_size * sizeof(int16_t));
|
|
memset(snd_info->buffer, 0x00
|
|
, (uint)snd_info->buffer_size * sizeof(int16_t));
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started.");
|
|
device_status =STATUS_OPENED;
|
|
|
|
}
|
|
|
|
void cls_sound::pulse_capture()
|
|
{
|
|
ctx_snd_pulse *pulse = snd_info->snd_pulse;
|
|
int errcd, retcd;
|
|
|
|
if (snd_info->source != "pulse") {
|
|
return;
|
|
}
|
|
|
|
retcd = pa_simple_read(pulse->dev, snd_info->buffer
|
|
, (uint)snd_info->buffer_size, &errcd);
|
|
if (retcd < 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Error capturing PulseAudio (%s)")
|
|
, pa_strerror(errcd));
|
|
device_status = STATUS_CLOSED;
|
|
}
|
|
}
|
|
|
|
void cls_sound::pulse_cleanup()
|
|
{
|
|
if (snd_info->source != "pulse") {
|
|
return;
|
|
}
|
|
|
|
if (snd_info->snd_pulse->dev != NULL) {
|
|
pa_simple_free(snd_info->snd_pulse->dev);
|
|
}
|
|
}
|
|
|
|
#endif /************ End PULSE *******************/
|
|
|
|
#ifdef HAVE_FFTW3 /************ Start FFTW3 *******************/
|
|
|
|
void cls_sound::fftw_open()
|
|
{
|
|
ctx_snd_fftw *fftw = snd_info->snd_fftw;
|
|
int indx;
|
|
|
|
if (device_status == STATUS_CLOSED) {
|
|
return;
|
|
}
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO
|
|
, _("Opening FFTW plan"));
|
|
|
|
fftw->ff_in = (double*) fftw_malloc(
|
|
sizeof(fftw_complex) * (uint)snd_info->frames);
|
|
fftw->ff_out = (fftw_complex*) fftw_malloc(
|
|
sizeof(fftw_complex) * (uint)snd_info->frames);
|
|
fftw->ff_plan = fftw_plan_dft_r2c_1d(
|
|
snd_info->frames, fftw->ff_in, fftw->ff_out, FFTW_MEASURE);
|
|
|
|
for (indx=0; indx<snd_info->frames; indx++) {
|
|
fftw->ff_in[indx] = 0;
|
|
}
|
|
fftw->bin_min = 1;
|
|
fftw->bin_max = (snd_info->frames / 2);
|
|
fftw->bin_size = ((float)snd_info->sample_rate / (float)snd_info->frames);
|
|
|
|
}
|
|
|
|
float cls_sound::HammingWindow(int n1, int N2){
|
|
return 0.54F - 0.46F * (float)(cos((2 * M_PI * n1)) / (N2 - 1));
|
|
}
|
|
|
|
float cls_sound::HannWindow(int n1, int N2){
|
|
return 0.5F * (float)(1 - (cos(2 * M_PI * n1 * N2)));
|
|
}
|
|
|
|
void cls_sound::check_alerts()
|
|
{
|
|
double freq_value;
|
|
int indx, chkval, chkcnt;
|
|
double pMaxIntensity;
|
|
int pMaxBinIndex;
|
|
double pRealNbr;
|
|
double pImaginaryNbr;
|
|
double pIntensity;
|
|
bool trigger;
|
|
std::list<ctx_snd_alert>::iterator it;
|
|
struct timespec trig_ts;
|
|
|
|
for (indx=0;indx <snd_info->frames;indx++){
|
|
if (cfg->snd_window == "hamming") {
|
|
snd_info->snd_fftw->ff_in[indx] =
|
|
snd_info->buffer[indx] * HammingWindow(indx, snd_info->frames);
|
|
} else if (cfg->snd_window == "hann") {
|
|
snd_info->snd_fftw->ff_in[indx] =
|
|
snd_info->buffer[indx] * HannWindow(indx, snd_info->frames);
|
|
} else {
|
|
snd_info->snd_fftw->ff_in[indx] = snd_info->buffer[indx];
|
|
}
|
|
}
|
|
|
|
fftw_execute(snd_info->snd_fftw->ff_plan);
|
|
|
|
pMaxIntensity = 0;
|
|
pMaxBinIndex = 0;
|
|
|
|
for (indx = snd_info->snd_fftw->bin_min;
|
|
indx <= snd_info->snd_fftw->bin_max; indx++) {
|
|
pRealNbr = snd_info->snd_fftw->ff_out[indx][0];
|
|
pImaginaryNbr = snd_info->snd_fftw->ff_out[indx][1];
|
|
pIntensity = pRealNbr * pRealNbr + pImaginaryNbr * pImaginaryNbr;
|
|
if (pIntensity > pMaxIntensity){
|
|
pMaxIntensity = pIntensity;
|
|
pMaxBinIndex = indx;
|
|
}
|
|
}
|
|
|
|
freq_value = (snd_info->snd_fftw->bin_size * pMaxBinIndex * snd_info->channels);
|
|
|
|
if (cfg->snd_show) {
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO
|
|
, _("Freq: %.4f threshold: %d count: %d maximum: %d")
|
|
, freq_value, snd_info->vol_min
|
|
, snd_info->vol_count, snd_info->vol_max);
|
|
}
|
|
|
|
for (it=snd_info->alerts.begin(); it!=snd_info->alerts.end(); it++) {
|
|
trigger = false;
|
|
if ((freq_value >= it->freq_low) && (freq_value <= it->freq_high)) {
|
|
chkcnt = 0;
|
|
for(indx=0; indx < snd_info->frames; indx++) {
|
|
chkval = abs((int)snd_info->buffer[indx] / 256);
|
|
if (chkval >= it->volume_level) {
|
|
chkcnt++;
|
|
}
|
|
}
|
|
if (chkcnt >= it->volume_count) {
|
|
trigger = true;
|
|
}
|
|
}
|
|
if (trigger) {
|
|
clock_gettime(CLOCK_MONOTONIC, &trig_ts);
|
|
if ((trig_ts.tv_sec - it->trigger_time.tv_sec) > it->trigger_duration) {
|
|
it->trigger_count = 1;
|
|
} else {
|
|
it->trigger_count++;
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, &it->trigger_time);
|
|
|
|
if (it->trigger_count == it->trigger_threshold) {
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO
|
|
, _("Sound Alert %d-%s : level %d count %d max vol %d")
|
|
, it->alert_id ,it->alert_nm.c_str()
|
|
, it->volume_level, chkcnt
|
|
, snd_info->vol_max);
|
|
if (cfg->on_sound_alert != "") {
|
|
snd_info->trig_freq =std::to_string(freq_value);
|
|
snd_info->trig_nbr = std::to_string(it->alert_id);
|
|
snd_info->trig_nm = it->alert_nm;
|
|
util_exec_command(this, cfg->on_sound_alert);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif /************ End FFTW3 *******************/
|
|
|
|
void cls_sound::capture()
|
|
{
|
|
if (device_status == STATUS_CLOSED) {
|
|
return;
|
|
}
|
|
#ifdef HAVE_ALSA
|
|
alsa_capture();
|
|
#endif
|
|
#ifdef HAVE_PULSE
|
|
pulse_capture();
|
|
#endif
|
|
}
|
|
|
|
void cls_sound::cleanup()
|
|
{
|
|
#ifdef HAVE_ALSA
|
|
alsa_cleanup();
|
|
#endif
|
|
#ifdef HAVE_PULSE
|
|
pulse_cleanup();
|
|
#endif
|
|
#ifdef HAVE_FFTW3
|
|
fftw_destroy_plan(snd_info->snd_fftw->ff_plan);
|
|
fftw_free(snd_info->snd_fftw->ff_in);
|
|
fftw_free(snd_info->snd_fftw->ff_out);
|
|
#endif
|
|
if (snd_info->buffer != NULL) {
|
|
free(snd_info->buffer);
|
|
snd_info->buffer = NULL;
|
|
}
|
|
|
|
mydelete(snd_info->snd_alsa);
|
|
mydelete(snd_info->snd_fftw);
|
|
mydelete(snd_info->snd_pulse);
|
|
mydelete(snd_info->params);
|
|
mydelete(snd_info);
|
|
|
|
device_status = STATUS_CLOSED;
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Stopped.");
|
|
|
|
}
|
|
|
|
void cls_sound::init()
|
|
{
|
|
if ((device_status != STATUS_INIT) && (restart == false)) {
|
|
return;
|
|
}
|
|
|
|
if (restart == true) {
|
|
cleanup();
|
|
restart = false;
|
|
}
|
|
|
|
cfg->parms_copy(conf_src);
|
|
|
|
mythreadname_set("sl",cfg->device_id, cfg->device_name.c_str());
|
|
|
|
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Initialize sound frequency"));
|
|
|
|
snd_info = new ctx_snd_info;
|
|
snd_info->params = new ctx_params;
|
|
snd_info->snd_fftw = new ctx_snd_fftw;
|
|
snd_info->snd_alsa = new ctx_snd_alsa;
|
|
snd_info->snd_pulse = new ctx_snd_pulse;
|
|
|
|
init_values();
|
|
load_params();
|
|
load_alerts();
|
|
|
|
if ((snd_info->source != "alsa") &&
|
|
(snd_info->source != "pulse")) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,_("Invalid sound source."));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_ALSA
|
|
alsa_init();
|
|
#endif
|
|
#ifdef HAVE_PULSE
|
|
pulse_init();
|
|
#endif
|
|
#ifdef HAVE_FFTW3
|
|
fftw_open();
|
|
#endif
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Detecting"));
|
|
|
|
}
|
|
|
|
void cls_sound::check_levels()
|
|
{
|
|
#ifdef HAVE_FFTW3
|
|
int indx, chkval;
|
|
|
|
if (device_status == STATUS_CLOSED) {
|
|
return;
|
|
}
|
|
|
|
snd_info->vol_max = 0;
|
|
snd_info->vol_count = 0;
|
|
|
|
for(indx=0; indx < snd_info->frames; indx++) {
|
|
chkval = abs((int)snd_info->buffer[indx] / 256);
|
|
if (chkval > snd_info->vol_max) snd_info->vol_max = chkval ;
|
|
if (chkval > snd_info->vol_min) snd_info->vol_count++;
|
|
}
|
|
|
|
if (snd_info->vol_count > 0) {
|
|
check_alerts();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void cls_sound::handler()
|
|
{
|
|
device_status = STATUS_INIT;
|
|
|
|
while (handler_stop == false) {
|
|
watchdog = cfg->watchdog_tmo;
|
|
init();
|
|
capture();
|
|
check_levels();
|
|
if (device_status == STATUS_CLOSED) {
|
|
handler_stop = true;
|
|
}
|
|
}
|
|
|
|
cleanup();
|
|
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Sound exiting"));
|
|
|
|
handler_running = false;
|
|
pthread_exit(nullptr);
|
|
}
|
|
|
|
void cls_sound::handler_startup()
|
|
{
|
|
int retcd;
|
|
pthread_attr_t thread_attr;
|
|
|
|
#if !defined(HAVE_FFTW3) || (!defined(HAVE_ALSA) && !defined(HAVE_PULSE))
|
|
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Required packages not installed"));
|
|
device_status = STATUS_CLOSED;
|
|
return;
|
|
#endif
|
|
|
|
if (handler_running == false) {
|
|
handler_running = true;
|
|
handler_stop = false;
|
|
pthread_attr_init(&thread_attr);
|
|
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
|
|
retcd = pthread_create(&handler_thread, &thread_attr, &sound_handler, this);
|
|
if (retcd != 0) {
|
|
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Unable to start sound frequency detection loop."));
|
|
handler_running = false;
|
|
handler_stop = true;
|
|
}
|
|
pthread_attr_destroy(&thread_attr);
|
|
}
|
|
}
|
|
|
|
void cls_sound::handler_shutdown()
|
|
{
|
|
int waitcnt;
|
|
|
|
if (handler_running == true) {
|
|
handler_stop = true;
|
|
waitcnt = 0;
|
|
while ((handler_running == true) && (waitcnt < cfg->watchdog_tmo)){
|
|
SLEEP(1,0)
|
|
waitcnt++;
|
|
}
|
|
if (waitcnt == cfg->watchdog_tmo) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Normal shutdown of sound frequency detection failed"));
|
|
if (cfg->watchdog_kill > 0) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
,_("Waiting additional %d seconds (watchdog_kill).")
|
|
,cfg->watchdog_kill);
|
|
waitcnt = 0;
|
|
while ((handler_running == true) && (waitcnt < cfg->watchdog_kill)){
|
|
SLEEP(1,0)
|
|
waitcnt++;
|
|
}
|
|
if (waitcnt == cfg->watchdog_kill) {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("No response to shutdown. Killing it."));
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("Memory leaks will occur."));
|
|
pthread_kill(handler_thread, SIGVTALRM);
|
|
}
|
|
} else {
|
|
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO
|
|
, _("watchdog_kill set to terminate application."));
|
|
exit(1);
|
|
}
|
|
}
|
|
handler_running = false;
|
|
watchdog = cfg->watchdog_tmo;
|
|
}
|
|
|
|
}
|
|
|
|
cls_sound::cls_sound(cls_motapp *p_app)
|
|
{
|
|
app = p_app;
|
|
handler_running = false;
|
|
handler_stop = true;
|
|
restart = false;
|
|
watchdog = 30;
|
|
}
|
|
|
|
cls_sound::~cls_sound()
|
|
{
|
|
mydelete(conf_src);
|
|
mydelete(cfg);
|
|
}
|
|
|