Last active
March 31, 2019 00:58
-
-
Save jackoalan/944a10950791e04487620e8c826c4ffb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef HAVE_CONFIG_H | |
#include <config.h> | |
#endif | |
#include <pulse/xmalloc.h> | |
#include <pulsecore/namereg.h> | |
#include <pulsecore/sink.h> | |
#include <pulsecore/module.h> | |
#include <pulsecore/core-util.h> | |
#include <libcec/cecc.h> | |
#define CEC_LOGGING 0 | |
PA_MODULE_AUTHOR("Jack Andersen"); | |
PA_MODULE_DESCRIPTION(_("HDMI CEC Volume")); | |
PA_MODULE_VERSION(PACKAGE_VERSION); | |
PA_MODULE_LOAD_ONCE(true); | |
static libcec_configuration g_config; | |
struct userdata { | |
pa_module *module; | |
pa_sink *sink; | |
pa_hook_slot *volume_changed_slot; | |
pa_hook_slot *sink_put_slot; | |
pa_defer_event *recover_defer; | |
libcec_connection_t cec; | |
int target_vol; | |
int key_pressed; | |
#if CEC_LOGGING | |
FILE* log_file; | |
#endif | |
}; | |
#if CEC_LOGGING | |
static void cb_cec_log_message(void* usr_data, const cec_log_message* message) { | |
struct userdata* u = usr_data; | |
const char* strLevel; | |
switch (message->level) | |
{ | |
case CEC_LOG_ERROR: | |
strLevel = "ERROR: "; | |
break; | |
case CEC_LOG_WARNING: | |
strLevel = "WARNING: "; | |
break; | |
case CEC_LOG_NOTICE: | |
strLevel = "NOTICE: "; | |
break; | |
case CEC_LOG_TRAFFIC: | |
strLevel = "TRAFFIC: "; | |
break; | |
case CEC_LOG_DEBUG: | |
strLevel = "DEBUG: "; | |
break; | |
default: | |
break; | |
} | |
fprintf(u->log_file, "%s[%" PRId64 "]\t%s\n", strLevel, message->time, message->message); | |
fflush(u->log_file); | |
} | |
#endif | |
static void give_audio_status(struct userdata* u) { | |
cec_command give_status = {}; | |
give_status.initiator = CECDEVICE_RECORDINGDEVICE1; | |
give_status.destination = CECDEVICE_AUDIOSYSTEM; | |
give_status.opcode = CEC_OPCODE_GIVE_AUDIO_STATUS; | |
give_status.opcode_set = 1; | |
give_status.transmit_timeout = CEC_DEFAULT_TRANSMIT_TIMEOUT; | |
libcec_transmit(u->cec, &give_status); | |
} | |
static void cb_cec_command_received(void* usr_data, const cec_command* command) { | |
struct userdata* u = usr_data; | |
if (command->opcode == CEC_OPCODE_REPORT_AUDIO_STATUS) { | |
/* Volume reply from receiver. */ | |
int cur_vol = command->parameters.data[0]; | |
/* Press volume key in a closed-loop fashion. */ | |
if (cur_vol > u->target_vol) { | |
libcec_send_keypress(u->cec, CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_DOWN, 1); | |
u->key_pressed = 1; | |
} else if (cur_vol < u->target_vol) { | |
libcec_send_keypress(u->cec, CECDEVICE_AUDIOSYSTEM, CEC_USER_CONTROL_CODE_VOLUME_UP, 1); | |
u->key_pressed = 1; | |
} | |
/* Release volume key if close to avoid severe overshoot */ | |
if (u->key_pressed && abs(cur_vol - u->target_vol) < 8) { | |
libcec_send_key_release(u->cec, CECDEVICE_AUDIOSYSTEM, 1); | |
u->key_pressed = 0; | |
} | |
/* Request new volume (next loop iteration). */ | |
give_audio_status(u); | |
} else if (command->opcode == CEC_OPCODE_SET_SYSTEM_AUDIO_MODE) { | |
/* Receiver turned on; initiate request loop. */ | |
if (command->parameters.data[0]) | |
give_audio_status(u); | |
} | |
} | |
static void cb_cec_alert(void* usr_data, const libcec_alert alert, const libcec_parameter param) { | |
struct userdata* u = usr_data; | |
/* This generally happens after waking from sleep. Defer libcec reset. */ | |
if (alert == CEC_ALERT_CONNECTION_LOST) | |
u->module->core->mainloop->defer_enable(u->recover_defer, 1); | |
} | |
static ICECCallbacks g_callbacks = { | |
#if CEC_LOGGING | |
.logMessage = cb_cec_log_message, | |
#endif | |
.commandReceived = cb_cec_command_received, | |
.alert = cb_cec_alert, | |
}; | |
static pa_hook_result_t volume_changed_cb(pa_core *c, pa_sink *sink, struct userdata* u) { | |
if (u->sink != sink) | |
return PA_HOOK_OK; | |
u->target_vol = pa_cvolume_avg(&sink->reference_volume) / (double)PA_VOLUME_NORM * 100; | |
return PA_HOOK_OK; | |
} | |
static void pa_sink_set_volume_cb(pa_sink *sink) { | |
/* Empty callback to use "hardware" volume. | |
* | |
* We use the PA_CORE_HOOK_SINK_VOLUME_CHANGED hook to actually handle | |
* the volume change since the module userdata is not available this way. */ | |
} | |
static pa_hook_result_t sink_put_cb(pa_core *c, pa_sink *sink, struct userdata* u) { | |
if (strstr(sink->name, "hdmi")) { | |
/* Reregister HDMI sink (typically happens with new user session). */ | |
u->sink = sink; | |
u->target_vol = pa_cvolume_avg(&sink->reference_volume) / (double)PA_VOLUME_NORM * 100; | |
pa_sink_set_set_volume_callback(sink, pa_sink_set_volume_cb); | |
pa_cvolume_reset(&sink->soft_volume, sink->sample_spec.channels); | |
} | |
return PA_HOOK_OK; | |
} | |
static void recover_cb(pa_mainloop_api*a, pa_defer_event* e, struct userdata *u) { | |
a->defer_enable(e, 0); | |
/* Completely reset libcec. */ | |
if (u->cec) | |
libcec_destroy(u->cec); | |
u->cec = libcec_initialise(&g_config); | |
if (u->cec) { | |
cec_adapter devices[1]; | |
int8_t iDevicesFound; | |
iDevicesFound = libcec_find_adapters(u->cec, devices, 1, NULL); | |
if (iDevicesFound) { | |
libcec_open(u->cec, devices[0].comm, 5000); | |
/* Turn on receiver. */ | |
libcec_power_on_devices(u->cec, CECDEVICE_AUDIOSYSTEM); | |
/* If receiver is already on, initiate volume request loop. */ | |
give_audio_status(u); | |
} | |
} | |
} | |
int pa__init(pa_module*m) { | |
struct userdata *u; | |
m->userdata = u = pa_xnew(struct userdata, 1); | |
u->module = m; | |
u->sink = NULL; | |
#if CEC_LOGGING | |
u->log_file = fopen("/home/jacko/Desktop/audlog.txt", "w"); | |
#endif | |
u->target_vol = 0; | |
u->key_pressed = 0; | |
if (m->core->sinks) { | |
pa_sink *sink; | |
uint32_t sidx; | |
PA_IDXSET_FOREACH(sink, m->core->sinks, sidx) | |
sink_put_cb(m->core, sink, u); | |
} | |
libcec_clear_configuration(&g_config); | |
g_config.clientVersion = LIBCEC_VERSION_CURRENT; | |
g_config.bActivateSource = 0; | |
g_config.iComboKeyTimeoutMs = 0; | |
g_config.iButtonReleaseDelayMs = 0; | |
g_config.callbacks = &g_callbacks; | |
g_config.callbackParam = u; | |
snprintf(g_config.strDeviceName, sizeof(g_config.strDeviceName), "PA CEC"); | |
g_config.deviceTypes.types[0] = CEC_DEVICE_TYPE_RECORDING_DEVICE; | |
u->cec = NULL; | |
u->recover_defer = m->core->mainloop->defer_new(m->core->mainloop, (pa_defer_event_cb_t)recover_cb, u); | |
u->volume_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], PA_HOOK_LATE, | |
(pa_hook_cb_t)volume_changed_cb, u); | |
u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, | |
(pa_hook_cb_t)sink_put_cb, u); | |
return 0; | |
} | |
void pa__done(pa_module*m) { | |
struct userdata *u; | |
pa_assert(m); | |
if (!(u = m->userdata)) | |
return; | |
if (u->cec) { | |
/* Turn off receiver. */ | |
libcec_standby_devices(u->cec, CECDEVICE_AUDIOSYSTEM); | |
libcec_destroy(u->cec); | |
} | |
#if CEC_LOGGING | |
fclose(u->log_file); | |
#endif | |
if (u->volume_changed_slot) | |
pa_hook_slot_free(u->volume_changed_slot); | |
if (u->sink_put_slot) | |
pa_hook_slot_free(u->sink_put_slot); | |
if (u->recover_defer) | |
m->core->mainloop->defer_free(u->recover_defer); | |
pa_xfree(u); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment