/* * Copyright (c) Linux community. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include "timer.h" #include "utility.h" #include "support.h" #include "dconf.h" #include "log.h" #include "gst-listener.h" #include "audio-sources.h" #include "rec-manager.h" // Timer function call frequency in seconds #define TIMER_CALL_FREQ 1 // Default silence period (in seconds) #define DEF_SILENCE_DURATION 7 // Default sound/audio/voice period (in seconds) #define DEF_SOUND_DURATION 3 // Default RMS level for "silence" and "sound", "voice", "audio". #define DEF_RMS_LEVEL 7 // 7% (ca. -24dB) // Timer function id static guint g_timer_func_id = 0; // A GList of TimerRec nodes from the timer-parser.c G_LOCK_DEFINE_STATIC(g_t_list); static GList *g_t_list = NULL; // Timer's start time static struct tm g_timer_start_time; void timer_func_stop(); gboolean timer_func_cb(gpointer user_data); void timer_func_exit_cb(gpointer user_data); void timer_func_exec_command(TimerRec *tr); static void timer_set_start_time(); static struct tm timer_get_start_time(); static void timer_update_records_1(); static void timer_update_records_2(); static gchar timer_test_filesize(TimerRec *tr); static gchar timer_test_silence(TimerRec *tr); static gchar timer_test_sound(TimerRec *tr); static gchar timer_test_clock_time(TimerRec *tr); static gchar timer_test_time_duration(TimerRec *tr); void timer_module_init() { LOG_DEBUG("Init timer.c.\n"); // Init gst-listener.c listener_module_init(); g_timer_func_id = 0; // Init parser module parser_module_init(); // Start the timer function timer_func_start(); } void timer_module_exit() { LOG_DEBUG("Clean up timer.c.\n"); // Stop timer function timer_func_stop(); // Clean up parser module parser_module_exit(); // Clean up gst-listener.c listener_module_exit(); g_t_list = NULL; } void timer_set_debug_flag(gboolean on_off) { // Set debug flag. Please see application options: // $ audio-recorder --help listener_set_debug_flag(on_off); } void timer_module_reset(gint for_state) { // Reset timer before we move to the given state switch (for_state) { case GST_STATE_PLAYING: timer_update_records_1(); break; case GST_STATE_PAUSED: case GST_STATE_NULL: timer_update_records_2(); break; default: ; } } void timer_module_rec_start() { // Called when recording stops. // Reset listener's signal/level statistics listener_clear_data(); // Reset timer timer_update_records_2(); } // -------------------------------------------------- // The actual timer function // -------------------------------------------------- void timer_func_start() { // Already running? if (g_timer_func_id > 0) { return; } // Start the timer function g_timer_func_id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, TIMER_CALL_FREQ, (GSourceFunc)timer_func_cb, (gpointer)1/*!= 0*/, (GDestroyNotify)timer_func_exit_cb); } void timer_func_stop() { // Stop the timer funcion if (g_timer_func_id > 0) { g_source_remove(g_timer_func_id); } g_timer_func_id = 0; } void timer_func_exit_cb(gpointer user_data) { // Nothing to cleanup ; } void timer_settings_changed() { // Increment the counter so various modules know that the timer-settings have been altered gint count = 0; conf_get_int_value("timer-setting-counter", &count); conf_save_int_value("timer-setting-counter", count+1); } static void timer_update_records_1() { // Reset timer nodes G_LOCK(g_t_list); GList *item = g_list_first(g_t_list); while (item) { TimerRec *tr = (TimerRec*)item->data; if (tr->done) { // Start to count from 0 tr->seconds = 0; tr->seconds_x = 0; } // Next item item = g_list_next(item); } G_UNLOCK(g_t_list); } static void timer_update_records_2() { // Reset timer nodes G_LOCK(g_t_list); GList *item = g_list_first(g_t_list); while (item) { TimerRec *tr = (TimerRec*)item->data; // Start to count from 0 tr->seconds = 0; tr->seconds_x = 0; // Next item item = g_list_next(item); } G_UNLOCK(g_t_list); } static void timer_clear_list() { // Reset the timer list // Lock g_timer_list G_LOCK(g_t_list); parser_free_list(); g_t_list = NULL; // Unlock G_UNLOCK(g_t_list); } static void timer_set_start_time() { // Set timer's start time time_t t = time(NULL); localtime_r(&t, &g_timer_start_time); } static struct tm timer_get_start_time() { return g_timer_start_time; } gboolean timer_func_cb(gpointer user_data) { // The actual timer function // Timer is ON/OFF? static gboolean timer_active = FALSE; // Do we need data from the gst-listener.c? static gboolean need_listener = FALSE; // Counter to detect if GConf settings have been altered static gint setting_counter = -1; // Timer (GConf) settings changed? gint val = 0; conf_get_int_value("timer-setting-counter", &val); if (val != setting_counter) { // Save settings counter setting_counter = val; // Get new values from GConf conf_get_boolean_value("timer-active", &timer_active); LOG_TIMER("Timer settings changed:<%s>\n", (timer_active ? "timer ON" : "timer OFF")); // Timer is ON/OFF? if (!timer_active) { // It's OFF timer_clear_list(); // Stop the listener listener_stop_listening(); goto LBL_1; } else { // Set timer's start time timer_set_start_time(); // Free the old g_t_list timer_clear_list(); // Set lock G_LOCK(g_t_list); // Get timer text gchar *timer_text = NULL; conf_get_string_value("timer-text", &timer_text); LOG_TIMER("----------------\nTimer text is:\n<%s>\n--------------\n", timer_text); // Parse timer conditions. // This will return pointer to the g_timer_list (GList) in timer-parser.c. g_t_list = parser_parse_actions(timer_text); g_free(timer_text); if (g_list_length(g_t_list) < 1) { LOG_TIMER("The timer has no conditions.\n"); } else { LOG_TIMER("The timer conditions are:\n"); #if defined(DEBUG_TIMER) // Debug print the command list parser_print_list(g_t_list); #endif } // Important: Start the listener only when we need it. It's CPU intensive. // Only "silence", "voice", "audio" and "sound" commands/conditions need data from the listener. need_listener = FALSE; GList *item = g_list_first(g_t_list); while (item) { TimerRec *tr = (TimerRec*)item->data; if (!g_strcmp0(tr->label, "silence") || !g_strcmp0(tr->label, "voice") || !g_strcmp0(tr->label, "sound") || !g_strcmp0(tr->label, "audio")) { need_listener = TRUE; break; } // Next item item = g_list_next(item); } // Unlock G_UNLOCK(g_t_list); } } // Timer is ON? if (!timer_active) { // No. // Make sure the listener has stopped (do not waste CPU cycles) listener_stop_listening(); goto LBL_1; } else { // Yes. Timer is ON. // Do we need data from gst-listener.c? if (!need_listener) { // No. // Make sure the listener has stopped (do not waste CPU cycles) listener_stop_listening(); } else { // Yes. // Start the listener and collect signal/level statistics listener_start_listening(); } } // Execute the TimerRec commands. For all TimerRec structures in GList... GList *item = g_list_first(g_t_list); while (item) { TimerRec *tr = (TimerRec*)item->data; // Set lock G_LOCK(g_t_list); // Check the timer condition timer_func_exec_command(tr); // Unlock G_UNLOCK(g_t_list); // Next item item = g_list_next(item); } LBL_1: // Continue calling this function return TRUE; } void timer_func_exec_command(TimerRec *tr) { // Actions: 'S'=start, 'T'=stop, 'P'=pause, 'p'=pause, 'c'=continue, 0=No action. gchar action = 0; // Test filesize? if (tr->data_type == 'f') { // stop/pause if ### bytes/KB/MB/GB/TB // Example: // stop/pause if 250 MB action = timer_test_filesize(tr); if (action) { LOG_TIMER("Filesize test = TRUE.\n"); } // Test for silence (if average audio signal is under givel level)? } else if (!g_strcmp0(tr->label, "silence")) { // Examples: // stop/pause if silence 8s -26 dB // stop/pause if silence 8 sec 7 % // = start/stop/pause recording if sound level is under x dB in n seconds time. action = timer_test_silence(tr); if (action == 'c') { LOG_TIMER("Silence test. Continue after pause = TRUE.\n"); } else if (action) { LOG_TIMER("Silence test = TRUE.\n"); } // Test for voice/audio/sound? } else if (!g_strcmp0(tr->label, "voice") || !g_strcmp0(tr->label, "sound") || !g_strcmp0(tr->label, "audio")) { // start/stop/pause if voice/audio/sound 8s 7% // start/stop/pause if voice/audio/sound 8s -26dB // = start/stop/pause recording if voice/audio/sound is above x dB in n seconds period. action = timer_test_sound(tr); if (action == 'p') { LOG_TIMER("Sound/Audio/Voice test. Recording paused.\n"); } else if (action) { LOG_TIMER("Sound/Audio/Voice test = TRUE.\n"); } // Test clock time ##:##:##? } else if (tr->data_type == 't') { // start/stop/pause at ##:##:## am/pm (where ##:##:## is clock time in hh:mm:ss format) // Example: // start/stop/pause at 10:15:00 pm action = timer_test_clock_time(tr); if (action) { LOG_TIMER("Clock time test = TRUE.\n"); } // Test time duration? } else if (tr->data_type == 'd') { // start/stop/pause after # hour # min # seconds // Example: // start/stop/pause after 1 h 25 min action = timer_test_time_duration(tr); if (action) { LOG_TIMER("Test for time period/duration = TRUE.\n"); } } switch (action) { case 'S': // Start recording tr->done = TRUE; rec_manager_start_recording(); break; case 'T': // Stop recording tr->done = TRUE; rec_manager_stop_recording(); break; case 'P': // Pause recording tr->done = TRUE; rec_manager_pause_recording(); break; case 'p': // Pause (if recording was on). tr->done = TRUE; rec_manager_pause_recording(); break; case 'c': // Continue (if was paused). Does not restart if recording is off. rec_manager_continue_recording(); break; case 0: // Do nothing break; default: LOG_ERROR("Unknown timer action <%c>.\n", action); } } static gchar timer_test_filesize(TimerRec *tr) { // Test filesize. // stop/pause if/after/on ### bytes/KB/MB/GB/TB // Examples: // stop after 250 MB // pause if 1.2 GB gchar action = 0; // Get output filename gchar *filename = rec_manager_get_output_filename(); if (!filename) { return action; } // Get file size gdouble filesize = 1.0 * get_file_size(filename); LOG_TIMER("Testing filesize: filesize=%3.1f bytes, unit=%s, current filesize=%3.1f bytes, filename=<%s>\n", tr->val[0], tr->label, filesize, filename); g_free(filename); // Filesize limit exceeded? if (filesize >= tr->val[0]) { // Execute action = tr->action; } return action; } static gchar timer_test_silence(TimerRec *tr) { // Test for silence (test if average audio signal is under givel level). // // 1. Test if audio level drops below x dB treshold (in n seconds time) // 2. Stop or pause recording if the condition is true. // 3. When audio level rises over x dB (in 1 seconds time), resume recording. // 4. Goto step 1. // // Examples: // stop/pause if silence 8s -20 dB // stop/pause if silence 8 sec 10 % gchar action = 0; // Length of silence gdouble seconds = tr->val[0]*3600.0 + tr->val[1]*60.0 + tr->val[2]; if (seconds < 1) seconds = DEF_SILENCE_DURATION; // Get average RMS signal value from the listener process gdouble avg_rms_dB = 0; // In decibel (dB), -41dB to 0dB. gdouble avg_rms = 0; // In %, 0 - 100%. 0dB=100%, -41dB=0%. listener_get_average_rms(&avg_rms_dB, &avg_rms); double rms_level = 0; // The tr->level is given as % (0 - 100)? if (!g_strcmp0(tr->level_unit, "%")) { rms_level = tr->level; // % } else if (!g_strcmp0(tr->level_unit, "db")) { // Convert dB to % value rms_level = pow(10, tr->level / 20); // 0 - 1.0 rms_level = rms_level * 100.0; // 0 - 100% } else { // Set default value rms_level = DEF_RMS_LEVEL; } // Get current recording state gint state = -1; gint pending = -1; rec_manager_get_state(&state, &pending); #if defined(DEBUG_TIMER) || defined(DEBUG_ALL) // Name of state (for debugging) const gchar *state_name = rec_manager_get_state_name(state); LOG_TIMER("Silence test, seconds:%ld curr seconds:%ld level:%3.1f%s (%3.1f%%), avg.rms:%3.1fdB (%3.1f%%) rec.state=%s\n", (long)seconds, (long)tr->seconds, tr->level, (tr->level_unit ? tr->level_unit : "dB"), rms_level, avg_rms_dB, avg_rms, state_name); #endif // Step 3: // Test if we have paused the recording. // Eg. "pause if silence 5s 6%" has triggered a pause. // Now we have to continue/resume recording if the signal rises over rms_level (there is sound on the line). // Audio level is over or equal to rms_level? if (avg_rms >= rms_level && state == GST_STATE_PAUSED) { // Count seconds to continue tr->seconds_x += TIMER_CALL_FREQ; // Step 3: if (tr->seconds_x >= 1 /*1 SECONDS HARD-CODED*/) { // Continue recording (small 'c') action = 'c'; tr->seconds = 0; tr->seconds_x = 0; // Reset done flag tr->done = FALSE; } } else { // Reset seconds_x tr->seconds_x = 0; } // Step 1: // Audio level is below rms_level? if (avg_rms < rms_level) { // Count seconds tr->seconds += TIMER_CALL_FREQ; if (tr->seconds >= seconds) { // Step 2: // Execute this command action = tr->action; // Reset silence counter tr->seconds = 0; tr->seconds_x = 0; } } else { // Reset silence counter tr->seconds = 0; tr->seconds_x = 0; } return action; } static gchar timer_test_sound(TimerRec *tr) { // Test for voice/audio/sound. // // 1. Test if audio level rises over x dB treshold (in n seconds time) // 2. Start/stop/pause recording if the condition is true. // 3. When audio level drops under x dB (in 4 seconds time), pause recording. // 4. Goto step 1. // // Examples: // start/stop/pause if voice/audio/sound 8s 10% // start/stop/pause if voice/audio/sound 8s -20dB gchar action = 0; // Length of audio/voice/sound gint64 seconds = (gint64)(tr->val[0]*3600 + tr->val[1]*60 + tr->val[2]); if (seconds < 1) seconds = DEF_SOUND_DURATION; // Get average RMS signal values from the listener process gdouble avg_rms_dB = 0; // In decibel (dB), -50dB to 0dB. gdouble avg_rms = 0; // In %, 0 - 100%. 0dB=100%, -41dB=0%. listener_get_average_rms(&avg_rms_dB, &avg_rms); double rms_level = 0; // The tr->level is given as % (0 - 100)? if (!g_strcmp0(tr->level_unit, "%")) { rms_level = tr->level; // % } else if (!g_strcmp0(tr->level_unit, "db")) { // Convert from dB to % value rms_level = pow(10, tr->level / 20); // 0 - 1.0 rms_level = rms_level * 100.0; // 0 - 100% } else { // Set default value rms_level = DEF_RMS_LEVEL; } // Get current recording state gint state = -1; gint pending = -1; rec_manager_get_state(&state, &pending); #if defined(DEBUG_TIMER) || defined(DEBUG_ALL) // Name of state (for debugging) const gchar *state_name = rec_manager_get_state_name(state); LOG_TIMER("Sound/Audio/Voice test: seconds:%ld curr seconds:%ld(%ld) level:%3.1f%s (%3.1f%%), avg.rms:%3.1fdB (%3.1f%%), rec.state:%s\n", (long)seconds, (long)tr->seconds, (long)tr->seconds_x, tr->level, (tr->level_unit ? tr->level_unit : "dB"), rms_level, avg_rms_dB, avg_rms, state_name); #endif // Step 3: // Test if we have already started recording. // Eg. "start if audio 4%" command has started recording. // Now we have to _pause_ if the signal drops under rms_level value for 5 seconds time. // Audio level is under rms_level? if (avg_rms < rms_level && state == GST_STATE_PLAYING) { // Count seconds to pause tr->seconds_x += TIMER_CALL_FREQ; if (tr->seconds_x >= 4 /* 4 SECONDS HARD-CODED */) { // Pause recording (small 'p') action = 'p'; tr->seconds = 0; tr->seconds_x = 0; } } else { // Reset seconds_x (seconds before we pause) tr->seconds_x = 0; } gint64 test_seconds = 0; if (state == GST_STATE_PAUSED) { // Resume recording after 2 seconds if in paused state (see above). test_seconds = 2/* 2 SECONDS HARD-CODED */; } else { // Seconds defined in the timer. // start if sound 5s <--- test_seconds = seconds; } // Step 1: // Audio level is above rms_level (and state != GST_STATE_PLAYING)? if (avg_rms >= rms_level && state != GST_STATE_PLAYING) { // Count seconds tr->seconds += TIMER_CALL_FREQ; if (tr->seconds >= test_seconds) { // Step 2: // Execute command (normally 'S' for start) action = tr->action; // Reset seconds tr->seconds = 0; tr->seconds_x = 0; } } else { // Reset seconds tr->seconds = 0; } return action; } static gchar timer_test_clock_time(TimerRec *tr) { // Test clock time. // start/stop/pause at ##:##:## am/pm (where ##:##:## is clock time in hh:mm:ss format) // Examples: // start at 10:15:00 pm // pause at 9:30 am // stop at 23:00 gchar action = 0; // Get current, local date & time time_t t = time(NULL); struct tm *tmp; tmp = localtime(&t); gint64 clock_secs = tmp->tm_hour*3600 + tmp->tm_min*60 + tmp->tm_sec; gint64 timer_secs = tr->val[0]*3600 + tr->val[1]*60 + tr->val[2]; LOG_TIMER("Test clock time: current time is %02d:%02d:%02d timer setting is %02.0f:%02.0f:%02.0f (day_of_year:%d/%d) diff in seconds:%ld\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec, tr->val[0], tr->val[1], tr->val[2], tmp->tm_yday, tr->day_of_year, (long)(timer_secs - clock_secs)); // Note: // Do NOT fire if current clock time is 60 minutes or more over the timer value. // Eg. Do not fire if clock time is 14:00:00, and timer setting is:12:10:00 pm. We will assume here that the user means 12:10pm tomorrow, not today. gint64 diff_secs = (clock_secs - timer_secs); gboolean do_action = (diff_secs > 0 && diff_secs < (60 * 60)/*1 HOUR HARD-CODED*/); if (do_action && tr->day_of_year != tmp->tm_yday) { action = tr->action; // Save day_of_year so we know when the clock turns around (to the next day). // This timer command will become valid and fire again. tr->day_of_year = tmp->tm_yday; } return action; } static gchar timer_test_time_duration(TimerRec *tr) { // Test time duration/time period. // start/stop/pause after # hour/h # minuntes/m/min # seconds/s/sec // Examples: // start after 1 h 25 min // stop after 30 minutes // pause after 1 h 15 m 20 s gchar action = 0; // Action is 'T' (sTop) or 'P' (Pause) recording? if (tr->action == 'T' || tr->action == 'P') { // Eg. stop/pause after 8 min 20 sec // Compare recording's stream time to the given timer value. // Get actual recording time in seconds gint64 recording_time_secs = rec_manager_get_stream_time(); // This TimeRec's value in seconds gint64 timer_secs = tr->val[0]*3600.0 + tr->val[1]*60.0 + tr->val[2]; #if defined(DEBUG_TIMER) || defined(DEBUG_ALL) guint hh = -1; guint mm = -1; guint ss = -1; // Split value to hours, minutes and seconds seconds_to_h_m_s(recording_time_secs, &hh, &mm, &ss); gint64 diff = timer_secs - recording_time_secs; LOG_TIMER("Test time period (for sTop and Pause): current rec.time:%02d:%02d:%02d timer setting is:%02.0f:%02.0f:%02.0f diff in seconds:%ld\n", hh, mm, ss, tr->val[0], tr->val[1], tr->val[2], (long)diff); #endif if (recording_time_secs >= timer_secs) { // Execute action = tr->action; } // Action is 'S' (Start) recording? // Eg. start after 1 min 20 sec (execute "start after..." command only once during timer's life time) } else { // Execute only once pr. timer's life time! if (tr->done) goto LBL_1; // Get start time for this timer (when lines were parsed) struct tm start_time = timer_get_start_time(); gint64 start_time_secs = start_time.tm_hour*3600.0 + start_time.tm_min*60.0 + start_time.tm_sec; // Get current, local date & time time_t t = time(NULL); struct tm *tmp; tmp = localtime(&t); gint64 curr_time_secs = tmp->tm_hour*3600.0 + tmp->tm_min*60.0 + tmp->tm_sec; // This TimerRec's value in seconds gint64 timer_secs = tr->val[0]*3600.0 + tr->val[1]*60.0 + tr->val[2]; gint64 diff = timer_secs - (curr_time_secs - start_time_secs); (void) diff; // Avoid unused var message LOG_TIMER("Test time period (for Start): clock time:%02d:%02d:%02d timer thread started:%02d:%02d:%02d setting:%02.0f:%02.0f:%02.0f diff in secs:%ld\n", tmp->tm_hour, tmp->tm_min, tmp->tm_sec, start_time.tm_hour, start_time.tm_min, start_time.tm_sec, tr->val[0], tr->val[1], tr->val[2], (long)diff); if ((curr_time_secs - start_time_secs) >= timer_secs ) { // Execute command action = tr->action; } LBL_1: ; } return action; }