]> git.ipfire.org Git - thirdparty/shairport-sync.git/commitdiff
Add an activity monitor -- can set an activity idle timeout.
authorMike Brady <mikebrady@eircom.net>
Sun, 20 Jan 2019 22:15:02 +0000 (22:15 +0000)
committerMike Brady <mikebrady@eircom.net>
Sun, 20 Jan 2019 22:15:02 +0000 (22:15 +0000)
Makefile.am
activity_monitor.c [new file with mode: 0644]
activity_monitor.h [new file with mode: 0644]
common.h
player.c
shairport.c

index 7106f35a3489cbdb49a92243c12b135ea9300d0c..c384807ccc97c6541fe9411f4b71a2529b293f8d 100644 (file)
@@ -8,7 +8,7 @@ bin_PROGRAMS = shairport-sync
 
 # See below for the flags for the test client program
 
-shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c
+shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c loudness.c activity_monitor.c
 
 if BUILD_FOR_FREEBSD
   AM_CXXFLAGS = -I/usr/local/include -Wno-multichar -Wall -Wextra -pthread -DSYSCONFDIR=\"$(sysconfdir)\"
diff --git a/activity_monitor.c b/activity_monitor.c
new file mode 100644 (file)
index 0000000..3393e95
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Activity Monitor
+ * 
+ * Contains code to run an activity flag and associated timer
+ * A pthread implements a simple state machine with three states, 
+ * "idle", "active" and "timing out".
+ * 
+ * 
+ * This file is part of Shairport Sync.
+ * Copyright (c) Mike Brady 2019
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "config.h"
+
+#include "common.h"
+#include "activity_monitor.h"
+
+enum am_state {am_inactive, am_active, am_timing_out} state;
+enum ps_state {ps_inactive, ps_active} player_state;
+
+pthread_t activity_monitor_thread;
+pthread_mutex_t activity_monitor_mutex;
+pthread_cond_t activity_monitor_cv;
+
+void activity_monitor_signify_activity(int active) {
+  pthread_mutex_lock(&activity_monitor_mutex);
+  player_state = active == 0 ? ps_inactive : ps_active;
+  pthread_cond_signal(&activity_monitor_cv);
+  pthread_mutex_unlock(&activity_monitor_mutex);
+}
+
+void going_active() {
+  pthread_mutex_unlock(&activity_monitor_mutex);  
+  debug(1, "activity_monitor: state transitioning to \"active\"");
+  pthread_mutex_lock(&activity_monitor_mutex);
+}
+
+void going_inactive() {
+  pthread_mutex_unlock(&activity_monitor_mutex);  
+  debug(1, "activity_monitor: state transitioning to \"inactive\"");
+  pthread_mutex_lock(&activity_monitor_mutex);
+}
+
+void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
+  debug(3, "activity_monitor: thread exit.");
+  pthread_cond_destroy(&activity_monitor_cv);
+  pthread_mutex_destroy(&activity_monitor_mutex);
+}
+
+void *activity_monitor_thread_code(void *arg) {
+  int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
+  if (rc)
+    die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);
+
+// set the flowcontrol condition variable to wait on a monotonic clock
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+  pthread_condattr_t attr;
+  pthread_condattr_init(&attr);
+  pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
+  rc = pthread_cond_init(&activity_monitor_cv, &attr);
+  pthread_condattr_destroy(&attr);
+  
+#endif
+#ifdef COMPILE_FOR_OSX
+  rc = pthread_cond_init(&activity_monitor_cv, NULL);
+#endif
+  if (rc)
+    die("activity_monitor: error %d initialising activity_monitor_cv.");
+  pthread_cleanup_push(activity_thread_cleanup_handler, arg);
+  
+  uint64_t sec;
+  uint64_t nsec;
+  int rc;
+  struct timespec time_for_wait;
+  
+  state = am_inactive;
+  player_state = ps_inactive;
+  
+  pthread_mutex_lock(&activity_monitor_mutex);
+  do {
+    switch (state) {
+      case am_inactive:
+       debug(1,"am_state: am_inactive");
+       while (player_state != ps_active)
+          pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);               
+        state = am_active;
+        going_active();                
+      break;
+      case am_active:
+       debug(1,"am_state: am_active");
+        while (player_state != ps_inactive)
+          pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
+        if (config.active_timeout == 0.0) {
+          state = am_inactive;
+          going_inactive();
+        } else {
+          state = am_timing_out;
+
+          uint64_t time_to_wait_for_wakeup_fp = (uint64_t)(config.active_timeout *1000000); // resolution of microseconds
+          time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp << 32;
+          time_to_wait_for_wakeup_fp = time_to_wait_for_wakeup_fp / 1000000;
+          
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+          uint64_t time_of_wakeup_fp = get_absolute_time_in_fp() + time_to_wait_for_wakeup_fp;
+          sec = time_of_wakeup_fp >> 32;
+          nsec = ((time_of_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+          time_for_wait.tv_sec = sec;
+          time_for_wait.tv_nsec = nsec;
+#endif
+#ifdef COMPILE_FOR_OSX
+          sec = time_to_wait_for_wakeup_fp >> 32;
+          nsec = ((time_to_wait_for_wakeup_fp & 0xffffffff) * 1000000000) >> 32;
+          time_for_wait.tv_sec = sec;
+          time_for_wait.tv_nsec = nsec;
+#endif
+        }      
+      break;
+      case am_timing_out:
+       debug(1,"am_state: am_timing_out");
+        rc = 0;
+        while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
+#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
+          rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait); // this is a pthread cancellation point
+#endif
+#ifdef COMPILE_FOR_OSX
+          rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, &time_for_wait);         
+#endif
+        }
+        if (player_state == ps_active)
+          state = am_active; // player has gone active -- do nothing, because it's still active
+        else if (rc == ETIMEDOUT) {
+            state = am_inactive;
+            going_inactive();        
+        }  else {
+            // activity monitor was woken up in the state am_timing_out, but not by a timeout and player is not in ps_active state
+            debug(1, "activity monitor was woken up in the state am_timing_out, but didn't change state");
+        }        
+      break;
+      default:
+        debug(1,"activity monitor in an illegal state!");
+        state = am_inactive;
+      break;
+    }
+  } while (1);
+  pthread_mutex_unlock(&activity_monitor_mutex);  
+  pthread_cleanup_pop(0); // should never happen
+  pthread_exit(NULL);
+}
+
+void activity_monitor_start() {
+  debug(1,"activity_monitor_start");
+  config.active_timeout = 5.5; // hack
+  pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
+}
+
+void activity_monitor_stop() {
+  debug(1,"activity_monitor_stop start...");
+  pthread_cancel(activity_monitor_thread);
+  pthread_join(activity_monitor_thread,NULL);
+  debug(1,"activity_monitor_stop complete");
+}
+
diff --git a/activity_monitor.h b/activity_monitor.h
new file mode 100644 (file)
index 0000000..9de3345
--- /dev/null
@@ -0,0 +1,4 @@
+#pragma once
+void activity_monitor_start();
+void activity_monitor_stop();
+void activity_monitor_signify_activity(int active); // 0 means inactive, non-zero means active
index c6d5ae3a2b8b474c79ef5a263c2a3db504198ca9..505c426b442fdc3e49a32833839135947b316137 100644 (file)
--- a/common.h
+++ b/common.h
@@ -177,6 +177,7 @@ typedef struct {
   double audio_backend_latency_offset; // this will be the offset in seconds to compensate for any
                                        // fixed latency there might be in the audio path
   double audio_backend_silent_lead_in_time; // the length of the silence that should precede a play.
+  double active_timeout; // the amount of time from when play ends to when the system does into the "inactive".
   uint32_t volume_range_db; // the range, in dB, from max dB to min dB. Zero means use the mixer's
                             // native range.
   enum sps_format_t output_format;
index ba40bf5be9e5743230dc751c70014e37c3a32ffe..6548b381458fea6c43cbf353054481cef59a4994 100644 (file)
--- a/player.c
+++ b/player.c
@@ -89,6 +89,8 @@
 
 #include "loudness.h"
 
+#include "activity_monitor.h"
+
 // default buffer size
 // needs to be a power of 2 because of the way BUFIDX(seqno) works
 //#define BUFFER_FRAMES 512
@@ -1486,9 +1488,11 @@ void player_thread_cleanup_handler(void *arg) {
   clear_reference_timestamp(conn);
   conn->rtp_running = 0;
   pthread_setcancelstate(oldState, NULL);
+  activity_monitor_signify_activity(0); // inactive
 }
 
 void *player_thread_func(void *arg) {
+    activity_monitor_signify_activity(1); // active
   rtsp_conn_info *conn = (rtsp_conn_info *)arg;
   // pthread_cleanup_push(player_thread_initial_cleanup_handler, arg);
   conn->packet_count = 0;
index a02d541e388f9bd2dfeacf67f8b7b14263772796..382536ec8653611d900bfdf0dfe3d90a86e49187 100644 (file)
@@ -63,6 +63,7 @@
 #include "mdns.h"
 #include "rtp.h"
 #include "rtsp.h"
+#include "activity_monitor.h"
 
 #if defined(CONFIG_DACP_CLIENT)
 #include "dacp.h"
@@ -1160,6 +1161,9 @@ void main_cleanup_handler(__attribute__((unused)) void *arg) {
 #ifdef CONFIG_METADATA
   metadata_stop(); // close down the metadata pipe
 #endif
+
+  activity_monitor_stop(0);
+  
   if (config.output->deinit) {
     debug(1, "Deinitialise the audio backend.");
     config.output->deinit();
@@ -1633,6 +1637,8 @@ int main(int argc, char **argv) {
   }
 #endif
 
+  activity_monitor_start();
+
   // daemon_log(LOG_INFO, "Successful Startup");
   rtsp_listen_loop();