diff mbox series

[v3,2/2] cec-follower: emulate programmed timer recordings

Message ID b940889da47bc1c636993d9c7a93892eb18fa252.1625962440.git.deborahbrouwer3563@gmail.com (mailing list archive)
State New, archived
Headers show
Series cec: Timer Programming | expand

Commit Message

Deborah Brouwer July 11, 2021, 12:37 a.m. UTC
Start and stop recording as timers are scheduled. Schedule future timers
if a completed timer has a recording sequence. Delete overlapped and
unfinished timers. Reduce available media space when a recording is
completed.

Signed-off-by: Deborah Brouwer <deborahbrouwer3563@gmail.com>
---
 utils/cec-follower/cec-follower.cpp   |  5 +++
 utils/cec-follower/cec-follower.h     |  1 +
 utils/cec-follower/cec-processing.cpp | 56 +++++++++++++++++++++++++++
 utils/cec-follower/cec-tuner.cpp      | 26 ++++++++++---
 4 files changed, 83 insertions(+), 5 deletions(-)

Comments

Hans Verkuil July 12, 2021, 7:04 a.m. UTC | #1
On 11/07/2021 02:37, Deborah Brouwer wrote:
> Start and stop recording as timers are scheduled. Schedule future timers
> if a completed timer has a recording sequence. Delete overlapped and
> unfinished timers. Reduce available media space when a recording is
> completed.
> 
> Signed-off-by: Deborah Brouwer <deborahbrouwer3563@gmail.com>
> ---
>  utils/cec-follower/cec-follower.cpp   |  5 +++
>  utils/cec-follower/cec-follower.h     |  1 +
>  utils/cec-follower/cec-processing.cpp | 56 +++++++++++++++++++++++++++
>  utils/cec-follower/cec-tuner.cpp      | 26 ++++++++++---
>  4 files changed, 83 insertions(+), 5 deletions(-)
> 
> diff --git a/utils/cec-follower/cec-follower.cpp b/utils/cec-follower/cec-follower.cpp
> index b273b988..0adf6ce8 100644
> --- a/utils/cec-follower/cec-follower.cpp
> +++ b/utils/cec-follower/cec-follower.cpp
> @@ -301,6 +301,10 @@ void print_timers(struct node *node)
>  {
>  	if (show_info) {
>  		printf("Timers Set:\n");
> +		if (node->state.recording_controlled_by_timer)
> +			printf("Deck is currently recording from the first timer.\n");
> +		if (node->state.one_touch_record_on && !node->state.recording_controlled_by_timer)
> +			printf("Deck is currently recording independent of timers.\n");
>  		for (auto &t : programmed_timers) {
>  			std::string start = ctime(&t.start_time);
>  			time_t end_time = t.start_time + t.duration;
> @@ -373,6 +377,7 @@ void state_init(struct node &node)
>  	node.state.one_touch_record_on = false;
>  	node.state.record_received_standby = false;
>  	node.state.media_space_available = 36000; /* In MB; space for 10 hours @ 1MB/sec */
> +	node.state.recording_controlled_by_timer = false;
>  	tuner_dev_info_init(&node.state);
>  	node.state.last_aud_rate_rx_ts = 0;
>  }
> diff --git a/utils/cec-follower/cec-follower.h b/utils/cec-follower/cec-follower.h
> index 69c96aa7..7b22368b 100644
> --- a/utils/cec-follower/cec-follower.h
> +++ b/utils/cec-follower/cec-follower.h
> @@ -60,6 +60,7 @@ struct state {
>  	bool one_touch_record_on;
>  	bool record_received_standby;
>  	int media_space_available;
> +	bool recording_controlled_by_timer;
>  	time_t toggle_power_status;
>  	__u64 last_aud_rate_rx_ts;
>  };
> diff --git a/utils/cec-follower/cec-processing.cpp b/utils/cec-follower/cec-processing.cpp
> index 32375966..2987cb99 100644
> --- a/utils/cec-follower/cec-processing.cpp
> +++ b/utils/cec-follower/cec-processing.cpp
> @@ -1164,6 +1164,62 @@ void testProcessing(struct node *node, bool wallclock)
>  			node->state.deck_skip_start = 0;
>  			update_deck_state(node, me, CEC_OP_DECK_INFO_PLAY);
>  		}
> +
> +		if (!programmed_timers.empty()) {
> +			std::set<struct Timer>::iterator it = programmed_timers.begin();
> +			/* Use the current minute because timers do not have second precision. */
> +			time_t current_minute = time(nullptr) / 60;
> +			time_t timer_start_minute = it->start_time / 60;
> +			time_t timer_end_minute = (it->start_time + it->duration) / 60;
> +
> +			/* Start the timed recording only if the deck is not already recording. */
> +			if (timer_start_minute == current_minute && !node->state.one_touch_record_on) {
> +				node->state.one_touch_record_on = true;
> +				node->state.recording_controlled_by_timer = true;
> +				print_timers(node);
> +			}
> +
> +			/* Delete an overlapped timer. Recording will be at best incomplete. */
> +			if (timer_start_minute < current_minute &&
> +			    (!node->state.recording_controlled_by_timer || !node->state.one_touch_record_on)) {
> +				programmed_timers.erase(*it);
> +				if (show_info)
> +					printf("Deleted overlapped timer.\n");
> +				print_timers(node);
> +			}
> +
> +			/* Delete finished timers. */
> +			if (timer_end_minute == current_minute && node->state.recording_controlled_by_timer) {
> +				node->state.one_touch_record_on = false;
> +				node->state.recording_controlled_by_timer = false;
> +				node->state.media_space_available -= it->duration; /* 1 MB per second */
> +				/*
> +				 * TODO: We are only ever decreasing the amount of space available,
> +				 * there is no heuristic that reclaims the space.
> +				 */
> +
> +				if (it->recording_seq) {
> +					struct tm last_start_time = *(localtime(&(it->start_time)));
> +					int next_wday = (last_start_time.tm_wday + 1) % 7;
> +					int days_to_move_ahead = 1;
> +
> +					while ((it->recording_seq & (1 << next_wday)) == 0) {
> +						days_to_move_ahead++;
> +						next_wday = (next_wday + 1) % 7;
> +					}
> +					struct Timer next_timer = {};
> +					next_timer = *it;
> +					last_start_time.tm_mday += days_to_move_ahead;
> +					last_start_time.tm_isdst = -1;
> +					next_timer.start_time = mktime(&last_start_time);
> +					programmed_timers.insert(next_timer);
> +				}
> +				programmed_timers.erase(*it);
> +				if (show_info)
> +					printf("Deleted finished timer.\n");
> +				print_timers(node);
> +			}
> +		}
>  	}
>  	mode = CEC_MODE_INITIATOR;
>  	doioctl(node, CEC_S_MODE, &mode);
> diff --git a/utils/cec-follower/cec-tuner.cpp b/utils/cec-follower/cec-tuner.cpp
> index 544cb662..37e2de24 100644
> --- a/utils/cec-follower/cec-tuner.cpp
> +++ b/utils/cec-follower/cec-tuner.cpp
> @@ -864,6 +864,16 @@ void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, uns
>  		cec_msg_record_status(&msg, CEC_OP_RECORD_STATUS_TERMINATED_OK);
>  		transmit(node, &msg);
>  		node->state.one_touch_record_on = false;
> +
> +		/* Delete any currently active recording timer or it may restart itself in first minute. */
> +		if (node->state.recording_controlled_by_timer) {
> +			node->state.recording_controlled_by_timer = false;
> +			std::set<struct Timer>::iterator it = programmed_timers.begin();
> +			programmed_timers.erase(*it);

I think you can just do:

			programmed_timers.erase(programmed_timers.begin());

> +			if (show_info)
> +				printf("Deleted manually stopped timer.\n");
> +			print_timers(node);
> +		}
>  		/*
>  		 * If standby was received during recording, enter standby when the
>  		 * recording is finished unless recording device is the active source.
> @@ -946,12 +956,18 @@ void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, uns
>  			break;
>  
>  		__u8 timer_cleared_status = CEC_OP_TIMER_CLR_STAT_NO_MATCHING;
> -		struct Timer timer = get_timer_from_message(msg);
>  
> -		if (programmed_timers.find(timer) != programmed_timers.end()) {
> -			timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
> -			programmed_timers.erase(timer);
> -			print_timers(node);
> +		if (node->state.one_touch_record_on && node->state.recording_controlled_by_timer) {
> +			timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING;
> +			/* TODO: Allow other timers to be cleared while one timer is recording. */

