{"id":1813,"date":"2016-02-24T09:00:49","date_gmt":"2016-02-24T08:00:49","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=1813"},"modified":"2023-07-25T09:15:33","modified_gmt":"2023-07-25T07:15:33","slug":"niskopoziomowa-obsluga-audio-w-windows","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/niskopoziomowa-obsluga-audio-w-windows\/","title":{"rendered":"Niskopoziomowa obs\u0142uga audio w Windows"},"content":{"rendered":"\n<p>Przedmiotem artyku\u0142u b\u0119dzie obs\u0142uga d\u017awi\u0119ku w systemie Windows poprzez natywne API systemu. Na co dzie\u0144 mamy do dyspozycji r\u00f3\u017cnego rodzaju frameworki i biblioteki, kt\u00f3re du\u017co pracy wykonuj\u0105 za nas. Do obs\u0142ugi audio powsta\u0142o ju\u017c wiele takowych &#8211; chocia\u017cby BASS (C++), NAudio (.NET) i wiele innych&#8230; Dzisiaj jednak spr\u00f3bujemy odwo\u0142a\u0107 si\u0119 do karty d\u017awi\u0119kowej niskopoziomowo &#8211; stworzymy prosty program, kt\u00f3ry pozwoli na odtworzenie pewnych danych audio.<br>Zacznijmy od do\u0142\u0105czenia pliku nag\u0142\u00f3wkowego &#8222;mmsystem.h&#8221; oraz poinformowania linkera o u\u017cyciu pliku &#8222;winmm.lib&#8221;, czyli:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include \"mmsystem.h\"\n#pragma comment(lib, \"winmm.lib\"). \/\/oczywi\u015bcie mo\u017cna r\u00f3wnie\u017c zrobi\u0107 to w spos\u00f3b \"klasyczny\" - w opcjach linkera.\n<\/pre><\/div>\n\n\n<p>Zanim przejdziemy do meritum, czyli do samego odtwarzania, zastan\u00f3wmy si\u0119 jakie dane chcemy odtwarza\u0107. Mamy kilka mo\u017cliwo\u015bci &#8211; odtworzenie jakiego\u015b pliku WAV, strumienia danych z sieci, etc&#8230; mo\u017cemy r\u00f3wnie\u017c sami wygenerowa\u0107 sobie d\u017awi\u0119k kt\u00f3ry p\u00f3\u017aniej odtworzymy. Skorzystamy z ostatniej opcji &#8211; przygotujemy prosty algorytm za pomoc\u0105 kt\u00f3rego wygenerowana zostanie tablica bajt\u00f3w b\u0119d\u0105ca \u017ar\u00f3d\u0142em danych wysy\u0142anych do karty d\u017awi\u0119kowej.<br>Dla uproszczenia przyjmiemy, \u017ce dane d\u017awi\u0119kowe zapisane b\u0119d\u0105 w prostym formacie:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>1 kana\u0142 (czyli mono)<\/li>\n\n\n\n<li>8 bit\u00f3w na pr\u00f3bk\u0119<\/li>\n\n\n\n<li>8000 pr\u00f3bek na sekund\u0119.<\/li>\n<\/ul>\n\n\n\n<p>Czyli na ka\u017cd\u0105 sekund\u0119 nale\u017cy wygenerowa\u0107 8000 pr\u00f3bek 1 bajtowych.<br>Algorytm przedstawia si\u0119 nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nchar buffer&#x5B;8000 * 10]; \/\/bufor na pr\u00f3bki o wielko\u015bci 10 sekund * 8000 pr\u00f3bek\nfor (DWORD t = 0; t &lt; sizeof(buffer); ++t)\n{\nbuffer&#x5B;t] = (t * 5 &amp; t &gt;&gt; 7) | (t * 3 &amp; t &gt;&gt; 10);\n}\n<\/pre><\/div>\n\n\n<p>Oczywi\u015bcie jest to tylko przyk\u0142ad algorytmu tego rodzaju. Zach\u0119cam czytelnika do eksperymentowania i zapoznania si\u0119 z ciekawymi publikacjami na ten temat, gdy\u017c mo\u017cna osi\u0105gn\u0105\u0107 bardzo ciekawe efekty. Dla zainteresowanych polecam link:<br>http:\/\/countercomplex.blogspot.com\/2011\/10\/algorithmic-symphonies-from-one-line-of.html<\/p>\n\n\n\n<p>Teraz przejd\u0119 do w\u0142a\u015bciwej cz\u0119\u015bci artyku\u0142u, czyli odtwarzania naszego d\u017awieku.<br>Na pocz\u0105tek trzeba wype\u0142ni\u0107 struktur\u0119 opisuj\u0105c\u0105 format audio. Ta struktura to WAVEFORMATEX a przedstawia si\u0119 ona nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\ntypedef struct tWAVEFORMATEX\n{\nWORD        wFormatTag;         \/* identyfikator formatu *\/\nWORD        nChannels;          \/* liczba kana\u0142\u00f3w (mono, stereo itp...) *\/\nDWORD       nSamplesPerSec;     \/* ilo\u015b\u0107 pr\u00f3bek na sekund\u0119 *\/\nDWORD       nAvgBytesPerSec;    \/* wymagana szybko\u015b\u0107 transferu dancyh. Dla formatu PCM, wynosi nSamplesPerSec \u00d7 nBlockAlign. *\/\nWORD        nBlockAlign;        \/* minimalna wielko\u015b\u0107 paczki danych. Dla formatu PCM, wynosi (nChannels \u00d7 wBitsPerSample) \/ 8. *\/\nWORD        wBitsPerSample;     \/* ilo\u015b\u0107 bit\u00f3w dla jednej pr\u00f3bki *\/\nWORD        cbSize;             \/* ilo\u015b\u0107 (w bajtach) danych dodatkowych (dla innych format\u00f3w) *\/\n\/* dane dodatkowe (je\u015bli s\u0105) *\/\n} WAVEFORMATEX\n<\/pre><\/div>\n\n\n<p>Tutaj powy\u017csza struktura ma posta\u0107:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nWAVEFORMATEX wfx;\nwfx.wFormatTag = WAVE_FORMAT_PCM; \/\/zdefiniowany w WINAPI identyfikator formatu PCM\nwfx.wBitsPerSample =8;\nwfx.nSamplesPerSec = 8000;\nwfx.nChannels = 1;\nwfx.nBlockAlign = 1;\nwfx.nAvgBytesPerSec = 8000;\nwfx.cbSize = 0;\n<\/pre><\/div>\n\n\n<p>Nast\u0119pn\u0105 struktur\u0105 kt\u00f3r\u0105 musimy wype\u0142ni\u0107 jest WAVEHDR:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\ntypedef struct wavehdr_tag\n{\nLPSTR       lpData;                 \/* bufor z danymi audio *\/\nDWORD       dwBufferLength;         \/* d\u0142ugo\u015b\u0107 bufora audio *\/\nDWORD       dwBytesRecorded;        \/* ilo\u015b\u0107 nagranych danych (tylko do u\u017cytku przy nagrywaniu) *\/\nDWORD_PTR   dwUser;                 \/* inne dane u\u017cytkownika *\/\nDWORD       dwFlags;                \/* flagi stanu, p\u0119tli *\/\nDWORD       dwLoops;                \/* licznik p\u0119tli *\/\nstruct wavehdr_tag FAR *lpNext;     \/* zarezerwowane *\/\nDWORD_PTR   reserved;               \/* zarezerwowane *\/\n} WAVEHDR\n<\/pre><\/div>\n\n\n<p>W tym przypadku b\u0119dziemy u\u017cywa\u0107 tylko dw\u00f3ch p\u00f3l i b\u0119dzie mia\u0142a ona posta\u0107:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nWAVEHDR header;\nmemset(&header, 0, sizeof(WAVEHDR)); \/\/zerujemy wszystkie pola struktury\nheader.lpData = buffer;\nheader.dwBufferLength = sizeof(buffer);\n<\/pre><\/div>\n\n\n<p>Dane wej\u015bciowe s\u0105 ju\u017c przygotowane, wi\u0119c nadesz\u0142a pora na wydobycie d\u017awi\u0119ku z naszego komputera.<br>Na pocz\u0105tek nale\u017cy sprawdzi\u0107 czy mo\u017cna w og\u00f3le odtworzy\u0107 dane w formacie zdefiniowanym w strukturze WAVEFORMATEX. W tym celu wykorzystamy funkcj\u0119 WaveOutOpen, kt\u00f3ra przedstawia si\u0119 nast\u0119puj\u0105co.<br>(dok\u0142adny opis w MSDN: https:\/\/msdn.microsoft.com\/en-us\/library\/windows\/desktop\/dd743866(v=vs.85).aspx)<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nMMRESULT waveOutOpen(\nLPHWAVEOUT     phwo, \/\/wska\u017anik do zmiennej typu HWAVEOUT w kt\u00f3rej przechowywany b\u0119dzie uchwyt do urz\u0105dzenia audio\nUINT_PTR       uDeviceID, \/\/identyfikator urz\u0105dzenia audio w systemie. Je\u015bli podamy WAVE_MAPPER (zdefiniowany w WINAPI), system sam wybierze odpowiednie urz\u0105dzenie\nLPWAVEFORMATEX pwfx, \/\/wska\u017anik do struktury WAVEFORMATEX definiuj\u0105cej nasz format audio\nDWORD_PTR      dwCallback, \/\/wska\u017anik do funkcji callback, uchwyt do okna, identyfikator w\u0105tku lub NULL (szczeg\u00f3\u0142y na MSDN)\nDWORD_PTR      dwCallbackInstance, \/\/dane kt\u00f3re zostan\u0105 przekazane do funkcji callback\nDWORD          fdwOpen \/\/Flagi definiuj\u0105ce tryb wywo\u0142ywania tej funkcji (szczeg\u00f3\u0142y na MSDN)\n);\n<\/pre><\/div>\n\n\n<p>Powy\u017csz\u0105 funkcj\u0119 wywo\u0142ujemy z parametrami:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nwaveOutOpen(NULL, WAVE_MAPPER, &wfx, NULL, 0, WAVE_FORMAT_QUERY);\n<\/pre><\/div>\n\n\n<p>Je\u015bli wywo\u0142anie tej funkcji zwr\u00f3ci MMSYSERR_NOERROR (czyli 0), mo\u017cna pr\u00f3bowa\u0107 otworzy\u0107 urz\u0105dzenie audio. W tym celu wykorzystujemy t\u0105 sam\u0105 funkcj\u0119, tylko z nieco innymi parametrami:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nHWAVEOUT hWaveOut;\nwaveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION);\n<\/pre><\/div>\n\n\n<p>Funkcja callback do kt\u00f3rej wska\u017anik przekazujemy, wed\u0142ug dokumentacji przedstawia si\u0119 nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nvoid CALLBACK waveOutProc(\nHWAVEOUT  hwo, \/\/uchwyt do urz\u0105dzenia audio\nUINT      uMsg, \/\/wiadomo\u015b\u0107 przekazana do funkcji. Mo\u017ce przyjmowa\u0107 warto\u015bci WOM_OPEN, WOM_DONE, WOM_CLOSE\nDWORD_PTR dwInstance, \/\/dane przekazane w funkcji WaveOutOpen\nDWORD_PTR dwParam1, \/\/ parametr wiadomo\u015bci (zale\u017cy od typu wiadomo\u015bci uMsg)\nDWORD_PTR dwParam2 \/\/ parametr wiadomo\u015bci (zale\u017cy od typu wiadomo\u015bci uMsg)\n);\n<\/pre><\/div>\n\n\n<p>W naszym przypadku, ma ona posta\u0107:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nvoid CALLBACK waveOutProc(HWAVEOUT  hwo,UINT  uMsg, DWORD_PTR dwInstance,   DWORD_PTR dwParam1, DWORD_PTR dwParam2)\n{\n \nswitch (uMsg)\n{\ncase WOM_DONE:\nSetEvent(onPlaybackEnd);\nbreak;\ncase WOM_CLOSE:\nbreak;\ncase WOM_OPEN:\nbreak;\n}\n}\n<\/pre><\/div>\n\n\n<p>Funkcj\u0119 t\u0105 bardziej szczeg\u00f3\u0142owo om\u00f3wi\u0119 w dalszej cz\u0119\u015bci artyku\u0142u.<br>Skoro mamy ju\u017c zdefiniowan\u0105 funkcj\u0119 callback, oraz mo\u017cemy otworzy\u0107 urz\u0105dzenie audio, nic nie stoi na przeszkodzie aby wys\u0142a\u0107 do niego nasze dane.<br>W tym celu najpierw wywo\u0142ujemy funkcj\u0119 waveOutPrepareHeader kt\u00f3ra ma nast\u0119puj\u0105c\u0105 deklaracj\u0119:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nMMRESULT waveOutPrepareHeader(\nHWAVEOUT  hwo, \/\/uchwyt do urz\u0105dzenia audio\nLPWAVEHDR pwh, \/\/wska\u017anik do struktury WAVEHDR opisuj\u0105cej blok danych audio\nUINT      cbwh \/\/wielko\u015b\u0107 (w bajtach) struktury WAVEHDR\n);\n<\/pre><\/div>\n\n\n<p>W naszym przypadku mamy:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nwaveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));\n<\/pre><\/div>\n\n\n<p>Je\u015bli funkcja nie zwr\u00f3ci nam b\u0142\u0119du, pozostaje ju\u017c tylko wywo\u0142a\u0107 funkcj\u0119 kt\u00f3ra wysy\u0142a dane audio do karty d\u017awi\u0119kowej, czyli funkcj\u0119 waveOutWrite:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nMMRESULT waveOutWrite(\nHWAVEOUT  hwo, \/\/ uchwyt do urz\u0105dzenia audio\nLPWAVEHDR pwh, \/\/ wska\u017anik do struktury WAVEHDR opisuj\u0105cej blok danych audio\nUINT      cbwh \/\/wielko\u015b\u0107 (w bajtach) struktury WAVEHDR\n);\n<\/pre><\/div>\n\n\n<p>U nas wygl\u0105da to nast\u0119puj\u0105co:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nwaveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));\n<\/pre><\/div>\n\n\n<p>Je\u015bli wszystko przebieg\u0142o pomy\u015blnie, powinni\u015bmy us\u0142ysze\u0107 efekty naszej pracy. Pozosta\u0142o ju\u017c tylko po sobie posprz\u0105ta\u0107, czyli pozamyka\u0107 odpowiednie uchwyty itp&#8230;<br>Zanim to zrobimy, po\u015bwi\u0119c\u0119 chwil\u0119 na om\u00f3wienie funkcji callback kt\u00f3ra pojawi\u0142a si\u0119 wcze\u015bniej.<br>Funkcja ta posiada parametr uMsg, kt\u00f3ry mo\u017ce przyj\u0105\u0107 jedn\u0105 z trzech warto\u015bci:<\/p>\n\n\n\n<p>WOM_CLOSE &#8211; w przypadku kiedy urz\u0105dzenie jest zamykane przy u\u017cyciu funkcji waveOutClose<br>WOM_DONE &#8211; w przypadku kiedy odtwarzanie bufora zostanie zako\u0144czone<br>WOM_OPEN &#8211; w przypadku kiedy urz\u0105dzenie jest otwierane przy pomocy funkcji waveOutOpen<\/p>\n\n\n\n<p>Interesuje nas przypadek, kiedy przyjdzie wiadomo\u015b\u0107 WOM_DONE &#8211; informacja o zako\u0144czeniu odtwarzania. Jak ju\u017c wcze\u015bniej wspomnia\u0142em, w tym momencie nale\u017cy pozamyka\u0107 u\u017cyte zasoby (b\u0105d\u017a przes\u0142a\u0107 kolejne dane &#8211; ale o tym p\u00f3\u017aniej). Tutaj pojawia si\u0119 pewien problem, gdy\u017c zgodnie z dokumentacj\u0105 &#8211; wywo\u0142anie z poziomu funkcji callback, jakichkolwiek funkcji poza: EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, timeSetEvent mo\u017ce spowodowa\u0107 zawieszenie aplikacji (deadlock).<br>Aby unikn\u0105\u0107 k\u0142opot\u00f3w z tym zwi\u0105zanych, pos\u0142u\u017cymy si\u0119 zdarzeniem systemowym.<br>Na pocz\u0105tku programu nale\u017cy utworzy\u0107 uchwyt do zdarzenia (jako zmienna globalna):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nHANDLE onPlaybackEnd; \/\/deklarujemy jako zmienn\u0105 globaln\u0105\n\/\/...\nonPlaybackEnd = CreateEvent(NULL, false, false, NULL); \/\/uchwyt musimy utworzy\u0107 przed wywo\u0142aniem funkcji waveOutWrite.\n<\/pre><\/div>\n\n\n<p>Nast\u0119pnie po wywo\u0142aniu funkcji waveOutWrite, wywo\u0142ujemy funkcj\u0119:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nWaitForSingleObject(onPlaybackEnd, INFINITE);\n<\/pre><\/div>\n\n\n<p>W tym momencie program czeka na zasygnalizowanie zdarzenia onPlaybackEnd, aby mo\u017cna by\u0142o rozpocz\u0105\u0107 zwalnianie u\u017cytych zasob\u00f3w. Teraz ju\u017c wida\u0107 co si\u0119 dzieje w funkcji callback &#8211; w momencie otrzymania wiadomo\u015bci o zako\u0144czeniu odtwarzania (WOM_DONE) ustawiane jest zdarzenie onPlaybackEnd, czyli:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nSetEvent(onPlaybackEnd);\n<\/pre><\/div>\n\n\n<p>Teraz program mo\u017ce przyst\u0105pi\u0107 do zwalniania zasob\u00f3w, czyli wywo\u0142ania funkcji waveoutUnprepareHeader, oraz waveOutClose. Maj\u0105 one nast\u0119puj\u0105ce deklaracje:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nMMRESULT waveOutUnprepareHeader(\nHWAVEOUT  hwo, \/\/uchwyt do urz\u0105dzenia audio\nLPWAVEHDR pwh, \/\/wska\u017anik do struktury WAVEHDR opisuj\u0105cej blok danych audio\nUINT      cbwh \/\/wielko\u015b\u0107 (w bajtach) struktury WAVEHDR\n);\n \nMMRESULT waveOutClose(\nHWAVEOUT hwo \/\/uchwyt do urz\u0105dzenia audio\n);\n<\/pre><\/div>\n\n\n<p>Wywo\u0142ujemy je w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nwaveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));\n<\/pre><\/div>\n\n\n<p>Je\u015bli nie otrzymamy b\u0142\u0119du, wywo\u0142ujemy:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nwaveOutClose(hWaveOut);\n<\/pre><\/div>\n\n\n<p>Tutaj r\u00f3wnie\u017c nale\u017cy sprawdzi\u0107, czy funkcja nie zwr\u00f3ci\u0142a warto\u015bci oznaczaj\u0105cej b\u0142\u0105d. Teraz ju\u017c tylko wystarczy zamkn\u0105\u0107 uchwyt do zdarzenia onPlaybackEnd:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\nCloseHandle(onPlaybackEnd);\n<\/pre><\/div>\n\n\n<p>Mamy ju\u017c opanowany najprostszy przypadek w kt\u00f3rym wys\u0142ali\u015bmy jednorazowo do karty d\u017awi\u0119kowej wszystkie dane. Nie uwzgl\u0119dnili\u015bmy jednak jednej bardzo wa\u017cnej kwestii &#8211; danych do odtworzenia mo\u017ce by\u0107 bardzo du\u017co (np. musimy odtworzy\u0107 plik WAV o wielko\u015bci kilkuset megabajt\u00f3w). Nie jest dobr\u0105 praktyk\u0105 wys\u0142anie tak wielkiej porcji danych za pomoc\u0105 funkcji waveOutWrite.<br>Pierwsz\u0105 rzecz\u0105 kt\u00f3ra przychodzi na my\u015bl jest cykliczne wysy\u0142anie danych w cz\u0119\u015bciach, np. po 512 bajt\u00f3w, w skr\u00f3cie:<br>&#8211; przygotowujemy struktur\u0119 WAVEHDR<br>&#8211; wywo\u0142ujemy waveOutPrepareHeader<br>&#8211; wywo\u0142ujemy waveOutWrite<br>&#8211; czekamy na callBack (WOM_DONE)<br>&#8211; wywo\u0142ujemy waveOutUnprepareHeader<br>&#8211; aktualizujemy struktur\u0119 WAVEHDR przestawiaj\u0105c wska\u017anik do danych o 512 bajt\u00f3w<br>&#8211; wywo\u0142ujemy waveOutPrepareHeader<br>&#8211; wywo\u0142ujemy waveOutWrite&#8230;<br>&#8230; i tak a\u017c do zako\u0144czenia danych wej\u015bciowych. Pomys\u0142 wydaje si\u0119 dobry, jednak odtwarzaj\u0105c w ten spos\u00f3b pojawi\u0105 si\u0119 kr\u00f3tkie, aczkolwiek s\u0142yszalne przerwy w pomi\u0119dzy ka\u017cdymi 512 bajtami danych. Jak temu zaradzi\u0107? Z pomoc\u0105 przychodzi algorytm zwany &#8222;double buffering&#8221;.<br>Polega on na tym, \u017ce na pocz\u0105tku przygotowujemy nie jeden, a dwa (lub wi\u0119cej) bufory danych, nast\u0119pnie wysy\u0142amy do karty d\u017awi\u0119kowej dane z obydwu bufor\u00f3w. Potem czekamy na wiadomo\u015b\u0107 o zako\u0144czeniu odtwarzania danych z pierwszego z nich. W tym momencie (podczas gdy dane z drugiego bufora s\u0105 ci\u0105gle odtwarzane) przygotowujemy dane dla pierwszego bufora (aktualizujemy struktur\u0119 WAVEHDR) i wysy\u0142amy dane do karty d\u017awi\u0119kowej. Gdy dostaniemy informacj\u0119 o zako\u0144czeniu przetwarzania drugiego bufora, aktualizujemy go (w tym czasie odtwarzana jest kolejna porcja z bufora pierwszego) i wysy\u0142amy do urz\u0105dzenia audio. Powtarzamy ca\u0142y cykl a\u017c do wyczerpania naszych danych wej\u015bciowych. W ten spos\u00f3b wyeliminowali\u015bmy przerwy pomi\u0119dzy odtwarzaniem kolejnych porcji danych, poniewa\u017c karta d\u017awi\u0119kowa ca\u0142y czas ma dane do odtworzenia i nie musi czeka\u0107 na dostarczenie nowych danych. Analogiczna metoda znajduje r\u00f3wnie\u017c zastosowanie w wy\u015bwietlaniu grafiki na ekranie monitora &#8211; zapobiega &#8222;migotaniu&#8221; obrazu.<\/p>\n\n\n\n<p>Tak w du\u017cym skr\u00f3cie wygl\u0105da odtwarzanie danych audio w przy u\u017cyciu WINAPI. Oczywi\u015bcie nie om\u00f3wi\u0142em tutaj wszystkich aspekt\u00f3w i funkcji API zwi\u0105zanych z tym zagadnieniem. Zach\u0119cam czytelnika do samodzielnego eksperymentowania z tym tematem. Oczywi\u015bcie istniej\u0105 inne &#8211; prostsze interfejsy do obs\u0142ugi multimedi\u00f3w (np. MCI), oraz rozmaite biblioteki. Warto jednak wiedzie\u0107 co kryje si\u0119 &#8222;pod mask\u0105&#8221; tych bibliotek.<\/p>\n\n\n\n<p>Poni\u017cej listing programu z podw\u00f3jnym buforowaniem:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include \u201estdafx.h\u201d\n#include \u201ewindows.h\u201d\n#include \u201estdio.h\u201d\n#include \u201emmsystem.h\u201d\n\n#pragma comment(lib, \u201ewinmm.lib\u201d)\n\n#define BUFFER_LENGTH 8000*10 \/\/10 sekund * 8000 pr\u00f3bek\n#define AUDIO_BUFFER_LENGTH 512\n#define BUFFERS_COUNT 3\n\nvoid CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);\nvoid PrepareNextBuffer(HWAVEOUT *hWaveOut, WAVEHDR* hdr, char* buffer);\nDWORD CalculateReadLength();\nHANDLE onPlaybackEnd;\nUINT position = 0;\nUINT currentBuffer = 0;\n\nint _tmain(int argc, _TCHAR* argv&#x5B;])\n{\nonPlaybackEnd = CreateEvent(NULL, false, false, NULL);\nif (onPlaybackEnd == NULL)\n{\nprintf(\u201eERROR \u2013 CreateEvent\u201d);\nreturn 0;\n}\n\nHWAVEOUT hWaveOut = 0;\nchar buffer&#x5B;BUFFER_LENGTH];\nfor (DWORD t = 0; t &gt; 7) | (t * 3 &amp; t &gt;&gt; 10);\n}\n\nWAVEFORMATEX wfx;\nmemset(&amp;wfx, 0, sizeof(WAVEFORMATEX));\nwfx.wFormatTag = WAVE_FORMAT_PCM;\nwfx.wBitsPerSample = 8;\nwfx.nSamplesPerSec = 8000;\nwfx.nChannels = 1;\nwfx.nBlockAlign = 1;\nwfx.nAvgBytesPerSec = 8000;\nwfx.cbSize = 0;\n\nWAVEHDR* headers = new WAVEHDR&#x5B;BUFFERS_COUNT];\nfor (int i = 0; i lpData = buffer + (position*AUDIO_BUFFER_LENGTH);\n(headers + i)-&gt;dwBufferLength = CalculateReadLength();\nposition++;\n}\n\nif (waveOutOpen(NULL, WAVE_MAPPER, &amp;wfx, NULL, 0, WAVE_FORMAT_QUERY) != MMSYSERR_NOERROR)\n{\nprintf(\u201eERROR \u2013 waveOutOpen \u2013 format query\u201d);\nCloseHandle(onPlaybackEnd);\ndelete&#x5B;] headers;\nreturn 0;\n}\n\nif (waveOutOpen(&amp;hWaveOut, WAVE_MAPPER, &amp;wfx, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)\n{\nprintf(\u201eERROR \u2013 waveOutOpen\u201d);\nCloseHandle(onPlaybackEnd);\ndelete&#x5B;] headers;\n\nreturn 0;\n}\n\nfor (int i = 0; i lpData = buffer + (position*AUDIO_BUFFER_LENGTH);\nhdr-&gt;dwBufferLength = CalculateReadLength();\nif (waveOutPrepareHeader(*hWaveOut, hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)\n{\nprintf(\u201eERROR \u2013 waveotPrepareHeader\u201d);\n}\nif (waveOutWrite(*hWaveOut, hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR)\n{\nprintf(\u201eERROR \u2013 waveoutWrite\u201d);\n}\nprintf(\u201ePosition: %i\\n\u201d, position*AUDIO_BUFFER_LENGTH + CalculateReadLength());\nposition++;\n\n}\n\nDWORD CalculateReadLength()\n{\nif ((position + 1)*AUDIO_BUFFER_LENGTH\n<\/pre><\/div>\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;1813&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;1&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( vote: 1)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Niskopoziomowa obs\u0142uga audio w Windows&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( vote: 1)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Przedmiotem artyku\u0142u b\u0119dzie obs\u0142uga d\u017awi\u0119ku w systemie Windows poprzez natywne API systemu. Na co dzie\u0144 mamy do dyspozycji r\u00f3\u017cnego rodzaju &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/niskopoziomowa-obsluga-audio-w-windows\/\">Continued<\/a><\/p>\n","protected":false},"author":77,"featured_media":15438,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1314],"tags":[129,249,248,250],"class_list":["post-1813","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-c","tag-audio","tag-winapi","tag-windows"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2016\/02\/windows_audio-1.jpg","category_names":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/1813"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/77"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=1813"}],"version-history":[{"count":2,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/1813\/revisions"}],"predecessor-version":[{"id":23159,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/1813\/revisions\/23159"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/15438"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=1813"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=1813"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=1813"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}