4863110 [rkeene@sledge /home/rkeene/tmp]$ cat -n cec-volume.cc
  1 #include <libcec/cec.h>
  2 #include <iostream>
  3 #include <thread>
  4 #include <stdio.h>
  5 #include <unistd.h>
  6 #include <assert.h>
  7 
  8 /*
  9  * Constants
 10  */
 11 const unsigned int receiver_steps = 36;
 12 const unsigned int default_volume = 20;
 13 const useconds_t post_send_delay = 200000; 
 14 const double step_size = ((double) (CEC::CEC_AUDIO_VOLUME_MAX - CEC::CEC_AUDIO_VOLUME_MIN)) / ((double) receiver_steps);
 15 
 16 /*
 17  * Global state
 18  */
 19 double current_volume = 0;
 20 bool current_mute = false;
 21 CEC::ICECAdapter *adapter = 0;
 22 
 23 /*
 24  * Log object
 25  */
 26 class logger {
 27 public:
 28     ~logger() { std::cerr << std::endl; }
 29     template <class T>
 30     logger &operator<<(T x) {
 31         std::cerr << x;
 32         return(*this);
 33     }
 34 };
 35 
 36 #define logstream(x...) { logger logstreaminstance; logstreaminstance x; }
 37 
 38 /*
 39  * CEC Helpers
 40  */
 41 bool cec_execute_command(CEC::ICECAdapter *adapter, char *command) {
 42     auto command_data = adapter->CommandFromString(command);
 43     auto tx = adapter->Transmit(command_data);
 44     if (!tx) {
 45         std::cerr << "Failed to execute command: " << command << std::endl;
 46         return(false);
 47     }
 48 
 49     return(true);
 50 }
 51 
 52 bool cec_enable_audio(CEC::ICECAdapter *adapter) {
 53     return(cec_execute_command(adapter, (char *) "50:72:01"));
 54 }
 55 
 56 bool cec_set_volume(CEC::ICECAdapter *adapter, int volume, bool muted = false) {
 57     char command[64];
 58     if (muted) {
 59         volume |= CEC::CEC_AUDIO_MUTE_STATUS_MASK;
 60     }
 61 
 62     snprintf(command, sizeof(command), "50:7A:%02x", volume);
 63 
 64     return(cec_execute_command(adapter, command));
 65 }
 66 
 67 void cec_log_message(void *cb_data, const CEC::cec_log_message *message) {
 68     // std::cerr << message->message << std::endl;
 69 }
 70 
 71 /*
 72  * IR Helpers
 73  */
 74 void ir_increase_volume() {
 75     logstream(<< "Increasing volume (from " << current_volume << ")");
 76 
 77     system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-up.ir");
 78     usleep(post_send_delay);
 79     return;
 80 }
 81 
 82 void ir_decrease_volume() {
 83     logstream(<< "Decreasing volume (from " << current_volume << ")");
 84 
 85     system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-down.ir");
 86     usleep(post_send_delay);
 87     return;
 88 }
 89 
 90 void ir_toggle_mute() {
 91     logstream(<< "Toggling mute (from " << current_mute << ")");
 92 
 93     system("ir-ctl -d /dev/lirc0 --send ~/cec-volume/mcm-vol-mute.ir");
 94     usleep(post_send_delay);
 95     return;
 96 }
 97 
 98 void ir_set_volume(unsigned int value) {
 99     bool set_mute = current_mute;
100 
101     logstream(<< "Setting volume to " << value << " with mute = " << set_mute);
102 
103     if (current_mute) {
104         ir_toggle_mute();
105         current_mute = !current_mute;
106     }
107 
108     current_volume = CEC::CEC_AUDIO_VOLUME_MAX;
109 
110     for (unsigned int i = 0; i < receiver_steps; i++) {
111         ir_decrease_volume();
112         current_volume -= step_size;
113     }
114 
115     assert(current_volume < (CEC::CEC_AUDIO_VOLUME_MIN + step_size + 0.1));
116 
117     auto steps_value = value / step_size;
118     for (unsigned int i = 0; i < steps_value; i++) {
119         ir_increase_volume();
120         current_volume += step_size;
121     }
122 
123     if (set_mute) {
124         ir_toggle_mute();
125         current_mute = !current_mute;
126     }
127 
128     logstream(<< "Done setting volume to " << value << "(current_volume =" << current_volume << ")");
129 }
130 
131 /*
132  * CEC Callback for a command from the bus to our process
133  */
134 void cec_handle_command(void *cb_data, const CEC::cec_command *command) {
135     if (command->opcode == CEC::CEC_OPCODE_GIVE_AUDIO_STATUS) {
136         ((CEC::cec_command *) command)->Clear();
137         logstream(<< "Got command CEC_OPCODE_GIVE_AUDIO_STATUS");
138         cec_set_volume(adapter, (int) current_volume, current_mute);
139     }
140 }
141 
142 /*
143  * CEC Callback for handling a key
144  */
145 std::thread *change_thread = 0;
146 void cec_handle_key_thread(CEC::cec_user_control_code keycode) {
147     switch (keycode) {
148         case CEC::CEC_USER_CONTROL_CODE_VOLUME_DOWN:
149             if (current_mute) {
150                 ir_toggle_mute();
151                 current_mute = !current_mute;
152             }
153             ir_decrease_volume();
154             current_volume -= step_size;
155             break;
156         case CEC::CEC_USER_CONTROL_CODE_VOLUME_UP:
157             if (current_mute) {
158                 ir_toggle_mute();
159                 current_mute = !current_mute;
160             }
161             ir_increase_volume();
162             current_volume += step_size;
163             break;
164         case CEC::CEC_USER_CONTROL_CODE_MUTE:
165             ir_toggle_mute();
166             current_mute = !current_mute;
167             break;
168         default:
169             /* Ignored */
170             break;
171     }
172 
173     if (current_volume < CEC::CEC_AUDIO_VOLUME_MIN) {
174         current_volume = CEC::CEC_AUDIO_VOLUME_MIN;
175     }
176 
177     if (current_volume > CEC::CEC_AUDIO_VOLUME_MAX) {
178         current_volume = CEC::CEC_AUDIO_VOLUME_MAX;
179     }
180 
181     if (adapter) {
182         auto tx = cec_set_volume(adapter, (int) current_volume, current_mute);
183         if (!tx) {
184             std::cerr << "Failed to set volume" << std::endl;
185         }
186     }
187 
188     /* Really should be a mutex around this */
189     change_thread = 0;
190 }
191 
192 void cec_handle_key(void *cb_data, const CEC::cec_keypress *key_info) {
193     /*
194      * Key down events are sent with Duration=0, ignore them
195      */
196     if (key_info->duration == 0) {
197         return;
198     }
199 
200     /*
201      * If an event is already being processed, ignore this one
202      */
203     if (change_thread) {
204         return;
205     }
206 
207     logstream(<< "Key " << key_info->keycode << "; Duration=" << key_info->duration);
208     change_thread = new std::thread(cec_handle_key_thread, key_info->keycode);
209 }
210 
211 int main(int argc, char *argv[]) {
212     CEC::libcec_configuration configuration;
213     CEC::ICECCallbacks callbacks;
214 
215     configuration.Clear();
216     configuration.deviceTypes.Add(CEC::CEC_DEVICE_TYPE_AUDIO_SYSTEM);
217     configuration.clientVersion = CEC::LIBCEC_VERSION_CURRENT;
218     configuration.bActivateSource = 0;
219     sprintf(configuration.strDeviceName, "SchiitModi3");
220 
221     adapter = CECInitialise(&configuration);
222 
223     /*
224      * What if I wasn't using the RPI port ?
225      */
226     adapter->InitVideoStandalone();
227 
228     callbacks.Clear();
229     callbacks.logMessage = &cec_log_message;
230     callbacks.keyPress = &cec_handle_key;
231     callbacks.commandReceived = &cec_handle_command;
232 
233 #if CEC_LIB_VERSION_MAJOR >= 5
234     auto set_callbacks_ret = adapter->SetCallbacks(&callbacks);
235 #else
236     auto set_callbacks_ret = adapter->EnableCallbacks(0, &callbacks);
237 #endif
238 
239     if (!set_callbacks_ret) {
240         std::cerr << "Failed to set callbacks" << std::endl;
241         return(4);
242     }
243 
244     auto open_ret = adapter->Open("RPI");
245     if (!open_ret) {
246         std::cerr << "Failed to open port" << std::endl;
247         return(1);
248     }
249 
250     auto audio_ret = cec_enable_audio(adapter);
251     if (!audio_ret) {
252         std::cerr << "Failed to enable audio" << std::endl;
253         return(2);
254     }
255 
256     ir_set_volume(default_volume);
257 
258     /*
259      * Another thread handles sending callbacks, so we just need to wait forever.
260      */
261     while (true) {
262         sleep(300);
263     }
264 }
4863111 [rkeene@sledge /home/rkeene/tmp]$

Click here to go back to the directory listing.
Click here to download this file.
last modified: 2020-12-19 18:48:56