I don't understand this TODO.

Can't you just do:

		auto it = programmed_timers.find(timer);
		if (it != programmed_timers.end()) {
			if (node->state.one_touch_record_on && node->state.recording_controlled_by_timer &&
			    it == programmed_timers.begin()) {
				timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING;
				// Also stop the recording at this time, updating the state accordingly.
			} else {
				timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
			}
			programmed_timers.erase(timer);
			print_timers(node);
		}

> +		} else {
> +			struct Timer timer = get_timer_from_message(msg);
> +
> +			if (programmed_timers.find(timer) != programmed_timers.end()) {
> +				timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
> +				programmed_timers.erase(timer);
> +				print_timers(node);
> +			}
>  		}
>  		cec_msg_set_reply_to(&msg, &msg);
>  		cec_msg_timer_cleared_status(&msg, timer_cleared_status);
>
diff mbox series

Patch

diff --git a/utils/cec-follower/cec-follower.cpp b/utils/cec-follower/cec-follower.cpp
index b273b988..0adf6ce8 100644
--- a/utils/cec-follower/cec-follower.cpp
+++ b/utils/cec-follower/cec-follower.cpp
@@ -301,6 +301,10 @@  void print_timers(struct node *node)
 {
 	if (show_info) {
 		printf("Timers Set:\n");
+		if (node->state.recording_controlled_by_timer)
+			printf("Deck is currently recording from the first timer.\n");
+		if (node->state.one_touch_record_on && !node->state.recording_controlled_by_timer)
+			printf("Deck is currently recording independent of timers.\n");
 		for (auto &t : programmed_timers) {
 			std::string start = ctime(&t.start_time);
 			time_t end_time = t.start_time + t.duration;
@@ -373,6 +377,7 @@  void state_init(struct node &node)
 	node.state.one_touch_record_on = false;
 	node.state.record_received_standby = false;
 	node.state.media_space_available = 36000; /* In MB; space for 10 hours @ 1MB/sec */
+	node.state.recording_controlled_by_timer = false;
 	tuner_dev_info_init(&node.state);
 	node.state.last_aud_rate_rx_ts = 0;
 }
diff --git a/utils/cec-follower/cec-follower.h b/utils/cec-follower/cec-follower.h
index 69c96aa7..7b22368b 100644
--- a/utils/cec-follower/cec-follower.h
+++ b/utils/cec-follower/cec-follower.h
@@ -60,6 +60,7 @@  struct state {
 	bool one_touch_record_on;
 	bool record_received_standby;
 	int media_space_available;
+	bool recording_controlled_by_timer;
 	time_t toggle_power_status;
 	__u64 last_aud_rate_rx_ts;
 };
diff --git a/utils/cec-follower/cec-processing.cpp b/utils/cec-follower/cec-processing.cpp
index 32375966..2987cb99 100644
--- a/utils/cec-follower/cec-processing.cpp
+++ b/utils/cec-follower/cec-processing.cpp
@@ -1164,6 +1164,62 @@  void testProcessing(struct node *node, bool wallclock)
 			node->state.deck_skip_start = 0;
 			update_deck_state(node, me, CEC_OP_DECK_INFO_PLAY);
 		}
+
+		if (!programmed_timers.empty()) {
+			std::set<struct Timer>::iterator it = programmed_timers.begin();
+			/* Use the current minute because timers do not have second precision. */
+			time_t current_minute = time(nullptr) / 60;
+			time_t timer_start_minute = it->start_time / 60;
+			time_t timer_end_minute = (it->start_time + it->duration) / 60;
+
+			/* Start the timed recording only if the deck is not already recording. */
+			if (timer_start_minute == current_minute && !node->state.one_touch_record_on) {
+				node->state.one_touch_record_on = true;
+				node->state.recording_controlled_by_timer = true;
+				print_timers(node);
+			}
+
+			/* Delete an overlapped timer. Recording will be at best incomplete. */
+			if (timer_start_minute < current_minute &&
+			    (!node->state.recording_controlled_by_timer || !node->state.one_touch_record_on)) {
+				programmed_timers.erase(*it);
+				if (show_info)
+					printf("Deleted overlapped timer.\n");
+				print_timers(node);
+			}
+
+			/* Delete finished timers. */
+			if (timer_end_minute == current_minute && node->state.recording_controlled_by_timer) {
+				node->state.one_touch_record_on = false;
+				node->state.recording_controlled_by_timer = false;
+				node->state.media_space_available -= it->duration; /* 1 MB per second */
+				/*
+				 * TODO: We are only ever decreasing the amount of space available,
+				 * there is no heuristic that reclaims the space.
+				 */
+
+				if (it->recording_seq) {
+					struct tm last_start_time = *(localtime(&(it->start_time)));
+					int next_wday = (last_start_time.tm_wday + 1) % 7;
+					int days_to_move_ahead = 1;
+
+					while ((it->recording_seq & (1 << next_wday)) == 0) {
+						days_to_move_ahead++;
+						next_wday = (next_wday + 1) % 7;
+					}
+					struct Timer next_timer = {};
+					next_timer = *it;
+					last_start_time.tm_mday += days_to_move_ahead;
+					last_start_time.tm_isdst = -1;
+					next_timer.start_time = mktime(&last_start_time);
+					programmed_timers.insert(next_timer);
+				}
+				programmed_timers.erase(*it);
+				if (show_info)
+					printf("Deleted finished timer.\n");
+				print_timers(node);
+			}
+		}
 	}
 	mode = CEC_MODE_INITIATOR;
 	doioctl(node, CEC_S_MODE, &mode);
