├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── examples ├── CMakeLists.txt ├── Makefile ├── interleaved_reader.cpp ├── pcminfo.cpp └── pcmlist.cpp ├── tinyalsa.cpp └── tinyalsa.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.o 3 | *.a 4 | examples/pcminfo 5 | examples/pcmlist 6 | examples/interleaved_reader 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8.2) 2 | 3 | project("tinyalsa-cxx" CXX) 4 | 5 | option(TINYALSA_EXAMPLES "Whether or not to build the examples." OFF) 6 | 7 | set(common_cxxflags -Wall -Wextra -Werror -Wfatal-errors) 8 | 9 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 10 | set(tinyalsa_cxxflags ${common_warnings}) 11 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 12 | set(tinyalsa_cxxflags ${common_warnings} -Wdocumentation) 13 | endif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 14 | 15 | add_library("tinyalsa-cxx" 16 | "tinyalsa.hpp" 17 | "tinyalsa.cpp") 18 | 19 | target_compile_options("tinyalsa-cxx" PRIVATE ${tinyalsa_cxxflags} -fno-rtti -fno-exceptions) 20 | 21 | target_include_directories("tinyalsa-cxx" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") 22 | 23 | if(TINYALSA_EXAMPLES) 24 | add_subdirectory("examples") 25 | endif(TINYALSA_EXAMPLES) 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Here's some general rules of thumb when contributing to this project. 2 | If there's a question about any of these, feel free to create an issue with the question. 3 | 4 | - All code should be properly indented and consistent with the rest of the C++ style. 5 | - Avoid using exceptions and note them in documentation if they must be used. 6 | - Pull requests should not be larger than 200 lines of code. 7 | - Good pull requests are minimal change sets (don't add multiple features in one PR.) 8 | - This project is not affiliated with Android. 9 | PRs that support Android are welcome but whether or not they're accepted is based on what's best for this project. 10 | - Keep what's exposed in the header file to a bare minimum. 11 | - Any functions that are added to the header file should be formally documented with Doxygen. 12 | - When possible, write a test (automated if possible) that verifies the behavior of a function. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Taylor Holberton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include config.mk 2 | 3 | CXXFLAGS := -Wall -Wextra -Werror -Wfatal-errors -I $(CURDIR) 4 | 5 | ifdef TINYALSA_DEBUG 6 | CXXFLAGS := $(CXXFLAGS) -g 7 | else 8 | CXXFLAGS := $(CXXFLAGS) -Os -fno-rtti -fno-exceptions 9 | endif 10 | 11 | examples += examples/pcminfo 12 | examples += examples/pcmlist 13 | 14 | .PHONY: all 15 | all: libtinyalsa-cxx.a 16 | 17 | libtinyalsa-cxx.a: tinyalsa.o 18 | $(AR) $(ARFLAGS) $@ $^ 19 | 20 | tinyalsa.o: tinyalsa.cpp tinyalsa.hpp 21 | 22 | .PHONY: examples 23 | examples: $(examples) 24 | 25 | examples/pcminfo: examples/pcminfo.o libtinyalsa-cxx.a 26 | 27 | examples/pcminfo.o: examples/pcminfo.cpp tinyalsa.hpp 28 | 29 | examples/pcmlist: examples/pcmlist.o libtinyalsa-cxx.a 30 | 31 | examples/pcmlist.o: examples/pcmlist.cpp tinyalsa.hpp 32 | 33 | examples/%: examples/%.o libtinyalsa-cxx.a 34 | $(CXX) $^ -o $@ libtinyalsa-cxx.a 35 | 36 | .PHONY: clean 37 | clean: 38 | $(RM) tinyalsa.o libtinyalsa-cxx.a $(examples) examples/*.o 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinyalsa-cxx 2 | 3 | A paper-thin C++ library for interfacing with ALSA. 4 | 5 | ### Examples 6 | 7 | Reading information on a PCM: 8 | 9 | ```cxx 10 | tinyalsa::pcm pcm; 11 | 12 | auto open_result = pcm.open_capture_device(); 13 | if (open_result.failed()) { 14 | std::cerr << open_result << std::endl; 15 | return; 16 | } 17 | 18 | std::cout << pcm.get_info(); 19 | ``` 20 | 21 | Reading audio data from a PCM: 22 | ```cxx 23 | // This is a 16-bit integer stereo buffer. 24 | unsigned short int frames[1024]; 25 | unsigned int frame_count = 2; 26 | 27 | // Error checking omitted for brevity. 28 | tinyalsa::interleaved_pcm_reader pcm_reader; 29 | pcm_reader.open(); // Choose the device to open. 30 | pcm_reader.setup(); // Apply hardware parameters 31 | pcm_reader.prepare(); // Prepare the PCM to be started 32 | pcm_reader.start(); // Begin recording 33 | pcm_reader.read_unformatted(frames, frame_count); // Read recorded data 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8.2) 2 | 3 | function(add_tinyalsa_example example) 4 | 5 | set(target tinyalsa_example_${example}) 6 | 7 | add_executable(${target} ${ARGN}) 8 | 9 | target_link_libraries(${target} PRIVATE tinyalsa-cxx) 10 | 11 | target_compile_options(${target} PRIVATE ${tinyalsa_cxxflags}) 12 | 13 | set_target_properties(${target} 14 | PROPERTIES 15 | RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}" 16 | OUTPUT_NAME "${example}") 17 | 18 | endfunction(add_tinyalsa_example example) 19 | 20 | add_tinyalsa_example("interleaved_reader" "interleaved_reader.cpp") 21 | add_tinyalsa_example("pcminfo" "pcminfo.cpp") 22 | add_tinyalsa_example("pcmlist" "pcmlist.cpp") 23 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | ifndef TOP 2 | TOP = .. 3 | endif 4 | 5 | VPATH += $(TOP)/src 6 | VPATH += $(TOP)/include/tinyalsa 7 | 8 | CXXFLAGS = -Wall -Wextra -Werror -Wfatal-errors 9 | CXXFLAGS += -std=c++11 10 | CXXFLAGS += -I $(TOP)/include 11 | 12 | .PHONY: all 13 | all: input 14 | 15 | input: input.o -ltinyalsa-cxx 16 | 17 | input.o: input.cpp 18 | 19 | .PHONY: clean 20 | clean: 21 | $(RM) input input.o 22 | 23 | -------------------------------------------------------------------------------- /examples/interleaved_reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | unsigned short int frames[1024]; 9 | 10 | constexpr const unsigned int frame_count = sizeof(frames) / (2 * sizeof(frames[0])); 11 | 12 | tinyalsa::interleaved_pcm_reader pcm_reader; 13 | 14 | auto open_result = pcm_reader.open(); 15 | if (open_result.failed()) { 16 | std::printf("Failed to open PCM: %s\n", open_result.error_description()); 17 | return EXIT_FAILURE; 18 | } 19 | 20 | auto setup_result = pcm_reader.setup(); 21 | if (setup_result.failed()) { 22 | std::printf("Failed to setup PCM: %s\n", setup_result.error_description()); 23 | return EXIT_FAILURE; 24 | } 25 | 26 | auto prepare_result = pcm_reader.prepare(); 27 | if (prepare_result.failed()) { 28 | std::printf("Failed to prepare PCM: %s\n", prepare_result.error_description()); 29 | return EXIT_FAILURE; 30 | } 31 | 32 | auto start_result = pcm_reader.start(); 33 | if (start_result.failed()) { 34 | std::printf("Failed to start PCM: %s\n", start_result.error_description()); 35 | return EXIT_FAILURE; 36 | } 37 | 38 | auto read_result = pcm_reader.read_unformatted(frames, frame_count); 39 | if (read_result.failed()) { 40 | std::printf("Failed to read PCM: %s\n", read_result.error_description()); 41 | return EXIT_FAILURE; 42 | } 43 | 44 | std::printf("Read %lu frames from PCM.\n", read_result.unwrap()); 45 | 46 | return EXIT_SUCCESS; 47 | } 48 | -------------------------------------------------------------------------------- /examples/pcminfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | int main() 8 | { 9 | tinyalsa::pcm pcm; 10 | 11 | auto open_result = pcm.open_capture_device(); 12 | if (open_result.failed()) { 13 | std::cerr << "Failed to open PCM: " << open_result << std::endl; 14 | return EXIT_FAILURE; 15 | } 16 | 17 | auto info_result = pcm.get_info(); 18 | if (info_result.failed()) { 19 | std::cerr << "Failed to get PCM info: " << info_result << std::endl; 20 | return EXIT_FAILURE; 21 | } 22 | 23 | std::cout << info_result.unwrap(); 24 | 25 | return EXIT_SUCCESS; 26 | } 27 | -------------------------------------------------------------------------------- /examples/pcmlist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main() 6 | { 7 | tinyalsa::pcm_list list; 8 | 9 | for (const auto& pcm_info : list) { 10 | std::cout << pcm_info << std::endl; 11 | } 12 | 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tinyalsa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace tinyalsa { 17 | 18 | //================// 19 | // Section: Masks // 20 | //================// 21 | 22 | namespace { 23 | 24 | /// The type used to identify hardware parameters. 25 | using parameter_name = int; 26 | 27 | /// Represents an indexed mask parameter. 28 | /// 29 | /// @tparam param The index of the mask parameter. 30 | template 31 | struct mask_ref final 32 | { 33 | /// A type definition for values assigned to mask parameters. 34 | using value_type = std::remove_reference::type; 35 | /// Indicates whether or not this is a valid mask parameter. 36 | /// 37 | /// @return True if it's a valid hardware parameter, false if it's not. 38 | static constexpr bool out_of_range() noexcept 39 | { 40 | return (param < SNDRV_PCM_HW_PARAM_FIRST_MASK) 41 | || (param > SNDRV_PCM_HW_PARAM_LAST_MASK); 42 | } 43 | /// Initializes the mask. 44 | /// 45 | /// @param hw_params The hardware parameters instance to initialize the mask in. 46 | static constexpr void init(snd_pcm_hw_params& hw_params) noexcept 47 | { 48 | static_assert(!out_of_range(), "Not a mask parameter."); 49 | 50 | auto& mask = hw_params.masks[param - SNDRV_PCM_HW_PARAM_FIRST_MASK]; 51 | mask.bits[0] = std::numeric_limits::max(); 52 | mask.bits[1] = std::numeric_limits::max(); 53 | } 54 | /// Sets the value of a hardware mask parameter. 55 | /// 56 | /// @param hw_params The hardware parameters containing the mask to modify. 57 | /// @param value The value to assign the mask parameter. 58 | static constexpr void set(snd_pcm_hw_params& hw_params, value_type value) noexcept 59 | { 60 | static_assert(!out_of_range(), "Not a mask parameter."); 61 | 62 | auto& mask = hw_params.masks[param - SNDRV_PCM_HW_PARAM_FIRST_MASK]; 63 | mask.bits[0] = 0; 64 | mask.bits[1] = 0; 65 | mask.bits[value >> 5] |= (1 << (value & 31)); 66 | } 67 | }; 68 | 69 | /// Used for initializing all the masks in a hardware parameter structure. 70 | /// 71 | /// @tparam param The caller should leave this to its default 72 | /// value. This parameter is used to control the recursive loop. 73 | template 74 | struct masks_initializer final 75 | { 76 | /// Initializes all the masks in a hardware parameter structure. 77 | static constexpr void init(snd_pcm_hw_params& hw_params) noexcept 78 | { 79 | mask_ref::init(hw_params); 80 | 81 | masks_initializer::init(hw_params); 82 | } 83 | }; 84 | 85 | /// Terminates a mask initialization loop. 86 | template <> 87 | struct masks_initializer final 88 | { 89 | static constexpr void init(snd_pcm_hw_params&) noexcept { } 90 | }; 91 | 92 | } // namespace 93 | 94 | //====================// 95 | // Section: Intervals // 96 | //====================// 97 | 98 | namespace { 99 | 100 | /// Used for accessing and modifying 101 | /// interval hardware parameters. 102 | /// 103 | /// @tparam name The name of the interval being regarded. 104 | template 105 | struct interval_ref final 106 | { 107 | /// A type definition for an interval value. 108 | using value_type = decltype(snd_interval::min); 109 | /// Indicates if this is a valid interval parameter. 110 | /// 111 | /// @return True if it's a valid interval parameter, 112 | /// false if it is not. 113 | static constexpr bool out_of_range() noexcept 114 | { 115 | return (name < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) 116 | || (name > SNDRV_PCM_HW_PARAM_LAST_INTERVAL); 117 | } 118 | /// Sets the value of the interval. 119 | /// 120 | /// @param hw_params The hardware parameters that the interval resides in. 121 | /// 122 | /// @param value_type The value to assign the interval parameter. 123 | static constexpr void set(snd_pcm_hw_params& hw_params, value_type value) noexcept 124 | { 125 | static_assert(!out_of_range(), "Not an interval parameter."); 126 | 127 | auto& ref = hw_params.intervals[name - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; 128 | ref.min = value; 129 | ref.max = value; 130 | ref.integer = 1; 131 | } 132 | /// Initializes the interval. 133 | /// 134 | /// @param hw_params The hardware parameters containing the interval to initialize. 135 | static constexpr void init(snd_pcm_hw_params& hw_params) noexcept 136 | { 137 | static_assert(!out_of_range(), "Not an interval parameter."); 138 | 139 | auto& ref = hw_params.intervals[name - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]; 140 | 141 | ref.max = std::numeric_limits::max(); 142 | } 143 | }; 144 | 145 | /// This is used to initialize all the interval 146 | /// structures in a hardware parameters instance. 147 | /// 148 | /// @tparam name Callers should keep this at its 149 | /// default value. This parameter is used to iterate 150 | /// the recusive template loop. 151 | template 152 | struct intervals_initializer final 153 | { 154 | /// Initializes the intervals in a hardware parameters instance. 155 | /// 156 | /// @param hw_params The hardware parameters instance containing 157 | /// the intervals to be initialized. 158 | inline static constexpr void init(snd_pcm_hw_params& hw_params) noexcept 159 | { 160 | interval_ref::init(hw_params); 161 | intervals_initializer::init(hw_params); 162 | } 163 | }; 164 | 165 | /// Terminates an interval initialization loop. 166 | template <> 167 | struct intervals_initializer final 168 | { 169 | /// Does nothing. 170 | inline static constexpr void init(snd_pcm_hw_params&) noexcept {} 171 | }; 172 | 173 | } // namespace 174 | 175 | //==============================// 176 | // Section: Hardware Parameters // 177 | //==============================// 178 | 179 | /// Initializes a new instance of hardware parameters. 180 | /// 181 | /// @return A new instance of hardware parameters. 182 | inline constexpr snd_pcm_hw_params init_hw_parameters() noexcept 183 | { 184 | snd_pcm_hw_params params {}; 185 | 186 | masks_initializer<>::init(params); 187 | 188 | intervals_initializer<>::init(params); 189 | 190 | #if 0 191 | for (int n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) { 192 | struct snd_interval *i = param_to_interval(p, n); 193 | i->max = ~0; 194 | } 195 | #endif 196 | 197 | params.rmask = ~0U; 198 | params.info = ~0U; 199 | 200 | return params; 201 | } 202 | 203 | //==================================// 204 | // Section: Native ALSA to TinyALSA // 205 | //==================================// 206 | 207 | namespace { 208 | 209 | auto to_tinyalsa_class(int native_class_) noexcept 210 | { 211 | switch (native_class_) { 212 | case SNDRV_PCM_CLASS_GENERIC: 213 | return pcm_class::generic; 214 | case SNDRV_PCM_CLASS_MULTI: 215 | return pcm_class::multi_channel; 216 | case SNDRV_PCM_CLASS_MODEM: 217 | return pcm_class::modem; 218 | case SNDRV_PCM_CLASS_DIGITIZER: 219 | return pcm_class::digitizer; 220 | default: 221 | break; 222 | } 223 | 224 | return pcm_class::unknown; 225 | } 226 | 227 | auto to_tinyalsa_subclass(int native_subclass) noexcept 228 | { 229 | switch (native_subclass) { 230 | case SNDRV_PCM_SUBCLASS_GENERIC_MIX: 231 | return pcm_subclass::generic_mix; 232 | case SNDRV_PCM_SUBCLASS_MULTI_MIX: 233 | return pcm_subclass::multi_channel_mix; 234 | default: 235 | break; 236 | } 237 | 238 | return pcm_subclass::unknown; 239 | } 240 | 241 | tinyalsa::pcm_info to_tinyalsa_info(const snd_pcm_info& native_info) noexcept 242 | { 243 | tinyalsa::pcm_info out; 244 | 245 | out.device = native_info.device; 246 | out.subdevice = native_info.subdevice; 247 | out.card = native_info.card; 248 | out.subdevices_count = native_info.subdevices_count; 249 | out.subdevices_available = native_info.subdevices_avail; 250 | 251 | memcpy(out.id, native_info.id, std::min(sizeof(out.id), sizeof(native_info.id))); 252 | memcpy(out.name, native_info.name, std::min(sizeof(out.name), sizeof(native_info.name))); 253 | memcpy(out.subname, native_info.subname, std::min(sizeof(out.subname), sizeof(native_info.subname))); 254 | 255 | out.class_ = to_tinyalsa_class(native_info.dev_class); 256 | out.subclass = to_tinyalsa_subclass(native_info.dev_subclass); 257 | 258 | return out; 259 | } 260 | 261 | } // namespace 262 | 263 | //==================================// 264 | // Section: TinyALSA to Native ASLA // 265 | //==================================// 266 | 267 | namespace { 268 | 269 | /// Converts a sample format to 270 | /// one that's recognized by the ALSA drivers. 271 | constexpr int to_alsa_format(sample_format sf) noexcept 272 | { 273 | switch (sf) { 274 | case sample_format::u8: 275 | return SNDRV_PCM_FORMAT_U8; 276 | 277 | case sample_format::u16_le: 278 | return SNDRV_PCM_FORMAT_U16_LE; 279 | 280 | case sample_format::u16_be: 281 | return SNDRV_PCM_FORMAT_U16_BE; 282 | 283 | case sample_format::u18_3le: 284 | return SNDRV_PCM_FORMAT_U18_3LE; 285 | 286 | case sample_format::u18_3be: 287 | return SNDRV_PCM_FORMAT_U18_3BE; 288 | 289 | case sample_format::u20_3le: 290 | return SNDRV_PCM_FORMAT_U20_3LE; 291 | 292 | case sample_format::u20_3be: 293 | return SNDRV_PCM_FORMAT_U20_3BE; 294 | 295 | case sample_format::u24_3le: 296 | return SNDRV_PCM_FORMAT_U24_3LE; 297 | 298 | case sample_format::u24_3be: 299 | return SNDRV_PCM_FORMAT_U24_3BE; 300 | 301 | case sample_format::u24_le: 302 | return SNDRV_PCM_FORMAT_U24_LE; 303 | 304 | case sample_format::u24_be: 305 | return SNDRV_PCM_FORMAT_U24_BE; 306 | 307 | case sample_format::u32_le: 308 | return SNDRV_PCM_FORMAT_U32_LE; 309 | 310 | case sample_format::u32_be: 311 | return SNDRV_PCM_FORMAT_U32_BE; 312 | 313 | case sample_format::s8: 314 | return SNDRV_PCM_FORMAT_S8; 315 | 316 | case sample_format::s16_le: 317 | return SNDRV_PCM_FORMAT_S16_LE; 318 | 319 | case sample_format::s16_be: 320 | return SNDRV_PCM_FORMAT_S16_BE; 321 | 322 | case sample_format::s18_3le: 323 | return SNDRV_PCM_FORMAT_S18_3LE; 324 | 325 | case sample_format::s18_3be: 326 | return SNDRV_PCM_FORMAT_S18_3BE; 327 | 328 | case sample_format::s20_3le: 329 | return SNDRV_PCM_FORMAT_S20_3LE; 330 | 331 | case sample_format::s20_3be: 332 | return SNDRV_PCM_FORMAT_S20_3BE; 333 | 334 | case sample_format::s24_3le: 335 | return SNDRV_PCM_FORMAT_S24_3LE; 336 | 337 | case sample_format::s24_3be: 338 | return SNDRV_PCM_FORMAT_S24_3BE; 339 | 340 | case sample_format::s24_le: 341 | return SNDRV_PCM_FORMAT_S24_LE; 342 | 343 | case sample_format::s24_be: 344 | return SNDRV_PCM_FORMAT_S24_BE; 345 | 346 | case sample_format::s32_le: 347 | return SNDRV_PCM_FORMAT_S32_LE; 348 | 349 | case sample_format::s32_be: 350 | return SNDRV_PCM_FORMAT_S32_BE; 351 | } 352 | 353 | /* unreachable */ 354 | 355 | return 0; 356 | } 357 | 358 | /// Converts a sample access pattern 359 | /// to one that's recognized by the ALSA drivers. 360 | constexpr int to_alsa_access(sample_access access) noexcept 361 | { 362 | switch (access) { 363 | case sample_access::interleaved: 364 | return SNDRV_PCM_ACCESS_RW_INTERLEAVED; 365 | case sample_access::non_interleaved: 366 | return SNDRV_PCM_ACCESS_RW_NONINTERLEAVED; 367 | case sample_access::mmap_interleaved: 368 | return SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; 369 | case sample_access::mmap_non_interleaved: 370 | return SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED; 371 | } 372 | 373 | /* unreachable */ 374 | 375 | return 0; 376 | } 377 | 378 | /// Configures a PCM configuration structure 379 | /// to a hardware parameters instance. 380 | constexpr snd_pcm_hw_params to_alsa_hw_params(const pcm_config& config, sample_access access) noexcept 381 | { 382 | auto params = init_hw_parameters(); 383 | 384 | interval_ref::set(params, config.channels); 385 | 386 | interval_ref::set(params, config.period_size); 387 | 388 | interval_ref::set(params, config.period_count); 389 | 390 | interval_ref::set(params, config.rate); 391 | 392 | mask_ref::set(params, to_alsa_format(config.format)); 393 | 394 | mask_ref::set(params, to_alsa_access(access)); 395 | 396 | return params; 397 | } 398 | 399 | /// Converts a PCM configuration into the relevant software parameters. 400 | /// 401 | /// @param config The PCM configuration to be converted. 402 | /// @param is_capture Whether or not this is a capture device. 403 | constexpr snd_pcm_sw_params to_alsa_sw_params(const pcm_config& config, bool is_capture) 404 | { 405 | snd_pcm_sw_params params {}; 406 | 407 | params.period_step = 1; 408 | params.avail_min = config.period_size; 409 | 410 | if (config.start_threshold) { 411 | params.start_threshold = config.start_threshold; 412 | } else if (is_capture) { 413 | params.start_threshold = 1; 414 | } else { 415 | params.start_threshold = config.period_count * config.period_size / 2; 416 | } 417 | 418 | if (config.stop_threshold) { 419 | params.stop_threshold = config.stop_threshold; 420 | } else if (is_capture) { 421 | params.stop_threshold = config.period_count * config.period_size * 10; 422 | } else { 423 | params.stop_threshold = config.period_count * config.period_size; 424 | } 425 | 426 | params.boundary = config.period_count * config.period_size; 427 | params.xfer_align = config.period_size / 2; 428 | params.silence_size = 0; 429 | params.silence_threshold = config.silence_threshold; 430 | 431 | return params; 432 | } 433 | 434 | } // namespace 435 | 436 | const char* get_error_description(int error) noexcept 437 | { 438 | if (error == 0) { 439 | return "Success"; 440 | } else { 441 | return ::strerror(error); 442 | } 443 | } 444 | 445 | //=====================// 446 | // Section: POD Buffer // 447 | //=====================// 448 | 449 | /// A simple buffer for POD type data. 450 | /// 451 | /// TODO : Check the impact of multiple instantiations 452 | /// on overall binary size. 453 | /// 454 | /// @tparam element_type The type of the element 455 | /// stored in the buffer. 456 | template 457 | struct pod_buffer final 458 | { 459 | /// The array of elements. 460 | element_type* data = nullptr; 461 | /// The number of elements in the buffer. 462 | size_type size = 0; 463 | /// Constructs an empty buffer. 464 | constexpr pod_buffer() noexcept : data(nullptr), size(0) {} 465 | /// Moves a buffer from one variable to another. 466 | /// 467 | /// @param other The variable to be moved. 468 | inline constexpr pod_buffer(pod_buffer&& other) noexcept 469 | : data(other.data), 470 | size(other.size) 471 | { 472 | other.data = nullptr; 473 | other.size = 0; 474 | } 475 | /// Releases memory allocated by the buffer. 476 | ~pod_buffer() 477 | { 478 | std::free(data); 479 | data = nullptr; 480 | size = 0; 481 | } 482 | /// Adds an element to the end of the buffer. 483 | /// 484 | /// @param e The element to add to the end of the buffer. 485 | /// 486 | /// @return True on success, false on failure. 487 | bool emplace_back(element_type&& e) noexcept 488 | { 489 | auto* tmp = (element_type*) std::realloc(data, (size + 1) * sizeof(element_type)); 490 | if (!tmp) { 491 | return false; 492 | } 493 | data = tmp; 494 | size++; 495 | data[size - 1] = std::move(e); 496 | return true; 497 | } 498 | }; 499 | 500 | //=============================// 501 | // Section: Interleaved Reader // 502 | //=============================// 503 | 504 | result interleaved_pcm_reader::open(size_type card, size_type device, bool non_blocking) noexcept 505 | { 506 | return pcm::open_capture_device(card, device, non_blocking); 507 | } 508 | 509 | generic_result interleaved_pcm_reader::read_unformatted(void* frames, size_type frame_count) noexcept 510 | { 511 | snd_xferi transfer { 512 | 0 /* result */, 513 | frames, 514 | snd_pcm_uframes_t(frame_count), 515 | }; 516 | 517 | auto err = ioctl(get_file_descriptor(), SNDRV_PCM_IOCTL_READI_FRAMES, &transfer); 518 | if (err < 0) { 519 | return { errno, 0 }; 520 | } 521 | 522 | return { 0, size_type(transfer.result) }; 523 | } 524 | 525 | //==============// 526 | // Section: PCM // 527 | //==============// 528 | 529 | /// Contains all the implementation data for a PCM. 530 | class pcm_impl final 531 | { 532 | friend pcm; 533 | /// The file descriptor for the opened PCM. 534 | int fd = invalid_fd(); 535 | /// Opens a PCM by a specified path. 536 | /// 537 | /// @param path The path of the PCM to open. 538 | /// 539 | /// @param non_blocking Whether or not the call to ::open 540 | /// should block if the device is not available. 541 | /// 542 | /// @return On success, zero is returned. 543 | /// On failure, a copy of errno is returned. 544 | result open_by_path(const char* path, bool non_blocking) noexcept; 545 | }; 546 | 547 | namespace { 548 | 549 | /// This function allocates an instance 550 | /// of the implementation data, if it is not null. 551 | /// 552 | /// @param impl The current pointer to the implementation data. 553 | /// If this is null then a new one is allocated. 554 | /// 555 | /// @return A pointer to either an existing implementation 556 | /// data instance, a new implementation data instance, or 557 | /// in the case of a memory allocation failure a null pointer. 558 | pcm_impl* lazy_init(pcm_impl* impl) noexcept 559 | { 560 | if (impl) { 561 | return impl; 562 | } 563 | 564 | return new (std::nothrow) pcm_impl(); 565 | } 566 | 567 | } // namespace 568 | 569 | pcm::pcm() noexcept : self(nullptr) { } 570 | 571 | pcm::pcm(pcm&& other) noexcept : self(other.self) 572 | { 573 | other.self = nullptr; 574 | } 575 | 576 | pcm::~pcm() 577 | { 578 | close(); 579 | delete self; 580 | } 581 | 582 | int pcm::close() noexcept 583 | { 584 | if (!self) { 585 | return 0; 586 | } 587 | 588 | if (self->fd != invalid_fd()) { 589 | 590 | auto result = ::close(self->fd); 591 | 592 | self->fd = invalid_fd(); 593 | 594 | if (result == -1) { 595 | return errno; 596 | } 597 | } 598 | 599 | return 0; 600 | } 601 | 602 | int pcm::get_file_descriptor() const noexcept 603 | { 604 | return self ? self->fd : invalid_fd(); 605 | } 606 | 607 | bool pcm::is_open() const noexcept 608 | { 609 | if (!self) { 610 | return false; 611 | } 612 | 613 | return self->fd != invalid_fd(); 614 | } 615 | 616 | result pcm::prepare() noexcept 617 | { 618 | if (!self) { 619 | return ENOENT; 620 | } 621 | 622 | auto err = ::ioctl(self->fd, SNDRV_PCM_IOCTL_PREPARE); 623 | if (err < 0) { 624 | return errno; 625 | } 626 | 627 | return result(); 628 | } 629 | 630 | result pcm::setup(const pcm_config& config, sample_access access, bool is_capture) noexcept 631 | { 632 | auto hw_params = to_alsa_hw_params(config, access); 633 | 634 | auto err = ioctl(get_file_descriptor(), SNDRV_PCM_IOCTL_HW_PARAMS, &hw_params); 635 | if (err < 0) { 636 | return errno; 637 | } 638 | 639 | auto sw_params = to_alsa_sw_params(config, is_capture); 640 | 641 | err = ioctl(get_file_descriptor(), SNDRV_PCM_IOCTL_SW_PARAMS, &sw_params); 642 | if (err < 0) { 643 | return errno; 644 | } 645 | 646 | return 0; 647 | } 648 | 649 | result pcm::start() noexcept 650 | { 651 | if (!self) { 652 | return ENOENT; 653 | } 654 | 655 | auto err = ::ioctl(self->fd, SNDRV_PCM_IOCTL_START); 656 | if (err < 0) { 657 | return errno; 658 | } 659 | 660 | return result(); 661 | } 662 | 663 | result pcm::drop() noexcept 664 | { 665 | if (!self) { 666 | return ENOENT; 667 | } 668 | 669 | auto err = ::ioctl(self->fd, SNDRV_PCM_IOCTL_DROP); 670 | if (err < 0) { 671 | return errno; 672 | } 673 | 674 | return result(); 675 | } 676 | 677 | generic_result pcm::get_info() const noexcept 678 | { 679 | using result_type = generic_result; 680 | 681 | if (!self) { 682 | return result_type { ENOENT }; 683 | } 684 | 685 | snd_pcm_info native_info; 686 | 687 | int err = ioctl(self->fd, SNDRV_PCM_IOCTL_INFO, &native_info); 688 | if (err != 0) { 689 | return result_type { errno }; 690 | } 691 | 692 | return result_type { 0, to_tinyalsa_info(native_info) }; 693 | } 694 | 695 | result pcm::open_capture_device(size_type card, size_type device, bool non_blocking) noexcept 696 | { 697 | self = lazy_init(self); 698 | if (!self) { 699 | return result { ENOMEM }; 700 | } 701 | 702 | char path[256]; 703 | 704 | snprintf(path, sizeof(path), "/dev/snd/pcmC%luD%luc", 705 | (unsigned long) card, 706 | (unsigned long) device); 707 | 708 | return self->open_by_path(path, non_blocking); 709 | } 710 | 711 | result pcm::open_playback_device(size_type card, size_type device, bool non_blocking) noexcept 712 | { 713 | self = lazy_init(self); 714 | if (!self) { 715 | return result { ENOMEM }; 716 | } 717 | 718 | char path[256]; 719 | 720 | snprintf(path, sizeof(path), "/dev/snd/pcmC%luD%lup", 721 | (unsigned long) card, 722 | (unsigned long) device); 723 | 724 | return self->open_by_path(path, non_blocking); 725 | } 726 | 727 | result pcm_impl::open_by_path(const char* path, bool non_blocking) noexcept 728 | { 729 | if (fd != invalid_fd()) { 730 | ::close(fd); 731 | } 732 | 733 | fd = ::open(path, non_blocking ? (O_RDWR | O_NONBLOCK) : O_RDWR); 734 | if (fd < 0) { 735 | fd = invalid_fd(); 736 | return result { errno }; 737 | } else { 738 | return result { 0 }; 739 | } 740 | } 741 | 742 | //===================// 743 | // Section: PCM list // 744 | //===================// 745 | 746 | namespace { 747 | 748 | /// A wrapper around a directory pointer 749 | /// that closes the directory when it goes out of scope. 750 | struct dir_wrapper final 751 | { 752 | /// A pointer to the opened directory. 753 | DIR* ptr = nullptr; 754 | /// Casts the wrapper to a directory pointer. 755 | /// 756 | /// @return The directory pointer. 757 | inline operator DIR* () noexcept 758 | { 759 | return ptr; 760 | } 761 | /// Closes the directory if it was opened. 762 | ~dir_wrapper() 763 | { 764 | if (ptr) { 765 | closedir(ptr); 766 | } 767 | } 768 | /// Opens a directory. 769 | /// 770 | /// @param path The path of the directory to open. 771 | /// 772 | /// @return True on success, false on failure. 773 | bool open(const char* path) noexcept 774 | { 775 | ptr = opendir(path); 776 | return !!ptr; 777 | } 778 | }; 779 | 780 | /// Represents a PCM name that was parsed 781 | /// from a directory entry. 782 | struct parsed_name final 783 | { 784 | /// Whether or not the name is valid. 785 | bool valid = false; 786 | /// The index of the card. 787 | size_type card = 0; 788 | /// The index of the device. 789 | size_type device = 0; 790 | /// Whether or not it's a capture PCM. 791 | bool is_capture = false; 792 | /// Constructs a new parsed name instance. 793 | /// 794 | /// @param name A pointer to the name to parse. 795 | constexpr parsed_name(const char* name) noexcept 796 | { 797 | valid = parse(name); 798 | } 799 | private: 800 | /// Parses a name given to the constructor. 801 | /// 802 | /// @param name The filename to be parsed. 803 | /// 804 | /// @return True on a match, false on failure. 805 | bool parse(const char* name) noexcept; 806 | /// Indicates if a character is a decimal number or not. 807 | /// 808 | /// @return True if it is a decimal character, false otherwise. 809 | static constexpr bool is_dec(char c) noexcept 810 | { 811 | return (c >= '0') && (c <= '9'); 812 | } 813 | /// Parses a decimal number. 814 | /// 815 | /// @param c The character to convert into a decimal number. 816 | /// 817 | /// @return The resultant decimal number. 818 | constexpr size_type to_dec(char c) noexcept 819 | { 820 | return size_type(c - '0'); 821 | } 822 | }; 823 | 824 | bool parsed_name::parse(const char* name) noexcept 825 | { 826 | auto name_length = strlen(name); 827 | if (!name_length) { 828 | return false; 829 | } 830 | 831 | if ((name[0] != 'p') 832 | || (name[1] != 'c') 833 | || (name[2] != 'm') 834 | || (name[3] != 'C')) { 835 | return false; 836 | } 837 | 838 | size_type d_pos = name_length; 839 | 840 | for (size_type i = 4; i < name_length; i++) { 841 | if (name[i] == 'D') { 842 | d_pos = i; 843 | break; 844 | } 845 | } 846 | 847 | if (d_pos >= name_length) { 848 | return false; 849 | } 850 | 851 | if (name[name_length - 1] == 'c') { 852 | is_capture = true; 853 | } else if (name[name_length - 1] == 'p') { 854 | is_capture = false; 855 | } else { 856 | return false; 857 | } 858 | 859 | device = 0; 860 | card = 0; 861 | 862 | for (size_type i = 4; i < d_pos; i++) { 863 | if (!is_dec(name[i])) { 864 | return false; 865 | } 866 | card *= 10; 867 | card += to_dec(name[i]); 868 | } 869 | 870 | for (size_type i = d_pos + 1; i < (name_length - 1); i++) { 871 | if (!is_dec(name[i])) { 872 | return false; 873 | } 874 | device *= 10; 875 | device += to_dec(name[i]); 876 | } 877 | 878 | return true; 879 | } 880 | 881 | } // namespace 882 | 883 | class pcm_list_impl final 884 | { 885 | friend pcm_list; 886 | /// The array of information instances. 887 | pod_buffer info_buffer; 888 | }; 889 | 890 | pcm_list::pcm_list() noexcept : self(nullptr) 891 | { 892 | self = new (std::nothrow) pcm_list_impl(); 893 | if (!self) { 894 | return; 895 | } 896 | 897 | dir_wrapper snd_dir; 898 | 899 | if (!snd_dir.open("/dev/snd")) { 900 | return; 901 | } 902 | 903 | dirent* entry = nullptr; 904 | 905 | for (;;) { 906 | 907 | entry = readdir(snd_dir); 908 | if (!entry) { 909 | break; 910 | } 911 | 912 | parsed_name name(entry->d_name); 913 | if (!name.valid) { 914 | continue; 915 | } 916 | 917 | result open_result; 918 | 919 | pcm p; 920 | 921 | if (name.is_capture) { 922 | open_result = p.open_capture_device(name.card, name.device); 923 | } else { 924 | open_result = p.open_playback_device(name.card, name.device); 925 | } 926 | 927 | if (open_result.failed()) { 928 | continue; 929 | } 930 | 931 | auto info_result = p.get_info(); 932 | if (info_result.failed()) { 933 | continue; 934 | } 935 | 936 | if (!self->info_buffer.emplace_back(info_result.unwrap())) { 937 | break; 938 | } 939 | } 940 | } 941 | 942 | pcm_list::pcm_list(pcm_list&& other) noexcept : self(other.self) 943 | { 944 | other.self = nullptr; 945 | } 946 | 947 | pcm_list::~pcm_list() 948 | { 949 | delete self; 950 | } 951 | 952 | const pcm_info* pcm_list::data() const noexcept 953 | { 954 | return self ? self->info_buffer.data : nullptr; 955 | } 956 | 957 | size_type pcm_list::size() const noexcept 958 | { 959 | return self ? self->info_buffer.size : 0; 960 | } 961 | 962 | } // namespace tinyalsa 963 | -------------------------------------------------------------------------------- /tinyalsa.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TINYALSA_CXX_TINYALSA_HPP 2 | #define TINYALSA_CXX_TINYALSA_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace tinyalsa { 9 | 10 | /// A type used to indicate array sizes 11 | /// and indices. 12 | using size_type = std::size_t; 13 | 14 | /// Gets the description of an error. 15 | /// 16 | /// @param error The error to get the description of. 17 | /// 18 | /// @return A description of the error. 19 | /// If there is no error (error == 0), then "Success" is returned. 20 | const char* get_error_description(int error) noexcept; 21 | 22 | /// This structure is used to show the 23 | /// result of an operation that may fail. 24 | /// 25 | /// @tparam value_type If the operation returns 26 | /// information other than an error code, then 27 | /// it is of this type. 28 | template 29 | struct generic_result final { 30 | /// An errno-type value to indicate success or failure. 31 | int error = 0; 32 | /// The result of the operation. 33 | value_type value = value_type(); 34 | /// Indicates whether the result is 35 | /// a failure or not. 36 | constexpr bool failed() const noexcept 37 | { 38 | return error != 0; 39 | } 40 | /// Gets a description of the error. 41 | /// 42 | /// @return A description of the error. 43 | /// If there was no error, then "Success" is returned. 44 | inline const char* error_description() const noexcept 45 | { 46 | return get_error_description(error); 47 | } 48 | /// Gets the resultant value of 49 | /// the operation. 50 | /// 51 | /// @return The result of the operation. 52 | value_type unwrap() noexcept 53 | { 54 | return std::move(value); 55 | } 56 | }; 57 | 58 | /// This structure is used to show 59 | /// the result of an operation 60 | /// that has no additional information 61 | /// other than the error code. 62 | template<> 63 | struct generic_result final 64 | { 65 | /// An errno-type value to indicate the result of the operation. 66 | int error = 0; 67 | /// Constructs a simple result instance. 68 | /// 69 | /// @param errno_copy An errno value to indicate whether 70 | /// or not the result was successful. 71 | constexpr generic_result(int errno_copy = 0) noexcept 72 | : error(errno_copy) { } 73 | /// Indicates whether the result is 74 | /// a failure or not. 75 | constexpr bool failed() const noexcept 76 | { 77 | return error != 0; 78 | } 79 | /// Gets a description of the error. 80 | /// 81 | /// @return A description of the error. 82 | /// If there was no error, then "Success" is returned. 83 | inline const char* error_description() const noexcept 84 | { 85 | return get_error_description(error); 86 | } 87 | }; 88 | 89 | /// A type definition for a result that 90 | /// has nothing except an errno value. 91 | using result = generic_result; 92 | 93 | /// Indicates the number assigned to 94 | /// invalid file descriptors. These can 95 | /// appear when either the PCM hasn't been 96 | /// opened yet or it failed to open. 97 | static constexpr int invalid_fd() noexcept 98 | { 99 | return -1; 100 | } 101 | 102 | /// A magic value used to indicate an invalid card number. 103 | static constexpr size_type invalid_card() noexcept 104 | { 105 | return 0xffff; 106 | } 107 | 108 | /// A magic value used to indicate an invalid device number. 109 | static constexpr size_type invalid_device() noexcept 110 | { 111 | return 0xffff; 112 | } 113 | 114 | /// A magic value used to indicate an invalid subdevice number. 115 | static constexpr size_type invalid_subdevice() noexcept 116 | { 117 | return 0xffff; 118 | } 119 | 120 | /// Enumerates the supported sample formats. 121 | enum class sample_format 122 | { 123 | s8, 124 | s16_le, 125 | s16_be, 126 | s18_3le, 127 | s18_3be, 128 | s20_3le, 129 | s20_3be, 130 | s24_3le, 131 | s24_3be, 132 | s24_le, 133 | s24_be, 134 | s32_le, 135 | s32_be, 136 | u8, 137 | u16_le, 138 | u16_be, 139 | u18_3le, 140 | u18_3be, 141 | u20_3le, 142 | u20_3be, 143 | u24_3le, 144 | u24_3be, 145 | u24_le, 146 | u24_be, 147 | u32_le, 148 | u32_be 149 | }; 150 | 151 | /// Used to query parameters about a certain 152 | /// sample format. 153 | /// 154 | /// @tparam sf The sample format to get the parameters for. 155 | template 156 | struct sample_traits final { }; 157 | 158 | template <> 159 | struct sample_traits final 160 | { 161 | bool is_signed() noexcept { return true; } 162 | }; 163 | 164 | /// Enumerates the several possible 165 | /// modes of accessing sample data from a PCM. 166 | enum class sample_access 167 | { 168 | /// Interleaved sample buffers. 169 | /// Samples of a frame appear next 170 | /// to each other in a single frame. 171 | /// Only one audio buffer is required. 172 | interleaved, 173 | /// Non interleaved sample buffers. 174 | /// Samples for a channel are separated 175 | /// into distinct buffers. Usually at least 176 | /// one or two audio buffers are required. 177 | non_interleaved, 178 | /// Memory mapped interleaved buffers. 179 | mmap_interleaved, 180 | /// Memory mapped non-interleaved buffers. 181 | mmap_non_interleaved, 182 | }; 183 | 184 | /// Enumerates the known PCM classes. 185 | enum class pcm_class 186 | { 187 | /// A placeholder for uninitialized values or errors. 188 | unknown, 189 | /// A generic mono or stereo device. 190 | generic, 191 | /// A multi channel device. 192 | multi_channel, 193 | /// A software modem calss. 194 | modem, 195 | /// A digitizer class. 196 | digitizer 197 | }; 198 | 199 | /// Converts a PCM class to a human-readable string. 200 | /// 201 | /// @param c The PCM class to convert. 202 | /// 203 | /// @return A human-readable string describing the PCM class. 204 | inline constexpr const char* to_string(pcm_class c) noexcept; 205 | 206 | /// Enumerates the known PCM sub-classes. 207 | enum class pcm_subclass 208 | { 209 | /// A placeholder for uninitialized values or errors. 210 | unknown, 211 | /// Mono or stereo sub-devices are mixed together. 212 | generic_mix, 213 | /// Multi-channel subdevices are mixed together. 214 | multi_channel_mix 215 | }; 216 | 217 | /// Converts a PCM subclass to a human-readable string. 218 | /// 219 | /// @param subclass The PCM subclass to be converted. 220 | /// 221 | /// @return A human-readable form of the subclass. 222 | inline constexpr const char* to_string(pcm_subclass subclass) noexcept; 223 | 224 | /// Used to describe the configuration 225 | /// of a PCM device. 226 | struct pcm_config final 227 | { 228 | /// The number of samples per frame. 229 | size_type channels = 2; 230 | /// The number of frames per second. 231 | size_type rate = 48000; 232 | /// The number of frames in one period. 233 | size_type period_size = 1024; 234 | /// The total number of periods. 235 | size_type period_count = 2; 236 | /// The format for one sample. 237 | sample_format format = sample_format::s16_le; 238 | /// The number of frames to buffer 239 | /// before starting playback or capture. 240 | size_type start_threshold = 0; 241 | /// The number of frames to buffer 242 | /// before stopping the playback or capture. 243 | size_type stop_threshold = 0; 244 | /// The number of frames to buffer 245 | /// before silencing the audio. 246 | size_type silence_threshold = 0; 247 | }; 248 | 249 | /// Contains information on a PCM device. 250 | struct pcm_info final 251 | { 252 | /// The card number of the PCM. 253 | size_type card = invalid_card(); 254 | /// The device number of the PCM. 255 | size_type device = invalid_device(); 256 | /// The subdevice number of the PCM. 257 | size_type subdevice = invalid_subdevice(); 258 | /// The PCM class identifier. 259 | pcm_class class_; 260 | /// The PCM subclass identifier. 261 | pcm_subclass subclass; 262 | /// The human readable device name. 263 | char id[64]; 264 | /// The name of the device. 265 | char name[80]; 266 | /// The name of the subdevice. 267 | char subname[32]; 268 | /// The number of of subdevices. 269 | size_type subdevices_count = 0; 270 | /// The number of available subdevices. 271 | size_type subdevices_available = 0; 272 | }; 273 | 274 | class pcm_impl; 275 | 276 | /// This is the base of any kind of PCM. 277 | /// The base class is responsible for interfacing 278 | /// with the file descriptor of the PCM device. 279 | class pcm 280 | { 281 | /// A pointer to the implementation data. 282 | pcm_impl* self = nullptr; 283 | public: 284 | /// Constructs an unopened PCM device. 285 | pcm() noexcept; 286 | /// Moves a PCM from one variable to another. 287 | /// 288 | /// @param other The PCM to be moved. 289 | pcm(pcm&& other) noexcept; 290 | /// Closes the PCM device. 291 | virtual ~pcm(); 292 | /// Closes the PCM device. 293 | /// This function does nothing if 294 | /// the PCM was already closed or was never opened. 295 | /// 296 | /// @return Zero on success. On failure, a copy 297 | /// of errno is returned. 298 | int close() noexcept; 299 | /// Accesses the underlying file descriptor of the PCM. 300 | /// This is useful when polling the file descriptor. 301 | /// 302 | /// @return The file descriptor of the PCM. 303 | /// If the PCM has not been opened yet, 304 | /// then @ref pcm::invalid_id is returned instead. 305 | int get_file_descriptor() const noexcept; 306 | /// Gets information on the PCM. 307 | /// 308 | /// @return A structure containing information on the PCM. 309 | generic_result get_info() const noexcept; 310 | /// Indicates whether or not the PCM is opened. 311 | /// 312 | /// @return True if the PCM is opened, 313 | /// false if it is not. 314 | bool is_open() const noexcept; 315 | /// Prepares the PCM to be started. 316 | /// 317 | /// @return On success, zero is returned. 318 | /// On failure, a copy of errno is returned. 319 | result prepare() noexcept; 320 | /// Starts the PCM. This can have different 321 | /// meanings depending on whether the PCM is 322 | /// a capture device or a playback device. 323 | /// 324 | /// If it is a playback device, this function 325 | /// tells the audio device to begin playing audio 326 | /// to the speaker. Ideally, there would be audio 327 | /// data buffered to the device at this point. 328 | /// 329 | /// If it is a capture device, this function 330 | /// tells the audio device to begin sending audio 331 | /// data to the host. 332 | /// 333 | /// @return On success, zero is returned. 334 | /// On failure, a copy of errno is returned. 335 | result start() noexcept; 336 | /// Stops either the playback or capture loop 337 | /// of the audio device. Any buffered audio that 338 | /// exist at the point of calling this function 339 | /// is lost. 340 | /// 341 | /// @return On success, zero is returned. 342 | /// On failure, a copy of errno is returned instead. 343 | result drop() noexcept; 344 | /// Opens a capture PCM. 345 | /// 346 | /// @param card The index of the card to open the PCM from. 347 | /// @param device The index of the device to open the PCM from. 348 | /// @param non_blocking Whether or not the PCM should be opened in non-blocking mode. 349 | result open_capture_device(size_type card = 0, size_type device = 0, bool non_blocking = true) noexcept; 350 | /// Opens a playback PCM. 351 | /// 352 | /// @param card The index of the card to open the PCM from. 353 | /// @param device The index of the device to open the PCM from. 354 | /// @param non_blocking Whether or not the PCM should be opened in non-blocking mode. 355 | result open_playback_device(size_type card = 0, size_type device = 0, bool non_blocking = true) noexcept; 356 | protected: 357 | /// Applys a configuration to a PCM. 358 | /// 359 | /// @param access The access mode to assign the PCM. 360 | /// This affects what read and write operations are available. 361 | /// 362 | /// @param is_capture Whether or not the PCM is a capture device. 363 | result setup(const pcm_config& config, sample_access access, bool is_capture) noexcept; 364 | }; 365 | 366 | class interleaved_reader 367 | { 368 | public: 369 | /// Reads unformatted data directly from the device. 370 | /// 371 | /// @param frames A pointer to the frame buffer to fill. 372 | /// @param frame_count The number of audio frames to be read. 373 | /// 374 | /// @return Both an error code and the number of read frames are returned. 375 | /// On success, the error code has a value of zero. 376 | /// On failure, the error code has an errno value. 377 | /// On failure, the number of read frames is zero. 378 | virtual generic_result read_unformatted(void* frames, size_type frame_count) noexcept = 0; 379 | }; 380 | 381 | class interleaved_pcm_reader final : public pcm, public interleaved_reader 382 | { 383 | public: 384 | /// Opens a new PCM reader. 385 | /// 386 | /// @param card The index of the card to open. 387 | /// @param device The index of the device to open. 388 | /// @param non_blocking Whether or not the call 389 | /// should block if the device is not available. 390 | result open(size_type card = 0, size_type device = 0, bool non_blocking = false) noexcept; 391 | /// Sets up the PCM with a given config. 392 | /// 393 | /// @param config The config to setup the PCM with. 394 | /// It's perfectly valid to leave out this parameter 395 | /// and use the default configuration. 396 | inline result setup(const pcm_config& config = pcm_config()) noexcept 397 | { 398 | return pcm::setup(config, sample_access::interleaved, true /* is capture */); 399 | } 400 | generic_result read_unformatted(void* frames, size_type frame_count) noexcept override; 401 | }; 402 | 403 | class pcm_list_impl; 404 | 405 | /// This class is used for enumerating 406 | /// the PCMs available on the system. 407 | /// 408 | /// The best way to use this class is to 409 | /// declare it in a function scope with a 410 | /// short life time. That way, the list is 411 | /// always up to date. 412 | class pcm_list final 413 | { 414 | /// A pointer to the implementation data. 415 | pcm_list_impl* self = nullptr; 416 | public: 417 | /// Constructs the PCM list. 418 | /// Internally, this constructor will discover the PCMs on the system. 419 | pcm_list() noexcept; 420 | /// Moves the PCM list from one variable to another. 421 | /// 422 | /// @param other The PCM list to move. 423 | pcm_list(pcm_list&& other) noexcept; 424 | /// Releases the memory allocated by the list. 425 | ~pcm_list(); 426 | /// Indicates the number of PCMs in the list. 427 | size_type size() const noexcept; 428 | /// Accesses the array of PCM info instances. 429 | /// 430 | /// @return A pointer to the beginning of the PCM info array. 431 | const pcm_info* data() const noexcept; 432 | /// Accesses an entry from the list. 433 | /// 434 | /// @note This function does not perform boundary checking. 435 | /// 436 | /// @param index The index of the entry to access. 437 | /// 438 | /// @return A const-reference to the specified entry. 439 | inline const pcm_info& operator [] (size_type index) const noexcept 440 | { 441 | return data()[index]; 442 | } 443 | /// Accesses the beginning iterator of the list. 444 | /// Used in range-based for loops. 445 | /// 446 | /// @return A pointer to the beginning of the list. 447 | inline const pcm_info* begin() const noexcept 448 | { 449 | return data(); 450 | } 451 | /// Accesses the ending iterator of the list. 452 | /// Used in range-based for loops. 453 | /// 454 | /// @return A pointer to the end of the list. 455 | /// This value should not be dereferenced. 456 | inline const pcm_info* end() const noexcept 457 | { 458 | return data() + size(); 459 | } 460 | }; 461 | 462 | /// Prints the result of an operation. 463 | /// If the result failed, then the error description 464 | /// is printed. If the result did not fail, then the value 465 | /// of the result is printed. 466 | /// 467 | /// @tparam stream_type The type of the stream being printed to. 468 | /// @tparam value_type The type of the value contained in the result structure. 469 | /// 470 | /// @param output The stream to print the result to. 471 | /// @param result The result to be printed. 472 | /// 473 | /// @return A reference to @p output. 474 | template 475 | stream_type& operator << (stream_type& output, const generic_result& res); 476 | 477 | /// Prints the error of a result. 478 | /// 479 | /// @param output The stream to print to. 480 | /// @param res The result to print the error of. 481 | /// 482 | /// @return A reference to @p output. 483 | template 484 | stream_type& operator << (stream_type& output, const result& res); 485 | 486 | template 487 | stream_type& operator << (stream_type& output, pcm_class class_); 488 | 489 | template 490 | stream_type& operator << (stream_type& output, pcm_subclass subclass); 491 | 492 | /// Prints a PCM information structure to an output stream. 493 | /// 494 | /// @param output The stream to print to. 495 | /// @param info The PCM information to be printed. 496 | /// 497 | /// @return A reference to @p output. 498 | template 499 | stream_type& operator << (stream_type& output, const pcm_info& info); 500 | 501 | // Template and inlined implementation below this point. 502 | 503 | template 504 | stream_type& operator << (stream_type& output, const generic_result& res) 505 | { 506 | if (res.failed()) { 507 | return output << get_error_description(res.error); 508 | } else { 509 | return output << res.value; 510 | } 511 | } 512 | 513 | template 514 | stream_type& operator << (stream_type& output, const result& res) 515 | { 516 | return output << get_error_description(res.error); 517 | } 518 | 519 | template 520 | stream_type& operator << (stream_type& output, pcm_class class_) 521 | { 522 | return output << to_string(class_); 523 | } 524 | 525 | template 526 | stream_type& operator << (stream_type& output, pcm_subclass subclass) 527 | { 528 | return output << to_string(subclass); 529 | } 530 | 531 | template 532 | stream_type& operator << (stream_type& output, const pcm_info& info) 533 | { 534 | output << "card : " << info.card << '\n'; 535 | output << "device : " << info.device << '\n'; 536 | output << "subdevice : " << info.subdevice << '\n'; 537 | output << "class : " << info.class_ << '\n'; 538 | output << "subclass : " << info.subclass << '\n'; 539 | output << "id : " << info.id << '\n'; 540 | output << "name: : " << info.name << '\n'; 541 | output << "subname : " << info.subname << '\n'; 542 | output << "subdevices count : " << info.subdevices_count << '\n'; 543 | output << "subdevices available : " << info.subdevices_available << '\n'; 544 | return output; 545 | } 546 | 547 | inline constexpr const char* to_string(pcm_class c) noexcept 548 | { 549 | switch (c) { 550 | case pcm_class::unknown: 551 | break; 552 | case pcm_class::generic: 553 | return "Generic"; 554 | case pcm_class::multi_channel: 555 | return "Multi-channel"; 556 | case pcm_class::modem: 557 | return "Modem"; 558 | case pcm_class::digitizer: 559 | return "Digitizer"; 560 | } 561 | 562 | return "Unknown"; 563 | } 564 | 565 | inline constexpr const char* to_string(pcm_subclass subclass) noexcept 566 | { 567 | switch (subclass) { 568 | case pcm_subclass::unknown: 569 | break; 570 | case pcm_subclass::generic_mix: 571 | return "Generic Mix"; 572 | case pcm_subclass::multi_channel_mix: 573 | return "Multi-channel Mix"; 574 | } 575 | 576 | return "Unknown"; 577 | } 578 | 579 | } // namespace tinyalsa 580 | 581 | #endif // TINYALSA_CXX_TINYALSA_HPP 582 | --------------------------------------------------------------------------------