/* * MIT License * * Copyright (c) Robin E.R. Davies * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "alsaCheck.hpp" #include #include #include #include #include "ss.hpp" #include #include "Finally.hpp" using namespace pipedal; static std::string GetChannelDescription( snd_pcm_t* handle, snd_pcm_hw_params_t*hwParams, unsigned int channels) { snd_pcm_chmap_query_t **chmaps = snd_pcm_query_chmaps(handle); if (chmaps) { Finally ff([chmaps] { snd_pcm_free_chmaps(chmaps); }); std::stringstream ss; bool firstMap = true; for (size_t i = 0; chmaps[i] != nullptr; ++i) { auto chmap = chmaps[i]; if (chmap->map.channels == channels) { if (!firstMap) { ss << "/"; } ss << channels << " "; firstMap = false; switch (chmap->type) { case SND_CHMAP_TYPE_NONE: default: ss << "Unknown"; break; case SND_CHMAP_TYPE_VAR: case SND_CHMAP_TYPE_FIXED: for (size_t ch = 0; ch < chmap->map.channels; ++ch) { if (ch != 0) { ss << " "; } snd_pcm_chmap_position pos =(snd_pcm_chmap_position)(chmap->map.pos[ch]); if (pos == snd_pcm_chmap_position::SND_CHMAP_MONO) { ss << "ch" << (i+1); } ss << snd_pcm_chmap_name((snd_pcm_chmap_position)(chmap->map.pos[ch])); } break; case SND_CHMAP_TYPE_PAIRED: for (size_t pair = 0; pair < chmap->map.channels/2; ++pair) { if (pair != 0) { ss << " "; } ss << "L" << (pair+1) << " R" << (pair+1); } break; } if (chmap->type == SND_CHMAP_TYPE_VAR) { ss << " (var)"; } } } return ss.str(); } std::string config_name; switch (channels) { case 1: config_name = "Mono"; break; case 2: config_name = "Stereo"; break; case 4: config_name = "Quadraphonic"; break; case 6: config_name = "5.1 Surround"; break; case 8: config_name = "7.1 Surround"; break; default: config_name = std::to_string(channels) + " channels"; } return config_name; } void pipedal::check_alsa_channel_configs(const char* device_name) { snd_pcm_t* handle; int err; // Open PCM device for playback if ((err = snd_pcm_open(&handle, device_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { throw std::runtime_error(SS("Cannot open audio device " << device_name << ": " << snd_strerror(err))); return; } Finally ff([handle] { snd_pcm_close(handle); }); // Get hardware parameters snd_pcm_hw_params_t* hw_params; snd_pcm_hw_params_alloca(&hw_params); if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) { const std::string errorMessage = snd_strerror(err); throw std::runtime_error(SS("Cannot initialize hardware parameter structure: " << errorMessage)); } // Get channel count range unsigned int min_channels, max_channels; if ((err = snd_pcm_hw_params_get_channels_min(hw_params, &min_channels)) < 0) { const std::string errorMessage = snd_strerror(err); throw std::runtime_error(SS("Cannot get minimum channels: " << errorMessage)); } if ((err = snd_pcm_hw_params_get_channels_max(hw_params, &max_channels)) < 0) { std::cerr << "Cannot get maximum channels: " << snd_strerror(err) << std::endl; return; } std::cout << "Device: " << device_name << std::endl; std::cout << "Channel range: " << min_channels << " to " << max_channels << std::endl; std::cout << "\nSupported channel configurations:" << std::endl; // Test specific channel counts std::vector common_configs = {1, 2, 4, 6, 8}; for (unsigned int channels : common_configs) { if (channels >= min_channels && channels <= max_channels) { snd_pcm_hw_params_t* test_params; snd_pcm_hw_params_alloca(&test_params); snd_pcm_hw_params_copy(test_params, hw_params); if (snd_pcm_hw_params_set_channels(handle, test_params, channels) == 0) { std::string channel_description = GetChannelDescription(handle,test_params,channels); std::cout << " " << channels << " channels (" << channel_description << ")" << std::endl; } } } } static bool hasChannelMaps(snd_pcm_t* pcm_handle) { snd_pcm_chmap_query_t **chmaps = snd_pcm_query_chmaps(pcm_handle); if (chmaps) { snd_pcm_free_chmaps(chmaps); return true; } return false; } static void print_device_info(snd_ctl_t* handle, int card, int device) { snd_pcm_info_t* pcminfo; snd_pcm_info_alloca(&pcminfo); snd_pcm_info_set_device(pcminfo, device); snd_pcm_info_set_subdevice(pcminfo, 0); // Check playback snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); bool has_playback = snd_ctl_pcm_info(handle, pcminfo) >= 0; // Check capture snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); bool has_capture = snd_ctl_pcm_info(handle, pcminfo) >= 0; if (has_playback || has_capture) { char name[32]; snprintf(name, sizeof(name), "hw:%d,%d", card, device); // Get device name snd_pcm_t* pcm; std::string device_name = "Unknown"; if (snd_pcm_open(&pcm, name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) >= 0) { device_name = snd_pcm_name(pcm); snd_pcm_close(pcm); } // Print basic info std::cout << "\n Subdevice: " << name << " - " << device_name << std::endl; std::cout << " ID: " << snd_pcm_info_get_id(pcminfo) << std::endl; std::cout << " Name: " << snd_pcm_info_get_name(pcminfo) << std::endl; std::cout << " Capabilities:"; if (has_playback) std::cout << " PLAYBACK"; if (has_capture) std::cout << " CAPTURE"; std::cout << std::endl; // Get hardware parameters for more detailed info if (has_playback) { snd_pcm_t* pcm_handle; if (snd_pcm_open(&pcm_handle, name, SND_PCM_STREAM_PLAYBACK, 0) >= 0) { snd_pcm_hw_params_t* hw_params; snd_pcm_hw_params_alloca(&hw_params); if (snd_pcm_hw_params_any(pcm_handle, hw_params) >= 0) { unsigned int min_channels = 1024, max_channels = 1024; unsigned int min_rate = 1, max_rate = 1; snd_pcm_hw_params_get_channels_min(hw_params, &min_channels); snd_pcm_hw_params_get_channels_max(hw_params, &max_channels); snd_pcm_hw_params_get_rate_min(hw_params, &min_rate, 0); snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, 0); std::cout << " Channels: " << min_channels << " - " << max_channels << std::endl; std::cout << " Sample rates: " << min_rate << " - " << max_rate << " Hz" << std::endl; // Get supported formats std::cout << " Supported formats:"; for (snd_pcm_format_t fmt = SND_PCM_FORMAT_S8; fmt <= SND_PCM_FORMAT_LAST; fmt = snd_pcm_format_t(fmt + 1)) { if (snd_pcm_hw_params_test_format(pcm_handle, hw_params, fmt) == 0) { std::cout << " " << snd_pcm_format_name(fmt); } } std::cout << std::endl; std::cout << " Channel maps:" << std::endl; snd_pcm_hw_params_t* test_params; snd_pcm_hw_params_alloca(&test_params); if (!hasChannelMaps(pcm_handle)) { std::cout << " No channel maps." << std::endl; } else { for (size_t i = min_channels; i <= max_channels; ++i) { snd_pcm_hw_params_copy(test_params, hw_params); if (snd_pcm_hw_params_set_channels(pcm_handle, test_params, i) == 0) { std::cout << " " << GetChannelDescription(pcm_handle,hw_params,i) << std::endl; } } } } snd_pcm_close(pcm_handle); } else { std::cout << " (device not available)" << std::endl; } } } } void pipedal::list_alsa_devices() { int card = -1; int err; std::cout << "=== ALSA Hardware Devices ===" << std::endl; // Iterate through cards while ((err = snd_card_next(&card)) >= 0 && card >= 0) { snd_ctl_t* handle; char name[32]; snprintf(name, sizeof(name), "hw:%d", card); if ((err = snd_ctl_open(&handle, name, 0)) < 0) { std::cerr << "Cannot open control for card " << card << ": " << snd_strerror(err) << std::endl; continue; } // Get card info snd_ctl_card_info_t* info; snd_ctl_card_info_alloca(&info); if ((err = snd_ctl_card_info(handle, info)) < 0) { std::cerr << "Cannot get card info: " << snd_strerror(err) << std::endl; snd_ctl_close(handle); continue; } std::cout << "\nSound Card hw:" << snd_ctl_card_info_get_id(info) << std::endl; std::cout << " Name: " << snd_ctl_card_info_get_name(info) << std::endl; std::cout << " Driver: " << snd_ctl_card_info_get_driver(info) << std::endl; // Iterate through devices on this card int device = -1; while ((err = snd_ctl_pcm_next_device(handle, &device)) >= 0 && device >= 0) { print_device_info(handle, card, device); } snd_ctl_close(handle); } }