Теперь же мой самодельный аудиодвижок научился выводить звук.
Я реализовал это засчет низкоуровневых API waveOut/waveIn - первому API для вывода и ввода аудио, который появился ещё во времена Windows 3.0 и 3.1 в начале 90-х.
Как мы уже знаем, для того, что открыть аудиоустройство вывода необходимо на вход функции waveOutOpen внести его идентификатор и информацию о формате, имеющую структуру WAVEFORMAT или WAVEFORMATEX.
И тут я столкнулся с такой проблемой, что WAVEFORMAT в целом не совместим с этой APIшкой. Даже если запускать программу с виндой помладше, мы все равно получим ошибку WAVERR_BADFORMAT.
Более того, в официальной документации Win32 Programmer’s Reference 1994 года присутствует только упоминание структуры WAVEFORMAT, когда как описание WAVEFORMATEX находится в заголовке mmsystem.h.
В общем, только с WAVEFORMATEX удалось заставить винду взаимодействовать с звуковой подсистемой.
А поскольку аудиодорожка составляет почти весь файл как есть и без промежутков, легче поделить её на кадры.
Для воспроизведения WAV-файла выделяется буфер размером в 32 кадра. Один кадр содержит 1024 сэмпла, размер сэмпла зависит от разрядности и количества аудиоканалов. Таким образом, получаем размер одного кадра в байтах:
8 бит, моно = 1024 * 1 * 1 = 1024 байта = 1 кБ 16 бит, моно = 1024 * 2 * 1 = 2048 байт = 2 кБ 16 бит, стерео = 1024 * 2 * 2 = 4096 байт = 4 кБ
Конечно, такой подход имеет существенный недостаток - необходимо предугадать конец буфера перед заполнением новых данных, чтобы процесс декодирования аудиофайлов не заставлял операционную систему либо скакать с одного места на другую, либо искусственно завышать задержку.
Системная функция Sleep() такой точности не обладает. Мало того, что даже простая проверка флагов для WAVEHDR не спасает, так еще и на коллбек предстоит посмотреть поближе - а вдруг состояние готовности не совсем означает готовность залить следующий кадр перед выводом. Посмотрим.
Не обошлось и без нюанса, связанный с отображением имени аудиоустройства.
Строку с этим именем обязательно копируем в другой строковый указатель, чтобы случайно не повредить данные, а то SetConsoleCursorPosition очень любит стирать данные от прошлых Win32шных функций.