diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7393e46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.o diff --git a/current/easypulse_core.h b/current/easypulse_core.h deleted file mode 100644 index 32f0694..0000000 --- a/current/easypulse_core.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * @file core.h - * @brief EasyPulse Library Header. - * - * EasyPulse is a library designed to provide pseudo-object oriented programming - * functions to simplify access to PulseAudio. - */ - -#ifndef EASPYPULSE_CORE_H -#define EASPYPULSE_CORE_H -#define DEBUG_MODE 0 // Debug mode flag - -#include -#include -#include -#include - - -// Forward declarations -typedef struct pulseaudio_manager pulseaudio_manager; -typedef struct pulseaudio_sink pulseaudio_sink; - -/** - * @brief Represents a PulseAudio sink. - */ -struct pulseaudio_sink { - uint32_t index; ///< Index of the sink. - char *name; ///< Name of the sink. - char *description; ///< Description of the sink. - pa_cvolume volume; ///< Volume of the sink. - pa_channel_map channel_map; ///< Channel map of the sink. - int mute; ///< Mute status of the sink (1 for muted, 0 for unmuted). - int number_of_channels; //The number of channels of the sink. -}; - -/** - * @brief Represents the main manager for PulseAudio operations. - */ -struct pulseaudio_manager { - pa_threaded_mainloop *mainloop; ///< Mainloop for PulseAudio operations. - pa_context *context; ///< PulseAudio context. - pulseaudio_sink *sinks; ///< Array of available sinks. - uint32_t sink_count; ///< Count of available sinks. - int pa_ready; ///< Indicates if PulseAudio is ready (1 for ready, 2 for error). - int sinks_loaded; ///< Indicates if sinks are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). - int operations_pending; // Counter for pending operations. - int active_sink_index; // The active sink index, i.e, the sink being used for playback. - char *active_sink_name; // The name of the active sink. - uint32_t current_sink_index; // The sink being processed right now by the program. It's not necessarily the same as the playback sink. - - - bool (*initialize)(pulseaudio_manager *self); ///< Function to initialize the manager. - bool (*load_sinks)(pulseaudio_manager *self); ///< Function to load available sinks. - bool (*switch_sink)(pulseaudio_manager *self, uint32_t sink_index); ///< Function to switch to a specified sink. - bool (*set_volume)(pulseaudio_manager *self, uint32_t sink_index, float percentage); // Function to set the volume to a specified percentage. - void (*get_active_sink)(pulseaudio_manager *manager); // Function to set the volume to a specified percentage. - void (*iterate)(pulseaudio_manager *manager, pa_operation *op); //Functions to go through every step of a threaded loop. - void (*destroy)(pulseaudio_manager *manager); - int (*get_sink_channels) (const pulseaudio_sink *sinks, int sink_index); //Function to get the number of channels of a given sink. - pulseaudio_sink (*get_sink_list)(pulseaudio_manager *self); //Function to get a list of available sinks. - -}; - -/** - * @brief Create a new pulseaudio_manager instance. - * @return A pointer to the newly created pulseaudio_manager instance. - */ -pulseaudio_manager* new_manager(void); - -/** - * @brief Free the memory associated with a pulseaudio_manager instance. - * @param manager The pulseaudio_manager instance to delete. - */ -void get_active_sink(pulseaudio_manager *manager); -bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage); -void iterate(pulseaudio_manager *manager, pa_operation *op); -int get_sink_channels(const pulseaudio_sink *sinks, int sink_index); -void destroy(pulseaudio_manager *manager); -pulseaudio_sink* get_sink_list(pulseaudio_manager *self); - -#endif // CORE_H diff --git a/current/examples/error.txt b/current/examples/error.txt deleted file mode 100644 index 1c0dada..0000000 --- a/current/examples/error.txt +++ /dev/null @@ -1,4 +0,0 @@ -[DEBUG, getActiveSink]: Iterating to get active sink info. -[DEBUG]: Default sink name from server: alsa_output.pci-0000_01_00.1.hdmi-stereo -[DEBUG]: Comparing with sink 0: alsa_output.pci-0000_00_1b.0.analog-surround-51 -[DEBUG]: Comparing with sink 1: alsa_output.pci-0000_01_00.1.hdmi-stereo diff --git a/current/examples/switch-sink b/current/examples/switch-sink deleted file mode 100644 index 78b8af8..0000000 Binary files a/current/examples/switch-sink and /dev/null differ diff --git a/current/examples/volume-change b/current/examples/volume-change deleted file mode 100644 index 04b3689..0000000 Binary files a/current/examples/volume-change and /dev/null differ diff --git a/current/Makefile b/v-0.02/Makefile similarity index 100% rename from current/Makefile rename to v-0.02/Makefile diff --git a/current/documentation/pa_context -- interface overview.docx b/v-0.02/documentation/pa_context -- interface overview.docx similarity index 100% rename from current/documentation/pa_context -- interface overview.docx rename to v-0.02/documentation/pa_context -- interface overview.docx diff --git a/current/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.02/documentation/pavucontrol/pavucontrol sink update flow.txt similarity index 100% rename from current/documentation/pavucontrol/pavucontrol sink update flow.txt rename to v-0.02/documentation/pavucontrol/pavucontrol sink update flow.txt diff --git a/current/documentation/pulseaudio/introspect.c summary b/v-0.02/documentation/pulseaudio/introspect.c summary similarity index 100% rename from current/documentation/pulseaudio/introspect.c summary rename to v-0.02/documentation/pulseaudio/introspect.c summary diff --git a/current/documentation/pulseaudio/mainloop code flow.txt b/v-0.02/documentation/pulseaudio/mainloop code flow.txt similarity index 100% rename from current/documentation/pulseaudio/mainloop code flow.txt rename to v-0.02/documentation/pulseaudio/mainloop code flow.txt diff --git a/current/easypulse_core.c b/v-0.02/easypulse_core.c similarity index 72% rename from current/easypulse_core.c rename to v-0.02/easypulse_core.c index e9c4355..d1de775 100644 --- a/current/easypulse_core.c +++ b/v-0.02/easypulse_core.c @@ -17,15 +17,15 @@ * @param c The PulseAudio context. * @param i The sink information. * @param eol End of list flag. - * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + * @param userdata User-provided data (expected to be a pointer to PulseAudioManager). */ static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { - pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + PulseAudioManager *manager = (PulseAudioManager *)userdata; // Check if we need to resize the sinks array if (manager->sink_count % INITIAL_ALLOCATION_SIZE == 0) { // Resize every time we hit a multiple of INITIAL_ALLOCATION_SIZE - size_t newSize = (manager->sink_count + INITIAL_ALLOCATION_SIZE) * sizeof(pulseaudio_sink); - pulseaudio_sink *new_sinks = realloc(manager->sinks, newSize); + size_t newSize = (manager->sink_count + INITIAL_ALLOCATION_SIZE) * sizeof(PulseAudioSink); + PulseAudioSink *new_sinks = realloc(manager->sinks, newSize); if (!new_sinks) { fprintf(stderr, "[ERROR]: Failed to resize the sinks array.\n"); @@ -52,7 +52,7 @@ static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdat } // Store the sink's information - pulseaudio_sink sink; + PulseAudioSink sink; sink.index = i->index; sink.name = strdup(i->name); sink.description = strdup(i->description); @@ -72,7 +72,7 @@ static void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdat */ static void context_state_cb(pa_context *c, void *userdata) { pa_context_state_t state; - pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + PulseAudioManager *manager = (PulseAudioManager *) userdata; int *pa_ready = &(manager->pa_ready); pa_threaded_mainloop *m = manager->mainloop; @@ -97,12 +97,12 @@ static void context_state_cb(pa_context *c, void *userdata) { } /** - * @brief Initializes the pulseaudio_manager. + * @brief Initializes the PulseAudioManager. * - * @param self Pointer to the pulseaudio_manager instance. + * @param self Pointer to the PulseAudioManager instance. * @return Boolean indicating success or failure. */ -bool initialize(pulseaudio_manager *self) { +bool initialize(PulseAudioManager *self) { // 1. Create the threaded mainloop self->mainloop = pa_threaded_mainloop_new(); if (!self->mainloop) { @@ -150,12 +150,12 @@ bool initialize(pulseaudio_manager *self) { /** - * @brief Cleans up the pulseaudio_manager. + * @brief Cleans up the PulseAudioManager. * - * @param self Pointer to the pulseaudio_manager instance. + * @param self Pointer to the PulseAudioManager instance. * @return Boolean indicating success or failure. */ -static bool cleanup(pulseaudio_manager *self) { +bool cleanup(PulseAudioManager *self) { // Lock the mainloop before making changes to the context pa_threaded_mainloop_lock(self->mainloop); @@ -179,10 +179,10 @@ static bool cleanup(pulseaudio_manager *self) { /** * @brief Load available sound cards (sinks). - * @param self The pulseaudio_manager instance. + * @param self The PulseAudioManager instance. * @return true on success, false otherwise. */ -bool load_sinks(pulseaudio_manager *self) { +bool loadSinks(PulseAudioManager *self) { pa_operation *op; op = pa_context_get_sink_info_list(self->context, sink_cb, self); @@ -194,43 +194,17 @@ bool load_sinks(pulseaudio_manager *self) { return true; } -/** - * @brief Retrieve a list of available sinks. - * @param self A pointer to the pulseaudio_manager instance. - * @return A pointer to an array of available sinks. The caller is responsible for freeing this memory. - */ -pulseaudio_sink* get_sink_list(pulseaudio_manager *self) { - if (!self || !self->sinks_loaded) { - fprintf(stderr, "[ERROR]: Manager not initialized or sinks not loaded.\n"); - return NULL; - } - - // Allocate memory for the list of sinks - pulseaudio_sink *sinks_list = malloc(self->sink_count * sizeof(pulseaudio_sink)); - if (!sinks_list) { - fprintf(stderr, "[ERROR]: Failed to allocate memory for sinks list.\n"); - return NULL; - } - - // Copy sink information from the manager's sinks array - for (uint32_t i = 0; i < self->sink_count; i++) { - sinks_list[i] = self->sinks[i]; - } - - return sinks_list; -} - /** * @brief Callback function handling the completion of the "unmute" operation. * * @param c The PulseAudio context. * @param success A flag indicating the success or failure of the operation. - * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + * @param userdata User-provided data, expected to be a pointer to a PulseAudioManager instance. */ static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { (void)c; // Suppress unused parameter warning - pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + PulseAudioManager* manager = (PulseAudioManager*) userdata; manager->operations_pending--; if (!success) { @@ -246,10 +220,10 @@ static void operation_complete_unmute_cb(pa_context *c, int success, void *userd * * @param c The PulseAudio context. * @param success A flag indicating the success or failure of the operation. - * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + * @param userdata User-provided data, expected to be a pointer to a PulseAudioManager instance. */ static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { - pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + PulseAudioManager* manager = (PulseAudioManager*) userdata; if (success) { pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_sink_index, 0, operation_complete_unmute_cb, manager); @@ -265,10 +239,10 @@ static void operation_complete_move_cb(pa_context *c, int success, void *userdat * * @param c The PulseAudio context. * @param success A flag indicating the success or failure of the operation. - * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + * @param userdata User-provided data, expected to be a pointer to a PulseAudioManager instance. */ static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { - pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + PulseAudioManager* manager = (PulseAudioManager*) userdata; uint32_t target_sink_index = manager->current_sink_index; if (success) { @@ -288,7 +262,7 @@ static void operation_complete_mute_cb(pa_context *c, int success, void *userdat * @param userdata User-provided data (expected to be a pointer to the target sink index). */ static void switch_sink_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { - pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + PulseAudioManager* manager = (PulseAudioManager*) userdata; if (!eol && i) { // Move this sink input to the desired sink @@ -302,13 +276,13 @@ static void switch_sink_cb(pa_context *c, const pa_sink_input_info *i, int eol, } /** - * @brief Switches the sink (audio source) for the pulseaudio_manager. + * @brief Switches the sink (audio source) for the PulseAudioManager. * - * @param self Pointer to the pulseaudio_manager instance. + * @param self Pointer to the PulseAudioManager instance. * @param sink_index Index of the sink to switch to. * @return Boolean indicating success or failure. */ -bool switch_sink(pulseaudio_manager *self, uint32_t sink_index) { +bool switchSink(PulseAudioManager *self, uint32_t sink_index) { // Ensure the context is valid if (!self || !self->context) { return false; @@ -361,12 +335,11 @@ bool switch_sink(pulseaudio_manager *self, uint32_t sink_index) { * @param eol End of list flag. * @param userdata User-provided data. */ -static void set_volume_check_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { - (void) c; - pulseaudio_manager *self = (pulseaudio_manager *)userdata; +void volume_check_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + PulseAudioManager *self = (PulseAudioManager *)userdata; if (eol > 0) { - //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); self->operations_pending--; return; } @@ -400,8 +373,8 @@ static void set_volume_check_cb(pa_context *c, const pa_sink_info *i, int eol, v // Completion callback for volume set operation static void volume_set_complete_cb(pa_context *c, int success, void *userdata) { - (void) c; - pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + (void)c; + PulseAudioManager* manager = (PulseAudioManager*) userdata; //printf("[DEBUG, volume_set_complete_cb()]: inside volume_set_complete_cb()\n"); //printf("[DEBUG, volume_set_complete_cb()]: sink name is, %s\n", manager->active_sink_name); @@ -411,7 +384,7 @@ static void volume_set_complete_cb(pa_context *c, int success, void *userdata) { } // Debug: Print cvolume values for each channel as percentages - //pa_cvolume cvolume = manager->sinks[manager->active_sink_index].volume; + pa_cvolume cvolume = manager->sinks[manager->active_sink_index].volume; /*for (int i = 0; i < cvolume.channels; i++) { float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; @@ -426,22 +399,6 @@ static void volume_set_complete_cb(pa_context *c, int success, void *userdata) { pa_threaded_mainloop_signal(manager->mainloop, 0); } -/** - * @brief Retrieves the number of channels for a specified sink. - * - * @param sinks Pointer to an array of PulseSink structures. - * @param sink_index Index of the sink whose number of channels is to be retrieved. - * - * @return Number of channels for the specified sink. Returns -1 on error. - */ -int get_sink_channels(const pulseaudio_sink *sinks, int sink_index) { - if (!sinks || sink_index < 0) { - fprintf(stderr, "[ERROR]: Invalid sinks array or sink index in get_sink_channels.\n"); - return -1; // Return -1 or another indicator of failure - } - return sinks[sink_index].channel_map.channels; -} - /** * @brief Set the volume for a specified sink. @@ -450,12 +407,12 @@ int get_sink_channels(const pulseaudio_sink *sinks, int sink_index) { * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. * It will adjust the volume for all channels of the sink to the desired level. * - * @param self Pointer to the pulseaudio_manager instance. + * @param self Pointer to the PulseAudioManager instance. * @param sink_index Index of the sink to set the volume for. * @param percentage Desired volume level as a percentage. * @return Boolean indicating success or failure. */ -bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) { +bool setVolume(PulseAudioManager *self, uint32_t sink_index, float percentage) { if (!self || percentage < 0.0f || percentage > 100.0f || sink_index >= self->sink_count) { return false; } @@ -467,8 +424,8 @@ bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) } // Debug: show index and desired volume. - //printf("[DEBUG, set_volume()] Index is: %i\n", sink_index); - //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + //printf("[DEBUG, setVolume()] Index is: %i\n", sink_index); + //printf("[DEBUG, setVolume()] Desired volume: %f%% (value: %u)\n", percentage, volume); // Ensure PulseAudio is ready and sinks are loaded if (self->pa_ready != 1 || self->sinks_loaded != 1) { @@ -477,7 +434,7 @@ bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) // Debug: Show channel volumes before the change. /*for (int channel = 0; channel < self->sinks[sink_index].channel_map.channels; channel++) { - printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + printf("[DEBUG, setVolume()]: Channel %d Before volume: %f%%\n", channel, 100.0 * self->sinks[sink_index].volume.values[channel] / PA_VOLUME_NORM); }*/ @@ -488,7 +445,7 @@ bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) cvolume.channels = self->sinks[sink_index].channel_map.channels; // Manually set channels pa_cvolume_set(&cvolume, cvolume.channels, volume); - printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + printf("[DEBUG, setVolume()] channels: %d\n", cvolume.channels); // Apply the volume change to the specific sink by index and wait for the operation to complete const char *sink_name_to_change = self->sinks[sink_index].name; @@ -497,7 +454,7 @@ bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) self->iterate(self, op); // Fetch the updated volume for the sink and wait for the operation to complete - pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, sink_name_to_change, set_volume_check_cb, self); + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, sink_name_to_change, volume_check_cb, self); self->iterate(self, op2); return true; @@ -505,23 +462,21 @@ bool set_volume(pulseaudio_manager *self, uint32_t sink_index, float percentage) /** - * @brief Iterates through operations in the pulseaudio_manager. + * @brief Iterates through operations in the PulseAudioManager. * - * @param manager Pointer to the pulseaudio_manager instance. + * @param manager Pointer to the PulseAudioManager instance. * @param op Pointer to the pa_operation instance. */ -void iterate(pulseaudio_manager *manager, pa_operation *op) { - pa_threaded_mainloop_lock(manager->mainloop); - +void iterate(PulseAudioManager *manager, pa_operation *op) { if (!op) { - pa_threaded_mainloop_signal(manager->mainloop, 0); // Signal completion even if there's no operation - pa_threaded_mainloop_unlock(manager->mainloop); return; } const int MAX_WAIT_CYCLES = 100; // For example, wait for 100 cycles int wait_cycles = 0; + pa_threaded_mainloop_lock(manager->mainloop); + // Wait for the operation to complete, but not indefinitely while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { if (wait_cycles >= MAX_WAIT_CYCLES) { @@ -533,48 +488,57 @@ void iterate(pulseaudio_manager *manager, pa_operation *op) { wait_cycles++; } - pa_threaded_mainloop_signal(manager->mainloop, 0); // Signal that iteration is complete + // pa_operation_unref(op); // Commented out to prevent early unreference pa_threaded_mainloop_unlock(manager->mainloop); } /** - * @brief Create a new pulseaudio_manager instance. - * @return Returns a pointer to a manager structure. + * @brief Create a new PulseAudioManager instance. + * @return A pointer to the newly created PulseAudioManager instance. */ -pulseaudio_manager *new_manager(void) { - pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); +PulseAudioManager* newPulseAudioManager() { + PulseAudioManager *manager = (PulseAudioManager *)malloc(sizeof(PulseAudioManager)); if (!manager) return NULL; - manager->sinks = (pulseaudio_sink *)malloc(5 * sizeof(pulseaudio_sink)); - if (!manager->sinks) { - free(manager); - return NULL; - } - - // Initialize pointers to 0 -- good practice. - manager->sink_count = 0; - manager->sinks_loaded = 0; - manager->sinks = (pulseaudio_sink *)malloc(5 * sizeof(pulseaudio_sink)); + manager->sinks = (PulseAudioSink *)malloc(5 * sizeof(PulseAudioSink)); manager->sink_count = 0; manager->sinks_loaded = 0; // Initialize to 0 - manager->load_sinks = load_sinks; - manager->destroy = destroy; - manager->switch_sink = switch_sink; - manager->set_volume = set_volume; - manager->get_active_sink = get_active_sink; + manager->initialize = initialize; + manager->cleanup = cleanup; + manager->loadSinks = loadSinks; + manager->switchSink = switchSink; + manager->setVolume = setVolume; + manager->getActiveSink = getActiveSink; manager->iterate = iterate; - manager->get_sink_channels = get_sink_channels; - if(!initialize(manager)) { - free(manager->sinks); - free(manager); - return NULL; - } return manager; } +/** + * @brief Request the default sink name from the PulseAudio server and determine the active sink. + * + * This function initiates a request to retrieve the default sink name (active sink) from the PulseAudio server. + * Once the server provides this information, the server_info_cb callback is triggered to process the response. + * + * @param manager Pointer to the PulseAudioManager instance. + */ +void getActiveSink(PulseAudioManager *manager) { + // Request server information to get the default sink name + pa_context_get_server_info(manager->context, server_info_cb, manager); + + // Wait for the active sink name to be set + int timeout = 50; // Number of iterations or timeout value + + while (!manager->active_sink_name && timeout-- > 0) { + manager->iterate(manager, NULL); + //fprintf(stderr, "[DEBUG, getActiveSink]: Iterating to get active sink info.\n"); + usleep(1000); // Sleep for 1ms + } +} + + /** * @brief Callback function to process the PulseAudio server information. * @@ -584,22 +548,22 @@ pulseaudio_manager *new_manager(void) { * * @param c Pointer to the PulseAudio context. * @param info Pointer to the pa_server_info structure containing the server's information. - * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + * @param userdata User-provided data (expected to be a pointer to PulseAudioManager). */ -static void active_sink_cb(pa_context *c, const pa_server_info *info, void *userdata) { +void server_info_cb(pa_context *c, const pa_server_info *info, void *userdata) { if (!info || !info->default_sink_name) { - fprintf(stderr, "[ERROR]: Null pointer in active_sink_cb.\n"); + fprintf(stderr, "[ERROR]: Null pointer in server_info_cb.\n"); return; } (void) c; - pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + PulseAudioManager *manager = (PulseAudioManager *)userdata; // Get the default sink name from the server information const char *default_sink_name = info->default_sink_name; //fprintf(stderr, "[DEBUG]: Default sink name from server: %s\n", default_sink_name); // Iterate over the available sinks to find the active one - for (uint32_t i = 0; i < manager->sink_count; i++) { + for (int i = 0; i < manager->sink_count; i++) { //fprintf(stderr, "[DEBUG]: Comparing with sink %d: %s\n", i, manager->sinks[i].name); if (strcmp(manager->sinks[i].name, default_sink_name) == 0) { // Set the active sink index when a match is found @@ -608,52 +572,23 @@ static void active_sink_cb(pa_context *c, const pa_server_info *info, void *user break; } } - //printf("[DEBUG, active_sink_cb()]: Active sink index is, %i\n", manager->active_sink_index); - //printf("[DEBUG, active_sink_cb()]: Active sink name is, %s\n", manager->active_sink_name); - pa_threaded_mainloop_signal(manager->mainloop, 0); -} - -/** - * @brief Request the default sink name from the PulseAudio server and determine the active sink. - * - * This function initiates a request to retrieve the default sink name (active sink) from the PulseAudio server. - * Once the server provides this information, the active_sink_cb callback is triggered to process the response. - * - * @param manager Pointer to the pulseaudio_manager instance. - */ -void get_active_sink(pulseaudio_manager *manager) { - pa_threaded_mainloop_lock(manager->mainloop); - - // Request server information - pa_context_get_server_info(manager->context, active_sink_cb, manager); - - // Wait until the iterate function signals that it's done - pa_threaded_mainloop_wait(manager->mainloop); - - pa_threaded_mainloop_unlock(manager->mainloop); - - // The original loop to wait for active_sink_name to be set - int timeout = 50; // Number of iterations or timeout value - while (!manager->active_sink_name && timeout-- > 0) { - manager->iterate(manager, NULL); - } + //printf("[DEBUG, server_info_cb()]: Active sink index is, %i\n", manager->active_sink_index); + //printf("[DEBUG, server_info_cb()]: Active sink name is, %s\n", manager->active_sink_name); } /** - * @brief Frees the memory of a pulseaudio_manager instance. + * @brief Deletes and cleans up a PulseAudioManager instance. * - * @param manager Pointer to the pulseaudio_manager instance to be deleted. + * @param manager Pointer to the PulseAudioManager instance to be deleted. */ -void destroy(pulseaudio_manager *self) { - cleanup(self); - - if (self) { - for (uint32_t i = 0; i < self->sink_count; i++) { - free(self->sinks[i].name); - free(self->sinks[i].description); +void deletePulseAudioManager(PulseAudioManager *manager) { + if (manager) { + for (int i = 0; i < manager->sink_count; i++) { + free(manager->sinks[i].name); + free(manager->sinks[i].description); } - free(self->sinks); - free(self); + free(manager->sinks); + free(manager); } } diff --git a/v-0.02/easypulse_core.h b/v-0.02/easypulse_core.h new file mode 100644 index 0000000..daadbab --- /dev/null +++ b/v-0.02/easypulse_core.h @@ -0,0 +1,77 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include +#include +#include + + +// Forward declaration +typedef struct PulseAudioManager PulseAudioManager; + +/** + * @brief Represents a PulseAudio sink. + */ +typedef struct { + uint32_t index; ///< Index of the sink. + char *name; ///< Name of the sink. + char *description; ///< Description of the sink. + pa_cvolume volume; ///< Volume of the sink. + pa_channel_map channel_map; ///< Channel map of the sink. + int mute; ///< Mute status of the sink (1 for muted, 0 for unmuted). + int number_of_channels; //The number of channels of the sink. +} PulseAudioSink; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct PulseAudioManager { + pa_threaded_mainloop *mainloop; ///< Mainloop for PulseAudio operations. + pa_context *context; ///< PulseAudio context. + PulseAudioSink *sinks; ///< Array of available sinks. + int sink_count; ///< Count of available sinks. + int pa_ready; ///< Indicates if PulseAudio is ready (1 for ready, 2 for error). + int sinks_loaded; ///< Indicates if sinks are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + int active_sink_index; // The active sink index, i.e, the sink being used for playback. + char *active_sink_name; // The name of the active sink. + uint32_t current_sink_index; // The sink being processed right now by the program. It's not necessarily the same as the playback sink. + + + bool (*initialize)(PulseAudioManager *self); ///< Function to initialize the manager. + bool (*cleanup)(PulseAudioManager *self); ///< Function to cleanup the manager. + bool (*loadSinks)(PulseAudioManager *self); ///< Function to load available sinks. + bool (*switchSink)(PulseAudioManager *self, uint32_t sink_index); ///< Function to switch to a specified sink. + bool (*setVolume)(PulseAudioManager *self, uint32_t sink_index, float percentage); // Function to set the volume to a specified percentage. + void (*getActiveSink)(PulseAudioManager *manager); // Function to set the volume to a specified percentage. + void (*iterate)(PulseAudioManager *manager, pa_operation *op); + +}; + +/** + * @brief Create a new PulseAudioManager instance. + * @return A pointer to the newly created PulseAudioManager instance. + */ +PulseAudioManager* newPulseAudioManager(); + +/** + * @brief Free the memory associated with a PulseAudioManager instance. + * @param manager The PulseAudioManager instance to delete. + */ +void deletePulseAudioManager(PulseAudioManager *manager); +void server_info_cb(pa_context *c, const pa_server_info *info, void *userdata); +void getActiveSink(PulseAudioManager *manager); +bool setVolume(PulseAudioManager *self, uint32_t sink_index, float percentage); +void iterate(PulseAudioManager *manager, pa_operation *op); + +#endif // CORE_H diff --git a/current/examples/Makefile b/v-0.02/examples/Makefile similarity index 100% rename from current/examples/Makefile rename to v-0.02/examples/Makefile diff --git a/current/examples/switch-sink-pulseaudio.c b/v-0.02/examples/switch-sink-pulseaudio.c similarity index 100% rename from current/examples/switch-sink-pulseaudio.c rename to v-0.02/examples/switch-sink-pulseaudio.c diff --git a/current/examples/switch-sink.c b/v-0.02/examples/switch-sink.c similarity index 75% rename from current/examples/switch-sink.c rename to v-0.02/examples/switch-sink.c index 1c5e8c2..cc0a1ac 100644 --- a/current/examples/switch-sink.c +++ b/v-0.02/examples/switch-sink.c @@ -13,18 +13,18 @@ int main() { // Initialize the PulseAudio Manager - pulseaudio_manager *manager = new_manager(); - pulseaudio_manager *self = manager; - - if (!manager) { - fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); - manager->destroy(self); + PulseAudioManager *manager = newPulseAudioManager(); + if (!manager->initialize(manager)) { + fprintf(stderr, "Failed to initialize PulseAudio Manager.\n"); return 1; } - if (!manager->load_sinks(manager)) { + // Load available sinks + //printf("[DEBUG, main()]: trying to load sinks...\n"); + + if (!manager->loadSinks(manager)) { fprintf(stderr, "Failed to load sinks.\n"); - manager->destroy(self); + manager->cleanup(manager); return 1; } @@ -43,17 +43,18 @@ int main() { // Validate the user's choice if (choice < 1 || choice > manager->sink_count) { fprintf(stderr, "Invalid choice.\n"); - manager->destroy(self); + manager->cleanup(manager); return 1; } + // Switch to the selected sink - if (manager->switch_sink(manager, choice - 1)) { + if (manager->switchSink(manager, choice - 1)) { printf("Successfully switched to the selected sink.\n"); //Get active sink. - manager->get_active_sink(manager); + manager->getActiveSink(manager); // Debug code to print the default sink after the switch fprintf(stderr, "[DEBUG]: Default sink after switch: %s\n", manager->active_sink_name); @@ -64,7 +65,8 @@ int main() { } // Cleanup - manager->destroy(self); + manager->cleanup(manager); + deletePulseAudioManager(manager); return 0; } diff --git a/v-0.02/examples/volume-change b/v-0.02/examples/volume-change new file mode 100644 index 0000000..376eb30 Binary files /dev/null and b/v-0.02/examples/volume-change differ diff --git a/v-0.02/examples/volume-change-pulseaudio b/v-0.02/examples/volume-change-pulseaudio new file mode 100644 index 0000000..1c6b2aa Binary files /dev/null and b/v-0.02/examples/volume-change-pulseaudio differ diff --git a/current/examples/volume-change-pulseaudio.c b/v-0.02/examples/volume-change-pulseaudio.c similarity index 100% rename from current/examples/volume-change-pulseaudio.c rename to v-0.02/examples/volume-change-pulseaudio.c diff --git a/current/examples/volume-change.c b/v-0.02/examples/volume-change.c similarity index 66% rename from current/examples/volume-change.c rename to v-0.02/examples/volume-change.c index 316e243..7b6913d 100644 --- a/current/examples/volume-change.c +++ b/v-0.02/examples/volume-change.c @@ -13,42 +13,36 @@ * @date 10-08-2023 (creation date) */ -#include #include #include "../easypulse_core.h" int main() { - // Initialize the pulseaudio manager - pulseaudio_manager *manager = new_manager(); - pulseaudio_manager *self = manager; - - if (!manager) { + // Initialize PulseAudioManager + PulseAudioManager *manager = newPulseAudioManager(); + if (!manager->initialize(manager)) { fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); - manager->destroy(self); + deletePulseAudioManager(manager); return 1; } // Load available sinks - if (!manager->load_sinks(manager)) { + if (!manager->loadSinks(manager)) { fprintf(stderr, "Failed to load sinks.\n"); - manager->destroy(self); + manager->cleanup(manager); + deletePulseAudioManager(manager); return 1; } // Get the active sink - manager->get_active_sink(self); + manager->getActiveSink(manager); // Display the active sink and its master volume - uint32_t active_sink_index = manager->active_sink_index; - uint32_t total_channels = manager->get_sink_channels(self->sinks, self->active_sink_index); - - printf("DEBUG, total_channels is: %lu\n", (long unsigned int) total_channels); - + int active_sink_index = manager->active_sink_index; printf("Current Sink: %s\n", manager->active_sink_name); // Debug: Print the volume of individual channels of the active sink - for (uint32_t channel = 0; channel < total_channels; channel++) { + for (int channel = 0; channel < manager->sinks[active_sink_index].channel_map.channels; channel++) { printf("Channel %d volume before change: %f%%\n", channel, (100.0 * manager->sinks[active_sink_index].volume.values[channel] / PA_VOLUME_NORM)); @@ -64,17 +58,22 @@ int main() { scanf("%f", &new_volume); // Set the new volume - manager->set_volume(manager, manager->active_sink_index, new_volume); - + bool success = manager->setVolume(manager, manager->active_sink_index, new_volume); + if (success) { + printf("Successfully applied the new volume.\n"); + } else { + printf("Failed to apply the new volume.\n"); + } // Debug: Print the volume of individual channels of the active sink - for (uint32_t channel = 0; channel < total_channels; channel++) { + for (int channel = 0; channel < manager->sinks[active_sink_index].channel_map.channels; channel++) { printf("Channel %d volume after change: %f%%\n", channel, (100.0 * manager->sinks[active_sink_index].volume.values[channel] / PA_VOLUME_NORM)); } // Cleanup and exit - manager->destroy(self); + manager->cleanup(manager); + deletePulseAudioManager(manager); return 0; } diff --git a/v-0.03/Makefile b/v-0.03/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.03/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.03/documentation/pa_context -- interface overview.docx b/v-0.03/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.03/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.03/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.03/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.03/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.03/documentation/pulseaudio/introspect.c summary b/v-0.03/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.03/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.03/documentation/pulseaudio/mainloop code flow.txt b/v-0.03/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.03/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.03/easypulse_core.c b/v-0.03/easypulse_core.c new file mode 100644 index 0000000..94c4fe0 --- /dev/null +++ b/v-0.03/easypulse_core.c @@ -0,0 +1,675 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include +#define INITIAL_ALLOCATION_SIZE 5 +#include +#include + +/** + * @brief Callback function for retrieving device information. + * @param c The PulseAudio context. + * @param i The device information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void device_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if we need to resize the devices array + if (manager->device_count % INITIAL_ALLOCATION_SIZE == 0) { // Resize every time we hit a multiple of INITIAL_ALLOCATION_SIZE + size_t newSize = (manager->device_count + INITIAL_ALLOCATION_SIZE) * sizeof(pulseaudio_device); + pulseaudio_device *new_devices = realloc(manager->devices, newSize); + + if (!new_devices) { + fprintf(stderr, "[ERROR]: Failed to resize the devices array.\n"); + free(manager->devices); // Free old memory + manager->devices = NULL; + manager->device_count = 0; // Reset device_count to prevent out-of-bounds access + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + manager->devices = new_devices; + } + + if (eol < 0) { + if (pa_context_errno(c) != PA_ERR_NOENTITY) + fprintf(stderr, "Sink callback failure\\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + if (eol > 0) { + manager->devices_loaded = 1; + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Store the device's information + pulseaudio_device device; + device.index = i->index; + device.name = strdup(i->name); + device.description = strdup(i->description); + device.volume = i->volume; + device.channel_map = i->channel_map; + device.mute = i->mute; + + // Add the device to the manager's list + manager->devices[manager->device_count++] = device; +} + + +/** + * @brief Callback function to check the state of the PulseAudio context. + * @param c The PulseAudio context. + * @param userdata User-provided data (expected to be a pointer to an int indicating readiness). + */ +static void context_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + +/** + * @brief Initializes the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +bool initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, context_state_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + + +/** + * @brief Cleans up the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +static bool cleanup(pulseaudio_manager *self) { + // Lock the mainloop before making changes to the context + pa_threaded_mainloop_lock(self->mainloop); + + // Disconnect the context + pa_context_disconnect(self->context); + + // Unlock the mainloop before stopping it + pa_threaded_mainloop_unlock(self->mainloop); + + // Stop the threaded mainloop + pa_threaded_mainloop_stop(self->mainloop); + + // Unreference the context + pa_context_unref(self->context); + + // Free the threaded mainloop + pa_threaded_mainloop_free(self->mainloop); + + return true; +} + +/** + * @brief Load available sound cards (devices). + * @param self The pulseaudio_manager instance. + * @return true on success, false otherwise. + */ +bool load_devices(pulseaudio_manager *self) { + pa_operation *op; + op = pa_context_get_sink_info_list(self->context, device_cb, self); + + if (!op) { + return false; + } + + self->iterate(self, op); + return true; +} + +/** + * @brief Retrieve a list of available devices. + * @param self A pointer to the pulseaudio_manager instance. + * @return A pointer to an array of available devices. The caller is responsible for freeing this memory. + */ +pulseaudio_device* get_device_list(pulseaudio_manager *self) { + if (!self || !self->devices_loaded) { + fprintf(stderr, "[ERROR]: Manager not initialized or devices not loaded.\n"); + return NULL; + } + + // Allocate memory for the list of devices + pulseaudio_device *devices_list = malloc(self->device_count * sizeof(pulseaudio_device)); + if (!devices_list) { + fprintf(stderr, "[ERROR]: Failed to allocate memory for devices list.\n"); + return NULL; + } + + // Copy device information from the manager's devices array + for (uint32_t i = 0; i < self->device_count; i++) { + devices_list[i] = self->devices[i]; + } + + return devices_list; +} + +/** + * @brief Callback function handling the completion of the "unmute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { + (void)c; // Suppress unused parameter warning + + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + manager->operations_pending--; + + if (!success) { + fprintf(stderr, "Failed to unmute the device input.\n"); + } + + // Signal the mainloop to resume any waiting threads. + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback function handling the completion of the "move" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (success) { + pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_device_index, 0, operation_complete_unmute_cb, manager); + pa_operation_unref(unmute_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function handling the completion of the "mute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + uint32_t target_device_index = manager->current_device_index; + + if (success) { + pa_operation* move_op = pa_context_move_sink_input_by_index(c, manager->current_device_index, target_device_index, operation_complete_move_cb, manager); + pa_operation_unref(move_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function to handle each device input. + * @param c The PulseAudio context. + * @param i The device input information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to the target device index). + */ +static void switch_device_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (!eol && i) { + // Move this device input to the desired device + pa_operation* move_op = pa_context_move_sink_input_by_index(c, i->index, manager->devices[manager->current_device_index].index, NULL, NULL); + pa_operation_unref(move_op); + } + + if (eol) { + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Switches the device (audio source) for the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to switch to. + * @return Boolean indicating success or failure. + */ +bool switch_device(pulseaudio_manager *self, uint32_t device_index) { + // Ensure the context is valid + if (!self || !self->context) { + return false; + } + + // Check if device_index is out of bounds + if (device_index >= self->device_count) { + fprintf(stderr, "[ERROR]: device_index out of bounds.\n"); + return false; + } + + self->current_device_index = device_index; + + // Set the desired device as the default device + /*fprintf(stderr, "[DEBUG]: self->context = %p\n", self->context); + fprintf(stderr, "[DEBUG]: self->devices = %p\n", self->devices); + fprintf(stderr, "[DEBUG]: device_index = %d\n", device_index);*/ + + if (self->devices) { + // Check if the name attribute is NULL + if (!self->devices[device_index].name) { + fprintf(stderr, "[ERROR]: Sink's name is NULL.\n"); + return false; + } + //fprintf(stderr, "[DEBUG]: self->devices[device_index].name = %s\n", self->devices[device_index].name); + } + pa_operation* set_default_op = pa_context_set_default_sink(self->context, self->devices[device_index].name, NULL, NULL); + pa_operation_unref(set_default_op); + + // Use the introspect API to get a list of all device inputs + pa_operation *op = pa_context_get_sink_input_info_list(self->context, switch_device_cb, self); + + if (!op) { + return false; + } + + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + self->iterate(self, op); + } + + pa_operation_unref(op); + + return true; +} + + +/** + * @brief Retrieves the number of channels for a specified device. + * + * @param devices Pointer to an array of PulseSink structures. + * @param device_index Index of the device whose number of channels is to be retrieved. + * + * @return Number of channels for the specified device. Returns -1 on error. + */ +int get_device_channels(const pulseaudio_device *devices, int device_index) { + if (!devices || device_index < 0) { + fprintf(stderr, "[ERROR]: Invalid devices array or device index in get_device_channels.\n"); + return -1; // Return -1 or another indicator of failure + } + return devices[device_index].channel_map.channels; +} + + + +/** + * @brief Callback function belonging to set_volume. Triggers when audio volume is set. + * + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb1(pa_context *c, int success, void *userdata) { + (void) c; + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + + if (!success) { + fprintf(stderr, "[ERROR]: Failed to set volume. Reason: %s\n", pa_strerror(pa_context_errno(c))); + } + + // Debug: Print cvolume values for each channel as percentages + //pa_cvolume cvolume = manager->devices[manager->active_device_index].volume; + + /*for (int i = 0; i < cvolume.channels; i++) { + float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; + //printf("[DEBUG, volume_set_complete_cb()]: Channel %d cvolume value: %u (%.2f%%)\n", i, cvolume.values[i], percentage); + } + //printf("[DEBUG, volume_set_complete_cb invoked. Success: %d\n", success);*/ + + // Decrease the operations count and potentially signal the condition variable. + manager->operations_pending--; + + // Signal the main loop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Callback function belonging to set_volume. + * Triggers after audio volume levels are updated. + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb2(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + pulseaudio_manager *self = (pulseaudio_manager *)userdata; + + if (eol > 0) { + //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + self->operations_pending--; + return; + } + + if (!i) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for pa_sink_info.\n"); + self->operations_pending--; + return; + } + + if (!i->name) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for device name.\n"); + self->operations_pending--; + return; + } + + printf("[DEBUG, volume_check_cb()]: Processing device info for device: %s\n", i->name); + + // Update the volume values in the manager's devices structure + self->devices[self->active_device_index].volume = i->volume; + + // Print the volume for each channel + for (int channel = 0; channel < i->volume.channels; channel++) { + //float volume_percentage = (float)i->volume.values[channel] / PA_VOLUME_NORM * 100.0; + //printf("Channel %d Volume: %.2f%%\n", channel, volume_percentage); + } + + //Signaling to continue. + pa_threaded_mainloop_signal(self->mainloop, 0); +} + + +/** + * @brief Set the volume for a specified device. + * + * This function allows setting the volume for a specific device based on the given percentage. + * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. + * It will adjust the volume for all channels of the device to the desired level. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to set the volume for. + * @param percentage Desired volume level as a percentage. + * @return Boolean indicating success or failure. + */ +bool set_volume(pulseaudio_manager *self, uint32_t device_index, float percentage) { + if (!self || percentage < 0.0f || percentage > 100.0f || device_index >= self->device_count) { + return false; + } + + // Convert percentage to volume + pa_volume_t volume = (percentage / 100.0) * PA_VOLUME_NORM; + if (volume >= PA_VOLUME_NORM) { + volume = PA_VOLUME_NORM - 1; + } + + // Debug: show index and desired volume. + //printf("[DEBUG, set_volume()] Index is: %i\n", device_index); + //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + + // Ensure PulseAudio is ready and devices are loaded + if (self->pa_ready != 1 || self->devices_loaded != 1) { + return false; + } + + // Debug: Show channel volumes before the change. + /*for (int channel = 0; channel < self->devices[device_index].channel_map.channels; channel++) { + printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + channel, + 100.0 * self->devices[device_index].volume.values[channel] / PA_VOLUME_NORM); + }*/ + + // Create a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_init(&cvolume); + cvolume.channels = self->devices[device_index].channel_map.channels; // Manually set channels + pa_cvolume_set(&cvolume, cvolume.channels, volume); + + printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + + // Apply the volume change to the specific device by index and wait for the operation to complete + const char *device_name_to_change = self->devices[device_index].name; + self->operations_pending++; + pa_operation *op = pa_context_set_sink_volume_by_name(self->context, device_name_to_change, &cvolume, set_volume_cb1, self); + self->iterate(self, op); + + // Fetch the updated volume for the device and wait for the operation to complete + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, device_name_to_change, set_volume_cb2, self); + self->iterate(self, op2); + + return true; +} + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +void iterate(pulseaudio_manager *manager, pa_operation *op) { + pa_threaded_mainloop_lock(manager->mainloop); + + if (!op) { + pa_threaded_mainloop_signal(manager->mainloop, 0); // Signal completion even if there's no operation + pa_threaded_mainloop_unlock(manager->mainloop); + return; + } + + const int MAX_WAIT_CYCLES = 100; // For example, wait for 100 cycles + int wait_cycles = 0; + + // Wait for the operation to complete, but not indefinitely + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + if (wait_cycles >= MAX_WAIT_CYCLES) { + fprintf(stderr, "Error: Operation timeout. Exiting loop.\\n"); + break; // Exit the loop if we've waited for too long + } + + pa_threaded_mainloop_wait(manager->mainloop); + wait_cycles++; + } + + pa_threaded_mainloop_signal(manager->mainloop, 0); // Signal that iteration is complete + pa_threaded_mainloop_unlock(manager->mainloop); +} + + +/** + * @brief Create a new pulseaudio_manager instance. + * @return Returns a pointer to a manager structure. + */ +pulseaudio_manager *new_manager(void) { + pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); + if (!manager) return NULL; + + manager->devices = (pulseaudio_device *)malloc(5 * sizeof(pulseaudio_device)); + if (!manager->devices) { + free(manager); + return NULL; + } + + // Initialize pointers to 0 -- good practice. + manager->device_count = 0; + manager->devices_loaded = 0; + manager->devices = (pulseaudio_device *)malloc(5 * sizeof(pulseaudio_device)); + manager->device_count = 0; + manager->devices_loaded = 0; // Initialize to 0 + manager->load_devices = load_devices; + manager->destroy = destroy; + manager->switch_device = switch_device; + manager->set_volume = set_volume; + manager->get_active_device = get_active_device; + manager->iterate = iterate; + manager->get_device_channels = get_device_channels; + + if(!initialize(manager)) { + free(manager->devices); + free(manager); + return NULL; + } + + return manager; +} + +/** + * @brief Callback function to process the PulseAudio server information. + * + * This callback is invoked once the server information is available. It retrieves the default device name + * from the server response and determines the active device by matching the name with the available devices + * in the manager's list. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the pa_server_info structure containing the server's information. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void active_device_cb(pa_context *c, const pa_server_info *info, void *userdata) { + if (!info || !info->default_sink_name) { + fprintf(stderr, "[ERROR]: Null pointer in active_device_cb.\n"); + return; + } + (void) c; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Get the default device name from the server information + const char *default_device_name = info->default_sink_name; + //fprintf(stderr, "[DEBUG]: Default device name from server: %s\n", default_device_name); + + // Iterate over the available devices to find the active one + for (uint32_t i = 0; i < manager->device_count; i++) { + //fprintf(stderr, "[DEBUG]: Comparing with device %d: %s\n", i, manager->devices[i].name); + if (strcmp(manager->devices[i].name, default_device_name) == 0) { + // Set the active device index when a match is found + manager->active_device_index = i; + manager->active_device_name = manager->devices[i].name; // Set the active device name here + break; + } + } + //printf("[DEBUG, active_device_cb()]: Active device index is, %i\n", manager->active_device_index); + //printf("[DEBUG, active_device_cb()]: Active device name is, %s\n", manager->active_device_name); + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Request the default device name from the PulseAudio server and determine the active device. + * + * This function initiates a request to retrieve the default device name (active device) from the PulseAudio server. + * Once the server provides this information, the active_device_cb callback is triggered to process the response. + * + * @param manager Pointer to the pulseaudio_manager instance. + */ +void get_active_device(pulseaudio_manager *manager) { + pa_threaded_mainloop_lock(manager->mainloop); + + // Request server information + pa_context_get_server_info(manager->context, active_device_cb, manager); + + // Wait until the iterate function signals that it's done + pa_threaded_mainloop_wait(manager->mainloop); + + pa_threaded_mainloop_unlock(manager->mainloop); + + // The original loop to wait for active_device_name to be set + int timeout = 50; // Number of iterations or timeout value + while (!manager->active_device_name && timeout-- > 0) { + manager->iterate(manager, NULL); + } +} + + +/** + * @brief Frees the memory of a pulseaudio_manager instance. + * + * @param manager Pointer to the pulseaudio_manager instance to be deleted. + */ +void destroy(pulseaudio_manager *self) { + cleanup(self); + + if (self) { + for (uint32_t i = 0; i < self->device_count; i++) { + free(self->devices[i].name); + free(self->devices[i].description); + } + free(self->devices); + free(self); + } +} diff --git a/v-0.03/easypulse_core.h b/v-0.03/easypulse_core.h new file mode 100644 index 0000000..9e39c64 --- /dev/null +++ b/v-0.03/easypulse_core.h @@ -0,0 +1,81 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include +#include +#include + + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; + +/** + * @brief Represents a PulseAudio devices. + */ +struct pulseaudio_device { + uint32_t index; ///< Index of the devices. + char *name; ///< Name of the devices. + char *description; ///< Description of the devices. + pa_cvolume volume; ///< Volume of the devices. + pa_channel_map channel_map; ///< Channel map of the devices. + int mute; ///< Mute status of the devices (1 for muted, 0 for unmuted). + int number_of_channels; //The number of channels of the devices. +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; ///< Mainloop for PulseAudio operations. + pa_context *context; ///< PulseAudio context. + pulseaudio_device *devices; ///< Array of available devices. + uint32_t device_count; ///< Count of available devices. + int pa_ready; ///< Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; ///< Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + int active_device_index; // The active device index, i.e, the devices being used for playback. + char *active_device_name; // The name of the active device. + uint32_t current_device_index; // The devices being processed right now by the program. It's not necessarily the same as the playback device. + + + bool (*initialize)(pulseaudio_manager *self); ///< Function to initialize the manager. + bool (*load_devices)(pulseaudio_manager *self); ///< Function to load available devices. + bool (*switch_device)(pulseaudio_manager *self, uint32_t devices_index); ///< Function to switch to a specified devics. + bool (*set_volume)(pulseaudio_manager *self, uint32_t devices_index, float percentage); // Function to set the volume to a specified percentage. + void (*get_active_device)(pulseaudio_manager *manager); // Function to set the volume to a specified percentage. + void (*iterate)(pulseaudio_manager *manager, pa_operation *op); //Functions to go through every step of a threaded loop. + void (*destroy)(pulseaudio_manager *manager); + int (*get_device_channels) (const pulseaudio_device *device, int device_index); //Function to get the number of channels of a given device. + pulseaudio_device (*get_device_list)(pulseaudio_manager *self); //Function to get a list of available device. + +}; + +/** + * @brief Create a new pulseaudio_manager instance. + * @return A pointer to the newly created pulseaudio_manager instance. + */ +pulseaudio_manager* new_manager(void); + +/** + * @brief Free the memory associated with a pulseaudio_manager instance. + * @param manager The pulseaudio_manager instance to delete. + */ +void get_active_device(pulseaudio_manager *manager); +bool set_volume(pulseaudio_manager *self, uint32_t devices_index, float percentage); +void iterate(pulseaudio_manager *manager, pa_operation *op); +int get_device_channels(const pulseaudio_device *devices, int devices_index); +void destroy(pulseaudio_manager *manager); +pulseaudio_device* get_device_list(pulseaudio_manager *self); + +#endif // CORE_H diff --git a/v-0.03/easypulse_core.o b/v-0.03/easypulse_core.o new file mode 100644 index 0000000..cf9bb79 Binary files /dev/null and b/v-0.03/easypulse_core.o differ diff --git a/v-0.03/examples/Makefile b/v-0.03/examples/Makefile new file mode 100644 index 0000000..1543a8e --- /dev/null +++ b/v-0.03/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)easypulse_core.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.03/examples/error.txt b/v-0.03/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.03/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.03/examples/switch-sink b/v-0.03/examples/switch-sink new file mode 100755 index 0000000..f440576 Binary files /dev/null and b/v-0.03/examples/switch-sink differ diff --git a/current/examples/switch-sink-pulseaudio b/v-0.03/examples/switch-sink-pulseaudio old mode 100644 new mode 100755 similarity index 61% rename from current/examples/switch-sink-pulseaudio rename to v-0.03/examples/switch-sink-pulseaudio index f56f46c..bb7daa8 Binary files a/current/examples/switch-sink-pulseaudio and b/v-0.03/examples/switch-sink-pulseaudio differ diff --git a/v-0.03/examples/switch-sink-pulseaudio.c b/v-0.03/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.03/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.03/examples/switch-sink.c b/v-0.03/examples/switch-sink.c new file mode 100644 index 0000000..2859641 --- /dev/null +++ b/v-0.03/examples/switch-sink.c @@ -0,0 +1,70 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + if (!manager->load_devices(manager)) { + fprintf(stderr, "Failed to load devices.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (int i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].name, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + int choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + //Get active device. + manager->get_active_device(manager); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device_name); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} diff --git a/v-0.03/examples/volume-change b/v-0.03/examples/volume-change new file mode 100755 index 0000000..f1909a8 Binary files /dev/null and b/v-0.03/examples/volume-change differ diff --git a/current/examples/volume-change-pulseaudio b/v-0.03/examples/volume-change-pulseaudio old mode 100644 new mode 100755 similarity index 55% rename from current/examples/volume-change-pulseaudio rename to v-0.03/examples/volume-change-pulseaudio index ea852f3..906d1b4 Binary files a/current/examples/volume-change-pulseaudio and b/v-0.03/examples/volume-change-pulseaudio differ diff --git a/v-0.03/examples/volume-change-pulseaudio.c b/v-0.03/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.03/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.03/examples/volume-change.c b/v-0.03/examples/volume-change.c new file mode 100644 index 0000000..ec00934 --- /dev/null +++ b/v-0.03/examples/volume-change.c @@ -0,0 +1,80 @@ +/** + * @file volume_app.c + * @brief PulseAudio Volume Control Application + * + * This application provides a simple interface to interact with PulseAudio. + * It displays the current active device, its name, and the master volume. + * The user can then input a new volume value, which the application attempts + * to set. The result of the operation (success or failure) is displayed to the user. + * + * The application leverages the EasyPulse library to communicate with PulseAudio. + * + * + * @date 10-08-2023 (creation date) + */ + +#include +#include +#include "../easypulse_core.h" + +int main() { + // Initialize the pulseaudio manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Load available devices + if (!manager->load_devices(manager)) { + fprintf(stderr, "Failed to load devices.\n"); + manager->destroy(self); + return 1; + } + + // Get the active device + manager->get_active_device(self); + + // Display the active device and its master volume + uint32_t active_device_index = manager->active_device_index; + uint32_t total_channels = manager->get_device_channels(self->devices, self->active_device_index); + + printf("DEBUG, total_channels is: %lu\n", (long unsigned int) total_channels); + + + printf("Current Sink: %s\n", manager->active_device_name); + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume before change: %f%%\n", + channel, + (100.0 * manager->devices[active_device_index].volume.values[channel] / PA_VOLUME_NORM)); + } + + printf("Master Volume: %f%%\n", + (100.0 * pa_cvolume_avg(&manager->devices[active_device_index].volume) / PA_VOLUME_NORM)); + + + // Ask the user for a new volume value + float new_volume; + printf("Enter a new volume value (0.0 - 100.0): "); + scanf("%f", &new_volume); + + // Set the new volume + manager->set_volume(manager, manager->active_device_index, new_volume); + + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume after change: %f%%\n", + channel, + (100.0 * manager->devices[active_device_index].volume.values[channel] / PA_VOLUME_NORM)); + } + + // Cleanup and exit + manager->destroy(self); + return 0; +} diff --git a/v-0.03/libeasypulse_core.a b/v-0.03/libeasypulse_core.a new file mode 100644 index 0000000..f50db60 Binary files /dev/null and b/v-0.03/libeasypulse_core.a differ diff --git a/v-0.04/Makefile b/v-0.04/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.04/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.04/documentation/pa_context -- interface overview.docx b/v-0.04/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.04/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.04/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.04/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.04/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.04/documentation/pulseaudio/introspect.c summary b/v-0.04/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.04/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.04/documentation/pulseaudio/mainloop code flow.txt b/v-0.04/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.04/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.04/easypulse_core.c b/v-0.04/easypulse_core.c new file mode 100644 index 0000000..718aff0 --- /dev/null +++ b/v-0.04/easypulse_core.c @@ -0,0 +1,843 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include +#include +#include +#include +#include +#include +#include + +//Variables shared for count_profile function and its callback. +typedef struct { + pulseaudio_manager *manager; + pulseaudio_device *device; + uint32_t profile_count; +} _shared_vars_1; + +static _shared_vars_1 shared_vars_1; + +/** + * @brief Callback function to check the state of the PulseAudio context. + * @param c The PulseAudio context. + * @param userdata User-provided data (expected to be a pointer to an int indicating readiness). + */ +static void context_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +bool initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, context_state_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + + +/** + * @brief Cleans up the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +static bool cleanup(pulseaudio_manager *self) { + // Lock the mainloop before making changes to the context + pa_threaded_mainloop_lock(self->mainloop); + + // Disconnect the context + pa_context_disconnect(self->context); + + // Unlock the mainloop before stopping it + pa_threaded_mainloop_unlock(self->mainloop); + + // Stop the threaded mainloop + pa_threaded_mainloop_stop(self->mainloop); + + // Unreference the context + pa_context_unref(self->context); + + // Free the threaded mainloop + pa_threaded_mainloop_free(self->mainloop); + + return true; +} + + +/** + * @brief Nested callback to load sound card profiles for a device. + * + * This function is triggered after invoking pa_context_get_card_info_by_index() + * to fetch details about a specific card. It is responsible for allocating memory + * for the profiles associated with the card and populating them with the relevant details. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the pa_card_info structure containing details about the card. + * @param eol Indicates the end of the list. If eol > 0, the list has ended or no more data is available. + * @param userdata User-provided data, expected to be a pointer to the profiles field of a pulseaudio_device structure. + */ +static void load_devices_cb_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + // Cast userdata to a double pointer to pulseaudio_profile + pulseaudio_profile **profiles_ptr = (pulseaudio_profile **) userdata; + + // If eol > 0, the list has ended or no more data is available + if (eol > 0) { + return; + } + + // Allocate memory for device profiles based on the number of profiles available for the card + *profiles_ptr = malloc(i->n_profiles * sizeof(pulseaudio_profile)); + if (!*profiles_ptr) { + fprintf(stderr, "[ERROR]: Failed to allocate memory for profiles.\n"); + return; + } + + // Populate the allocated memory with the profile details + for (uint32_t j = 0; j < i->n_profiles; j++) { + (*profiles_ptr)[j].name = strdup(i->profiles[j].name); + (*profiles_ptr)[j].description = strdup(i->profiles[j].description); + } +} + +/** + * @brief Callback function for retrieving device information. + * @param c The PulseAudio context. + * @param i The device information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void load_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; +#if 0 + //Variable to check if devices need to be reloaded. + static bool reload_devices = false; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + pulseaudio_device *devices = NULL; + pulseaudio_profile *profiles = NULL; + + if(manager->devices) { + devices = manager->devices; + if(manager->devices->profiles) profiles = manager->devices->profiles; + } + + if(reload_devices == true) { + for(uint32_t i = 0; i < manager->device_count; ++i) { + for(uint32_t j = 0 j > profiles-> + } + } + + manager->devices = (pulseaudio_device *)malloc(manager->device_count * sizeof(pulseaudio_device)); + + if (!manager->devices) { + free(manager); + return NULL; + } +#endif + +#if 0 + //TODO: rewrite this so that this is called every time load_devices has been called again. + //We need to detect that. + //size_t newSize = (manager->device_count) * sizeof(pulseaudio_device); + //pulseaudio_device *new_devices = realloc(manager->devices, newSize); + + /*if (!new_devices) { + fprintf(stderr, "[ERROR]: Failed to resize the devices array.\n"); + free(manager->devices); // Free old memory + manager->devices = NULL; + manager->device_count = 0; // Reset device_count to prevent out-of-bounds access + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + manager->devices = new_devices; */ + + if (eol < 0) { + if (pa_context_errno(c) != PA_ERR_NOENTITY) + fprintf(stderr, "Sink callback failure\\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + if (eol > 0) { + manager->devices_loaded = 1; + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Store the device's information + pulseaudio_device device; + device.index = i->index; + device.code = strdup(i->name); + device.description = strdup(i->description); + device.volume = i->volume; + device.channel_map = i->channel_map; + device.mute = i->mute; + + //Pulseaudio operation to fetch card profiles. + //pa_operation *profile_op = pa_context_get_card_info_by_index(manager->context,device.index,load_devices_cb_cb,&i->profiles); + //iterate(manager, profile_op); + + // Add the device to the manager's list + manager->devices[manager->device_count++] = device; + + pa_threaded_mainloop_signal(manager->mainloop, 0); + #endif +} + +/** + * @brief Load available sound cards (devices). + * @param self The pulseaudio_manager instance. + * @return true on success, false otherwise. + */ +bool load_devices(pulseaudio_manager *self) { + pa_operation *data_op; + + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + iterate(self, data_op); + + #if 0 + pa_threaded_mainloop_lock(self->mainloop); + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + pa_threaded_mainloop_wait(self->mainloop); + + // Once all devices are loaded, loads the active device. + // get_active_device(self); + + pa_threaded_mainloop_unlock(self->mainloop); + pa_operation_unref(data_op); + #endif + return true; +} + + +/** + * @brief Callback function handling the completion of the "unmute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { + (void)c; // Suppress unused parameter warning + + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + manager->operations_pending--; + + if (!success) { + fprintf(stderr, "Failed to unmute the device input.\n"); + } + + // Signal the mainloop to resume any waiting threads. + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback function handling the completion of the "move" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (success) { + pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_device_index, 0, operation_complete_unmute_cb, manager); + pa_operation_unref(unmute_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function handling the completion of the "mute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + uint32_t target_device_index = manager->current_device_index; + + if (success) { + pa_operation* move_op = pa_context_move_sink_input_by_index(c, manager->current_device_index, target_device_index, operation_complete_move_cb, manager); + pa_operation_unref(move_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function to handle each device input. + * @param c The PulseAudio context. + * @param i The device input information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to the target device index). + */ +static void switch_device_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (!eol && i) { + // Move this device input to the desired device + pa_operation* move_op = pa_context_move_sink_input_by_index(c, i->index, manager->devices[manager->current_device_index].index, NULL, NULL); + pa_operation_unref(move_op); + } + + if (eol) { + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Switches the device (audio source) for the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to switch to. + * @return Boolean indicating success or failure. + */ +bool switch_device(pulseaudio_manager *self, uint32_t device_index) { + // Ensure the context is valid + if (!self || !self->context) { + return false; + } + + // Check if device_index is out of bounds + if (device_index >= self->device_count) { + fprintf(stderr, "[ERROR]: device_index out of bounds.\n"); + return false; + } + + self->current_device_index = device_index; + + // Set the desired device as the default device + /*fprintf(stderr, "[DEBUG]: self->context = %p\n", self->context); + fprintf(stderr, "[DEBUG]: self->devices = %p\n", self->devices); + fprintf(stderr, "[DEBUG]: device_index = %d\n", device_index);*/ + + if (self->devices) { + // Check if the name attribute is NULL + if (!self->devices[device_index].code) { + fprintf(stderr, "[ERROR]: Sink's name is NULL.\n"); + return false; + } + //fprintf(stderr, "[DEBUG]: self->devices[device_index].code = %s\n", self->devices[device_index].code); + } + pa_operation* set_default_op = pa_context_set_default_sink(self->context, self->devices[device_index].code, NULL, NULL); + pa_operation_unref(set_default_op); + + // Use the introspect API to get a list of all device inputs + pa_operation *op = pa_context_get_sink_input_info_list(self->context, switch_device_cb, self); + + if (!op) { + return false; + } + + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + iterate(self, op); + } + + pa_operation_unref(op); + + return true; +} + + +/** + * @brief Process PulseAudio profiles using a null sink. + * + * This function initializes a null sink, iterates through all `pulseaudio_profile`s, + * retrieves the number of channels from the device's channel map, assigns it to the + * profile's `channels` attribute, and finally unloads the null sink. + * + * @param manager A pointer to the `pulseaudio_manager` structure. + * + * @note This function uses the `iterate` method from `pulseaudio_manager` to wait for PulseAudio operations to complete. + * + * @warning Ensure that the `pulseaudio_manager` is properly initialized before calling this function. + */ +void get_profile_channels(pulseaudio_manager *manager) { + uint32_t module_index = PA_INVALID_INDEX; + + // Static variable to track if the mainloop is locked + static int is_mainloop_locked = 0; + + // Check if the threaded mainloop is locked + if (!is_mainloop_locked) { + pa_threaded_mainloop_lock(manager->mainloop); + is_mainloop_locked = 1; + } + + // Callback to capture the module index when loading the null sink module + void load_module_callback(pa_context *c, uint32_t idx, void *userdata) { + (void) c; + (void) userdata; + module_index = idx; + } + + // 1. Initialize a Null Sink + pa_operation *load_op = pa_context_load_module(manager->context, "module-null-sink", "sink_name=easy_pulse_null_sink", load_module_callback, NULL); + if (!load_op) { + fprintf(stderr, "Failed to load null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete and for the callback to capture the module index + iterate(manager, load_op); + + // 2. Iterate through Profiles and Extract Channel Map + for (uint32_t i = 0; i < manager->device_count; i++) { + pulseaudio_device *device = &(manager->devices[i]); + for (uint32_t j = 0; j < device->profile_count; j++) { + pulseaudio_profile *profile = &(device->profiles[j]); + + // Extract the number of channels from the device's channel map + int num_channels = device->channel_map.channels; + + // Update the pulseaudio_profile with the number of channels + profile->channels = num_channels; + } + } + + // 3. Unload the Null Sink using the captured module index + if (module_index != PA_INVALID_INDEX) { + pa_operation *unload_op = pa_context_unload_module(manager->context, module_index, NULL, NULL); + if (!unload_op) { + fprintf(stderr, "Failed to unload null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete + iterate(manager, unload_op); + } else { + fprintf(stderr, "Invalid module index for null sink\n"); + } + + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } +} + + +/** + * @brief Retrieves the number of channels for a specified device. + * + * @param devices Pointer to an array of PulseSink structures. + * @param device_index Index of the device whose number of channels is to be retrieved. + * + * @return Number of channels for the specified device. Returns -1 on error. + */ +int get_active_profile_channels(const pulseaudio_device *devices, int device_index) { + if (!devices || device_index < 0) { + fprintf(stderr, "[ERROR]: Invalid devices array or device index in get_device_channels.\n"); + return -1; // Return -1 or another indicator of failure + } + return devices[device_index].channel_map.channels; +} + + + +/** + * @brief Callback function belonging to set_volume. Triggers when audio volume is set. + * + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb1(pa_context *c, int success, void *userdata) { + (void) c; + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + + if (!success) { + fprintf(stderr, "[ERROR]: Failed to set volume. Reason: %s\n", pa_strerror(pa_context_errno(c))); + } + + // Debug: Print cvolume values for each channel as percentages + //pa_cvolume cvolume = manager->devices[manager->active_device_index].volume; + + /*for (int i = 0; i < cvolume.channels; i++) { + float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; + //printf("[DEBUG, volume_set_complete_cb()]: Channel %d cvolume value: %u (%.2f%%)\n", i, cvolume.values[i], percentage); + } + //printf("[DEBUG, volume_set_complete_cb invoked. Success: %d\n", success);*/ + + // Decrease the operations count and potentially signal the condition variable. + manager->operations_pending--; + + // Signal the main loop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Callback function belonging to set_volume. + * Triggers after audio volume levels are updated. + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb2(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + pulseaudio_manager *self = (pulseaudio_manager *)userdata; + + if (eol > 0) { + //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + self->operations_pending--; + return; + } + + if (!i) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for pa_sink_info.\n"); + self->operations_pending--; + return; + } + + if (!i->name) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for device name.\n"); + self->operations_pending--; + return; + } + + printf("[DEBUG, volume_check_cb()]: Processing device info for device: %s\n", i->name); + + // Update the volume values in the manager's devices structure + self->active_device->volume = i->volume; + + // Print the volume for each channel + for (int channel = 0; channel < i->volume.channels; channel++) { + //float volume_percentage = (float)i->volume.values[channel] / PA_VOLUME_NORM * 100.0; + //printf("Channel %d Volume: %.2f%%\n", channel, volume_percentage); + } + + //Signaling to continue. + pa_threaded_mainloop_signal(self->mainloop, 0); +} + + +/** + * @brief Set the volume for a specified device. + * + * This function allows setting the volume for a specific device based on the given percentage. + * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. + * It will adjust the volume for all channels of the device to the desired level. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to set the volume for. + * @param percentage Desired volume level as a percentage. + * @return Boolean indicating success or failure. + */ +bool set_volume(pulseaudio_manager *self, uint32_t device_index, float percentage) { + if (!self || percentage < 0.0f || percentage > 100.0f || device_index >= self->device_count) { + return false; + } + + // Convert percentage to volume + pa_volume_t volume = (percentage / 100.0) * PA_VOLUME_NORM; + if (volume >= PA_VOLUME_NORM) { + volume = PA_VOLUME_NORM - 1; + } + + // Debug: show index and desired volume. + //printf("[DEBUG, set_volume()] Index is: %i\n", device_index); + //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + + // Ensure PulseAudio is ready and devices are loaded + if (self->pa_ready != 1 || self->devices_loaded != 1) { + return false; + } + + // Debug: Show channel volumes before the change. + /*for (int channel = 0; channel < self->devices[device_index].channel_map.channels; channel++) { + printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + channel, + 100.0 * self->devices[device_index].volume.values[channel] / PA_VOLUME_NORM); + }*/ + + // Create a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_init(&cvolume); + cvolume.channels = self->devices[device_index].channel_map.channels; // Manually set channels + pa_cvolume_set(&cvolume, cvolume.channels, volume); + + printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + + // Apply the volume change to the specific device by index and wait for the operation to complete + const char *device_name_to_change = self->devices[device_index].code; + self->operations_pending++; + pa_operation *op = pa_context_set_sink_volume_by_name(self->context, device_name_to_change, &cvolume, set_volume_cb1, self); + iterate(self, op); + + // Fetch the updated volume for the device and wait for the operation to complete + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, device_name_to_change, set_volume_cb2, self); + iterate(self, op2); + + return true; +} + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Create a new pulseaudio_manager instance. + * @return Returns a pointer to a manager structure. + */ +pulseaudio_manager *new_manager(void) { + pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); + if (!manager) return NULL; + + + // Initialize pointers to 0 -- good practice. + manager->device_count = 0; + manager->devices_loaded = 0; // Initialize to 0 + manager->load_devices = load_devices; + manager->destroy = destroy; + manager->switch_device = switch_device; + manager->set_volume = set_volume; + manager->get_active_device = get_active_device; + manager->iterate = iterate; + manager->get_active_profile_channels = get_active_profile_channels; + //manager->get_profiles_for_device = get_profiles_for_device; + manager->get_profile_channels = get_profile_channels; + manager->get_profile_count = get_profile_count; + + if(!initialize(manager)) { + free(manager->devices); + free(manager); + return NULL; + } + + manager->device_count = get_device_count(); + manager->devices = malloc(manager->device_count * sizeof(pulseaudio_manager)); + + if(!manager->devices) { + fprintf(stderr, "[new_manager()] Could not allocate memory for the manager devices.\n"); + return NULL; + } + +#if 0 + if (!load_devices(manager)) { + fprintf(stderr, "No audio devices were detected. Aborting.\n"); + destroy(manager); + return NULL; + } +#endif + + return manager; +} + +/** + * @brief Callback function to process the PulseAudio server information. + * + * This callback is invoked once the server information is available. It retrieves the default device name + * from the server response and determines the active device by matching the name with the available devices + * in the manager's list. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the pa_server_info structure containing the server's information. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void get_active_device_cb(pa_context *c, const pa_server_info *info, void *userdata) { + (void) c; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + if (!info || !info->default_sink_name) { + fprintf(stderr, "[ERROR]: Null pointer in active_device_cb.\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Get the default device name from the server information + const char *default_device_name = info->default_sink_name; + + // Iterate over the available devices to find the active one + for (uint32_t i = 0; i < manager->device_count; i++) { + if (strcmp(manager->devices[i].code, default_device_name) == 0) { + // Set the active device pointer when a match is found + manager->active_device = &manager->devices[i]; + break; + } + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Request the default device name from the PulseAudio server and determine the active device. + * + * This function initiates a request to retrieve the default device name (active device) from the PulseAudio server. + * Once the server provides this information, the active_device_cb callback is triggered to process the response. + * + * @param manager Pointer to the pulseaudio_manager instance. + */ +//TODO: REWORK THE WAITING LOGIC. +void get_active_device(pulseaudio_manager *manager) { + pa_threaded_mainloop_lock(manager->mainloop); + + // Request server information + pa_context_get_server_info(manager->context, get_active_device_cb, manager); + + // Wait until the iterate function signals that it's done + pa_threaded_mainloop_wait(manager->mainloop); + + pa_threaded_mainloop_unlock(manager->mainloop); + + // The original loop to wait for active_device_name to be set + int timeout = 50; // Number of iterations or timeout value + while (!manager->active_device->code && timeout-- > 0) { + manager->iterate(manager, NULL); + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + + +/** + * @brief Frees the memory of a pulseaudio_manager instance. + * + * @param manager Pointer to the pulseaudio_manager instance to be deleted. + */ +void destroy(pulseaudio_manager *self) { + cleanup(self); + + if (self) { + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].code) { + free(self->devices[i].code); + self->devices[i].code = NULL; + } + if (self->devices[i].description) { + free(self->devices[i].description); + self->devices[i].description = NULL; + } + } + // Now, free profiles for each device (if they exist) + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].profiles) free(self->devices[i].profiles); + } + if (self->devices) free(self->devices); + free(self); + } +} diff --git a/v-0.04/easypulse_core.h b/v-0.04/easypulse_core.h new file mode 100644 index 0000000..9129942 --- /dev/null +++ b/v-0.04/easypulse_core.h @@ -0,0 +1,90 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; + +/** + * @brief Represents a PulseAudio devices. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Name of the device. + char *description; // Description of the device. + pa_cvolume volume; // Volume of the device. + /* pulseaudio_profile *active_profile; // Active alsa profile of this device. */ + pa_channel_map channel_map; // Channel map of the devices. + int mute; // Mute status of the devices (1 for muted, 0 for unmuted). + int min_play_channels; // The minimum number of playback channels of the device. + int max_play_channels; // The maximum number of playback channels of the device. + pulseaudio_profile *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *devices; // Array of available devices. + uint32_t device_count; // Count of available devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + pulseaudio_device *active_device; // Pointer to active device. + uint32_t current_device_index; // The devices being processed right now by the program. It's not necessarily the same as the playback device. + + bool (*initialize)(pulseaudio_manager *self); // Initializes the manager. + void (*destroy)(pulseaudio_manager *manager); // Destroys the manager. + bool (*load_devices)(pulseaudio_manager *self); // Loads available devices. + bool (*switch_device)(pulseaudio_manager *self, uint32_t devices_index); // Switches to a specified device. + bool (*set_volume)(pulseaudio_manager *self, uint32_t devices_index, float percentage); // Sets the volume to a specified percentage. + void (*get_active_device)(pulseaudio_manager *manager); // Assures that a pulseaudio operation is not pending. + void (*iterate)(pulseaudio_manager *manager, pa_operation *op); // Goes through every step of a threaded loop. + int (*get_active_profile_channels) (const pulseaudio_device *device, int device_index); // Gets the number of channels of an active profile. + //void (*get_profiles_for_device)(pulseaudio_manager *manager, const char* device_code); // Updates the profiles of a particular device. + void (*get_profile_channels)(pulseaudio_manager *manager); // Extracts profile channels by copying them to null sink. + uint32_t (*get_profile_count)(uint32_t card_index); // Gets the number of pulseaudio profiles in the system. +}; + +/** + * @brief Create a new pulseaudio_manager instance. + * @return A pointer to the newly created pulseaudio_manager instance. + */ +pulseaudio_manager* new_manager(void); + +bool load_devices(pulseaudio_manager *self); +int get_active_profile_channels(const pulseaudio_device *devices, int device_index); +bool set_volume(pulseaudio_manager *self, uint32_t devices_index, float percentage); +void iterate(pulseaudio_manager *manager, pa_operation *op); +int get_device_channels(const pulseaudio_device *devices, int devices_index); +void destroy(pulseaudio_manager *manager); +//void get_profiles_for_device(pulseaudio_manager *manager, const char* device_code); +void get_active_device(pulseaudio_manager *manager); +void get_profile_channels(pulseaudio_manager *manager); + +#endif // CORE_H diff --git a/v-0.04/examples/Makefile b/v-0.04/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.04/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.04/examples/alsa-mapper_pulseaudio-api b/v-0.04/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..4c41ccc Binary files /dev/null and b/v-0.04/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.04/examples/alsa-mapper_pulseaudio-api.c b/v-0.04/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.04/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.04/examples/change-speaker-mode b/v-0.04/examples/change-speaker-mode new file mode 100755 index 0000000..4ec86bb Binary files /dev/null and b/v-0.04/examples/change-speaker-mode differ diff --git a/v-0.04/examples/change-speaker-mode.c b/v-0.04/examples/change-speaker-mode.c new file mode 100644 index 0000000..64fa962 --- /dev/null +++ b/v-0.04/examples/change-speaker-mode.c @@ -0,0 +1,88 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + #if 0 + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + #endif +} diff --git a/v-0.04/examples/error.txt b/v-0.04/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.04/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.04/examples/get-card-profiles-pulseaudio_api b/v-0.04/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..21c05f7 Binary files /dev/null and b/v-0.04/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.04/examples/get-card-profiles-pulseaudio_api.c b/v-0.04/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..a068814 --- /dev/null +++ b/v-0.04/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_sinks(total_devices); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_name(sink_info[i]->name); + const char *alsa_id = get_alsa_id(sink_info[i]->name); + + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + + // Get and display the minimum and maximum channels + int min_channels = get_min_channels(alsa_id); + int max_channels = get_max_channels(alsa_id); + + printf(" - Minimum channels: %d\n", min_channels); + printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } else { + printf(" - No corresponding ALSA name found.\n"); + } + } + + free(sink_info); + return 0; +} + diff --git a/v-0.04/examples/switch-sink b/v-0.04/examples/switch-sink new file mode 100755 index 0000000..cdd6d77 Binary files /dev/null and b/v-0.04/examples/switch-sink differ diff --git a/v-0.04/examples/switch-sink-pulseaudio b/v-0.04/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..f954d5d Binary files /dev/null and b/v-0.04/examples/switch-sink-pulseaudio differ diff --git a/v-0.04/examples/switch-sink-pulseaudio.c b/v-0.04/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.04/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.04/examples/switch-sink.c b/v-0.04/examples/switch-sink.c new file mode 100644 index 0000000..45a8739 --- /dev/null +++ b/v-0.04/examples/switch-sink.c @@ -0,0 +1,61 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} diff --git a/v-0.04/examples/volume-change b/v-0.04/examples/volume-change new file mode 100755 index 0000000..0517bc7 Binary files /dev/null and b/v-0.04/examples/volume-change differ diff --git a/v-0.04/examples/volume-change-pulseaudio b/v-0.04/examples/volume-change-pulseaudio new file mode 100755 index 0000000..8e32da0 Binary files /dev/null and b/v-0.04/examples/volume-change-pulseaudio differ diff --git a/v-0.04/examples/volume-change-pulseaudio.c b/v-0.04/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.04/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.04/examples/volume-change.c b/v-0.04/examples/volume-change.c new file mode 100644 index 0000000..e15f0eb --- /dev/null +++ b/v-0.04/examples/volume-change.c @@ -0,0 +1,82 @@ +/** + * @file volume_app.c + * @brief PulseAudio Volume Control Application + * + * This application provides a simple interface to interact with PulseAudio. + * It displays the current active device, its name, and the master volume. + * The user can then input a new volume value, which the application attempts + * to set. The result of the operation (success or failure) is displayed to the user. + * + * The application leverages the EasyPulse library to communicate with PulseAudio. + * + * + * @date 10-08-2023 (creation date) + */ + +#include +#include +#include "../easypulse_core.h" + +int main() { + // Initialize the pulseaudio manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + pulseaudio_device *active_device = self->active_device; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + +#if 0 + // Display the channels of the active profile + uint32_t total_channels = manager->get_active_profile_channels(active_device, active_device->index); + + printf("[DEBUG] The active device is: %s\n", active_device->code); + printf("[DEBUG] The following profiles were found:\n"); + + for (uint32_t i = 0; i < active_device->profile_count; i++) { + if (active_device->profiles[i].name) { + printf("[DEBUG] Profile name: %s\n", active_device->profiles[i].name); + } else { + printf("[DEBUG] Profile name is NULL or invalid.\n"); + } + printf("[DEBUG] This profile has %d channels.\n", active_device->profiles[i].channels); + } + + + printf("Current device: %s\n", manager->active_device->code); + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume before change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } + + printf("Master Volume: %f%%\n", + (100.0 * pa_cvolume_avg(&manager->active_device->volume) / PA_VOLUME_NORM)); + + + // Ask the user for a new volume value + float new_volume; + printf("Enter a new volume value (0.0 - 100.0): "); + scanf("%f", &new_volume); + + // Set the new volume + manager->set_volume(manager, manager->active_device->index, new_volume); + + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume after change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } +#endif + // Cleanup and exit + manager->destroy(self); + return 0; + +} diff --git a/v-0.04/libeasypulse_core.a b/v-0.04/libeasypulse_core.a new file mode 100644 index 0000000..4f0fd25 Binary files /dev/null and b/v-0.04/libeasypulse_core.a differ diff --git a/v-0.04/system_query.c b/v-0.04/system_query.c new file mode 100644 index 0000000..ad581eb --- /dev/null +++ b/v-0.04/system_query.c @@ -0,0 +1,726 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + const char *alsa_name; + const char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_sinks its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(shared_data_1.mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + } + +} + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_sinks_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_sinks(uint32_t card_index) { + pa_operation *op = NULL; + + // Using get_device_count() to obtain the number of sinks + uint32_t max_sinks = get_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_sinks(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_sinks_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + + + +/** + * @brief Frees the dynamically allocated memory for the sink list. + * + * This function is used to free the memory allocated for the sink list + * after it's no longer needed. This ensures that there are no memory leaks. + * + * @param data Pointer to the sink list data structure. + */ +static void free_sink_list(void) { + if (shared_data_3.sinks) { + free(shared_data_3.sinks); + } +} + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + fprintf(stderr,"[DEBUG, get_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + fprintf(stderr,"[DEBUG, get_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_channels(const char *alsa_id) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels; + int err; + + if (!alsa_id) { + fprintf(stderr, "Invalid ALSA device ID provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return -1; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set additional hardware parameters here if needed + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_channels(const char *alsa_id) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels; + int err; + + if (!alsa_id) { + fprintf(stderr, "Invalid ALSA device ID provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return -1; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set additional hardware parameters here if needed + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + snd_pcm_close(handle); + return min_channels; +} + +static void free_alsa_name(void) { + if (shared_data_2.alsa_name) { + free((char*)shared_data_2.alsa_name); + shared_data_2.alsa_name = NULL; + } +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +const char* get_alsa_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +#include // Include this for the isdigit() function + +static void get_alsa_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + fprintf(stderr, "ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +const char* get_alsa_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + diff --git a/v-0.04/system_query.h b/v-0.04/system_query.h new file mode 100644 index 0000000..1094c37 --- /dev/null +++ b/v-0.04/system_query.h @@ -0,0 +1,20 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + + + +uint32_t get_device_count(void); //Gets the number of devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_sinks(uint32_t card_index); //Gets the total available sinks for this system. +const char* get_alsa_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio card. +int get_max_channels(const char *alsa_id); //Gets the maximum channels an ALSA card supports. +int get_min_channels(const char *alsa_id); //Gets the maximum channels an ALSA card supports. +const char* get_alsa_id(const char *sink_name); //Gets the alsa id based on the pulseaudio channel name. + +#endif diff --git a/v-0.05/Makefile b/v-0.05/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.05/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.05/documentation/pa_context -- interface overview.docx b/v-0.05/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.05/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.05/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.05/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.05/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.05/documentation/pulseaudio/introspect.c summary b/v-0.05/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.05/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.05/documentation/pulseaudio/mainloop code flow.txt b/v-0.05/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.05/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.05/easypulse_core.c b/v-0.05/easypulse_core.c new file mode 100644 index 0000000..718aff0 --- /dev/null +++ b/v-0.05/easypulse_core.c @@ -0,0 +1,843 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include +#include +#include +#include +#include +#include +#include + +//Variables shared for count_profile function and its callback. +typedef struct { + pulseaudio_manager *manager; + pulseaudio_device *device; + uint32_t profile_count; +} _shared_vars_1; + +static _shared_vars_1 shared_vars_1; + +/** + * @brief Callback function to check the state of the PulseAudio context. + * @param c The PulseAudio context. + * @param userdata User-provided data (expected to be a pointer to an int indicating readiness). + */ +static void context_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +bool initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, context_state_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + + +/** + * @brief Cleans up the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +static bool cleanup(pulseaudio_manager *self) { + // Lock the mainloop before making changes to the context + pa_threaded_mainloop_lock(self->mainloop); + + // Disconnect the context + pa_context_disconnect(self->context); + + // Unlock the mainloop before stopping it + pa_threaded_mainloop_unlock(self->mainloop); + + // Stop the threaded mainloop + pa_threaded_mainloop_stop(self->mainloop); + + // Unreference the context + pa_context_unref(self->context); + + // Free the threaded mainloop + pa_threaded_mainloop_free(self->mainloop); + + return true; +} + + +/** + * @brief Nested callback to load sound card profiles for a device. + * + * This function is triggered after invoking pa_context_get_card_info_by_index() + * to fetch details about a specific card. It is responsible for allocating memory + * for the profiles associated with the card and populating them with the relevant details. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the pa_card_info structure containing details about the card. + * @param eol Indicates the end of the list. If eol > 0, the list has ended or no more data is available. + * @param userdata User-provided data, expected to be a pointer to the profiles field of a pulseaudio_device structure. + */ +static void load_devices_cb_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + // Cast userdata to a double pointer to pulseaudio_profile + pulseaudio_profile **profiles_ptr = (pulseaudio_profile **) userdata; + + // If eol > 0, the list has ended or no more data is available + if (eol > 0) { + return; + } + + // Allocate memory for device profiles based on the number of profiles available for the card + *profiles_ptr = malloc(i->n_profiles * sizeof(pulseaudio_profile)); + if (!*profiles_ptr) { + fprintf(stderr, "[ERROR]: Failed to allocate memory for profiles.\n"); + return; + } + + // Populate the allocated memory with the profile details + for (uint32_t j = 0; j < i->n_profiles; j++) { + (*profiles_ptr)[j].name = strdup(i->profiles[j].name); + (*profiles_ptr)[j].description = strdup(i->profiles[j].description); + } +} + +/** + * @brief Callback function for retrieving device information. + * @param c The PulseAudio context. + * @param i The device information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void load_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; +#if 0 + //Variable to check if devices need to be reloaded. + static bool reload_devices = false; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + pulseaudio_device *devices = NULL; + pulseaudio_profile *profiles = NULL; + + if(manager->devices) { + devices = manager->devices; + if(manager->devices->profiles) profiles = manager->devices->profiles; + } + + if(reload_devices == true) { + for(uint32_t i = 0; i < manager->device_count; ++i) { + for(uint32_t j = 0 j > profiles-> + } + } + + manager->devices = (pulseaudio_device *)malloc(manager->device_count * sizeof(pulseaudio_device)); + + if (!manager->devices) { + free(manager); + return NULL; + } +#endif + +#if 0 + //TODO: rewrite this so that this is called every time load_devices has been called again. + //We need to detect that. + //size_t newSize = (manager->device_count) * sizeof(pulseaudio_device); + //pulseaudio_device *new_devices = realloc(manager->devices, newSize); + + /*if (!new_devices) { + fprintf(stderr, "[ERROR]: Failed to resize the devices array.\n"); + free(manager->devices); // Free old memory + manager->devices = NULL; + manager->device_count = 0; // Reset device_count to prevent out-of-bounds access + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + manager->devices = new_devices; */ + + if (eol < 0) { + if (pa_context_errno(c) != PA_ERR_NOENTITY) + fprintf(stderr, "Sink callback failure\\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + if (eol > 0) { + manager->devices_loaded = 1; + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Store the device's information + pulseaudio_device device; + device.index = i->index; + device.code = strdup(i->name); + device.description = strdup(i->description); + device.volume = i->volume; + device.channel_map = i->channel_map; + device.mute = i->mute; + + //Pulseaudio operation to fetch card profiles. + //pa_operation *profile_op = pa_context_get_card_info_by_index(manager->context,device.index,load_devices_cb_cb,&i->profiles); + //iterate(manager, profile_op); + + // Add the device to the manager's list + manager->devices[manager->device_count++] = device; + + pa_threaded_mainloop_signal(manager->mainloop, 0); + #endif +} + +/** + * @brief Load available sound cards (devices). + * @param self The pulseaudio_manager instance. + * @return true on success, false otherwise. + */ +bool load_devices(pulseaudio_manager *self) { + pa_operation *data_op; + + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + iterate(self, data_op); + + #if 0 + pa_threaded_mainloop_lock(self->mainloop); + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + pa_threaded_mainloop_wait(self->mainloop); + + // Once all devices are loaded, loads the active device. + // get_active_device(self); + + pa_threaded_mainloop_unlock(self->mainloop); + pa_operation_unref(data_op); + #endif + return true; +} + + +/** + * @brief Callback function handling the completion of the "unmute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { + (void)c; // Suppress unused parameter warning + + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + manager->operations_pending--; + + if (!success) { + fprintf(stderr, "Failed to unmute the device input.\n"); + } + + // Signal the mainloop to resume any waiting threads. + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback function handling the completion of the "move" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (success) { + pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_device_index, 0, operation_complete_unmute_cb, manager); + pa_operation_unref(unmute_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function handling the completion of the "mute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + uint32_t target_device_index = manager->current_device_index; + + if (success) { + pa_operation* move_op = pa_context_move_sink_input_by_index(c, manager->current_device_index, target_device_index, operation_complete_move_cb, manager); + pa_operation_unref(move_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function to handle each device input. + * @param c The PulseAudio context. + * @param i The device input information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to the target device index). + */ +static void switch_device_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (!eol && i) { + // Move this device input to the desired device + pa_operation* move_op = pa_context_move_sink_input_by_index(c, i->index, manager->devices[manager->current_device_index].index, NULL, NULL); + pa_operation_unref(move_op); + } + + if (eol) { + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Switches the device (audio source) for the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to switch to. + * @return Boolean indicating success or failure. + */ +bool switch_device(pulseaudio_manager *self, uint32_t device_index) { + // Ensure the context is valid + if (!self || !self->context) { + return false; + } + + // Check if device_index is out of bounds + if (device_index >= self->device_count) { + fprintf(stderr, "[ERROR]: device_index out of bounds.\n"); + return false; + } + + self->current_device_index = device_index; + + // Set the desired device as the default device + /*fprintf(stderr, "[DEBUG]: self->context = %p\n", self->context); + fprintf(stderr, "[DEBUG]: self->devices = %p\n", self->devices); + fprintf(stderr, "[DEBUG]: device_index = %d\n", device_index);*/ + + if (self->devices) { + // Check if the name attribute is NULL + if (!self->devices[device_index].code) { + fprintf(stderr, "[ERROR]: Sink's name is NULL.\n"); + return false; + } + //fprintf(stderr, "[DEBUG]: self->devices[device_index].code = %s\n", self->devices[device_index].code); + } + pa_operation* set_default_op = pa_context_set_default_sink(self->context, self->devices[device_index].code, NULL, NULL); + pa_operation_unref(set_default_op); + + // Use the introspect API to get a list of all device inputs + pa_operation *op = pa_context_get_sink_input_info_list(self->context, switch_device_cb, self); + + if (!op) { + return false; + } + + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + iterate(self, op); + } + + pa_operation_unref(op); + + return true; +} + + +/** + * @brief Process PulseAudio profiles using a null sink. + * + * This function initializes a null sink, iterates through all `pulseaudio_profile`s, + * retrieves the number of channels from the device's channel map, assigns it to the + * profile's `channels` attribute, and finally unloads the null sink. + * + * @param manager A pointer to the `pulseaudio_manager` structure. + * + * @note This function uses the `iterate` method from `pulseaudio_manager` to wait for PulseAudio operations to complete. + * + * @warning Ensure that the `pulseaudio_manager` is properly initialized before calling this function. + */ +void get_profile_channels(pulseaudio_manager *manager) { + uint32_t module_index = PA_INVALID_INDEX; + + // Static variable to track if the mainloop is locked + static int is_mainloop_locked = 0; + + // Check if the threaded mainloop is locked + if (!is_mainloop_locked) { + pa_threaded_mainloop_lock(manager->mainloop); + is_mainloop_locked = 1; + } + + // Callback to capture the module index when loading the null sink module + void load_module_callback(pa_context *c, uint32_t idx, void *userdata) { + (void) c; + (void) userdata; + module_index = idx; + } + + // 1. Initialize a Null Sink + pa_operation *load_op = pa_context_load_module(manager->context, "module-null-sink", "sink_name=easy_pulse_null_sink", load_module_callback, NULL); + if (!load_op) { + fprintf(stderr, "Failed to load null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete and for the callback to capture the module index + iterate(manager, load_op); + + // 2. Iterate through Profiles and Extract Channel Map + for (uint32_t i = 0; i < manager->device_count; i++) { + pulseaudio_device *device = &(manager->devices[i]); + for (uint32_t j = 0; j < device->profile_count; j++) { + pulseaudio_profile *profile = &(device->profiles[j]); + + // Extract the number of channels from the device's channel map + int num_channels = device->channel_map.channels; + + // Update the pulseaudio_profile with the number of channels + profile->channels = num_channels; + } + } + + // 3. Unload the Null Sink using the captured module index + if (module_index != PA_INVALID_INDEX) { + pa_operation *unload_op = pa_context_unload_module(manager->context, module_index, NULL, NULL); + if (!unload_op) { + fprintf(stderr, "Failed to unload null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete + iterate(manager, unload_op); + } else { + fprintf(stderr, "Invalid module index for null sink\n"); + } + + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } +} + + +/** + * @brief Retrieves the number of channels for a specified device. + * + * @param devices Pointer to an array of PulseSink structures. + * @param device_index Index of the device whose number of channels is to be retrieved. + * + * @return Number of channels for the specified device. Returns -1 on error. + */ +int get_active_profile_channels(const pulseaudio_device *devices, int device_index) { + if (!devices || device_index < 0) { + fprintf(stderr, "[ERROR]: Invalid devices array or device index in get_device_channels.\n"); + return -1; // Return -1 or another indicator of failure + } + return devices[device_index].channel_map.channels; +} + + + +/** + * @brief Callback function belonging to set_volume. Triggers when audio volume is set. + * + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb1(pa_context *c, int success, void *userdata) { + (void) c; + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + + if (!success) { + fprintf(stderr, "[ERROR]: Failed to set volume. Reason: %s\n", pa_strerror(pa_context_errno(c))); + } + + // Debug: Print cvolume values for each channel as percentages + //pa_cvolume cvolume = manager->devices[manager->active_device_index].volume; + + /*for (int i = 0; i < cvolume.channels; i++) { + float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; + //printf("[DEBUG, volume_set_complete_cb()]: Channel %d cvolume value: %u (%.2f%%)\n", i, cvolume.values[i], percentage); + } + //printf("[DEBUG, volume_set_complete_cb invoked. Success: %d\n", success);*/ + + // Decrease the operations count and potentially signal the condition variable. + manager->operations_pending--; + + // Signal the main loop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Callback function belonging to set_volume. + * Triggers after audio volume levels are updated. + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb2(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + pulseaudio_manager *self = (pulseaudio_manager *)userdata; + + if (eol > 0) { + //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + self->operations_pending--; + return; + } + + if (!i) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for pa_sink_info.\n"); + self->operations_pending--; + return; + } + + if (!i->name) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for device name.\n"); + self->operations_pending--; + return; + } + + printf("[DEBUG, volume_check_cb()]: Processing device info for device: %s\n", i->name); + + // Update the volume values in the manager's devices structure + self->active_device->volume = i->volume; + + // Print the volume for each channel + for (int channel = 0; channel < i->volume.channels; channel++) { + //float volume_percentage = (float)i->volume.values[channel] / PA_VOLUME_NORM * 100.0; + //printf("Channel %d Volume: %.2f%%\n", channel, volume_percentage); + } + + //Signaling to continue. + pa_threaded_mainloop_signal(self->mainloop, 0); +} + + +/** + * @brief Set the volume for a specified device. + * + * This function allows setting the volume for a specific device based on the given percentage. + * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. + * It will adjust the volume for all channels of the device to the desired level. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to set the volume for. + * @param percentage Desired volume level as a percentage. + * @return Boolean indicating success or failure. + */ +bool set_volume(pulseaudio_manager *self, uint32_t device_index, float percentage) { + if (!self || percentage < 0.0f || percentage > 100.0f || device_index >= self->device_count) { + return false; + } + + // Convert percentage to volume + pa_volume_t volume = (percentage / 100.0) * PA_VOLUME_NORM; + if (volume >= PA_VOLUME_NORM) { + volume = PA_VOLUME_NORM - 1; + } + + // Debug: show index and desired volume. + //printf("[DEBUG, set_volume()] Index is: %i\n", device_index); + //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + + // Ensure PulseAudio is ready and devices are loaded + if (self->pa_ready != 1 || self->devices_loaded != 1) { + return false; + } + + // Debug: Show channel volumes before the change. + /*for (int channel = 0; channel < self->devices[device_index].channel_map.channels; channel++) { + printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + channel, + 100.0 * self->devices[device_index].volume.values[channel] / PA_VOLUME_NORM); + }*/ + + // Create a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_init(&cvolume); + cvolume.channels = self->devices[device_index].channel_map.channels; // Manually set channels + pa_cvolume_set(&cvolume, cvolume.channels, volume); + + printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + + // Apply the volume change to the specific device by index and wait for the operation to complete + const char *device_name_to_change = self->devices[device_index].code; + self->operations_pending++; + pa_operation *op = pa_context_set_sink_volume_by_name(self->context, device_name_to_change, &cvolume, set_volume_cb1, self); + iterate(self, op); + + // Fetch the updated volume for the device and wait for the operation to complete + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, device_name_to_change, set_volume_cb2, self); + iterate(self, op2); + + return true; +} + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Create a new pulseaudio_manager instance. + * @return Returns a pointer to a manager structure. + */ +pulseaudio_manager *new_manager(void) { + pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); + if (!manager) return NULL; + + + // Initialize pointers to 0 -- good practice. + manager->device_count = 0; + manager->devices_loaded = 0; // Initialize to 0 + manager->load_devices = load_devices; + manager->destroy = destroy; + manager->switch_device = switch_device; + manager->set_volume = set_volume; + manager->get_active_device = get_active_device; + manager->iterate = iterate; + manager->get_active_profile_channels = get_active_profile_channels; + //manager->get_profiles_for_device = get_profiles_for_device; + manager->get_profile_channels = get_profile_channels; + manager->get_profile_count = get_profile_count; + + if(!initialize(manager)) { + free(manager->devices); + free(manager); + return NULL; + } + + manager->device_count = get_device_count(); + manager->devices = malloc(manager->device_count * sizeof(pulseaudio_manager)); + + if(!manager->devices) { + fprintf(stderr, "[new_manager()] Could not allocate memory for the manager devices.\n"); + return NULL; + } + +#if 0 + if (!load_devices(manager)) { + fprintf(stderr, "No audio devices were detected. Aborting.\n"); + destroy(manager); + return NULL; + } +#endif + + return manager; +} + +/** + * @brief Callback function to process the PulseAudio server information. + * + * This callback is invoked once the server information is available. It retrieves the default device name + * from the server response and determines the active device by matching the name with the available devices + * in the manager's list. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the pa_server_info structure containing the server's information. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void get_active_device_cb(pa_context *c, const pa_server_info *info, void *userdata) { + (void) c; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + if (!info || !info->default_sink_name) { + fprintf(stderr, "[ERROR]: Null pointer in active_device_cb.\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Get the default device name from the server information + const char *default_device_name = info->default_sink_name; + + // Iterate over the available devices to find the active one + for (uint32_t i = 0; i < manager->device_count; i++) { + if (strcmp(manager->devices[i].code, default_device_name) == 0) { + // Set the active device pointer when a match is found + manager->active_device = &manager->devices[i]; + break; + } + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Request the default device name from the PulseAudio server and determine the active device. + * + * This function initiates a request to retrieve the default device name (active device) from the PulseAudio server. + * Once the server provides this information, the active_device_cb callback is triggered to process the response. + * + * @param manager Pointer to the pulseaudio_manager instance. + */ +//TODO: REWORK THE WAITING LOGIC. +void get_active_device(pulseaudio_manager *manager) { + pa_threaded_mainloop_lock(manager->mainloop); + + // Request server information + pa_context_get_server_info(manager->context, get_active_device_cb, manager); + + // Wait until the iterate function signals that it's done + pa_threaded_mainloop_wait(manager->mainloop); + + pa_threaded_mainloop_unlock(manager->mainloop); + + // The original loop to wait for active_device_name to be set + int timeout = 50; // Number of iterations or timeout value + while (!manager->active_device->code && timeout-- > 0) { + manager->iterate(manager, NULL); + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + + +/** + * @brief Frees the memory of a pulseaudio_manager instance. + * + * @param manager Pointer to the pulseaudio_manager instance to be deleted. + */ +void destroy(pulseaudio_manager *self) { + cleanup(self); + + if (self) { + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].code) { + free(self->devices[i].code); + self->devices[i].code = NULL; + } + if (self->devices[i].description) { + free(self->devices[i].description); + self->devices[i].description = NULL; + } + } + // Now, free profiles for each device (if they exist) + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].profiles) free(self->devices[i].profiles); + } + if (self->devices) free(self->devices); + free(self); + } +} diff --git a/v-0.05/easypulse_core.h b/v-0.05/easypulse_core.h new file mode 100644 index 0000000..9129942 --- /dev/null +++ b/v-0.05/easypulse_core.h @@ -0,0 +1,90 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; + +/** + * @brief Represents a PulseAudio devices. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Name of the device. + char *description; // Description of the device. + pa_cvolume volume; // Volume of the device. + /* pulseaudio_profile *active_profile; // Active alsa profile of this device. */ + pa_channel_map channel_map; // Channel map of the devices. + int mute; // Mute status of the devices (1 for muted, 0 for unmuted). + int min_play_channels; // The minimum number of playback channels of the device. + int max_play_channels; // The maximum number of playback channels of the device. + pulseaudio_profile *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *devices; // Array of available devices. + uint32_t device_count; // Count of available devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + pulseaudio_device *active_device; // Pointer to active device. + uint32_t current_device_index; // The devices being processed right now by the program. It's not necessarily the same as the playback device. + + bool (*initialize)(pulseaudio_manager *self); // Initializes the manager. + void (*destroy)(pulseaudio_manager *manager); // Destroys the manager. + bool (*load_devices)(pulseaudio_manager *self); // Loads available devices. + bool (*switch_device)(pulseaudio_manager *self, uint32_t devices_index); // Switches to a specified device. + bool (*set_volume)(pulseaudio_manager *self, uint32_t devices_index, float percentage); // Sets the volume to a specified percentage. + void (*get_active_device)(pulseaudio_manager *manager); // Assures that a pulseaudio operation is not pending. + void (*iterate)(pulseaudio_manager *manager, pa_operation *op); // Goes through every step of a threaded loop. + int (*get_active_profile_channels) (const pulseaudio_device *device, int device_index); // Gets the number of channels of an active profile. + //void (*get_profiles_for_device)(pulseaudio_manager *manager, const char* device_code); // Updates the profiles of a particular device. + void (*get_profile_channels)(pulseaudio_manager *manager); // Extracts profile channels by copying them to null sink. + uint32_t (*get_profile_count)(uint32_t card_index); // Gets the number of pulseaudio profiles in the system. +}; + +/** + * @brief Create a new pulseaudio_manager instance. + * @return A pointer to the newly created pulseaudio_manager instance. + */ +pulseaudio_manager* new_manager(void); + +bool load_devices(pulseaudio_manager *self); +int get_active_profile_channels(const pulseaudio_device *devices, int device_index); +bool set_volume(pulseaudio_manager *self, uint32_t devices_index, float percentage); +void iterate(pulseaudio_manager *manager, pa_operation *op); +int get_device_channels(const pulseaudio_device *devices, int devices_index); +void destroy(pulseaudio_manager *manager); +//void get_profiles_for_device(pulseaudio_manager *manager, const char* device_code); +void get_active_device(pulseaudio_manager *manager); +void get_profile_channels(pulseaudio_manager *manager); + +#endif // CORE_H diff --git a/v-0.05/examples/Makefile b/v-0.05/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.05/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.05/examples/alsa-mapper_pulseaudio-api b/v-0.05/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..caa8a22 Binary files /dev/null and b/v-0.05/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.05/examples/alsa-mapper_pulseaudio-api.c b/v-0.05/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.05/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.05/examples/change-speaker-mode b/v-0.05/examples/change-speaker-mode new file mode 100755 index 0000000..1c38674 Binary files /dev/null and b/v-0.05/examples/change-speaker-mode differ diff --git a/v-0.05/examples/change-speaker-mode.c b/v-0.05/examples/change-speaker-mode.c new file mode 100644 index 0000000..64fa962 --- /dev/null +++ b/v-0.05/examples/change-speaker-mode.c @@ -0,0 +1,88 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + #if 0 + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + #endif +} diff --git a/v-0.05/examples/error.txt b/v-0.05/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.05/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.05/examples/get-card-profiles-pulseaudio_api b/v-0.05/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..52d6860 Binary files /dev/null and b/v-0.05/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.05/examples/get-card-profiles-pulseaudio_api.c b/v-0.05/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..6f6c112 --- /dev/null +++ b/v-0.05/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_sinks(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_name(sink_info[i]->name); + const char *alsa_id = get_alsa_id(sink_info[i]->name); + int sample_rate = get_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_channels(alsa_id, sink_info[i]); + int max_channels = get_max_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_devices(sink_info); + return 0; +} diff --git a/v-0.05/examples/switch-sink b/v-0.05/examples/switch-sink new file mode 100755 index 0000000..39a03e7 Binary files /dev/null and b/v-0.05/examples/switch-sink differ diff --git a/v-0.05/examples/switch-sink-pulseaudio b/v-0.05/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..484ea50 Binary files /dev/null and b/v-0.05/examples/switch-sink-pulseaudio differ diff --git a/v-0.05/examples/switch-sink-pulseaudio.c b/v-0.05/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.05/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.05/examples/switch-sink.c b/v-0.05/examples/switch-sink.c new file mode 100644 index 0000000..45a8739 --- /dev/null +++ b/v-0.05/examples/switch-sink.c @@ -0,0 +1,61 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} diff --git a/v-0.05/examples/volume-change b/v-0.05/examples/volume-change new file mode 100755 index 0000000..9270d9e Binary files /dev/null and b/v-0.05/examples/volume-change differ diff --git a/v-0.05/examples/volume-change-pulseaudio b/v-0.05/examples/volume-change-pulseaudio new file mode 100755 index 0000000..5993de8 Binary files /dev/null and b/v-0.05/examples/volume-change-pulseaudio differ diff --git a/v-0.05/examples/volume-change-pulseaudio.c b/v-0.05/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.05/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.05/examples/volume-change.c b/v-0.05/examples/volume-change.c new file mode 100644 index 0000000..e15f0eb --- /dev/null +++ b/v-0.05/examples/volume-change.c @@ -0,0 +1,82 @@ +/** + * @file volume_app.c + * @brief PulseAudio Volume Control Application + * + * This application provides a simple interface to interact with PulseAudio. + * It displays the current active device, its name, and the master volume. + * The user can then input a new volume value, which the application attempts + * to set. The result of the operation (success or failure) is displayed to the user. + * + * The application leverages the EasyPulse library to communicate with PulseAudio. + * + * + * @date 10-08-2023 (creation date) + */ + +#include +#include +#include "../easypulse_core.h" + +int main() { + // Initialize the pulseaudio manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + pulseaudio_device *active_device = self->active_device; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + +#if 0 + // Display the channels of the active profile + uint32_t total_channels = manager->get_active_profile_channels(active_device, active_device->index); + + printf("[DEBUG] The active device is: %s\n", active_device->code); + printf("[DEBUG] The following profiles were found:\n"); + + for (uint32_t i = 0; i < active_device->profile_count; i++) { + if (active_device->profiles[i].name) { + printf("[DEBUG] Profile name: %s\n", active_device->profiles[i].name); + } else { + printf("[DEBUG] Profile name is NULL or invalid.\n"); + } + printf("[DEBUG] This profile has %d channels.\n", active_device->profiles[i].channels); + } + + + printf("Current device: %s\n", manager->active_device->code); + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume before change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } + + printf("Master Volume: %f%%\n", + (100.0 * pa_cvolume_avg(&manager->active_device->volume) / PA_VOLUME_NORM)); + + + // Ask the user for a new volume value + float new_volume; + printf("Enter a new volume value (0.0 - 100.0): "); + scanf("%f", &new_volume); + + // Set the new volume + manager->set_volume(manager, manager->active_device->index, new_volume); + + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume after change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } +#endif + // Cleanup and exit + manager->destroy(self); + return 0; + +} diff --git a/v-0.05/libeasypulse_core.a b/v-0.05/libeasypulse_core.a new file mode 100644 index 0000000..7b62bfe Binary files /dev/null and b/v-0.05/libeasypulse_core.a differ diff --git a/v-0.05/system_query.c b/v-0.05/system_query.c new file mode 100644 index 0000000..88184a4 --- /dev/null +++ b/v-0.05/system_query.c @@ -0,0 +1,767 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + const char *alsa_name; + const char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_sinks its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(shared_data_1.mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + } + +} + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_sinks_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_sinks() { + pa_operation *op = NULL; + + // Using get_device_count() to obtain the number of sinks + uint32_t max_sinks = get_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_sinks(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_sinks_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +void delete_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +const char* get_alsa_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +#include // Include this for the isdigit() function + +static void get_alsa_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +const char* get_alsa_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +int get_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate; + int err; + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; // Return sample rate from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + //fprintf(stderr, "Error getting sample rate for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + snd_pcm_close(handle); + return sample_rate; +} + + + diff --git a/v-0.05/system_query.h b/v-0.05/system_query.h new file mode 100644 index 0000000..274e5e1 --- /dev/null +++ b/v-0.05/system_query.h @@ -0,0 +1,20 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +uint32_t get_device_count(void); //Gets the number of devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_sinks(); //Gets the total available sinks for this system. +const char* get_alsa_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio card. +int get_max_channels(const char *alsa_id, const pa_sink_info *sink_info); //Gets the maximum channels an ALSA card supports. +int get_min_channels(const char *alsa_id, const pa_sink_info *sink_info); //Gets the maximum channels an ALSA card supports. +const char* get_alsa_id(const char *sink_name); //Gets the alsa id based on the pulseaudio channel name. +int get_sample_rate(const char *alsa_id, const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink. +void delete_devices(pa_sink_info **sinks); //Releases memory for allocated devices. + +#endif diff --git a/v-0.06/Makefile b/v-0.06/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.06/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.06/documentation/pa_context -- interface overview.docx b/v-0.06/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.06/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.06/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.06/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.06/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.06/documentation/pulseaudio/introspect.c summary b/v-0.06/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.06/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.06/documentation/pulseaudio/mainloop code flow.txt b/v-0.06/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.06/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.06/easypulse_core.c b/v-0.06/easypulse_core.c new file mode 100644 index 0000000..15ec1ba --- /dev/null +++ b/v-0.06/easypulse_core.c @@ -0,0 +1,843 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include +#include +#include +#include +#include +#include +#include + +//Variables shared for count_profile function and its callback. +typedef struct { + pulseaudio_manager *manager; + pulseaudio_device *device; + uint32_t profile_count; +} _shared_vars_1; + +static _shared_vars_1 shared_vars_1; + +/** + * @brief Callback function to check the state of the PulseAudio context. + * @param c The PulseAudio context. + * @param userdata User-provided data (expected to be a pointer to an int indicating readiness). + */ +static void context_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +bool initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, context_state_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + + +/** + * @brief Cleans up the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +static bool cleanup(pulseaudio_manager *self) { + // Lock the mainloop before making changes to the context + pa_threaded_mainloop_lock(self->mainloop); + + // Disconnect the context + pa_context_disconnect(self->context); + + // Unlock the mainloop before stopping it + pa_threaded_mainloop_unlock(self->mainloop); + + // Stop the threaded mainloop + pa_threaded_mainloop_stop(self->mainloop); + + // Unreference the context + pa_context_unref(self->context); + + // Free the threaded mainloop + pa_threaded_mainloop_free(self->mainloop); + + return true; +} + + +/** + * @brief Nested callback to load sound card profiles for a device. + * + * This function is triggered after invoking pa_context_get_card_info_by_index() + * to fetch details about a specific card. It is responsible for allocating memory + * for the profiles associated with the card and populating them with the relevant details. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the pa_card_info structure containing details about the card. + * @param eol Indicates the end of the list. If eol > 0, the list has ended or no more data is available. + * @param userdata User-provided data, expected to be a pointer to the profiles field of a pulseaudio_device structure. + */ +static void load_devices_cb_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + // Cast userdata to a double pointer to pulseaudio_profile + pulseaudio_profile **profiles_ptr = (pulseaudio_profile **) userdata; + + // If eol > 0, the list has ended or no more data is available + if (eol > 0) { + return; + } + + // Allocate memory for device profiles based on the number of profiles available for the card + *profiles_ptr = malloc(i->n_profiles * sizeof(pulseaudio_profile)); + if (!*profiles_ptr) { + fprintf(stderr, "[ERROR]: Failed to allocate memory for profiles.\n"); + return; + } + + // Populate the allocated memory with the profile details + for (uint32_t j = 0; j < i->n_profiles; j++) { + (*profiles_ptr)[j].name = strdup(i->profiles[j].name); + (*profiles_ptr)[j].description = strdup(i->profiles[j].description); + } +} + +/** + * @brief Callback function for retrieving device information. + * @param c The PulseAudio context. + * @param i The device information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void load_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; +#if 0 + //Variable to check if devices need to be reloaded. + static bool reload_devices = false; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + pulseaudio_device *devices = NULL; + pulseaudio_profile *profiles = NULL; + + if(manager->devices) { + devices = manager->devices; + if(manager->devices->profiles) profiles = manager->devices->profiles; + } + + if(reload_devices == true) { + for(uint32_t i = 0; i < manager->device_count; ++i) { + for(uint32_t j = 0 j > profiles-> + } + } + + manager->devices = (pulseaudio_device *)malloc(manager->device_count * sizeof(pulseaudio_device)); + + if (!manager->devices) { + free(manager); + return NULL; + } +#endif + +#if 0 + //TODO: rewrite this so that this is called every time load_devices has been called again. + //We need to detect that. + //size_t newSize = (manager->device_count) * sizeof(pulseaudio_device); + //pulseaudio_device *new_devices = realloc(manager->devices, newSize); + + /*if (!new_devices) { + fprintf(stderr, "[ERROR]: Failed to resize the devices array.\n"); + free(manager->devices); // Free old memory + manager->devices = NULL; + manager->device_count = 0; // Reset device_count to prevent out-of-bounds access + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + manager->devices = new_devices; */ + + if (eol < 0) { + if (pa_context_errno(c) != PA_ERR_NOENTITY) + fprintf(stderr, "Sink callback failure\\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + if (eol > 0) { + manager->devices_loaded = 1; + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Store the device's information + pulseaudio_device device; + device.index = i->index; + device.code = strdup(i->name); + device.description = strdup(i->description); + device.volume = i->volume; + device.channel_map = i->channel_map; + device.mute = i->mute; + + //Pulseaudio operation to fetch card profiles. + //pa_operation *profile_op = pa_context_get_card_info_by_index(manager->context,device.index,load_devices_cb_cb,&i->profiles); + //iterate(manager, profile_op); + + // Add the device to the manager's list + manager->devices[manager->device_count++] = device; + + pa_threaded_mainloop_signal(manager->mainloop, 0); + #endif +} + +/** + * @brief Load available sound cards (devices). + * @param self The pulseaudio_manager instance. + * @return true on success, false otherwise. + */ +bool load_devices(pulseaudio_manager *self) { + pa_operation *data_op; + + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + iterate(self, data_op); + + #if 0 + pa_threaded_mainloop_lock(self->mainloop); + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + pa_threaded_mainloop_wait(self->mainloop); + + // Once all devices are loaded, loads the active device. + // get_active_device(self); + + pa_threaded_mainloop_unlock(self->mainloop); + pa_operation_unref(data_op); + #endif + return true; +} + + +/** + * @brief Callback function handling the completion of the "unmute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { + (void)c; // Suppress unused parameter warning + + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + manager->operations_pending--; + + if (!success) { + fprintf(stderr, "Failed to unmute the device input.\n"); + } + + // Signal the mainloop to resume any waiting threads. + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback function handling the completion of the "move" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (success) { + pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_device_index, 0, operation_complete_unmute_cb, manager); + pa_operation_unref(unmute_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function handling the completion of the "mute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + uint32_t target_device_index = manager->current_device_index; + + if (success) { + pa_operation* move_op = pa_context_move_sink_input_by_index(c, manager->current_device_index, target_device_index, operation_complete_move_cb, manager); + pa_operation_unref(move_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function to handle each device input. + * @param c The PulseAudio context. + * @param i The device input information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to the target device index). + */ +static void switch_device_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (!eol && i) { + // Move this device input to the desired device + pa_operation* move_op = pa_context_move_sink_input_by_index(c, i->index, manager->devices[manager->current_device_index].index, NULL, NULL); + pa_operation_unref(move_op); + } + + if (eol) { + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Switches the device (audio source) for the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to switch to. + * @return Boolean indicating success or failure. + */ +bool switch_device(pulseaudio_manager *self, uint32_t device_index) { + // Ensure the context is valid + if (!self || !self->context) { + return false; + } + + // Check if device_index is out of bounds + if (device_index >= self->device_count) { + fprintf(stderr, "[ERROR]: device_index out of bounds.\n"); + return false; + } + + self->current_device_index = device_index; + + // Set the desired device as the default device + /*fprintf(stderr, "[DEBUG]: self->context = %p\n", self->context); + fprintf(stderr, "[DEBUG]: self->devices = %p\n", self->devices); + fprintf(stderr, "[DEBUG]: device_index = %d\n", device_index);*/ + + if (self->devices) { + // Check if the name attribute is NULL + if (!self->devices[device_index].code) { + fprintf(stderr, "[ERROR]: Sink's name is NULL.\n"); + return false; + } + //fprintf(stderr, "[DEBUG]: self->devices[device_index].code = %s\n", self->devices[device_index].code); + } + pa_operation* set_default_op = pa_context_set_default_sink(self->context, self->devices[device_index].code, NULL, NULL); + pa_operation_unref(set_default_op); + + // Use the introspect API to get a list of all device inputs + pa_operation *op = pa_context_get_sink_input_info_list(self->context, switch_device_cb, self); + + if (!op) { + return false; + } + + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + iterate(self, op); + } + + pa_operation_unref(op); + + return true; +} + + +/** + * @brief Process PulseAudio profiles using a null sink. + * + * This function initializes a null sink, iterates through all `pulseaudio_profile`s, + * retrieves the number of channels from the device's channel map, assigns it to the + * profile's `channels` attribute, and finally unloads the null sink. + * + * @param manager A pointer to the `pulseaudio_manager` structure. + * + * @note This function uses the `iterate` method from `pulseaudio_manager` to wait for PulseAudio operations to complete. + * + * @warning Ensure that the `pulseaudio_manager` is properly initialized before calling this function. + */ +void get_profile_channels(pulseaudio_manager *manager) { + uint32_t module_index = PA_INVALID_INDEX; + + // Static variable to track if the mainloop is locked + static int is_mainloop_locked = 0; + + // Check if the threaded mainloop is locked + if (!is_mainloop_locked) { + pa_threaded_mainloop_lock(manager->mainloop); + is_mainloop_locked = 1; + } + + // Callback to capture the module index when loading the null sink module + void load_module_callback(pa_context *c, uint32_t idx, void *userdata) { + (void) c; + (void) userdata; + module_index = idx; + } + + // 1. Initialize a Null Sink + pa_operation *load_op = pa_context_load_module(manager->context, "module-null-sink", "sink_name=easy_pulse_null_sink", load_module_callback, NULL); + if (!load_op) { + fprintf(stderr, "Failed to load null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete and for the callback to capture the module index + iterate(manager, load_op); + + // 2. Iterate through Profiles and Extract Channel Map + for (uint32_t i = 0; i < manager->device_count; i++) { + pulseaudio_device *device = &(manager->devices[i]); + for (uint32_t j = 0; j < device->profile_count; j++) { + pulseaudio_profile *profile = &(device->profiles[j]); + + // Extract the number of channels from the device's channel map + int num_channels = device->channel_map.channels; + + // Update the pulseaudio_profile with the number of channels + profile->channels = num_channels; + } + } + + // 3. Unload the Null Sink using the captured module index + if (module_index != PA_INVALID_INDEX) { + pa_operation *unload_op = pa_context_unload_module(manager->context, module_index, NULL, NULL); + if (!unload_op) { + fprintf(stderr, "Failed to unload null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete + iterate(manager, unload_op); + } else { + fprintf(stderr, "Invalid module index for null sink\n"); + } + + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } +} + + +/** + * @brief Retrieves the number of channels for a specified device. + * + * @param devices Pointer to an array of PulseSink structures. + * @param device_index Index of the device whose number of channels is to be retrieved. + * + * @return Number of channels for the specified device. Returns -1 on error. + */ +int get_active_profile_channels(const pulseaudio_device *devices, int device_index) { + if (!devices || device_index < 0) { + fprintf(stderr, "[ERROR]: Invalid devices array or device index in get_device_channels.\n"); + return -1; // Return -1 or another indicator of failure + } + return devices[device_index].channel_map.channels; +} + + + +/** + * @brief Callback function belonging to set_volume. Triggers when audio volume is set. + * + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb1(pa_context *c, int success, void *userdata) { + (void) c; + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + + if (!success) { + fprintf(stderr, "[ERROR]: Failed to set volume. Reason: %s\n", pa_strerror(pa_context_errno(c))); + } + + // Debug: Print cvolume values for each channel as percentages + //pa_cvolume cvolume = manager->devices[manager->active_device_index].volume; + + /*for (int i = 0; i < cvolume.channels; i++) { + float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; + //printf("[DEBUG, volume_set_complete_cb()]: Channel %d cvolume value: %u (%.2f%%)\n", i, cvolume.values[i], percentage); + } + //printf("[DEBUG, volume_set_complete_cb invoked. Success: %d\n", success);*/ + + // Decrease the operations count and potentially signal the condition variable. + manager->operations_pending--; + + // Signal the main loop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Callback function belonging to set_volume. + * Triggers after audio volume levels are updated. + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb2(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + pulseaudio_manager *self = (pulseaudio_manager *)userdata; + + if (eol > 0) { + //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + self->operations_pending--; + return; + } + + if (!i) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for pa_sink_info.\n"); + self->operations_pending--; + return; + } + + if (!i->name) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for device name.\n"); + self->operations_pending--; + return; + } + + printf("[DEBUG, volume_check_cb()]: Processing device info for device: %s\n", i->name); + + // Update the volume values in the manager's devices structure + self->active_device->volume = i->volume; + + // Print the volume for each channel + for (int channel = 0; channel < i->volume.channels; channel++) { + //float volume_percentage = (float)i->volume.values[channel] / PA_VOLUME_NORM * 100.0; + //printf("Channel %d Volume: %.2f%%\n", channel, volume_percentage); + } + + //Signaling to continue. + pa_threaded_mainloop_signal(self->mainloop, 0); +} + + +/** + * @brief Set the volume for a specified device. + * + * This function allows setting the volume for a specific device based on the given percentage. + * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. + * It will adjust the volume for all channels of the device to the desired level. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to set the volume for. + * @param percentage Desired volume level as a percentage. + * @return Boolean indicating success or failure. + */ +bool set_volume(pulseaudio_manager *self, uint32_t device_index, float percentage) { + if (!self || percentage < 0.0f || percentage > 100.0f || device_index >= self->device_count) { + return false; + } + + // Convert percentage to volume + pa_volume_t volume = (percentage / 100.0) * PA_VOLUME_NORM; + if (volume >= PA_VOLUME_NORM) { + volume = PA_VOLUME_NORM - 1; + } + + // Debug: show index and desired volume. + //printf("[DEBUG, set_volume()] Index is: %i\n", device_index); + //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + + // Ensure PulseAudio is ready and devices are loaded + if (self->pa_ready != 1 || self->devices_loaded != 1) { + return false; + } + + // Debug: Show channel volumes before the change. + /*for (int channel = 0; channel < self->devices[device_index].channel_map.channels; channel++) { + printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + channel, + 100.0 * self->devices[device_index].volume.values[channel] / PA_VOLUME_NORM); + }*/ + + // Create a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_init(&cvolume); + cvolume.channels = self->devices[device_index].channel_map.channels; // Manually set channels + pa_cvolume_set(&cvolume, cvolume.channels, volume); + + printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + + // Apply the volume change to the specific device by index and wait for the operation to complete + const char *device_name_to_change = self->devices[device_index].code; + self->operations_pending++; + pa_operation *op = pa_context_set_sink_volume_by_name(self->context, device_name_to_change, &cvolume, set_volume_cb1, self); + iterate(self, op); + + // Fetch the updated volume for the device and wait for the operation to complete + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, device_name_to_change, set_volume_cb2, self); + iterate(self, op2); + + return true; +} + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Create a new pulseaudio_manager instance. + * @return Returns a pointer to a manager structure. + */ +pulseaudio_manager *new_manager(void) { + pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); + if (!manager) return NULL; + + + // Initialize pointers to 0 -- good practice. + manager->device_count = 0; + manager->devices_loaded = 0; // Initialize to 0 + manager->load_devices = load_devices; + manager->destroy = destroy; + manager->switch_device = switch_device; + manager->set_volume = set_volume; + manager->get_active_device = get_active_device; + manager->iterate = iterate; + manager->get_active_profile_channels = get_active_profile_channels; + //manager->get_profiles_for_device = get_profiles_for_device; + manager->get_profile_channels = get_profile_channels; + manager->get_profile_count = get_profile_count; + + if(!initialize(manager)) { + free(manager->devices); + free(manager); + return NULL; + } + + manager->device_count = get_output_device_count(); + manager->devices = malloc(manager->device_count * sizeof(pulseaudio_manager)); + + if(!manager->devices) { + fprintf(stderr, "[new_manager()] Could not allocate memory for the manager devices.\n"); + return NULL; + } + +#if 0 + if (!load_devices(manager)) { + fprintf(stderr, "No audio devices were detected. Aborting.\n"); + destroy(manager); + return NULL; + } +#endif + + return manager; +} + +/** + * @brief Callback function to process the PulseAudio server information. + * + * This callback is invoked once the server information is available. It retrieves the default device name + * from the server response and determines the active device by matching the name with the available devices + * in the manager's list. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the pa_server_info structure containing the server's information. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void get_active_device_cb(pa_context *c, const pa_server_info *info, void *userdata) { + (void) c; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + if (!info || !info->default_sink_name) { + fprintf(stderr, "[ERROR]: Null pointer in active_device_cb.\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Get the default device name from the server information + const char *default_device_name = info->default_sink_name; + + // Iterate over the available devices to find the active one + for (uint32_t i = 0; i < manager->device_count; i++) { + if (strcmp(manager->devices[i].code, default_device_name) == 0) { + // Set the active device pointer when a match is found + manager->active_device = &manager->devices[i]; + break; + } + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Request the default device name from the PulseAudio server and determine the active device. + * + * This function initiates a request to retrieve the default device name (active device) from the PulseAudio server. + * Once the server provides this information, the active_device_cb callback is triggered to process the response. + * + * @param manager Pointer to the pulseaudio_manager instance. + */ +//TODO: REWORK THE WAITING LOGIC. +void get_active_device(pulseaudio_manager *manager) { + pa_threaded_mainloop_lock(manager->mainloop); + + // Request server information + pa_context_get_server_info(manager->context, get_active_device_cb, manager); + + // Wait until the iterate function signals that it's done + pa_threaded_mainloop_wait(manager->mainloop); + + pa_threaded_mainloop_unlock(manager->mainloop); + + // The original loop to wait for active_device_name to be set + int timeout = 50; // Number of iterations or timeout value + while (!manager->active_device->code && timeout-- > 0) { + manager->iterate(manager, NULL); + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + + +/** + * @brief Frees the memory of a pulseaudio_manager instance. + * + * @param manager Pointer to the pulseaudio_manager instance to be deleted. + */ +void destroy(pulseaudio_manager *self) { + cleanup(self); + + if (self) { + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].code) { + free(self->devices[i].code); + self->devices[i].code = NULL; + } + if (self->devices[i].description) { + free(self->devices[i].description); + self->devices[i].description = NULL; + } + } + // Now, free profiles for each device (if they exist) + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].profiles) free(self->devices[i].profiles); + } + if (self->devices) free(self->devices); + free(self); + } +} diff --git a/v-0.06/easypulse_core.h b/v-0.06/easypulse_core.h new file mode 100644 index 0000000..9129942 --- /dev/null +++ b/v-0.06/easypulse_core.h @@ -0,0 +1,90 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; + +/** + * @brief Represents a PulseAudio devices. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Name of the device. + char *description; // Description of the device. + pa_cvolume volume; // Volume of the device. + /* pulseaudio_profile *active_profile; // Active alsa profile of this device. */ + pa_channel_map channel_map; // Channel map of the devices. + int mute; // Mute status of the devices (1 for muted, 0 for unmuted). + int min_play_channels; // The minimum number of playback channels of the device. + int max_play_channels; // The maximum number of playback channels of the device. + pulseaudio_profile *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *devices; // Array of available devices. + uint32_t device_count; // Count of available devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + pulseaudio_device *active_device; // Pointer to active device. + uint32_t current_device_index; // The devices being processed right now by the program. It's not necessarily the same as the playback device. + + bool (*initialize)(pulseaudio_manager *self); // Initializes the manager. + void (*destroy)(pulseaudio_manager *manager); // Destroys the manager. + bool (*load_devices)(pulseaudio_manager *self); // Loads available devices. + bool (*switch_device)(pulseaudio_manager *self, uint32_t devices_index); // Switches to a specified device. + bool (*set_volume)(pulseaudio_manager *self, uint32_t devices_index, float percentage); // Sets the volume to a specified percentage. + void (*get_active_device)(pulseaudio_manager *manager); // Assures that a pulseaudio operation is not pending. + void (*iterate)(pulseaudio_manager *manager, pa_operation *op); // Goes through every step of a threaded loop. + int (*get_active_profile_channels) (const pulseaudio_device *device, int device_index); // Gets the number of channels of an active profile. + //void (*get_profiles_for_device)(pulseaudio_manager *manager, const char* device_code); // Updates the profiles of a particular device. + void (*get_profile_channels)(pulseaudio_manager *manager); // Extracts profile channels by copying them to null sink. + uint32_t (*get_profile_count)(uint32_t card_index); // Gets the number of pulseaudio profiles in the system. +}; + +/** + * @brief Create a new pulseaudio_manager instance. + * @return A pointer to the newly created pulseaudio_manager instance. + */ +pulseaudio_manager* new_manager(void); + +bool load_devices(pulseaudio_manager *self); +int get_active_profile_channels(const pulseaudio_device *devices, int device_index); +bool set_volume(pulseaudio_manager *self, uint32_t devices_index, float percentage); +void iterate(pulseaudio_manager *manager, pa_operation *op); +int get_device_channels(const pulseaudio_device *devices, int devices_index); +void destroy(pulseaudio_manager *manager); +//void get_profiles_for_device(pulseaudio_manager *manager, const char* device_code); +void get_active_device(pulseaudio_manager *manager); +void get_profile_channels(pulseaudio_manager *manager); + +#endif // CORE_H diff --git a/v-0.06/examples/Makefile b/v-0.06/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.06/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.06/examples/alsa-mapper_pulseaudio-api b/v-0.06/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..64fafb7 Binary files /dev/null and b/v-0.06/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.06/examples/alsa-mapper_pulseaudio-api.c b/v-0.06/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.06/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.06/examples/change-speaker-mode b/v-0.06/examples/change-speaker-mode new file mode 100755 index 0000000..78ccaba Binary files /dev/null and b/v-0.06/examples/change-speaker-mode differ diff --git a/v-0.06/examples/change-speaker-mode.c b/v-0.06/examples/change-speaker-mode.c new file mode 100644 index 0000000..64fa962 --- /dev/null +++ b/v-0.06/examples/change-speaker-mode.c @@ -0,0 +1,88 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + #if 0 + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + #endif +} diff --git a/v-0.06/examples/error.txt b/v-0.06/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.06/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.06/examples/get-card-profiles-pulseaudio_api b/v-0.06/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..f11d0e0 Binary files /dev/null and b/v-0.06/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.06/examples/get-card-profiles-pulseaudio_api.c b/v-0.06/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..de59ad9 --- /dev/null +++ b/v-0.06/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.06/examples/get-input-sources b/v-0.06/examples/get-input-sources new file mode 100755 index 0000000..262274c Binary files /dev/null and b/v-0.06/examples/get-input-sources differ diff --git a/v-0.06/examples/get-input-sources.c b/v-0.06/examples/get-input-sources.c new file mode 100644 index 0000000..fc4c948 --- /dev/null +++ b/v-0.06/examples/get-input-sources.c @@ -0,0 +1,62 @@ +#include +#include "../system_query.h" + +int main() { + char *alsa_name = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + pa_source_info *source_info = input_devices[i]; + alsa_name = get_alsa_input_name(source_info->name); + printf("Input Device %u:\n", i); + printf(" Pulseaudio ID: %s\n", source_info->name); + printf(" Pulseaudio name: %s\n", source_info->description); + if (alsa_name) { + printf(" Alsa name: %s\n", alsa_name); + } + uint32_t sample_rate = get_input_sample_rate(source_info->name); + printf(" Sample Rate: %u Hz\n", sample_rate); + } + + // Clean up all input devices + delete_input_devices(input_devices); + + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + + // Normally we would clean up PulseAudio context here, if it was initialized in this file + + return 0; +} diff --git a/v-0.06/examples/print_volume_output_devices b/v-0.06/examples/print_volume_output_devices new file mode 100755 index 0000000..58ad300 Binary files /dev/null and b/v-0.06/examples/print_volume_output_devices differ diff --git a/v-0.06/examples/print_volume_output_devices.c b/v-0.06/examples/print_volume_output_devices.c new file mode 100644 index 0000000..2d93f54 --- /dev/null +++ b/v-0.06/examples/print_volume_output_devices.c @@ -0,0 +1,66 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + printf("Device %u: %s\n", i, sinks[i]->name); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u Volume: %.2f%%\n", ch, volume_percent); + } + } + + // Cleanup + delete_output_devices(sinks); + // You may need to add cleanup functions from system_query.c here to clean up PulseAudio + + return 0; +} diff --git a/v-0.06/examples/switch-sink b/v-0.06/examples/switch-sink new file mode 100755 index 0000000..48291a3 Binary files /dev/null and b/v-0.06/examples/switch-sink differ diff --git a/v-0.06/examples/switch-sink-pulseaudio b/v-0.06/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..c745609 Binary files /dev/null and b/v-0.06/examples/switch-sink-pulseaudio differ diff --git a/v-0.06/examples/switch-sink-pulseaudio.c b/v-0.06/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.06/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.06/examples/switch-sink.c b/v-0.06/examples/switch-sink.c new file mode 100644 index 0000000..45a8739 --- /dev/null +++ b/v-0.06/examples/switch-sink.c @@ -0,0 +1,61 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} diff --git a/v-0.06/examples/volume-change b/v-0.06/examples/volume-change new file mode 100755 index 0000000..466bc43 Binary files /dev/null and b/v-0.06/examples/volume-change differ diff --git a/v-0.06/examples/volume-change-pulseaudio b/v-0.06/examples/volume-change-pulseaudio new file mode 100755 index 0000000..95d00d9 Binary files /dev/null and b/v-0.06/examples/volume-change-pulseaudio differ diff --git a/v-0.06/examples/volume-change-pulseaudio.c b/v-0.06/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.06/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.06/examples/volume-change.c b/v-0.06/examples/volume-change.c new file mode 100644 index 0000000..6600e95 --- /dev/null +++ b/v-0.06/examples/volume-change.c @@ -0,0 +1,82 @@ +/** + * @file volume-change.c + * @brief PulseAudio Volume Control Application + * + * This application provides a simple interface to interact with PulseAudio. + * It displays the current active device, its name, and the master volume. + * The user can then input a new volume value, which the application attempts + * to set. The result of the operation (success or failure) is displayed to the user. + * + * The application leverages the EasyPulse library to communicate with PulseAudio. + * + * + * @date 10-08-2023 (creation date) + */ + +#include +#include +#include "../easypulse_core.h" + +int main() { + // Initialize the pulseaudio manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + pulseaudio_device *active_device = self->active_device; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + +#if 0 + // Display the channels of the active profile + uint32_t total_channels = manager->get_active_profile_channels(active_device, active_device->index); + + printf("[DEBUG] The active device is: %s\n", active_device->code); + printf("[DEBUG] The following profiles were found:\n"); + + for (uint32_t i = 0; i < active_device->profile_count; i++) { + if (active_device->profiles[i].name) { + printf("[DEBUG] Profile name: %s\n", active_device->profiles[i].name); + } else { + printf("[DEBUG] Profile name is NULL or invalid.\n"); + } + printf("[DEBUG] This profile has %d channels.\n", active_device->profiles[i].channels); + } + + + printf("Current device: %s\n", manager->active_device->code); + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume before change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } + + printf("Master Volume: %f%%\n", + (100.0 * pa_cvolume_avg(&manager->active_device->volume) / PA_VOLUME_NORM)); + + + // Ask the user for a new volume value + float new_volume; + printf("Enter a new volume value (0.0 - 100.0): "); + scanf("%f", &new_volume); + + // Set the new volume + manager->set_volume(manager, manager->active_device->index, new_volume); + + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume after change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } +#endif + // Cleanup and exit + manager->destroy(self); + return 0; + +} diff --git a/v-0.06/libeasypulse_core.a b/v-0.06/libeasypulse_core.a new file mode 100644 index 0000000..7b62bfe Binary files /dev/null and b/v-0.06/libeasypulse_core.a differ diff --git a/v-0.06/system_query.c b/v-0.06/system_query.c new file mode 100644 index 0000000..d4c798e --- /dev/null +++ b/v-0.06/system_query.c @@ -0,0 +1,1227 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + const char *alsa_name; + const char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +const char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "get_alsa_input_name(): Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +const char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ + + +static void get_alsa_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +const char* get_alsa_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + +static void get_input_sample_rate_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + if (eol < 0) { + // An error occurred + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // No more entries + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Extract the sample rate and store it in the user data + uint32_t *sample_rate = userdata; + *sample_rate = i->sample_spec.rate; +} + +uint32_t get_input_sample_rate(const char *source_name) { + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "PulseAudio is not initialized.\n"); + return 0; + } + + uint32_t sample_rate = 0; + pa_operation *op = NULL; + + + // Get the source info, passing the address of sample_rate as user data + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_input_sample_rate_cb, &sample_rate); + iterate(op); + + return sample_rate; +} + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate; + int err; + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; // Return sample rate from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + //fprintf(stderr, "Error getting sample rate for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + snd_pcm_close(handle); + return sample_rate; +} + +// Callback for source information to get ports +void get_source_ports(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +// Callback for source information to get the active port +void get_active_port(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +// Function to collect source port information and return it +pa_source_info_list* get_source_port_info() { + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_ports, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_active_port, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + // Check if the sink_info is NULL + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + //fprintf(stderr, "[DEBUG] %s called with eol=%d\n", __FUNCTION__, eol); // Debug statement for entry + (void)userdata; // Unused parameter + //fprintf(stderr, "[DEBUG] %s called with eol=%d\n", __FUNCTION__, eol); // Debug statement for entry + + // Error or end of list + if (eol < 0) { + //fprintf(stderr, "Failed to get source info.\n"); + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list, signal the main loop to stop waiting + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate or resize the sources array as needed + if (shared_data_sources.count >= shared_data_sources.allocated) { + uint32_t new_size = (shared_data_sources.allocated + 8) * sizeof(pa_source_info *); + pa_source_info **temp = realloc(shared_data_sources.sources, new_size); + if (!temp) { + //fprintf(stderr, "Out of memory.\n"); + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated += 8; + } + + // Allocate memory for a new pa_source_info structure + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Failed to allocate memory for source info.\n"); + fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the information from the callback + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + // Duplicate the strings to ensure they will remain valid + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + } + + // Increase the count of found sources + shared_data_sources.count++; +} + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "PulseAudio is not initialized.\\n"); + return NULL; + } + + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + + // Prepare the PulseAudio operation to list the sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + + // Wait for the operation to complete + iterate(op); + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + shared_data_sources.sources[shared_data_sources.count] = NULL; + + return shared_data_sources.sources; +} + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_input_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + diff --git a/v-0.06/system_query.h b/v-0.06/system_query.h new file mode 100644 index 0000000..e7b87a7 --- /dev/null +++ b/v-0.06/system_query.h @@ -0,0 +1,51 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. +const char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). +const char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +const char* get_alsa_id(const char *sink_name); //Gets the alsa id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +uint32_t get_input_sample_rate(const char *source_name); //Gets the sample rate of a pulseaudio source (input device). + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + + +#endif diff --git a/v-0.07/Makefile b/v-0.07/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.07/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.07/documentation/pa_context -- interface overview.docx b/v-0.07/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.07/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.07/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.07/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.07/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.07/documentation/pulseaudio/introspect.c summary b/v-0.07/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.07/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.07/documentation/pulseaudio/mainloop code flow.txt b/v-0.07/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.07/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.07/easypulse_core.c b/v-0.07/easypulse_core.c new file mode 100644 index 0000000..15ec1ba --- /dev/null +++ b/v-0.07/easypulse_core.c @@ -0,0 +1,843 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include +#include +#include +#include +#include +#include +#include + +//Variables shared for count_profile function and its callback. +typedef struct { + pulseaudio_manager *manager; + pulseaudio_device *device; + uint32_t profile_count; +} _shared_vars_1; + +static _shared_vars_1 shared_vars_1; + +/** + * @brief Callback function to check the state of the PulseAudio context. + * @param c The PulseAudio context. + * @param userdata User-provided data (expected to be a pointer to an int indicating readiness). + */ +static void context_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +bool initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, context_state_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + + +/** + * @brief Cleans up the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @return Boolean indicating success or failure. + */ +static bool cleanup(pulseaudio_manager *self) { + // Lock the mainloop before making changes to the context + pa_threaded_mainloop_lock(self->mainloop); + + // Disconnect the context + pa_context_disconnect(self->context); + + // Unlock the mainloop before stopping it + pa_threaded_mainloop_unlock(self->mainloop); + + // Stop the threaded mainloop + pa_threaded_mainloop_stop(self->mainloop); + + // Unreference the context + pa_context_unref(self->context); + + // Free the threaded mainloop + pa_threaded_mainloop_free(self->mainloop); + + return true; +} + + +/** + * @brief Nested callback to load sound card profiles for a device. + * + * This function is triggered after invoking pa_context_get_card_info_by_index() + * to fetch details about a specific card. It is responsible for allocating memory + * for the profiles associated with the card and populating them with the relevant details. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the pa_card_info structure containing details about the card. + * @param eol Indicates the end of the list. If eol > 0, the list has ended or no more data is available. + * @param userdata User-provided data, expected to be a pointer to the profiles field of a pulseaudio_device structure. + */ +static void load_devices_cb_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + // Cast userdata to a double pointer to pulseaudio_profile + pulseaudio_profile **profiles_ptr = (pulseaudio_profile **) userdata; + + // If eol > 0, the list has ended or no more data is available + if (eol > 0) { + return; + } + + // Allocate memory for device profiles based on the number of profiles available for the card + *profiles_ptr = malloc(i->n_profiles * sizeof(pulseaudio_profile)); + if (!*profiles_ptr) { + fprintf(stderr, "[ERROR]: Failed to allocate memory for profiles.\n"); + return; + } + + // Populate the allocated memory with the profile details + for (uint32_t j = 0; j < i->n_profiles; j++) { + (*profiles_ptr)[j].name = strdup(i->profiles[j].name); + (*profiles_ptr)[j].description = strdup(i->profiles[j].description); + } +} + +/** + * @brief Callback function for retrieving device information. + * @param c The PulseAudio context. + * @param i The device information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void load_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; +#if 0 + //Variable to check if devices need to be reloaded. + static bool reload_devices = false; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + pulseaudio_device *devices = NULL; + pulseaudio_profile *profiles = NULL; + + if(manager->devices) { + devices = manager->devices; + if(manager->devices->profiles) profiles = manager->devices->profiles; + } + + if(reload_devices == true) { + for(uint32_t i = 0; i < manager->device_count; ++i) { + for(uint32_t j = 0 j > profiles-> + } + } + + manager->devices = (pulseaudio_device *)malloc(manager->device_count * sizeof(pulseaudio_device)); + + if (!manager->devices) { + free(manager); + return NULL; + } +#endif + +#if 0 + //TODO: rewrite this so that this is called every time load_devices has been called again. + //We need to detect that. + //size_t newSize = (manager->device_count) * sizeof(pulseaudio_device); + //pulseaudio_device *new_devices = realloc(manager->devices, newSize); + + /*if (!new_devices) { + fprintf(stderr, "[ERROR]: Failed to resize the devices array.\n"); + free(manager->devices); // Free old memory + manager->devices = NULL; + manager->device_count = 0; // Reset device_count to prevent out-of-bounds access + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + manager->devices = new_devices; */ + + if (eol < 0) { + if (pa_context_errno(c) != PA_ERR_NOENTITY) + fprintf(stderr, "Sink callback failure\\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + if (eol > 0) { + manager->devices_loaded = 1; + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Store the device's information + pulseaudio_device device; + device.index = i->index; + device.code = strdup(i->name); + device.description = strdup(i->description); + device.volume = i->volume; + device.channel_map = i->channel_map; + device.mute = i->mute; + + //Pulseaudio operation to fetch card profiles. + //pa_operation *profile_op = pa_context_get_card_info_by_index(manager->context,device.index,load_devices_cb_cb,&i->profiles); + //iterate(manager, profile_op); + + // Add the device to the manager's list + manager->devices[manager->device_count++] = device; + + pa_threaded_mainloop_signal(manager->mainloop, 0); + #endif +} + +/** + * @brief Load available sound cards (devices). + * @param self The pulseaudio_manager instance. + * @return true on success, false otherwise. + */ +bool load_devices(pulseaudio_manager *self) { + pa_operation *data_op; + + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + iterate(self, data_op); + + #if 0 + pa_threaded_mainloop_lock(self->mainloop); + data_op = pa_context_get_sink_info_list(self->context, load_devices_cb, self); + pa_threaded_mainloop_wait(self->mainloop); + + // Once all devices are loaded, loads the active device. + // get_active_device(self); + + pa_threaded_mainloop_unlock(self->mainloop); + pa_operation_unref(data_op); + #endif + return true; +} + + +/** + * @brief Callback function handling the completion of the "unmute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_unmute_cb(pa_context *c, int success, void *userdata) { + (void)c; // Suppress unused parameter warning + + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + manager->operations_pending--; + + if (!success) { + fprintf(stderr, "Failed to unmute the device input.\n"); + } + + // Signal the mainloop to resume any waiting threads. + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback function handling the completion of the "move" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_move_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (success) { + pa_operation* unmute_op = pa_context_set_sink_input_mute(c, manager->current_device_index, 0, operation_complete_unmute_cb, manager); + pa_operation_unref(unmute_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function handling the completion of the "mute" operation. + * + * @param c The PulseAudio context. + * @param success A flag indicating the success or failure of the operation. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void operation_complete_mute_cb(pa_context *c, int success, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + uint32_t target_device_index = manager->current_device_index; + + if (success) { + pa_operation* move_op = pa_context_move_sink_input_by_index(c, manager->current_device_index, target_device_index, operation_complete_move_cb, manager); + pa_operation_unref(move_op); + } else { + // Handle error... + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Callback function to handle each device input. + * @param c The PulseAudio context. + * @param i The device input information. + * @param eol End of list flag. + * @param userdata User-provided data (expected to be a pointer to the target device index). + */ +static void switch_device_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + if (!eol && i) { + // Move this device input to the desired device + pa_operation* move_op = pa_context_move_sink_input_by_index(c, i->index, manager->devices[manager->current_device_index].index, NULL, NULL); + pa_operation_unref(move_op); + } + + if (eol) { + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + +/** + * @brief Switches the device (audio source) for the pulseaudio_manager. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to switch to. + * @return Boolean indicating success or failure. + */ +bool switch_device(pulseaudio_manager *self, uint32_t device_index) { + // Ensure the context is valid + if (!self || !self->context) { + return false; + } + + // Check if device_index is out of bounds + if (device_index >= self->device_count) { + fprintf(stderr, "[ERROR]: device_index out of bounds.\n"); + return false; + } + + self->current_device_index = device_index; + + // Set the desired device as the default device + /*fprintf(stderr, "[DEBUG]: self->context = %p\n", self->context); + fprintf(stderr, "[DEBUG]: self->devices = %p\n", self->devices); + fprintf(stderr, "[DEBUG]: device_index = %d\n", device_index);*/ + + if (self->devices) { + // Check if the name attribute is NULL + if (!self->devices[device_index].code) { + fprintf(stderr, "[ERROR]: Sink's name is NULL.\n"); + return false; + } + //fprintf(stderr, "[DEBUG]: self->devices[device_index].code = %s\n", self->devices[device_index].code); + } + pa_operation* set_default_op = pa_context_set_default_sink(self->context, self->devices[device_index].code, NULL, NULL); + pa_operation_unref(set_default_op); + + // Use the introspect API to get a list of all device inputs + pa_operation *op = pa_context_get_sink_input_info_list(self->context, switch_device_cb, self); + + if (!op) { + return false; + } + + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + iterate(self, op); + } + + pa_operation_unref(op); + + return true; +} + + +/** + * @brief Process PulseAudio profiles using a null sink. + * + * This function initializes a null sink, iterates through all `pulseaudio_profile`s, + * retrieves the number of channels from the device's channel map, assigns it to the + * profile's `channels` attribute, and finally unloads the null sink. + * + * @param manager A pointer to the `pulseaudio_manager` structure. + * + * @note This function uses the `iterate` method from `pulseaudio_manager` to wait for PulseAudio operations to complete. + * + * @warning Ensure that the `pulseaudio_manager` is properly initialized before calling this function. + */ +void get_profile_channels(pulseaudio_manager *manager) { + uint32_t module_index = PA_INVALID_INDEX; + + // Static variable to track if the mainloop is locked + static int is_mainloop_locked = 0; + + // Check if the threaded mainloop is locked + if (!is_mainloop_locked) { + pa_threaded_mainloop_lock(manager->mainloop); + is_mainloop_locked = 1; + } + + // Callback to capture the module index when loading the null sink module + void load_module_callback(pa_context *c, uint32_t idx, void *userdata) { + (void) c; + (void) userdata; + module_index = idx; + } + + // 1. Initialize a Null Sink + pa_operation *load_op = pa_context_load_module(manager->context, "module-null-sink", "sink_name=easy_pulse_null_sink", load_module_callback, NULL); + if (!load_op) { + fprintf(stderr, "Failed to load null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete and for the callback to capture the module index + iterate(manager, load_op); + + // 2. Iterate through Profiles and Extract Channel Map + for (uint32_t i = 0; i < manager->device_count; i++) { + pulseaudio_device *device = &(manager->devices[i]); + for (uint32_t j = 0; j < device->profile_count; j++) { + pulseaudio_profile *profile = &(device->profiles[j]); + + // Extract the number of channels from the device's channel map + int num_channels = device->channel_map.channels; + + // Update the pulseaudio_profile with the number of channels + profile->channels = num_channels; + } + } + + // 3. Unload the Null Sink using the captured module index + if (module_index != PA_INVALID_INDEX) { + pa_operation *unload_op = pa_context_unload_module(manager->context, module_index, NULL, NULL); + if (!unload_op) { + fprintf(stderr, "Failed to unload null sink module\n"); + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } + return; + } + + // Wait for the operation to complete + iterate(manager, unload_op); + } else { + fprintf(stderr, "Invalid module index for null sink\n"); + } + + if (is_mainloop_locked) { + pa_threaded_mainloop_unlock(manager->mainloop); + is_mainloop_locked = 0; + } +} + + +/** + * @brief Retrieves the number of channels for a specified device. + * + * @param devices Pointer to an array of PulseSink structures. + * @param device_index Index of the device whose number of channels is to be retrieved. + * + * @return Number of channels for the specified device. Returns -1 on error. + */ +int get_active_profile_channels(const pulseaudio_device *devices, int device_index) { + if (!devices || device_index < 0) { + fprintf(stderr, "[ERROR]: Invalid devices array or device index in get_device_channels.\n"); + return -1; // Return -1 or another indicator of failure + } + return devices[device_index].channel_map.channels; +} + + + +/** + * @brief Callback function belonging to set_volume. Triggers when audio volume is set. + * + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb1(pa_context *c, int success, void *userdata) { + (void) c; + pulseaudio_manager* manager = (pulseaudio_manager*) userdata; + + + if (!success) { + fprintf(stderr, "[ERROR]: Failed to set volume. Reason: %s\n", pa_strerror(pa_context_errno(c))); + } + + // Debug: Print cvolume values for each channel as percentages + //pa_cvolume cvolume = manager->devices[manager->active_device_index].volume; + + /*for (int i = 0; i < cvolume.channels; i++) { + float percentage = (cvolume.values[i] / (float)PA_VOLUME_NORM) * 100; + //printf("[DEBUG, volume_set_complete_cb()]: Channel %d cvolume value: %u (%.2f%%)\n", i, cvolume.values[i], percentage); + } + //printf("[DEBUG, volume_set_complete_cb invoked. Success: %d\n", success);*/ + + // Decrease the operations count and potentially signal the condition variable. + manager->operations_pending--; + + // Signal the main loop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Callback function belonging to set_volume. + * Triggers after audio volume levels are updated. + * + * @param c Pointer to the PulseAudio context. + * @param success Indicates the success (1) or failure (0) of the volume setting operation. + * @param userdata User data provided during the set_volume operation, expected to be of type `pulseaudio_manager`. + * + * @note On failure, an error message is printed to stderr with the reason for the failure. + * @note After the operations are processed, the function signals the main loop to continue. + */ +static void set_volume_cb2(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + pulseaudio_manager *self = (pulseaudio_manager *)userdata; + + if (eol > 0) { + //printf("[DEBUG, volume_check_cb()]: End-of-list reached.\n"); + self->operations_pending--; + return; + } + + if (!i) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for pa_sink_info.\n"); + self->operations_pending--; + return; + } + + if (!i->name) { + fprintf(stderr, "[ERROR, volume_check_cb()]: Null pointer for device name.\n"); + self->operations_pending--; + return; + } + + printf("[DEBUG, volume_check_cb()]: Processing device info for device: %s\n", i->name); + + // Update the volume values in the manager's devices structure + self->active_device->volume = i->volume; + + // Print the volume for each channel + for (int channel = 0; channel < i->volume.channels; channel++) { + //float volume_percentage = (float)i->volume.values[channel] / PA_VOLUME_NORM * 100.0; + //printf("Channel %d Volume: %.2f%%\n", channel, volume_percentage); + } + + //Signaling to continue. + pa_threaded_mainloop_signal(self->mainloop, 0); +} + + +/** + * @brief Set the volume for a specified device. + * + * This function allows setting the volume for a specific device based on the given percentage. + * The function performs various checks to ensure valid inputs and that the PulseAudio system is ready. + * It will adjust the volume for all channels of the device to the desired level. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the device to set the volume for. + * @param percentage Desired volume level as a percentage. + * @return Boolean indicating success or failure. + */ +bool set_volume(pulseaudio_manager *self, uint32_t device_index, float percentage) { + if (!self || percentage < 0.0f || percentage > 100.0f || device_index >= self->device_count) { + return false; + } + + // Convert percentage to volume + pa_volume_t volume = (percentage / 100.0) * PA_VOLUME_NORM; + if (volume >= PA_VOLUME_NORM) { + volume = PA_VOLUME_NORM - 1; + } + + // Debug: show index and desired volume. + //printf("[DEBUG, set_volume()] Index is: %i\n", device_index); + //printf("[DEBUG, set_volume()] Desired volume: %f%% (value: %u)\n", percentage, volume); + + // Ensure PulseAudio is ready and devices are loaded + if (self->pa_ready != 1 || self->devices_loaded != 1) { + return false; + } + + // Debug: Show channel volumes before the change. + /*for (int channel = 0; channel < self->devices[device_index].channel_map.channels; channel++) { + printf("[DEBUG, set_volume()]: Channel %d Before volume: %f%%\n", + channel, + 100.0 * self->devices[device_index].volume.values[channel] / PA_VOLUME_NORM); + }*/ + + // Create a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_init(&cvolume); + cvolume.channels = self->devices[device_index].channel_map.channels; // Manually set channels + pa_cvolume_set(&cvolume, cvolume.channels, volume); + + printf("[DEBUG, set_volume()] channels: %d\n", cvolume.channels); + + // Apply the volume change to the specific device by index and wait for the operation to complete + const char *device_name_to_change = self->devices[device_index].code; + self->operations_pending++; + pa_operation *op = pa_context_set_sink_volume_by_name(self->context, device_name_to_change, &cvolume, set_volume_cb1, self); + iterate(self, op); + + // Fetch the updated volume for the device and wait for the operation to complete + pa_operation *op2 = pa_context_get_sink_info_by_name(self->context, device_name_to_change, set_volume_cb2, self); + iterate(self, op2); + + return true; +} + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Create a new pulseaudio_manager instance. + * @return Returns a pointer to a manager structure. + */ +pulseaudio_manager *new_manager(void) { + pulseaudio_manager *manager = (pulseaudio_manager *)malloc(sizeof(pulseaudio_manager)); + if (!manager) return NULL; + + + // Initialize pointers to 0 -- good practice. + manager->device_count = 0; + manager->devices_loaded = 0; // Initialize to 0 + manager->load_devices = load_devices; + manager->destroy = destroy; + manager->switch_device = switch_device; + manager->set_volume = set_volume; + manager->get_active_device = get_active_device; + manager->iterate = iterate; + manager->get_active_profile_channels = get_active_profile_channels; + //manager->get_profiles_for_device = get_profiles_for_device; + manager->get_profile_channels = get_profile_channels; + manager->get_profile_count = get_profile_count; + + if(!initialize(manager)) { + free(manager->devices); + free(manager); + return NULL; + } + + manager->device_count = get_output_device_count(); + manager->devices = malloc(manager->device_count * sizeof(pulseaudio_manager)); + + if(!manager->devices) { + fprintf(stderr, "[new_manager()] Could not allocate memory for the manager devices.\n"); + return NULL; + } + +#if 0 + if (!load_devices(manager)) { + fprintf(stderr, "No audio devices were detected. Aborting.\n"); + destroy(manager); + return NULL; + } +#endif + + return manager; +} + +/** + * @brief Callback function to process the PulseAudio server information. + * + * This callback is invoked once the server information is available. It retrieves the default device name + * from the server response and determines the active device by matching the name with the available devices + * in the manager's list. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the pa_server_info structure containing the server's information. + * @param userdata User-provided data (expected to be a pointer to pulseaudio_manager). + */ +static void get_active_device_cb(pa_context *c, const pa_server_info *info, void *userdata) { + (void) c; + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + if (!info || !info->default_sink_name) { + fprintf(stderr, "[ERROR]: Null pointer in active_device_cb.\n"); + pa_threaded_mainloop_signal(manager->mainloop, 0); + return; + } + + // Get the default device name from the server information + const char *default_device_name = info->default_sink_name; + + // Iterate over the available devices to find the active one + for (uint32_t i = 0; i < manager->device_count; i++) { + if (strcmp(manager->devices[i].code, default_device_name) == 0) { + // Set the active device pointer when a match is found + manager->active_device = &manager->devices[i]; + break; + } + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Request the default device name from the PulseAudio server and determine the active device. + * + * This function initiates a request to retrieve the default device name (active device) from the PulseAudio server. + * Once the server provides this information, the active_device_cb callback is triggered to process the response. + * + * @param manager Pointer to the pulseaudio_manager instance. + */ +//TODO: REWORK THE WAITING LOGIC. +void get_active_device(pulseaudio_manager *manager) { + pa_threaded_mainloop_lock(manager->mainloop); + + // Request server information + pa_context_get_server_info(manager->context, get_active_device_cb, manager); + + // Wait until the iterate function signals that it's done + pa_threaded_mainloop_wait(manager->mainloop); + + pa_threaded_mainloop_unlock(manager->mainloop); + + // The original loop to wait for active_device_name to be set + int timeout = 50; // Number of iterations or timeout value + while (!manager->active_device->code && timeout-- > 0) { + manager->iterate(manager, NULL); + pa_threaded_mainloop_signal(manager->mainloop, 0); + } +} + + +/** + * @brief Frees the memory of a pulseaudio_manager instance. + * + * @param manager Pointer to the pulseaudio_manager instance to be deleted. + */ +void destroy(pulseaudio_manager *self) { + cleanup(self); + + if (self) { + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].code) { + free(self->devices[i].code); + self->devices[i].code = NULL; + } + if (self->devices[i].description) { + free(self->devices[i].description); + self->devices[i].description = NULL; + } + } + // Now, free profiles for each device (if they exist) + for (uint32_t i = 0; i < self->device_count; i++) { + if (self->devices[i].profiles) free(self->devices[i].profiles); + } + if (self->devices) free(self->devices); + free(self); + } +} diff --git a/v-0.07/easypulse_core.h b/v-0.07/easypulse_core.h new file mode 100644 index 0000000..9129942 --- /dev/null +++ b/v-0.07/easypulse_core.h @@ -0,0 +1,90 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; + +/** + * @brief Represents a PulseAudio devices. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Name of the device. + char *description; // Description of the device. + pa_cvolume volume; // Volume of the device. + /* pulseaudio_profile *active_profile; // Active alsa profile of this device. */ + pa_channel_map channel_map; // Channel map of the devices. + int mute; // Mute status of the devices (1 for muted, 0 for unmuted). + int min_play_channels; // The minimum number of playback channels of the device. + int max_play_channels; // The maximum number of playback channels of the device. + pulseaudio_profile *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *devices; // Array of available devices. + uint32_t device_count; // Count of available devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + int operations_pending; // Counter for pending operations. + pulseaudio_device *active_device; // Pointer to active device. + uint32_t current_device_index; // The devices being processed right now by the program. It's not necessarily the same as the playback device. + + bool (*initialize)(pulseaudio_manager *self); // Initializes the manager. + void (*destroy)(pulseaudio_manager *manager); // Destroys the manager. + bool (*load_devices)(pulseaudio_manager *self); // Loads available devices. + bool (*switch_device)(pulseaudio_manager *self, uint32_t devices_index); // Switches to a specified device. + bool (*set_volume)(pulseaudio_manager *self, uint32_t devices_index, float percentage); // Sets the volume to a specified percentage. + void (*get_active_device)(pulseaudio_manager *manager); // Assures that a pulseaudio operation is not pending. + void (*iterate)(pulseaudio_manager *manager, pa_operation *op); // Goes through every step of a threaded loop. + int (*get_active_profile_channels) (const pulseaudio_device *device, int device_index); // Gets the number of channels of an active profile. + //void (*get_profiles_for_device)(pulseaudio_manager *manager, const char* device_code); // Updates the profiles of a particular device. + void (*get_profile_channels)(pulseaudio_manager *manager); // Extracts profile channels by copying them to null sink. + uint32_t (*get_profile_count)(uint32_t card_index); // Gets the number of pulseaudio profiles in the system. +}; + +/** + * @brief Create a new pulseaudio_manager instance. + * @return A pointer to the newly created pulseaudio_manager instance. + */ +pulseaudio_manager* new_manager(void); + +bool load_devices(pulseaudio_manager *self); +int get_active_profile_channels(const pulseaudio_device *devices, int device_index); +bool set_volume(pulseaudio_manager *self, uint32_t devices_index, float percentage); +void iterate(pulseaudio_manager *manager, pa_operation *op); +int get_device_channels(const pulseaudio_device *devices, int devices_index); +void destroy(pulseaudio_manager *manager); +//void get_profiles_for_device(pulseaudio_manager *manager, const char* device_code); +void get_active_device(pulseaudio_manager *manager); +void get_profile_channels(pulseaudio_manager *manager); + +#endif // CORE_H diff --git a/v-0.07/examples/Makefile b/v-0.07/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.07/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.07/examples/alsa-mapper_pulseaudio-api b/v-0.07/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..a06e234 Binary files /dev/null and b/v-0.07/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.07/examples/alsa-mapper_pulseaudio-api.c b/v-0.07/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.07/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.07/examples/change-speaker-mode b/v-0.07/examples/change-speaker-mode new file mode 100755 index 0000000..9a3bbc1 Binary files /dev/null and b/v-0.07/examples/change-speaker-mode differ diff --git a/v-0.07/examples/change-speaker-mode.c b/v-0.07/examples/change-speaker-mode.c new file mode 100644 index 0000000..64fa962 --- /dev/null +++ b/v-0.07/examples/change-speaker-mode.c @@ -0,0 +1,88 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + #if 0 + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + #endif +} diff --git a/v-0.07/examples/error.txt b/v-0.07/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.07/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.07/examples/get-card-profiles-pulseaudio_api b/v-0.07/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..ee4bee2 Binary files /dev/null and b/v-0.07/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.07/examples/get-card-profiles-pulseaudio_api.c b/v-0.07/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.07/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.07/examples/print-input-sources b/v-0.07/examples/print-input-sources new file mode 100755 index 0000000..8465784 Binary files /dev/null and b/v-0.07/examples/print-input-sources differ diff --git a/v-0.07/examples/print-input-sources.c b/v-0.07/examples/print-input-sources.c new file mode 100644 index 0000000..fdb585e --- /dev/null +++ b/v-0.07/examples/print-input-sources.c @@ -0,0 +1,90 @@ +#include +#include "../system_query.h" + +int main() { + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] Alsa_id is: %s\n", alsa_id); + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + + uint32_t sample_rate = get_input_sample_rate(source_info->name); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.07/examples/print_volume_output_devices b/v-0.07/examples/print_volume_output_devices new file mode 100755 index 0000000..aae0366 Binary files /dev/null and b/v-0.07/examples/print_volume_output_devices differ diff --git a/v-0.07/examples/print_volume_output_devices.c b/v-0.07/examples/print_volume_output_devices.c new file mode 100644 index 0000000..9546a1e --- /dev/null +++ b/v-0.07/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf("Device %u: %s\n", i, sinks[i]->name); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.07/examples/switch-sink b/v-0.07/examples/switch-sink new file mode 100755 index 0000000..f25b460 Binary files /dev/null and b/v-0.07/examples/switch-sink differ diff --git a/v-0.07/examples/switch-sink-pulseaudio b/v-0.07/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..ec5a352 Binary files /dev/null and b/v-0.07/examples/switch-sink-pulseaudio differ diff --git a/v-0.07/examples/switch-sink-pulseaudio.c b/v-0.07/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.07/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.07/examples/switch-sink.c b/v-0.07/examples/switch-sink.c new file mode 100644 index 0000000..45a8739 --- /dev/null +++ b/v-0.07/examples/switch-sink.c @@ -0,0 +1,61 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} diff --git a/v-0.07/examples/volume-change b/v-0.07/examples/volume-change new file mode 100755 index 0000000..7ff9e51 Binary files /dev/null and b/v-0.07/examples/volume-change differ diff --git a/v-0.07/examples/volume-change-pulseaudio b/v-0.07/examples/volume-change-pulseaudio new file mode 100755 index 0000000..adf5643 Binary files /dev/null and b/v-0.07/examples/volume-change-pulseaudio differ diff --git a/v-0.07/examples/volume-change-pulseaudio.c b/v-0.07/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.07/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.07/examples/volume-change.c b/v-0.07/examples/volume-change.c new file mode 100644 index 0000000..6600e95 --- /dev/null +++ b/v-0.07/examples/volume-change.c @@ -0,0 +1,82 @@ +/** + * @file volume-change.c + * @brief PulseAudio Volume Control Application + * + * This application provides a simple interface to interact with PulseAudio. + * It displays the current active device, its name, and the master volume. + * The user can then input a new volume value, which the application attempts + * to set. The result of the operation (success or failure) is displayed to the user. + * + * The application leverages the EasyPulse library to communicate with PulseAudio. + * + * + * @date 10-08-2023 (creation date) + */ + +#include +#include +#include "../easypulse_core.h" + +int main() { + // Initialize the pulseaudio manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + pulseaudio_device *active_device = self->active_device; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + +#if 0 + // Display the channels of the active profile + uint32_t total_channels = manager->get_active_profile_channels(active_device, active_device->index); + + printf("[DEBUG] The active device is: %s\n", active_device->code); + printf("[DEBUG] The following profiles were found:\n"); + + for (uint32_t i = 0; i < active_device->profile_count; i++) { + if (active_device->profiles[i].name) { + printf("[DEBUG] Profile name: %s\n", active_device->profiles[i].name); + } else { + printf("[DEBUG] Profile name is NULL or invalid.\n"); + } + printf("[DEBUG] This profile has %d channels.\n", active_device->profiles[i].channels); + } + + + printf("Current device: %s\n", manager->active_device->code); + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume before change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } + + printf("Master Volume: %f%%\n", + (100.0 * pa_cvolume_avg(&manager->active_device->volume) / PA_VOLUME_NORM)); + + + // Ask the user for a new volume value + float new_volume; + printf("Enter a new volume value (0.0 - 100.0): "); + scanf("%f", &new_volume); + + // Set the new volume + manager->set_volume(manager, manager->active_device->index, new_volume); + + + // Debug: Print the volume of individual channels of the active device + for (uint32_t channel = 0; channel < total_channels; channel++) { + printf("Channel %d volume after change: %f%%\n", + channel, + (100.0 * manager->active_device->volume.values[channel] / PA_VOLUME_NORM)); + } +#endif + // Cleanup and exit + manager->destroy(self); + return 0; + +} diff --git a/v-0.07/libeasypulse_core.a b/v-0.07/libeasypulse_core.a new file mode 100644 index 0000000..3ad5769 Binary files /dev/null and b/v-0.07/libeasypulse_core.a differ diff --git a/v-0.07/system_query.c b/v-0.07/system_query.c new file mode 100644 index 0000000..54a7aa4 --- /dev/null +++ b/v-0.07/system_query.c @@ -0,0 +1,1712 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + const char *alsa_name; + const char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +#define MAX_CHANNELS 32 // You can adjust this number based on expected maximum channels + +typedef struct channel_info_s { + char **channel_names; // Pointer to an array of string pointers + int num_channels; // Counter for the number of channels +} channel_info_t; + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +const char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "get_alsa_input_name(): Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +const char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +const char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_input_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "get_alsa_input_id(): Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +const char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + +static void get_input_sample_rate_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + if (eol < 0) { + // An error occurred + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // No more entries + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Extract the sample rate and store it in the user data + uint32_t *sample_rate = userdata; + *sample_rate = i->sample_spec.rate; +} + +uint32_t get_input_sample_rate(const char *source_name) { + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "PulseAudio is not initialized.\n"); + return 0; + } + + uint32_t sample_rate = 0; + pa_operation *op = NULL; + + + // Get the source info, passing the address of sample_rate as user data + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_input_sample_rate_cb, &sample_rate); + iterate(op); + + return sample_rate; +} + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate; + int err; + + if (!alsa_id || !sink_info) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; // Return sample rate from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + //fprintf(stderr, "Error getting sample rate for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.rate; + } + + snd_pcm_close(handle); + return sample_rate; +} + +// Callback for source information to get ports +void get_source_ports(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +// Callback for source information to get the active port +void get_active_port(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +// Function to collect source port information and return it +pa_source_info_list* get_source_port_info() { + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_ports, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_active_port, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + // Check if the sink_info is NULL + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + //fprintf(stderr, "[DEBUG] %s called with eol=%d\n", __FUNCTION__, eol); // Debug statement for entry + (void)userdata; // Unused parameter + //fprintf(stderr, "[DEBUG] %s called with eol=%d\n", __FUNCTION__, eol); // Debug statement for entry + + // Error or end of list + if (eol < 0) { + //fprintf(stderr, "Failed to get source info.\n"); + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list, signal the main loop to stop waiting + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate or resize the sources array as needed + if (shared_data_sources.count >= shared_data_sources.allocated) { + uint32_t new_size = (shared_data_sources.allocated + 8) * sizeof(pa_source_info *); + pa_source_info **temp = realloc(shared_data_sources.sources, new_size); + if (!temp) { + //fprintf(stderr, "Out of memory.\n"); + //fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated += 8; + } + + // Allocate memory for a new pa_source_info structure + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Failed to allocate memory for source info.\n"); + fprintf(stderr, "[DEBUG] Signaling main loop to continue.\n"); // Debug statement before signaling + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the information from the callback + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + // Duplicate the strings to ensure they will remain valid + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + } + + // Increase the count of found sources + shared_data_sources.count++; +} + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "PulseAudio is not initialized.\\n"); + return NULL; + } + + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + + // Prepare the PulseAudio operation to list the sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + + // Wait for the operation to complete + iterate(op); + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + shared_data_sources.sources[shared_data_sources.count] = NULL; + + return shared_data_sources.sources; +} + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_input_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *alsa_id, int num_channels) { + // Validate input parameters + if (!alsa_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(alsa_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *alsa_id, int num_channels) { + // Validate input parameters + if (!alsa_id) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(alsa_id); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + + + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * @warning The function returns NULL if the sink_name argument is NULL, or if the + * get_available_output_devices function fails to retrieve the sinks. + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} diff --git a/v-0.07/system_query.h b/v-0.07/system_query.h new file mode 100644 index 0000000..93fa8d3 --- /dev/null +++ b/v-0.07/system_query.h @@ -0,0 +1,65 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. +const char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). +pa_source_info *get_input_device_by_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). +const char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char** get_input_channel_names(const char *alsa_id, int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *sink_name, int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +const char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +const char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +uint32_t get_input_sample_rate(const char *source_name); //Gets the sample rate of a pulseaudio source (input device). +pa_sink_info *get_output_device_by_name(const char *sink_name); //Gets output device by name. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + + +#endif diff --git a/v-0.08/Makefile b/v-0.08/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.08/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.08/documentation/pa_context -- interface overview.docx b/v-0.08/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.08/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.08/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.08/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.08/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.08/documentation/pulseaudio/introspect.c summary b/v-0.08/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.08/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.08/documentation/pulseaudio/mainloop code flow.txt b/v-0.08/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.08/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.08/easypulse_core.c b/v-0.08/easypulse_core.c new file mode 100644 index 0000000..6351b7c --- /dev/null +++ b/v-0.08/easypulse_core.c @@ -0,0 +1,458 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + self->outputs[i].sample_rate = get_output_sample_rate(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + // Add any additional fields to populate + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + self->inputs[i].sample_rate = get_input_sample_rate(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + // Add any additional fields to populate + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + // Don't forget to clean up the temporary lists of devices after you're done with them + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +// Callback function to handle mute toggling completion +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} diff --git a/v-0.08/easypulse_core.h b/v-0.08/easypulse_core.h new file mode 100644 index 0000000..c9b9bcf --- /dev/null +++ b/v-0.08/easypulse_core.h @@ -0,0 +1,86 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume to muted / unmuted. + +#endif // CORE_H diff --git a/v-0.08/examples/Makefile b/v-0.08/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.08/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.08/examples/alsa-mapper_pulseaudio-api b/v-0.08/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..a263904 Binary files /dev/null and b/v-0.08/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.08/examples/alsa-mapper_pulseaudio-api.c b/v-0.08/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.08/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.08/examples/change-speaker-mode b/v-0.08/examples/change-speaker-mode new file mode 100755 index 0000000..f51724e Binary files /dev/null and b/v-0.08/examples/change-speaker-mode differ diff --git a/v-0.08/examples/change-speaker-mode.c b/v-0.08/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.08/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.08/examples/error.txt b/v-0.08/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.08/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.08/examples/get-card-profiles-pulseaudio_api b/v-0.08/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..d1089d1 Binary files /dev/null and b/v-0.08/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.08/examples/get-card-profiles-pulseaudio_api.c b/v-0.08/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.08/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.08/examples/mute_output_demo b/v-0.08/examples/mute_output_demo new file mode 100755 index 0000000..1c8e305 Binary files /dev/null and b/v-0.08/examples/mute_output_demo differ diff --git a/v-0.08/examples/mute_output_demo.c b/v-0.08/examples/mute_output_demo.c new file mode 100644 index 0000000..1d427ed --- /dev/null +++ b/v-0.08/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date 09-11-2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == 1 ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if (index >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.08/examples/print-input-sources b/v-0.08/examples/print-input-sources new file mode 100755 index 0000000..ca0f955 Binary files /dev/null and b/v-0.08/examples/print-input-sources differ diff --git a/v-0.08/examples/print-input-sources.c b/v-0.08/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.08/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.08/examples/print_volume_output_devices b/v-0.08/examples/print_volume_output_devices new file mode 100755 index 0000000..4f11b2f Binary files /dev/null and b/v-0.08/examples/print_volume_output_devices differ diff --git a/v-0.08/examples/print_volume_output_devices.c b/v-0.08/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.08/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.08/examples/switch-sink b/v-0.08/examples/switch-sink new file mode 100755 index 0000000..3f9b4ee Binary files /dev/null and b/v-0.08/examples/switch-sink differ diff --git a/v-0.08/examples/switch-sink-pulseaudio b/v-0.08/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..5d72de3 Binary files /dev/null and b/v-0.08/examples/switch-sink-pulseaudio differ diff --git a/v-0.08/examples/switch-sink-pulseaudio.c b/v-0.08/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.08/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.08/examples/switch-sink.c b/v-0.08/examples/switch-sink.c new file mode 100644 index 0000000..6f4111b --- /dev/null +++ b/v-0.08/examples/switch-sink.c @@ -0,0 +1,67 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +#if 0 +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.08/examples/volume-change b/v-0.08/examples/volume-change new file mode 100755 index 0000000..9065633 Binary files /dev/null and b/v-0.08/examples/volume-change differ diff --git a/v-0.08/examples/volume-change-pulseaudio b/v-0.08/examples/volume-change-pulseaudio new file mode 100755 index 0000000..8c984c0 Binary files /dev/null and b/v-0.08/examples/volume-change-pulseaudio differ diff --git a/v-0.08/examples/volume-change-pulseaudio.c b/v-0.08/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.08/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.08/examples/volume-change.c b/v-0.08/examples/volume-change.c new file mode 100644 index 0000000..013d39d --- /dev/null +++ b/v-0.08/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author mbyte-2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.08/libeasypulse_core.a b/v-0.08/libeasypulse_core.a new file mode 100644 index 0000000..90cb2ad Binary files /dev/null and b/v-0.08/libeasypulse_core.a differ diff --git a/v-0.08/system_query.c b/v-0.08/system_query.c new file mode 100644 index 0000000..e9b842e --- /dev/null +++ b/v-0.08/system_query.c @@ -0,0 +1,2211 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "get_alsa_input_name(): Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_input_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "get_alsa_input_id(): Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +// Callback for source information to get ports +void get_source_ports(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +// Callback for source information to get the active port +void get_active_port(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +// Function to collect source port information and return it +pa_source_info_list* get_source_port_info() { + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_ports, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_active_port, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + // Check if the sink_info is NULL + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_input_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + + + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * @warning The function returns NULL if the sink_name argument is NULL, or if the + * get_available_output_devices function fails to retrieve the sinks. + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + + + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} diff --git a/v-0.08/system_query.h b/v-0.08/system_query.h new file mode 100644 index 0000000..230291f --- /dev/null +++ b/v-0.08/system_query.h @@ -0,0 +1,85 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_source_info *get_input_device_by_name(const char *source_name); //Gets alsa name of a pulseaudio source (input device) by its name. + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (input device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_sink_info *get_output_device_by_name(const char *sink_name); //Gets output device by name. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + + +#endif diff --git a/v-0.09/Makefile b/v-0.09/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.09/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.09/documentation/pa_context -- interface overview.docx b/v-0.09/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.09/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.09/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.09/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.09/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.09/documentation/pulseaudio/introspect.c summary b/v-0.09/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.09/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.09/documentation/pulseaudio/mainloop code flow.txt b/v-0.09/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.09/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.09/easypulse_core.c b/v-0.09/easypulse_core.c new file mode 100644 index 0000000..e3bf574 --- /dev/null +++ b/v-0.09/easypulse_core.c @@ -0,0 +1,528 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + self->outputs[i].sample_rate = get_output_sample_rate(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + // Add any additional fields to populate + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + self->inputs[i].sample_rate = get_input_sample_rate(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + // Add any additional fields to populate + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + // Don't forget to clean up the temporary lists of devices after you're done with them + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} diff --git a/v-0.09/easypulse_core.h b/v-0.09/easypulse_core.h new file mode 100644 index 0000000..be7b003 --- /dev/null +++ b/v-0.09/easypulse_core.h @@ -0,0 +1,89 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +#endif // CORE_H diff --git a/v-0.09/examples/Makefile b/v-0.09/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.09/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.09/examples/alsa-mapper_pulseaudio-api b/v-0.09/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..66b32de Binary files /dev/null and b/v-0.09/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.09/examples/alsa-mapper_pulseaudio-api.c b/v-0.09/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.09/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.09/examples/change-speaker-mode b/v-0.09/examples/change-speaker-mode new file mode 100755 index 0000000..8d77203 Binary files /dev/null and b/v-0.09/examples/change-speaker-mode differ diff --git a/v-0.09/examples/change-speaker-mode.c b/v-0.09/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.09/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.09/examples/error.txt b/v-0.09/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.09/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.09/examples/get-card-profiles-pulseaudio_api b/v-0.09/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..fa948d7 Binary files /dev/null and b/v-0.09/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.09/examples/get-card-profiles-pulseaudio_api.c b/v-0.09/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.09/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.09/examples/mute_input_demo b/v-0.09/examples/mute_input_demo new file mode 100755 index 0000000..45fb374 Binary files /dev/null and b/v-0.09/examples/mute_input_demo differ diff --git a/v-0.09/examples/mute_input_demo.c b/v-0.09/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.09/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.09/examples/mute_output_demo b/v-0.09/examples/mute_output_demo new file mode 100755 index 0000000..51c733a Binary files /dev/null and b/v-0.09/examples/mute_output_demo differ diff --git a/v-0.09/examples/mute_output_demo.c b/v-0.09/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.09/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.09/examples/print-input-sources b/v-0.09/examples/print-input-sources new file mode 100755 index 0000000..b5b9fac Binary files /dev/null and b/v-0.09/examples/print-input-sources differ diff --git a/v-0.09/examples/print-input-sources.c b/v-0.09/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.09/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.09/examples/print_volume_output_devices b/v-0.09/examples/print_volume_output_devices new file mode 100755 index 0000000..b392cdb Binary files /dev/null and b/v-0.09/examples/print_volume_output_devices differ diff --git a/v-0.09/examples/print_volume_output_devices.c b/v-0.09/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.09/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.09/examples/switch-sink b/v-0.09/examples/switch-sink new file mode 100755 index 0000000..d849c0d Binary files /dev/null and b/v-0.09/examples/switch-sink differ diff --git a/v-0.09/examples/switch-sink-pulseaudio b/v-0.09/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..c9629d9 Binary files /dev/null and b/v-0.09/examples/switch-sink-pulseaudio differ diff --git a/v-0.09/examples/switch-sink-pulseaudio.c b/v-0.09/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.09/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.09/examples/switch-sink.c b/v-0.09/examples/switch-sink.c new file mode 100644 index 0000000..6f4111b --- /dev/null +++ b/v-0.09/examples/switch-sink.c @@ -0,0 +1,67 @@ +/** + * Demo Code: PulseAudio Sink Switcher + * + * This demonstration code showcases the functionality of switching audio sinks + * using the PulseAudio API. It initializes the PulseAudio manager, loads available + * audio sinks, prompts the user to select a sink, and then switches to the chosen sink. + * + * Note: Ensure the PulseAudio server is running and in a good state before executing. + */ +#include "../easypulse_core.h" +#include +#include + +#if 0 +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = new_manager(); + pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + manager->destroy(self); + return 1; + } + + // Display available devices to the user + printf("Available Sinks:\n"); + for (uint32_t i = 0; i < manager->device_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->devices[i].code, manager->devices[i].description); + } + + // Prompt the user to select a device + printf("Enter the number of the sink you want to switch to: "); + uint32_t choice; + scanf("%d", &choice); + + + // Validate the user's choice + if (choice < 1 || choice > manager->device_count) { + fprintf(stderr, "Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + + // Switch to the selected device + if (manager->switch_device(manager, choice - 1)) { + printf("Successfully switched to the selected device.\n"); + + // Debug code to print the default device after the switch + fprintf(stderr, "[DEBUG]: Default device after switch: %s\n", manager->active_device->code); + } + else { + fprintf(stderr, "Failed to switch to the selected device.\n"); + return 1; + } + + // Cleanup + manager->destroy(self); + + return 0; +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.09/examples/volume-change b/v-0.09/examples/volume-change new file mode 100755 index 0000000..c3d6843 Binary files /dev/null and b/v-0.09/examples/volume-change differ diff --git a/v-0.09/examples/volume-change-pulseaudio b/v-0.09/examples/volume-change-pulseaudio new file mode 100755 index 0000000..1a0e992 Binary files /dev/null and b/v-0.09/examples/volume-change-pulseaudio differ diff --git a/v-0.09/examples/volume-change-pulseaudio.c b/v-0.09/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.09/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.09/examples/volume-change.c b/v-0.09/examples/volume-change.c new file mode 100644 index 0000000..013d39d --- /dev/null +++ b/v-0.09/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author mbyte-2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.09/libeasypulse_core.a b/v-0.09/libeasypulse_core.a new file mode 100644 index 0000000..ab94362 Binary files /dev/null and b/v-0.09/libeasypulse_core.a differ diff --git a/v-0.09/system_query.c b/v-0.09/system_query.c new file mode 100644 index 0000000..98e6134 --- /dev/null +++ b/v-0.09/system_query.c @@ -0,0 +1,2376 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "get_alsa_input_name(): Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_input_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "get_alsa_input_id(): Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +// Callback for source information to get ports +void get_source_ports(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +// Callback for source information to get the active port +void get_active_port(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +// Function to collect source port information and return it +pa_source_info_list* get_source_port_info() { + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_ports, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_active_port, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + // Check if the sink_info is NULL + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_input_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + + + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * @warning The function returns NULL if the sink_name argument is NULL, or if the + * get_available_output_devices function fails to retrieve the sinks. + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} diff --git a/v-0.09/system_query.h b/v-0.09/system_query.h new file mode 100644 index 0000000..300bd0c --- /dev/null +++ b/v-0.09/system_query.h @@ -0,0 +1,88 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_source_info *get_input_device_by_name(const char *source_name); //Gets alsa name of a pulseaudio source (input device) by its name. + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_sink_info *get_output_device_by_name(const char *sink_name); //Gets output device by name. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + + +#endif diff --git a/v-0.10/Makefile b/v-0.10/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.10/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.10/documentation/pa_context -- interface overview.docx b/v-0.10/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.10/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.10/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.10/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.10/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.10/documentation/pulseaudio/introspect.c summary b/v-0.10/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.10/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.10/documentation/pulseaudio/mainloop code flow.txt b/v-0.10/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.10/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.10/easypulse_core.c b/v-0.10/easypulse_core.c new file mode 100644 index 0000000..3695828 --- /dev/null +++ b/v-0.10/easypulse_core.c @@ -0,0 +1,606 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} shared_data_1; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + self->outputs[i].sample_rate = get_output_sample_rate(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(get_alsa_output_id(output_devices[i]->name), output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + // Add any additional fields to populate + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + self->inputs[i].sample_rate = get_input_sample_rate(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(get_alsa_input_id(input_devices[i]->name), input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + // Add any additional fields to populate + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + // Don't forget to clean up the temporary lists of devices after you're done with them + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + + + +// Callback for setting the default sink +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +// Callback for moving sink inputs +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + shared_data_1 *shared_data = (shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + + + diff --git a/v-0.10/easypulse_core.h b/v-0.10/easypulse_core.h new file mode 100644 index 0000000..c273dfc --- /dev/null +++ b/v-0.10/easypulse_core.h @@ -0,0 +1,92 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default sink. + +#endif // CORE_H diff --git a/v-0.10/examples/Makefile b/v-0.10/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.10/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.10/examples/alsa-mapper_pulseaudio-api b/v-0.10/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..1c3d200 Binary files /dev/null and b/v-0.10/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.10/examples/alsa-mapper_pulseaudio-api.c b/v-0.10/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.10/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.10/examples/change-speaker-mode b/v-0.10/examples/change-speaker-mode new file mode 100755 index 0000000..3c9cbd6 Binary files /dev/null and b/v-0.10/examples/change-speaker-mode differ diff --git a/v-0.10/examples/change-speaker-mode.c b/v-0.10/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.10/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.10/examples/error.txt b/v-0.10/examples/error.txt new file mode 100644 index 0000000..edb3387 --- /dev/null +++ b/v-0.10/examples/error.txt @@ -0,0 +1,103 @@ +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Operation state at cycle 18: 0 +Operation state at cycle 19: 0 +Operation state at cycle 20: 0 +Operation state at cycle 21: 0 +Operation state at cycle 22: 0 +Operation state at cycle 23: 0 +Operation state at cycle 24: 0 +Operation state at cycle 25: 0 +Operation state at cycle 26: 0 +Operation state at cycle 27: 0 +Operation state at cycle 28: 0 +Operation state at cycle 29: 0 +Operation state at cycle 30: 0 +Operation state at cycle 31: 0 +Operation state at cycle 32: 0 +Operation state at cycle 33: 0 +Operation state at cycle 34: 0 +Operation state at cycle 35: 0 +Operation state at cycle 36: 0 +Operation state at cycle 37: 0 +Operation state at cycle 38: 0 +Operation state at cycle 39: 0 +Operation state at cycle 40: 0 +Operation state at cycle 41: 0 +Operation state at cycle 42: 0 +Operation state at cycle 43: 0 +Operation state at cycle 44: 0 +Operation state at cycle 45: 0 +Operation state at cycle 46: 0 +Operation state at cycle 47: 0 +Operation state at cycle 48: 0 +Operation state at cycle 49: 0 +Operation state at cycle 50: 0 +Operation state at cycle 51: 0 +Operation state at cycle 52: 0 +Operation state at cycle 53: 0 +Operation state at cycle 54: 0 +Operation state at cycle 55: 0 +Operation state at cycle 56: 0 +Operation state at cycle 57: 0 +Operation state at cycle 58: 0 +Operation state at cycle 59: 0 +Operation state at cycle 60: 0 +Operation state at cycle 61: 1 +Operation state at cycle 0: 0 +Operation state at cycle 1: 0 +Operation state at cycle 2: 0 +Operation state at cycle 3: 0 +Operation state at cycle 4: 0 +Operation state at cycle 5: 0 +Operation state at cycle 6: 0 +Operation state at cycle 7: 0 +Operation state at cycle 8: 0 +Operation state at cycle 9: 0 +Operation state at cycle 10: 0 +Operation state at cycle 11: 0 +Operation state at cycle 12: 0 +Operation state at cycle 13: 0 +Operation state at cycle 14: 0 +Operation state at cycle 15: 0 +Operation state at cycle 16: 0 +Operation state at cycle 17: 0 +Assertion '!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)' failed at ../pulseaudio/src/pulse/thread-mainloop.c:179, function pa_threaded_mainloop_lock(). Aborting. diff --git a/v-0.10/examples/get-card-profiles-pulseaudio_api b/v-0.10/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..7e5ec4c Binary files /dev/null and b/v-0.10/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.10/examples/get-card-profiles-pulseaudio_api.c b/v-0.10/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.10/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.10/examples/mute_input_demo b/v-0.10/examples/mute_input_demo new file mode 100755 index 0000000..4268ffa Binary files /dev/null and b/v-0.10/examples/mute_input_demo differ diff --git a/v-0.10/examples/mute_input_demo.c b/v-0.10/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.10/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.10/examples/mute_output_demo b/v-0.10/examples/mute_output_demo new file mode 100755 index 0000000..05ad014 Binary files /dev/null and b/v-0.10/examples/mute_output_demo differ diff --git a/v-0.10/examples/mute_output_demo.c b/v-0.10/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.10/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.10/examples/print-input-sources b/v-0.10/examples/print-input-sources new file mode 100755 index 0000000..8843366 Binary files /dev/null and b/v-0.10/examples/print-input-sources differ diff --git a/v-0.10/examples/print-input-sources.c b/v-0.10/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.10/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.10/examples/print_volume_output_devices b/v-0.10/examples/print_volume_output_devices new file mode 100755 index 0000000..aa40e83 Binary files /dev/null and b/v-0.10/examples/print_volume_output_devices differ diff --git a/v-0.10/examples/print_volume_output_devices.c b/v-0.10/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.10/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.10/examples/switch-sink b/v-0.10/examples/switch-sink new file mode 100755 index 0000000..fbfa578 Binary files /dev/null and b/v-0.10/examples/switch-sink differ diff --git a/v-0.10/examples/switch-sink-pulseaudio b/v-0.10/examples/switch-sink-pulseaudio new file mode 100755 index 0000000..5a2484c Binary files /dev/null and b/v-0.10/examples/switch-sink-pulseaudio differ diff --git a/v-0.10/examples/switch-sink-pulseaudio.c b/v-0.10/examples/switch-sink-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.10/examples/switch-sink-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.10/examples/switch-sink.c b/v-0.10/examples/switch-sink.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.10/examples/switch-sink.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.10/examples/volume-change b/v-0.10/examples/volume-change new file mode 100755 index 0000000..a1b1c02 Binary files /dev/null and b/v-0.10/examples/volume-change differ diff --git a/v-0.10/examples/volume-change-pulseaudio b/v-0.10/examples/volume-change-pulseaudio new file mode 100755 index 0000000..bec90d7 Binary files /dev/null and b/v-0.10/examples/volume-change-pulseaudio differ diff --git a/v-0.10/examples/volume-change-pulseaudio.c b/v-0.10/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.10/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.10/examples/volume-change.c b/v-0.10/examples/volume-change.c new file mode 100644 index 0000000..013d39d --- /dev/null +++ b/v-0.10/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author mbyte-2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.10/libeasypulse_core.a b/v-0.10/libeasypulse_core.a new file mode 100644 index 0000000..2193944 Binary files /dev/null and b/v-0.10/libeasypulse_core.a differ diff --git a/v-0.10/system_query.c b/v-0.10/system_query.c new file mode 100644 index 0000000..a06a0cd --- /dev/null +++ b/v-0.10/system_query.c @@ -0,0 +1,2492 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "get_alsa_input_name(): Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_input_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "get_alsa_input_id(): Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_alsa_id(): PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "get_alsa_id(): Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + + +/** + * @brief Retrieves the sample rate of the given ALSA device. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the sample rate. + * + * @param alsa_id Name of the ALSA device. + * @param sink_info Pointer to a PulseAudio sink_info structure. + * @return Sample rate of the device or -1 on error. + */ +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +// Callback for source information to get ports +void get_source_ports(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +// Callback for source information to get the active port +void get_active_port(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +// Function to collect source port information and return it +pa_source_info_list* get_source_port_info() { + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_ports, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_active_port, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + // Check if the sink_info is NULL + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_input_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + + + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * @warning The function returns NULL if the sink_name argument is NULL, or if the + * get_available_output_devices function fails to retrieve the sinks. + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + diff --git a/v-0.10/system_query.h b/v-0.10/system_query.h new file mode 100644 index 0000000..d8a52c6 --- /dev/null +++ b/v-0.10/system_query.h @@ -0,0 +1,95 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_source_info *get_input_device_by_name(const char *source_name); //Gets alsa name of a pulseaudio source (input device) by its name. + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_sink_info *get_output_device_by_name(const char *sink_name); //Gets output device by name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + + +#endif diff --git a/v-0.11/Makefile b/v-0.11/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.11/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.11/documentation/pa_context -- interface overview.docx b/v-0.11/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.11/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.11/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.11/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.11/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.11/documentation/pulseaudio/introspect.c summary b/v-0.11/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.11/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.11/documentation/pulseaudio/mainloop code flow.txt b/v-0.11/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.11/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.11/easypulse_core.c b/v-0.11/easypulse_core.c new file mode 100644 index 0000000..607b8a8 --- /dev/null +++ b/v-0.11/easypulse_core.c @@ -0,0 +1,892 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} shared_data_1; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + + //Initializing the stream like that is important so that we can modify the sample rate later. + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->outputs[i].sample_rate, + .channels = (uint8_t) self->outputs[i].max_channels + }; + + self->outputs[i].stream = pa_stream_new(self->context, self->outputs[i].name, &default_sample_spec, NULL); + if (!self->outputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->outputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE + if (pa_stream_connect_playback(self->outputs[i].stream, NULL, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->outputs[i].name); + pa_stream_unref(self->outputs[i].stream); + self->outputs[i].stream = NULL; + continue; + } + + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->inputs[i].sample_rate, + .channels = (uint8_t) self->inputs[i].max_channels + }; + + self->inputs[i].stream = pa_stream_new(self->context, self->inputs[i].name, &default_sample_spec, NULL); + + if (!self->inputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->inputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE to the specific device + if (pa_stream_connect_playback(self->inputs[i].stream, self->inputs[i].code, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->inputs[i].code); + pa_stream_unref(self->inputs[i].stream); + self->inputs[i].stream = NULL; + continue; + } + + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + shared_data_1 *shared_data = (shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + + + diff --git a/v-0.11/easypulse_core.h b/v-0.11/easypulse_core.h new file mode 100644 index 0000000..a3b4b3c --- /dev/null +++ b/v-0.11/easypulse_core.h @@ -0,0 +1,102 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles + pa_stream *stream; // Associated PulseAudio stream (audio flow to be played / recorded). +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + + +#endif // CORE_H diff --git a/v-0.11/examples/Makefile b/v-0.11/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.11/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.11/examples/alsa-mapper_pulseaudio-api b/v-0.11/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..7f7e94e Binary files /dev/null and b/v-0.11/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.11/examples/alsa-mapper_pulseaudio-api.c b/v-0.11/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.11/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.11/examples/change-speaker-mode b/v-0.11/examples/change-speaker-mode new file mode 100755 index 0000000..441118e Binary files /dev/null and b/v-0.11/examples/change-speaker-mode differ diff --git a/v-0.11/examples/change-speaker-mode.c b/v-0.11/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.11/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.11/examples/change_global_sample_rate b/v-0.11/examples/change_global_sample_rate new file mode 100755 index 0000000..81fc50b Binary files /dev/null and b/v-0.11/examples/change_global_sample_rate differ diff --git a/v-0.11/examples/change_global_sample_rate.c b/v-0.11/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.11/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.11/examples/get-card-profiles-pulseaudio_api b/v-0.11/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..c27be41 Binary files /dev/null and b/v-0.11/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.11/examples/get-card-profiles-pulseaudio_api.c b/v-0.11/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.11/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.11/examples/mute_input_demo b/v-0.11/examples/mute_input_demo new file mode 100755 index 0000000..5211d6f Binary files /dev/null and b/v-0.11/examples/mute_input_demo differ diff --git a/v-0.11/examples/mute_input_demo.c b/v-0.11/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.11/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.11/examples/mute_output_demo b/v-0.11/examples/mute_output_demo new file mode 100755 index 0000000..b2a02f0 Binary files /dev/null and b/v-0.11/examples/mute_output_demo differ diff --git a/v-0.11/examples/mute_output_demo.c b/v-0.11/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.11/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.11/examples/print-input-sources b/v-0.11/examples/print-input-sources new file mode 100755 index 0000000..e3a2f85 Binary files /dev/null and b/v-0.11/examples/print-input-sources differ diff --git a/v-0.11/examples/print-input-sources.c b/v-0.11/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.11/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.11/examples/print_volume_output_devices b/v-0.11/examples/print_volume_output_devices new file mode 100755 index 0000000..db24a00 Binary files /dev/null and b/v-0.11/examples/print_volume_output_devices differ diff --git a/v-0.11/examples/print_volume_output_devices.c b/v-0.11/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.11/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.11/examples/switch-input-devices b/v-0.11/examples/switch-input-devices new file mode 100755 index 0000000..d8715fd Binary files /dev/null and b/v-0.11/examples/switch-input-devices differ diff --git a/v-0.11/examples/switch-input-devices.c b/v-0.11/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.11/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.11/examples/switch-output-device b/v-0.11/examples/switch-output-device new file mode 100755 index 0000000..9830534 Binary files /dev/null and b/v-0.11/examples/switch-output-device differ diff --git a/v-0.11/examples/switch-output-device-pulseaudio b/v-0.11/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..5e427e2 Binary files /dev/null and b/v-0.11/examples/switch-output-device-pulseaudio differ diff --git a/v-0.11/examples/switch-output-device-pulseaudio.c b/v-0.11/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.11/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.11/examples/switch-output-device.c b/v-0.11/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.11/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.11/examples/volume-change b/v-0.11/examples/volume-change new file mode 100755 index 0000000..92e237a Binary files /dev/null and b/v-0.11/examples/volume-change differ diff --git a/v-0.11/examples/volume-change-pulseaudio b/v-0.11/examples/volume-change-pulseaudio new file mode 100755 index 0000000..dc9fe49 Binary files /dev/null and b/v-0.11/examples/volume-change-pulseaudio differ diff --git a/v-0.11/examples/volume-change-pulseaudio.c b/v-0.11/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.11/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.11/examples/volume-change.c b/v-0.11/examples/volume-change.c new file mode 100644 index 0000000..013d39d --- /dev/null +++ b/v-0.11/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author mbyte-2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.11/libeasypulse_core.a b/v-0.11/libeasypulse_core.a new file mode 100644 index 0000000..90915fd Binary files /dev/null and b/v-0.11/libeasypulse_core.a differ diff --git a/v-0.11/system_query.c b/v-0.11/system_query.c new file mode 100644 index 0000000..649a2eb --- /dev/null +++ b/v-0.11/system_query.c @@ -0,0 +1,2867 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + diff --git a/v-0.11/system_query.h b/v-0.11/system_query.h new file mode 100644 index 0000000..4043485 --- /dev/null +++ b/v-0.11/system_query.h @@ -0,0 +1,101 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +#endif diff --git a/v-0.12/Makefile b/v-0.12/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.12/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.12/documentation/pa_context -- interface overview.docx b/v-0.12/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.12/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.12/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.12/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.12/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.12/documentation/pulseaudio/introspect.c summary b/v-0.12/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.12/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.12/documentation/pulseaudio/mainloop code flow.txt b/v-0.12/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.12/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.12/easypulse_core.c b/v-0.12/easypulse_core.c new file mode 100644 index 0000000..1a96252 --- /dev/null +++ b/v-0.12/easypulse_core.c @@ -0,0 +1,1104 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, +int eol, void *userdata); + +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} _shared_data_1; + +//Shared data between manager_set_output_channel_mute_state and its callbacks +typedef struct _shared_data_2 { + pulseaudio_manager *manager; + uint32_t channel_index; + bool mute_state; + pa_cvolume new_volume; +} _shared_data_2; + +_shared_data_2 volume_data; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + + //Initializing the stream like that is important so that we can modify the sample rate later. + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->outputs[i].sample_rate, + .channels = (uint8_t) self->outputs[i].max_channels + }; + + self->outputs[i].stream = pa_stream_new(self->context, self->outputs[i].name, &default_sample_spec, NULL); + if (!self->outputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->outputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE + if (pa_stream_connect_playback(self->outputs[i].stream, NULL, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->outputs[i].name); + pa_stream_unref(self->outputs[i].stream); + self->outputs[i].stream = NULL; + continue; + } + + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->inputs[i].sample_rate, + .channels = (uint8_t) self->inputs[i].max_channels + }; + + self->inputs[i].stream = pa_stream_new(self->context, self->inputs[i].name, &default_sample_spec, NULL); + + if (!self->inputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->inputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE to the specific device + if (pa_stream_connect_playback(self->inputs[i].stream, self->inputs[i].code, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->inputs[i].code); + pa_stream_unref(self->inputs[i].stream); + self->inputs[i].stream = NULL; + continue; + } + + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + _shared_data_1 *shared_data = (_shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + _shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + +/** + * @brief Callback function for setting the mute state of a channel in a PulseAudio sink. + * + * This callback function is triggered by `pa_context_get_sink_info_by_index` to process + * information about a specific PulseAudio sink. It modifies the volume of a given channel + * in the sink to either muted or unmuted state, as specified in the user data. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type `struct volume_update_data`. + * + */ +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data.manager->mainloop, 0); + return; + } + + struct _shared_data_2 *data = (struct _shared_data_2 *)userdata; + + if (info) { + // Modify the volume of the specified channel + data->new_volume = info->volume; + data->new_volume.values[data->channel_index] = data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio sink. + * + * This callback function is used to verify the success of a volume set operation + * on a PulseAudio sink. It is called after attempting to set the volume of a specific + * channel within a sink, indicating whether the operation was successful. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data. This parameter is not used in this callback. + * + */ +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + (void) userdata; + + if (!success) { + fprintf(stderr, "Failed to set sink volume.\n"); + } + pa_threaded_mainloop_signal(volume_data.manager->mainloop, 0); +} + +/** + * @brief Sets the mute state for a single channel of an output device. + * + * This function controls the mute state of a specific channel for a given PulseAudio sink (output device). + * It uses the EasyPulse API to interact with the PulseAudio server. + * + * @param sink_index Index of the sink (output device) whose channel mute state is to be set. + * @param channel_index Index of the channel within the sink to be muted or unmuted. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, non-zero on failure. + * + */ +int manager_set_output_mute_state(pulseaudio_manager *self, uint32_t sink_index, +uint32_t channel_index, bool mute_state) { + + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, sink_index, + manager_set_output_channel_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start sink information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume + op = pa_context_set_sink_volume_by_index(self->context, sink_index, + &(volume_data.new_volume), manager_set_output_channel_mute_state_cb2, NULL); + + iterate(self, op); + + return 0; // Success +} + +/** + * @brief Callback function for handling input device information. + * + * This function is called in response to a request for information about a specific PulseAudio input device. + * It is used to modify the volume of a specified channel in the input device based on the mute state. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *)userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + if (info) { + // Modify the volume of the specified channel + volume_data->new_volume = info->volume; + pa_volume_t new_channel_volume = volume_data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + volume_data->new_volume.values[volume_data->channel_index] = new_channel_volume; + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio input device. + * + * This callback function is used to verify the success of setting the volume of a specified channel in + * a PulseAudio input device. It is called after an attempt to set the volume of a channel within an input device. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *)userdata; + + if (!success) { + fprintf(stderr, "Failed to set sink volume.\n"); + } + + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + + +/** + * @brief Sets the mute state for an entire PulseAudio input device. + * + * This function controls the mute state of a specified PulseAudio input device (source). + * It mutes or unmutes all channels of the input device based on the provided mute state. + * + * @param self Pointer to the pulseaudio_manager structure, containing the necessary PulseAudio context. + * @param input_index Index of the input device (source) whose mute state is to be set. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, -1 on failure. + * + */ +int manager_set_input_mute_state(pulseaudio_manager *self, uint32_t input_index, +uint32_t channel_index, bool mute_state) { + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, input_index, manager_set_input_mute_state_cb, self); + + if (!op) { + fprintf(stderr, "Failed to start sink information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume for the specified channel (effectively muting or unmuting the channel) + op = pa_context_set_sink_volume_by_index(self->context, input_index, + &(volume_data.new_volume), manager_set_input_mute_state_cb2, self); + + if (!op) { + fprintf(stderr, "Failed to start sink volume set operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; +} diff --git a/v-0.12/easypulse_core.h b/v-0.12/easypulse_core.h new file mode 100644 index 0000000..3d352ae --- /dev/null +++ b/v-0.12/easypulse_core.h @@ -0,0 +1,108 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles + pa_stream *stream; // Associated PulseAudio stream (audio flow to be played / recorded). +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + int devices_loaded; // Indicates if devices are loaded (0 for not loaded, 1 for loaded successfully, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + +int manager_set_output_mute_state(pulseaudio_manager *self, +uint32_t output_index, uint32_t channel_index, bool mute_state); //Changes a number of output channels to mute / unmuted. + +int manager_set_input_mute_state(pulseaudio_manager *self, +uint32_t input_index, uint32_t channel_index, bool mute_state); //Changes a number of input channels to mute / unmuted. + + +#endif // CORE_H diff --git a/v-0.12/examples/Makefile b/v-0.12/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.12/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.12/examples/alsa-mapper_pulseaudio-api b/v-0.12/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..4b63f1c Binary files /dev/null and b/v-0.12/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.12/examples/alsa-mapper_pulseaudio-api.c b/v-0.12/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..47eb39b --- /dev/null +++ b/v-0.12/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,91 @@ +/** + * @file pulseaudio_alsa_mapper.c + * @brief Demonstrates how to map PulseAudio sinks to their corresponding ALSA device names. + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.12/examples/change-speaker-mode b/v-0.12/examples/change-speaker-mode new file mode 100755 index 0000000..8aef6eb Binary files /dev/null and b/v-0.12/examples/change-speaker-mode differ diff --git a/v-0.12/examples/change-speaker-mode.c b/v-0.12/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.12/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.12/examples/change_global_sample_rate b/v-0.12/examples/change_global_sample_rate new file mode 100755 index 0000000..18e9e2c Binary files /dev/null and b/v-0.12/examples/change_global_sample_rate differ diff --git a/v-0.12/examples/change_global_sample_rate.c b/v-0.12/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.12/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.12/examples/get-card-profiles-pulseaudio_api b/v-0.12/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..db62124 Binary files /dev/null and b/v-0.12/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.12/examples/get-card-profiles-pulseaudio_api.c b/v-0.12/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.12/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.12/examples/mute_input_demo b/v-0.12/examples/mute_input_demo new file mode 100755 index 0000000..aed313f Binary files /dev/null and b/v-0.12/examples/mute_input_demo differ diff --git a/v-0.12/examples/mute_input_demo.c b/v-0.12/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.12/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.12/examples/mute_output_demo b/v-0.12/examples/mute_output_demo new file mode 100755 index 0000000..de70d9c Binary files /dev/null and b/v-0.12/examples/mute_output_demo differ diff --git a/v-0.12/examples/mute_output_demo.c b/v-0.12/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.12/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.12/examples/print-input-sources b/v-0.12/examples/print-input-sources new file mode 100755 index 0000000..dbaed01 Binary files /dev/null and b/v-0.12/examples/print-input-sources differ diff --git a/v-0.12/examples/print-input-sources.c b/v-0.12/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.12/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.12/examples/print_volume_output_devices b/v-0.12/examples/print_volume_output_devices new file mode 100755 index 0000000..65d01bb Binary files /dev/null and b/v-0.12/examples/print_volume_output_devices differ diff --git a/v-0.12/examples/print_volume_output_devices.c b/v-0.12/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.12/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.12/examples/switch-input-devices b/v-0.12/examples/switch-input-devices new file mode 100755 index 0000000..e9b2c30 Binary files /dev/null and b/v-0.12/examples/switch-input-devices differ diff --git a/v-0.12/examples/switch-input-devices.c b/v-0.12/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.12/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.12/examples/switch-output-device b/v-0.12/examples/switch-output-device new file mode 100755 index 0000000..1ae3fe8 Binary files /dev/null and b/v-0.12/examples/switch-output-device differ diff --git a/v-0.12/examples/switch-output-device-pulseaudio b/v-0.12/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..55716fe Binary files /dev/null and b/v-0.12/examples/switch-output-device-pulseaudio differ diff --git a/v-0.12/examples/switch-output-device-pulseaudio.c b/v-0.12/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.12/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.12/examples/switch-output-device.c b/v-0.12/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.12/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.12/examples/toggle_mute_output_demo b/v-0.12/examples/toggle_mute_output_demo new file mode 100755 index 0000000..9c1fc61 Binary files /dev/null and b/v-0.12/examples/toggle_mute_output_demo differ diff --git a/v-0.12/examples/toggle_mute_output_demo.c b/v-0.12/examples/toggle_mute_output_demo.c new file mode 100644 index 0000000..84a60ea --- /dev/null +++ b/v-0.12/examples/toggle_mute_output_demo.c @@ -0,0 +1,84 @@ +/** + * @file toggle_channels.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio output device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * output devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available output devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main() { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // List output devices + for (uint32_t i = 0; i < self->output_count; i++) { + printf("%d: %s\n", i, self->outputs[i].name); + } + + // User selects a device + uint32_t device_index; + printf("Enter the number of the device you want to select: "); + scanf("%d", &device_index); + + if (device_index >= self->output_count) { + fprintf(stderr, "Invalid device index\n"); + manager_cleanup(self); + return 1; + } + + pulseaudio_device *selected_device = &self->outputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + scanf(" %[^\n]", input); + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_output_mute_state(self, selected_device->index, channel, new_mute_state); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.12/examples/volume-change b/v-0.12/examples/volume-change new file mode 100755 index 0000000..074ae25 Binary files /dev/null and b/v-0.12/examples/volume-change differ diff --git a/v-0.12/examples/volume-change-pulseaudio b/v-0.12/examples/volume-change-pulseaudio new file mode 100755 index 0000000..aafaef4 Binary files /dev/null and b/v-0.12/examples/volume-change-pulseaudio differ diff --git a/v-0.12/examples/volume-change-pulseaudio.c b/v-0.12/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.12/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.12/examples/volume-change.c b/v-0.12/examples/volume-change.c new file mode 100644 index 0000000..013d39d --- /dev/null +++ b/v-0.12/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author mbyte-2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.12/libeasypulse_core.a b/v-0.12/libeasypulse_core.a new file mode 100644 index 0000000..a3fb0e1 Binary files /dev/null and b/v-0.12/libeasypulse_core.a differ diff --git a/v-0.12/system_query.c b/v-0.12/system_query.c new file mode 100644 index 0000000..a4cbbd1 --- /dev/null +++ b/v-0.12/system_query.c @@ -0,0 +1,3012 @@ +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +#define MUTED 1 +#define UNMUTED 0 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +// Strcture to share data between get_channel_mute_state and its callback. +typedef struct { + uint32_t channel_index; + bool mute_state; +} _shared_data_5; + + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + +/** + * @brief Callback function for handling sink information. + * + * This function is used as a callback to process information about a PulseAudio sink. + * It retrieves the mute state of a specific channel from the sink's volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the sink. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of sink information. + */ +static void get_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given sink. + * + * This function checks if the specified channel in a given sink is muted. It utilizes + * the PulseAudio API to query sink information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param sink_index The index of the sink whose channel mute state is to be checked. + * @param channel_index The index of the channel within the sink to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the sink or channel + * indices are invalid. + */ +bool get_output_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index) { + + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(context, sink_index, get_output_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for handling input device information. + * + * This function is used as a callback to process information about a PulseAudio input device. + * It retrieves the mute state of a specific channel from the input device volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the input device information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the input device. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of input device information. + */ +static void get_input_channel_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given input device. + * + * This function checks if the specified channel in a given input device is muted. It utilizes + * the PulseAudio API to query input device information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param source_index The index of the input device whose channel mute state is to be checked. + * @param channel_index The index of the channel within the input device to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the input device or channel + * indices are invalid. + */ +bool get_input_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index) { + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified input device + pa_operation *op = pa_context_get_source_info_by_index(context, source_index, get_input_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} diff --git a/v-0.12/system_query.h b/v-0.12/system_query.h new file mode 100644 index 0000000..ea9e22c --- /dev/null +++ b/v-0.12/system_query.h @@ -0,0 +1,109 @@ +//Header definition files to query about sound card properties (number of sinks, profiles...) +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +bool get_output_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index); //Gets mute state of single output channel (0 = unmuted, 1 = muted). + +bool get_input_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index); ///Gets mute state of single input channel (0 = unmuted, 1 = muted). + +#endif diff --git a/v-0.13/.system_query.c.kate-swp b/v-0.13/.system_query.c.kate-swp new file mode 100644 index 0000000..98d0cd5 Binary files /dev/null and b/v-0.13/.system_query.c.kate-swp differ diff --git a/v-0.13/Makefile b/v-0.13/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.13/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.13/documentation/pa_context -- interface overview.docx b/v-0.13/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.13/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.13/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.13/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.13/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.13/documentation/pulseaudio/introspect.c summary b/v-0.13/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.13/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.13/documentation/pulseaudio/mainloop code flow.txt b/v-0.13/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.13/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.13/easypulse_core.c b/v-0.13/easypulse_core.c new file mode 100644 index 0000000..f0bfb9b --- /dev/null +++ b/v-0.13/easypulse_core.c @@ -0,0 +1,1148 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, +int eol, void *userdata); + +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} _shared_data_1; + +//Shared data between manager_set_output_channel_mute_state and its callbacks +typedef struct _shared_data_2 { + pulseaudio_manager *manager; + uint32_t channel_index; + bool mute_state; + pa_cvolume new_volume; +} _shared_data_2; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + + //Initializing the stream like that is important so that we can modify the sample rate later. + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->outputs[i].sample_rate, + .channels = (uint8_t) self->outputs[i].max_channels + }; + + self->outputs[i].stream = pa_stream_new(self->context, self->outputs[i].name, &default_sample_spec, NULL); + if (!self->outputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->outputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE + if (pa_stream_connect_playback(self->outputs[i].stream, NULL, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->outputs[i].name); + pa_stream_unref(self->outputs[i].stream); + self->outputs[i].stream = NULL; + continue; + } + + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + + pa_sample_spec default_sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = self->inputs[i].sample_rate, + .channels = (uint8_t) self->inputs[i].max_channels + }; + + self->inputs[i].stream = pa_stream_new(self->context, self->inputs[i].name, &default_sample_spec, NULL); + + if (!self->inputs[i].stream) { + fprintf(stderr, "Failed to create stream for output device: %s\n", self->inputs[i].name); + continue; + } + + // Connect the stream with PA_STREAM_VARIABLE_RATE to the specific device + if (pa_stream_connect_playback(self->inputs[i].stream, self->inputs[i].code, NULL, PA_STREAM_VARIABLE_RATE, NULL, NULL) < 0) { + fprintf(stderr, "Failed to connect playback stream for device: %s\n", self->inputs[i].code); + pa_stream_unref(self->inputs[i].stream); + self->inputs[i].stream = NULL; + continue; + } + + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + _shared_data_1 *shared_data = (_shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + _shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + +/** + * @brief Callback function for setting the mute state of a channel in a PulseAudio sink. + * + * This callback function is triggered by `pa_context_get_sink_info_by_index` to process + * information about a specific PulseAudio sink. It modifies the volume of a given channel + * in the sink to either muted or unmuted state, as specified in the user data. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type `struct volume_update_data`. + * + */ +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + struct _shared_data_2 *data = (struct _shared_data_2 *)userdata; + + if (info) { + // Modify the volume of the specified channel + data->new_volume = info->volume; + data->new_volume.values[data->channel_index] = data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio sink. + * + * This callback function is used to verify the success of a volume set operation + * on a PulseAudio sink. It is called after attempting to set the volume of a specific + * channel within a sink, indicating whether the operation was successful. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data. This parameter is not used in this callback. + * + */ +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set output device volume.\n"); + } + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + +/** + * @brief Sets the mute state for a single channel of an output device. + * + * This function controls the mute state of a specific channel for a given PulseAudio sink (output device). + * It uses the EasyPulse API to interact with the PulseAudio server. + * + * @param sink_index Index of the sink (output device) whose channel mute state is to be set. + * @param channel_index Index of the channel within the sink to be muted or unmuted. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, non-zero on failure. + * + */ +int manager_set_output_mute_state(pulseaudio_manager *self, uint32_t sink_index, +uint32_t channel_index, bool mute_state) { + + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, sink_index, + manager_set_output_channel_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start output device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume + op = pa_context_set_sink_volume_by_index(self->context, sink_index, + &(volume_data.new_volume), manager_set_output_channel_mute_state_cb2, &volume_data); + + iterate(self, op); + + return 0; // Success +} + +/** + * @brief Callback function for handling input device information. + * + * This function is called in response to a request for information about a specific PulseAudio input device. + * It is used to modify the volume of a specified channel in the input device based on the mute state. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the source information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + //fprintf(stderr, "[DEBUG, manager_set_input_mute_state_cb()] info->description is, %s\n", info->description); + + if (info) { + // Modify the volume of the specified channel + volume_data->new_volume = info->volume; + pa_volume_t new_channel_volume = volume_data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + volume_data->new_volume.values[volume_data->channel_index] = new_channel_volume; + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio input device. + * + * This callback function is used to verify the success of setting the volume of a specified channel in + * a PulseAudio input device. It is called after an attempt to set the volume of a channel within an input device. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set input device volume.\n"); + } + + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + + +/** + * @brief Sets the mute state for a single channel of a PulseAudio input device. + * + * This function controls the mute state of a specified PulseAudio input device (source). + * It mutes or unmutes all channels of the input device based on the provided mute state. + * + * @param self Pointer to the pulseaudio_manager structure, containing the necessary PulseAudio context. + * @param input_index Index of the input device (source) whose mute state is to be set. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, -1 on failure. + * + */ +int manager_set_input_mute_state(pulseaudio_manager *self, uint32_t input_index, +uint32_t channel_index, bool mute_state) { + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified source + pa_operation *op = pa_context_get_source_info_by_index(self->context, input_index, manager_set_input_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start input device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume for the specified channel (effectively muting or unmuting the channel) + op = pa_context_set_source_volume_by_index(self->context, input_index, + &(volume_data.new_volume), manager_set_input_mute_state_cb2, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start source volume set operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; +} + +/** + * @brief Moves playback from one sink to another. + * + * This function moves all playback streams from one output device (sink) to another. + * It is used to switch the audio output from one device to another, for example, from + * speakers to headphones. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param sink1_index Index of the current sink (output device). + * @param sink2_index Index of the new sink (output device) to move streams to. + * @return Returns 0 on success, -1 on failure. + */ +int manager_move_output_playback(pulseaudio_manager *manager, uint32_t sink1_index, uint32_t sink2_index) { + if (!manager || sink1_index >= manager->output_count || sink2_index >= manager->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + _shared_data_1 shared_data = { manager, sink2_index }; + + // Lock the main loop for thread safety + pa_threaded_mainloop_lock(manager->mainloop); + + // Iterate over all sink inputs and move them to the new sink + pa_operation *op = pa_context_get_sink_input_info_list(manager->context, manager_switch_default_output_cb_2, &shared_data); + + if (!op) { + fprintf(stderr, "Failed to start sink input info list operation.\n"); + pa_threaded_mainloop_unlock(manager->mainloop); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(manager->mainloop); + + return 0; // Success +} diff --git a/v-0.13/easypulse_core.h b/v-0.13/easypulse_core.h new file mode 100644 index 0000000..baadcd6 --- /dev/null +++ b/v-0.13/easypulse_core.h @@ -0,0 +1,110 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles + pa_stream *stream; // Associated PulseAudio stream (audio flow to be played / recorded). +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + +int manager_set_output_mute_state(pulseaudio_manager *self, +uint32_t output_index, uint32_t channel_index, bool mute_state); //Changes a number of output channels to mute / unmuted. + +int manager_set_input_mute_state(pulseaudio_manager *self, +uint32_t input_index, uint32_t channel_index, bool mute_state); //Changes a number of input channels to mute / unmuted. + +int manager_move_output_playback(pulseaudio_manager *manager, +uint32_t sink1_index, uint32_t sink2_index); //Moves playback from one sink to another. + + +#endif // CORE_H diff --git a/v-0.13/examples/Makefile b/v-0.13/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.13/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.13/examples/alsa-mapper_pulseaudio-api b/v-0.13/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..d57623b Binary files /dev/null and b/v-0.13/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.13/examples/alsa-mapper_pulseaudio-api.c b/v-0.13/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..a9b0b52 --- /dev/null +++ b/v-0.13/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,113 @@ +/** + * @file alsa-mapper_pulseaudio-api.c + * @brief Fetches and displays UDEV descriptions and ALSA names for PulseAudio sinks. + * + * This program interfaces with both the ALSA and PulseAudio APIs to retrieve information about available audio sinks. + * It lists each sink's UDEV description, as well as its corresponding ALSA hardware (hw) name and a more user-friendly + * ALSA name. This is useful for applications that need to display detailed information about the audio devices in the system, + * especially when working with systems where multiple audio devices are present. + * + * The program utilizes the PulseAudio asynchronous API to fetch sink information and then queries ALSA to get a friendly + * name for each sink. The ALSA friendly name is typically more readable and user-friendly compared to the default hardware + * name provided by ALSA. + * + * Functions: + * - get_alsa_friendly_name: Retrieves a user-friendly name for an ALSA card. + * - sink_info_cb: Callback function for processing and displaying each sink's information. + * - context_state_cb: Callback function to handle the state changes of the PulseAudio context. + * - main: Sets up the PulseAudio main loop and context, and runs the main loop. + * + * Usage: + * - The program does not require any command-line arguments. + * - On execution, it lists all available PulseAudio sinks with their UDEV descriptions and ALSA names. + * + * @author Mbyte2 + * @date November 13, 2023 + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.13/examples/change-speaker-mode b/v-0.13/examples/change-speaker-mode new file mode 100755 index 0000000..62cabb2 Binary files /dev/null and b/v-0.13/examples/change-speaker-mode differ diff --git a/v-0.13/examples/change-speaker-mode.c b/v-0.13/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.13/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.13/examples/change_global_sample_rate b/v-0.13/examples/change_global_sample_rate new file mode 100755 index 0000000..f757107 Binary files /dev/null and b/v-0.13/examples/change_global_sample_rate differ diff --git a/v-0.13/examples/change_global_sample_rate.c b/v-0.13/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.13/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.13/examples/get-card-profiles-pulseaudio_api b/v-0.13/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..e7b8816 Binary files /dev/null and b/v-0.13/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.13/examples/get-card-profiles-pulseaudio_api.c b/v-0.13/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.13/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.13/examples/mute-channel-input-demo b/v-0.13/examples/mute-channel-input-demo new file mode 100755 index 0000000..94a7194 Binary files /dev/null and b/v-0.13/examples/mute-channel-input-demo differ diff --git a/v-0.13/examples/mute-channel-input-demo.c b/v-0.13/examples/mute-channel-input-demo.c new file mode 100644 index 0000000..2337f5c --- /dev/null +++ b/v-0.13/examples/mute-channel-input-demo.c @@ -0,0 +1,126 @@ +/** + * @file mute-channel-input-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio input device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * input devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available input devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + uint32_t device_index; + bool keep_running = true; + char choice[100]; + int c; //To clear the buffer + + while(keep_running) { + // List input devices + for (uint32_t i = 0; i < self->input_count; i++) { + printf("%d: %s\n", i, self->inputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid input. Please try again.\n"); + } + + pulseaudio_device *selected_device = &self->inputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_input_mute_state(self, selected_device->index, channel, new_mute_state); + new_mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + printf("The new mute state is, %s\n\n", new_mute_state ? "Muted" : "Unmuted"); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.13/examples/mute-channel-output-demo b/v-0.13/examples/mute-channel-output-demo new file mode 100755 index 0000000..146bc99 Binary files /dev/null and b/v-0.13/examples/mute-channel-output-demo differ diff --git a/v-0.13/examples/mute-channel-output-demo.c b/v-0.13/examples/mute-channel-output-demo.c new file mode 100644 index 0000000..0d513c9 --- /dev/null +++ b/v-0.13/examples/mute-channel-output-demo.c @@ -0,0 +1,131 @@ +/** + * @file mute-channel-output-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio output device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * output devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available output devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + bool keep_running = true; + int c; //To clear the buffer + + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + char choice[100]; + uint32_t device_index; + + while(keep_running) { + // List output devices + for (uint32_t i = 0; i < self->output_count; i++) { + printf("%d: %s\n", i, self->outputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid input. Please try again.\n"); + } + + if (device_index >= self->output_count) { + fprintf(stderr, "Invalid device index\n"); + manager_cleanup(self); + return 1; + } + + pulseaudio_device *selected_device = &self->outputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_output_mute_state(self, selected_device->index, channel, new_mute_state); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.13/examples/mute_input_demo b/v-0.13/examples/mute_input_demo new file mode 100755 index 0000000..14cf82b Binary files /dev/null and b/v-0.13/examples/mute_input_demo differ diff --git a/v-0.13/examples/mute_input_demo.c b/v-0.13/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.13/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.13/examples/mute_output_demo b/v-0.13/examples/mute_output_demo new file mode 100755 index 0000000..0fa49da Binary files /dev/null and b/v-0.13/examples/mute_output_demo differ diff --git a/v-0.13/examples/mute_output_demo.c b/v-0.13/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.13/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.13/examples/print-input-sources b/v-0.13/examples/print-input-sources new file mode 100755 index 0000000..10fab23 Binary files /dev/null and b/v-0.13/examples/print-input-sources differ diff --git a/v-0.13/examples/print-input-sources.c b/v-0.13/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.13/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.13/examples/print_sink_outputs b/v-0.13/examples/print_sink_outputs new file mode 100755 index 0000000..e8f4d82 Binary files /dev/null and b/v-0.13/examples/print_sink_outputs differ diff --git a/v-0.13/examples/print_sink_outputs.c b/v-0.13/examples/print_sink_outputs.c new file mode 100644 index 0000000..5517150 --- /dev/null +++ b/v-0.13/examples/print_sink_outputs.c @@ -0,0 +1,70 @@ +/** + * @file list_sinks.c + * @brief PulseAudio Sink Listing Program + * + * This program demonstrates the use of the EasyPulse API to list all available + * PulseAudio sink outputs (such as speakers or headphones) on the system. + * It creates an instance of pulseaudio_manager, initializes it, and iterates + * through all the available sink outputs, displaying their details. + * + * Usage: + * Compile and run this program to list all the PulseAudio sink outputs. + * Ensure that the PulseAudio development libraries are installed and linked + * correctly when compiling this program. + * + * Compilation (example): + * gcc -o list_sinks list_sinks.c -lpulse -lpulse-simple + * + * Author: Mbyte2 + * Date: November 18, 2023 + */ + +#include "../easypulse_core.h" +#include + +int main() { + pa_card_profile_info *default_profile = NULL; + + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Check if output devices are loaded + if (!manager->outputs) { + fprintf(stderr, "Output devices are not loaded.\n"); + manager_cleanup(manager); + return -1; + } + + printf("Listing available sink outputs:\n"); + + for (uint32_t i = 0; i < manager->output_count; ++i) { + default_profile = get_active_profile(manager->context, manager->outputs[i].index); + printf("Sink Index: %u\n", manager->outputs[i].index); + printf("Name: %s\n", manager->outputs[i].name); + printf("Description: %s\n", manager->outputs[i].code); + printf("Sample Rate: %d\n", manager->outputs[i].sample_rate); + printf("Channels: Min %d, Max %d\n", manager->outputs[i].min_channels, manager->outputs[i].max_channels); + + if (default_profile) { + printf("Active Profile: %s\n", default_profile->name ? default_profile->name : "Unknown"); + } else { + printf("Active Profile: Unknown\n"); + printf("\n"); + //free(default_profile); + } + } + + // Cleanup + manager_cleanup(manager); + return 0; +} diff --git a/v-0.13/examples/print_volume_output_devices b/v-0.13/examples/print_volume_output_devices new file mode 100755 index 0000000..06a4f83 Binary files /dev/null and b/v-0.13/examples/print_volume_output_devices differ diff --git a/v-0.13/examples/print_volume_output_devices.c b/v-0.13/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.13/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.13/examples/switch-input-devices b/v-0.13/examples/switch-input-devices new file mode 100755 index 0000000..8542d2c Binary files /dev/null and b/v-0.13/examples/switch-input-devices differ diff --git a/v-0.13/examples/switch-input-devices.c b/v-0.13/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.13/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.13/examples/switch-output-device b/v-0.13/examples/switch-output-device new file mode 100755 index 0000000..20fd707 Binary files /dev/null and b/v-0.13/examples/switch-output-device differ diff --git a/v-0.13/examples/switch-output-device-pulseaudio b/v-0.13/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..b3ee3c1 Binary files /dev/null and b/v-0.13/examples/switch-output-device-pulseaudio differ diff --git a/v-0.13/examples/switch-output-device-pulseaudio.c b/v-0.13/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.13/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.13/examples/switch-output-device.c b/v-0.13/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.13/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.13/examples/volume-change b/v-0.13/examples/volume-change new file mode 100755 index 0000000..10a6ca1 Binary files /dev/null and b/v-0.13/examples/volume-change differ diff --git a/v-0.13/examples/volume-change-pulseaudio b/v-0.13/examples/volume-change-pulseaudio new file mode 100755 index 0000000..db4de34 Binary files /dev/null and b/v-0.13/examples/volume-change-pulseaudio differ diff --git a/v-0.13/examples/volume-change-pulseaudio.c b/v-0.13/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.13/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.13/examples/volume-change.c b/v-0.13/examples/volume-change.c new file mode 100644 index 0000000..2353401 --- /dev/null +++ b/v-0.13/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author Mbyte2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.13/libeasypulse_core.a b/v-0.13/libeasypulse_core.a new file mode 100644 index 0000000..a90f757 Binary files /dev/null and b/v-0.13/libeasypulse_core.a differ diff --git a/v-0.13/system_query.c b/v-0.13/system_query.c new file mode 100644 index 0000000..44d35f7 --- /dev/null +++ b/v-0.13/system_query.c @@ -0,0 +1,3238 @@ +/** + * @file system_query.c + * @brief Functions for querying system audio properties using PulseAudio in a Linux environment. + * + * This implementation file provides a collection of functions designed to interact with the + * PulseAudio sound server to query and manipulate various audio properties of a Linux system. + * These functions allow for the examination and control of audio devices (sinks and sources), + * such as enumerating available devices, retrieving their properties, checking and changing + * volume and mute states, and managing audio profiles. The functionalities encapsulated in + * this file are crucial for applications that need to interface with the system's audio + * hardware and perform operations like audio routing, volume control, or retrieving hardware + * information. + * + * + * @author Mbyte2 + * @date November 13, 2023 + * + */ + +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +#define MUTED 1 +#define UNMUTED 0 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +// Strcture to share data between get_channel_mute_state and its callback. +typedef struct { + uint32_t channel_index; + bool mute_state; +} _shared_data_5; + +// Structure to hold the sink profile information +typedef struct { + pa_card_profile_info *active_profile; + bool found; +} card_profile_info; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + +/** + * @brief Callback function for handling sink information. + * + * This function is used as a callback to process information about a PulseAudio sink. + * It retrieves the mute state of a specific channel from the sink's volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the sink. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of sink information. + */ +static void get_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given sink. + * + * This function checks if the specified channel in a given sink is muted. It utilizes + * the PulseAudio API to query sink information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param sink_index The index of the sink whose channel mute state is to be checked. + * @param channel_index The index of the channel within the sink to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the sink or channel + * indices are invalid. + */ +bool get_output_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index) { + + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(context, sink_index, get_output_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for handling input device information. + * + * This function is used as a callback to process information about a PulseAudio input device. + * It retrieves the mute state of a specific channel from the input device volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the input device information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the input device. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of input device information. + */ +static void get_input_channel_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given input device. + * + * This function checks if the specified channel in a given input device is muted. It utilizes + * the PulseAudio API to query input device information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param source_index The index of the input device whose channel mute state is to be checked. + * @param channel_index The index of the channel within the input device to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the input device or channel + * indices are invalid. + */ +bool get_input_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index) { + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified input device + pa_operation *op = pa_context_get_source_info_by_index(context, source_index, get_input_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for the sink input information retrieval. + * + * This function is called by the PulseAudio context for each sink input + * retrieved by pa_context_get_sink_input_info_list(). It stores each + * sink input's information in a dynamically growing array within + * output_stream_list + * + * @param c The PulseAudio context. + * @param i The sink input information structure. + * @param eol End of list indicator. If positive, it's the end of the list. + * @param userdata User data pointer, cast to output_stream_list, passed to the function. + */ +static void get_output_streams_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + + output_stream_list *input_list = (output_stream_list *) userdata; + + if (eol) { + // End of list + return; + } + + // Resize the inputs array to accommodate one more pa_sink_input_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(pa_sink_input_info)); + if (input_list->inputs == NULL) { + // Handle allocation failure; ideally by signaling an error to the main program + fprintf(stderr, "Failed to allocate memory for sink input list\n"); + return; + } + + // Copy the sink input info to the newly allocated spot + memcpy(&input_list->inputs[input_list->num_inputs], i, sizeof(pa_sink_input_info)); + input_list->num_inputs++; +} + + +/** + * @brief Retrieves a list of current sink inputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of sink inputs from the PulseAudio server. It allocates and populates an + * instance of output_stream_list with the results. The caller is responsible + * for freeing the memory allocated for the pa_sink_input_info structures and + * the output_stream_list itself. + * + * This function will block until the operation is completed or fails. The + * sink input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to a output_stream_list containing all retrieved sink inputs, + * or NULL if an error occurs. + */ +output_stream_list *get_output_streams(pa_context *context) { + output_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for output_stream_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of sink inputs + pa_operation *op = pa_context_get_sink_input_info_list(context, get_output_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get sink input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + + +/** + * @brief Callback function for receiving information about each source output (input stream). + * + * This function is called by the PulseAudio API for each source output (input stream) + * queried via the pa_context_get_source_output_info_list() function. It appends each + * source output's information to the list provided by the user. + * + * The function will resize the underlying storage array to accommodate new entries as they + * are received. If realloc fails, it logs an error message and returns early. + * + * @param c The PulseAudio context. + * @param o Information about a source output (input stream). + * @param eol End of the list indicator. If non-zero, all source outputs have been received. + * @param userdata User data passed to the function, cast to input_stream_list. + */ +static void get_input_streams_cb(pa_context *c, const pa_source_output_info *o, int eol, void *userdata) { + (void) c; + + input_stream_list *input_list = (input_stream_list *) userdata; + + if (eol) { + // End of list + return; + } + + // Resize the inputs array to accommodate one more pa_source_output_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(pa_source_output_info)); + if (input_list->inputs == NULL) { + // Handle allocation failure; ideally by signaling an error to the main program + fprintf(stderr, "Failed to allocate memory for source input list\n"); + return; + } + + // Copy the source input info to the newly allocated spot + memcpy(&input_list->inputs[input_list->num_inputs], o, sizeof(pa_source_output_info)); + input_list->num_inputs++; +} + +/** + * @brief Retrieves a list of current source outputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of source outputs from the PulseAudio server. It allocates and populates an + * instance of output_stream_list with the results. The caller is responsible + * for freeing the memory allocated for the pa_source_output_info structures and + * the output_stream_list itself. + * + * This function will block until the operation is completed or fails. The + * source input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to a output_stream_list containing all retrieved source outputs, + * or NULL if an error occurs. + */ +input_stream_list *get_input_streams(pa_context *context) { + input_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for pa_source_input_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of source inputs + pa_operation *op = pa_context_get_source_output_info_list(context, get_input_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get source input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + + +static void card_info_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + fprintf(stderr, "[DEBUG, card_info_cb()], reached function\n"); + + card_profile_info *info = (card_profile_info *)userdata; + + if (eol > 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // No more data, or null card info + } + + + if (i->active_profile && !info->found) { + // Allocate memory for the new profile + info->active_profile = malloc(sizeof(pa_card_profile_info)); + if (info->active_profile) { + // Duplicate the name and description strings + info->active_profile->name = strdup(i->active_profile->name); + info->active_profile->description = strdup(i->active_profile->description); + info->found = true; + } + } + + fprintf(stderr, "[DEBUG, card_info_cb()] card_info_cb is %s\n", i->active_profile->description); +} + +pa_card_profile_info *get_active_profile(pa_context *context, uint32_t card_index) { + static card_profile_info info; + + if (!context) { + fprintf(stderr, "Invalid arguments.\n"); + return NULL; + } + + info.active_profile = NULL; + info.found = false; + + pa_operation *op = pa_context_get_card_info_by_index(context, card_index, card_info_cb, &info); + iterate(op); + + //fprintf(stderr, "[DEBUG, get_active_profile()] info.active_profile is %s\n", info.active_profile->description); + return info.active_profile; +} diff --git a/v-0.13/system_query.h b/v-0.13/system_query.h new file mode 100644 index 0000000..975d3f1 --- /dev/null +++ b/v-0.13/system_query.h @@ -0,0 +1,146 @@ +/** + * @file system_query.h + * @brief Header file for querying sound card properties in a PulseAudio environment. + * + * This header file provides a collection of functions and structures to interact with and query + * various properties of sound cards using PulseAudio and ALSA interfaces. It includes functions + * to obtain information about output and input devices (sinks and sources), such as the number + * of devices, device names, channel names, sample rates, and mute states. It also offers + * capabilities to manipulate and retrieve detailed information about ALSA cards and ports. + * + * Structures: + * - pa_port_info: Represents port information (e.g., line-in, microphone). + * - pa_source_info_list: Holds a list of pa_port_info structures. + * + * This file serves as an essential component for applications that need to interact with + * PulseAudio and ALSA for detailed audio device management and information retrieval. + * + * @author Mbyte2 + * @date November 13, 2023 + */ +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + + +//Used by get_output_streams() to get a list of all Pulseaudio sink inputs. +typedef struct output_stream_list { + pa_sink_input_info *inputs; + int num_inputs; +} output_stream_list; + +//Used by get_input_streams() to get a list of all Pulseaudio sink inputs. +typedef struct input_stream_list { + pa_source_output_info *inputs; // Note that the structure for source inputs is pa_source_output_info + int num_inputs; +} input_stream_list; + + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +bool get_output_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index); //Gets mute state of single output channel (0 = unmuted, 1 = muted). + +bool get_input_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index); //Gets mute state of single input channel (0 = unmuted, 1 = muted). +output_stream_list *get_output_streams(pa_context *context); //Gets a list of output (sink) inputs. +input_stream_list *get_input_streams(pa_context *context); //Gets a list of input (source) inputs. + +pa_card_profile_info *get_active_profile(pa_context *context, +uint32_t card_index); //Gets the active profile of a card. +#endif diff --git a/v-0.14/Makefile b/v-0.14/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.14/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.14/documentation/pa_context -- interface overview.docx b/v-0.14/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.14/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.14/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.14/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.14/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.14/documentation/pulseaudio/introspect.c summary b/v-0.14/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.14/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.14/documentation/pulseaudio/mainloop code flow.txt b/v-0.14/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.14/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.14/easypulse_core.c b/v-0.14/easypulse_core.c new file mode 100644 index 0000000..32f1d18 --- /dev/null +++ b/v-0.14/easypulse_core.c @@ -0,0 +1,1097 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, +int eol, void *userdata); + +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} _shared_data_1; + +//Shared data between manager_set_output_channel_mute_state and its callbacks +typedef struct _shared_data_2 { + pulseaudio_manager *manager; + uint32_t channel_index; + bool mute_state; + pa_cvolume new_volume; +} _shared_data_2; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + _shared_data_1 *shared_data = (_shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + _shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + +/** + * @brief Callback function for setting the mute state of a channel in a PulseAudio sink. + * + * This callback function is triggered by `pa_context_get_sink_info_by_index` to process + * information about a specific PulseAudio sink. It modifies the volume of a given channel + * in the sink to either muted or unmuted state, as specified in the user data. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type `struct volume_update_data`. + * + */ +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + struct _shared_data_2 *data = (struct _shared_data_2 *)userdata; + + if (info) { + // Modify the volume of the specified channel + data->new_volume = info->volume; + data->new_volume.values[data->channel_index] = data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio sink. + * + * This callback function is used to verify the success of a volume set operation + * on a PulseAudio sink. It is called after attempting to set the volume of a specific + * channel within a sink, indicating whether the operation was successful. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data. This parameter is not used in this callback. + * + */ +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set output device volume.\n"); + } + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + +/** + * @brief Sets the mute state for a single channel of an output device. + * + * This function controls the mute state of a specific channel for a given PulseAudio sink (output device). + * It uses the EasyPulse API to interact with the PulseAudio server. + * + * @param sink_index Index of the sink (output device) whose channel mute state is to be set. + * @param channel_index Index of the channel within the sink to be muted or unmuted. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, non-zero on failure. + * + */ +int manager_set_output_mute_state(pulseaudio_manager *self, uint32_t sink_index, +uint32_t channel_index, bool mute_state) { + + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, sink_index, + manager_set_output_channel_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start output device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume + op = pa_context_set_sink_volume_by_index(self->context, sink_index, + &(volume_data.new_volume), manager_set_output_channel_mute_state_cb2, &volume_data); + + iterate(self, op); + + return 0; // Success +} + +/** + * @brief Callback function for handling input device information. + * + * This function is called in response to a request for information about a specific PulseAudio input device. + * It is used to modify the volume of a specified channel in the input device based on the mute state. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the source information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + //fprintf(stderr, "[DEBUG, manager_set_input_mute_state_cb()] info->description is, %s\n", info->description); + + if (info) { + // Modify the volume of the specified channel + volume_data->new_volume = info->volume; + pa_volume_t new_channel_volume = volume_data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + volume_data->new_volume.values[volume_data->channel_index] = new_channel_volume; + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio input device. + * + * This callback function is used to verify the success of setting the volume of a specified channel in + * a PulseAudio input device. It is called after an attempt to set the volume of a channel within an input device. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set input device volume.\n"); + } + + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + + +/** + * @brief Sets the mute state for a single channel of a PulseAudio input device. + * + * This function controls the mute state of a specified PulseAudio input device (source). + * It mutes or unmutes all channels of the input device based on the provided mute state. + * + * @param self Pointer to the pulseaudio_manager structure, containing the necessary PulseAudio context. + * @param input_index Index of the input device (source) whose mute state is to be set. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, -1 on failure. + * + */ +int manager_set_input_mute_state(pulseaudio_manager *self, uint32_t input_index, +uint32_t channel_index, bool mute_state) { + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified source + pa_operation *op = pa_context_get_source_info_by_index(self->context, input_index, manager_set_input_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start input device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume for the specified channel (effectively muting or unmuting the channel) + op = pa_context_set_source_volume_by_index(self->context, input_index, + &(volume_data.new_volume), manager_set_input_mute_state_cb2, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start source volume set operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; +} + +/** + * @brief Moves playback from one sink to another. + * + * This function moves all playback streams from one output device (sink) to another. + * It is used to switch the audio output from one device to another, for example, from + * speakers to headphones. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param sink1_index Index of the current sink (output device). + * @param sink2_index Index of the new sink (output device) to move streams to. + * @return Returns 0 on success, -1 on failure. + */ +int manager_move_output_playback(pulseaudio_manager *self, uint32_t sink1_index, uint32_t sink2_index) { + if (!self || sink1_index >= self->output_count || sink2_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + _shared_data_1 shared_data = { self, sink2_index }; + + // Iterate over all sink inputs and move them to the new sink + pa_operation *op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + + if (!op) { + fprintf(stderr, "Failed to start sink input info list operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; // Success +} diff --git a/v-0.14/easypulse_core.h b/v-0.14/easypulse_core.h new file mode 100644 index 0000000..c63fcd8 --- /dev/null +++ b/v-0.14/easypulse_core.h @@ -0,0 +1,109 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + +int manager_set_output_mute_state(pulseaudio_manager *self, +uint32_t output_index, uint32_t channel_index, bool mute_state); //Changes a number of output channels to mute / unmuted. + +int manager_set_input_mute_state(pulseaudio_manager *self, +uint32_t input_index, uint32_t channel_index, bool mute_state); //Changes a number of input channels to mute / unmuted. + +int manager_move_output_playback(pulseaudio_manager *manager, +uint32_t sink1_index, uint32_t sink2_index); //Moves playback from one sink to another. + + +#endif // CORE_H diff --git a/v-0.14/examples/Makefile b/v-0.14/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.14/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.14/examples/alsa-mapper_pulseaudio-api b/v-0.14/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..244ef4d Binary files /dev/null and b/v-0.14/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.14/examples/alsa-mapper_pulseaudio-api.c b/v-0.14/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..a9b0b52 --- /dev/null +++ b/v-0.14/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,113 @@ +/** + * @file alsa-mapper_pulseaudio-api.c + * @brief Fetches and displays UDEV descriptions and ALSA names for PulseAudio sinks. + * + * This program interfaces with both the ALSA and PulseAudio APIs to retrieve information about available audio sinks. + * It lists each sink's UDEV description, as well as its corresponding ALSA hardware (hw) name and a more user-friendly + * ALSA name. This is useful for applications that need to display detailed information about the audio devices in the system, + * especially when working with systems where multiple audio devices are present. + * + * The program utilizes the PulseAudio asynchronous API to fetch sink information and then queries ALSA to get a friendly + * name for each sink. The ALSA friendly name is typically more readable and user-friendly compared to the default hardware + * name provided by ALSA. + * + * Functions: + * - get_alsa_friendly_name: Retrieves a user-friendly name for an ALSA card. + * - sink_info_cb: Callback function for processing and displaying each sink's information. + * - context_state_cb: Callback function to handle the state changes of the PulseAudio context. + * - main: Sets up the PulseAudio main loop and context, and runs the main loop. + * + * Usage: + * - The program does not require any command-line arguments. + * - On execution, it lists all available PulseAudio sinks with their UDEV descriptions and ALSA names. + * + * @author Mbyte2 + * @date November 13, 2023 + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.14/examples/change-speaker-mode b/v-0.14/examples/change-speaker-mode new file mode 100755 index 0000000..a3d91ff Binary files /dev/null and b/v-0.14/examples/change-speaker-mode differ diff --git a/v-0.14/examples/change-speaker-mode.c b/v-0.14/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.14/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.14/examples/change_global_sample_rate b/v-0.14/examples/change_global_sample_rate new file mode 100755 index 0000000..559a300 Binary files /dev/null and b/v-0.14/examples/change_global_sample_rate differ diff --git a/v-0.14/examples/change_global_sample_rate.c b/v-0.14/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.14/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.14/examples/get-card-by-name-pulseaudio b/v-0.14/examples/get-card-by-name-pulseaudio new file mode 100755 index 0000000..2b1abcc Binary files /dev/null and b/v-0.14/examples/get-card-by-name-pulseaudio differ diff --git a/v-0.14/examples/get-card-by-name-pulseaudio.c b/v-0.14/examples/get-card-by-name-pulseaudio.c new file mode 100644 index 0000000..60d2227 --- /dev/null +++ b/v-0.14/examples/get-card-by-name-pulseaudio.c @@ -0,0 +1,136 @@ +/** + * @file get-card-by-name-pulseaudio.c + * @brief PulseAudio Card Information Demo + * + * This program demonstrates how to interact with the PulseAudio (PA) sound server + * to retrieve information about a specific sound card by its name. It utilizes the + * PulseAudio API to establish a connection with the PA server, queries for a card + * by name, and then prints out the name and active profile of the card. + * + * The program employs the asynchronous PA API with a threaded main loop to handle + * the communication with the PA server. + * + * The card name should be the technical name as recognized by PulseAudio, which + * can be obtained using `pactl list cards` or `pacmd list-cards`. + * + * + * @author Mbyte2 + * @date November 18, 2023 + */ +#include +#include +#include +#include + +typedef struct { + pa_threaded_mainloop *mainloop; + pa_context *context; + char **card_names; + size_t num_cards; +} pa_userdata; + +static void context_state_cb(pa_context *context, void *userdata) { + pa_userdata *ud = (pa_userdata *) userdata; + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + default: + break; + } +} + +static void card_list_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + ud->card_names = realloc(ud->card_names, sizeof(char *) * (ud->num_cards + 1)); + ud->card_names[ud->num_cards] = strdup(i->name); + ud->num_cards++; + } +} + +static void card_info_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + printf("Card Name: %s\n", i->name); + if (i->active_profile) { + printf("Active Profile: %s\n", i->active_profile->name); + } + printf("\n"); + } +} + +int main() { + pa_userdata userdata = {0}; + + userdata.mainloop = pa_threaded_mainloop_new(); + userdata.context = pa_context_new(pa_threaded_mainloop_get_api(userdata.mainloop), "PA Demo"); + + pa_context_set_state_callback(userdata.context, context_state_cb, &userdata); + + if (pa_context_connect(userdata.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + fprintf(stderr, "PulseAudio connection failed\n"); + pa_threaded_mainloop_free(userdata.mainloop); + return 1; + } + + pa_threaded_mainloop_start(userdata.mainloop); + pa_threaded_mainloop_lock(userdata.mainloop); + + while (pa_context_get_state(userdata.context) != PA_CONTEXT_READY) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + + // List all cards + pa_operation *op = pa_context_get_card_info_list(userdata.context, card_list_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + + // Get detailed info for each card + for (size_t i = 0; i < userdata.num_cards; i++) { + op = pa_context_get_card_info_by_name(userdata.context, userdata.card_names[i], card_info_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + } + + pa_threaded_mainloop_unlock(userdata.mainloop); + pa_threaded_mainloop_stop(userdata.mainloop); + + pa_context_disconnect(userdata.context); + pa_context_unref(userdata.context); + pa_threaded_mainloop_free(userdata.mainloop); + + for (size_t i = 0; i < userdata.num_cards; i++) { + free(userdata.card_names[i]); + } + free(userdata.card_names); + + return 0; +} + diff --git a/v-0.14/examples/get-card-profiles-pulseaudio_api b/v-0.14/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..cf7e182 Binary files /dev/null and b/v-0.14/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.14/examples/get-card-profiles-pulseaudio_api.c b/v-0.14/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.14/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.14/examples/mute-channel-input-demo b/v-0.14/examples/mute-channel-input-demo new file mode 100755 index 0000000..6b1e88b Binary files /dev/null and b/v-0.14/examples/mute-channel-input-demo differ diff --git a/v-0.14/examples/mute-channel-input-demo.c b/v-0.14/examples/mute-channel-input-demo.c new file mode 100644 index 0000000..2337f5c --- /dev/null +++ b/v-0.14/examples/mute-channel-input-demo.c @@ -0,0 +1,126 @@ +/** + * @file mute-channel-input-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio input device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * input devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available input devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + uint32_t device_index; + bool keep_running = true; + char choice[100]; + int c; //To clear the buffer + + while(keep_running) { + // List input devices + for (uint32_t i = 0; i < self->input_count; i++) { + printf("%d: %s\n", i, self->inputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid input. Please try again.\n"); + } + + pulseaudio_device *selected_device = &self->inputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_input_mute_state(self, selected_device->index, channel, new_mute_state); + new_mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + printf("The new mute state is, %s\n\n", new_mute_state ? "Muted" : "Unmuted"); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.14/examples/mute-channel-output-demo b/v-0.14/examples/mute-channel-output-demo new file mode 100755 index 0000000..33976f1 Binary files /dev/null and b/v-0.14/examples/mute-channel-output-demo differ diff --git a/v-0.14/examples/mute-channel-output-demo.c b/v-0.14/examples/mute-channel-output-demo.c new file mode 100644 index 0000000..e96c51a --- /dev/null +++ b/v-0.14/examples/mute-channel-output-demo.c @@ -0,0 +1,131 @@ +/** + * @file mute-channel-output-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio output device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * output devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available output devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + bool keep_running = true; + int c; //To clear the buffer + + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + char choice[100]; + uint32_t device_index; + + while(keep_running) { + // List output devices + for (uint32_t i = 0; i < self->output_count; i++) { + printf("%d: %s\n", i, self->outputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid user input. Please try again.\n"); + } + + if (device_index >= self->output_count) { + fprintf(stderr, "Invalid device index.\n"); + manager_cleanup(self); + return 1; + } + + pulseaudio_device *selected_device = &self->outputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_output_mute_state(self, selected_device->index, channel, new_mute_state); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.14/examples/mute_input_demo b/v-0.14/examples/mute_input_demo new file mode 100755 index 0000000..68074dc Binary files /dev/null and b/v-0.14/examples/mute_input_demo differ diff --git a/v-0.14/examples/mute_input_demo.c b/v-0.14/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.14/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.14/examples/mute_output_demo b/v-0.14/examples/mute_output_demo new file mode 100755 index 0000000..6322539 Binary files /dev/null and b/v-0.14/examples/mute_output_demo differ diff --git a/v-0.14/examples/mute_output_demo.c b/v-0.14/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.14/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.14/examples/print-input-sources b/v-0.14/examples/print-input-sources new file mode 100755 index 0000000..98c5ab1 Binary files /dev/null and b/v-0.14/examples/print-input-sources differ diff --git a/v-0.14/examples/print-input-sources.c b/v-0.14/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.14/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.14/examples/print_sink_outputs b/v-0.14/examples/print_sink_outputs new file mode 100755 index 0000000..66a4149 Binary files /dev/null and b/v-0.14/examples/print_sink_outputs differ diff --git a/v-0.14/examples/print_sink_outputs.c b/v-0.14/examples/print_sink_outputs.c new file mode 100644 index 0000000..8e65711 --- /dev/null +++ b/v-0.14/examples/print_sink_outputs.c @@ -0,0 +1,46 @@ +#include "../easypulse_core.h" +#include +#include +#include +#include + +int main(void) { + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Get a list of all output streams + output_stream_list *streams = get_output_streams(manager->context); + if (!streams) { + fprintf(stderr, "Failed to get output streams.\n"); + manager_cleanup(manager); + return -1; + } + + printf("*** Listing all output devices and streams *** \n"); + + // Iterate over each output device + for (uint32_t i = 0; i < manager->output_count; ++i) { + + // For each device, list the streams associated with it + for (uint32_t j = 0; j < streams->num_inputs; ++j) { + if (streams->inputs[j].parent_index == manager->outputs[i].index) { + printf("\tStream %u: %s\n", streams->inputs[j].index, streams->inputs[j].name); + } + } + } + + output_streams_cleanup(streams); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.14/examples/print_sink_outputs_pulseaudio b/v-0.14/examples/print_sink_outputs_pulseaudio new file mode 100755 index 0000000..f4e8cc8 Binary files /dev/null and b/v-0.14/examples/print_sink_outputs_pulseaudio differ diff --git a/v-0.14/examples/print_sink_outputs_pulseaudio.c b/v-0.14/examples/print_sink_outputs_pulseaudio.c new file mode 100644 index 0000000..82c42b3 --- /dev/null +++ b/v-0.14/examples/print_sink_outputs_pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file pa_sink_list.c + * @brief Demonstrates listing all available PulseAudio sink inputs and their active profiles. + * + * This program connects to the PulseAudio server and enumerates all available sink inputs. + * For each sink input, it retrieves and displays its name and active profile. The program + * showcases the basic use of the PulseAudio API in a C program for audio device management. + * + * Author: Mbyte2 + * Date: November 21, 2023 + * + */ + +#include +#include + +// Callback function for sink input info +void sink_input_info_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_mainloop_quit((pa_mainloop*)userdata, 0); + return; + } + + printf("Sink Input #%u\n", i->index); + printf("Name: %s\n", i->name); +} + +// Callback function for server info +void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void) i; + (void) userdata; + + pa_operation *o; + + if (!(o = pa_context_get_sink_input_info_list(c, sink_input_info_cb, userdata))) { + fprintf(stderr, "pa_context_get_sink_input_info_list() failed\n"); + return; + } + + pa_operation_unref(o); +} + +// State callback for connection +void state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + pa_context_state_t state; + state = pa_context_get_state(c); + + switch (state) { + case PA_CONTEXT_READY: + server_info_cb(c, NULL, userdata); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + + default: + break; + } +} + +int main() { + pa_mainloop *m = NULL; + pa_mainloop_api *api = NULL; + pa_context *context = NULL; + int ret = 1; + + // Create a mainloop API and connection to the default server + m = pa_mainloop_new(); + api = pa_mainloop_get_api(m); + context = pa_context_new(api, "Sink Input List"); + + // Connect to the PulseAudio server + pa_context_connect(context, NULL, 0, NULL); + + // Set the callback so we can wait for the server to be ready + pa_context_set_state_callback(context, state_cb, m); + + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "Failed to run mainloop\n"); + goto quit; + } + +quit: + if (context) { + pa_context_unref(context); + } + + if (m) { + pa_mainloop_free(m); + } + + return ret; +} diff --git a/v-0.14/examples/print_volume_output_devices b/v-0.14/examples/print_volume_output_devices new file mode 100755 index 0000000..3cf4a2f Binary files /dev/null and b/v-0.14/examples/print_volume_output_devices differ diff --git a/v-0.14/examples/print_volume_output_devices.c b/v-0.14/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.14/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.14/examples/switch-input-devices b/v-0.14/examples/switch-input-devices new file mode 100755 index 0000000..33b1de1 Binary files /dev/null and b/v-0.14/examples/switch-input-devices differ diff --git a/v-0.14/examples/switch-input-devices.c b/v-0.14/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.14/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.14/examples/switch-output-device b/v-0.14/examples/switch-output-device new file mode 100755 index 0000000..4d3ebd8 Binary files /dev/null and b/v-0.14/examples/switch-output-device differ diff --git a/v-0.14/examples/switch-output-device-pulseaudio b/v-0.14/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..4c7ba53 Binary files /dev/null and b/v-0.14/examples/switch-output-device-pulseaudio differ diff --git a/v-0.14/examples/switch-output-device-pulseaudio.c b/v-0.14/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.14/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.14/examples/switch-output-device.c b/v-0.14/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.14/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.14/examples/volume-change b/v-0.14/examples/volume-change new file mode 100755 index 0000000..2729a34 Binary files /dev/null and b/v-0.14/examples/volume-change differ diff --git a/v-0.14/examples/volume-change-pulseaudio b/v-0.14/examples/volume-change-pulseaudio new file mode 100755 index 0000000..7b4bf5a Binary files /dev/null and b/v-0.14/examples/volume-change-pulseaudio differ diff --git a/v-0.14/examples/volume-change-pulseaudio.c b/v-0.14/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.14/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.14/examples/volume-change.c b/v-0.14/examples/volume-change.c new file mode 100644 index 0000000..2353401 --- /dev/null +++ b/v-0.14/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author Mbyte2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.14/libeasypulse_core.a b/v-0.14/libeasypulse_core.a new file mode 100644 index 0000000..4d778d1 Binary files /dev/null and b/v-0.14/libeasypulse_core.a differ diff --git a/v-0.14/system_query.c b/v-0.14/system_query.c new file mode 100644 index 0000000..9ff07d1 --- /dev/null +++ b/v-0.14/system_query.c @@ -0,0 +1,3291 @@ +/** + * @file system_query.c + * @brief Functions for querying system audio properties using PulseAudio in a Linux environment. + * + * This implementation file provides a collection of functions designed to interact with the + * PulseAudio sound server to query and manipulate various audio properties of a Linux system. + * These functions allow for the examination and control of audio devices (sinks and sources), + * such as enumerating available devices, retrieving their properties, checking and changing + * volume and mute states, and managing audio profiles. The functionalities encapsulated in + * this file are crucial for applications that need to interface with the system's audio + * hardware and perform operations like audio routing, volume control, or retrieving hardware + * information. + * + * + * @author Mbyte2 + * @date November 13, 2023 + * + */ + +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +#define MUTED 1 +#define UNMUTED 0 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +// Strcture to share data between get_channel_mute_state and its callback. +typedef struct { + uint32_t channel_index; + bool mute_state; +} _shared_data_5; + +// Structure to hold the sink profile information +typedef struct { + pa_card_profile_info *active_profile; + bool found; +} card_profile_info; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + +/** + * @brief Callback function for handling sink information. + * + * This function is used as a callback to process information about a PulseAudio sink. + * It retrieves the mute state of a specific channel from the sink's volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the sink. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of sink information. + */ +static void get_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given sink. + * + * This function checks if the specified channel in a given sink is muted. It utilizes + * the PulseAudio API to query sink information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param sink_index The index of the sink whose channel mute state is to be checked. + * @param channel_index The index of the channel within the sink to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the sink or channel + * indices are invalid. + */ +bool get_output_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index) { + + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(context, sink_index, get_output_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for handling input device information. + * + * This function is used as a callback to process information about a PulseAudio input device. + * It retrieves the mute state of a specific channel from the input device volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the input device information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the input device. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of input device information. + */ +static void get_input_channel_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given input device. + * + * This function checks if the specified channel in a given input device is muted. It utilizes + * the PulseAudio API to query input device information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param source_index The index of the input device whose channel mute state is to be checked. + * @param channel_index The index of the channel within the input device to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the input device or channel + * indices are invalid. + */ +bool get_input_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index) { + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified input device + pa_operation *op = pa_context_get_source_info_by_index(context, source_index, get_input_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Frees memory allocated for an output_stream_list. + * + * This function is responsible for properly freeing the memory allocated for an + * output_stream_list and its associated array of output_stream_info structures. + * It safely handles the deallocation process, first freeing the array of output_stream_info + * and then the output_stream_list structure itself. + * + * Usage of this function is essential after you're done using an output_stream_list + * to avoid memory leaks. This function safely handles NULL pointers, so it can be + * called with output_stream_list structures that may not have been fully initialized. + * + * @param list A pointer to the output_stream_list to be freed. If this pointer is NULL, + * the function does nothing. + */ +void output_streams_cleanup(output_stream_list *list) { + if (list != NULL) { + // Free the array of output_stream_info structures + if (list->inputs != NULL) { + free(list->inputs); + } + + // Free the output_stream_list structure itself + free(list); + } +} + +/** + * @brief Callback function for the sink input information retrieval. + * + * This function is called by the PulseAudio context for each sink input + * retrieved by pa_context_get_sink_input_info_list(). It stores each + * sink input's information in a dynamically growing array within + * output_stream_list + * + * @param c The PulseAudio context. + * @param i The sink input information structure. + * @param eol End of list indicator. If positive, it's the end of the list. + * @param userdata User data pointer, cast to output_stream_list, passed to the function. + */ +static void get_output_streams_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + output_stream_list *input_list = (output_stream_list *) userdata; + + // Resize the inputs array to accommodate one more output_stream_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(output_stream_info)); + if (input_list->inputs == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input list\n"); + return; + } + + // Populate the output_stream_info structure + input_list->inputs[input_list->num_inputs].index = i->index; + input_list->inputs[input_list->num_inputs].parent_index = i->sink; + strncpy(input_list->inputs[input_list->num_inputs].name, pa_proplist_gets(i->proplist, "media.name"), sizeof(input_list->inputs[input_list->num_inputs].name) - 1); + + input_list->num_inputs++; +} + + +/** + * @brief Retrieves a list of current sink inputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of sink inputs from the PulseAudio server. It allocates and populates an + * instance of output_stream_list with the results. The caller is responsible + * for freeing the memory allocated for the pa_sink_input_info structures and + * the output_stream_list itself. + * + * This function will block until the operation is completed or fails. The + * sink input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to a output_stream_list containing all retrieved sink inputs, + * or NULL if an error occurs. + */ +output_stream_list *get_output_streams(pa_context *context) { + output_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for output_stream_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of sink inputs + pa_operation *op = pa_context_get_sink_input_info_list(context, get_output_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get sink input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + +/** + * @brief Callback function for receiving information about each source output (input stream). + * + * This function is called by the PulseAudio API for each source output (input stream) + * queried via the pa_context_get_source_output_info_list() function. It appends each + * source output's information to the list provided by the user. + * + * The function will resize the underlying storage array to accommodate new entries as they + * are received. If realloc fails, it logs an error message and returns early. + * + * @param c The PulseAudio context. + * @param o Information about a source output (input stream). + * @param eol End of the list indicator. If non-zero, all source outputs have been received. + * @param userdata User data passed to the function, cast to input_stream_list. + */ +static void get_input_streams_cb(pa_context *c, const pa_source_output_info *o, int eol, void *userdata) { + (void) c; + + input_stream_list *input_list = (input_stream_list *) userdata; + + if (eol) { + // End of list + return; + } + + // Resize the inputs array to accommodate one more pa_source_output_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(pa_source_output_info)); + if (input_list->inputs == NULL) { + // Handle allocation failure; ideally by signaling an error to the main program + fprintf(stderr, "Failed to allocate memory for source input list\n"); + return; + } + + // Copy the source input info to the newly allocated spot + memcpy(&input_list->inputs[input_list->num_inputs], o, sizeof(pa_source_output_info)); + input_list->num_inputs++; +} + +/** + * @brief Retrieves a list of current source outputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of source outputs from the PulseAudio server. It allocates and populates an + * instance of output_stream_list with the results. The caller is responsible + * for freeing the memory allocated for the pa_source_output_info structures and + * the output_stream_list itself. + * + * This function will block until the operation is completed or fails. The + * source input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to a output_stream_list containing all retrieved source outputs, + * or NULL if an error occurs. + */ +input_stream_list *get_input_streams(pa_context *context) { + input_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for pa_source_input_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of source inputs + pa_operation *op = pa_context_get_source_output_info_list(context, get_input_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get source input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + +/** + * @brief Callback function for retrieving the active profile information of a PulseAudio card. + * + * This function is called by PulseAudio in response to pa_context_get_card_info_by_name. + * It processes the card information and stores the active profile if found. + * + * @param c Pointer to the PulseAudio context. Not used in this function. + * @param i Pointer to the PulseAudio card information structure. + * @param eol End-of-list flag. If positive, it indicates no more data is available. + * @param userdata User-provided pointer to store the resulting profile information. + * + * @note The function assumes userdata is a pointer to card_profile_info structure. + */ +static void get_active_profile_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + fprintf(stderr, "[DEBUG, card_info_cb()], reached function\n"); + + card_profile_info *info = (card_profile_info *)userdata; + printf("[DEBUG] i is: %p\n", i); + + if (eol > 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // No more data, or null card info + } + + if (i->active_profile && !info->found) { + // Allocate memory for the new profile + info->active_profile = malloc(sizeof(pa_card_profile_info)); + if (info->active_profile) { + // Duplicate the name and description strings + info->active_profile->name = strdup(i->active_profile->name); + info->active_profile->description = strdup(i->active_profile->description); + info->found = true; + } + } + + fprintf(stderr, "[DEBUG, card_info_cb()] card_info_cb is %s\n", i->active_profile->description); +} + +/** + * @brief Retrieves the active profile information for a specified PulseAudio card. + * + * This function initiates a query to PulseAudio to get information about a specific card. + * It blocks until the callback (get_active_profile_cb) has processed the data. + * + * @param context Pointer to the PulseAudio context. + * @param card_name Name of the PulseAudio card to query. + * + * @return Pointer to the active profile information on success, NULL on failure or if no active profile is found. + * + * @note The returned pointer should not be freed by the caller, as it points to statically allocated memory. + */ +pa_card_profile_info *get_active_profile(pa_context *context, char *card_name) { + static card_profile_info info; + + if (!context) { + fprintf(stderr, "Invalid arguments.\n"); + return NULL; + } + + info.active_profile = NULL; + info.found = false; + + pa_operation *op = pa_context_get_card_info_by_name(context, card_name, get_active_profile_cb, &info); + iterate(op); + + //fprintf(stderr, "[DEBUG, get_active_profile()] info.active_profile is %s\n", info.active_profile->description); + return info.active_profile; +} diff --git a/v-0.14/system_query.h b/v-0.14/system_query.h new file mode 100644 index 0000000..c1da065 --- /dev/null +++ b/v-0.14/system_query.h @@ -0,0 +1,157 @@ +/** + * @file system_query.h + * @brief Header file for querying sound card properties in a PulseAudio environment. + * + * This header file provides a collection of functions and structures to interact with and query + * various properties of sound cards using PulseAudio and ALSA interfaces. It includes functions + * to obtain information about output and input devices (sinks and sources), such as the number + * of devices, device names, channel names, sample rates, and mute states. It also offers + * capabilities to manipulate and retrieve detailed information about ALSA cards and ports. + * + * Structures: + * - pa_port_info: Represents port information (e.g., line-in, microphone). + * - pa_source_info_list: Holds a list of pa_port_info structures. + * + * This file serves as an essential component for applications that need to interact with + * PulseAudio and ALSA for detailed audio device management and information retrieval. + * + * @author Mbyte2 + * @date November 13, 2023 + */ +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +typedef struct { + uint32_t index; + uint32_t parent_index; //Index of the corresponding output device (sink). + char name[256]; // Adjust size as needed + // Add other fields as needed, like volume, application name, etc. +} output_stream_info; + + +//Used by get_output_streams() to get a list of all Pulseaudio sink inputs. +typedef struct output_stream_list { + output_stream_info *inputs; + uint32_t num_inputs; +} output_stream_list; + +//Used by get_input_streams() to get a list of all Pulseaudio sink inputs. +typedef struct input_stream_list { + pa_source_output_info *inputs; // Note that the structure for source inputs is pa_source_output_info + uint32_t num_inputs; +} input_stream_list; + + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +bool get_output_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index); //Gets mute state of single output channel (0 = unmuted, 1 = muted). + +bool get_input_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index); //Gets mute state of single input channel (0 = unmuted, 1 = muted). + +output_stream_list *get_output_streams(pa_context *context); //Gets a list of output (sink) inputs. They must be freed after allocated. +void output_streams_cleanup(output_stream_list *list); //Cleans up allocated output streams. + +input_stream_list *get_input_streams(pa_context *context); //Gets a list of input (source) inputs. + +pa_card_profile_info *get_active_profile(pa_context *context, +char *output_name); //Gets the active profile of a card. + +#endif diff --git a/v-0.15/Makefile b/v-0.15/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.15/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.15/documentation/pa_context -- interface overview.docx b/v-0.15/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.15/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.15/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.15/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.15/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.15/documentation/pulseaudio/introspect.c summary b/v-0.15/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.15/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.15/documentation/pulseaudio/mainloop code flow.txt b/v-0.15/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.15/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.15/easypulse_core.c b/v-0.15/easypulse_core.c new file mode 100644 index 0000000..32f1d18 --- /dev/null +++ b/v-0.15/easypulse_core.c @@ -0,0 +1,1097 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, +int eol, void *userdata); + +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} _shared_data_1; + +//Shared data between manager_set_output_channel_mute_state and its callbacks +typedef struct _shared_data_2 { + pulseaudio_manager *manager; + uint32_t channel_index; + bool mute_state; + pa_cvolume new_volume; +} _shared_data_2; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + _shared_data_1 *shared_data = (_shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + _shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + +/** + * @brief Callback function for setting the mute state of a channel in a PulseAudio sink. + * + * This callback function is triggered by `pa_context_get_sink_info_by_index` to process + * information about a specific PulseAudio sink. It modifies the volume of a given channel + * in the sink to either muted or unmuted state, as specified in the user data. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type `struct volume_update_data`. + * + */ +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + struct _shared_data_2 *data = (struct _shared_data_2 *)userdata; + + if (info) { + // Modify the volume of the specified channel + data->new_volume = info->volume; + data->new_volume.values[data->channel_index] = data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio sink. + * + * This callback function is used to verify the success of a volume set operation + * on a PulseAudio sink. It is called after attempting to set the volume of a specific + * channel within a sink, indicating whether the operation was successful. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data. This parameter is not used in this callback. + * + */ +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set output device volume.\n"); + } + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + +/** + * @brief Sets the mute state for a single channel of an output device. + * + * This function controls the mute state of a specific channel for a given PulseAudio sink (output device). + * It uses the EasyPulse API to interact with the PulseAudio server. + * + * @param sink_index Index of the sink (output device) whose channel mute state is to be set. + * @param channel_index Index of the channel within the sink to be muted or unmuted. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, non-zero on failure. + * + */ +int manager_set_output_mute_state(pulseaudio_manager *self, uint32_t sink_index, +uint32_t channel_index, bool mute_state) { + + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, sink_index, + manager_set_output_channel_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start output device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume + op = pa_context_set_sink_volume_by_index(self->context, sink_index, + &(volume_data.new_volume), manager_set_output_channel_mute_state_cb2, &volume_data); + + iterate(self, op); + + return 0; // Success +} + +/** + * @brief Callback function for handling input device information. + * + * This function is called in response to a request for information about a specific PulseAudio input device. + * It is used to modify the volume of a specified channel in the input device based on the mute state. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the source information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + //fprintf(stderr, "[DEBUG, manager_set_input_mute_state_cb()] info->description is, %s\n", info->description); + + if (info) { + // Modify the volume of the specified channel + volume_data->new_volume = info->volume; + pa_volume_t new_channel_volume = volume_data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + volume_data->new_volume.values[volume_data->channel_index] = new_channel_volume; + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio input device. + * + * This callback function is used to verify the success of setting the volume of a specified channel in + * a PulseAudio input device. It is called after an attempt to set the volume of a channel within an input device. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set input device volume.\n"); + } + + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + + +/** + * @brief Sets the mute state for a single channel of a PulseAudio input device. + * + * This function controls the mute state of a specified PulseAudio input device (source). + * It mutes or unmutes all channels of the input device based on the provided mute state. + * + * @param self Pointer to the pulseaudio_manager structure, containing the necessary PulseAudio context. + * @param input_index Index of the input device (source) whose mute state is to be set. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, -1 on failure. + * + */ +int manager_set_input_mute_state(pulseaudio_manager *self, uint32_t input_index, +uint32_t channel_index, bool mute_state) { + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified source + pa_operation *op = pa_context_get_source_info_by_index(self->context, input_index, manager_set_input_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start input device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume for the specified channel (effectively muting or unmuting the channel) + op = pa_context_set_source_volume_by_index(self->context, input_index, + &(volume_data.new_volume), manager_set_input_mute_state_cb2, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start source volume set operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; +} + +/** + * @brief Moves playback from one sink to another. + * + * This function moves all playback streams from one output device (sink) to another. + * It is used to switch the audio output from one device to another, for example, from + * speakers to headphones. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param sink1_index Index of the current sink (output device). + * @param sink2_index Index of the new sink (output device) to move streams to. + * @return Returns 0 on success, -1 on failure. + */ +int manager_move_output_playback(pulseaudio_manager *self, uint32_t sink1_index, uint32_t sink2_index) { + if (!self || sink1_index >= self->output_count || sink2_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + _shared_data_1 shared_data = { self, sink2_index }; + + // Iterate over all sink inputs and move them to the new sink + pa_operation *op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + + if (!op) { + fprintf(stderr, "Failed to start sink input info list operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; // Success +} diff --git a/v-0.15/easypulse_core.h b/v-0.15/easypulse_core.h new file mode 100644 index 0000000..c63fcd8 --- /dev/null +++ b/v-0.15/easypulse_core.h @@ -0,0 +1,109 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + +int manager_set_output_mute_state(pulseaudio_manager *self, +uint32_t output_index, uint32_t channel_index, bool mute_state); //Changes a number of output channels to mute / unmuted. + +int manager_set_input_mute_state(pulseaudio_manager *self, +uint32_t input_index, uint32_t channel_index, bool mute_state); //Changes a number of input channels to mute / unmuted. + +int manager_move_output_playback(pulseaudio_manager *manager, +uint32_t sink1_index, uint32_t sink2_index); //Moves playback from one sink to another. + + +#endif // CORE_H diff --git a/v-0.15/examples/Makefile b/v-0.15/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.15/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.15/examples/alsa-mapper_pulseaudio-api b/v-0.15/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..201f93d Binary files /dev/null and b/v-0.15/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.15/examples/alsa-mapper_pulseaudio-api.c b/v-0.15/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..a9b0b52 --- /dev/null +++ b/v-0.15/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,113 @@ +/** + * @file alsa-mapper_pulseaudio-api.c + * @brief Fetches and displays UDEV descriptions and ALSA names for PulseAudio sinks. + * + * This program interfaces with both the ALSA and PulseAudio APIs to retrieve information about available audio sinks. + * It lists each sink's UDEV description, as well as its corresponding ALSA hardware (hw) name and a more user-friendly + * ALSA name. This is useful for applications that need to display detailed information about the audio devices in the system, + * especially when working with systems where multiple audio devices are present. + * + * The program utilizes the PulseAudio asynchronous API to fetch sink information and then queries ALSA to get a friendly + * name for each sink. The ALSA friendly name is typically more readable and user-friendly compared to the default hardware + * name provided by ALSA. + * + * Functions: + * - get_alsa_friendly_name: Retrieves a user-friendly name for an ALSA card. + * - sink_info_cb: Callback function for processing and displaying each sink's information. + * - context_state_cb: Callback function to handle the state changes of the PulseAudio context. + * - main: Sets up the PulseAudio main loop and context, and runs the main loop. + * + * Usage: + * - The program does not require any command-line arguments. + * - On execution, it lists all available PulseAudio sinks with their UDEV descriptions and ALSA names. + * + * @author Mbyte2 + * @date November 13, 2023 + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.15/examples/change-speaker-mode b/v-0.15/examples/change-speaker-mode new file mode 100755 index 0000000..b8a9875 Binary files /dev/null and b/v-0.15/examples/change-speaker-mode differ diff --git a/v-0.15/examples/change-speaker-mode.c b/v-0.15/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.15/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.15/examples/change_global_sample_rate b/v-0.15/examples/change_global_sample_rate new file mode 100755 index 0000000..eddedda Binary files /dev/null and b/v-0.15/examples/change_global_sample_rate differ diff --git a/v-0.15/examples/change_global_sample_rate.c b/v-0.15/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.15/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.15/examples/get-card-by-name-pulseaudio b/v-0.15/examples/get-card-by-name-pulseaudio new file mode 100755 index 0000000..436d72d Binary files /dev/null and b/v-0.15/examples/get-card-by-name-pulseaudio differ diff --git a/v-0.15/examples/get-card-by-name-pulseaudio.c b/v-0.15/examples/get-card-by-name-pulseaudio.c new file mode 100644 index 0000000..60d2227 --- /dev/null +++ b/v-0.15/examples/get-card-by-name-pulseaudio.c @@ -0,0 +1,136 @@ +/** + * @file get-card-by-name-pulseaudio.c + * @brief PulseAudio Card Information Demo + * + * This program demonstrates how to interact with the PulseAudio (PA) sound server + * to retrieve information about a specific sound card by its name. It utilizes the + * PulseAudio API to establish a connection with the PA server, queries for a card + * by name, and then prints out the name and active profile of the card. + * + * The program employs the asynchronous PA API with a threaded main loop to handle + * the communication with the PA server. + * + * The card name should be the technical name as recognized by PulseAudio, which + * can be obtained using `pactl list cards` or `pacmd list-cards`. + * + * + * @author Mbyte2 + * @date November 18, 2023 + */ +#include +#include +#include +#include + +typedef struct { + pa_threaded_mainloop *mainloop; + pa_context *context; + char **card_names; + size_t num_cards; +} pa_userdata; + +static void context_state_cb(pa_context *context, void *userdata) { + pa_userdata *ud = (pa_userdata *) userdata; + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + default: + break; + } +} + +static void card_list_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + ud->card_names = realloc(ud->card_names, sizeof(char *) * (ud->num_cards + 1)); + ud->card_names[ud->num_cards] = strdup(i->name); + ud->num_cards++; + } +} + +static void card_info_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + printf("Card Name: %s\n", i->name); + if (i->active_profile) { + printf("Active Profile: %s\n", i->active_profile->name); + } + printf("\n"); + } +} + +int main() { + pa_userdata userdata = {0}; + + userdata.mainloop = pa_threaded_mainloop_new(); + userdata.context = pa_context_new(pa_threaded_mainloop_get_api(userdata.mainloop), "PA Demo"); + + pa_context_set_state_callback(userdata.context, context_state_cb, &userdata); + + if (pa_context_connect(userdata.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + fprintf(stderr, "PulseAudio connection failed\n"); + pa_threaded_mainloop_free(userdata.mainloop); + return 1; + } + + pa_threaded_mainloop_start(userdata.mainloop); + pa_threaded_mainloop_lock(userdata.mainloop); + + while (pa_context_get_state(userdata.context) != PA_CONTEXT_READY) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + + // List all cards + pa_operation *op = pa_context_get_card_info_list(userdata.context, card_list_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + + // Get detailed info for each card + for (size_t i = 0; i < userdata.num_cards; i++) { + op = pa_context_get_card_info_by_name(userdata.context, userdata.card_names[i], card_info_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + } + + pa_threaded_mainloop_unlock(userdata.mainloop); + pa_threaded_mainloop_stop(userdata.mainloop); + + pa_context_disconnect(userdata.context); + pa_context_unref(userdata.context); + pa_threaded_mainloop_free(userdata.mainloop); + + for (size_t i = 0; i < userdata.num_cards; i++) { + free(userdata.card_names[i]); + } + free(userdata.card_names); + + return 0; +} + diff --git a/v-0.15/examples/get-card-profiles-pulseaudio_api b/v-0.15/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..2be3d7d Binary files /dev/null and b/v-0.15/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.15/examples/get-card-profiles-pulseaudio_api.c b/v-0.15/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.15/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.15/examples/mute-channel-input-demo b/v-0.15/examples/mute-channel-input-demo new file mode 100755 index 0000000..9bb992f Binary files /dev/null and b/v-0.15/examples/mute-channel-input-demo differ diff --git a/v-0.15/examples/mute-channel-input-demo.c b/v-0.15/examples/mute-channel-input-demo.c new file mode 100644 index 0000000..2337f5c --- /dev/null +++ b/v-0.15/examples/mute-channel-input-demo.c @@ -0,0 +1,126 @@ +/** + * @file mute-channel-input-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio input device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * input devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available input devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + uint32_t device_index; + bool keep_running = true; + char choice[100]; + int c; //To clear the buffer + + while(keep_running) { + // List input devices + for (uint32_t i = 0; i < self->input_count; i++) { + printf("%d: %s\n", i, self->inputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid input. Please try again.\n"); + } + + pulseaudio_device *selected_device = &self->inputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_input_mute_state(self, selected_device->index, channel, new_mute_state); + new_mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + printf("The new mute state is, %s\n\n", new_mute_state ? "Muted" : "Unmuted"); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.15/examples/mute-channel-output-demo b/v-0.15/examples/mute-channel-output-demo new file mode 100755 index 0000000..2db1467 Binary files /dev/null and b/v-0.15/examples/mute-channel-output-demo differ diff --git a/v-0.15/examples/mute-channel-output-demo.c b/v-0.15/examples/mute-channel-output-demo.c new file mode 100644 index 0000000..e96c51a --- /dev/null +++ b/v-0.15/examples/mute-channel-output-demo.c @@ -0,0 +1,131 @@ +/** + * @file mute-channel-output-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio output device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * output devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available output devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + bool keep_running = true; + int c; //To clear the buffer + + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + char choice[100]; + uint32_t device_index; + + while(keep_running) { + // List output devices + for (uint32_t i = 0; i < self->output_count; i++) { + printf("%d: %s\n", i, self->outputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid user input. Please try again.\n"); + } + + if (device_index >= self->output_count) { + fprintf(stderr, "Invalid device index.\n"); + manager_cleanup(self); + return 1; + } + + pulseaudio_device *selected_device = &self->outputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_output_mute_state(self, selected_device->index, channel, new_mute_state); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.15/examples/mute_input_demo b/v-0.15/examples/mute_input_demo new file mode 100755 index 0000000..27d3c09 Binary files /dev/null and b/v-0.15/examples/mute_input_demo differ diff --git a/v-0.15/examples/mute_input_demo.c b/v-0.15/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.15/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.15/examples/mute_output_demo b/v-0.15/examples/mute_output_demo new file mode 100755 index 0000000..9f1e1ad Binary files /dev/null and b/v-0.15/examples/mute_output_demo differ diff --git a/v-0.15/examples/mute_output_demo.c b/v-0.15/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.15/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.15/examples/print-input-sources b/v-0.15/examples/print-input-sources new file mode 100755 index 0000000..9be1f1b Binary files /dev/null and b/v-0.15/examples/print-input-sources differ diff --git a/v-0.15/examples/print-input-sources.c b/v-0.15/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.15/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.15/examples/print_sink_inputs b/v-0.15/examples/print_sink_inputs new file mode 100755 index 0000000..ba1093e Binary files /dev/null and b/v-0.15/examples/print_sink_inputs differ diff --git a/v-0.15/examples/print_sink_inputs.c b/v-0.15/examples/print_sink_inputs.c new file mode 100644 index 0000000..ce524c1 --- /dev/null +++ b/v-0.15/examples/print_sink_inputs.c @@ -0,0 +1,94 @@ +/** + * @file print_sink_inputs.c + * @brief This file demonstrates the usage of the EasyPulse library to interact with PulseAudio. + * + * The program initializes a PulseAudio manager, checks its readiness, and retrieves a list + * of all output streams. It then iterates over each output device to list the streams associated + * with it. This serves as an example of how to use the EasyPulse library to interact with PulseAudio + * for querying and managing audio streams and devices. + * + * Dependencies: + * - easypulse_core.h: Provides the core functionalities and structures for the EasyPulse library. + * - pulse/introspect.h: PulseAudio API for introspection functionalities. + * - stdint.h: Standard integer types. + * - stdio.h: Standard input/output library for C. + * - unistd.h: Provides access to the POSIX operating system API. + * + * Functions: + * - manager_create(): Initializes and returns a new pulseaudio_manager instance. + * - manager_cleanup(): Cleans up and deallocates a pulseaudio_manager instance. + * - get_output_streams(): Retrieves a list of all output streams from the PulseAudio context. + * - output_streams_cleanup(): Cleans up and deallocates an output_stream_list instance. + * + * The program demonstrates error handling, stream listing, and cleanup procedures in a PulseAudio context. + * + * @author Mbyte2 + * @date November 23, 2023 + * + */ + +#include "../easypulse_core.h" +#include +#include +#include +#include + +int main(void) { + const char *key; + void *proplist_state = NULL; + + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Get a list of all output streams + output_stream_list *streams = get_output_streams(manager->context); + if (!streams) { + fprintf(stderr, "Failed to get output streams.\n"); + manager_cleanup(manager); + return -1; + } + + printf("*** Listing all output devices and streams *** \n"); + + // Iterate over each output device + for (uint32_t i = 0; i < manager->output_count; ++i) { + + // For each device, list the streams associated with it + for (uint32_t j = 0; j < streams->num_inputs; ++j) { + if (streams->inputs[j].parent_index == manager->outputs[i].index) { + printf("\tStream [%u] name: %s\n", streams->inputs[j].index, streams->inputs[j].name); + printf("\tOwner: %lu\n", (unsigned long) streams->inputs[j].owner_module); + printf("\tParent index: %lu\n", (unsigned long) streams->inputs[j].parent_index); + printf("\tVolume channels: %u\n", (unsigned) streams->inputs[j].volume.channels); + printf("\tChannel map channels: %u\n", (unsigned) streams->inputs[j].channel_map.channels); + printf("\tSink Input Format Encoding: %u\n", streams->inputs[j].format->encoding); + printf("\tProperties:\n"); + + while ((key = pa_proplist_iterate(streams->inputs[j].proplist, &proplist_state))) { + const char *value = pa_proplist_gets(streams->inputs[j].proplist, key); + if (value) { + printf("\t%s = %s\n", key, value); + } else { + printf("\t%s = \n", key); + } + } + printf("\t***\n"); + } + } + } + + output_streams_cleanup(streams); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.15/examples/print_sink_inputs_pulseaudio b/v-0.15/examples/print_sink_inputs_pulseaudio new file mode 100755 index 0000000..3ccb722 Binary files /dev/null and b/v-0.15/examples/print_sink_inputs_pulseaudio differ diff --git a/v-0.15/examples/print_sink_inputs_pulseaudio.c b/v-0.15/examples/print_sink_inputs_pulseaudio.c new file mode 100644 index 0000000..82c42b3 --- /dev/null +++ b/v-0.15/examples/print_sink_inputs_pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file pa_sink_list.c + * @brief Demonstrates listing all available PulseAudio sink inputs and their active profiles. + * + * This program connects to the PulseAudio server and enumerates all available sink inputs. + * For each sink input, it retrieves and displays its name and active profile. The program + * showcases the basic use of the PulseAudio API in a C program for audio device management. + * + * Author: Mbyte2 + * Date: November 21, 2023 + * + */ + +#include +#include + +// Callback function for sink input info +void sink_input_info_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_mainloop_quit((pa_mainloop*)userdata, 0); + return; + } + + printf("Sink Input #%u\n", i->index); + printf("Name: %s\n", i->name); +} + +// Callback function for server info +void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void) i; + (void) userdata; + + pa_operation *o; + + if (!(o = pa_context_get_sink_input_info_list(c, sink_input_info_cb, userdata))) { + fprintf(stderr, "pa_context_get_sink_input_info_list() failed\n"); + return; + } + + pa_operation_unref(o); +} + +// State callback for connection +void state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + pa_context_state_t state; + state = pa_context_get_state(c); + + switch (state) { + case PA_CONTEXT_READY: + server_info_cb(c, NULL, userdata); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + + default: + break; + } +} + +int main() { + pa_mainloop *m = NULL; + pa_mainloop_api *api = NULL; + pa_context *context = NULL; + int ret = 1; + + // Create a mainloop API and connection to the default server + m = pa_mainloop_new(); + api = pa_mainloop_get_api(m); + context = pa_context_new(api, "Sink Input List"); + + // Connect to the PulseAudio server + pa_context_connect(context, NULL, 0, NULL); + + // Set the callback so we can wait for the server to be ready + pa_context_set_state_callback(context, state_cb, m); + + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "Failed to run mainloop\n"); + goto quit; + } + +quit: + if (context) { + pa_context_unref(context); + } + + if (m) { + pa_mainloop_free(m); + } + + return ret; +} diff --git a/v-0.15/examples/print_source_outputs b/v-0.15/examples/print_source_outputs new file mode 100755 index 0000000..17ef2ff Binary files /dev/null and b/v-0.15/examples/print_source_outputs differ diff --git a/v-0.15/examples/print_source_outputs.c b/v-0.15/examples/print_source_outputs.c new file mode 100644 index 0000000..01c564e --- /dev/null +++ b/v-0.15/examples/print_source_outputs.c @@ -0,0 +1,71 @@ +/** + * @file print_source_outputs.c + * @brief Demonstrates interaction with PulseAudio using the EasyPulse library for input streams. + * + * This program initializes a PulseAudio manager, verifies its readiness, and retrieves a list + * of all input streams (source outputs). It iterates over each input stream, printing detailed + * information about them. This example showcases the use of the EasyPulse library to interface + * with PulseAudio for querying and managing audio input streams and their associated devices. + * + * Dependencies: + * - easypulse_core.h: Provides core functionalities and structures for the EasyPulse library. + * - pulse/introspect.h: PulseAudio API for introspection functionalities. + * - stdint.h: Standard integer types. + * - stdio.h: Standard input/output library for C. + * - unistd.h: Access to the POSIX operating system API. + * + * Functions: + * - manager_create(): Initializes and returns a new pulseaudio_manager instance. + * - manager_cleanup(): Cleans up and deallocates a pulseaudio_manager instance. + * - get_input_streams(): Retrieves a list of all input streams (source outputs) from the PulseAudio context. + * - input_streams_cleanup(): Cleans up and deallocates an input_stream_list instance. + * + * The program demonstrates error handling, input stream listing, and cleanup procedures within a PulseAudio context. + * + * @author Mbyte2 + * @date November 23, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include +#include + +int main(void) { + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Get a list of all input streams (source outputs) + input_stream_list *input_streams = get_input_streams(manager->context); + if (!input_streams) { + fprintf(stderr, "Failed to get input streams.\n"); + manager_cleanup(manager); + return -1; + } + + printf("*** Listing all input streams (source outputs) ***\n"); + + // Iterate over each input stream + for (uint32_t i = 0; i < input_streams->num_inputs; ++i) { + printf("\tInput Stream [%u] name: %s\n", input_streams->outputs[i].index, input_streams->outputs[i].name); + // Additional properties of the input stream can be printed here + printf("\t***\n"); + } + + // Cleanup + input_streams_cleanup(input_streams); // Assuming a cleanup function for input streams + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.15/examples/print_volume_output_devices b/v-0.15/examples/print_volume_output_devices new file mode 100755 index 0000000..a50c780 Binary files /dev/null and b/v-0.15/examples/print_volume_output_devices differ diff --git a/v-0.15/examples/print_volume_output_devices.c b/v-0.15/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.15/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.15/examples/switch-input-devices b/v-0.15/examples/switch-input-devices new file mode 100755 index 0000000..8de0cb7 Binary files /dev/null and b/v-0.15/examples/switch-input-devices differ diff --git a/v-0.15/examples/switch-input-devices.c b/v-0.15/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.15/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.15/examples/switch-output-device b/v-0.15/examples/switch-output-device new file mode 100755 index 0000000..b738206 Binary files /dev/null and b/v-0.15/examples/switch-output-device differ diff --git a/v-0.15/examples/switch-output-device-pulseaudio b/v-0.15/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..d4eaa13 Binary files /dev/null and b/v-0.15/examples/switch-output-device-pulseaudio differ diff --git a/v-0.15/examples/switch-output-device-pulseaudio.c b/v-0.15/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.15/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.15/examples/switch-output-device.c b/v-0.15/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.15/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.15/examples/volume-change b/v-0.15/examples/volume-change new file mode 100755 index 0000000..14b39e7 Binary files /dev/null and b/v-0.15/examples/volume-change differ diff --git a/v-0.15/examples/volume-change-pulseaudio b/v-0.15/examples/volume-change-pulseaudio new file mode 100755 index 0000000..d276e9f Binary files /dev/null and b/v-0.15/examples/volume-change-pulseaudio differ diff --git a/v-0.15/examples/volume-change-pulseaudio.c b/v-0.15/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.15/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.15/examples/volume-change.c b/v-0.15/examples/volume-change.c new file mode 100644 index 0000000..2353401 --- /dev/null +++ b/v-0.15/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author Mbyte2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.15/libeasypulse_core.a b/v-0.15/libeasypulse_core.a new file mode 100644 index 0000000..4d778d1 Binary files /dev/null and b/v-0.15/libeasypulse_core.a differ diff --git a/v-0.15/system_query.c b/v-0.15/system_query.c new file mode 100644 index 0000000..c71d345 --- /dev/null +++ b/v-0.15/system_query.c @@ -0,0 +1,3401 @@ +/** + * @file system_query.c + * @brief Functions for querying system audio properties using PulseAudio in a Linux environment. + * + * This implementation file provides a collection of functions designed to interact with the + * PulseAudio sound server to query and manipulate various audio properties of a Linux system. + * These functions allow for the examination and control of audio devices (sinks and sources), + * such as enumerating available devices, retrieving their properties, checking and changing + * volume and mute states, and managing audio profiles. The functionalities encapsulated in + * this file are crucial for applications that need to interface with the system's audio + * hardware and perform operations like audio routing, volume control, or retrieving hardware + * information. + * + * + * @author Mbyte2 + * @date November 13, 2023 + * + */ + +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +#define MUTED 1 +#define UNMUTED 0 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +// Strcture to share data between get_channel_mute_state and its callback. +typedef struct { + uint32_t channel_index; + bool mute_state; +} _shared_data_5; + +// Structure to hold the sink profile information +typedef struct { + pa_card_profile_info *active_profile; + bool found; +} card_profile_info; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + +/** + * @brief Callback function for handling sink information. + * + * This function is used as a callback to process information about a PulseAudio sink. + * It retrieves the mute state of a specific channel from the sink's volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the sink. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of sink information. + */ +static void get_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given sink. + * + * This function checks if the specified channel in a given sink is muted. It utilizes + * the PulseAudio API to query sink information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param sink_index The index of the sink whose channel mute state is to be checked. + * @param channel_index The index of the channel within the sink to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the sink or channel + * indices are invalid. + */ +bool get_output_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index) { + + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(context, sink_index, get_output_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for handling input device information. + * + * This function is used as a callback to process information about a PulseAudio input device. + * It retrieves the mute state of a specific channel from the input device volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the input device information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the input device. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of input device information. + */ +static void get_input_channel_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given input device. + * + * This function checks if the specified channel in a given input device is muted. It utilizes + * the PulseAudio API to query input device information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param source_index The index of the input device whose channel mute state is to be checked. + * @param channel_index The index of the channel within the input device to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the input device or channel + * indices are invalid. + */ +bool get_input_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index) { + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified input device + pa_operation *op = pa_context_get_source_info_by_index(context, source_index, get_input_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Frees memory allocated for an output_stream_list. + * + * This function is responsible for properly freeing the memory allocated for an + * output_stream_list and its associated array of output_stream_info structures. + * It frees the array of output_stream_info and then the output_stream_list structure itself. + + * + * @param list A pointer to the output_stream_list to be freed. If this pointer is NULL, + * the function does nothing. + */ +void output_streams_cleanup(output_stream_list *list) { + if (list != NULL) { + // Iterate over each output_stream_info and free its dynamically allocated fields + for (uint32_t i = 0; i < list->num_inputs; ++i) { + free(list->inputs[i].name); + free(list->inputs[i].driver); + + if (list->inputs[i].format != NULL) { + pa_format_info_free(list->inputs[i].format); + } + + if (list->inputs[i].proplist != NULL) { + pa_proplist_free(list->inputs[i].proplist); + } + } + + // Free the array of output_stream_info + free(list->inputs); + + // Free the output_stream_list structure itself + free(list); + } +} + +/** + * @brief Frees memory allocated for an input_stream_list. + * + * This function is responsible for properly freeing the memory allocated for an + * input_stream_list and its associated array of input_stream_info structures. + * It frees the array of input_stream_info and then the input_stream_list structure itself. + * + * Usage of this function is essential after you're done using an input_stream_list + * to avoid memory leaks. This function safely handles NULL pointers, so it can be + * called with input_stream_list structures that may not have been fully initialized. + * + * @param list A pointer to the input_stream_list to be freed. If this pointer is NULL, + * the function does nothing. + */ +void input_streams_cleanup(input_stream_list *list) { + if (list != NULL) { + // Iterate over each input_stream_info and free its dynamically allocated fields + for (uint32_t i = 0; i < list->num_inputs; ++i) { + free(list->outputs[i].name); + free(list->outputs[i].driver); + + if (list->outputs[i].format != NULL) { + pa_format_info_free(list->outputs[i].format); + } + + if (list->outputs[i].proplist != NULL) { + pa_proplist_free(list->outputs[i].proplist); + } + } + + // Free the array of input_stream_info + free(list->outputs); + + // Free the input_stream_list structure itself + free(list); + } +} + +/** + * @brief Callback function for sink input information retrieval. + * + * This function is called by the PulseAudio context for each sink input + * retrieved by pa_context_get_sink_input_info_list(). It allocates memory + * for a new output_stream_info structure, copies the relevant sink input + * information into it, and stores it in the output_stream_list. + * + * This function handles the allocation of memory for each output_stream_info + * and ensures that relevant data from the pa_sink_input_info, such as the + * index, parent index, and name, are copied over. + * + * @param c The PulseAudio context. + * @param i The sink input information structure. + * @param eol End of list indicator. If positive, it's the end of the list. + * @param userdata User data pointer, cast to output_stream_list, passed to the function. + */ +static void get_output_streams_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + output_stream_list *input_list = (output_stream_list *) userdata; + + // Resize the inputs array to accommodate one more output_stream_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(output_stream_info)); + if (input_list->inputs == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input list\n"); + return; + } + + output_stream_info *new_input = &input_list->inputs[input_list->num_inputs]; + new_input->index = i->index; + new_input->owner_module = i->owner_module; + new_input->parent_index = i->sink; // Index of the corresponding output device (sink) + new_input->volume = i->volume; + new_input->sample_spec = i->sample_spec; + new_input->channel_map = i->channel_map; + + // Copy format info + if (i->format) { + new_input->format = pa_format_info_copy(i->format); + } else { + new_input->format = NULL; + } + + // Copy proplist + if (i->proplist) { + new_input->proplist = pa_proplist_copy(i->proplist); + } else { + new_input->proplist = NULL; + } + + // Copy name + new_input->name = strdup(i->name); + if (new_input->name == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input name\n"); + } + + // Copy driver + new_input->driver = strdup(i->driver); + if (new_input->driver == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input driver\n"); + } + + input_list->num_inputs++; +} + + + +/** + * @brief Retrieves a list of current sink inputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of sink inputs from the PulseAudio server. It allocates an instance of + * output_stream_list and populates it with output_stream_info structures. + * Each entry in the list is a dynamically allocated output_stream_info structure + * containing the sink input details such as index, parent index, and name. + * + * The caller is responsible for freeing the memory allocated for each + * output_stream_info structure and the output_stream_list itself after use. + * The function blocks until the operation is completed or fails. + * The sink input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to an output_stream_list containing all retrieved sink inputs, + * or NULL if an error occurs. + */ +output_stream_list *get_output_streams(pa_context *context) { + output_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for output_stream_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of sink inputs + pa_operation *op = pa_context_get_sink_input_info_list(context, get_output_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get sink input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + + +/** + * @brief Callback function to process each input stream found. + * + * This function is called by the PulseAudio context for each source output. + * It stores information about each input stream into an input_stream_list structure. + * When the end of the list is reached, it signals the main loop to continue execution. + * + * @param c Pointer to the PulseAudio context (not used in this callback). + * @param o Pointer to the pa_source_output_info structure containing the source output info. + * @param eol End of list flag. If non-zero, indicates no more data is forthcoming. + * @param userdata User data pointer, expected to be a pointer to an input_stream_list structure. + */ +static void get_input_streams_cb(pa_context *c, const pa_source_output_info *o, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + input_stream_list *input_list = (input_stream_list *) userdata; + + // Resize the outputs array to accommodate one more input_stream_info + input_list->outputs = realloc(input_list->outputs, (input_list->num_inputs + 1) * sizeof(input_stream_info)); + if (input_list->outputs == NULL) { + fprintf(stderr, "Failed to allocate memory for source output list\n"); + return; + } + + // Reference the new element where we will store the info + input_stream_info *new_info = &input_list->outputs[input_list->num_inputs]; + + // Copy the relevant info from pa_source_output_info to input_stream_info + new_info->index = o->index; + new_info->owner_module = o->owner_module; + new_info->parent_index = o->source; // Index of the corresponding input device (source) + new_info->volume = o->volume; + new_info->sample_spec = o->sample_spec; + new_info->channel_map = o->channel_map; + + // Copy format info + if (o->format) { + new_info->format = pa_format_info_copy(o->format); + } else { + new_info->format = NULL; + } + + // Copy proplist + if (o->proplist) { + new_info->proplist = pa_proplist_copy(o->proplist); + } else { + new_info->proplist = NULL; + } + + // Copy name + new_info->name = strdup(o->name); + if (new_info->name == NULL) { + fprintf(stderr, "Failed to allocate memory for source output name\n"); + } + + // Copy driver + new_info->driver = strdup(o->driver); + if (new_info->driver == NULL) { + fprintf(stderr, "Failed to allocate memory for source output driver\n"); + } + + // Increment the inputs counter + input_list->num_inputs++; +} + +/** + * @brief Retrieves a list of all input streams (source outputs) from the PulseAudio server. + * + * This function initiates an asynchronous operation to list all the source outputs (input streams) + * currently available in the PulseAudio server. It waits for the operation to complete and + * returns a list of input streams. + * + * @param context Pointer to the initialized and connected PulseAudio context. + * @return Pointer to an input_stream_list structure containing the list of input streams. + * Returns NULL if the operation fails or memory allocation is not possible. + */ +input_stream_list *get_input_streams(pa_context *context) { + input_stream_list *input_list = malloc(sizeof(input_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for input_stream_list\n"); + return NULL; + } + + input_list->outputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of source outputs + pa_operation *op = pa_context_get_source_output_info_list(context, get_input_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get source output list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + +/** + * @brief Callback function for retrieving the active profile information of a PulseAudio card. + * + * This function is called by PulseAudio in response to pa_context_get_card_info_by_name. + * It processes the card information and stores the active profile if found. + * + * @param c Pointer to the PulseAudio context. Not used in this function. + * @param i Pointer to the PulseAudio card information structure. + * @param eol End-of-list flag. If positive, it indicates no more data is available. + * @param userdata User-provided pointer to store the resulting profile information. + * + * @note The function assumes userdata is a pointer to card_profile_info structure. + */ +static void get_active_profile_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + fprintf(stderr, "[DEBUG, card_info_cb()], reached function\n"); + + card_profile_info *info = (card_profile_info *)userdata; + printf("[DEBUG] i is: %p\n", i); + + if (eol > 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // No more data, or null card info + } + + if (i->active_profile && !info->found) { + // Allocate memory for the new profile + info->active_profile = malloc(sizeof(pa_card_profile_info)); + if (info->active_profile) { + // Duplicate the name and description strings + info->active_profile->name = strdup(i->active_profile->name); + info->active_profile->description = strdup(i->active_profile->description); + info->found = true; + } + } + + fprintf(stderr, "[DEBUG, card_info_cb()] card_info_cb is %s\n", i->active_profile->description); +} + +/** + * @brief Retrieves the active profile information for a specified PulseAudio card. + * + * This function initiates a query to PulseAudio to get information about a specific card. + * It blocks until the callback (get_active_profile_cb) has processed the data. + * + * @param context Pointer to the PulseAudio context. + * @param card_name Name of the PulseAudio card to query. + * + * @return Pointer to the active profile information on success, NULL on failure or if no active profile is found. + * + * @note The returned pointer should not be freed by the caller, as it points to statically allocated memory. + */ +pa_card_profile_info *get_active_profile(pa_context *context, char *card_name) { + static card_profile_info info; + + if (!context) { + fprintf(stderr, "Invalid arguments.\n"); + return NULL; + } + + info.active_profile = NULL; + info.found = false; + + pa_operation *op = pa_context_get_card_info_by_name(context, card_name, get_active_profile_cb, &info); + iterate(op); + + //fprintf(stderr, "[DEBUG, get_active_profile()] info.active_profile is %s\n", info.active_profile->description); + return info.active_profile; +} diff --git a/v-0.15/system_query.h b/v-0.15/system_query.h new file mode 100644 index 0000000..6774e59 --- /dev/null +++ b/v-0.15/system_query.h @@ -0,0 +1,176 @@ +/** + * @file system_query.h + * @brief Header file for querying sound card properties in a PulseAudio environment. + * + * This header file provides a collection of functions and structures to interact with and query + * various properties of sound cards using PulseAudio and ALSA interfaces. It includes functions + * to obtain information about output and input devices (sinks and sources), such as the number + * of devices, device names, channel names, sample rates, and mute states. It also offers + * capabilities to manipulate and retrieve detailed information about ALSA cards and ports. + * + * Structures: + * - pa_port_info: Represents port information (e.g., line-in, microphone). + * - pa_source_info_list: Holds a list of pa_port_info structures. + * + * This file serves as an essential component for applications that need to interact with + * PulseAudio and ALSA for detailed audio device management and information retrieval. + * + * @author Mbyte2 + * @date November 13, 2023 + */ +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +typedef struct { + uint32_t index; + uint32_t owner_module; + uint32_t parent_index; //Index of the corresponding output device (sink). + pa_cvolume volume; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_proplist *proplist; + char *name; + char *driver; +} output_stream_info; + +//Used by get_output_streams() to get a list of all Pulseaudio sink inputs. +typedef struct output_stream_list { + output_stream_info *inputs; + uint32_t num_inputs; +} output_stream_list; + +typedef struct { + uint32_t index; + uint32_t owner_module; + uint32_t parent_index; //Index of the corresponding output device (sink). + pa_cvolume volume; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_proplist *proplist; + char *name; + char *driver; +} input_stream_info; + +//Used by get_input_streams() to get a list of all Pulseaudio source outputs. +typedef struct input_stream_list { + input_stream_info *outputs; + uint32_t num_inputs; +} input_stream_list; + + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the minimum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the minimum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +bool get_output_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index); //Gets mute state of single output channel (0 = unmuted, 1 = muted). + +bool get_input_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index); //Gets mute state of single input channel (0 = unmuted, 1 = muted). + +output_stream_list *get_output_streams(pa_context *context); //Gets a list of output (sink) inputs. They must be freed after allocated. +void output_streams_cleanup(output_stream_list *list); //Cleans up allocated output streams. +void input_streams_cleanup(input_stream_list *list); //Cleans up allocated input streams. + +input_stream_list *get_input_streams(pa_context *context); //Gets a list of input (source) inputs. + +pa_card_profile_info *get_active_profile(pa_context *context, +char *output_name); //Gets the active profile of a card. + +#endif diff --git a/v-0.16/Makefile b/v-0.16/Makefile new file mode 100644 index 0000000..b1c5107 --- /dev/null +++ b/v-0.16/Makefile @@ -0,0 +1,20 @@ +CC = gcc +CFLAGS = -Wall -g -Wextra + +LIB_NAME = easypulse_core +LIB_SRC = easypulse_core.c +LIB_OBJ = easypulse_core.o +LIB_OUT = lib$(LIB_NAME).a + +all: $(LIB_OUT) + +$(LIB_OUT): $(LIB_OBJ) + ar rcs $(LIB_OUT) $(LIB_OBJ) + +$(LIB_OBJ): $(LIB_SRC) + $(CC) $(CFLAGS) -c $(LIB_SRC) -o $(LIB_OBJ) + +clean: + rm -f $(LIB_OBJ) $(LIB_OUT) + +.PHONY: all clean diff --git a/v-0.16/documentation/pa_context -- interface overview.docx b/v-0.16/documentation/pa_context -- interface overview.docx new file mode 100644 index 0000000..6cbe2d0 Binary files /dev/null and b/v-0.16/documentation/pa_context -- interface overview.docx differ diff --git a/v-0.16/documentation/pavucontrol/pavucontrol sink update flow.txt b/v-0.16/documentation/pavucontrol/pavucontrol sink update flow.txt new file mode 100644 index 0000000..9428de8 --- /dev/null +++ b/v-0.16/documentation/pavucontrol/pavucontrol sink update flow.txt @@ -0,0 +1,41 @@ ++----------------+ +| pavucontrol | +| (initial file)| ++----------------+ + | + | Callback function triggered when there's info/change related to a sink + V ++----------------+ +| sink_cb | +| (pavucontrol.cc)| ++----------------+ + | + | - If error, show error and return + | - If end-of-list (eol) is reached, decrease outstanding tasks count and return + | - Update GUI representation of a sink using w->updateSink() + V ++---------------------------+ +| MainWindow::updateSink | +| (mainwindow.cc & .h) | ++---------------------------+ + | + | - Retrieve or create SinkWidget for the sink + | - Update SinkWidget properties (e.g., volume, mute state, active port, etc.) + | - Prepare context menu for the sink + | - Ensure the sink is displayed correctly via updateDeviceVisibility + V ++-------------------------------------+ +| MainWindow::updateDeviceVisibility | +| (mainwindow.cc & .h) | ++-------------------------------------+ + | + | - If idle source/callback is already queued, return + | - Schedule the idle_cb function to be called when the app is idle + V ++----------------+ +| idle_cb | +| (mainwindow.cc)| ++----------------+ + | + V + ... (Specific steps and logic for updating device visibility) diff --git a/v-0.16/documentation/pulseaudio/introspect.c summary b/v-0.16/documentation/pulseaudio/introspect.c summary new file mode 100644 index 0000000..d9616f2 --- /dev/null +++ b/v-0.16/documentation/pulseaudio/introspect.c summary @@ -0,0 +1,79 @@ +* !pa_tagstruct_eof (context_string_callback): this function seems to be a callback function for processing string responses from the server. It examines the incoming command and checks for errors. If there's an error or if the command isn't a reply, it sets the success flag to 0 and prepares an empty response string. If the command is a reply, it extracts the response string from the tag structure. If there's any issue with extracting the string or if there's unexpected data in the tag structure, it flags a protocol error. + +* pa_context_stat: this function sends a simple command to request statistics. It leverages another function pa_context_send_simple_command and provides the context_stat_callback as the callback function to handle the response. It's used to retrieve statistical information. + +* pa_context_get_server_info: similar to the previous function, this function sends a command to get server information. It again uses pa_context_send_simple_command, but this time with a different callback, context_get_server_info_callback, to process the server information response. + +* context_get_sink_info_callback: this function is a callback that processes information about a sink. It first ensures that the operation and context are valid. If the received command is not a reply, it handles the error. If it is a reply, it starts parsing the sink information from the tag structure, extracting details like the sink's name, description, sample specification, channel map, owner module, volume, mute status, monitor source, latency, and more. The function seems to account for different versions of the context and extracts varying levels of information based on that. + +* pa_tagstruct_getu32: this function seems to be a snippet or part of another function and is possibly not a standalone function. It attempts to retrieve a 32-bit unsigned integer from a tag structure. If unsuccessful, it returns an error indicating a protocol issue. + +* pa_context_get_sink_info_list: this function sends a command to retrieve a list of sink information. It utilizes the pa_context_send_simple_command function and provides the context_get_sink_info_callback as the callback to process the list of sinks. + +* pa_context_get_sink_info_by_index: this function requests information about a specific sink identified by its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SINK_INFO) with the specified sink index and sends it to the server. The response from the server is expected to be handled by the context_get_sink_info_callback. + +* pa_context_get_sink_info_by_name: similar to the previous function, this one requests information about a sink, but it identifies the sink by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SINK_INFO) with the specified sink name to the server. The server's response will be processed by the context_get_sink_info_callback. + +* pa_context_set_sink_port_by_index: this function is designed to set the port for a specific sink identified by its index. After some validity checks, it builds a command (PA_COMMAND_SET_SINK_PORT) with the given sink index and port information, then sends this command to the server. The acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_sink_port_by_name: this function sets the port of a sink based on its name. It first performs some validation checks, then constructs a command with the sink's name and desired port. This command is sent to the PulseAudio server, and the response will be handled by a generic acknowledgment callback. + +* context_get_source_info_callback: this callback function processes information about a source. It starts by performing some initializations and checks. If the received command is a reply, it begins parsing the source information from the tag structure. This includes extracting details like the source's name, description, sample specification, volume, mute status, latency, and more. It appears to accommodate different versions of the context, extracting varying levels of information based on that. + +* pa_context_get_source_info_list: this function sends a command to retrieve a list of source information. It employs the pa_context_send_simple_command function, providing the context_get_source_info_callback as the callback to process the list of sources. + +* pa_context_get_source_info_by_index: this function retrieves information about a specific audio source based on its index. After performing some validity checks, it constructs a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source index and sends it to the server. The response from the server is expected to be handled by the context_get_source_info_callback. + +* pa_context_get_source_info_by_name: similar to the previous function, this one retrieves information about a source but identifies the source by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_SOURCE_INFO) with the specified source name to the server. The server's response is again expected to be processed by the context_get_source_info_callback. + +* pa_context_set_source_port_by_index: this function sets the port of a specific source based on its index. After performing some validity checks and ensuring that the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source index. This command is sent to the server, and the acknowledgment from the server will be handled by a generic callback, pa_context_simple_ack_callback. + +* pa_context_set_source_port_by_name: this function is designed to set the port of a specific audio source based on its name. It constructs a command (PA_COMMAND_SET_SOURCE_PORT) with the desired port information and source name and sends this command to the PulseAudio server. The server's response will be handled by a generic acknowledgment callback. + +* context_get_client_info_callback: This callback function processes client-related information. It first performs checks to ensure the received command is a reply. If it is, the function begins parsing the client information, extracting details like client index, name, owner module, and driver. The function appears to be prepared to handle multiple client records in the same response and will loop through them until the end of the tag structure. + +* pa_context_get_client_info: this function requests information about a specific client based on its index. It constructs and sends a command (PA_COMMAND_GET_CLIENT_INFO) with the specified client index to the server. The server's response is expected to be processed by the context_get_client_info_callback. + +* pa_context_get_client_info_list: this function requests a list of client information from the PulseAudio server. It utilizes the pa_context_send_simple_command function and specifies the PA_COMMAND_GET_CLIENT_INFO_LIST command. The response from the server is expected to be handled by the context_get_client_info_callback. + + +* card_info_free: this is a utility function designed to free the memory associated with a pa_card_info structure. It releases memory for various properties and profiles associated with the card, such as its property list (proplist), profiles (profiles), and extended profile information (profiles2). + +* fill_card_port_info: this function populates the port information for a given card. It starts by extracting the number of ports (n_ports) for the card from a tag structure. If the card has ports, it then proceeds to fill in details about each port. If there's a protocol error while fetching the details, it returns an error. + +* fill_card_profile_info: this function is responsible for populating the profile information of a given card. It begins by allocating memory for the profiles and then proceeds to iterate through each profile, extracting details like name, description, number of sinks, number of sources, and priority. It also accounts for different versions of the context, fetching additional details if the version is >= 29. + + +* context_get_card_info_callback: this callback function processes card-related information. It starts by performing initialization checks and then examines the received command. If the command is a reply, the function starts parsing card details like index, name, owner module, driver, and the number of profiles. It then calls the fill_card_profile_info function to populate the profiles for the card. If there are any protocol errors during this process, the function handles them accordingly. + + +* pa_tagstruct_get_proplist: This function seems to be a snippet or a partial representation of a larger function. Its purpose appears to be to extract a property list (proplist) from a tag structure. If there's any issue during this extraction, it flags an error. + + +* pa_context_get_card_info_by_index: this function requests information about a specific card based on its index. After performing several validity checks, it constructs a command (PA_COMMAND_GET_CARD_INFO) with the specified card index and sends it to the server. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_by_name: similar to the previous function, this one requests information about a card, but it identifies the card by its name instead of its index. It constructs and sends a command (PA_COMMAND_GET_CARD_INFO) with the specified card name to the server. Again, the server's response is expected to be processed by the context_get_card_info_callback. + +* pa_context_get_card_info_list: this function requests a list of all card information from the PulseAudio server. It employs the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_CARD_INFO_LIST command. The response from the server will be processed by the context_get_card_info_callback. + +* pa_context_set_card_profile_by_index: this function sets the profile of a specific card based on its index. After checking the validity of the context and ensuring the PulseAudio server version supports this operation, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the specified card index and desired profile. This command is sent to the server, and the response is handled by a generic acknowledgment callback. + +* pa_context_set_card_profile_by_name: this function sets the profile of a card identified by its name. Like the previous function, it constructs a command (PA_COMMAND_SET_CARD_PROFILE) with the card name and desired profile, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_module_info_callback: this callback function processes module-related information. It checks if the received command is a reply and, if so, starts parsing the module details. This includes extracting details like the module's index, name, argument, and usage count. If there's an error or unexpected end of file in the tag structure, it flags a protocol error. + +* pa_context_get_module_info: this function retrieves information about a specific module based on its index. After conducting various validity checks, it constructs a command (PA_COMMAND_GET_MODULE_INFO) with the specified module index and sends it to the server. The response from the server will be processed by the context_get_module_info_callback. + +* pa_context_get_module_info_list: this function requests a list of all module information from the PulseAudio server. It uses the pa_context_send_simple_command function, specifying the PA_COMMAND_GET_MODULE_INFO_LIST command. The server's response will be processed by the context_get_module_info_callback. + +* context_get_sink_input_info_callback: this callback function processes information related to sink inputs. It checks if the received command is a reply and, if so, starts parsing the sink input details. This includes extracting details like the sink input's index, name, owner module, client, sink, sample specification, channel map, volume, buffer usec, sink usec, resample method, driver, and more. It also accounts for different versions of the context, extracting varying levels of information based on that. + +* pa_context_set_source_output_volume: this function sets the volume of a specific source output based on its index. It starts by performing various checks, including verifying the validity of the provided volume. It then constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME) with the specified source output index and volume and sends it to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_set_source_output_mute: this function mutes or unmutes a specific source output based on its index. It constructs a command (PA_COMMAND_SET_SOURCE_OUTPUT_MUTE) with the desired mute status and the source output index, then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* context_get_sample_info_callback: this callback function processes sample-related information. It checks if the received command is a reply and, if so, begins parsing the sample details, extracting attributes like the sample's index, name, volume, mute status, proplist, and more. The function also handles possible protocol errors and is prepared to process multiple sample records in the same response. + +* pa_context_suspend_source_by_index: this function suspends or resumes a specific source based on its index. It verifies the server version, constructs a command (PA_COMMAND_SUSPEND_SOURCE) with the desired suspend status and source index, and then sends this command to the server. The server's response will be processed by a generic acknowledgment callback. + +* pa_context_send_message_to_object: this function sends a message to a specific object within the PulseAudio server. After checking the server version and other conditions, it constructs a command (PA_COMMAND_SEND_OBJECT_MESSAGE) with the object's path, the message, and any additional parameters. The server's response will be processed by a callback function. diff --git a/v-0.16/documentation/pulseaudio/mainloop code flow.txt b/v-0.16/documentation/pulseaudio/mainloop code flow.txt new file mode 100644 index 0000000..f4c8b96 --- /dev/null +++ b/v-0.16/documentation/pulseaudio/mainloop code flow.txt @@ -0,0 +1,17 @@ ++---------------------------------------------+ +| Mainloop | +| | +| +--------------+ +-------------------+ | +| | I/O Events | | Timers | | +| +--------------+ +-------------------+ | +| | +| +--------------+ +-------------------+ | +| | Signals | | Deferred Callbacks| | +| +--------------+ +-------------------+ | +| | +| +---------------------+ | +| | Other Asynchronous | | +| | Tasks | | +| +---------------------+ | +| | ++---------------------------------------------+ diff --git a/v-0.16/easypulse_core.c b/v-0.16/easypulse_core.c new file mode 100644 index 0000000..b242806 --- /dev/null +++ b/v-0.16/easypulse_core.c @@ -0,0 +1,1147 @@ +/** + * @file easypulse_core.c + * @brief Implementation of the easypulse core functions. + * + * This file provides the core functionality to interact with PulseAudio, + * allowing operations like setting the default device and adjusting volume. + */ + +#include "easypulse_core.h" +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + + +static bool manager_initialize(pulseaudio_manager *self); +static void iterate(pulseaudio_manager *manager, pa_operation *op); + +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, +int eol, void *userdata); + +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata); + +//Shared data between manager_switch_default_output and its callbacks +typedef struct _shared_data_1 { + pulseaudio_manager *manager; + uint32_t new_index; //Index of the new default sink. + +} _shared_data_1; + +//Shared data between manager_set_output_channel_mute_state and its callbacks +typedef struct _shared_data_2 { + pulseaudio_manager *manager; + uint32_t channel_index; + bool mute_state; + pa_cvolume new_volume; +} _shared_data_2; + +/** + * @brief Creates a new pulseaudio_manager instance. + * + * This function allocates memory for a new pulseaudio_manager instance and initializes it. + * It allocates memory for the output and input devices based on the current system state, + * and initializes the PulseAudio context and mainloop. It also sets the active output and + * input devices. + * + * If any memory allocation or initialization operation fails, the function cleans up any + * resources that were successfully allocated or initialized, and returns NULL. + * + * @return A pointer to the newly created pulseaudio_manager instance, or NULL if the + * creation failed. + */ +pulseaudio_manager *manager_create(void) { + pulseaudio_manager *self = malloc(sizeof(pulseaudio_manager)); + if (!self) { + fprintf(stderr, "Failed to allocate memory for pulseaudio_manager.\n"); + return NULL; + } + + // Zero-initialize the structure to set sensible defaults + memset(self, 0, sizeof(pulseaudio_manager)); + + // Initialize manager's PulseAudio main loop and context + if (!manager_initialize(self)) { + fprintf(stderr, "Failed to initialize pulseaudio_manager.\n"); + free(self); + return NULL; + } + + // Get the count of output and input devices + self->output_count = get_output_device_count(); + self->input_count = get_input_device_count(); + + // Allocate memory for outputs + if (self->output_count > 0) { + self->outputs = calloc(self->output_count, sizeof(pulseaudio_device)); + if (!self->outputs) { + fprintf(stderr, "Failed to allocate memory for outputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate output devices + pa_sink_info **output_devices = get_available_output_devices(); + + for (uint32_t i = 0; i < self->output_count; ++i) { + self->outputs[i].index = output_devices[i]->index; + self->outputs[i].name = strdup(output_devices[i]->description); + self->outputs[i].code = strdup(output_devices[i]->name); + + + char *alsa_id = get_alsa_output_id(output_devices[i]->name); + + //Do NOT attempt to duplicate the string if alsa_id is null, as the program can crash! + if (alsa_id) { + self->outputs[i].alsa_id = strdup(alsa_id); + } else { + self->outputs[i].alsa_id = NULL; + } + self->outputs[i].sample_rate = get_output_sample_rate(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].max_channels = get_max_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].min_channels = get_min_output_channels(self->outputs[i].alsa_id, output_devices[i]); + self->outputs[i].channel_names = get_output_channel_names(output_devices[i]->name, self->outputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->output_count; ++i) { + if (output_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(output_devices[i]); + } + } + free(output_devices); + } + + // Allocate memory for inputs + if (self->input_count > 0) { + self->inputs = calloc(self->input_count, sizeof(pulseaudio_device)); + if (!self->inputs) { + fprintf(stderr, "Failed to allocate memory for inputs.\n"); + manager_cleanup(self); + return NULL; + } + // Retrieve and populate input devices + pa_source_info **input_devices = get_available_input_devices(); + + for (uint32_t i = 0; i < self->input_count; ++i) { + self->inputs[i].index = input_devices[i]->index; + self->inputs[i].name = strdup(input_devices[i]->description); + self->inputs[i].code = strdup(input_devices[i]->name); + + char *alsa_id = get_alsa_input_id(input_devices[i]->name); + + if (alsa_id) { + self->inputs[i].alsa_id = strdup(alsa_id); + } else { + self->inputs[i].alsa_id = NULL; + } + + self->inputs[i].sample_rate = get_input_sample_rate(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].max_channels = get_max_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].min_channels = get_min_input_channels(self->inputs[i].alsa_id, input_devices[i]); + self->inputs[i].channel_names = get_input_channel_names(input_devices[i]->name, self->inputs[i].max_channels); + + free(alsa_id); + } + for (uint32_t i = 0; i < self->input_count; ++i) { + if (input_devices[i]) { + // Free other dynamically allocated fields within output_devices[i] if any + free(input_devices[i]); + } + } + free(input_devices); + } + + // Set the default output and input devices + self->active_output_device = strdup(get_default_output(self->context)); + self->active_input_device = strdup(get_default_input(self->context)); + + // Check that the active devices were set + if (!self->active_output_device || !self->active_input_device) { + fprintf(stderr, "Failed to set the active output or input device.\n"); + manager_cleanup(self); + return NULL; + } + + return self; +} + + +/** + * @brief Callback function for handling PulseAudio context state changes. + * + * This callback is invoked by the PulseAudio mainloop when the context state changes. + * It updates the `pa_ready` flag in the pulseaudio_manager structure based on the + * context's state. The `pa_ready` flag is set to 1 when the context is ready, and + * to 2 when the context has failed or terminated. This callback will signal the + * mainloop to continue its operations whenever the state changes to either READY, + * FAILED, or TERMINATED. + * + * @param c Pointer to the PulseAudio context. + * @param userdata User-provided pointer to the pulseaudio_manager structure. + */ +static void manager_initialize_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + int *pa_ready = &(manager->pa_ready); + pa_threaded_mainloop *m = manager->mainloop; + + state = pa_context_get_state(c); + switch (state) { + // There are other states you can handle, but these are the important ones + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_READY: + *pa_ready = 1; + pa_threaded_mainloop_signal(m, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + *pa_ready = 2; + pa_threaded_mainloop_signal(m, 0); + break; + } +} + + +/** + * @brief Initializes the PulseAudio manager. + * + * This function sets up the PulseAudio threaded mainloop and context for the given manager. + * It creates the mainloop, context, and connects to the PulseAudio server, then starts + * the mainloop and waits for the context to be ready. It also sets up a state callback + * to handle the context state changes. + * + * @param self Pointer to the pulseaudio_manager structure to be initialized. + * @return Returns true if initialization is successful, false otherwise. + * + * @note The function will clean up allocated resources and return false if any step + * of the initialization fails. + */ +static bool manager_initialize(pulseaudio_manager *self) { + // 1. Create the threaded mainloop + self->mainloop = pa_threaded_mainloop_new(); + if (!self->mainloop) { + return false; + } + + // Get the mainloop API + pa_mainloop_api *mlapi = pa_threaded_mainloop_get_api(self->mainloop); + + // Create the context + self->context = pa_context_new(mlapi, "PulseAudio Manager"); + if (!self->context) { + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // Set the state callback + pa_context_set_state_callback(self->context, manager_initialize_cb, self); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(self->mainloop); + + if (pa_context_connect(self->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(self->mainloop); + pa_threaded_mainloop_free(self->mainloop); + return false; + } + + // 2. Start the threaded mainloop + pa_threaded_mainloop_start(self->mainloop); + + // 4. Wait for the context to be ready + while (self->pa_ready == 0) { + pa_threaded_mainloop_wait(self->mainloop); + } + + pa_threaded_mainloop_unlock(self->mainloop); + + if (self->pa_ready == 2) { + return false; + } + + return true; +} + +/** + * Cleans up and frees all resources associated with a pulseaudio_manager object. + * This function ensures that all memory allocated for output and input devices + * within the manager is released. It includes freeing of all associated strings, + * channel names, and profile data. Additionally, it shuts down and frees the + * PulseAudio context and mainloop, if they have been initialized. + * + * @param manager A pointer to the pulseaudio_manager object to be cleaned up. + * If the pointer is NULL, the function does nothing. + */ +void manager_cleanup(pulseaudio_manager *manager) { + if (manager) { + // Free output devices + if (manager->outputs) { + for (uint32_t i = 0; i < manager->output_count; ++i) { + free(manager->outputs[i].code); + free(manager->outputs[i].name); + free(manager->outputs[i].alsa_id); + if (manager->outputs[i].channel_names) { + for (int j = 0; j < manager->outputs[i].max_channels; ++j) { + free(manager->outputs[i].channel_names[j]); + } + free(manager->outputs[i].channel_names); + } + if (manager->outputs[i].profiles) { + for (uint32_t j = 0; j < manager->outputs[i].profile_count; ++j) { + free((char*)manager->outputs[i].profiles[j].name); + free((char*)manager->outputs[i].profiles[j].description); + } + free(manager->outputs[i].profiles); + } + } + free(manager->outputs); // Finally free the array itself + } + + // Free input devices + if (manager->inputs) { + for (uint32_t i = 0; i < manager->input_count; ++i) { + free(manager->inputs[i].code); + free(manager->inputs[i].name); + free(manager->inputs[i].alsa_id); + if (manager->inputs[i].channel_names) { + for (int j = 0; j < manager->inputs[i].max_channels; ++j) { + free(manager->inputs[i].channel_names[j]); + } + free(manager->inputs[i].channel_names); + } + if (manager->inputs[i].profiles) { + for (uint32_t j = 0; j < manager->inputs[i].profile_count; ++j) { + free((char*)manager->inputs[i].profiles[j].name); + free((char*)manager->inputs[i].profiles[j].description); + } + free(manager->inputs[i].profiles); + } + } + free(manager->inputs); // Finally free the array itself + } + + // Free the names of active output and input devices + free(manager->active_output_device); + free(manager->active_input_device); + + // Disconnect and unreference the context if it's there + if (manager->context) { + // Check if the context is in a state that can be disconnected + if (pa_context_get_state(manager->context) == PA_CONTEXT_READY) { + pa_context_disconnect(manager->context); + } + pa_context_unref(manager->context); + } + + // Stop and free the mainloop if it's there + if (manager->mainloop) { + pa_threaded_mainloop_stop(manager->mainloop); + pa_threaded_mainloop_free(manager->mainloop); + } + + // Free the manager itself + free(manager); + } +} + + + + +/** + * @brief Iterates through operations in the pulseaudio_manager. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pulseaudio_manager *manager, pa_operation *op) { + //Leaves if operation is invalid. + if (!op) return; + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(manager->mainloop); + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(manager->mainloop); + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + pa_threaded_mainloop_wait(manager->mainloop); + + //Cleaning up. + pa_operation_unref(op); + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(manager->mainloop); + } +} + +/** + * @brief Callback function for setting master volume on a device. + * + * This function is called when the asynchronous operation to set the volume + * for a sink completes. It will signal the mainloop to stop waiting. + * + * @param c The PulseAudio context. + * @param success Non-zero if the operation succeeded, zero if it failed. + * @param userdata The userdata passed to the function, a pointer to the pulseaudio_manager. + */ +void manager_set_master_volume_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + + // Check if the operation was successful + if (success) { + printf("Volume set successfully.\n"); + } else { + printf("Failed to set volume.\n"); + } + + // Signal the mainloop to stop waiting + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +/** + * @brief Sets the master volume of a given device. + * + * This function sets the master volume of a device specified by its PulseAudio ID. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param device_id The PulseAudio ID of the device. + * @param volume The new volume level to set. This should be a value between 0 (mute) and 100 (maximum volume). + * @return 0 if the operation was successful, or a non-zero error code if the operation failed. + */ +int manager_set_master_volume(pulseaudio_manager *manager, uint32_t device_id, int volume) { + if (!manager) { + fprintf(stderr, "Manager is NULL\n"); + return -1; + } + + if(volume < 0 || volume > 100) { + fprintf(stderr, "[manager_set_master_volume] The volume specified is out of range (0-100).\n"); + return -1; + } + + // Fetch the sink information for the device ID + const pa_sink_info *sink_info = get_output_device_by_index(device_id); + if (!sink_info) { + fprintf(stderr, "Could not retrieve sink info for device ID %u\n", device_id); + return -1; + } + + // Calculate the PA volume from the provided percentage + pa_volume_t pa_volume = (pa_volume_t) ((double) volume / 100.0 * PA_VOLUME_NORM); + + // Initialize a pa_cvolume structure and set the volume for all channels + pa_cvolume cvolume; + pa_cvolume_set(&cvolume, sink_info->channel_map.channels, pa_volume); + + // Start the asynchronous operation to set the sink volume + pa_operation *op = pa_context_set_sink_volume_by_index(manager->context, device_id, &cvolume, manager_set_master_volume_cb, manager); + if (!op) { + fprintf(stderr, "Failed to start volume set operation\n"); + return -1; + } + + // Wait for the operation to complete + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an output mute toggle operation. + * + * This function is invoked by the PulseAudio main loop upon the completion of an operation + * to toggle the mute state of an output device (sink). It is used in conjunction with + * `pa_context_set_sink_mute_by_index` as part of the `manager_toggle_output_mute` function. + * The callback checks if the mute toggle operation was successful and signals the mainloop + * to continue processing. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * A non-zero value indicates success, while zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_output_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * Toggle the mute state of a given output device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the output device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->output_count) { + fprintf(stderr, "Output device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_sink_mute_by_index(manager->context, + index, state, manager_toggle_output_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback function for handling the completion of an input mute toggle operation. + * + * This function is called by the PulseAudio main loop when the operation to toggle + * the mute state of an input device (source) is completed. The function is used in + * conjunction with `pa_context_set_source_mute_by_index` within the `manager_toggle_input_mute` + * function. It checks if the operation was successful and signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. This parameter is not used in this callback. + * @param success An integer indicating the success of the mute toggle operation. + * Non-zero value indicates success, zero indicates failure. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pulseaudio_manager` instance. + * + */ +static void manager_toggle_input_mute_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + if (!success) { + fprintf(stderr, "Failed to toggle input mute state.\n"); + } + + // Signal the mainloop to continue + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Toggle the mute state of a given input device. + * + * @param manager A pointer to the initialized pulseaudio_manager instance. + * @param index The index of the input device to toggle mute state. + * @param state The desired mute state (1 for ON/mute, 0 for OFF/unmute). + * @return Returns 0 on success, -1 on failure. + */ +int manager_toggle_input_mute(pulseaudio_manager *manager, uint32_t index, int state) { + + if (!manager || !manager->context) { + fprintf(stderr, "Invalid PulseAudio manager or context.\n"); + return -1; + } + + if (index >= manager->input_count) { + fprintf(stderr, "Input device index out of range.\n"); + return -1; + } + + pa_operation *op = pa_context_set_source_mute_by_index(manager->context, + index, state, manager_toggle_input_mute_cb, manager); + + iterate(manager, op); + + return 0; +} + +/** + * @brief Callback for handling the completion of setting the default sink. + * + * This callback is invoked when the operation to set the default sink in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_output_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default sink.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * @brief Callback for handling each sink input during the process of moving them to a new sink. + * + * This callback is invoked for each sink input (audio stream) currently active. It moves + * each sink input to the new default sink specified in the shared_data. + * + * @param c The PulseAudio context. + * @param i The sink input information. + * @param eol End of list flag, indicating no more data. + * @param userdata User-provided data, expected to be a pointer to shared_data_1 structure. + */ +static void manager_switch_default_output_cb_2(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + + _shared_data_1 *shared_data = (_shared_data_1 *) userdata; + pa_threaded_mainloop *mainloop = shared_data->manager->mainloop; + + if (eol < 0) { + // Error occurred, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + if (!eol && i) { + // Move sink input to the new sink index stored in shared_data + pa_operation *op_move = pa_context_move_sink_input_by_index(c, i->index, shared_data->new_index, NULL, NULL); + if (op_move) { + pa_operation_unref(op_move); + pa_threaded_mainloop_signal(mainloop, 0); + } + } + + if (eol > 0) { + // End of list, signal the main loop to continue + pa_threaded_mainloop_signal(mainloop, 0); + } +} + +/** + * @brief Switches the default output device to the specified device. + * + * This function sets the specified output device as the default sink in PulseAudio. + * It also moves all current sink inputs (audio streams) to the new default sink. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the output device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_output(pulseaudio_manager *self, uint32_t device_index) { + //To be sent to the second callback. + _shared_data_1 shared_data = {self, self->outputs[device_index].index}; + + if (!self || !self->context || device_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + const char *new_sink_name = self->outputs[device_index].code; + if (!new_sink_name) { + fprintf(stderr, "Output device code is NULL.\n"); + return false; + } + + // Set the new default sink + pa_operation *op = pa_context_set_default_sink(self->context, new_sink_name, manager_switch_default_output_cb, self); + iterate(self, op); + + shared_data.new_index = get_output_device_index_by_code(self->context, self->outputs[device_index].code); + //fprintf(stderr, "[DEBUG, manager_switch_default_output()] index is %lu\n", (unsigned long) shared_data.new_index); + + // Move all sink inputs to the new default sink + op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + iterate(self, op); + + return true; +} + +/** + * @brief Callback for handling the completion of setting the default source. + * + * This callback is invoked when the operation to set the default source in PulseAudio + * is completed. It signals the main loop to continue the execution flow. + * + * @param c The PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User-provided data, expected to be a pointer to a pulseaudio_manager instance. + */ +static void manager_switch_default_input_cb(pa_context *c, int success, void *userdata) { + (void) c; + + pulseaudio_manager *manager = (pulseaudio_manager *)userdata; + if (!success) { + fprintf(stderr, "Failed to set default source.\n"); + } + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + + +/** + * @brief Switches the default input device to the specified device. + * + * This function sets the specified input device as the default source in PulseAudio. + * It requires a valid PulseAudio context and uses the PulseAudio API to set the new default source. + * + * @param self Pointer to the pulseaudio_manager instance. + * @param device_index Index of the input device to be set as the default. + * @return True if the operation was successful, False otherwise. + */ +bool manager_switch_default_input(pulseaudio_manager *self, uint32_t device_index) { + // Validate the arguments + if (!self || !self->context || device_index >= self->input_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return false; + } + + // Retrieve the code (PulseAudio name) of the new default input device + const char *new_source_name = self->inputs[device_index].code; + if (!new_source_name) { + fprintf(stderr, "Input device code is NULL.\n"); + return false; + } + + // Lock the main loop to ensure thread safety during the operation + pa_threaded_mainloop_lock(self->mainloop); + + // Initiate the operation to set the new default source + pa_operation *op = pa_context_set_default_source(self->context, new_source_name, manager_switch_default_input_cb, self); + if (op) { + pa_operation_unref(op); + } else { + fprintf(stderr, "Failed to set default source.\n"); + pa_threaded_mainloop_unlock(self->mainloop); + return false; + } + + // Wait for the completion of the operation + pa_threaded_mainloop_wait(self->mainloop); + + // Unlock the main loop after the operation is complete + pa_threaded_mainloop_unlock(self->mainloop); + + return true; +} + +/** + * @brief Sets the global sample rate for PulseAudio. + * + * This function attempts to set the global sample rate for PulseAudio by modifying + * the PulseAudio configuration files. It first tries to update the system-wide + * configuration file (/etc/pulse/daemon.conf). If it does not have permission to + * write to the system-wide file or the file does not exist, it then tries to + * update the user's local configuration file (~/.config/pulse/daemon.conf). + * + * The function searches for the 'default-sample-rate' line in the configuration file. + * If found, it updates this line with the new sample rate. If the line is not found, + * it appends the setting to the end of the configuration file. + * + * @param sample_rate The new sample rate to set (in Hz). + * @return Returns 0 on success, -1 on failure (e.g., if both configuration files + * cannot be opened for writing). + */ +int manager_set_pulseaudio_global_rate(int sample_rate) { + + //Delay for waiting to restarting pulseaudio (in seconds). + const int restart_delay = 2; + + const char* system_conf = DAEMON_CONF; + struct passwd *pw = getpwuid(getuid()); + const char* homedir = pw ? pw->pw_dir : NULL; + char local_conf[MAX_LINE_LENGTH]; + if (homedir) { + snprintf(local_conf, sizeof(local_conf), "%s/.config/pulse/daemon.conf", homedir); + } else { + strcpy(local_conf, DAEMON_CONF); // Use system config as fallback + } + + const char* paths[] = { system_conf, local_conf }; + int operation_successful = 0; + + for (int i = 0; i < 2; ++i) { + FILE* file = fopen(paths[i], "r+"); + if (!file && i == 1) { // If local file doesn't exist, create it + file = fopen(local_conf, "w+"); + } + if (!file) { + continue; + } + + char new_config[MAX_LINE_LENGTH * 10] = ""; + char line[MAX_LINE_LENGTH]; + int found = 0; + + while (fgets(line, sizeof(line), file)) { + char *trimmed_line = line; + // Skip leading whitespace + while (*trimmed_line && isspace((unsigned char)*trimmed_line)) { + trimmed_line++; + } + + if (strncmp(trimmed_line, "default-sample-rate", 19) == 0) { + sprintf(line, "default-sample-rate = %d\n", sample_rate); + found = 1; + } + strcat(new_config, line); + } + + if (!found) { + sprintf(new_config + strlen(new_config), "default-sample-rate = %d\n", sample_rate); + } + + rewind(file); // Rewind to the beginning of the file for writing + if (fputs(new_config, file) != EOF) { + operation_successful = 1; + } + fclose(file); + + if (operation_successful) { + break; // Exit loop if operation was successful + } + } + + if (!operation_successful) { + fprintf(stderr, "Failed to update PulseAudio configuration file\n"); + return -1; + } + + // Check if running as root + if (getuid() == 0) { + // Inform the user to manually restart PulseAudio + printf("[WARNING] Pulseaudio cannot be restarted automatically as root.\n"); + printf("Please restart PulseAudio manually to apply changes.\n"); + return 0; + } + + // Check if PulseAudio is running; if so, kill it. + if (system("pulseaudio --check") == 0) { + if(system("pulseaudio --kill") != 0) { + perror("Failed to kill PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + sleep(restart_delay); + } + + // Restart PulseAudio to apply changes + if (system("pulseaudio --start") != 0) { + perror("Failed to restart PulseAudio"); + return -1; // Indicate an error in restarting PulseAudio + } + + return 0; // Configuration updated and PulseAudio restarted successfully +} + + +/** + * @brief Callback function for setting the mute state of a channel in a PulseAudio sink. + * + * This callback function is triggered by `pa_context_get_sink_info_by_index` to process + * information about a specific PulseAudio sink. It modifies the volume of a given channel + * in the sink to either muted or unmuted state, as specified in the user data. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type `struct volume_update_data`. + * + */ +static void manager_set_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + struct _shared_data_2 *data = (struct _shared_data_2 *)userdata; + + if (info) { + // Modify the volume of the specified channel + data->new_volume = info->volume; + data->new_volume.values[data->channel_index] = data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio sink. + * + * This callback function is used to verify the success of a volume set operation + * on a PulseAudio sink. It is called after attempting to set the volume of a specific + * channel within a sink, indicating whether the operation was successful. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data. This parameter is not used in this callback. + * + */ +static void manager_set_output_channel_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set output device volume.\n"); + } + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + +/** + * @brief Sets the mute state for a single channel of an output device. + * + * This function controls the mute state of a specific channel for a given PulseAudio sink (output device). + * It uses the EasyPulse API to interact with the PulseAudio server. + * + * @param sink_index Index of the sink (output device) whose channel mute state is to be set. + * @param channel_index Index of the channel within the sink to be muted or unmuted. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, non-zero on failure. + * + */ +int manager_set_output_mute_state(pulseaudio_manager *self, uint32_t sink_index, +uint32_t channel_index, bool mute_state) { + + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(self->context, sink_index, + manager_set_output_channel_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start output device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume + op = pa_context_set_sink_volume_by_index(self->context, sink_index, + &(volume_data.new_volume), manager_set_output_channel_mute_state_cb2, &volume_data); + + iterate(self, op); + + return 0; // Success +} + +/** + * @brief Callback function for handling input device information. + * + * This function is called in response to a request for information about a specific PulseAudio input device. + * It is used to modify the volume of a specified channel in the input device based on the mute state. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the source information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); + return; + } + + //fprintf(stderr, "[DEBUG, manager_set_input_mute_state_cb()] info->description is, %s\n", info->description); + + if (info) { + // Modify the volume of the specified channel + volume_data->new_volume = info->volume; + pa_volume_t new_channel_volume = volume_data->mute_state ? PA_VOLUME_MUTED : pa_cvolume_max(&info->volume); + volume_data->new_volume.values[volume_data->channel_index] = new_channel_volume; + } +} + +/** + * @brief Callback function for confirming the volume set operation on a PulseAudio input device. + * + * This callback function is used to verify the success of setting the volume of a specified channel in + * a PulseAudio input device. It is called after an attempt to set the volume of a channel within an input device. + * + * @param c Pointer to the PulseAudio context. + * @param success Integer indicating the success of the volume set operation. Non-zero if successful. + * @param userdata Pointer to user data, expected to be of type pulseaudio_manager. + * + */ +static void manager_set_input_mute_state_cb2(pa_context *c, int success, void *userdata) { + (void) c; + _shared_data_2 *volume_data = (_shared_data_2 *) userdata; + + if (!success) { + fprintf(stderr, "Failed to set input device volume.\n"); + } + + pa_threaded_mainloop_signal(volume_data->manager->mainloop, 0); +} + + +/** + * @brief Sets the mute state for a single channel of a PulseAudio input device. + * + * This function controls the mute state of a specified PulseAudio input device (source). + * It mutes or unmutes all channels of the input device based on the provided mute state. + * + * @param self Pointer to the pulseaudio_manager structure, containing the necessary PulseAudio context. + * @param input_index Index of the input device (source) whose mute state is to be set. + * @param mute_state Boolean value indicating the desired mute state. 'true' to mute, 'false' to unmute. + * + * @return Returns 0 on success, -1 on failure. + * + */ +int manager_set_input_mute_state(pulseaudio_manager *self, uint32_t input_index, +uint32_t channel_index, bool mute_state) { + if (!self->context) { + fprintf(stderr, "Failed to get PulseAudio context.\n"); + return -1; + } + + _shared_data_2 volume_data; + + volume_data.channel_index = channel_index; + volume_data.mute_state = mute_state; + volume_data.manager = self; + + // Requesting information about the specified source + pa_operation *op = pa_context_get_source_info_by_index(self->context, input_index, manager_set_input_mute_state_cb, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start input device information operation.\n"); + return -1; + } + + // Wait for the operation to complete + iterate(self, op); + + // Set the updated volume for the specified channel (effectively muting or unmuting the channel) + op = pa_context_set_source_volume_by_index(self->context, input_index, + &(volume_data.new_volume), manager_set_input_mute_state_cb2, &volume_data); + + if (!op) { + fprintf(stderr, "Failed to start source volume set operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; +} + +/** + * @brief Moves playback from one sink to another. + * + * This function moves all playback streams from one output device (sink) to another. + * It is used to switch the audio output from one device to another, for example, from + * speakers to headphones. + * + * @param manager Pointer to the pulseaudio_manager instance. + * @param sink1_index Index of the current sink (output device). + * @param sink2_index Index of the new sink (output device) to move streams to. + * @return Returns 0 on success, -1 on failure. + */ +int manager_move_output_playback(pulseaudio_manager *self, uint32_t sink1_index, uint32_t sink2_index) { + if (!self || sink1_index >= self->output_count || sink2_index >= self->output_count) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + _shared_data_1 shared_data = { self, sink2_index }; + + // Iterate over all sink inputs and move them to the new sink + pa_operation *op = pa_context_get_sink_input_info_list(self->context, manager_switch_default_output_cb_2, &shared_data); + + if (!op) { + fprintf(stderr, "Failed to start sink input info list operation.\n"); + return -1; + } + + iterate(self, op); + + return 0; // Success +} + +/** + * Callback function after attempting to move the sink input. + * Signals the main loop upon completion. + * + * @param c PulseAudio context. + * @param success Indicates if the operation was successful. + * @param userdata User data passed to the callback, expected to be pulseaudio_manager. + */ +static void manager_move_sink_input_cb(pa_context *c, int success, void *userdata) { + (void) c; // Unused parameter + + pulseaudio_manager *manager = (pulseaudio_manager *) userdata; + fprintf(stderr, "[DEBUG, manager_move_sink_input_cb] success is: %i\n", success); + + if (success) { + printf("Sink input moved successfully.\n"); + } else { + fprintf(stderr, "Failed to move sink input.\n"); + } + + // Signal the main loop that the operation is complete + pa_threaded_mainloop_signal(manager->mainloop, 0); +} + +/** + * Initiates the operation to move a sink input to a new sink. + * + * @param manager Pointer to the pulseaudio_manager structure. + * @param sink_input_idx The index of the sink input to be moved. + * @param target_sink_idx The index of the target sink. + * @return Returns true if the operation was initiated successfully, false otherwise. + */ +bool manager_move_sink_input(pulseaudio_manager *manager, uint32_t sink_input_idx, uint32_t target_sink_idx) { + if (!manager || !manager->context) { + fprintf(stderr, "Invalid manager or context.\n"); + return false; + } + + // Initiate the move operation + pa_operation *op = pa_context_move_sink_input_by_index(manager->context, sink_input_idx, target_sink_idx, manager_move_sink_input_cb, manager); + if (!op) { + fprintf(stderr, "pa_context_move_sink_input_by_index() failed.\n"); + return false; + } + + // Iterate over the main loop until the operation is complete + iterate(manager, op); + return true; +} diff --git a/v-0.16/easypulse_core.h b/v-0.16/easypulse_core.h new file mode 100644 index 0000000..a2d1526 --- /dev/null +++ b/v-0.16/easypulse_core.h @@ -0,0 +1,112 @@ +/** + * @file core.h + * @brief EasyPulse Library Header. + * + * EasyPulse is a library designed to provide pseudo-object oriented programming + * functions to simplify access to PulseAudio. + */ + +#ifndef EASPYPULSE_CORE_H +#define EASPYPULSE_CORE_H +#include +#define DEBUG_MODE 0 // Debug mode flag + +#include +#include "system_query.h" +#include +#include +#include + +// Forward declarations +typedef struct pulseaudio_manager pulseaudio_manager; +typedef struct pulseaudio_device pulseaudio_device; +typedef struct pulseaudio_volume pulseaudio_volume; + + +typedef struct { + char *name; // Name of the profile + char *description; // Description of the profile + uint32_t channels; // Number of channels this profile has +} pulseaudio_profile; + +//Internal volume information. +typedef struct _internal_volume { + uint32_t index; + char *code; //Pulseaudio name of the volume. + pa_cvolume *volume; //Volume representation. + pa_channel_map *cmap; //Channel map representation. + +} internal_volume; + +/** + * @brief Represents a PulseAudio device. + */ +struct pulseaudio_device { + uint32_t index; // Index of the device. + char *code; // Pulseaudio name of the device. + char *name; // Pulseaudio description of the device. + char *alsa_id; // Alsa ID of the device. + int sample_rate; // Current sample rate of the device. + pa_card_profile_info *active_profile; // Active alsa profile of this device. + char **channel_names; // Public channel names. + int master_volume; // Average volume of all channels (in percentage). + int *channel_volume; // Volume of each individual channel (in percentage). + bool mute; // Mute status of the devices (true for muted, false for unmuted). + int min_channels; // The minimum number of channels of the device. + int max_channels; // The maximum number of channels of the device. + pa_card_profile_info *profiles; // Array of available profiles for the device + uint32_t profile_count; // Number of available profiles +}; + +/** + * @brief Represents the main manager for PulseAudio operations. + */ +struct pulseaudio_manager { + pa_threaded_mainloop *mainloop; // Mainloop for PulseAudio operations. + pa_context *context; // PulseAudio context. + pulseaudio_device *outputs; // Array of available output devices. + pulseaudio_device *inputs; // Array of available input devices. + int pa_ready; // Indicates if PulseAudio is ready (1 for ready, 2 for error). + char *active_output_device; // Pointer to active output device. + char *active_input_device; // Pointer to active input device. + uint32_t output_count; // Number of pulseaudio sinks (outputs). + uint32_t input_count; // Number of pulseaudio sources (inputs). +}; + +pulseaudio_manager *manager_create(void); +void manager_cleanup(pulseaudio_manager *manager); //Cleans up the manager. + +int manager_set_master_volume(pulseaudio_manager *manager, +uint32_t device_id, int volume); //Sets the master volume of a given volume. + +int manager_toggle_output_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of output device to muted / unmuted. + +int manager_toggle_input_mute(pulseaudio_manager *manager, +uint32_t index, int state); //Toggles the volume of input device to muted / unmuted. + +bool manager_switch_default_output(pulseaudio_manager *self, +uint32_t device_index); //Changes the default output device. + +bool manager_switch_default_input(pulseaudio_manager *self, +uint32_t device_index); //Changes the default input device. + +int manager_set_output_sample_rate(pulseaudio_manager *manager, +uint32_t device_index, int sample_rate); //Changes the output of an output device. + +int manager_set_pulseaudio_global_rate(int sample_rate); //Changes the output of an output device. + +int manager_set_output_mute_state(pulseaudio_manager *self, +uint32_t output_index, uint32_t channel_index, bool mute_state); //Changes a number of output channels to mute / unmuted. + +int manager_set_input_mute_state(pulseaudio_manager *self, +uint32_t input_index, uint32_t channel_index, bool mute_state); //Changes a number of input channels to mute / unmuted. + +int manager_move_output_playback(pulseaudio_manager *manager, +uint32_t sink1_index, uint32_t sink2_index); //Moves playback from one sink to another. + +bool manager_move_sink_input(pulseaudio_manager *manager, +uint32_t source_sink, uint32_t target_sink); //Moves sink input from a device to another + + +#endif // CORE_H diff --git a/v-0.16/examples/Makefile b/v-0.16/examples/Makefile new file mode 100644 index 0000000..6f11288 --- /dev/null +++ b/v-0.16/examples/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Wextra -g -O0 + +LIB_DIR = ../ +LIB_SRC = $(LIB_DIR)*.c + +EXAMPLES_DIR = . +EXAMPLES_SRC = $(wildcard $(EXAMPLES_DIR)/*.c) +EXAMPLES_OUT = $(patsubst $(EXAMPLES_DIR)/%.c,$(EXAMPLES_DIR)/%,$(EXAMPLES_SRC)) + +all: $(EXAMPLES_OUT) + +$(EXAMPLES_DIR)/%: $(EXAMPLES_DIR)/%.c + $(CC) $(CFLAGS) $< $(LIB_SRC) -o $@ -lpulse -lasound + +clean: + rm -f $(EXAMPLES_DIR)/*~ $(EXAMPLES_OUT) + +.PHONY: all clean diff --git a/v-0.16/examples/alsa-mapper_pulseaudio-api b/v-0.16/examples/alsa-mapper_pulseaudio-api new file mode 100755 index 0000000..e47d08a Binary files /dev/null and b/v-0.16/examples/alsa-mapper_pulseaudio-api differ diff --git a/v-0.16/examples/alsa-mapper_pulseaudio-api.c b/v-0.16/examples/alsa-mapper_pulseaudio-api.c new file mode 100644 index 0000000..a9b0b52 --- /dev/null +++ b/v-0.16/examples/alsa-mapper_pulseaudio-api.c @@ -0,0 +1,113 @@ +/** + * @file alsa-mapper_pulseaudio-api.c + * @brief Fetches and displays UDEV descriptions and ALSA names for PulseAudio sinks. + * + * This program interfaces with both the ALSA and PulseAudio APIs to retrieve information about available audio sinks. + * It lists each sink's UDEV description, as well as its corresponding ALSA hardware (hw) name and a more user-friendly + * ALSA name. This is useful for applications that need to display detailed information about the audio devices in the system, + * especially when working with systems where multiple audio devices are present. + * + * The program utilizes the PulseAudio asynchronous API to fetch sink information and then queries ALSA to get a friendly + * name for each sink. The ALSA friendly name is typically more readable and user-friendly compared to the default hardware + * name provided by ALSA. + * + * Functions: + * - get_alsa_friendly_name: Retrieves a user-friendly name for an ALSA card. + * - sink_info_cb: Callback function for processing and displaying each sink's information. + * - context_state_cb: Callback function to handle the state changes of the PulseAudio context. + * - main: Sets up the PulseAudio main loop and context, and runs the main loop. + * + * Usage: + * - The program does not require any command-line arguments. + * - On execution, it lists all available PulseAudio sinks with their UDEV descriptions and ALSA names. + * + * @author Mbyte2 + * @date November 13, 2023 + */ + +#include +#include +#include +#include + +static const char* get_alsa_friendly_name(int card_num) { + snd_ctl_t *ctl; + char name[128]; + snprintf(name, sizeof(name), "hw:%d", card_num); + + if (snd_ctl_open(&ctl, name, 0) < 0) { + return NULL; + } + + snd_ctl_card_info_t *info; + snd_ctl_card_info_alloca(&info); + if (snd_ctl_card_info(ctl, info) < 0) { + snd_ctl_close(ctl); + return NULL; + } + + const char *friendly_name = strdup(snd_ctl_card_info_get_name(info)); + snd_ctl_close(ctl); + return friendly_name; +} + +static void sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) userdata; + + if (eol > 0) { + pa_context_disconnect(c); + return; + } + + const char *udev_description = pa_proplist_gets(info->proplist, "device.description"); + const char *card_str = pa_proplist_gets(info->proplist, "alsa.card"); + const char *device_str = pa_proplist_gets(info->proplist, "alsa.device"); + + int card_num = card_str ? atoi(card_str) : -1; + const char *friendly_name = card_num != -1 ? get_alsa_friendly_name(card_num) : NULL; + + if (udev_description && card_str && device_str) { + printf("Sink: %s, UDEV description: %s, ALSA name: hw:%s,%s", info->name, udev_description, card_str, device_str); + if (friendly_name) { + printf(", Friendly ALSA name: %s", friendly_name); + free((void*)friendly_name); + } + printf("\n"); + } else if (udev_description) { + printf("Sink: %s, UDEV description: %s, Incomplete ALSA name information.\n", info->name, udev_description); + } +} + +static void context_state_cb(pa_context *c, void *userdata) { + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + pa_context_get_sink_info_list(c, sink_info_cb, NULL); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + default: + break; + } +} + +int main(void) { + pa_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; + + mainloop = pa_mainloop_new(); + mainloop_api = pa_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "udev_description_fetcher"); + + pa_context_set_state_callback(context, context_state_cb, mainloop); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + pa_mainloop_run(mainloop, NULL); + + pa_context_unref(context); + pa_mainloop_free(mainloop); + + return 0; +} diff --git a/v-0.16/examples/change-speaker-mode b/v-0.16/examples/change-speaker-mode new file mode 100755 index 0000000..57c5c7b Binary files /dev/null and b/v-0.16/examples/change-speaker-mode differ diff --git a/v-0.16/examples/change-speaker-mode.c b/v-0.16/examples/change-speaker-mode.c new file mode 100644 index 0000000..9bfc03d --- /dev/null +++ b/v-0.16/examples/change-speaker-mode.c @@ -0,0 +1,94 @@ +/** + * @file sample_program.c + * @brief PulseAudio Mode Selector + * + * This program is designed to manage the audio mode of the active PulseAudio sink. + * + * The program performs the following tasks: + * - Initializes the PulseAudio manager. + * - Detects and displays the current mode of the active device. + * - Lists audio modes supported by the active device based on its number of channels. + * - Prompts the user to select a desired mode from the list. + * - Sets the active device to the mode selected by the user. + * + * The available modes include: mono, stereo, 4.0, 5.0, 5.1, and 7.1. The program lists + * modes dynamically, ensuring that only those supported by the active device are presented. + * + * @date 10-15-2023 (creation date) + */ + +#if 0 +#include "../easypulse_core.h" +#include + +int main(void) { + // Initialize the pulseaudio_manager + pulseaudio_manager *manager = new_manager(); + //pulseaudio_manager *self = manager; + + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudio manager.\n"); + return 1; + } + + + // Detect the current mode of the active device + const char* current_mode = manager->get_device_mode_by_code(manager, manager->active_device->code); + printf("Current mode of the active device (%s): %s\n", manager->active_device->code, current_mode); + + // List available modes based on the number of channels of the active device + printf("\nAvailable modes:\n"); + printf("1. mono\n"); + printf("2. stereo\n"); + + int max_choice = 2; // To keep track of the maximum mode choice number + uint32_t channels = manager->active_device->number_of_channels; + + if (channels >= 4) { + printf("3. 4.0\n"); + max_choice = 3; + } + if (channels >= 5) { + printf("4. 5.0\n"); + max_choice = 4; + } + if (channels >= 6) { + printf("5. 5.1\n"); + max_choice = 5; + } + if (channels >= 8) { + printf("6. 7.1\n"); + max_choice = 6; + } + + // Prompt the user to select a mode + printf("\nEnter the number corresponding to the desired mode: "); + int choice; + scanf("%d", &choice); + + if (choice < 1 || choice > max_choice) { + printf("Invalid choice.\n"); + manager->destroy(self); + return 1; + } + + const char* mode_choices[] = {"mono", "stereo", "4.0", "5.0", "5.1", "7.1"}; + const char* mode_to_set = mode_choices[choice - 1]; + + // Set the mode of the active device + if (manager->set_device_mode_by_code(manager, manager->active_device->code, mode_to_set)) { + printf("Mode set to %s successfully!\n", mode_to_set); + } else { + fprintf(stderr, "Failed to set the mode.\n"); + } + + // Cleanup + manager->destroy(self); + return 0; + +} +#endif + +int main(void) { + return 0; +} diff --git a/v-0.16/examples/change_global_sample_rate b/v-0.16/examples/change_global_sample_rate new file mode 100755 index 0000000..f1c3177 Binary files /dev/null and b/v-0.16/examples/change_global_sample_rate differ diff --git a/v-0.16/examples/change_global_sample_rate.c b/v-0.16/examples/change_global_sample_rate.c new file mode 100644 index 0000000..b520fc4 --- /dev/null +++ b/v-0.16/examples/change_global_sample_rate.c @@ -0,0 +1,47 @@ +/** + * @file change_global_sample_rate.c + * @brief Adjusts the global PulseAudio sample rate. + * + * This program retrieves and displays the current global sample rate for PulseAudio. + * It then prompts the user to enter a new sample rate. Upon receiving a valid input, + * it attempts to set this new sample rate as the global sample rate in PulseAudio's + * configuration files. The program first tries to update the system-wide configuration + * and then falls back to the user's local configuration if necessary. + * + * @return Returns 0 on successful execution, 1 on failure or invalid input. + */ + +#include +#include +#include "../easypulse_core.h" + + +int main() { + // Fetch the global playback sample rate from the default PulseAudio configuration file + int sample_rate = get_pulseaudio_global_playback_rate(NULL); + printf("[DEBUG, main] sample rate is: %i\n", sample_rate); + + if (sample_rate > 0) { + printf("Current global playback sample rate: %d Hz\n", sample_rate); + } else { + printf("Failed to retrieve the current global playback sample rate.\n"); + return 1; + } + + // Prompt the user for a new sample rate + printf("Enter the new sample rate to set: "); + int new_sample_rate; + if (scanf("%d", &new_sample_rate) != 1) { + printf("Invalid input.\n"); + return 1; + } + + // Set the new global sample rate + if (manager_set_pulseaudio_global_rate(new_sample_rate) == 0) { + printf("Sample rate successfully set to %d Hz.\n", new_sample_rate); + } else { + printf("Failed to set the new sample rate.\n"); + } + + return 0; +} diff --git a/v-0.16/examples/get-card-by-name-pulseaudio b/v-0.16/examples/get-card-by-name-pulseaudio new file mode 100755 index 0000000..e3e171e Binary files /dev/null and b/v-0.16/examples/get-card-by-name-pulseaudio differ diff --git a/v-0.16/examples/get-card-by-name-pulseaudio.c b/v-0.16/examples/get-card-by-name-pulseaudio.c new file mode 100644 index 0000000..60d2227 --- /dev/null +++ b/v-0.16/examples/get-card-by-name-pulseaudio.c @@ -0,0 +1,136 @@ +/** + * @file get-card-by-name-pulseaudio.c + * @brief PulseAudio Card Information Demo + * + * This program demonstrates how to interact with the PulseAudio (PA) sound server + * to retrieve information about a specific sound card by its name. It utilizes the + * PulseAudio API to establish a connection with the PA server, queries for a card + * by name, and then prints out the name and active profile of the card. + * + * The program employs the asynchronous PA API with a threaded main loop to handle + * the communication with the PA server. + * + * The card name should be the technical name as recognized by PulseAudio, which + * can be obtained using `pactl list cards` or `pacmd list-cards`. + * + * + * @author Mbyte2 + * @date November 18, 2023 + */ +#include +#include +#include +#include + +typedef struct { + pa_threaded_mainloop *mainloop; + pa_context *context; + char **card_names; + size_t num_cards; +} pa_userdata; + +static void context_state_cb(pa_context *context, void *userdata) { + pa_userdata *ud = (pa_userdata *) userdata; + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_threaded_mainloop_signal(ud->mainloop, 0); + break; + default: + break; + } +} + +static void card_list_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + ud->card_names = realloc(ud->card_names, sizeof(char *) * (ud->num_cards + 1)); + ud->card_names[ud->num_cards] = strdup(i->name); + ud->num_cards++; + } +} + +static void card_info_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + pa_userdata *ud = (pa_userdata *) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(ud->mainloop, 0); + return; + } + + if (i) { + printf("Card Name: %s\n", i->name); + if (i->active_profile) { + printf("Active Profile: %s\n", i->active_profile->name); + } + printf("\n"); + } +} + +int main() { + pa_userdata userdata = {0}; + + userdata.mainloop = pa_threaded_mainloop_new(); + userdata.context = pa_context_new(pa_threaded_mainloop_get_api(userdata.mainloop), "PA Demo"); + + pa_context_set_state_callback(userdata.context, context_state_cb, &userdata); + + if (pa_context_connect(userdata.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + fprintf(stderr, "PulseAudio connection failed\n"); + pa_threaded_mainloop_free(userdata.mainloop); + return 1; + } + + pa_threaded_mainloop_start(userdata.mainloop); + pa_threaded_mainloop_lock(userdata.mainloop); + + while (pa_context_get_state(userdata.context) != PA_CONTEXT_READY) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + + // List all cards + pa_operation *op = pa_context_get_card_info_list(userdata.context, card_list_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + + // Get detailed info for each card + for (size_t i = 0; i < userdata.num_cards; i++) { + op = pa_context_get_card_info_by_name(userdata.context, userdata.card_names[i], card_info_cb, &userdata); + if (op) { + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(userdata.mainloop); + } + pa_operation_unref(op); + } + } + + pa_threaded_mainloop_unlock(userdata.mainloop); + pa_threaded_mainloop_stop(userdata.mainloop); + + pa_context_disconnect(userdata.context); + pa_context_unref(userdata.context); + pa_threaded_mainloop_free(userdata.mainloop); + + for (size_t i = 0; i < userdata.num_cards; i++) { + free(userdata.card_names[i]); + } + free(userdata.card_names); + + return 0; +} + diff --git a/v-0.16/examples/get-card-profiles-pulseaudio_api b/v-0.16/examples/get-card-profiles-pulseaudio_api new file mode 100755 index 0000000..35f79a9 Binary files /dev/null and b/v-0.16/examples/get-card-profiles-pulseaudio_api differ diff --git a/v-0.16/examples/get-card-profiles-pulseaudio_api.c b/v-0.16/examples/get-card-profiles-pulseaudio_api.c new file mode 100644 index 0000000..829f45c --- /dev/null +++ b/v-0.16/examples/get-card-profiles-pulseaudio_api.c @@ -0,0 +1,63 @@ +/** + * @file get-card-profiles-pulseaudio_api.c + * @brief This program queries and displays information about available sound devices in a computer. + * + * It uses the PulseAudio library to interact with the sound system and retrieve information about + * available sound devices, their properties, and ALSA (Advanced Linux Sound Architecture) related data. + */ + +#include +#include "../easypulse_core.h" + +int main(void) { + // Query the total number of PulseAudio devices + uint32_t total_devices = get_output_device_count(); + + printf("Total sound devices in this computer:%lu\n", (unsigned long) total_devices); + printf("Available PulseAudio sound devices:\n"); + + pa_sink_info **sink_info = get_available_output_devices(); + if (!sink_info) { + fprintf(stderr, "Error: Could not retrieve sound device information.\n"); + return 1; + } + + for (uint32_t i = 0; i < total_devices; i++) { + if (!sink_info[i]) { + fprintf(stderr, "Error: sound device information at index %u is NULL.\n", i); + continue; + } + + // Display sink name + printf("\n- Sound device name: %s\n", sink_info[i]->name ? sink_info[i]->name : "NULL"); + printf("\n- Sound device description: %s\n", sink_info[i]->description ? sink_info[i]->description : "NULL"); + + // Query and display the number of profiles + uint32_t profile_count = get_profile_count(i); + printf(" - Number of profiles: %u\n", profile_count); + + // Get and display the ALSA name + const char *alsa_name = get_alsa_output_name(sink_info[i]->name); + const char *alsa_id = get_alsa_output_id(sink_info[i]->name); + int sample_rate = get_output_sample_rate(alsa_id, sink_info[i]); + + printf(" - Sample rate: %i\n", sample_rate); + printf("\n- Alsa ID is: %s\n", alsa_id ? alsa_id : "NULL"); + + if (alsa_name) { + printf(" - ALSA name: %s\n", alsa_name); + } else { + printf(" - No corresponding ALSA name found.\n"); + } + // Get and display the minimum and maximum channels + int min_channels = get_min_output_channels(alsa_id, sink_info[i]); + int max_channels = get_max_output_channels(alsa_id, sink_info[i]); + + if(min_channels > 0) printf(" - Minimum channels: %d\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %d\n", max_channels); + free((char *)alsa_name); // Free the memory allocated for alsa_name + } + + delete_output_devices(sink_info); + return 0; +} diff --git a/v-0.16/examples/move_sink_input_demo b/v-0.16/examples/move_sink_input_demo new file mode 100755 index 0000000..1e96d34 Binary files /dev/null and b/v-0.16/examples/move_sink_input_demo differ diff --git a/v-0.16/examples/move_sink_input_demo.c b/v-0.16/examples/move_sink_input_demo.c new file mode 100644 index 0000000..e910b67 --- /dev/null +++ b/v-0.16/examples/move_sink_input_demo.c @@ -0,0 +1,121 @@ +/** + * @file move_sink_input_demo.c + * @brief Demo Program for PulseAudio Sink Input Manipulation + * + * This file contains a demonstration program for the PulseAudio API developed + * in 'easypulse_core.h' and 'system_query.h'. The main purpose of this program + * is to illustrate how to move a sink input from one sink to another in a + * PulseAudio environment. + * + * The program performs the following key operations: + * 1. Initializes a connection with the PulseAudio server. + * 2. Lists all available sinks and sink inputs in the current PulseAudio context. + * 3. Prompts the user to select a sink input and a target sink. + * 4. Moves the selected sink input to the chosen sink. + * 5. Cleans up and terminates the connection with the PulseAudio server. + * + * + * @author Mbyte2 + * @date November 24, 2023 + * + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include + +//Checks if sink entry (output) exists. +bool is_sink_valid(pulseaudio_manager *manager, uint32_t sink_id) { + bool valid_sink = false; + + for(uint32_t i = 0; i < manager->output_count; ++i) { + //fprintf(stderr,"[DEBUG, is_sink_valid()] i is %i\n", i); + if(sink_id == manager->outputs[i].index) + valid_sink = true; + } + return valid_sink; +} + +//Checks if sink input entry (output stream) exists. +bool is_sink_input_valid(output_stream_list *sink_inputs, uint32_t sink_id) { + bool valid_sink = false; + + for(uint32_t j = 0; j < sink_inputs->num_inputs; ++j) { + if(sink_id == sink_inputs->inputs[j].index) + valid_sink = true; + } + return valid_sink; +} + +int main(void) { + + bool is_valid_1 = false; + bool is_valid_2 = false; + + // Initialize the EasyPulse manager + pulseaudio_manager *manager = manager_create(); + if (!manager || manager->pa_ready != 1) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // List all available sink inputs + output_stream_list *sink_inputs = get_output_streams(manager->context); + + if (!sink_inputs) { + fprintf(stderr, "Failed to list sink inputs\n"); + output_streams_cleanup(sink_inputs); + manager_cleanup(manager); + return 1; + } + + // Display available sink inputs + printf("Available Sink Inputs:\n"); + for (uint32_t i = 0; i < sink_inputs->num_inputs; ++i) { + printf("ID: %u, Name: %s, Driver: %s\n", + sink_inputs->inputs[i].index, + sink_inputs->inputs[i].name, + sink_inputs->inputs[i].driver); + } + + printf("Available Sinks:\n"); + + for (uint32_t i = 0; i < manager->output_count; ++i) { + printf("ID: %u, Name: %s\n", + manager->outputs[i].index, + manager->outputs[i].name); + } + + // User selects a sink and a target sink input + uint32_t sink_id, target_sink_id; + + printf("Enter the ID of the sink input to move: "); + scanf("%d", &sink_id); + printf("Enter the ID of the target sink: "); + scanf("%d", &target_sink_id); + + is_valid_1 = is_sink_input_valid(sink_inputs, sink_id); + is_valid_2 = is_sink_valid(manager, target_sink_id); + + if(!is_valid_1 || !is_valid_2) { + fprintf(stderr, "Invalid sinks specified.\n"); + output_streams_cleanup(sink_inputs); + manager_cleanup(manager); + return 1; + } + + // Move the selected sink input to the target sink + if (!manager_move_sink_input(manager, sink_id, target_sink_id)) { + fprintf(stderr, "Failed to move sink input\n"); + } else { + printf("Successfully moved sink input %d to sink %d\n", sink_id, target_sink_id); + } + + // Cleanup + output_streams_cleanup(sink_inputs); + manager_cleanup(manager); + + return 0; +} + diff --git a/v-0.16/examples/mute-channel-input-demo b/v-0.16/examples/mute-channel-input-demo new file mode 100755 index 0000000..e823fc5 Binary files /dev/null and b/v-0.16/examples/mute-channel-input-demo differ diff --git a/v-0.16/examples/mute-channel-input-demo.c b/v-0.16/examples/mute-channel-input-demo.c new file mode 100644 index 0000000..2337f5c --- /dev/null +++ b/v-0.16/examples/mute-channel-input-demo.c @@ -0,0 +1,126 @@ +/** + * @file mute-channel-input-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio input device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * input devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available input devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + uint32_t device_index; + bool keep_running = true; + char choice[100]; + int c; //To clear the buffer + + while(keep_running) { + // List input devices + for (uint32_t i = 0; i < self->input_count; i++) { + printf("%d: %s\n", i, self->inputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid input. Please try again.\n"); + } + + pulseaudio_device *selected_device = &self->inputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_input_mute_state(self, selected_device->index, channel, new_mute_state); + new_mute_state = get_input_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + printf("The new mute state is, %s\n\n", new_mute_state ? "Muted" : "Unmuted"); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.16/examples/mute-channel-output-demo b/v-0.16/examples/mute-channel-output-demo new file mode 100755 index 0000000..015aa7a Binary files /dev/null and b/v-0.16/examples/mute-channel-output-demo differ diff --git a/v-0.16/examples/mute-channel-output-demo.c b/v-0.16/examples/mute-channel-output-demo.c new file mode 100644 index 0000000..e96c51a --- /dev/null +++ b/v-0.16/examples/mute-channel-output-demo.c @@ -0,0 +1,131 @@ +/** + * @file mute-channel-output-demo.c + * @brief Program to toggle mute state of specified channels on a selected PulseAudio output device. + * + * This program uses the EasyPulse library to interface with PulseAudio. It lists all available + * output devices and allows the user to select one. After a device is selected, the program displays + * the current mute state of each channel of that device. The user can then specify which channels' + * mute state they want to toggle. The program will only change the mute state of the channels specified + * by the user. + * + * Usage: + * 1. A list of available output devices is displayed. + * 2. User selects a device by entering its corresponding number. + * 3. The program displays the mute state of each channel of the selected device. + * 4. User enters the channel numbers they wish to toggle, separated by spaces. + * 5. The program toggles the mute state of the specified channels. + * + * @author Mbyte2 + * @date November 12, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include + +int main(void) { + // Initialize PulseAudio manager + pulseaudio_manager *self = manager_create(); + bool keep_running = true; + int c; //To clear the buffer + + if (self == NULL) { + fprintf(stderr, "Failed to initialize PulseAudio manager\n"); + return 1; + } + + // User selects a device + char choice[100]; + uint32_t device_index; + + while(keep_running) { + // List output devices + for (uint32_t i = 0; i < self->output_count; i++) { + printf("%d: %s\n", i, self->outputs[i].name); + } + + printf("Enter the number of the device you want to select ('q' to quit): "); + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // Remove newline character if present + choice[strcspn(choice, "\n")] = 0; + + if(strcmp(choice, "q") == 0) { + keep_running = false; + break; + } + char *end; + long val = strtol(choice, &end, 10); + + // Check for valid number and within range + if (end != choice && *end == '\0' && val >= 0 && val < self->output_count) { + device_index = (uint32_t)val; + } + else { + printf("Invalid user input. Please try again.\n"); + } + + if (device_index >= self->output_count) { + fprintf(stderr, "Invalid device index.\n"); + manager_cleanup(self); + return 1; + } + + pulseaudio_device *selected_device = &self->outputs[device_index]; + + // Display channels and their mute state + printf("Channels and their current mute state:\n"); + + for (int i = 0; i < selected_device->max_channels; i++) { + bool mute_state = get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, i); + printf("Channel %d: %s\n", i, mute_state ? "Muted" : "Unmuted"); + } + + // Ask the user to specify channels to toggle + printf("Enter the channel numbers to toggle, separated by spaces (e.g., 0 2 3): "); + char input[1024]; + + if (fgets(choice, sizeof(choice), stdin) != NULL) { + // Remove newline character if present + size_t len = strcspn(choice, "\n"); + if (choice[len] == '\n') { + // Newline found, remove it + choice[len] = '\0'; + } else { + // Newline not found, buffer was too small, clear remaining characters + while ((c = getchar()) != '\n' && c != EOF); + } + } + + char *token = strtok(input, " "); + + while (token != NULL) { + int channel = atoi(token); + if (channel >= 0 && channel < selected_device->max_channels) { + // Toggle the mute state of the specified channel + bool new_mute_state = !get_output_channel_mute_state(self->context, self->mainloop, selected_device->index, channel); + manager_set_output_mute_state(self, selected_device->index, channel, new_mute_state); + } else { + printf("Invalid channel number: %d\n", channel); + } + token = strtok(NULL, " "); + } + } + + // Clean up and close PulseAudio connection + manager_cleanup(self); + + return 0; +} diff --git a/v-0.16/examples/mute_input_demo b/v-0.16/examples/mute_input_demo new file mode 100755 index 0000000..6e19b80 Binary files /dev/null and b/v-0.16/examples/mute_input_demo differ diff --git a/v-0.16/examples/mute_input_demo.c b/v-0.16/examples/mute_input_demo.c new file mode 100644 index 0000000..096ffe8 --- /dev/null +++ b/v-0.16/examples/mute_input_demo.c @@ -0,0 +1,89 @@ +/** + * @file mute_input_demo.c + * @brief Demonstration program using PulseAudio to list input devices, toggle mute state. + * + * This program lists all available input devices managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle + * its mute state. The program uses the `easypulse_core` and `system_query` + * libraries to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available input devices along + * with their mute status. The user can then input the index of the device they + * wish to toggle. The program will then change the mute state of the selected device. + * + * Example Output: + * ``` + * Available input devices: + * 0: Device 1 (muted: yes) + * 1: Device 2 (muted: no) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + * + * @note This program is a simple demonstration and does not handle all edge cases + * and errors that could arise in a full-featured application. + */ + +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available input devices + printf("\n***TOGGLING MUTE / UNMUTE FOR INPUT DEVICES DEMO***\n\nAvailable input devices:\n"); + for (uint32_t i = 0; i < manager->input_count; i++) { + const char *device_name = manager->inputs[i].name; + int is_muted = get_muted_input_status(manager->inputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) > manager->input_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_input_status(manager->inputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected source + int new_mute_state = !current_mute_state; + if (manager_toggle_input_mute(manager, (index-1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->inputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.16/examples/mute_output_demo b/v-0.16/examples/mute_output_demo new file mode 100755 index 0000000..1be4de6 Binary files /dev/null and b/v-0.16/examples/mute_output_demo differ diff --git a/v-0.16/examples/mute_output_demo.c b/v-0.16/examples/mute_output_demo.c new file mode 100644 index 0000000..e6b2066 --- /dev/null +++ b/v-0.16/examples/mute_output_demo.c @@ -0,0 +1,89 @@ +/** + * @file main.c + * @brief Demonstration program using PulseAudio to list output devices and toggle mute state. + * + * This program lists all available output devices (sinks) managed by PulseAudio, checks + * if they are currently muted, and allows the user to select a device to toggle its mute state. + * The program uses the `easypulse_core` library to interact with the PulseAudio server. + * + * Usage: + * Run the program, and it will display a list of available output devices along with their + * mute status. The user can then input the index of the device they wish to toggle. The program + * will then change the mute state of the selected device. + * + * @note This program is a simple demonstration and does not handle all edge cases and errors + * that could arise in a full-featured application. + * + * Example Output: + * ``` + * Available output devices: + * 0: Device 1 (Muted: No) + * 1: Device 2 (Muted: Yes) + * Enter the index of the device you want to toggle the mute state for: + * ``` + * + * @author Mbyte2 + * @date November 11, 2023 + */ +#include +#include +#include "../easypulse_core.h" +#include "../system_query.h" + +// Forward declaration of the toggle_output_mute function +int toggle_output_mute(pulseaudio_manager *manager, uint32_t index, int state); + +int main() { + // Initialize the PulseAudio manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create the PulseAudio manager.\n"); + return 1; + } + + // Display available output devices + printf("\n***TOGGLING MUTE / UNMUTE DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + const char *device_name = manager->outputs[i].name; + int is_muted = get_muted_output_status(manager->outputs[i].code); + printf("%d: %s (muted: %s)\n", (i+1), device_name, is_muted == true ? "yes" : "no"); + } + + // Ask the user for the device index to toggle mute state + printf("\nEnter the index of the device you want to toggle the mute state for: "); + uint32_t index; + if (scanf("%u", &index) != 1) { + fprintf(stderr, "Invalid input.\n"); + manager_cleanup(manager); + return 1; + } + + // Check if the index is within range + if ((index - 1) >= manager->output_count) { + fprintf(stderr, "Index out of range.\n"); + manager_cleanup(manager); + return 1; + } + + // Get the current mute state + int current_mute_state = get_muted_output_status(manager->outputs[index-1].code); + if (current_mute_state == -1) { + fprintf(stderr, "Error getting the current mute state.\n"); + manager_cleanup(manager); + return 1; + } + + // Toggle the mute state of the selected sink + int new_mute_state = !current_mute_state; + if (manager_toggle_output_mute(manager, (index - 1), new_mute_state) != 0) { + fprintf(stderr, "Failed to toggle the mute state.\n"); + manager_cleanup(manager); + return 1; + } + + printf("The mute state of '%s' has been %s.\n", manager->outputs[index-1].name, new_mute_state ? "muted" : "unmuted"); + + // Clean up + manager_cleanup(manager); + return 0; +} diff --git a/v-0.16/examples/print-input-sources b/v-0.16/examples/print-input-sources new file mode 100755 index 0000000..68be716 Binary files /dev/null and b/v-0.16/examples/print-input-sources differ diff --git a/v-0.16/examples/print-input-sources.c b/v-0.16/examples/print-input-sources.c new file mode 100644 index 0000000..09fedfa --- /dev/null +++ b/v-0.16/examples/print-input-sources.c @@ -0,0 +1,92 @@ +#include +#include "../system_query.h" + +int main() { + + char *alsa_name = NULL; + int min_channels = 0; + int max_channels = 0; + char *alsa_id = NULL; + + // Get all available input devices + pa_source_info **input_devices = get_available_input_devices(); + if (input_devices == NULL) { + fprintf(stderr, "Failed to get input devices\n"); + // Normally we would clean up here, but no cleanup function is available + return 1; + } + + // Output the number of input devices + uint32_t input_device_count = get_input_device_count(); + printf("Number of input devices: %u\n", input_device_count); + + // Iterate over each input device, print its name and sample rate + for (uint32_t i = 0; input_devices[i] != NULL; ++i) { + + + pa_source_info *source_info = input_devices[i]; + alsa_id = get_alsa_input_id(source_info->name); + + //printf("[DEBUG, main()] alsa_id is, %s\n", alsa_id); + + min_channels = get_min_input_channels(alsa_id, source_info); + max_channels = get_max_input_channels(alsa_id, source_info); + alsa_name = get_alsa_input_name(source_info->name); + + printf(" - Input Device %u:\n", (i +1)); + printf(" - Pulseaudio ID: %s\n", source_info->name); + printf(" - Pulseaudio name: %s\n", source_info->description); + + uint32_t sample_rate = get_input_sample_rate(alsa_id, source_info); + printf(" - Sample Rate: %u Hz\n", sample_rate); + + if ((alsa_name) && (alsa_id)) { + printf(" - Alsa name: %s\n", alsa_name); + printf(" - Alsa id: %s\n", alsa_id); + free(alsa_name); + free(alsa_id); + } + else { + printf(" [!] Unable to find an alsa name and ID.\n"); + printf(" [!] This is probably a pulseaudio-only virtual device.\n"); + } + + if(min_channels > 0) printf(" - Minimum channels: %i\n", min_channels); + if(max_channels > 0) printf(" - Maximum channels: %i\n\n", max_channels); + + //Reset channels for now. + min_channels = 0; + max_channels = 0; + } + + // Clean up all input devices + delete_input_devices(input_devices); + + + #if 0 + // Using the get_source_port_info function to get port information + pa_source_info_list* source_ports_info = get_source_port_info(); + + if (source_ports_info == NULL) { + fprintf(stderr, "Failed to get source port information\n"); + // Cleanup would go here + return 1; + } + + // Iterate over the source ports and print information about active and available ports + printf("Available source ports:\n"); + for (int i = 0; i < source_ports_info->num_ports; ++i) { + pa_port_info *port_info = &source_ports_info->ports[i]; + printf(" Port name: %s\n", port_info->name); + printf(" Port description: %s\n", port_info->description); + printf(" Port status: %s\n", port_info->is_active ? "active" : "inactive"); + } + + // Remember to free the source_ports_info structure after use + // Note: This assumes that the 'ports' array and its contents are dynamically allocated + free(source_ports_info->ports); + free(source_ports_info); + #endif + + return 0; +} diff --git a/v-0.16/examples/print_sink_inputs b/v-0.16/examples/print_sink_inputs new file mode 100755 index 0000000..e7a00dc Binary files /dev/null and b/v-0.16/examples/print_sink_inputs differ diff --git a/v-0.16/examples/print_sink_inputs.c b/v-0.16/examples/print_sink_inputs.c new file mode 100644 index 0000000..ce524c1 --- /dev/null +++ b/v-0.16/examples/print_sink_inputs.c @@ -0,0 +1,94 @@ +/** + * @file print_sink_inputs.c + * @brief This file demonstrates the usage of the EasyPulse library to interact with PulseAudio. + * + * The program initializes a PulseAudio manager, checks its readiness, and retrieves a list + * of all output streams. It then iterates over each output device to list the streams associated + * with it. This serves as an example of how to use the EasyPulse library to interact with PulseAudio + * for querying and managing audio streams and devices. + * + * Dependencies: + * - easypulse_core.h: Provides the core functionalities and structures for the EasyPulse library. + * - pulse/introspect.h: PulseAudio API for introspection functionalities. + * - stdint.h: Standard integer types. + * - stdio.h: Standard input/output library for C. + * - unistd.h: Provides access to the POSIX operating system API. + * + * Functions: + * - manager_create(): Initializes and returns a new pulseaudio_manager instance. + * - manager_cleanup(): Cleans up and deallocates a pulseaudio_manager instance. + * - get_output_streams(): Retrieves a list of all output streams from the PulseAudio context. + * - output_streams_cleanup(): Cleans up and deallocates an output_stream_list instance. + * + * The program demonstrates error handling, stream listing, and cleanup procedures in a PulseAudio context. + * + * @author Mbyte2 + * @date November 23, 2023 + * + */ + +#include "../easypulse_core.h" +#include +#include +#include +#include + +int main(void) { + const char *key; + void *proplist_state = NULL; + + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Get a list of all output streams + output_stream_list *streams = get_output_streams(manager->context); + if (!streams) { + fprintf(stderr, "Failed to get output streams.\n"); + manager_cleanup(manager); + return -1; + } + + printf("*** Listing all output devices and streams *** \n"); + + // Iterate over each output device + for (uint32_t i = 0; i < manager->output_count; ++i) { + + // For each device, list the streams associated with it + for (uint32_t j = 0; j < streams->num_inputs; ++j) { + if (streams->inputs[j].parent_index == manager->outputs[i].index) { + printf("\tStream [%u] name: %s\n", streams->inputs[j].index, streams->inputs[j].name); + printf("\tOwner: %lu\n", (unsigned long) streams->inputs[j].owner_module); + printf("\tParent index: %lu\n", (unsigned long) streams->inputs[j].parent_index); + printf("\tVolume channels: %u\n", (unsigned) streams->inputs[j].volume.channels); + printf("\tChannel map channels: %u\n", (unsigned) streams->inputs[j].channel_map.channels); + printf("\tSink Input Format Encoding: %u\n", streams->inputs[j].format->encoding); + printf("\tProperties:\n"); + + while ((key = pa_proplist_iterate(streams->inputs[j].proplist, &proplist_state))) { + const char *value = pa_proplist_gets(streams->inputs[j].proplist, key); + if (value) { + printf("\t%s = %s\n", key, value); + } else { + printf("\t%s = \n", key); + } + } + printf("\t***\n"); + } + } + } + + output_streams_cleanup(streams); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.16/examples/print_sink_inputs_pulseaudio b/v-0.16/examples/print_sink_inputs_pulseaudio new file mode 100755 index 0000000..cdf2463 Binary files /dev/null and b/v-0.16/examples/print_sink_inputs_pulseaudio differ diff --git a/v-0.16/examples/print_sink_inputs_pulseaudio.c b/v-0.16/examples/print_sink_inputs_pulseaudio.c new file mode 100644 index 0000000..82c42b3 --- /dev/null +++ b/v-0.16/examples/print_sink_inputs_pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file pa_sink_list.c + * @brief Demonstrates listing all available PulseAudio sink inputs and their active profiles. + * + * This program connects to the PulseAudio server and enumerates all available sink inputs. + * For each sink input, it retrieves and displays its name and active profile. The program + * showcases the basic use of the PulseAudio API in a C program for audio device management. + * + * Author: Mbyte2 + * Date: November 21, 2023 + * + */ + +#include +#include + +// Callback function for sink input info +void sink_input_info_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_mainloop_quit((pa_mainloop*)userdata, 0); + return; + } + + printf("Sink Input #%u\n", i->index); + printf("Name: %s\n", i->name); +} + +// Callback function for server info +void server_info_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void) i; + (void) userdata; + + pa_operation *o; + + if (!(o = pa_context_get_sink_input_info_list(c, sink_input_info_cb, userdata))) { + fprintf(stderr, "pa_context_get_sink_input_info_list() failed\n"); + return; + } + + pa_operation_unref(o); +} + +// State callback for connection +void state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + pa_context_state_t state; + state = pa_context_get_state(c); + + switch (state) { + case PA_CONTEXT_READY: + server_info_cb(c, NULL, userdata); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + pa_mainloop_quit((pa_mainloop*)userdata, 0); + break; + + default: + break; + } +} + +int main() { + pa_mainloop *m = NULL; + pa_mainloop_api *api = NULL; + pa_context *context = NULL; + int ret = 1; + + // Create a mainloop API and connection to the default server + m = pa_mainloop_new(); + api = pa_mainloop_get_api(m); + context = pa_context_new(api, "Sink Input List"); + + // Connect to the PulseAudio server + pa_context_connect(context, NULL, 0, NULL); + + // Set the callback so we can wait for the server to be ready + pa_context_set_state_callback(context, state_cb, m); + + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "Failed to run mainloop\n"); + goto quit; + } + +quit: + if (context) { + pa_context_unref(context); + } + + if (m) { + pa_mainloop_free(m); + } + + return ret; +} diff --git a/v-0.16/examples/print_source_outputs b/v-0.16/examples/print_source_outputs new file mode 100755 index 0000000..9529fa1 Binary files /dev/null and b/v-0.16/examples/print_source_outputs differ diff --git a/v-0.16/examples/print_source_outputs.c b/v-0.16/examples/print_source_outputs.c new file mode 100644 index 0000000..01c564e --- /dev/null +++ b/v-0.16/examples/print_source_outputs.c @@ -0,0 +1,71 @@ +/** + * @file print_source_outputs.c + * @brief Demonstrates interaction with PulseAudio using the EasyPulse library for input streams. + * + * This program initializes a PulseAudio manager, verifies its readiness, and retrieves a list + * of all input streams (source outputs). It iterates over each input stream, printing detailed + * information about them. This example showcases the use of the EasyPulse library to interface + * with PulseAudio for querying and managing audio input streams and their associated devices. + * + * Dependencies: + * - easypulse_core.h: Provides core functionalities and structures for the EasyPulse library. + * - pulse/introspect.h: PulseAudio API for introspection functionalities. + * - stdint.h: Standard integer types. + * - stdio.h: Standard input/output library for C. + * - unistd.h: Access to the POSIX operating system API. + * + * Functions: + * - manager_create(): Initializes and returns a new pulseaudio_manager instance. + * - manager_cleanup(): Cleans up and deallocates a pulseaudio_manager instance. + * - get_input_streams(): Retrieves a list of all input streams (source outputs) from the PulseAudio context. + * - input_streams_cleanup(): Cleans up and deallocates an input_stream_list instance. + * + * The program demonstrates error handling, input stream listing, and cleanup procedures within a PulseAudio context. + * + * @author Mbyte2 + * @date November 23, 2023 + */ + +#include "../easypulse_core.h" +#include +#include +#include +#include + +int main(void) { + // Create and initialize the pulseaudio_manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create PulseAudio manager.\n"); + return -1; + } + + if (manager->pa_ready != 1) { + fprintf(stderr, "PulseAudio manager is not ready.\n"); + manager_cleanup(manager); + return -1; + } + + // Get a list of all input streams (source outputs) + input_stream_list *input_streams = get_input_streams(manager->context); + if (!input_streams) { + fprintf(stderr, "Failed to get input streams.\n"); + manager_cleanup(manager); + return -1; + } + + printf("*** Listing all input streams (source outputs) ***\n"); + + // Iterate over each input stream + for (uint32_t i = 0; i < input_streams->num_inputs; ++i) { + printf("\tInput Stream [%u] name: %s\n", input_streams->outputs[i].index, input_streams->outputs[i].name); + // Additional properties of the input stream can be printed here + printf("\t***\n"); + } + + // Cleanup + input_streams_cleanup(input_streams); // Assuming a cleanup function for input streams + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.16/examples/print_volume_output_devices b/v-0.16/examples/print_volume_output_devices new file mode 100755 index 0000000..6c1a6de Binary files /dev/null and b/v-0.16/examples/print_volume_output_devices differ diff --git a/v-0.16/examples/print_volume_output_devices.c b/v-0.16/examples/print_volume_output_devices.c new file mode 100644 index 0000000..bfa1b9b --- /dev/null +++ b/v-0.16/examples/print_volume_output_devices.c @@ -0,0 +1,72 @@ +/** + * @file print_volume_output_devices.c + * @brief This file contains the main function to demonstrate the retrieval + * of volume % of each individual audio channel of an output device with the PulseAudio API. + * + * volume-demo.c lists the available audio sinks, their ALSA IDs, sample rates, and + * channel volumes. This file assumes the presence of a system_query.h header + * file and related implementations for interacting with the PulseAudio system. + */ + +#include "../system_query.h" +#include + +/** + * @brief Main function which queries and displays audio device information. + * + * The function initializes necessary structures for PulseAudio (done in system_query.c), + * retrieves the count of available devices, and iterates through each device + * to display its details, including the ALSA ID, sample rate, and channel volumes. + * + * @return int Returns 0 on successful execution, 1 on failure (e.g., no sinks available). + */ +int main() { + // Initialize any necessary PulseAudio structures + + // Get the count of available devices + uint32_t device_count = get_output_device_count(); + printf("Total devices: %u\n", device_count); + + // Get all available sinks + pa_sink_info **sinks = get_available_output_devices(); + if (sinks == NULL) { + printf("No sinks available.\n"); + return 1; + } + + + // Iterate through each sink + for (uint32_t i = 0; i < device_count; ++i) { + if (sinks[i] == NULL) { + continue; + } + + char **channel_names = NULL; + + printf(" Device %u: %s\n", i, sinks[i]->description); + + // Get the ALSA ID for the sink + const char *alsa_id = get_alsa_output_id(sinks[i]->name); + printf("\tALSA ID: %s\n", alsa_id); + + // Get the sample rate for the sink + int sample_rate = get_output_sample_rate(alsa_id, sinks[i]); + printf("\tSample Rate: %d Hz\n", sample_rate); + + channel_names = get_output_channel_names(sinks[i]->name, sinks[i]->channel_map.channels); + + // Iterate through each channel + for (uint8_t ch = 0; ch < sinks[i]->channel_map.channels; ++ch) { + pa_volume_t volume = get_channel_volume(sinks[i], ch); + float volume_percent = (float)volume / PA_VOLUME_NORM * 100; + printf("\tChannel %u name: %s, volume: %.2f%%\n", (ch + 1), channel_names[ch], volume_percent); + free(channel_names[ch]); + } + free(channel_names); + } + + // Cleanup + delete_output_devices(sinks); + + return 0; +} diff --git a/v-0.16/examples/switch-input-devices b/v-0.16/examples/switch-input-devices new file mode 100755 index 0000000..1f8b3e5 Binary files /dev/null and b/v-0.16/examples/switch-input-devices differ diff --git a/v-0.16/examples/switch-input-devices.c b/v-0.16/examples/switch-input-devices.c new file mode 100644 index 0000000..a6697f7 --- /dev/null +++ b/v-0.16/examples/switch-input-devices.c @@ -0,0 +1,84 @@ +/** + * @file switch-input.c + * @brief Program to list and switch PulseAudio input devices. + * + * This program demonstrates how to use the EasyPulse library to interact with + * PulseAudio input devices. It lists all available input devices (sources) and + * allows the user to switch the default input device to any of the listed devices. + * + * The program performs the following steps: + * 1. Initializes the PulseAudio manager using the EasyPulse library. + * 2. Lists all available input devices with their names and internal codes. + * 3. Prompts the user to select an input device by entering its associated number. + * 4. Validates the user's choice to ensure it corresponds to an available device. + * 5. Switches the default input device to the user-selected device. + * 6. Cleans up the PulseAudio manager instance before program termination. + * + * Usage: + * Run the program, and it will display a list of available input devices. Enter + * the number corresponding to the desired input device to switch to it. + * + * + * @author Mbyte2 + * @date November 10, 2023 + */ + +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available input devices to the user + printf("\n\n***INPUT SWITCHING DEMO***\n\n"); + + //We will display human-friendly device name in the program + char *device_name = get_input_name_by_code(manager->context, manager->active_input_device); + + if(!device_name) { + fprintf(stderr, "[main()] Failed when trying to allocate memory for device name.\n"); + return 1; + } + + printf("[Default device: %s]\n\n", device_name); + printf("Available input devices:\n"); + + for (uint32_t i = 0; i < manager->input_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->inputs[i].name, manager->inputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the input device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + // Validate the user's choice + if ((choice - 1) >= manager->input_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_input(manager, manager->inputs[choice - 1].index) == true) { + printf("Successfully switched to the selected input device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected input device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + free(device_name); + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.16/examples/switch-output-device b/v-0.16/examples/switch-output-device new file mode 100755 index 0000000..5c868e2 Binary files /dev/null and b/v-0.16/examples/switch-output-device differ diff --git a/v-0.16/examples/switch-output-device-pulseaudio b/v-0.16/examples/switch-output-device-pulseaudio new file mode 100755 index 0000000..bfa42a2 Binary files /dev/null and b/v-0.16/examples/switch-output-device-pulseaudio differ diff --git a/v-0.16/examples/switch-output-device-pulseaudio.c b/v-0.16/examples/switch-output-device-pulseaudio.c new file mode 100644 index 0000000..0a0c85a --- /dev/null +++ b/v-0.16/examples/switch-output-device-pulseaudio.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; +static int sink_count = 0; +static char **sink_names = NULL; + +void sink_list_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + if (eol > 0) { + int chosen; + printf("Enter the number of the sink to set as default: "); + scanf("%d", &chosen); + + if (chosen >= 0 && chosen < sink_count) { + pa_context_set_default_sink(c, sink_names[chosen], NULL, NULL); + } + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + sink_names = realloc(sink_names, sizeof(char*) * (sink_count + 1)); + sink_names[sink_count] = strdup(info->name); + printf("%d: %s (%s)\n", sink_count++, info->name, info->description); +} + +void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_list(c, sink_list_cb, NULL)); + } +} + +int main(void) { + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "sink_switcher_threaded"); + + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + pa_threaded_mainloop_start(mainloop); + + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); // Wait until sink_list_cb signals completion + + for (int i = 0; i < sink_count; i++) { + free(sink_names[i]); + } + free(sink_names); + + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.16/examples/switch-output-device.c b/v-0.16/examples/switch-output-device.c new file mode 100644 index 0000000..0368692 --- /dev/null +++ b/v-0.16/examples/switch-output-device.c @@ -0,0 +1,47 @@ +#include "../easypulse_core.h" +#include "../system_query.h" +#include +#include +#include + +int main() { + // Initialize the PulseAudio Manager + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to initialize PulseAudioManager.\n"); + return 1; + } + + // Display available output devices to the user + printf("\n\n***OUTPUT SWITCHING DEMO***\n\nAvailable output devices:\n"); + for (uint32_t i = 0; i < manager->output_count; i++) { + printf("%d. %s - %s\n", i + 1, manager->outputs[i].name, manager->outputs[i].code); + } + + // Prompt the user to select a device + printf("Enter the number of the output device you want to switch to: "); + uint32_t choice; + scanf("%u", &choice); + + + // Validate the user's choice + if ((choice - 1) > manager->output_count) { + fprintf(stderr, "Invalid choice.\n"); + manager_cleanup(manager); + return 1; + } + + // Switch to the selected device + if (manager_switch_default_output(manager, manager->outputs[choice - 1].index) == true) { + printf("Successfully switched to the selected output device.\n"); + } else { + fprintf(stderr, "Failed to switch to the selected output device.\n"); + manager_cleanup(manager); + return 1; + } + + // Cleanup + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.16/examples/volume-change b/v-0.16/examples/volume-change new file mode 100755 index 0000000..3f34572 Binary files /dev/null and b/v-0.16/examples/volume-change differ diff --git a/v-0.16/examples/volume-change-pulseaudio b/v-0.16/examples/volume-change-pulseaudio new file mode 100755 index 0000000..0321159 Binary files /dev/null and b/v-0.16/examples/volume-change-pulseaudio differ diff --git a/v-0.16/examples/volume-change-pulseaudio.c b/v-0.16/examples/volume-change-pulseaudio.c new file mode 100644 index 0000000..aa629ab --- /dev/null +++ b/v-0.16/examples/volume-change-pulseaudio.c @@ -0,0 +1,101 @@ +/** + * @file volume-change-pulseaudio.c + * @brief Change the volume of the default PulseAudio sink. + * + * This program prompts the user for a desired volume level, displays the current volume + * of the default sink, sets the volume of the default sink to the user's input, and then + * displays the volume after the change. + */ + +#include +#include +#include + +static pa_threaded_mainloop *mainloop; +static pa_context *context; + +/** + * @brief Callback function to set the volume of the default sink. + * + * This function fetches the current volume of the default sink, displays it, + * then prompts the user for a desired volume level, sets the volume, and finally + * displays the volume after the change. + * + * @param c The PulseAudio context. + * @param info Information about the current sink. + * @param eol End-of-list flag. + * @param userdata User data (unused). + */ +void set_volume_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol > 0) { + pa_threaded_mainloop_signal(mainloop, 0); + return; + } + + // Prompt the user for the desired volume level first + int volume_percentage; + printf("Enter the desired volume (0-100): "); + scanf("%d", &volume_percentage); + + // Display the current volume percentage + int volume_percentage_before = (int)(100.0f * pa_cvolume_avg(&info->volume) / PA_VOLUME_NORM + 0.5); + printf("Volume before change: %d%%\n", volume_percentage_before); + + // Set the volume based on user input + pa_cvolume volume; + pa_cvolume_set(&volume, info->channel_map.channels, (volume_percentage * PA_VOLUME_NORM) / 100); + pa_context_set_sink_volume_by_index(c, info->index, &volume, NULL, NULL); + + // Display the volume after the change + int volume_percentage_after = (int)(100.0f * pa_cvolume_avg(&volume) / PA_VOLUME_NORM + 0.5); + printf("Volume after change: %d%%\n", volume_percentage_after); + + pa_threaded_mainloop_signal(mainloop, 0); +} + +/** + * @brief Callback function for context state changes. + * + * This function checks if the context is ready and then triggers the retrieval + * of the default sink's information. + * + * @param c The PulseAudio context. + * @param userdata User data (unused). + */ +void context_state_cb(pa_context *c, void *userdata) { + (void) c; + (void) userdata; + + if (pa_context_get_state(c) == PA_CONTEXT_READY) { + pa_operation_unref(pa_context_get_sink_info_by_name(c, "@DEFAULT_SINK@", set_volume_cb, NULL)); + } +} + +int main(void) { + // Initialize PulseAudio threaded mainloop and context + mainloop = pa_threaded_mainloop_new(); + pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop); + context = pa_context_new(mainloop_api, "volume_changer_threaded"); + + // Set context state callback and connect the context + pa_context_set_state_callback(context, context_state_cb, NULL); + pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL); + + // Start the threaded mainloop + pa_threaded_mainloop_start(mainloop); + + // Lock the mainloop and wait for operations to complete + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_wait(mainloop); + + // Cleanup and free resources + pa_context_disconnect(context); + pa_context_unref(context); + pa_threaded_mainloop_unlock(mainloop); + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + return 0; +} diff --git a/v-0.16/examples/volume-change.c b/v-0.16/examples/volume-change.c new file mode 100644 index 0000000..2353401 --- /dev/null +++ b/v-0.16/examples/volume-change.c @@ -0,0 +1,67 @@ +/** + * @file volume-change.c + * @brief PulseAudio Manager demo program + * + * This program demonstrates the usage of the PulseAudio Manager API. + * It creates a manager instance, lists the available output devices, + * asks the user to select one of the output devices and to enter a master volume. + * It then sets the master volume of the selected output device to the given value. + * + * @author Mbyte2 + * @date 11-07-2023 + */ +#include +#include "../easypulse_core.h" // Assuming this is the header file where your API is defined + +int main() { + // Create a manager instance + pulseaudio_manager *manager = manager_create(); + if (!manager) { + fprintf(stderr, "Failed to create manager.\n"); + return 1; + } + + // List the outputs + printf("Available output devices:\n"); + for (uint32_t i = 0; i < get_output_device_count(); ++i) { + printf("%d: %s\n", (i+1), manager->outputs[i].name); + } + + // Ask the user to select one of the outputs + uint32_t selected_output; + printf("Please enter the number of the output device you want to use: "); + scanf("%u", &selected_output); + + // Check if the selected output is valid + if ((selected_output - 1) >= get_output_device_count()) { + fprintf(stderr, "Invalid output device number.\n"); + manager_cleanup(manager); + return 1; + } + + // Ask the user to type a master volume + int master_volume; + printf("Please enter the master volume (0-100): "); + scanf("%d", &master_volume); + + // Check if the master volume is valid + if (master_volume < 0 || master_volume > 100) { + fprintf(stderr, "Invalid master volume. It should be between 0 and 100.\n"); + manager_cleanup(manager); + return 1; + } + + // Set the master volume to the given value + if (manager_set_master_volume(manager, (selected_output -1), master_volume) != 0) { + fprintf(stderr, "Failed to set master volume.\n"); + manager_cleanup(manager); + return 1; + } + + printf("Master volume for output device '%s' has been set to %d.\n", manager->outputs[(selected_output-1)].name, master_volume); + + // Clean up + manager_cleanup(manager); + + return 0; +} diff --git a/v-0.16/libeasypulse_core.a b/v-0.16/libeasypulse_core.a new file mode 100644 index 0000000..4d778d1 Binary files /dev/null and b/v-0.16/libeasypulse_core.a differ diff --git a/v-0.16/system_query.c b/v-0.16/system_query.c new file mode 100644 index 0000000..c71d345 --- /dev/null +++ b/v-0.16/system_query.c @@ -0,0 +1,3401 @@ +/** + * @file system_query.c + * @brief Functions for querying system audio properties using PulseAudio in a Linux environment. + * + * This implementation file provides a collection of functions designed to interact with the + * PulseAudio sound server to query and manipulate various audio properties of a Linux system. + * These functions allow for the examination and control of audio devices (sinks and sources), + * such as enumerating available devices, retrieving their properties, checking and changing + * volume and mute states, and managing audio profiles. The functionalities encapsulated in + * this file are crucial for applications that need to interface with the system's audio + * hardware and perform operations like audio routing, volume control, or retrieving hardware + * information. + * + * + * @author Mbyte2 + * @date November 13, 2023 + * + */ + +#include "system_query.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // Include this for the isdigit() function +#include +#include + +#define DAEMON_CONF "/etc/pulse/daemon.conf" +#define MAX_LINE_LENGTH 1024 + +#define MUTED 1 +#define UNMUTED 0 + +// Since pulseaudio uses callbacks, we need something that will allow us to share data +// between functions. +typedef struct { + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloop_api; + pa_context *context; +} _shared_data_1; + +static _shared_data_1 shared_data_1; + +// Structure to share data between get_alsa_name and its callback. +typedef struct { + char *alsa_name; + char *alsa_id; +} _shared_data_2; + +static _shared_data_2 shared_data_2 = {.alsa_name = NULL}; + + +// Structure to share data between get_available_output_devices and its callback. +typedef struct { + pa_sink_info **sinks; // An array of pointers to pa_sink_info + uint32_t count; +} _shared_data_3; + +static _shared_data_3 shared_data_3; + +// Structure to share data between get_profiles and its callback. +typedef struct { + pa_card_profile_info *profiles; + int num_profiles; +} _shared_data_4; + +_shared_data_4 shared_data_4 = {NULL, 0}; + +// Structure to share data between get_available_input_devices and its callback. +static struct { + pa_source_info **sources; // Array of pointers to pa_source_info structures + uint32_t count; // Count of available sources + uint32_t allocated; // Allocated size of the sources array +} shared_data_sources = {NULL, 0, 0}; + +// Strcture to share data between get_channel_mute_state and its callback. +typedef struct { + uint32_t channel_index; + bool mute_state; +} _shared_data_5; + +// Structure to hold the sink profile information +typedef struct { + pa_card_profile_info *active_profile; + bool found; +} card_profile_info; + + + +static void pulse_cleanup(void); +static bool is_pulse_initialized(void); +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata); + +// Utility function to print all properties in the proplist +void print_proplist(const pa_proplist *p) { + void *state = NULL; + const char *key; + while ((key = pa_proplist_iterate(p, &state))) { + const char *value = pa_proplist_gets(p, key); + if (value) { + printf("%s = %s\n", key, value); + } else { + printf("%s = \n", key); + } + } +} + +/** + * @brief Iterates through operations in the pulseaudio threaded loop. + * + * @param loop Pointer to the threaded loop instance. + * @param op Pointer to the pa_operation instance. + */ +static void iterate(pa_operation *op) { + //fprintf(stderr, "[DEBUG] Entering %s\n", __FUNCTION__); // Debug statement for entry + + //Leaves if operation is invalid. + if (!op) { + fprintf(stderr, "[DEBUG] Operation is NULL\n"); // Debug statement for NULL operation + return; + } + + bool is_in_mainloop_thread = pa_threaded_mainloop_in_thread(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Is in mainloop thread: %d\n", is_in_mainloop_thread); // Debug statement for thread check + + // If we're not in the mainloop thread, lock it. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_lock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop locked\n"); // Debug statement for mainloop locked + } + + //Wait for the operation to complete. + //The signaling to continue is performed inside the callback operation (op). + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Waiting in mainloop...\n"); // Debug message while waiting + } + + //Debug code if needed. + #if 0 + // Check the operation state after waiting + switch (pa_operation_get_state(op)) { + case PA_OPERATION_DONE: + fprintf(stderr, "[DEBUG] Operation completed successfully\n"); // Debug message for successful completion + break; + case PA_OPERATION_CANCELLED: + fprintf(stderr, "[DEBUG] Operation was cancelled\n"); // Debug message for cancellation + break; + case PA_OPERATION_RUNNING: // This case should not be possible after the wait + default: + fprintf(stderr, "[DEBUG] Operation is in an unexpected state: %d\n", pa_operation_get_state(op)); // Debug message for unexpected state + break; + } + #endif + + //Cleaning up. + pa_operation_unref(op); + //fprintf(stderr, "[DEBUG] Operation unreferenced and cleaned up\n"); // Debug statement for cleanup + + // If we locked the mainloop earlier, unlock it now. + if (!is_in_mainloop_thread) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + //fprintf(stderr, "[DEBUG] Mainloop unlocked\n"); // Debug statement for mainloop unlocked + } + + //fprintf(stderr, "[DEBUG] Exiting %s\n", __FUNCTION__); // Debug statement for exit +} + + +/** + * @brief Callback function to handle changes in PulseAudio context state. + * + * This function is triggered whenever the state of the PulseAudio context changes. + * It signals the mainloop to continue execution whenever the context is in a READY, FAILED, + * or TERMINATED state. + * + * @param c Pointer to the PulseAudio context. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop. + */ +static void context_state_cb(pa_context *c, void *userdata) { + (void) userdata; + pa_context_state_t state = pa_context_get_state(c); + + // If the context is in a READY, FAILED, or TERMINATED state, signal the mainloop to continue. + if (state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + +/** + * @brief Utility function to check if PulseAudio is initialized and in a READY state. + * + * This function checks the state of the PulseAudio context and other key PulseAudio resources + * to determine if PulseAudio has been successfully initialized and is in a READY state. + * + * @return True if PulseAudio is initialized and in a READY state, false otherwise. + */ +static bool is_pulse_initialized(void) { + + + // Check if the main components of PulseAudio (mainloop, mainloop_api, and context) are initialized. + if (shared_data_1.mainloop && shared_data_1.mainloop_api && shared_data_1.context) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + + // If the context is in a READY state, PulseAudio is initialized. + if (state == PA_CONTEXT_READY) { + return true; + } + } + return false; +} + +/** + * @brief Initializes the PulseAudio mainloop and context for querying audio information. + * + * This function sets up the necessary PulseAudio components for subsequent queries + * to the audio subsystem. It creates a new threaded mainloop, obtains the mainloop API, + * and creates a new context with a specified name. It also starts the mainloop and + * connects the context to the PulseAudio server, waiting until the context is ready + * or an error occurs. + * + * @note If this function fails at any point, it ensures that all allocated resources are + * cleaned up before returning. + * + * @return true if the PulseAudio components were initialized successfully, false otherwise. + */ +bool initialize_pulse() { + shared_data_1.mainloop = pa_threaded_mainloop_new(); + if (!shared_data_1.mainloop) { + fprintf(stderr, "Failed to create mainloop.\n"); + return false; + } + + shared_data_1.mainloop_api = pa_threaded_mainloop_get_api(shared_data_1.mainloop); + if (!shared_data_1.mainloop_api) { + fprintf(stderr, "Failed to get mainloop API.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + shared_data_1.context = pa_context_new(shared_data_1.mainloop_api, "Easypulse query API"); + if (!shared_data_1.context) { + fprintf(stderr, "Failed to create context.\n"); + pa_threaded_mainloop_free(shared_data_1.mainloop); + return false; + } + + + pa_context_set_state_callback(shared_data_1.context, context_state_cb, shared_data_1.mainloop); + + // Start the threaded mainloop + pa_threaded_mainloop_start(shared_data_1.mainloop); + + // Lock the mainloop and connect the context + pa_threaded_mainloop_lock(shared_data_1.mainloop); + if (pa_context_connect(shared_data_1.context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + + // Wait for the context to be ready + while (true) { + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + break; + } else if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) { + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + pulse_cleanup(); + return false; + } + pa_threaded_mainloop_wait(shared_data_1.mainloop); + } + + pa_threaded_mainloop_unlock(shared_data_1.mainloop); + return true; +} + + +/** + * @brief Cleanup function to properly disconnect and free PulseAudio resources. + * + * This function ensures that all the PulseAudio resources are properly disconnected, + * dereferenced, and freed to avoid any resource leaks. It should be called whenever + * PulseAudio operations are done and the program wishes to terminate or release PulseAudio. + */ +static void pulse_cleanup(void) { + if (shared_data_1.context) { + // Check if the context is in a state where it can be disconnected + pa_context_state_t state = pa_context_get_state(shared_data_1.context); + if (state == PA_CONTEXT_READY) { + pa_context_disconnect(shared_data_1.context); + } + pa_context_unref(shared_data_1.context); + shared_data_1.context = NULL; + } + if (shared_data_1.mainloop) { + pa_threaded_mainloop_free(shared_data_1.mainloop); + shared_data_1.mainloop = NULL; + } +} + +/** + * @brief Callback function used to retrieve the count of audio profiles for a specific card. + * + * This function is called by PulseAudio when querying for the list of profiles for a given card index. + * It sets the profile_count with the number of profiles available for the card. + * If there's an error or the list is exhausted, the function signals the mainloop to continue + * without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current card information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function, which contains the mainloop + * and the profile_count to be set. + */ +static void get_profile_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + uint32_t *profile_count = (uint32_t *) userdata; + + // Handle errors in retrieving profile count. + if (eol < 0) { + fprintf(stderr, "Failed to get profile count.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of profiles, signal the mainloop to continue. + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Set the profile_count with the number of profiles available for the card. + *profile_count = i->n_profiles; +} + + +/** + * @brief Retrieve the count of audio profiles for a specific card. + * + * This function queries PulseAudio to get a count of all available audio profiles + * for a specified card index. If PulseAudio is not initialized, the function attempts + * to initialize it. If there's an error in fetching the profile count or initializing + * PulseAudio, it returns UINT32_MAX. + * + * @param card_index The index of the card for which to retrieve the profile count. + * @return Count of audio profiles or UINT32_MAX on error. + */ +uint32_t get_profile_count(uint32_t card_index) { + uint32_t profile_count = 0; // Initialize profile count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_profiles_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Query PulseAudio for the list of audio profiles for the specified card. + // The callback get_profile_count_cb will populate the profile_count variable. + count_op = pa_context_get_card_info_by_index(shared_data_1.context, card_index, get_profile_count_cb, &profile_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return profile_count; // Return the total count of profiles. +} + +/** + * @brief Callback function used to populate the list of available audio sinks. + * + * This function is called for each audio sink found by PulseAudio when querying + * for the list of sinks. The function populates the `shared_sink_data` structure + * with `pa_sink_info` for each sink. When the list is exhausted or there's an error, + * the function exits without further processing. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio sink information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_available_output_devices_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + uint32_t *count = &shared_data_3.count; + + // Handle errors in retrieving sink information. + if (eol < 0) { + fprintf(stderr, "Failed to get sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // If we've reached the end of the list of sinks, mark the end and exit the callback. + if (eol > 0) { + shared_data_3.sinks[*count] = NULL; // Set the last element to NULL as sentinel + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Allocate memory for the pa_sink_info structure. + shared_data_3.sinks[*count] = malloc(sizeof(pa_sink_info)); + if (shared_data_3.sinks[*count] == NULL) { + fprintf(stderr, "Failed to allocate memory for sink info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Copy the pa_sink_info structure. + *(shared_data_3.sinks[*count]) = *i; + + // Duplicate the strings to ensure they remain valid. + if (i->name != NULL) { + shared_data_3.sinks[*count]->name = strdup(i->name); + } + if (i->description != NULL) { + shared_data_3.sinks[*count]->description = strdup(i->description); + } + + (*count)++; +} + +/** + * @brief Retrieve the list of available audio sinks for a specific card. + * + * This function queries PulseAudio to get a list of all available audio sinks + * for a specified card index. It dynamically allocates memory based on the count + * of sinks to store the list of sinks. + * If PulseAudio is not initialized, the function attempts to initialize it. + * + * @param card_index The index of the card for which to retrieve the sinks. + * @return Pointer to the first sink in the list or NULL on error. + */ +pa_sink_info **get_available_output_devices() { + pa_operation *op = NULL; + + // Using get_output_device_count() to obtain the number of sinks + uint32_t max_sinks = get_output_device_count(); + + // Allocate memory for pointers + shared_data_3.sinks = malloc((max_sinks + 1) * sizeof(pa_sink_info*)); + shared_data_3.count = 0; // Reset count + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + // Check for successful memory allocation + if (!shared_data_3.sinks) { + fprintf(stderr, "Failed to allocate memory for sinks.\n"); + return NULL; + } + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "get_available_output_devices(): failed to initialize pulseaudio.\n"); + free(shared_data_3.sinks); + shared_data_3.sinks = NULL; + return NULL; + } + } + + + // Query PulseAudio for the list of available sinks for the specified card + op = pa_context_get_sink_info_list(shared_data_1.context, get_available_output_devices_cb, NULL); + + // Wait for the PulseAudio operation to complete + iterate(op); + + return shared_data_3.sinks; +} + +/** + * @brief Frees the memory allocated for an array of output devices (sinks). + * + * This function iterates over an array of `pa_sink_info` pointers, freeing the memory for + * each sink's name and description strings, followed by the sink structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sinks A pointer to the first element in an array of `pa_sink_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_output_devices(pa_sink_info **sinks) { + if (sinks == NULL) { + return; + } + + for (int i = 0; sinks[i] != NULL; ++i) { + if (sinks[i]->name) { + free((char*)sinks[i]->name); + } + if (sinks[i]->description) { + free((char*)sinks[i]->description); + } + free(sinks[i]); + } + + free(sinks); +} + +/** + * @brief Frees the memory allocated for an array of input devices (sources). + * + * This function iterates over an array of `pa_source_info` pointers, freeing the memory for + * each source's name and description strings, followed by the source structure itself. It concludes + * by freeing the memory for the array of pointers. + * + * @param sources A pointer to the first element in an array of `pa_source_info` pointers, which + * must be terminated with a NULL pointer. If NULL is passed, the function does nothing. + */ +void delete_input_devices(pa_source_info **sources) { + if (!sources) { + return; // Nothing to do if the pointer is NULL + } + + // Iterate through each source info and free its memory + for (int i = 0; sources[i] != NULL; i++) { + if (sources[i]->name) { + free((char*)sources[i]->name); + } + if (sources[i]->description) { + free((char*)sources[i]->description); + } + free(sources[i]); + } + + // Free the array of pointers itself + free(sources); +} + + +/** + * @brief Callback function used to count the available audio devices (cards). + * + * This function is called for each audio device found by PulseAudio when querying + * for the list of devices. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio device information. + * @param eol Indicates the end of the list or an error. + * @param userdata Pointer to the data shared with the main function. + */ +static void get_output_device_count_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) i; + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo < 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + //fprintf(stderr,"[DEBUG, get_output_device_count_cb()] Reached elo > 0. Signal triggered.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + + +/** + * @brief Retrieve the count of audio devices in the system. + * + * This function queries PulseAudio to get a count of all available audio devices (cards). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio devices or UINT32_MAX on error. + */ +uint32_t get_output_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero. + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_device_count(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if context is valid after initialization attempt. + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_device_count.\n"); + return UINT32_MAX; + } + + //fprintf(stderr,"[get_device_count()] context is, %p\n",&shared_data_1.context); + //fprintf(stderr,"[get_device_count()] mainloop is, %p\n",&shared_data_1.mainloop); + + // Query PulseAudio for the list of audio devices (cards). + // The callback get_device_count_cb will increment the device_count for each device found. + count_op = pa_context_get_card_info_list(shared_data_1.context, get_output_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete. + iterate(count_op); + + return device_count; // Return the total count of devices. +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in capture mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. If ALSA fails, it will fall back to using the + * information from PulseAudio. + * + * @param alsa_name Name of the ALSA device. + * @param source_info Information about the PulseAudio source. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_input_channels(const char *alsa_id, const pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!source_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (source_info)) { + return source_info->sample_spec.channels; + } + + // Try to open the ALSA device in capture mode + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + // ALSA failed, fall back to PulseAudio information + return source_info->sample_spec.channels; + } + + // Allocate hardware parameters object + snd_pcm_hw_params_alloca(¶ms); + + // Fill it in with default values + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Get the maximum number of channels + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return source_info->sample_spec.channels; + } + + // Close the device + snd_pcm_close(handle); + + return max_channels; +} + +/** + * @brief Retrieves the maximum number of channels for the given ALSA device name. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the maximum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Maximum number of channels supported by the device or -1 on error. + */ +int get_max_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int max_channels = 0; + int err; + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "[get_max_output_channels()] Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; // Return channels from PulseAudio if ALSA fails + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, &max_channels)) < 0) { + //fprintf(stderr, "Error getting max channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + //fprintf(stderr, "[DEBUG, get_max_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return max_channels; +} + +/** + * @brief Retrieves the minimum number of channels for the given ALSA device id. + * + * This function opens the specified ALSA device in playback mode, retrieves + * its hardware parameters, and then queries for the minimum number of channels + * supported by the device. + * + * @param alsa_name Name of the ALSA device. + * @return Minimum number of channels supported by the device or -1 on error. + */ +int get_min_output_channels(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int min_channels = 0; + int err; + + //fprintf(stderr, "[DEBUG, get_min_output_channels()] sink_info->sample_spec.channels is %i\n", sink_info->sample_spec.channels); + + if ((!alsa_id) && (!sink_info)) { + fprintf(stderr, "Invalid parameters provided.\n"); + return -1; + } + //The input device was found, but there's no alsa information about it. + //Attempt to retrieve used channels instead. + else if((!alsa_id) && (sink_info)) { + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + //fprintf(stderr, "Unable to open PCM device: %s, error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.channels; + } + + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + //fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + if ((err = snd_pcm_hw_params_get_channels_min(params, &min_channels)) < 0) { + //fprintf(stderr, "Error getting min channels for device: %s, error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return sink_info->sample_spec.channels; + } + + snd_pcm_close(handle); + return min_channels; +} + +/** + * @brief Callback function for retrieving the ALSA card name of a PulseAudio source. + * + * This callback is called by the PulseAudio context during the operation to retrieve + * information about each available source. It is registered with the + * pa_context_get_source_info_by_name() function call. + * + * @param c The PulseAudio context. + * @param i The source information structure containing details about the source. + * @param eol End of list indicator. If non-zero, indicates no more data to process. + * @param userdata User data provided when registering the callback. In this case, it is + * expected to be the name of the source for which we want the ALSA card name. + * + * @note This function is intended to be used internally and should not be called directly. + * + * @warning This function uses global shared data (shared_data_2.alsa_name) to store the + * retrieved ALSA name, and signals the main loop to stop waiting. Ensure that + * the main loop and shared data are properly managed. + */ +static void get_alsa_input_name_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol < 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // Handle error or invalid source info + } + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // End of list + } + + // Check if this source is the one we're interested in + if (userdata && strcmp(i->name, (const char *)userdata) == 0) { + const char *prop_alsa_card_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (prop_alsa_card_name) { + shared_data_2.alsa_name = strdup(prop_alsa_card_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } + } +} + +/** + * @brief Retrieves the ALSA name of a given PulseAudio source. + * + * @param source_name The name of the source for which to retrieve the ALSA name. + * @return The ALSA name of the source or NULL if not found or on error. + */ +char* get_alsa_input_name(const char *source_name) { + if (!source_name) { + fprintf(stderr, "[get_alsa_input_name()] Invalid source name.\n"); + return NULL; + } + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "get_alsa_input_name(): PulseAudio initialization failed.\n"); + return NULL; + } + + pa_operation *name_op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_alsa_input_name_cb, (void*)source_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + + +/** + * @brief Callback function to retrieve the ALSA name from the card info. + * + * This callback checks for the "alsa.card_name" property in the card's + * proplist. If the property is found, it assigns the property's value + * to the shared data structure for further use. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the card info structure. + * @param eol Flag indicating end of list. + * @param userdata User-defined data pointer (unused in this callback). + */ +static void get_alsa_output_name_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + const char *target_sink_name = userdata; + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + const char *alsa_name = pa_proplist_gets(i->proplist, "alsa.card_name"); + if (alsa_name) { + shared_data_2.alsa_name = strdup(alsa_name); + if (!shared_data_2.alsa_name) { + fprintf(stderr, "Failed to allocate memory for ALSA name.\n"); + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } +} + + + +/** + * @brief Retrieves the ALSA name of the first audio device encountered. + * + * This function initializes PulseAudio (if not already initialized), then + * queries PulseAudio for the list of audio devices. It waits for the + * retrieval operation to complete and then returns the ALSA name + * of the first device it finds with the "alsa.card_name" property. + * + * @return ALSA name of the device or NULL if not found or on error. + */ +char* get_alsa_output_name(const char *sink_name) { + pa_operation *name_op; + + shared_data_2.alsa_name = NULL; // Ensure alsa_name is initialized to NULL + + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_name()] PulseAudio initialization failed.\n"); + return NULL; + } + + name_op = pa_context_get_sink_info_list(shared_data_1.context, get_alsa_output_name_cb, (void*)sink_name); + iterate(name_op); + + return shared_data_2.alsa_name; +} + +/** + * @brief Callback function to retrieve the ALSA device string based on PulseAudio source information. + * + * This function is called for each available PulseAudio source. It checks if the source's name matches + * the target source name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the source's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure. + * @param eol End of list flag. If non-zero, indicates the end of the source list. + * @param userdata User-defined data pointer. In this case, it points to the target source name string. + */ +static void get_alsa_input_id_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target source name from userdata + const char *target_source_name = (const char *)userdata; + + // Skip this source if it does not match the specified target name + if (target_source_name && strcmp(target_source_name, i->name) != 0) { + return; + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //print_proplist(i->proplist); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_card is %s\n", alsa_card); + //printf("[DEBUG, get_alsa_input_id_cb] alsa_device is %s\n", alsa_device); + + // Construct the ALSA device string if both properties are available + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + // Log an error if ALSA properties are not found or are invalid + //fprintf(stderr, " - ALSA properties not found or invalid for source.\n"); + shared_data_2.alsa_id = NULL; + } + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio source name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific source by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the source. + * + * @param source_name The name of the PulseAudio source. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_input_id(const char *source_name) { + // Operation object for asynchronous PulseAudio calls + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_input_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Duplicate the source name to ensure it remains valid throughout the operation + char *source_name_copy = strdup(source_name); + if (!source_name_copy) { + fprintf(stderr, "[get_alsa_input_id()] Failed to allocate memory for source name.\n"); + return NULL; + } + + // Start querying PulseAudio for the specified source information + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name_copy, get_alsa_input_id_cb, source_name_copy); + + // Block and wait for the operation to complete + iterate(op); + + // After the callback has been called with the source information, the ALSA device ID will be stored + // Return the stored ALSA device ID + return shared_data_2.alsa_id; +} + + +/** + * @brief Callback function to retrieve the ALSA device string based on the PulseAudio sink information. + * + * This function is called for each available PulseAudio sink. It checks if the sink's name matches the + * target sink name provided in the userdata. If a match is found, it retrieves the "alsa.card" and + * "alsa.device" properties from the sink's proplist, constructs the ALSA device string in the format + * "hw:,", and stores it in shared data for later retrieval. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the sink information structure. + * @param eol End of list flag. If non-zero, indicates the end of the sink list. + * @param userdata User-defined data pointer. In this case, it points to the target sink name string. + */ +static void get_alsa_output_id_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; // Unused parameter + + // Check for end of list + if (eol) { + // Signal the main loop to unblock the iterate function + //fprintf(stderr,"[DEBUG, get_alsa_id_cb()] End of function reached.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + free(userdata); + return; + } + + // Retrieve the target sink name from userdata + const char *target_sink_name = userdata; + + // If a target sink name is provided, check if it matches the current sink's name + if (target_sink_name && strcmp(target_sink_name, i->name) != 0) { + return; // Skip this sink, as it does not match the specified name + } + + // Attempt to retrieve the "alsa.card" and "alsa.device" properties + const char *alsa_card = pa_proplist_gets(i->proplist, "alsa.card"); + const char *alsa_device = pa_proplist_gets(i->proplist, "alsa.device"); + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.card is %s\n", alsa_card); + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()], alsa.device is %s\n", alsa_device); + + // Check if both properties are available and alsa.device is a digit + if (alsa_card && alsa_device && isdigit((unsigned char)alsa_device[0])) { + // Construct the ALSA device string + char alsa_device_string[128]; + snprintf(alsa_device_string, sizeof(alsa_device_string), "hw:%s,%s", alsa_card, alsa_device); + + // Store the ALSA device string in shared data + shared_data_2.alsa_id = strdup(alsa_device_string); + if (!shared_data_2.alsa_id) { + fprintf(stderr, "Failed to allocate memory for ALSA device id.\n"); + } + } else { + shared_data_2.alsa_id = NULL; + //fprintf(stderr, " - ALSA properties not found or invalid for sink.\n"); + } + + //fprintf(stderr, "[DEBUG, get_alsa_id_cb()] alsa ID is %s\n", shared_data_2.alsa_id); + + // Signal the main loop to unblock the iterate function + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the ALSA device string for a given PulseAudio sink name. + * + * This function initializes PulseAudio (if not already initialized), then queries PulseAudio for the + * information of a specific sink by its name. It waits for the retrieval operation to complete and then + * returns the constructed ALSA device string based on the "alsa.card" and "alsa.device" properties of + * the sink. + * + * @param sink_name The name of the PulseAudio sink. + * @return ALSA device string in the format "hw:," or NULL if not found or on error. + */ +char* get_alsa_output_id(const char *sink_name) { + pa_operation *op; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_alsa_output_id()] PulseAudio initialization failed.\n"); + return NULL; + } + } + + // Make a copy of the sink name to ensure it remains valid + char *sink_name_copy = strdup(sink_name); + if (!sink_name_copy) { + fprintf(stderr, "[get_alsa_output_id()] Failed to allocate memory for sink name.\n"); + return NULL; + } + + // Query PulseAudio for the information of the specified sink + //fprintf(stderr,"[DEBUG, get_alsa_id()] sink name is: %s\n", sink_name_copy); + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name_copy, get_alsa_output_id_cb, sink_name_copy); + + // Wait for the PulseAudio operation to complete + iterate(op); + + // Return the ALSA device string retrieved by the callback function + return shared_data_2.alsa_id; +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio source by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio source + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio source information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio source. + * @param source_info The PulseAudio source information structure. + * @return The sample rate of the source in Hz on success, or -1 on error. + */ +int get_input_sample_rate(const char *alsa_id, pa_source_info *source_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + // Output debug information to stderr + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + // If alsa_id is NULL, return the PulseAudio rate + if (!alsa_id && source_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", source_info->sample_spec.rate); + return source_info->sample_spec.rate; + } + + // Validate parameters + if (!alsa_id || !source_info) { + //fprintf(stderr, "Invalid parameters provided to get_input_sample_rate.\n"); + return -1; + } + + // Attempt to open the ALSA device + //fprintf(stderr, "[DEBUG, get_input_sample_rate()] Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return source_info->sample_spec.rate; + } + + // ALSA device successfully opened + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + + // Initialize hardware parameters + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, source_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Sample rate successfully obtained + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Retrieves the sample rate of the specified PulseAudio sink by ALSA identifier. + * + * Attempts to open the ALSA device corresponding to the given PulseAudio sink + * to query the sample rate. If the ALSA query fails, the function falls back to + * returning the sample rate as reported by the PulseAudio sink information. + * + * @param alsa_id The ALSA device identifier corresponding to the PulseAudio sink. + * @param sink_info The PulseAudio sink information structure. + * @return The sample rate of the sink in Hz on success, or -1 on error. + */ +int get_output_sample_rate(const char *alsa_id, const pa_sink_info *sink_info) { + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + unsigned int sample_rate = 0; // Default to a known value + int err; + + //fprintf(stderr, "[DEBUG, get_output_sample_rate()] called with alsa_id: '%s'\n", alsa_id); + + if (!alsa_id && sink_info) { + //fprintf(stderr, "alsa_id is NULL, returning PulseAudio sample rate: %u Hz\n", sink_info->sample_spec.rate); + return sink_info->sample_spec.rate; + } + + if (!alsa_id || !sink_info) { + //fprintf(stderr, "Invalid parameters provided to get_output_sample_rate.\n"); + return -1; + } + + //fprintf(stderr, "Attempting to open ALSA device: '%s'\n", alsa_id); + + if ((err = snd_pcm_open(&handle, alsa_id, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + fprintf(stderr, "Unable to open PCM device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + return sink_info->sample_spec.rate; + } + + //fprintf(stderr, "ALSA device: '%s' successfully opened.\n", alsa_id); + snd_pcm_hw_params_alloca(¶ms); + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the access type for the hardware parameters + if ((err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + fprintf(stderr, "Cannot set access type: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the sample format for the hardware parameters + if ((err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE)) < 0) { + fprintf(stderr, "Cannot set sample format: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Set the channel count for the hardware parameters + if ((err = snd_pcm_hw_params_set_channels(handle, params, sink_info->sample_spec.channels)) < 0) { + fprintf(stderr, "Cannot set channel count: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Apply the hardware parameters to the device + if ((err = snd_pcm_hw_params(handle, params)) < 0) { + fprintf(stderr, "Cannot set hardware parameters: %s\n", snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + // Now try to get the sample rate + if ((err = snd_pcm_hw_params_get_rate(params, &sample_rate, 0)) < 0) { + fprintf(stderr, "Error getting sample rate for device: '%s', error: %s\n", alsa_id, snd_strerror(err)); + snd_pcm_close(handle); + return -1; + } + + //fprintf(stderr, "Sample rate for ALSA device: '%s' is %u Hz\n", alsa_id, sample_rate); + snd_pcm_close(handle); + return sample_rate; // Successfully obtained sample rate +} + + +/** + * @brief Callback function for retrieving source information to get ports. + * + * This function is called by the PulseAudio context as a callback during the + * operation initiated by `pa_context_get_source_info_list()`. It processes + * each `pa_source_info` structure provided by PulseAudio, storing the relevant + * data (name and description) of each source port in a `pa_source_info_list`. + * The function also handles the end-of-list (EOL) signal from PulseAudio to + * mark completion of the data retrieval process. + * + * @param c The PulseAudio context. + * @param i The source information structure provided by PulseAudio. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + info_list->done = 1; + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + info_list->ports = realloc(info_list->ports, (info_list->num_ports + 1) * sizeof(pa_port_info)); + pa_port_info *port = &info_list->ports[info_list->num_ports]; + port->name = strdup(i->name); + port->description = strdup(i->description); + port->is_active = 0; // Will be set in the active port callback + + info_list->num_ports++; +} + +/** + * @brief Callback function for retrieving active source port information. + * + * This function is a callback for `pa_context_get_source_info_by_name()`. It is + * used to determine which of the previously listed source ports is currently active. + * It updates the `is_active` flag in the corresponding `pa_port_info` structure + * within the `pa_source_info_list` if a match is found with the active port name. + * + * @param c The PulseAudio context. + * @param i The source information structure, including the active port details. + * @param eol End-of-list flag. A positive value indicates the end of data from PulseAudio. + * @param userdata A pointer to user-provided data, expected to be of type `pa_source_info_list`. + */ +void get_source_port_info_cb2(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + pa_source_info_list *info_list = (pa_source_info_list *)userdata; + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->active_port) { + for (int j = 0; j < info_list->num_ports; ++j) { + if (strcmp(info_list->ports[j].name, i->active_port->name) == 0) { + info_list->ports[j].is_active = 1; + break; + } + } + } +} + +/** + * @brief Retrieves a list of source port information from PulseAudio. + * + * This function queries PulseAudio for the list of available source ports + * (such as microphone inputs, line-ins, etc.) and retrieves detailed information + * for each source. It initializes PulseAudio if not already initialized, then + * allocates and populates a `pa_source_info_list` structure with the source port + * information. Each entry in the list contains details about a specific source port. + * + * @note The function attempts to initialize PulseAudio if it is not already initialized. + * + * @return A pointer to a `pa_source_info_list` structure containing the list of source + * ports and their information. Returns NULL if PulseAudio cannot be initialized, + * if memory allocation fails, or if the query to PulseAudio fails. + */ +pa_source_info_list* get_source_port_info() { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_source_port_info()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + pa_source_info_list* info_list = malloc(sizeof(pa_source_info_list)); + if (!info_list) { + // Handle malloc failure + return NULL; + } + memset(info_list, 0, sizeof(pa_source_info_list)); + + // Call the function to get the list of sources + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_source_port_info_cb, info_list); + iterate(op); + + // Now iterate over the collected sources and get detailed info for each one + for (int i = 0; i < info_list->num_ports; ++i) { + op = pa_context_get_source_info_by_name(shared_data_1.context, info_list->ports[i].name, get_source_port_info_cb2, info_list); + + iterate(op); + } + + // The info_list now contains all the ports and their active status + return info_list; +} + + +/** + * @brief Retrieves the volume of a given channel from a PulseAudio sink. + * + * This function takes a pointer to a pa_sink_info structure and a channel index + * and returns the volume of that channel. The volume is given as a pa_volume_t, + * which is an unsigned 32-bit integer. The function checks if the channel index + * is within the valid range for the sink. + * + * @param sink_info A pointer to a pa_sink_info structure containing the sink details. + * @param channel_index The index of the channel for which to retrieve the volume. + * @return The volume of the specified channel as a pa_volume_t, or PA_VOLUME_INVALID on error. + */ +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, unsigned int channel_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_channel_volume(): failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + // Check if the sink_info is NULL + + if (sink_info == NULL) { + return PA_VOLUME_INVALID; // Return invalid volume if sink_info is NULL + } + + // Check if the provided channel index is valid + if (channel_index >= sink_info->channel_map.channels) { + return PA_VOLUME_INVALID; // Return invalid volume if the channel_index is out of range + } + + // Retrieve the volume of the given channel + return sink_info->volume.values[channel_index]; +} + + + +/** + * @brief Callback function for processing each available audio input device (source) found. + * + * This function is called by the PulseAudio context as part of the operation initiated by + * `get_available_input_devices`. It is invoked for each source found, and is responsible for + * storing the details of each source into a dynamically allocated array. The function handles + * memory allocation for the array of `pa_source_info` structures, as well as for the strings + * within them. It also handles error conditions and signals the mainloop to terminate the wait + * when the end of the source list is reached or an error occurs. + * + * @param c The PulseAudio context. + * @param i The `pa_source_info` structure containing details about the current source. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata User data pointer provided during the context operation setup; unused in this callback. + */ +static void get_available_input_devices_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void)c; // Unused parameter + (void)userdata; // Unused parameter + + // Error or end of list + if (eol < 0) { + fprintf(stderr, "Error occurred while getting source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Sentinel value + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (shared_data_sources.count >= shared_data_sources.allocated) { + size_t new_alloc = shared_data_sources.allocated + 8; + void *temp = realloc(shared_data_sources.sources, new_alloc * sizeof(pa_source_info *)); + if (!temp) { + fprintf(stderr, "Out of memory when reallocating sources array.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + shared_data_sources.sources = temp; + shared_data_sources.allocated = new_alloc; + } + + shared_data_sources.sources[shared_data_sources.count] = malloc(sizeof(pa_source_info)); + if (!shared_data_sources.sources[shared_data_sources.count]) { + fprintf(stderr, "Out of memory when allocating source info.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + *(shared_data_sources.sources[shared_data_sources.count]) = *i; + + if (i->name) { + shared_data_sources.sources[shared_data_sources.count]->name = strdup(i->name); + if (!shared_data_sources.sources[shared_data_sources.count]->name) { + fprintf(stderr, "Out of memory when duplicating source name.\n"); + } + } + if (i->description) { + shared_data_sources.sources[shared_data_sources.count]->description = strdup(i->description); + if (!shared_data_sources.sources[shared_data_sources.count]->description) { + fprintf(stderr, "Out of memory when duplicating source description.\n"); + } + } + + shared_data_sources.count++; +} + + +/** + * @brief Retrieves an array of available audio input devices (sources). + * + * This function queries the PulseAudio server for a list of all audio input devices + * currently available. It ensures that PulseAudio is initialized before making the query + * and locks the main loop to provide thread safety during the operation. + * + * Each call to this function should be followed by a call to `delete_input_devices` + * to free the allocated memory for the returned array of `pa_source_info` pointers. + * + * @note The array is terminated with a NULL pointer as the last element. + * + * @return On success, a pointer to an array of `pa_source_info` pointers, each representing + * an audio input device. On failure, or if PulseAudio is not initialized, NULL is returned. + */ +pa_source_info **get_available_input_devices() { + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_available_input_devices()] Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + // Initialize the data structure for storing the sources + shared_data_sources.sources = NULL; + shared_data_sources.count = 0; + shared_data_sources.allocated = 0; + + // Start the operation to get available input devices + pa_operation *op = pa_context_get_source_info_list(shared_data_1.context, get_available_input_devices_cb, NULL); + if (op) { + // iterate handles locking, waiting, and cleanup + iterate(op); + } else { + fprintf(stderr, "Failed to create the operation to get source info.\n"); + return NULL; + } + + // Allocate one extra pointer to NULL at the end as a sentinel + shared_data_sources.sources = realloc(shared_data_sources.sources, (shared_data_sources.count + 1) * sizeof(pa_source_info *)); + if (shared_data_sources.sources) { + shared_data_sources.sources[shared_data_sources.count] = NULL; // Set the sentinel value + } else { + fprintf(stderr, "Out of memory while allocating sources array.\n"); + } + + return shared_data_sources.sources; +} + + +/** + * @brief Callback function used to count the available audio input devices (sources). + * + * This function is called for each audio input device found by PulseAudio when querying + * for the list of sources. The function increments the device_count for each device. + * When the list is exhausted or there's an error, it signals the mainloop to continue. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the current audio input device information. + * @param eol End-Of-List indicator. If positive, indicates the end of the list; if negative, indicates an error. + * @param userdata Pointer to the user data, which in this case is expected to be a pointer to the device_count. + */ +static void get_input_device_count_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; // Suppress unused parameter warning + (void) i; // Suppress unused parameter warning + + uint32_t *device_count = (uint32_t *) userdata; + + if (eol < 0) { + // Handle error + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + if (eol > 0) { + // End of list + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + ++(*device_count); +} + + +/** + * @brief Retrieve the count of audio input devices in the system. + * + * This function queries PulseAudio to get a count of all available audio input devices (sources). + * If PulseAudio is not initialized, the function attempts to initialize it. If the initialization + * fails or there's an error in fetching the device count, it returns UINT32_MAX. + * + * @return Count of audio input devices or UINT32_MAX on error. + */ +uint32_t get_input_device_count(void) { + uint32_t device_count = 0; // Initialize device count to zero + pa_operation *count_op = NULL; + + // Check if PulseAudio is initialized + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_count()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails + } + } + + // Check if context is valid after initialization attempt + if (!shared_data_1.context) { + fprintf(stderr, "Context is NULL in get_input_device_count.\n"); + return UINT32_MAX; + } + + // Query PulseAudio for the list of audio input devices (sources) + count_op = pa_context_get_source_info_list(shared_data_1.context, get_input_device_count_cb, &device_count); + + // Wait for the PulseAudio operation to complete + iterate(count_op); + + return device_count; // Return the total count of input devices +} + + +/** + * @brief Get the channel names for a specific sink identified by its name. + * + * This function retrieves the channel names for a given sink using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the sink. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param sink_name The name of the sink whose channel names are to be retrieved. + * @param num_channels Number of channels of the sink. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the sink is not found or in case of an error. + */ +char** get_output_channel_names(const char *pulse_id, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_id) { + return NULL; // Return NULL if the sink name is not provided + } + + // Retrieve the sink information for the specified sink name + pa_sink_info *device_info = get_output_device_by_name(pulse_id); + + // Check if the sink was found + if (!device_info) { + return NULL; // Return NULL if the sink is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + // Free all previously allocated names and the array itself + while (i--) free(channel_names[i]); + free(channel_names); + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the sink_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); // Corrected free statement + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Get the channel names for a specific source identified by its name. + * + * This function retrieves the channel names for a given source using its unique name. + * It allocates an array of strings where each entry corresponds to a channel name + * of the source. The caller is responsible for freeing the memory allocated for the + * array of channel names and the strings themselves. + * + * @param source_name The name of the source whose channel names are to be retrieved. + * @param num_channels Number of channels of the source. + * @return A pointer to an array of strings containing the channel names, or NULL if + * the source is not found or in case of an error. + */ +char** get_input_channel_names(const char *pulse_code, int num_channels) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_channel_names()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Validate input parameters + if (!pulse_code) { + return NULL; // Return NULL if the source name is not provided + } + + // Retrieve the source information for the specified source name + pa_source_info *device_info = get_input_device_by_name(pulse_code); + + // Check if the source was found + if (!device_info) { + return NULL; // Return NULL if the source is not found or in case of an error + } + + char **channel_names = calloc(num_channels, sizeof(char*)); + if (!channel_names) { + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + + // Copy the channel names into the array + for (int i = 0; i < num_channels; ++i) { + channel_names[i] = strdup(pa_channel_position_to_pretty_string(device_info->channel_map.map[i])); + if (!channel_names[i]) { + // Handle allocation failure for a channel name + while (i--) free(channel_names[i]); // Free all previously allocated names + free(channel_names); // Free the array itself + free((char *)device_info->description); // Free the description if allocated + free(device_info); // Free the device_info structure + return NULL; // Return NULL if memory allocation fails + } + } + + // Free the source_info structure and its description field + if (device_info->description) { + free((char *)device_info->description); + } + free(device_info); + + // Return the array of channel names + return channel_names; +} + +/** + * @brief Retrieve a source (input device) information by its name. + * + * This function searches for an audio source with the given name and returns its information + * if found. The caller is responsible for freeing the memory allocated for the source info + * and its description. + * + * @param source_name The name of the source to be retrieved. + * @return A pointer to a pa_source_info structure containing the source information, + * or NULL if the source is not found or in case of an error. + */ +pa_source_info *get_input_device_by_name(const char *source_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!source_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available input devices (sources) + pa_source_info **available_sources = get_available_input_devices(); + if (!available_sources) { + return NULL; // Handle error in retrieving sources + } + + pa_source_info *input_device_info = NULL; + + // Iterate over the list of sources to find the one with the matching name + for (int i = 0; available_sources[i] != NULL; ++i) { + if (strcmp(available_sources[i]->name, source_name) == 0) { + // Found the matching source, make a copy of the source_info structure + input_device_info = malloc(sizeof(pa_source_info)); + if (input_device_info) { + memcpy(input_device_info, available_sources[i], sizeof(pa_source_info)); + + // If the source has a description, also copy that string + if (available_sources[i]->description) { + input_device_info->description = strdup(available_sources[i]->description); + } + } + break; // Exit the loop after finding the matching source + } + } + + // Clean up the source information now that we're done with it + // Assuming there's a function to delete input devices similar to delete_output_devices + delete_input_devices(available_sources); + + return input_device_info; // Return the found source or NULL if not found +} + +/** + * @brief Retrieve a copy of the sink information for a given sink name. + * + * This function searches through the available output devices and returns a copy of the + * pa_sink_info structure for the sink that matches the provided name. It uses the + * get_available_output_devices function to obtain the list of all sinks and then + * iterates through them to find the sink with the given name. + * + * @param sink_name The name of the sink to search for. Must not be NULL. + * @return A pointer to a newly allocated pa_sink_info structure containing the sink + * information, or NULL if the sink is not found or if an error occurs. The + * caller is responsible for freeing the returned structure and its description + * field (if not NULL) when no longer needed. + * + * @note The function allocates memory for the returned pa_sink_info structure and its + * description field. It is the responsibility of the caller to free this memory + * using free(). If the sink has other dynamically allocated fields, these must + * also be freed by the caller. + * + * + * Usage Example: + * @code + * pa_sink_info *sink_info = get_sink_by_name("alsa_output.pci-0000_00_1b.0.analog-stereo"); + * if (sink_info) { + * // Use the sink information + * ... + * // Free the memory allocated for the description + * if (sink_info->description) { + * free(sink_info->description); + * } + * // Free the memory allocated for the sink_info structure + * free(sink_info); + * } + * @endcode + */ +pa_sink_info *get_output_device_by_name(const char *sink_name) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_name()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!sink_name) { + return NULL; // Handle null pointer argument + } + + // Retrieve the list of available output devices (sinks) + pa_sink_info **available_sinks = get_available_output_devices(); + if (!available_sinks) { + return NULL; // Handle error in retrieving sinks + } + + pa_sink_info *output_device_to_return = NULL; + + // Iterate over the list of sinks to find the one with the matching name + for (int i = 0; available_sinks[i] != NULL; ++i) { + if (strcmp(available_sinks[i]->name, sink_name) == 0) { + // Found the matching output_device, make a copy of the sink_info structure + output_device_to_return = malloc(sizeof(pa_sink_info)); + if (output_device_to_return) { + memcpy(output_device_to_return, available_sinks[i], sizeof(pa_sink_info)); + + // If the sink has a description, also copy that string + if (available_sinks[i]->description) { + output_device_to_return->description = strdup(available_sinks[i]->description); + } + } + break; // Exit the loop after finding the matching sink + } + } + + // Clean up the sink information now that we're done with it + delete_output_devices(available_sinks); + + return output_device_to_return; // Return the found sink or NULL if not found +} + +/** + * @brief Callback for handling the result of the sink information fetch operation. + * + * This callback is called by the PulseAudio library when sink information is ready to be + * retrieved, or when the iteration over sinks has finished. The function will copy the sink + * information to the provided user data structure if available, or signal the main loop to + * continue if the end of the list is reached or if an error occurs. + * + * @param c The PulseAudio context. + * @param i The sink information structure provided by PulseAudio. + * @param eol End of list indicator. If positive, indicates the end of the list; if negative, + * indicates failure to retrieve sink information. + * @param userdata User data pointer provided to the pa_context_get_sink_info_by_index function, + * expected to be a pointer to a pa_sink_info structure. + */ +static void get_output_device_by_index_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + pa_sink_info *sink_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the sink + // Signal main loop to continue in case of end of list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the sink information to the allocated structure + *sink_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + sink_info->name = strdup(i->name); + } + if (i->description) { + sink_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieve the sink information for a given output device by its index. + * + * This function initiates an asynchronous operation to fetch the sink information + * for the specified device index. The operation is handled synchronously within this + * function using a threaded mainloop to wait for completion. + * + * @param index The index of the sink for which information is to be retrieved. + * @param sink_info A pointer to a pa_sink_info structure where the sink information will be stored. + * @return int Returns 1 on success or 0 if the operation fails. + * + */ +pa_sink_info* get_output_device_by_index(uint32_t index) { + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_device_by_index] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for sink_info + pa_sink_info *sink_info = malloc(sizeof(pa_sink_info)); + if (!sink_info) { + fprintf(stderr, "Memory allocation for sink_info failed.\n"); + return NULL; + } + + // Start the operation to get the sink information + pa_operation *op = pa_context_get_sink_info_by_index(shared_data_1.context, index, get_output_device_by_index_cb, sink_info); + iterate(op); + + // Check if the operation was successful + if (sink_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(sink_info); + return NULL; + } + + return sink_info; // Return the allocated sink_info +} + +/** + * @brief Callback for retrieving information about a specific audio input source by index. + * + * This function is the callback used by `pa_context_get_source_info_by_index` within + * the `get_input_device_by_index` function to handle the response from PulseAudio. + * It is called by the PulseAudio main loop when the source information is available or + * when an error or end-of-list condition is signaled. + * + * @param c Pointer to the PulseAudio context, not used in this callback. + * @param i Pointer to the source information structure containing the details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, negative + * if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a pointer + * to a `pa_source_info` structure where the source information will be stored. + * + */ +static void get_input_device_by_index_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + pa_source_info *source_info = userdata; + + if (eol < 0 || eol > 0 || !i) { + // Either an error occurred, or we've reached the end of the list without finding the source + if (eol != 0) { + // Signal main loop to continue + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + } + return; + } + + // Copy the source information to the allocated structure + *source_info = *i; // Shallow copy first + + // Now duplicate any strings + if (i->name) { + source_info->name = strdup(i->name); + } + if (i->description) { + source_info->description = strdup(i->description); + } + + // Signal the main loop that the data is ready + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the information of an audio input device (source) by its index. + * + * This function attempts to allocate memory for a `pa_source_info` structure and retrieve + * the information for the specified source index using PulseAudio's API. It blocks until + * the asynchronous operation to fetch the source information is complete or an error occurs. + * + * @param index The index of the input device (source) as recognized by PulseAudio. + * @return A pointer to the allocated `pa_source_info` structure containing the source + * information, or NULL if the operation failed or the specified index was not valid. + * The caller is responsible for freeing the allocated structure and any associated + * strings when they are no longer needed. + * + */ +pa_source_info* get_input_device_by_index(uint32_t index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_by_index()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + if (!shared_data_1.context || !shared_data_1.mainloop) { + fprintf(stderr, "Invalid shared data context or mainloop.\n"); + return NULL; + } + + // Allocate memory for source_info + pa_source_info *source_info = malloc(sizeof(pa_source_info)); + if (!source_info) { + fprintf(stderr, "Memory allocation for source_info failed.\n"); + return NULL; + } + + // Start the operation to get the source information + pa_operation *op = pa_context_get_source_info_by_index(shared_data_1.context, index, get_input_device_by_index_cb, source_info); + iterate(op); + + // Check if the operation was successful + if (source_info->name == NULL) { + // The operation was not successful, free the allocated memory. + free(source_info); + return NULL; + } + + return source_info; // Return the allocated source_info +} + +/** + * @brief Callback function for getting the default output device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_output_cb(pa_context *c, const pa_server_info *i, void *userdata) { + //fprintf(stderr, "[DEBUG, get_default_output()] Callback reached.\n"); + + (void)c; // Unused parameter + + char **default_sink_name = (char**)userdata; + + // Always signal the mainloop to unblock the iterate function, even if i is NULL + if (!i) { + fprintf(stderr, "Failed to get default sink information.\n"); + } else if (i->default_sink_name) { + // Duplicate the name string to our output variable + *default_sink_name = strdup(i->default_sink_name); + } + + // Signal the mainloop to unblock the iterate function, regardless of the outcome + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + + +/** + * @brief Retrieves the name of the default sink (output device) in the system. + * + * This function checks if PulseAudio is initialized and if not, tries to initialize it. + * Then, it queries the PulseAudio server for the default output device and waits for + * the operation to complete. + * + * @param mainloop A pointer to the mainloop structure. + * @param context A pointer to the PulseAudio context. + * @return A dynamically allocated string containing the default sink name, or NULL on error. + * The caller is responsible for freeing this string. + */ +char* get_default_output(pa_context *context) { + + //fprintf(stderr,"[DEBUG, get_default_output()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized()) { + if (!initialize_pulse()) { + fprintf(stderr, "Failed to initialize PulseAudio.\n"); + return NULL; + } + } + + char *default_sink_name = NULL; + + // Start the operation to get the default sink + pa_operation *op = pa_context_get_server_info(context, get_default_output_cb, &default_sink_name); + + if (op) { + // Wait for the operation to complete using the iterate function + iterate(op); // This function should handle the waiting and signaling + // pa_operation_unref(op); is called inside iterate, no need to call here + } else { + fprintf(stderr, "Failed to create the operation to get server info.\n"); + } + + return default_sink_name; // Caller must free this string +} +/** + * @brief Callback function for getting the default input device from the PulseAudio server. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the server information structure. + * @param userdata The user data passed to the callback function, which is a pointer to a char*. + */ +static void get_default_input_cb(pa_context *c, const pa_server_info *i, void *userdata) { + (void)c; // Unused parameter + + char **default_source_name = (char**)userdata; + + //fprintf(stderr, "[DEBUG, get_default_input_cb()] callback reached.\n"); + + if (!i) { + fprintf(stderr, "Failed to get default source information.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (i->default_source_name) { + // Duplicate the name string to our output variable + *default_source_name = strdup(i->default_source_name); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); // Signal to stop the loop + } +} + + +/** + * @brief Retrieves the name of the default source (input device) in the system. + * + * This function queries the PulseAudio server for all available input devices + * and iterates through them to find the one that is in the RUNNING state, + * which typically indicates that it is the default source being used by the system. + * The name of the default source is then returned. + * + * @note The caller is responsible for freeing the memory allocated for the + * returned source name using the standard free() function to avoid memory leaks. + * + * @return A pointer to a dynamically allocated string containing the name of + * the default source. If no active default source is found or in case of an error, + * NULL is returned. + */ +char* get_default_input(pa_context *context) { + + //fprintf(stderr, "[DEBUG, get_default_input()] Function reached.\n"); + + // Check if PulseAudio is initialized, and if not, initialize it + if (!is_pulse_initialized() && !initialize_pulse()) { + fprintf(stderr, "[get_default_onput()] Failed to initialize PulseAudio.\n"); + return NULL; + } + + char *default_source_name = NULL; + + // Start the operation to get the default source + pa_operation *op = pa_context_get_server_info(context, get_default_input_cb, &default_source_name); + iterate(op); + + return default_source_name; // Caller must free this string +} + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +void get_profiles_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + (void) userdata; + + if (eol < 0) { + fprintf(stderr, "Failed to fetch profiles.\n"); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + if (eol > 0) { + // All profiles have been fetched, the operation is complete + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Reallocate memory for profiles array to add new profiles + shared_data_4.profiles = realloc(shared_data_4.profiles, sizeof(pa_card_profile_info2) * (shared_data_4.num_profiles + i->n_profiles)); + + // Now copy the profiles from the PulseAudio provided array + for (unsigned int j = 0; j < i->n_profiles; ++j) { + shared_data_4.profiles[shared_data_4.num_profiles + j] = i->profiles[j]; + } + + // Update the number of profiles fetched + shared_data_4.num_profiles += i->n_profiles; +} + + + +/** + * @brief Fetches all profiles associated with a given sound card. + * + * This function queries the PulseAudio server for all profiles associated with the sound card + * specified by the card_index. It blocks until all profiles are fetched or an error occurs. + * + * @param pa_ctx A pointer to the initialized pa_context representing the connection to the PulseAudio server. + * @param card_index The index of the sound card for which to fetch profiles. + * @param num_profiles A pointer to an integer where the number of fetched profiles will be stored. + * @return A pointer to an array of pa_card_profile_info2 structures containing the profile info. + * This pointer must be freed by the caller. NULL is returned if an error occurs. + */ +pa_card_profile_info *get_profiles(pa_context *pa_ctx, uint32_t card_index) { + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_profiles()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + // Reset the static global variable before use + free(shared_data_4.profiles); + shared_data_4.profiles = NULL; + shared_data_4.num_profiles = 0; + + // Start the operation to fetch the profiles + pa_operation *op = pa_context_get_card_info_by_index(pa_ctx, card_index, get_profiles_cb, NULL); + + // Wait for the operation to complete + iterate(op); + + return shared_data_4.profiles; // Return the static global profiles array +} + +/** + * Callback function for retrieving the mute status of a sink. + * + * This callback is provided to the PulseAudio context as part of a request + * to obtain information about a particular sink. It will be called by the + * PulseAudio main loop when the sink information is available. The end of list + * (eol) parameter indicates whether the data received is the last in the list. + * + * @param c A pointer to the PulseAudio context. + * @param i A pointer to the sink information structure. + * @param eol An end-of-list flag that is positive if there is no more data to process. + * @param userdata A pointer to user data, expected to be a pointer to an integer that + * will be set to the mute status of the sink. + * + * @note The function sets the integer pointed to by `userdata` to the mute state + * of the sink. The mute state is non-zero when the sink is muted and zero + * when it is not muted. This function is not intended to be called directly + * by the user but as a callback from the PulseAudio API when + * pa_context_get_sink_info_by_name() is called. + */ +static void get_muted_output_status_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_output_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_output_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the sink information is valid, set is_muted to the sink's mute state + if (i) { + *is_muted = i->mute; + } +} + + + +/** + * Queries the mute status of a specified output sink. + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the sink specified by `sink_name`. It requires a valid `pulseaudio_manager` + * instance that has been previously initialized with a mainloop and context. + * The function blocks until the operation is complete or an error occurs. + * + * @param self A pointer to the initialized `pulseaudio_manager` instance. + * @param sink_name The name of the sink whose mute status is being queried. + * + * @return Returns 1 if the sink is muted, 0 if not muted, and -1 if an error + * occurred or the sink was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + * @note The function uses `iterate` to block and process the mainloop until + * the operation is complete. It is assumed that `iterate` and + * `get_muted_output_status_cb` are implemented elsewhere and are + * responsible for iterating the mainloop and handling the callback + * from the sink information operation, respectively. + */ +int get_muted_output_status(const char *sink_name) { + + //fprintf(stderr,"[DEBUG, get_muted_output_status()] sink_name is %s\n", sink_name); + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_muted_output_status()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!shared_data_1.mainloop || !shared_data_1.context || !sink_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or sink not found + + // Start a PulseAudio operation to get information about the sink + op = pa_context_get_sink_info_by_name(shared_data_1.context, sink_name, get_muted_output_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the sink was not found or another error occurred + return is_muted; +} + +/** + * @brief Callback function for retrieving the mute status of an audio input source. + * + * This callback is invoked by the PulseAudio main loop when the source information + * becomes available. It is used as part of an asynchronous operation initiated by + * `get_muted_input_status` to obtain the mute status of a specified audio source. + * The `eol` parameter indicates if the data received is the last in the list or if + * an error has occurred during the iteration. + * + * @param c Pointer to the PulseAudio context. + * @param i Pointer to the source information structure containing details of the source. + * @param eol End-of-list flag that is positive if there is no more data to process, + * negative if an error occurred during the iteration. + * @param userdata User data provided when initiating the operation; expected to be a + * pointer to an integer that will be set to the mute status of the source. + * + */ +static void get_muted_input_status_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: inside callback.\n"); + + // If eol is set to a positive number, you're at the end of the list + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + //fprintf(stderr, "[DEBUG, get_muted_input_status_cb]: leaving callback.\n"); + return; + } + + // If eol is negative, an error occurred + if (eol < 0) { + int err = pa_context_errno(c); // Retrieve the error number from the context + fprintf(stderr, "[ERROR, get_muted_input_status_cb]: Error occurred during iteration - %s\n", pa_strerror(err)); + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + // Cast the userdata to a boolean pointer + int *is_muted = (int *)userdata; + + // If the source information is valid, set is_muted to the source's mute state + if (i) { + *is_muted = i->mute; + } +} + + +/** + * @brief Queries the mute status of a specified audio input (source). + * + * This function initiates an asynchronous operation to retrieve the mute status + * of the source specified by `source_name`. It requires a valid PulseAudio mainloop + * and context to have been previously initialized and stored in shared_data_1. + * The function blocks until the operation is complete or an error occurs. + * + * @param source_name The name of the source whose mute status is being queried. + * This should be the exact name as recognized by PulseAudio. + * + * @return int Returns 1 if the source is muted, 0 if not muted, and -1 if an error + * occurred or the source was not found. In the case of an error, an + * appropriate message will be printed to standard error. + * + */ +int get_muted_input_status(const char *source_name) { + //fprintf(stderr,"[DEBUG, get_muted_input_status()] source_name is %s\n", source_name); + + if (!shared_data_1.mainloop || !shared_data_1.context || !source_name) { + fprintf(stderr, "Invalid arguments provided.\n"); + return -1; + } + + pa_operation *op = NULL; + int is_muted = -1; // Default to -1 in case of error or source not found + + // Start a PulseAudio operation to get information about the source + op = pa_context_get_source_info_by_name(shared_data_1.context, source_name, get_muted_input_status_cb, &is_muted); + iterate(op); + + // -1 will be returned if the source was not found or another error occurred + return is_muted; +} + + +/** + * @brief Callback function for handling sink information response. + * + * This function is called by the PulseAudio main loop when the information about a sink + * is available. It processes the sink information and stores the index of the sink in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the sink information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the sink index will be stored. + */ +static void get_output_device_index_by_code_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in sink_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Callback function for handling source information response. + * + * This function is called by the PulseAudio main loop when the information about a source + * is available. It processes the source information and stores the index of the source in the + * provided userdata pointer. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the source information structure. + * @param eol End-of-list flag indicating if there are more entries to process. + * @param userdata Pointer to user data where the source index will be stored. + */ +static void get_input_device_index_by_code_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + uint32_t *index_ptr = (uint32_t *) userdata; + + if (eol < 0) { + fprintf(stderr, "Error occurred in source_info_cb.\n"); + return; + } + + if (!eol && info) { + *index_ptr = info->index; + } + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); +} + +/** + * @brief Retrieves the index of an input device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an input device (source) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the source information once it's received. It waits for the completion of the operation + * and returns the index of the source. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the source whose index is to be retrieved. + * @return The index of the input device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_input_device_index_by_code(pa_context *context, const char *device_code) { + + // Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_source_info_by_name(context, device_code, get_input_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Retrieves the index of an output device based on its code (PulseAudio name). + * + * This function initiates an asynchronous operation to get information about an output device (sink) + * based on its PulseAudio name. The function requires a valid PulseAudio context and a callback to + * process the sink information once it's received. It waits for the completion of the operation + * and returns the index of the sink. + * + * @param context Pointer to the initialized PulseAudio context. + * @param device_code The pulseaudio code of the sink whose index is to be retrieved. + * @return The index of the output device if found, or UINT32_MAX if not found or in case of error. + */ +uint32_t get_output_device_index_by_code(pa_context *context, const char *device_code) { + + //Initializing the result variable. + uint32_t index = 0; + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "get_output_device_index_by_code()] Failed to initialize pulseaudio.\n"); + return UINT32_MAX; // Return error if initialization fails. + } + } + + if (!context || !device_code) { + fprintf(stderr, "Invalid arguments.\n"); + return UINT32_MAX; + } + + pa_operation *op = pa_context_get_sink_info_by_name(context, device_code, get_output_device_index_by_code_cb, &index); + iterate(op); + + return index; +} + +/** + * @brief Callback function used by get_sink_name_by_code to process information about each sink. + * + * This function is called by the PulseAudio context for each sink (output device). + * It compares the name of each sink with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the sink description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current sink being processed. + * @param eol End-of-list flag, non-zero if this is the last sink in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_output_name_by_code_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the sink description + } +} + +/** + * @brief Finds and returns the sink name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio sink (output device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sinks and processing each one using the sink_info_callback function. + * + * The function dynamically allocates memory for the sink description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the sink to search for. + * @return char* Dynamically allocated string containing the sink description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_output_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_output_name_by_code()] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sinks + pa_operation *op = pa_context_get_sink_info_list(pa_ctx, get_output_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated sink name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Callback function used by get_source_name_by_code to process information about each source. + * + * This function is called by the PulseAudio context for each source (input device). + * It compares the name of each source with a provided PulseAudio code. If a match is found, + * it dynamically allocates memory and copies the source description to the result. + * + * @param c The PulseAudio context. + * @param i Information about the current source being processed. + * @param eol End-of-list flag, non-zero if this is the last source in the list. + * @param userdata Pointer to a string (char pointer) where the result will be stored. + */ +void get_input_name_by_code_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + char **result = (char **)userdata; + + if (strcmp(i->name, *result) == 0) { + *result = strdup(i->description); // Dynamically allocate and copy the source description + } +} + + +/** + * @brief Finds and returns the source name (description) corresponding to a given PulseAudio code (name). + * + * This function searches for a PulseAudio source (input device) with a given code (name) + * and returns its description. It interacts directly with the PulseAudio server, querying + * the list of sources and processing each one using the get_source_name_by_code_cb function. + * + * The function dynamically allocates memory for the source description, which must be freed + * by the caller. + * + * @param pa_ctx A valid and connected PulseAudio context. + * @param code The PulseAudio code (name) of the source to search for. + * @return char* Dynamically allocated string containing the source description, or NULL if not found. + * The caller is responsible for freeing this memory. + * + */ +char* get_input_name_by_code(pa_context *pa_ctx, const char *code) { + if (pa_ctx == NULL || code == NULL) { + return NULL; + } + + // Check if PulseAudio is initialized. + if (!is_pulse_initialized()) { + // Attempt to initialize PulseAudio if it's not already initialized. + if (!initialize_pulse()) { + fprintf(stderr, "[get_input_name_by_code] Failed to initialize pulseaudio.\n"); + return NULL; // Return error if initialization fails. + } + } + + char *result = strdup(code); // Copy the code to result for the callback function + + // Querying the list of sources + pa_operation *op = pa_context_get_source_info_list(pa_ctx, get_input_name_by_code_cb, &result); + + if (op != NULL) iterate(op); + + if (result != NULL && strcmp(result, code) != 0) { + return result; // Return the dynamically allocated source name + } + + // If no matching device is found or if the result was not updated + free(result); + + return NULL; +} + +/** + * @brief Retrieves the global default playback sample rate from the PulseAudio configuration. + * + * This function reads the PulseAudio daemon configuration file to find the value of the + * `default-sample-rate` setting, which determines the default sample rate for playback streams. + * The function can optionally accept a custom path to a PulseAudio configuration file. If no + * custom path is provided, it defaults to using the standard PulseAudio configuration file + * located at '/etc/pulse/daemon.conf'. + * + * @param custom_config_path Optional path to a custom PulseAudio configuration file. If NULL, + * the function uses the default PulseAudio configuration file path. + * @return The default sample rate as an integer. Returns -1 if the function fails to open the + * configuration file or if the `default-sample-rate` setting is not found. + */ + +int get_pulseaudio_global_playback_rate(const char* custom_config_path) { + FILE* file = NULL; + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; + + char local_config_path[MAX_LINE_LENGTH]; + snprintf(local_config_path, sizeof(local_config_path), "%s/.config/pulse/daemon.conf", homedir); + + if (custom_config_path != NULL) { + file = fopen(custom_config_path, "r"); + } + + if (!file) { + file = fopen(local_config_path, "r"); + } + + if (!file) { + file = fopen(DAEMON_CONF, "r"); + } + + if (!file) { + perror("Failed to open PulseAudio configuration file"); + return -1; + } + + char line[MAX_LINE_LENGTH]; + int sample_rate = -1; + + while (fgets(line, sizeof(line), file)) { + char* p = line; + // Skip leading whitespace + while (*p && isspace((unsigned char)*p)) { + p++; + } + + // Skip comment lines + if (*p == ';' || *p == '#') { + continue; + } + + // Check if the line contains the required setting + if (strncmp(p, "default-sample-rate", 19) == 0) { + char* value_str = strchr(p, '='); + if (value_str) { + value_str++; + sample_rate = atoi(value_str); + break; + } + } + } + fclose(file); + + return sample_rate; +} + +/** + * @brief Callback function for handling sink information. + * + * This function is used as a callback to process information about a PulseAudio sink. + * It retrieves the mute state of a specific channel from the sink's volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the sink information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the sink. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of sink information. + */ +static void get_output_channel_mute_state_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given sink. + * + * This function checks if the specified channel in a given sink is muted. It utilizes + * the PulseAudio API to query sink information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param sink_index The index of the sink whose channel mute state is to be checked. + * @param channel_index The index of the channel within the sink to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the sink or channel + * indices are invalid. + */ +bool get_output_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index) { + + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified sink + pa_operation *op = pa_context_get_sink_info_by_index(context, sink_index, get_output_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Callback function for handling input device information. + * + * This function is used as a callback to process information about a PulseAudio input device. + * It retrieves the mute state of a specific channel from the input device volume information. + * + * @param c Pointer to the PulseAudio context. + * @param info Pointer to the structure containing the input device information. + * @param eol End-of-list flag. If non-zero, indicates no more data. + * @param userdata Pointer to user data, expected to be of type _shared_data_5. + * + * @details This function checks if the channel index specified in the user data (_shared_data_5) + * is within the range of available channels in the input device. If it is, the function then + * accesses the volume of the specified channel directly and determines if it is muted. + * The mute state is stored in the user data. + * + * @note The function returns immediately if the eol parameter is greater than zero, indicating + * the end of the list of input device information. + */ +static void get_input_channel_mute_state_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + _shared_data_5 *data = (_shared_data_5 *)userdata; + + // Ensure that the channel index is within the range of available channels + if (info && data->channel_index < info->volume.channels) { + // Directly accessing the volume of the specified channel + data->mute_state = (info->volume.values[data->channel_index] == PA_VOLUME_MUTED); + } +} + +/** + * @brief Retrieves the mute state of a specific channel of a given input device. + * + * This function checks if the specified channel in a given input device is muted. It utilizes + * the PulseAudio API to query input device information and then checks the volume level of + * the specified channel, considering it muted if the volume is set to PA_VOLUME_MUTED. + * + * @param context Pointer to the PulseAudio context. It should be a valid and initialized context. + * @param mainloop Pointer to a PulseAudio threaded mainloop. This mainloop should be running for the operation. + * @param source_index The index of the input device whose channel mute state is to be checked. + * @param channel_index The index of the channel within the input device to check for mute state. + * + * @return Returns true if the specified channel is muted, false if not muted or in case of an error. + * + * @note The function will return false if the context or mainloop pointers are null, or if the input device or channel + * indices are invalid. + */ +bool get_input_channel_mute_state(pa_context *context, pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index) { + if (!context || !mainloop) { + fprintf(stderr, "Invalid PulseAudio context or mainloop.\n"); + return false; + } + + _shared_data_5 data = {channel_index, false}; + // Requesting information about the specified input device + pa_operation *op = pa_context_get_source_info_by_index(context, source_index, get_input_channel_mute_state_cb, &data); + iterate(op); + + return data.mute_state; +} + +/** + * @brief Frees memory allocated for an output_stream_list. + * + * This function is responsible for properly freeing the memory allocated for an + * output_stream_list and its associated array of output_stream_info structures. + * It frees the array of output_stream_info and then the output_stream_list structure itself. + + * + * @param list A pointer to the output_stream_list to be freed. If this pointer is NULL, + * the function does nothing. + */ +void output_streams_cleanup(output_stream_list *list) { + if (list != NULL) { + // Iterate over each output_stream_info and free its dynamically allocated fields + for (uint32_t i = 0; i < list->num_inputs; ++i) { + free(list->inputs[i].name); + free(list->inputs[i].driver); + + if (list->inputs[i].format != NULL) { + pa_format_info_free(list->inputs[i].format); + } + + if (list->inputs[i].proplist != NULL) { + pa_proplist_free(list->inputs[i].proplist); + } + } + + // Free the array of output_stream_info + free(list->inputs); + + // Free the output_stream_list structure itself + free(list); + } +} + +/** + * @brief Frees memory allocated for an input_stream_list. + * + * This function is responsible for properly freeing the memory allocated for an + * input_stream_list and its associated array of input_stream_info structures. + * It frees the array of input_stream_info and then the input_stream_list structure itself. + * + * Usage of this function is essential after you're done using an input_stream_list + * to avoid memory leaks. This function safely handles NULL pointers, so it can be + * called with input_stream_list structures that may not have been fully initialized. + * + * @param list A pointer to the input_stream_list to be freed. If this pointer is NULL, + * the function does nothing. + */ +void input_streams_cleanup(input_stream_list *list) { + if (list != NULL) { + // Iterate over each input_stream_info and free its dynamically allocated fields + for (uint32_t i = 0; i < list->num_inputs; ++i) { + free(list->outputs[i].name); + free(list->outputs[i].driver); + + if (list->outputs[i].format != NULL) { + pa_format_info_free(list->outputs[i].format); + } + + if (list->outputs[i].proplist != NULL) { + pa_proplist_free(list->outputs[i].proplist); + } + } + + // Free the array of input_stream_info + free(list->outputs); + + // Free the input_stream_list structure itself + free(list); + } +} + +/** + * @brief Callback function for sink input information retrieval. + * + * This function is called by the PulseAudio context for each sink input + * retrieved by pa_context_get_sink_input_info_list(). It allocates memory + * for a new output_stream_info structure, copies the relevant sink input + * information into it, and stores it in the output_stream_list. + * + * This function handles the allocation of memory for each output_stream_info + * and ensures that relevant data from the pa_sink_input_info, such as the + * index, parent index, and name, are copied over. + * + * @param c The PulseAudio context. + * @param i The sink input information structure. + * @param eol End of list indicator. If positive, it's the end of the list. + * @param userdata User data pointer, cast to output_stream_list, passed to the function. + */ +static void get_output_streams_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) { + (void) c; + + if (eol > 0) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + output_stream_list *input_list = (output_stream_list *) userdata; + + // Resize the inputs array to accommodate one more output_stream_info + input_list->inputs = realloc(input_list->inputs, (input_list->num_inputs + 1) * sizeof(output_stream_info)); + if (input_list->inputs == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input list\n"); + return; + } + + output_stream_info *new_input = &input_list->inputs[input_list->num_inputs]; + new_input->index = i->index; + new_input->owner_module = i->owner_module; + new_input->parent_index = i->sink; // Index of the corresponding output device (sink) + new_input->volume = i->volume; + new_input->sample_spec = i->sample_spec; + new_input->channel_map = i->channel_map; + + // Copy format info + if (i->format) { + new_input->format = pa_format_info_copy(i->format); + } else { + new_input->format = NULL; + } + + // Copy proplist + if (i->proplist) { + new_input->proplist = pa_proplist_copy(i->proplist); + } else { + new_input->proplist = NULL; + } + + // Copy name + new_input->name = strdup(i->name); + if (new_input->name == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input name\n"); + } + + // Copy driver + new_input->driver = strdup(i->driver); + if (new_input->driver == NULL) { + fprintf(stderr, "Failed to allocate memory for sink input driver\n"); + } + + input_list->num_inputs++; +} + + + +/** + * @brief Retrieves a list of current sink inputs from the PulseAudio server. + * + * This function initiates an asynchronous operation to retrieve the list + * of sink inputs from the PulseAudio server. It allocates an instance of + * output_stream_list and populates it with output_stream_info structures. + * Each entry in the list is a dynamically allocated output_stream_info structure + * containing the sink input details such as index, parent index, and name. + * + * The caller is responsible for freeing the memory allocated for each + * output_stream_info structure and the output_stream_list itself after use. + * The function blocks until the operation is completed or fails. + * The sink input list will not be modified after the function returns. + * + * @param context A pointer to an initialized and connected pa_context object. + * @return A pointer to an output_stream_list containing all retrieved sink inputs, + * or NULL if an error occurs. + */ +output_stream_list *get_output_streams(pa_context *context) { + output_stream_list *input_list = malloc(sizeof(output_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for output_stream_list\n"); + return NULL; + } + + input_list->inputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of sink inputs + pa_operation *op = pa_context_get_sink_input_info_list(context, get_output_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get sink input list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + + +/** + * @brief Callback function to process each input stream found. + * + * This function is called by the PulseAudio context for each source output. + * It stores information about each input stream into an input_stream_list structure. + * When the end of the list is reached, it signals the main loop to continue execution. + * + * @param c Pointer to the PulseAudio context (not used in this callback). + * @param o Pointer to the pa_source_output_info structure containing the source output info. + * @param eol End of list flag. If non-zero, indicates no more data is forthcoming. + * @param userdata User data pointer, expected to be a pointer to an input_stream_list structure. + */ +static void get_input_streams_cb(pa_context *c, const pa_source_output_info *o, int eol, void *userdata) { + (void) c; + + if (eol) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; + } + + input_stream_list *input_list = (input_stream_list *) userdata; + + // Resize the outputs array to accommodate one more input_stream_info + input_list->outputs = realloc(input_list->outputs, (input_list->num_inputs + 1) * sizeof(input_stream_info)); + if (input_list->outputs == NULL) { + fprintf(stderr, "Failed to allocate memory for source output list\n"); + return; + } + + // Reference the new element where we will store the info + input_stream_info *new_info = &input_list->outputs[input_list->num_inputs]; + + // Copy the relevant info from pa_source_output_info to input_stream_info + new_info->index = o->index; + new_info->owner_module = o->owner_module; + new_info->parent_index = o->source; // Index of the corresponding input device (source) + new_info->volume = o->volume; + new_info->sample_spec = o->sample_spec; + new_info->channel_map = o->channel_map; + + // Copy format info + if (o->format) { + new_info->format = pa_format_info_copy(o->format); + } else { + new_info->format = NULL; + } + + // Copy proplist + if (o->proplist) { + new_info->proplist = pa_proplist_copy(o->proplist); + } else { + new_info->proplist = NULL; + } + + // Copy name + new_info->name = strdup(o->name); + if (new_info->name == NULL) { + fprintf(stderr, "Failed to allocate memory for source output name\n"); + } + + // Copy driver + new_info->driver = strdup(o->driver); + if (new_info->driver == NULL) { + fprintf(stderr, "Failed to allocate memory for source output driver\n"); + } + + // Increment the inputs counter + input_list->num_inputs++; +} + +/** + * @brief Retrieves a list of all input streams (source outputs) from the PulseAudio server. + * + * This function initiates an asynchronous operation to list all the source outputs (input streams) + * currently available in the PulseAudio server. It waits for the operation to complete and + * returns a list of input streams. + * + * @param context Pointer to the initialized and connected PulseAudio context. + * @return Pointer to an input_stream_list structure containing the list of input streams. + * Returns NULL if the operation fails or memory allocation is not possible. + */ +input_stream_list *get_input_streams(pa_context *context) { + input_stream_list *input_list = malloc(sizeof(input_stream_list)); + if (!input_list) { + fprintf(stderr, "Failed to allocate memory for input_stream_list\n"); + return NULL; + } + + input_list->outputs = NULL; + input_list->num_inputs = 0; + + // Initiate the operation to get the list of source outputs + pa_operation *op = pa_context_get_source_output_info_list(context, get_input_streams_cb, input_list); + if (!op) { + fprintf(stderr, "Failed to start operation to get source output list\n"); + free(input_list); + return NULL; + } + + // Wait for the operation to complete + iterate(op); + + return input_list; +} + +/** + * @brief Callback function for retrieving the active profile information of a PulseAudio card. + * + * This function is called by PulseAudio in response to pa_context_get_card_info_by_name. + * It processes the card information and stores the active profile if found. + * + * @param c Pointer to the PulseAudio context. Not used in this function. + * @param i Pointer to the PulseAudio card information structure. + * @param eol End-of-list flag. If positive, it indicates no more data is available. + * @param userdata User-provided pointer to store the resulting profile information. + * + * @note The function assumes userdata is a pointer to card_profile_info structure. + */ +static void get_active_profile_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { + (void) c; + + fprintf(stderr, "[DEBUG, card_info_cb()], reached function\n"); + + card_profile_info *info = (card_profile_info *)userdata; + printf("[DEBUG] i is: %p\n", i); + + if (eol > 0 || !i) { + pa_threaded_mainloop_signal(shared_data_1.mainloop, 0); + return; // No more data, or null card info + } + + if (i->active_profile && !info->found) { + // Allocate memory for the new profile + info->active_profile = malloc(sizeof(pa_card_profile_info)); + if (info->active_profile) { + // Duplicate the name and description strings + info->active_profile->name = strdup(i->active_profile->name); + info->active_profile->description = strdup(i->active_profile->description); + info->found = true; + } + } + + fprintf(stderr, "[DEBUG, card_info_cb()] card_info_cb is %s\n", i->active_profile->description); +} + +/** + * @brief Retrieves the active profile information for a specified PulseAudio card. + * + * This function initiates a query to PulseAudio to get information about a specific card. + * It blocks until the callback (get_active_profile_cb) has processed the data. + * + * @param context Pointer to the PulseAudio context. + * @param card_name Name of the PulseAudio card to query. + * + * @return Pointer to the active profile information on success, NULL on failure or if no active profile is found. + * + * @note The returned pointer should not be freed by the caller, as it points to statically allocated memory. + */ +pa_card_profile_info *get_active_profile(pa_context *context, char *card_name) { + static card_profile_info info; + + if (!context) { + fprintf(stderr, "Invalid arguments.\n"); + return NULL; + } + + info.active_profile = NULL; + info.found = false; + + pa_operation *op = pa_context_get_card_info_by_name(context, card_name, get_active_profile_cb, &info); + iterate(op); + + //fprintf(stderr, "[DEBUG, get_active_profile()] info.active_profile is %s\n", info.active_profile->description); + return info.active_profile; +} diff --git a/v-0.16/system_query.h b/v-0.16/system_query.h new file mode 100644 index 0000000..6774e59 --- /dev/null +++ b/v-0.16/system_query.h @@ -0,0 +1,176 @@ +/** + * @file system_query.h + * @brief Header file for querying sound card properties in a PulseAudio environment. + * + * This header file provides a collection of functions and structures to interact with and query + * various properties of sound cards using PulseAudio and ALSA interfaces. It includes functions + * to obtain information about output and input devices (sinks and sources), such as the number + * of devices, device names, channel names, sample rates, and mute states. It also offers + * capabilities to manipulate and retrieve detailed information about ALSA cards and ports. + * + * Structures: + * - pa_port_info: Represents port information (e.g., line-in, microphone). + * - pa_source_info_list: Holds a list of pa_port_info structures. + * + * This file serves as an essential component for applications that need to interact with + * PulseAudio and ALSA for detailed audio device management and information retrieval. + * + * @author Mbyte2 + * @date November 13, 2023 + */ +#ifndef SYSTEM_QUERY_H +#define SYSTEM_QUERY_H + +#include +#include +#include +#include + +//Structures to get source port information (e.g, line in, microphone...) +//Used by get_source_port_info, get_active_port, and get_source_ports. +typedef struct pa_port_info { + char *name; // Port name + char *description; // Port description + bool is_active; // Is this the active port +} pa_port_info; + +typedef struct pa_source_info_list { + pa_port_info *ports; // Array of ports + int num_ports; // Number of ports + bool done; // Indicates if the callback has been called +} pa_source_info_list; + +typedef struct { + uint32_t index; + uint32_t owner_module; + uint32_t parent_index; //Index of the corresponding output device (sink). + pa_cvolume volume; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_proplist *proplist; + char *name; + char *driver; +} output_stream_info; + +//Used by get_output_streams() to get a list of all Pulseaudio sink inputs. +typedef struct output_stream_list { + output_stream_info *inputs; + uint32_t num_inputs; +} output_stream_list; + +typedef struct { + uint32_t index; + uint32_t owner_module; + uint32_t parent_index; //Index of the corresponding output device (sink). + pa_cvolume volume; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_format_info *format; + pa_proplist *proplist; + char *name; + char *driver; +} input_stream_info; + +//Used by get_input_streams() to get a list of all Pulseaudio source outputs. +typedef struct input_stream_list { + input_stream_info *outputs; + uint32_t num_inputs; +} input_stream_list; + + +void print_proplist(const pa_proplist *p); // Utility function to print all properties in the proplist +uint32_t get_output_device_count(void); //Gets the number of output devices in the system. +uint32_t get_input_device_count(void); //Gets the number of input devices in the system. +uint32_t get_profile_count(uint32_t card_index); //Gets the number of profiles for a given soundcard. +pa_sink_info **get_available_output_devices(); //Gets the total available sinks (output devices) for this system. +pa_source_info **get_available_input_devices(); //Gets the total available sources (input devices) for this system. + +char* get_alsa_input_name(const char *source_name); //Gets the corresponding alsa name of a pulseaudio source (input device). +char* get_alsa_output_name(const char *sink_name); //Gets the corresponding alsa name of a pulseaudio sink (output device). + +pa_sink_info* get_output_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio sink (output device) by its index. +pa_source_info* get_input_device_by_index(uint32_t index); //Gets alsa name of a pulseaudio source (output device) by its index. + +pa_source_info_list* get_source_port_info(); //Returns which ports in the source are available (mic, line in...). + + +char** get_input_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an input device. +char** get_output_channel_names(const char *pulse_id, +int num_channels); //Returns the channel names of an output device. + +int get_min_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the minimum output channels an ALSA card supports. + +int get_max_input_channels(const char *alsa_id, +const pa_source_info *source_info); //Gets the maximum output channels an ALSA card supports. + +int get_max_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the maximum output channels an ALSA card supports. + +int get_min_output_channels(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the minimum output channels an ALSA card supports. + +char* get_alsa_input_id(const char *source_name); //Gets the alsa input id based on the pulseaudio channel name. + +char* get_alsa_output_id(const char *sink_name); //Gets the alsa output id based on the pulseaudio channel name. + +int get_output_sample_rate(const char *alsa_id, +const pa_sink_info *sink_info); //Gets the sample rate of a pulseaudio sink (output device). + +int get_input_sample_rate(const char *alsa_id, +pa_source_info *source_info); //Gets the sample rate of a pulseaudio source (input device). + +pa_source_info *get_input_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio source (input device) by its name. +pa_sink_info *get_output_device_by_name(const char *pulse_code); //Gets alsa name of a pulseaudio sink (output device) by its name. + +uint32_t get_output_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +uint32_t get_input_device_index_by_code(pa_context *context, +const char *device_code); //Gets index of an output device by its code (pulseaudio name). + +int get_muted_output_status(const char *sink_name); //Queries whether a given audio output (sink) is muted or not. + +int get_muted_input_status(const char *source_name); //Queries whether a given audio input (source) is muted or not. + +pa_volume_t get_channel_volume(const pa_sink_info *sink_info, +unsigned int channel_index); //Retrieves the volume of a given channel. + +char* get_default_output(pa_context *context); //Gets default output device (default sink). + +char* get_default_input(pa_context *context); //Gets default input device (default source). + +pa_card_profile_info *get_profiles(pa_context *pa_ctx, +uint32_t card_index); //Gets pulseaudio profiles. + +char* get_input_name_by_code(pa_context *pa_ctx, +const char *code); //Gets input name (pulseaudio device description) by code. + +char* get_output_name_by_code(pa_context *pa_ctx, +const char *code); //Gets output name (pulseaudio device description) by code. + +void delete_output_devices(pa_sink_info **sinks); //Releases memory for allocated output devices. +void delete_input_devices(pa_source_info **sources); //Releases memory for allocated input devices. + +int get_pulseaudio_global_playback_rate(const char* custom_config_path); //Gets the global pulseaudio playback rate from pulseaudio. + +bool get_output_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t sink_index, uint32_t channel_index); //Gets mute state of single output channel (0 = unmuted, 1 = muted). + +bool get_input_channel_mute_state(pa_context *context, +pa_threaded_mainloop *mainloop, +uint32_t source_index, uint32_t channel_index); //Gets mute state of single input channel (0 = unmuted, 1 = muted). + +output_stream_list *get_output_streams(pa_context *context); //Gets a list of output (sink) inputs. They must be freed after allocated. +void output_streams_cleanup(output_stream_list *list); //Cleans up allocated output streams. +void input_streams_cleanup(input_stream_list *list); //Cleans up allocated input streams. + +input_stream_list *get_input_streams(pa_context *context); //Gets a list of input (source) inputs. + +pa_card_profile_info *get_active_profile(pa_context *context, +char *output_name); //Gets the active profile of a card. + +#endif