diff --git a/utils/cec-follower/cec-tuner.cpp b/utils/cec-follower/cec-tuner.cpp
index 544cb662..37e2de24 100644
--- a/utils/cec-follower/cec-tuner.cpp
+++ b/utils/cec-follower/cec-tuner.cpp
@@ -864,6 +864,16 @@  void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, uns
 		cec_msg_record_status(&msg, CEC_OP_RECORD_STATUS_TERMINATED_OK);
 		transmit(node, &msg);
 		node->state.one_touch_record_on = false;
+
+		/* Delete any currently active recording timer or it may restart itself in first minute. */
+		if (node->state.recording_controlled_by_timer) {
+			node->state.recording_controlled_by_timer = false;
+			std::set<struct Timer>::iterator it = programmed_timers.begin();
+			programmed_timers.erase(*it);
+			if (show_info)
+				printf("Deleted manually stopped timer.\n");
+			print_timers(node);
+		}
 		/*
 		 * If standby was received during recording, enter standby when the
 		 * recording is finished unless recording device is the active source.
@@ -946,12 +956,18 @@  void process_tuner_record_timer_msgs(struct node *node, struct cec_msg &msg, uns
 			break;
 
 		__u8 timer_cleared_status = CEC_OP_TIMER_CLR_STAT_NO_MATCHING;
-		struct Timer timer = get_timer_from_message(msg);
 
-		if (programmed_timers.find(timer) != programmed_timers.end()) {
-			timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
-			programmed_timers.erase(timer);
-			print_timers(node);
+		if (node->state.one_touch_record_on && node->state.recording_controlled_by_timer) {
+			timer_cleared_status = CEC_OP_TIMER_CLR_STAT_RECORDING;
+			/* TODO: Allow other timers to be cleared while one timer is recording. */
+		} else {
+			struct Timer timer = get_timer_from_message(msg);
+
+			if (programmed_timers.find(timer) != programmed_timers.end()) {
+				timer_cleared_status = CEC_OP_TIMER_CLR_STAT_CLEARED;
+				programmed_timers.erase(timer);
+				print_timers(node);
+			}
 		}
 		cec_msg_set_reply_to(&msg, &msg);
 		cec_msg_timer_cleared_status(&msg, timer_cleared_status);