diff --git a/configure.ac b/configure.ac index 176c0dee..d2ff2e62 100644 --- a/configure.ac +++ b/configure.ac @@ -473,6 +473,86 @@ AS_IF([test "${SQLITE3}" = "no"], [ ] ) +############################################################################## +### Check for ALSA +############################################################################## +AC_ARG_WITH([alsa], + AS_HELP_STRING([--with-alsa[=DIR]],[Build with ALSA support]), + [ALSA=$withval], + [ALSA="yes"] +) + +AS_IF([test "${ALSA}" = "no"], [ + AC_MSG_CHECKING(for ALSA) + AC_MSG_RESULT(skipped) + ],[ + AC_MSG_CHECKING(ALSA pkg-config path) + TEMP_PATH=$PKG_CONFIG_PATH + AS_IF([test "${ALSA}" != "yes"], [ + PKG_CONFIG_PATH=${ALSA}/lib/pkgconfig:$PKG_CONFIG_PATH + ALSA="yes" + ] + ) + export PKG_CONFIG_PATH + AC_MSG_RESULT($PKG_CONFIG_PATH done) + + AC_MSG_CHECKING(for ALSA) + AS_IF([pkg-config alsa], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS "`pkg-config --cflags alsa` + TEMP_LIBS="$TEMP_LIBS "`pkg-config --libs alsa` + AC_DEFINE([HAVE_ALSA], [1], [Define to 1 if you have ALSA support]) + AC_MSG_RESULT(yes) + ],[ + ALSA="no" + ] + ) + + AC_MSG_RESULT([$ALSA]) + PKG_CONFIG_PATH=$TEMP_PATH + export PKG_CONFIG_PATH + ] +) + +############################################################################## +### Check for FFTW3 +############################################################################## +AC_ARG_WITH([fftw3], + AS_HELP_STRING([--with-fftw3[=DIR]],[Build with FFTW3 support]), + [FFTW3=$withval], + [FFTW3="yes"] +) + +AS_IF([test "${FFTW3}" = "no"], [ + AC_MSG_CHECKING(for FFTW3) + AC_MSG_RESULT(skipped) + ],[ + AC_MSG_CHECKING(FFTW3 pkg-config path) + TEMP_PATH=$PKG_CONFIG_PATH + AS_IF([test "${FFTW3}" != "yes"], [ + PKG_CONFIG_PATH=${FFTW3}/lib/pkgconfig:$PKG_CONFIG_PATH + FFTW3="yes" + ] + ) + export PKG_CONFIG_PATH + AC_MSG_RESULT($PKG_CONFIG_PATH done) + + AC_MSG_CHECKING(for FFTW3) + AS_IF([pkg-config fftw3], [ + TEMP_CPPFLAGS="$TEMP_CPPFLAGS "`pkg-config --cflags fftw3` + TEMP_LIBS="$TEMP_LIBS "`pkg-config --libs fftw3` + AC_DEFINE([HAVE_FFTW3], [1], [Define to 1 if you have FFTW3 support]) + AC_MSG_RESULT(yes) + ],[ + FFTW3="no" + ] + ) + + AC_MSG_RESULT([$FFTW3]) + PKG_CONFIG_PATH=$TEMP_PATH + export PKG_CONFIG_PATH + ] +) + ############################################################################## ### Optimize compiler ############################################################################## @@ -562,6 +642,8 @@ echo "SQLite3 support : $SQLITE3" echo "MYSQL support : $MYSQL" echo "PostgreSQL support : $PGSQL" echo "MariaDB support : $MARIADB" +echo "ALSA support : $ALSA" +echo "FFTW support : $FFTW3" echo echo "Install prefix: $prefix" echo diff --git a/src/Makefile.am b/src/Makefile.am index 3e6b904f..1b6cce80 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,5 +28,5 @@ motionplus_SOURCES = motionplus.cpp motion_loop.cpp logger.cpp conf.cpp util.cpp video_v4l2.cpp video_common.cpp video_loopback.cpp netcam.cpp jpegutils.cpp exif.cpp \ rotate.cpp draw.cpp event.cpp movie.cpp picture.cpp dbse.cpp \ webu.cpp webu_html.cpp webu_stream.cpp webu_json.cpp webu_post.cpp webu_file.cpp \ - libcam.cpp + libcam.cpp sound.cpp diff --git a/src/conf.cpp b/src/conf.cpp index dba20c70..a03e3f6b 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -118,6 +118,7 @@ ctx_parm config_parms[] = { {"on_camera_found", PARM_TYP_STRING, PARM_CAT_08, WEBUI_LEVEL_RESTRICTED }, {"on_secondary_detect", PARM_TYP_STRING, PARM_CAT_08, WEBUI_LEVEL_RESTRICTED }, {"on_action_user", PARM_TYP_STRING, PARM_CAT_08, WEBUI_LEVEL_RESTRICTED }, + {"on_sound_alert", PARM_TYP_STRING, PARM_CAT_08, WEBUI_LEVEL_RESTRICTED }, {"picture_output", PARM_TYP_LIST, PARM_CAT_09, WEBUI_LEVEL_LIMITED }, {"picture_output_motion", PARM_TYP_LIST, PARM_CAT_09, WEBUI_LEVEL_LIMITED }, @@ -201,6 +202,12 @@ ctx_parm config_parms[] = { {"ptz_zoom_in", PARM_TYP_STRING, PARM_CAT_17, WEBUI_LEVEL_RESTRICTED }, {"ptz_zoom_out", PARM_TYP_STRING, PARM_CAT_17, WEBUI_LEVEL_RESTRICTED }, + {"snd_device", PARM_TYP_STRING, PARM_CAT_18, WEBUI_LEVEL_ADVANCED }, + {"snd_params", PARM_TYP_STRING, PARM_CAT_18, WEBUI_LEVEL_ADVANCED }, + {"snd_alerts", PARM_TYP_ARRAY, PARM_CAT_18, WEBUI_LEVEL_ADVANCED }, + {"snd_window", PARM_TYP_LIST, PARM_CAT_18, WEBUI_LEVEL_ADVANCED }, + {"show_freq", PARM_TYP_BOOL, PARM_CAT_18, WEBUI_LEVEL_ADVANCED }, + { "", (enum PARM_TYP)0, (enum PARM_CAT)0, (enum WEBUI_LEVEL)0 } }; @@ -602,11 +609,13 @@ static void conf_edit_device_id(ctx_config *conf, std::string &parm, enum PARM_A int parm_in; if (pact == PARM_ACT_DFLT) { - conf->device_id = 1; + conf->device_id = 0; } else if (pact == PARM_ACT_SET) { parm_in = atoi(parm.c_str()); if (parm_in < 1) { MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Invalid device_id %d"),parm_in); + } else if (parm_in > 32000) { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Invalid device_id %d"),parm_in); } else { conf->device_id = parm_in; } @@ -1635,6 +1644,19 @@ static void conf_edit_on_action_user(ctx_config *conf, std::string &parm, enum P MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","on_action_user",_("on_action_user")); } +static void conf_edit_on_sound_alert(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->on_sound_alert = ""; + } else if (pact == PARM_ACT_SET) { + conf->on_sound_alert = parm; + } else if (pact == PARM_ACT_GET) { + parm = conf->on_sound_alert; + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","on_sound_alert",_("on_sound_alert")); +} + static void conf_edit_picture_output(ctx_config *conf, std::string &parm, enum PARM_ACT pact) { if (pact == PARM_ACT_DFLT) { @@ -2819,6 +2841,110 @@ static void conf_edit_ptz_zoom_out(ctx_config *conf, std::string &parm, enum PAR MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","ptz_zoom_out",_("ptz_zoom_out")); } +static void conf_edit_snd_device(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->snd_device = ""; + } else if (pact == PARM_ACT_SET) { + conf->snd_device = parm; + } else if (pact == PARM_ACT_GET) { + parm = conf->snd_device; + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","snd_device",_("snd_device")); +} + +static void conf_edit_snd_params(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->snd_params = ""; + } else if (pact == PARM_ACT_SET) { + conf->snd_params = parm; + } else if (pact == PARM_ACT_GET) { + parm = conf->snd_params; + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","snd_params",_("snd_params")); +} + +static void conf_edit_snd_alerts(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + std::list::iterator it; + + if (pact == PARM_ACT_DFLT) { + conf->snd_alerts.clear(); + if (parm == "") { + return; + } + conf->snd_alerts.push_back(parm); + } else if (pact == PARM_ACT_SET) { + if (parm == "") { + return; + } + conf->snd_alerts.push_back(parm); /* Add to the end of list*/ + for (it= conf->snd_alerts.begin(); it != conf->snd_alerts.end(); it++) { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO,"%s:%s" + ,"snd_alerts", it->c_str()); + } + } else if (pact == PARM_ACT_GET) { + if (conf->snd_alerts.empty()) { + parm = ""; + } else { + parm = conf->snd_alerts.back(); /* Give last item*/ + } + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","snd_alerts",_("snd_alerts")); +} + +static void conf_edit_snd_alerts(ctx_config *conf, std::list &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->snd_alerts.clear(); + conf->snd_alerts = parm; + } else if (pact == PARM_ACT_SET) { + conf->snd_alerts = parm; + } else if (pact == PARM_ACT_GET) { + parm = conf->snd_alerts; + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","snd_alerts",_("snd_alerts")); +} + +static void conf_edit_snd_window(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->snd_window = "hamming"; + } else if (pact == PARM_ACT_SET) { + if ((parm == "none") || (parm == "hamming") || (parm == "hann")) { + conf->snd_window = parm; + } else if (parm == "") { + conf->snd_window = "hamming"; + } else { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Invalid snd_window %s"), parm.c_str()); + } + } else if (pact == PARM_ACT_GET) { + parm = conf->snd_window; + } else if (pact == PARM_ACT_LIST) { + parm = "[\"none\",\"hamming\",\"hann\"]"; + + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","snd_window",_("snd_window")); +} + +static void conf_edit_show_freq(ctx_config *conf, std::string &parm, enum PARM_ACT pact) +{ + if (pact == PARM_ACT_DFLT) { + conf->show_freq = false; + } else if (pact == PARM_ACT_SET) { + conf_edit_set_bool(conf->show_freq, parm); + } else if (pact == PARM_ACT_GET) { + conf_edit_get_bool(parm, conf->show_freq); + } + return; + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,"%s:%s","show_freq",_("show_freq")); +} /* Application level parameters */ static void conf_edit_cat00(ctx_config *conf, std::string cmd @@ -2954,6 +3080,7 @@ static void conf_edit_cat08(ctx_config *conf, std::string parm_nm } else if (parm_nm == "on_camera_found") { conf_edit_on_camera_found(conf, parm_val, pact); } else if (parm_nm == "on_secondary_detect") { conf_edit_on_secondary_detect(conf, parm_val, pact); } else if (parm_nm == "on_action_user") { conf_edit_on_action_user(conf, parm_val, pact); + } else if (parm_nm == "on_sound_alert") { conf_edit_on_sound_alert(conf, parm_val, pact); } } @@ -3092,6 +3219,33 @@ static void conf_edit_cat17(ctx_config *conf, std::string parm_nm } } +static void conf_edit_cat18(ctx_config *conf, std::string parm_nm + , std::string &parm_val, enum PARM_ACT pact) +{ + if (parm_nm == "snd_device") { conf_edit_snd_device(conf, parm_val, pact); + } else if (parm_nm == "snd_params") { conf_edit_snd_params(conf, parm_val, pact); + } else if (parm_nm == "snd_window") { conf_edit_snd_window(conf, parm_val, pact); + } else if (parm_nm == "snd_alerts") { conf_edit_snd_alerts(conf, parm_val, pact); + } else if (parm_nm == "show_freq") { conf_edit_show_freq(conf, parm_val, pact); + + } +} + +static void conf_edit_cat18(ctx_config *conf, std::string parm_nm + ,std::list &parm_val, enum PARM_ACT pact) +{ + if (parm_nm == "snd_alerts") { conf_edit_snd_alerts(conf, parm_val, pact); + } +} + +static void conf_edit_cat(ctx_config *conf, std::string parm_nm + ,std::list &parm_val, enum PARM_ACT pact, enum PARM_CAT pcat) +{ + if (pcat == PARM_CAT_18) { + conf_edit_cat18(conf, parm_nm, parm_val, pact); + } +} + static void conf_edit_cat(ctx_config *conf, std::string parm_nm , std::string &parm_val, enum PARM_ACT pact, enum PARM_CAT pcat) { @@ -3113,6 +3267,7 @@ static void conf_edit_cat(ctx_config *conf, std::string parm_nm } else if (pcat == PARM_CAT_15) { conf_edit_cat15(conf, parm_nm, parm_val, pact); } else if (pcat == PARM_CAT_16) { conf_edit_cat16(conf, parm_nm, parm_val, pact); } else if (pcat == PARM_CAT_17) { conf_edit_cat17(conf, parm_nm, parm_val, pact); + } else if (pcat == PARM_CAT_18) { conf_edit_cat18(conf, parm_nm, parm_val, pact); } } @@ -3239,6 +3394,12 @@ void conf_edit_get(ctx_config *conf, std::string parm_nm, std::string &parm_val, conf_edit_cat(conf, parm_nm, parm_val, PARM_ACT_GET, parm_cat); } +void conf_edit_get(ctx_config *conf, std::string parm_nm + , std::list &parm_val, enum PARM_CAT parm_cat) +{ + conf_edit_cat(conf, parm_nm, parm_val, PARM_ACT_GET, parm_cat); +} + /* Assign the parameter value */ void conf_edit_set(ctx_config *conf, std::string parm_nm , std::string parm_val) @@ -3267,6 +3428,7 @@ std::string conf_type_desc(enum PARM_TYP ptype) } else if (ptype == PARM_TYP_INT) { return "int"; } else if (ptype == PARM_TYP_LIST) { return "list"; } else if (ptype == PARM_TYP_STRING) { return "string"; + } else if (ptype == PARM_TYP_ARRAY) { return "array"; } else { return "error"; } } @@ -3293,6 +3455,7 @@ std::string conf_cat_desc(enum PARM_CAT pcat, bool shrt) { } else if (pcat == PARM_CAT_15) { return "database"; } else if (pcat == PARM_CAT_16) { return "sql"; } else if (pcat == PARM_CAT_17) { return "track"; + } else if (pcat == PARM_CAT_18) { return "sound"; } else { return "unk"; } } else { @@ -3314,6 +3477,7 @@ std::string conf_cat_desc(enum PARM_CAT pcat, bool shrt) { } else if (pcat == PARM_CAT_15) { return "Database"; } else if (pcat == PARM_CAT_16) { return "SQL"; } else if (pcat == PARM_CAT_17) { return "Tracking"; + } else if (pcat == PARM_CAT_18) { return "Sound"; } else { return "Other"; } } @@ -3384,7 +3548,7 @@ static void conf_cmdline(ctx_motapp *motapp) } /* Add in a default filename for the last camera config if it wasn't provided. */ -static void conf_filenm_cam(ctx_motapp *motapp) +static void conf_camera_filenm(ctx_motapp *motapp) { int indx_cam, indx; std::string dirnm, fullnm; @@ -3453,11 +3617,11 @@ void conf_camera_add(ctx_motapp *motapp) indx++; } - conf_filenm_cam(motapp); + conf_camera_filenm(motapp); } -static void conf_parm_camera(ctx_motapp *motapp, std::string filename) +static void conf_camera_parm(ctx_motapp *motapp, std::string filename) { struct stat statbuf; @@ -3473,6 +3637,96 @@ static void conf_parm_camera(ctx_motapp *motapp, std::string filename) } +/* Add in a default filename for the last sound config if it wasn't provided. */ +static void conf_sound_filenm(ctx_motapp *motapp) +{ + int indx_snd, indx; + std::string dirnm, fullnm; + struct stat statbuf; + size_t lstpos; + + if (motapp->snd_list[motapp->snd_cnt-1]->conf->conf_filename != "") { + return; + } + + lstpos = motapp->conf->conf_filename.find_last_of("/"); + if (lstpos != std::string::npos) { + lstpos++; + } + dirnm = motapp->conf->conf_filename.substr(0, lstpos); + + indx_snd = 1; + fullnm = ""; + while (fullnm == "") { + fullnm = dirnm + "sound" + std::to_string(indx_snd) + ".conf"; + for (indx=0;indxsnd_cnt;indx++) { + if (fullnm == motapp->snd_list[indx_snd]->conf->conf_filename) { + fullnm = ""; + } + } + if (fullnm == "") { + indx_snd++; + } else { + if (stat(fullnm.c_str(), &statbuf) == 0) { + fullnm = ""; + indx_snd++; + } + } + } + + motapp->snd_list[motapp->snd_cnt-1]->conf->conf_filename = fullnm; + +} + +void conf_sound_add(ctx_motapp *motapp) +{ + int indx; + std::string parm_val; + + motapp->snd_cnt++; + motapp->snd_list = (ctx_dev **)myrealloc( + motapp->snd_list, sizeof(ctx_dev *) * (motapp->snd_cnt + 1), "config_sound"); + + motapp->snd_list[motapp->snd_cnt-1] = new ctx_dev; + memset(motapp->snd_list[motapp->snd_cnt-1],0,sizeof(ctx_dev)); + motapp->snd_list[motapp->snd_cnt-1]->conf = new ctx_config; + + motapp->snd_list[motapp->snd_cnt] = NULL; + motapp->snd_list[motapp->snd_cnt-1]->motapp = motapp; + + conf_edit_dflt(motapp->snd_list[motapp->snd_cnt-1]->conf); + + indx = 0; + while (config_parms[indx].parm_name != "") { + if (mystrne(config_parms[indx].parm_name.c_str(),"device_id")) { + conf_edit_get(motapp->conf, config_parms[indx].parm_name + , parm_val, config_parms[indx].parm_cat); + conf_edit_set(motapp->snd_list[motapp->snd_cnt-1]->conf + , config_parms[indx].parm_name, parm_val); + } + indx++; + } + + conf_sound_filenm(motapp); + +} + +static void conf_sound_parm(ctx_motapp *motapp, std::string filename) +{ + struct stat statbuf; + + if (stat(filename.c_str(), &statbuf) != 0) { + MOTION_LOG(ALR, TYPE_ALL, SHOW_ERRNO + ,_("Sound config file %s not found"), filename.c_str()); + return; + } + + conf_sound_add(motapp); + motapp->snd_list[motapp->snd_cnt-1]->conf->conf_filename = filename; + conf_process(motapp, motapp->snd_list[motapp->snd_cnt-1]->conf); + +} + /** Process config_dir */ static void conf_parm_config_dir(ctx_motapp *motapp, std::string confdir) { @@ -3488,7 +3742,7 @@ static void conf_parm_config_dir(ctx_motapp *motapp, std::string confdir) conf_file = confdir + "/" + conf_file; MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO ,_("Processing config file %s"), conf_file.c_str() ); - conf_parm_camera(motapp, conf_file); + conf_camera_parm(motapp, conf_file); motapp->cam_list[motapp->cam_cnt-1]->conf->from_conf_dir = true; } } @@ -3499,7 +3753,6 @@ static void conf_parm_config_dir(ctx_motapp *motapp, std::string confdir) } - /** Process each line from the config file. */ void conf_process(ctx_motapp *motapp, ctx_config *conf) { @@ -3535,10 +3788,13 @@ void conf_process(ctx_motapp *motapp, ctx_config *conf) myunquote(parm_nm); myunquote(parm_vl); if ((parm_nm == "camera") && (motapp->conf == conf)) { - conf_parm_camera(motapp, parm_vl); + conf_camera_parm(motapp, parm_vl); + } else if ((parm_nm == "sound") && (motapp->conf == conf)) { + conf_sound_parm(motapp, parm_vl); } else if ((parm_nm == "config_dir") && (motapp->conf == conf)){ conf_parm_config_dir(motapp, parm_vl); - } else if ((parm_nm != "camera") && (parm_nm != "config_dir")) { + } else if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir")) { conf_edit_set(conf, parm_nm, parm_vl); } } else if ((line != "") && @@ -3584,7 +3840,10 @@ void conf_parms_log(ctx_motapp *motapp) { int i, indx; std::string parm_vl, parm_main, parm_nm; + std::list parm_array; + std::list::iterator it; enum PARM_CAT parm_ct; + enum PARM_TYP parm_typ; MOTION_LOG(INF, TYPE_ALL, NO_ERRNO ,_("Logging configuration parameters from all files")); @@ -3596,11 +3855,20 @@ void conf_parms_log(ctx_motapp *motapp) while (config_parms[i].parm_name != "") { parm_nm=config_parms[i].parm_name; parm_ct=config_parms[i].parm_cat; - if ((parm_nm != "camera") && (parm_nm != "config_dir") && - (parm_nm != "conf_filename") ) { + parm_typ=config_parms[i].parm_type; + + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_typ != PARM_TYP_ARRAY)) { conf_edit_get(motapp->conf, parm_nm,parm_vl, parm_ct); conf_parms_log_parm(parm_nm, parm_vl); } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); it++) { + conf_parms_log_parm(parm_nm, it->c_str()); + } + } i++; } @@ -3613,17 +3881,50 @@ void conf_parms_log(ctx_motapp *motapp) while (config_parms[i].parm_name != "") { parm_nm=config_parms[i].parm_name; parm_ct=config_parms[i].parm_cat; + parm_typ=config_parms[i].parm_type; conf_edit_get(motapp->conf, parm_nm, parm_main, parm_ct); conf_edit_get(motapp->cam_list[indx]->conf, parm_nm, parm_vl, parm_ct); - if ((parm_nm != "camera") && (parm_nm != "config_dir") && - (parm_nm != "conf_filename") && - (parm_main != parm_vl) ) { + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_main != parm_vl) && (parm_typ != PARM_TYP_ARRAY) ) { conf_parms_log_parm(parm_nm, parm_vl); } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->cam_list[indx]->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); it++) { + conf_parms_log_parm(parm_nm, it->c_str()); + } + } i++; } } + for (indx=0; indxsnd_cnt; indx++) { + motion_log(INF, TYPE_ALL, NO_ERRNO, 0 + , _("Sound %d - Config file: %s") + , motapp->snd_list[indx]->conf->device_id + , motapp->snd_list[indx]->conf->conf_filename.c_str()); + i = 0; + while (config_parms[i].parm_name != "") { + parm_nm=config_parms[i].parm_name; + parm_ct=config_parms[i].parm_cat; + parm_typ=config_parms[i].parm_type; + conf_edit_get(motapp->conf, parm_nm, parm_main, parm_ct); + conf_edit_get(motapp->snd_list[indx]->conf, parm_nm, parm_vl, parm_ct); + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_main != parm_vl) && (parm_typ != PARM_TYP_ARRAY) ) { + conf_parms_log_parm(parm_nm, parm_vl); + } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->snd_list[indx]->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); it++) { + conf_parms_log_parm(parm_nm, it->c_str()); + } + } + i++; + } + } } @@ -3655,7 +3956,10 @@ void conf_parms_write_app(ctx_motapp *motapp) { int i, indx; std::string parm_vl, parm_main, parm_nm; + std::list parm_array; + std::list::iterator it; enum PARM_CAT parm_ct; + enum PARM_TYP parm_typ; char timestamp[32]; FILE *conffile; @@ -3681,11 +3985,19 @@ void conf_parms_write_app(ctx_motapp *motapp) while (config_parms[i].parm_name != "") { parm_nm=config_parms[i].parm_name; parm_ct=config_parms[i].parm_cat; - if ((parm_nm != "camera") && (parm_nm != "config_dir") && - (parm_nm != "conf_filename")) { + parm_typ=config_parms[i].parm_type; + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_typ != PARM_TYP_ARRAY)) { conf_edit_get(motapp->conf, parm_nm, parm_vl, parm_ct); conf_parms_write_parms(conffile, parm_nm, parm_vl, parm_ct, false); } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); it++) { + conf_parms_write_parms(conffile, parm_nm, it->c_str(), parm_ct, false); + } + } i++; } @@ -3697,6 +4009,14 @@ void conf_parms_write_app(ctx_motapp *motapp) } } + for (indx=0; indxsnd_cnt; indx++) { + if (motapp->snd_list[indx]->conf->from_conf_dir == false) { + conf_parms_write_parms(conffile, "sound" + , motapp->snd_list[indx]->conf->conf_filename + , PARM_CAT_01, false); + } + } + fprintf(conffile, "\n"); conf_edit_get(motapp->conf, "config_dir", parm_vl, PARM_CAT_01); @@ -3715,7 +4035,10 @@ void conf_parms_write_cam(ctx_motapp *motapp) { int i, indx; std::string parm_vl, parm_main, parm_nm; + std::list parm_array; + std::list::iterator it; enum PARM_CAT parm_ct; + enum PARM_TYP parm_typ; char timestamp[32]; FILE *conffile; @@ -3740,14 +4063,22 @@ void conf_parms_write_cam(ctx_motapp *motapp) while (config_parms[i].parm_name != "") { parm_nm=config_parms[i].parm_name; parm_ct=config_parms[i].parm_cat; - if ((parm_nm != "camera") && (parm_nm != "config_dir") && - (parm_nm != "conf_filename") ) { + parm_typ=config_parms[i].parm_type; + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_typ != PARM_TYP_ARRAY) ) { conf_edit_get(motapp->conf, parm_nm, parm_main, parm_ct); conf_edit_get(motapp->cam_list[indx]->conf, parm_nm, parm_vl, parm_ct); if (parm_main != parm_vl) { conf_parms_write_parms(conffile, parm_nm, parm_vl, parm_ct, false); } } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); it++) { + conf_parms_write_parms(conffile, parm_nm, it->c_str(), parm_ct, false); + } + } i++; } fprintf(conffile, "\n"); @@ -3760,12 +4091,72 @@ void conf_parms_write_cam(ctx_motapp *motapp) } +void conf_parms_write_snd(ctx_motapp *motapp) +{ + int i, indx; + std::string parm_vl, parm_main, parm_nm; + std::list parm_array; + std::list::iterator it; + enum PARM_CAT parm_ct; + enum PARM_TYP parm_typ; + char timestamp[32]; + FILE *conffile; + + time_t now = time(0); + strftime(timestamp, 32, "%Y-%m-%dT%H:%M:%S", localtime(&now)); + + for (indx=0; indxsnd_cnt; indx++) { + conffile = myfopen(motapp->snd_list[indx]->conf->conf_filename.c_str(), "we"); + if (conffile == NULL) { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , _("Failed to write configuration to %s") + , motapp->snd_list[indx]->conf->conf_filename.c_str()); + return; + } + fprintf(conffile, "; %s\n", motapp->snd_list[indx]->conf->conf_filename.c_str()); + fprintf(conffile, ";\n; This config file was generated by MotionPlus " VERSION "\n"); + fprintf(conffile, "; at %s\n", timestamp); + fprintf(conffile, "\n\n"); + conf_parms_write_parms(conffile, "", "", PARM_CAT_00, true); + + i=0; + while (config_parms[i].parm_name != "") { + parm_nm=config_parms[i].parm_name; + parm_ct=config_parms[i].parm_cat; + parm_typ=config_parms[i].parm_type; + if ((parm_nm != "camera") && (parm_nm != "sound") && + (parm_nm != "config_dir") && (parm_nm != "conf_filename") && + (parm_typ != PARM_TYP_ARRAY)) { + conf_edit_get(motapp->conf, parm_nm, parm_main, parm_ct); + conf_edit_get(motapp->snd_list[indx]->conf, parm_nm, parm_vl, parm_ct); + if (parm_main != parm_vl) { + conf_parms_write_parms(conffile, parm_nm, parm_vl, parm_ct, false); + } + } + if (parm_typ == PARM_TYP_ARRAY) { + conf_edit_get(motapp->conf, parm_nm, parm_array, parm_ct); + for (it = parm_array.begin(); it != parm_array.end(); ++it) { + conf_parms_write_parms(conffile, parm_nm, it->c_str(), parm_ct, false); + } + } + i++; + } + fprintf(conffile, "\n"); + myfclose(conffile); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , _("Configuration written to %s") + , motapp->snd_list[indx]->conf->conf_filename.c_str()); + } +} /** Write the configuration(s) to file */ void conf_parms_write(ctx_motapp *motapp) { conf_parms_write_app(motapp); conf_parms_write_cam(motapp); + conf_parms_write_snd(motapp); + } void conf_init(ctx_motapp *motapp) @@ -3828,6 +4219,10 @@ void conf_init(ctx_motapp *motapp) motapp->cam_list[indx]->threadnr = indx; } + for (indx=0; indxsnd_cnt; indx++) { + motapp->snd_list[indx]->threadnr = indx + motapp->cam_cnt; + } + } void conf_deinit(ctx_motapp *motapp) @@ -3840,5 +4235,11 @@ void conf_deinit(ctx_motapp *motapp) } myfree(&motapp->cam_list); + for (indx=0; indxsnd_cnt; indx++) { + delete motapp->snd_list[indx]->conf; + delete motapp->snd_list[indx]; + } + myfree(&motapp->snd_list); + } diff --git a/src/conf.hpp b/src/conf.hpp index 10027527..f36d3928 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -111,6 +111,7 @@ std::string on_camera_found; std::string on_secondary_detect; std::string on_action_user; + std::string on_sound_alert; /* Picture output configuration parameters */ std::string picture_output; @@ -204,6 +205,13 @@ std::string ptz_zoom_in; /* Zoom in command */ std::string ptz_zoom_out; /* Zoom out command */ + /* Sound processing parameters */ + std::string snd_device; + std::string snd_params; + std::list snd_alerts; + std::string snd_window; + bool show_freq; + }; /* Categories for he edits and display on web interface*/ @@ -226,6 +234,7 @@ ,PARM_CAT_15 /* database */ ,PARM_CAT_16 /* sql */ ,PARM_CAT_17 /* tracking */ + ,PARM_CAT_18 /* sound */ ,PARM_CAT_MAX }; enum PARM_TYP{ @@ -233,6 +242,7 @@ , PARM_TYP_INT , PARM_TYP_LIST , PARM_TYP_BOOL + , PARM_TYP_ARRAY }; /** Current parameters in the config file */ @@ -264,6 +274,8 @@ , std::string parm_val); void conf_edit_get(ctx_config *conf, std::string parm_nm , std::string &parm_val, enum PARM_CAT parm_cat); + void conf_edit_get(ctx_config *conf, std::string parm_nm + , std::list &parm_val, enum PARM_CAT parm_cat); void conf_edit_list(ctx_config *conf, std::string parm_nm , std::string &parm_val, enum PARM_CAT parm_cat); diff --git a/src/libcam.cpp b/src/libcam.cpp index df7d2183..eadeecfc 100644 --- a/src/libcam.cpp +++ b/src/libcam.cpp @@ -378,7 +378,7 @@ void libcam_cleanup(ctx_dev *cam) delete cam->libcam; cam->libcam = nullptr; #endif - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; } /** initialize and start libcam */ @@ -395,10 +395,10 @@ void libcam_start(ctx_dev *cam) MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO,_("libcam failed to open")); libcam_cleanup(cam); } else { - cam->camera_status = STATUS_OPENED; + cam->device_status = STATUS_OPENED; } #else - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; #endif } diff --git a/src/motion_loop.cpp b/src/motion_loop.cpp index 95a1634e..82440b3b 100644 --- a/src/motion_loop.cpp +++ b/src/motion_loop.cpp @@ -391,7 +391,7 @@ void mlp_cam_start(ctx_dev *cam) } else { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO ,_("No Camera device specified")); - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; } } @@ -434,7 +434,7 @@ static void mlp_init_firstimage(ctx_dev *cam) const char *msg; cam->current_image = &cam->imgs.image_ring[cam->imgs.ring_in]; - if (cam->camera_status == STATUS_OPENED) { + if (cam->device_status == STATUS_OPENED) { for (indx = 0; indx < 5; indx++) { if (mlp_cam_next(cam, cam->current_image) == CAPTURE_SUCCESS) { break; @@ -443,8 +443,8 @@ static void mlp_init_firstimage(ctx_dev *cam) } } - if ((indx >= 5) || (cam->camera_status != STATUS_OPENED)) { - if (cam->camera_status != STATUS_OPENED) { + if ((indx >= 5) || (cam->device_status != STATUS_OPENED)) { + if (cam->device_status != STATUS_OPENED) { msg = "Unable to open camera"; } else { msg = "Error capturing first image"; @@ -468,13 +468,13 @@ static void mlp_check_szimg(ctx_dev *cam) MOTION_LOG(CRT, TYPE_NETCAM, NO_ERRNO ,_("Image width (%d) or height(%d) requested is not modulo 8.") ,cam->imgs.width, cam->imgs.height); - cam->camera_status = STATUS_RESET; + cam->device_status = STATUS_RESET; } if ((cam->imgs.width < 64) || (cam->imgs.height < 64)) { MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO ,_("Motion only supports width and height greater than or equal to 64 %dx%d") ,cam->imgs.width, cam->imgs.height); - cam->camera_status = STATUS_RESET; + cam->device_status = STATUS_RESET; } /* Substream size notification*/ if ((cam->imgs.width % 16) || (cam->imgs.height % 16)) { @@ -554,7 +554,7 @@ static void mlp_init_values(ctx_dev *cam) } else { cam->threshold_maximum = (cam->imgs.height * cam->imgs.width * 3) / 2; } - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; cam->startup_frames = (cam->conf->framerate * 2) + cam->conf->pre_capture + cam->conf->minimum_motion_frames; cam->movie_passthrough = cam->conf->movie_passthrough; @@ -570,7 +570,7 @@ static void mlp_init_cam_start(ctx_dev *cam) { mlp_cam_start(cam); - if (cam->camera_status == STATUS_CLOSED) { + if (cam->device_status == STATUS_CLOSED) { MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,_("Failed to start camera.")); cam->imgs.width = cam->conf->width; cam->imgs.height = cam->conf->height; @@ -612,7 +612,7 @@ void mlp_cleanup(ctx_dev *cam) algsec_deinit(cam); - if (cam->camera_status == STATUS_OPENED) { + if (cam->device_status == STATUS_OPENED) { mlp_cam_close(cam); } @@ -655,12 +655,12 @@ void mlp_cleanup(ctx_dev *cam) /* initialize everything for the loop */ static void mlp_init(ctx_dev *cam) { - if ((cam->camera_status != STATUS_INIT) && - (cam->camera_status != STATUS_RESET)) { + if ((cam->device_status != STATUS_INIT) && + (cam->device_status != STATUS_RESET)) { return; } - if (cam->camera_status == STATUS_RESET) { + if (cam->device_status == STATUS_RESET) { mlp_cleanup(cam); } @@ -698,7 +698,7 @@ static void mlp_init(ctx_dev *cam) mlp_init_ref(cam); - if (cam->camera_status == STATUS_OPENED) { + if (cam->device_status == STATUS_OPENED) { MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO ,_("Camera %d started: motion detection %s"), cam->device_id, cam->pause ? _("Disabled"):_("Enabled")); @@ -797,7 +797,7 @@ static void mlp_retry(ctx_dev *cam) { int size_high; - if ((cam->camera_status == STATUS_CLOSED) && + if ((cam->device_status == STATUS_CLOSED) && (cam->frame_curr_ts.tv_sec % 10 == 0) && (cam->shots == 0)) { MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO @@ -809,7 +809,7 @@ static void mlp_retry(ctx_dev *cam) if (cam->imgs.width != cam->conf->width || cam->imgs.height != cam->conf->height) { MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO,_("Resetting image buffers")); - cam->camera_status = STATUS_RESET; + cam->device_status = STATUS_RESET; } /* * For high res, we check the size of buffer to determine whether to break out @@ -818,7 +818,7 @@ static void mlp_retry(ctx_dev *cam) */ size_high = (cam->imgs.width_high * cam->imgs.height_high * 3) / 2; if (cam->imgs.size_high != size_high) { - cam->camera_status = STATUS_RESET; + cam->device_status = STATUS_RESET; } } @@ -831,7 +831,7 @@ static int mlp_capture(ctx_dev *cam) char tmpout[80]; int retcd; - if (cam->camera_status != STATUS_OPENED) { + if (cam->device_status != STATUS_OPENED) { return 0; } @@ -857,13 +857,13 @@ static int mlp_capture(ctx_dev *cam) cam->missing_frame_counter++; - if ((cam->camera_status == STATUS_OPENED) && + if ((cam->device_status == STATUS_OPENED) && (cam->missing_frame_counter < (cam->conf->device_tmo * cam->conf->framerate))) { memcpy(cam->current_image->image_norm, cam->imgs.image_vprvcy, cam->imgs.size_norm); } else { cam->lost_connection = 1; - if (cam->camera_status == STATUS_OPENED) { + if (cam->device_status == STATUS_OPENED) { tmpin = "CONNECTION TO CAMERA LOST\\nSINCE %Y-%m-%d %T"; } else { tmpin = "UNABLE TO OPEN VIDEO DEVICE\\nSINCE %Y-%m-%d %T"; @@ -882,7 +882,7 @@ static int mlp_capture(ctx_dev *cam) event(cam, EVENT_CAMERA_LOST, NULL, NULL, NULL, &cam->connectionlosttime); } - if ((cam->camera_status == STATUS_OPENED) && + if ((cam->device_status == STATUS_OPENED) && (cam->missing_frame_counter == ((cam->conf->device_tmo * 4) * cam->conf->framerate))) { MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO ,_("Video signal still lost - Trying to close video device")); @@ -1434,7 +1434,7 @@ void *motion_loop(void *arg) cam->finish_dev = false; cam->restart_dev = false; - cam->camera_status = STATUS_INIT; + cam->device_status = STATUS_INIT; while (cam->finish_dev == false) { mlp_init(cam); diff --git a/src/motionplus.cpp b/src/motionplus.cpp index f51002da..01cb92b6 100644 --- a/src/motionplus.cpp +++ b/src/motionplus.cpp @@ -22,6 +22,7 @@ #include "logger.hpp" #include "util.hpp" #include "motion_loop.hpp" +#include "sound.hpp" #include "dbse.hpp" #include "webu.hpp" #include "video_v4l2.hpp" @@ -64,15 +65,15 @@ static void motion_signal_process(ctx_motapp *motapp) case MOTION_SIGNAL_SIGTERM: /* Quit application */ motapp->webcontrol_finish = true; - - if (motapp->cam_list != NULL) { - indx = 0; - while (motapp->cam_list[indx]) { - motapp->cam_list[indx]->event_stop = true; - motapp->cam_list[indx]->finish_dev = true; - motapp->cam_list[indx]->restart_dev = false; - indx++; - } + for (indx=0; indxcam_cnt; indx++) { + motapp->cam_list[indx]->event_stop = true; + motapp->cam_list[indx]->finish_dev = true; + motapp->cam_list[indx]->restart_dev = false; + } + for (indx=0; indxsnd_cnt; indx++) { + motapp->snd_list[indx]->event_stop = true; + motapp->snd_list[indx]->finish_dev = true; + motapp->snd_list[indx]->restart_dev = false; } motapp->finish_all = true; default: @@ -272,47 +273,61 @@ static void motion_shutdown(ctx_motapp *motapp) } -static void motion_device_ids(ctx_dev **cam_list) +static void motion_device_ids(ctx_motapp *motapp) { - /* Set the camera id's on the ctx_dev. They must be unique */ + /* Set the device id's on the ctx_dev. They must be unique */ int indx, indx2; int invalid_ids; - /* Set defaults */ - indx = 0; - while (cam_list[indx] != NULL){ - if (cam_list[indx]->conf->device_id > 0) { - cam_list[indx]->device_id = cam_list[indx]->conf->device_id; + /* Defaults */ + for (indx=0; indxcam_cnt; indx++) { + if (motapp->cam_list[indx]->conf->device_id != 0) { + motapp->cam_list[indx]->device_id = motapp->cam_list[indx]->conf->device_id; } else { - cam_list[indx]->device_id = indx; + motapp->cam_list[indx]->device_id = indx + 1; + } + } + for (indx=0; indxsnd_cnt; indx++) { + if (motapp->snd_list[indx]->conf->device_id != 0) { + motapp->snd_list[indx]->device_id = motapp->snd_list[indx]->conf->device_id; + } else { + motapp->snd_list[indx]->device_id = motapp->cam_cnt + indx + 1; } - indx++; } + /*Check for unique values*/ invalid_ids = false; - indx = 0; - while (cam_list[indx] != NULL){ - if (cam_list[indx]->device_id > 32000) { - invalid_ids = true; - } - indx2 = indx + 1; - while (cam_list[indx2] != NULL){ - if (cam_list[indx]->device_id == cam_list[indx2]->device_id) { + for (indx=0; indxcam_cnt; indx++) { + for (indx2=indx+1; indx2cam_cnt; indx2++) { + if (motapp->cam_list[indx]->device_id == motapp->cam_list[indx2]->device_id) { + invalid_ids = true; + } + } + for (indx2=0; indx2snd_cnt; indx2++) { + if (motapp->cam_list[indx]->device_id == motapp->snd_list[indx2]->device_id) { invalid_ids = true; } - indx2++; } - indx++; } + for (indx=0; indxsnd_cnt; indx++) { + for (indx2=indx+1; indx2snd_cnt; indx2++) { + if (motapp->snd_list[indx]->device_id == motapp->snd_list[indx2]->device_id) { + invalid_ids = true; + } + } + } + if (invalid_ids) { - MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO - ,_("Camara IDs are not unique or have values over 32,000. Falling back to thread numbers")); - indx = 0; - while (cam_list[indx] != NULL){ - cam_list[indx]->device_id = indx+1; - indx++; + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Device IDs are not unique.")); + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Falling back to sequence numbers")); + for (indx=0; indxcam_cnt; indx++) { + motapp->cam_list[indx]->device_id = indx + 1; + } + for (indx=0; indxsnd_cnt; indx++) { + motapp->snd_list[indx]->device_id = motapp->cam_cnt + indx + 1; } } + } static void motion_ntc(void) @@ -366,6 +381,18 @@ static void motion_ntc(void) MOTION_LOG(DBG, TYPE_DB, NO_ERRNO,_("nls : not available")); #endif + #ifdef HAVE_ALSA + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,_("alsa : available")); + #else + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,_("alsa : not available")); + #endif + + #ifdef HAVE_FFTW3 + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,_("fftw3 : available")); + #else + MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,_("fftw3 : not available")); + #endif + } /** Initialize upon start up or restart */ @@ -399,7 +426,7 @@ static void motion_startup(ctx_motapp *motapp, int daemonize) motion_ntc(); - motion_device_ids(motapp->cam_list); + motion_device_ids(motapp); dbse_init(motapp); @@ -410,23 +437,36 @@ static void motion_startup(ctx_motapp *motapp, int daemonize) } /** Start a camera thread */ -static void motion_start_thread(ctx_motapp *motapp, int indx) +static void motion_start_thread_cam(ctx_dev *cam) { int retcd; - pthread_attr_t thread_attr; pthread_attr_init(&thread_attr); pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); - motapp->cam_list[indx]->restart_dev = true; - - retcd = pthread_create(&motapp->cam_list[indx]->thread_id - , &thread_attr, &motion_loop, motapp->cam_list[indx]); + cam->restart_dev = true; + retcd = pthread_create(&cam->thread_id, &thread_attr, &motion_loop, cam); if (retcd != 0) { - MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Unable to start thread for MotionPlus loop.")); + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Unable to start camera thread.")); } + pthread_attr_destroy(&thread_attr); +} + +static void motion_start_thread_snd(ctx_dev *snd) +{ + int retcd; + pthread_attr_t thread_attr; + + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); + + snd->restart_dev = true; + retcd = pthread_create(&snd->thread_id, &thread_attr, &snd_loop, snd); + if (retcd != 0) { + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("Unable to start sound thread.")); + } pthread_attr_destroy(&thread_attr); } @@ -441,6 +481,7 @@ static void motion_restart(ctx_motapp *motapp) SLEEP(2, 0); motion_startup(motapp, false); + MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,_("MotionPlus restarted")); motapp->restart_all = false; @@ -550,6 +591,11 @@ static int motion_check_threadcount(ctx_motapp *motapp) thrdcnt++; } } + for (indx=0; indxsnd_cnt; indx++) { + if (motapp->snd_list[indx]->running_dev || motapp->snd_list[indx]->restart_dev) { + thrdcnt++; + } + } if ((motapp->webcontrol_finish == false) && (motapp->webcontrol_daemon != NULL)) { @@ -573,6 +619,9 @@ static void motion_init(ctx_motapp *motapp, int argc, char *argv[]) motapp->cam_list = (ctx_dev **)mymalloc(sizeof(ctx_dev *)); motapp->cam_list[0] = NULL; + motapp->snd_list = (ctx_dev **)mymalloc(sizeof(ctx_dev *)); + motapp->snd_list[0] = NULL; + motapp->threads_running = 0; motapp->finish_all = false; motapp->restart_all = false; @@ -581,6 +630,7 @@ static void motion_init(ctx_motapp *motapp, int argc, char *argv[]) motapp->cam_add = false; motapp->cam_delete = -1; motapp->cam_cnt = 0; + motapp->snd_cnt = 0; motapp->conf = new ctx_config; motapp->dbse = NULL; @@ -696,7 +746,6 @@ static void motion_cam_delete(ctx_motapp *motapp) } - /** Main entry point of MotionPlus. */ int main (int argc, char **argv) { @@ -720,12 +769,15 @@ int main (int argc, char **argv) } for (indx=0; indxcam_cnt; indx++) { - motapp->cam_list[indx]->threadnr = indx; - motion_start_thread(motapp, indx); + motion_start_thread_cam(motapp->cam_list[indx]); + } + + for (indx=0; indxsnd_cnt; indx++) { + motion_start_thread_snd(motapp->snd_list[indx]); } MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO - ,_("Waiting for threads to finish, pid: %d"), getpid()); + ,_("Motionplus pid: %d"), getpid()); while (true) { SLEEP(1, 0); @@ -741,10 +793,18 @@ int main (int argc, char **argv) MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO ,_("MotionPlus camera %d restart") , motapp->cam_list[indx]->device_id); - motion_start_thread(motapp, indx); + motion_start_thread_cam(motapp->cam_list[indx]); } motion_watchdog(motapp, indx); - + } + for (indx=0; indxsnd_cnt; indx++) { + if ((motapp->snd_list[indx]->running_dev == false) && + (motapp->snd_list[indx]->restart_dev == true)) { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + ,_("MotionPlus sound %d restart") + , motapp->snd_list[indx]->device_id); + motion_start_thread_snd(motapp->snd_list[indx]); + } } if (motsignal != MOTION_SIGNAL_NONE) { @@ -761,7 +821,7 @@ int main (int argc, char **argv) motapp->finish_all = false; - MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Threads finished")); + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Motionplus devices finished")); if (motapp->restart_all) { SLEEP(1, 0); /* Rest before restarting */ diff --git a/src/motionplus.hpp b/src/motionplus.hpp index e4880157..b7576e65 100644 --- a/src/motionplus.hpp +++ b/src/motionplus.hpp @@ -75,6 +75,18 @@ extern "C" { #endif #endif +#ifdef HAVE_ALSA + extern "C" { + #include + } +#endif + +#ifdef HAVE_FFTW3 + extern "C" { + #include + } +#endif + /* Forward declarations, used in functional definitions of headers */ struct ctx_motapp; struct ctx_rotate; @@ -181,7 +193,7 @@ enum CAPTURE_RESULT { CAPTURE_ATTEMPTED }; -enum CAM_STATUS { +enum DEVICE_STATUS { STATUS_CLOSED, /* Camera is closed */ STATUS_INIT, /* First time initialize */ STATUS_RESET, /* Clean up and re-initialize */ @@ -299,6 +311,60 @@ struct ctx_stream { ctx_stream_data secondary; /* Copy of the image to use for web stream*/ }; +struct ctx_snd_alert { + int alert_id; /* Id number for the alert*/ + std::string alert_nm; /* Name of the alert*/ + int volume_level; /* Volume level required to consider the sample*/ + int volume_count; /* For each sample, number of times required to exceed volumne level*/ + float freq_low; /* Lowest frequency for detecting this alert*/ + float freq_high; /* Highest frequency for detecting this alert*/ + int trigger_count; /* Count of how many times it has been triggered so far*/ + int trigger_threshold; /* How many times does it need to be triggered before an event*/ + timespec trigger_time; /* The last time the trigger was invoked */ + int trigger_duration; /* Min. duration to trigger a new /event */ +}; + +struct ctx_snd_alsa { + #ifdef HAVE_ALSA + std::string source; /* Source string in ALSA format e.g. hw:1,0*/ + unsigned int sample_rate; /* Sample rate of sound source*/ + int channels; /* Number of audio channels */ + std::string device_nm; + int device_id; + snd_pcm_t *pcm_dev; + snd_pcm_info_t *pcm_info; + int card_id; + snd_ctl_card_info_t *card_info; + snd_ctl_t *ctl_hdl; + int16_t *input_buffer; + int buf_frames; + long buf_size; + int buf_items; + #else + int dummy; + #endif +}; + +struct ctx_snd_fftw { + #ifdef HAVE_FFTW3 + fftw_plan ff_plan; + double *ff_in; + fftw_complex *ff_out; + int bin_max; + int bin_min; + float bin_size; + #else + int dummy; + #endif +}; + +struct ctx_snd_vars { + std::list snd_alerts; /* list of sound alert criteria */ + int snd_vol_min; /* The minimum volume from alerts*/ + int snd_vol_max; /* Maximum volume of sample*/ + int snd_vol_count; /* Number of times volumne exceeded user specified volume level */ +}; + struct ctx_dev { ctx_motapp *motapp; @@ -327,7 +393,7 @@ struct ctx_dev { int track_posy; int device_id; enum CAMERA_TYPE camera_type; - enum CAM_STATUS camera_status; + enum DEVICE_STATUS device_status; unsigned int new_img; int locate_motion_mode; int locate_motion_style; @@ -391,6 +457,7 @@ struct ctx_dev { unsigned int passflag; //only purpose is to flag first frame vs all others..... pthread_mutex_t parms_lock; + ctx_params *params; /* Device parameters*/ bool parms_changed; /*bool indicating if the parms have changed */ uint64_t info_diff_tot; @@ -399,12 +466,17 @@ struct ctx_dev { int info_sdev_max; uint64_t info_sdev_tot; + ctx_snd_fftw *snd_fftw; /* fftw for sound*/ + ctx_snd_alsa *snd_alsa; /* Alsa device for sound*/ + ctx_snd_vars *snd_vars; /* Values for sound processing*/ + }; /* ctx_motapp for whole motion application including all the cameras */ struct ctx_motapp { ctx_dev **cam_list; + ctx_dev **snd_list; pthread_mutex_t global_lock; volatile int threads_running; @@ -418,6 +490,7 @@ struct ctx_motapp { bool pause; ctx_config *conf; int cam_cnt; + int snd_cnt; volatile int webcontrol_running; volatile int webcontrol_finish; diff --git a/src/netcam.cpp b/src/netcam.cpp index ca09d789..1c77f9d2 100644 --- a/src/netcam.cpp +++ b/src/netcam.cpp @@ -2159,7 +2159,7 @@ void netcam_cleanup(ctx_dev *cam) } cam->netcam = NULL; cam->netcam_high = NULL; - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; } @@ -2241,7 +2241,7 @@ void netcam_start(ctx_dev *cam) indx_cam++; } - cam->camera_status = STATUS_OPENED; + cam->device_status = STATUS_OPENED; return; diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 00000000..5e4c7a57 --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,795 @@ +/* + * This file is part of MotionPlus. + * + * MotionPlus 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. + * + * MotionPlus 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 MotionPlus. If not, see . + * + * Copyright 2020-2023 MotionMrDave@gmail.com + * + */ + +#include "motionplus.hpp" +#include "conf.hpp" +#include "logger.hpp" +#include "util.hpp" +#include "sound.hpp" + +#if defined(HAVE_ALSA) && defined(HAVE_FFTW3) + +static float HammingWindow(int n1, int N2){ + return 0.54F - 0.46F * (float)(cos((2 * M_PI * n1)) / (N2 - 1)); +} +static float HannWindow(int n1, int N2){ + return 0.5F * (float)(1 - (cos(2 * M_PI * n1 * N2))); +} + +static void snd_init_values(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + ctx_snd_vars *vars = snd->snd_vars; + + alsa->pcm_dev = NULL; + alsa->pcm_info = NULL; + alsa->card_id = -1; + alsa->card_info = NULL; + alsa->pcm_dev = NULL; + alsa->sample_rate = 0; + alsa->channels = 0; + alsa->buf_frames = 0; + alsa->buf_size = 0; + alsa->buf_items = 0; + alsa->input_buffer = 0; + alsa->ctl_hdl = NULL; + alsa->card_info = NULL; + alsa->card_id = 0; + alsa->pcm_info = NULL; + alsa->device_nm = ""; + alsa->device_id = 0; + + snd->snd_fftw->bin_max = 0; + snd->snd_fftw->bin_min = 0; + snd->snd_fftw->bin_size = 0; + snd->snd_fftw->ff_in = NULL; + snd->snd_fftw->ff_out = NULL; + snd->snd_fftw->ff_plan = NULL; + + vars->snd_vol_count = 0; + vars->snd_vol_max = 0; + vars->snd_vol_min = 9999; + +} + +static void snd_init_alerts(ctx_snd_alert *tmp_alert) +{ + tmp_alert->alert_id = 0; + tmp_alert->alert_nm = ""; + tmp_alert->freq_high = 0; + 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); + +} + +static void snd_edit_alerts(ctx_dev *snd) +{ + ctx_snd_vars *vars = snd->snd_vars; + std::list::iterator it_a0; + std::list::iterator it_a1; + int indx, id_chk, id_cnt; + bool validids; + + validids = true; + for (it_a0=vars->snd_alerts.begin(); it_a0!=vars->snd_alerts.end(); it_a0++) { + id_chk = it_a0->alert_id; + id_cnt = 0; + for (it_a1=vars->snd_alerts.begin(); it_a1!=vars->snd_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=vars->snd_alerts.begin(); it_a0!=vars->snd_alerts.end(); it_a0++) { + it_a0->alert_id = indx; + indx++; + } + } + + for (it_a0=vars->snd_alerts.begin(); it_a0!=vars->snd_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 < vars->snd_vol_min) { + vars->snd_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_count: %d",it_a0->trigger_count); + 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); + } + +} + +static void snd_load_alerts(ctx_dev *snd) +{ + ctx_snd_vars *vars = snd->snd_vars; + ctx_snd_alert tmp_alert; + std::list parm_val; + std::list::iterator it_p; + ctx_params *tmp_params; + int indx; + + conf_edit_get(snd->conf, "snd_alerts", parm_val, PARM_CAT_18); + + for (it_p=parm_val.begin(); it_p!=parm_val.end(); it_p++) { + tmp_params = (ctx_params*)mymalloc(sizeof(ctx_params)); + tmp_params->update_params = true; + util_parms_parse(tmp_params, it_p->c_str()); + snd_init_alerts(&tmp_alert); + for (indx=0; indxparams_count; indx++) { + if (mystreq(tmp_params->params_array[indx].param_name,"alert_id")) { + tmp_alert.alert_id = atoi(tmp_params->params_array[indx].param_value); + } + if (mystreq(tmp_params->params_array[indx].param_name,"alert_nm")) { + tmp_alert.alert_nm = (tmp_params->params_array[indx].param_value); + } + if (mystreq(tmp_params->params_array[indx].param_name,"freq_low")) { + tmp_alert.freq_low = atof(tmp_params->params_array[indx].param_value); + } + if (mystreq(tmp_params->params_array[indx].param_name,"freq_high")) { + tmp_alert.freq_high = atof(tmp_params->params_array[indx].param_value); + } + if (mystreq(tmp_params->params_array[indx].param_name,"volume_count")) { + tmp_alert.volume_count = atoi(tmp_params->params_array[indx].param_value); + } + if (mystreq(tmp_params->params_array[indx].param_name,"volume_level")) { + tmp_alert.volume_level = atoi(tmp_params->params_array[indx].param_value); + } + } + vars->snd_alerts.push_back(tmp_alert); + util_parms_free(tmp_params); + myfree(&tmp_params); + tmp_params = NULL; + } + + snd_edit_alerts(snd); +} + +static void snd_load_params(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + int indx; + + snd->params = (ctx_params*)mymalloc(sizeof(ctx_params)); + snd->params->update_params = true; + util_parms_parse(snd->params, snd->conf->snd_params); + + util_parms_add_default(snd->params,"source","alsa"); + util_parms_add_default(snd->params,"channels","1"); + util_parms_add_default(snd->params,"frames","2048"); + util_parms_add_default(snd->params,"sample","44100"); + + for (indx = 0; indx < snd->params->params_count; indx++) { + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s : %s" + , snd->params->params_array[indx].param_name + , snd->params->params_array[indx].param_value + ); + } + + for (indx = 0; indx < snd->params->params_count; indx++) { + if (mystreq(snd->params->params_array[indx].param_name,"source")) { + alsa->source = snd->params->params_array[indx].param_value; + } + if (mystreq(snd->params->params_array[indx].param_name,"channels")) { + alsa->channels =atoi(snd->params->params_array[indx].param_value); + } + if (mystreq(snd->params->params_array[indx].param_name,"frames")) { + alsa->buf_frames =atoi(snd->params->params_array[indx].param_value); + } + if (mystreq(snd->params->params_array[indx].param_name,"sample")) { + alsa->sample_rate =atoi(snd->params->params_array[indx].param_value); + } + } + +} + +static void snd_device_list_subdev(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->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; indxpcm_info,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)); + } + } + +} + +static void snd_device_list_card(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->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, 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) { + snd_device_list_subdev(snd); + } 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")); + } + } + +} + +static void snd_device_list(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + int retcd; + + if (snd->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...")); + snd->device_status = STATUS_CLOSED; + snd->restart_dev = false; + snd->finish_dev = true; + 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) { + snd_device_list_card(snd); + 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); + } + +} + +static void snd_fftw_open(ctx_dev *snd) +{ + ctx_snd_fftw *fftw = snd->snd_fftw; + ctx_snd_alsa *alsa = snd->snd_alsa; + int indx; + + if (snd->device_status == STATUS_CLOSED) { + snd->finish_dev = true; + snd->restart_dev = false; + return; + } + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO + , _("Opening FFTW plan")); + + fftw->ff_in = (double*) fftw_malloc( + sizeof(fftw_complex) * alsa->buf_frames); + fftw->ff_out = (fftw_complex*) fftw_malloc( + sizeof(fftw_complex) * alsa->buf_frames); + fftw->ff_plan = fftw_plan_dft_r2c_1d( + alsa->buf_frames, fftw->ff_in, fftw->ff_out, FFTW_MEASURE); + + for (indx=0; indxbuf_frames; indx++) { + fftw->ff_in[indx] = 0; + } + fftw->bin_min = 1; + fftw->bin_max = (alsa->buf_frames / 2); + fftw->bin_size = ((float)alsa->sample_rate / (float)alsa->buf_frames); + alsa->buf_items = alsa->sample_rate * alsa->channels; + +} + +static void snd_device_start(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->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; + int retcd; + + frames_per = alsa->buf_frames; + + retcd = snd_pcm_open(&alsa->pcm_dev + , snd->conf->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)") + , snd->conf->snd_device.c_str(), snd_strerror (retcd)); + snd->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)); + snd->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)); + snd->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)); + snd->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)); + snd->device_status = STATUS_CLOSED; + return; + } + + retcd = snd_pcm_hw_params_set_rate_near(alsa->pcm_dev + , hw_params, &alsa->sample_rate, 0); + if (retcd < 0) { + MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO + , _("error: snd_pcm_hw_params_set_rate_near(%s)") + , snd_strerror (retcd)); + snd->device_status = STATUS_CLOSED; + return; + } + + retcd = snd_pcm_hw_params_set_channels(alsa->pcm_dev + , hw_params, alsa->channels); + if (retcd < 0) { + MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO + , _("error: snd_pcm_hw_params_set_channels(%s)") + , snd_strerror (retcd)); + snd->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)); + snd->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)); + snd->device_status = STATUS_CLOSED; + return; + } + + snd_pcm_hw_params_free(hw_params); + + 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)); + snd->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)); + snd->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)); + snd->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)); + snd->device_status = STATUS_CLOSED; + return; + } + + 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 */ + /*************************************************************/ + alsa->buf_size = alsa->buf_frames * 2; + alsa->input_buffer = (int16_t*)mymalloc(alsa->buf_size * sizeof(int16_t)); + memset(alsa->input_buffer, 0x00, alsa->buf_size * sizeof(int16_t)); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started."); + snd->device_status =STATUS_OPENED; + +} + +static void slp_capture(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + int retcd; + + if (snd->device_status == STATUS_CLOSED) { + snd->finish_dev = true; + snd->restart_dev = false; + return; + } + + retcd = snd_pcm_readi(alsa->pcm_dev, alsa->input_buffer + , alsa->buf_frames); + if (retcd != alsa->buf_frames) { + MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO + , _("error: read from audio interface failed (%s)") + , snd_strerror (retcd)); + snd->device_status = STATUS_CLOSED; + snd->finish_dev = true; + snd->restart_dev = false; + return; + } + +} + +void snd_cleanup(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + + if (alsa->pcm_dev != NULL) { + snd_pcm_close(alsa->pcm_dev); + snd_config_update_free_global(); + } + + snd_config_update_free_global(); + + if (alsa->input_buffer != NULL) { + free(alsa->input_buffer); + alsa->input_buffer = NULL; + } + + util_parms_free(snd->params); + myfree(&snd->params); + snd->params = NULL; + + fftw_destroy_plan(snd->snd_fftw->ff_plan); + fftw_free(snd->snd_fftw->ff_in); + fftw_free(snd->snd_fftw->ff_out); + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Stopped."); + +} + +static void slp_init(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + + if ((snd->device_status != STATUS_INIT) && + (snd->device_status != STATUS_RESET)) { + return; + } + + if (snd->device_status == STATUS_RESET) { + snd_cleanup(snd); + } + + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Initialize")); + + snd_init_values(snd); + + snd_load_params(snd); + + snd_load_alerts(snd); + + if (alsa->source == "alsa") { + snd_device_list(snd); + snd_device_start(snd); + snd_fftw_open(snd); + } else { + MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,_("Invalid sound source.")); + snd->finish_dev = true; + snd->device_status = STATUS_CLOSED; + } + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Detecting")); + +} + +static void snd_check_alerts(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + ctx_snd_vars *vars = snd->snd_vars; + float freq_value; + int indx, chkval, chkcnt; + float pMaxIntensity; + int pMaxBinIndex; + float pRealNbr; + float pImaginaryNbr; + float pIntensity; + bool trigger; + std::string pcmd; + std::list::iterator it; + struct timespec trig_ts; + + for (indx=0;indx buf_frames;indx++){ + if (snd->conf->snd_window == "hamming") { + snd->snd_fftw->ff_in[indx] = alsa->input_buffer[indx] * HammingWindow(indx, alsa->buf_frames); + } else if (snd->conf->snd_window == "hann") { + snd->snd_fftw->ff_in[indx] = alsa->input_buffer[indx] * HannWindow(indx, alsa->buf_frames); + } else { + snd->snd_fftw->ff_in[indx] = alsa->input_buffer[indx]; + } + } + + fftw_execute(snd->snd_fftw->ff_plan); + + pMaxIntensity = 0; + pMaxBinIndex = 0; + + for (indx = snd->snd_fftw->bin_min; indx <= snd->snd_fftw->bin_max; indx++){ + pRealNbr = snd->snd_fftw->ff_out[indx][0]; + pImaginaryNbr = snd->snd_fftw->ff_out[indx][1]; + pIntensity = pRealNbr * pRealNbr + pImaginaryNbr * pImaginaryNbr; + if (pIntensity > pMaxIntensity){ + pMaxIntensity = pIntensity; + pMaxBinIndex = indx; + } + } + + freq_value = (snd->snd_fftw->bin_size * pMaxBinIndex * alsa->channels); + + if (snd->conf->show_freq) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO + , _("Freq: %.4f threshold: %d count: %d maximum: %d") + , freq_value, vars->snd_vol_min + , vars->snd_vol_count, vars->snd_vol_max); + } + + for (it=vars->snd_alerts.begin(); it!=vars->snd_alerts.end(); it++) { + trigger = false; + if ((freq_value >= it->freq_low) && (freq_value <= it->freq_high)) { + chkcnt = 0; + for(indx=0; indx < alsa->buf_frames ; indx++) { + chkval = abs((int)alsa->input_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 + , vars->snd_vol_max); + if (snd->conf->on_sound_alert != "") { + pcmd = snd->conf->on_sound_alert; + pcmd = pcmd + " " + std::to_string(it->alert_id); + pcmd = pcmd + " " + std::to_string(freq_value); + util_exec_command(snd, pcmd.c_str(), NULL, 0); + } + } + } + } + +} + +static void snd_check_levels(ctx_dev *snd) +{ + ctx_snd_alsa *alsa = snd->snd_alsa; + ctx_snd_vars *vars = snd->snd_vars; + int indx, chkval; + + if (snd->device_status == STATUS_CLOSED) { + snd->finish_dev = true; + snd->restart_dev = false; + return; + } + + vars->snd_vol_max = 0; + vars->snd_vol_count = 0; + + for(indx=0; indx < alsa->buf_frames; indx++) { + chkval = abs((int)alsa->input_buffer[indx] / 256); + if (chkval > vars->snd_vol_max) vars->snd_vol_max = chkval ; + if (chkval > vars->snd_vol_min) vars->snd_vol_count++; + } + + if (vars->snd_vol_count > 0) { + snd_check_alerts(snd); + } + + } + +void *snd_loop(void *arg) +{ + ctx_dev *snd = (ctx_dev *)arg; + + pthread_mutex_lock(&snd->motapp->global_lock); + snd->motapp->threads_running++; + pthread_mutex_unlock(&snd->motapp->global_lock); + + mythreadname_set("sl", snd->threadnr, snd->conf->device_name.c_str()); + pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)snd->threadnr)); + + snd->snd_fftw = new ctx_snd_fftw; + snd->snd_alsa = new ctx_snd_alsa; + snd->snd_vars = new ctx_snd_vars; + snd->running_dev = true; + snd->finish_dev = false; + snd->restart_dev = false; + snd->device_status = STATUS_INIT; + + while (snd->finish_dev == false) { + slp_init(snd); + slp_capture(snd); + snd_check_levels(snd); + } + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Capture loop finished")); + + snd_cleanup(snd); + + pthread_mutex_lock(&snd->motapp->global_lock); + snd->motapp->threads_running--; + pthread_mutex_unlock(&snd->motapp->global_lock); + + snd->finish_dev = true; + snd->running_dev = false; + delete snd->snd_alsa; + delete snd->snd_fftw; + delete snd->snd_vars; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Exiting")); + + pthread_exit(NULL); + +} + +#else + +void *snd_loop(void *arg) +{ + ctx_dev *snd =(ctx_dev *) arg; + + snd->restart_dev = false; + snd->finish_dev = true; + snd->running_dev = false; + + MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, _("Sound detection functionality not supported")); + + pthread_exit(NULL); +} + +#endif diff --git a/src/sound.hpp b/src/sound.hpp new file mode 100644 index 00000000..e2a03f3a --- /dev/null +++ b/src/sound.hpp @@ -0,0 +1,26 @@ +/* + * This file is part of MotionPlus. + * + * MotionPlus 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. + * + * MotionPlus 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 MotionPlus. If not, see . + * + * Copyright 2020-2023 MotionMrDave@gmail.com + */ + +#ifndef _INCLUDE_SOUND_HPP_ +#define _INCLUDE_SOUND_HPP_ + + void *snd_loop(void *arg); + void snd_cleanup(ctx_dev *snd); + +#endif /* _INCLUDE_SOUND_HPP_ */ diff --git a/src/video_v4l2.cpp b/src/video_v4l2.cpp index 9ddd23a2..c3b09b32 100644 --- a/src/video_v4l2.cpp +++ b/src/video_v4l2.cpp @@ -1315,7 +1315,7 @@ void v4l2_cleanup(ctx_dev *cam) myfree(&cam->v4l2cam); #endif // HAVE_V4L2 - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; } @@ -1343,10 +1343,10 @@ void v4l2_start(ctx_dev *cam) v4l2_cleanup(cam); return; } - cam->camera_status = STATUS_OPENED; + cam->device_status = STATUS_OPENED; #else - cam->camera_status = STATUS_CLOSED; + cam->device_status = STATUS_CLOSED; #endif // HAVE_V4l2 }