Мультимедиа для Windows

         

Главное окно приложения WAVE



Рис. 2.5. Главное окно приложения WAVE

Это приложение по своему внешнему виду и функциям аналогично приложению MCIWAVER, которое было создано с использованием интерфейса MCI.

Приложение WAVE заказывает два глобальных блока памяти, один из которых используется при записи, другой - при воспроизведении. Исходный текст основного модуля приложения представлен в листинге 2.16.

Листинг 2.16. Файл wave\wave.cpp // ---------------------------------------- // Приложение WAVE // Проигрывание и запись wav-файлов на низком уровне // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <mmsystem.h> #include <mem.h> #pragma hdrstop #include "wave.hpp" #include "waveio.hpp" // Идентификатор таймера #define BEEP_TIMER 1 // Идентификатор полосы просмотра #define ID_SCROLL 10 // Длина полосы просмотра #define SCROLL_SIZE 400 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL WAVELoad(LPWAVEIOCB lpwiocb); // Глобальные переменные HWAVEOUT hWaveOut; WAVEIOCB waveiocbOut; HWAVEIN hWaveIn; WAVEIOCB waveiocbIn; int nMode = MODE_STOP; MMTIME mmtimeIn, mmtimeOut; BOOL fNeedSave = FALSE; BOOL fFileLoaded = FALSE; int nPosition; HWND hScroll; char const szClassName[] = "WaveClass"; char const szWindowTitle[] = "Wave Player/Recorder"; HINSTANCE hInst;


// ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(hPrevInstance) return FALSE; if(!InitApp(hInstance)) return FALSE; hInst = hInstance; hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // размеры и расположение окна CW_USEDEFAULT, 450, 120, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; int rc; switch (msg) { // ------------------------------------------------------------ // WM_CREATE // Создание главного окна приложения // ------------------------------------------------------------ case WM_CREATE: { nMode = MODE_STOP; fNeedSave = FALSE; fFileLoaded = FALSE; hWaveIn = NULL; hWaveOut = NULL; // Создаем таймер SetTimer(hwnd, BEEP_TIMER, 100, NULL); // Создаем полосу просмотра hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, 10, 40, SCROLL_SIZE, 15, hwnd, (HMENU) ID_SCROLL, hInst, NULL); // Устанавливаем текущую позицию nPosition = 0; // Устанавливаем минимальное и максимальное // значения для полосы просмотра SetScrollRange(hScroll, SB_CTL, 1, SCROLL_SIZE, TRUE); // Устанавливаем ползунок SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); return 0; } // ------------------------------------------------------------ // WM_PAINT // Рисование в окне // ------------------------------------------------------------ case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps); // Отображаем текущий режим работы if(nMode == MODE_STOP) TextOut(hdc, 10, 10, "Остановлено", 11); else if(nMode == MODE_RECORDING) TextOut(hdc, 10, 10, "Идет запись...", 14); else if(nMode == MODE_PLAYING) TextOut(hdc, 10, 10, "Идет проигрывание...", 20); else if(nMode == MODE_RECORDINGPAUSED) TextOut(hdc, 10, 10, "Запись остановлена", 18); else if(nMode == MODE_PLAYINGPAUSED) TextOut(hdc, 10, 10, "Проигрывание остановлено", 24); else TextOut(hdc, 10, 10, "Неправильный режим!", 19); // Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; } // ------------------------------------------------------------ // WM_COMMAND // Обработка сообщений от меню // ------------------------------------------------------------ case WM_COMMAND: { switch (wParam) { // ------------------------------------------------- // Строка "About" меню "Help" // ------------------------------------------------- case CM_HELPABOUT: { MessageBox(hwnd, "Wave, v.1.0\nWave Player/Recorder\n" "(C) Frolov A.V., 1994", "About Wave", MB_OK | MB_ICONINFORMATION); return 0; } // ------------------------------------------------- // Строка "Open" меню "File" // ------------------------------------------------- case CM_FILEOPEN: { // Если файл уже был загружен, возвращаем WAVE в // исходное состояние if(fFileLoaded) { if(hWaveOut) { // Останавливаем устройство вывода rc=waveOutReset(hWaveOut); if(rc) wioOutError(rc); // Закрываем устройство вывода rc=waveOutClose(hWaveOut); if(rc) wioOutError(rc); } // Освобождаем буфера, предназначенные для вывода GlobalFreePtr(waveiocbOut.lpWaveHdr); GlobalFreePtr(waveiocbOut.lpData); GlobalFreePtr(waveiocbOut.lpFmt); } // Загружаем новый файл if(!WAVELoad(&waveiocbOut)) return 0; // Устанавливаем флаг загрузки файла fFileLoaded = TRUE; return 0; } // ------------------------------------------------- // Строка "Play!" // Проигрывание загруженного wav-файла // ------------------------------------------------- case CM_CTLPLAY: { if(nMode == MODE_STOP) { // Если файл загружен и не проигрывается, // запускаем проигрывание файла if((fFileLoaded == TRUE) && (nMode != MODE_PLAYING)) { // Новый режим nMode = MODE_PLAYING; // Перерисовываем окно для отображения строки, // соответствующей новому режиму InvalidateRect(hwnd, NULL, TRUE); // Проигрываем файл rc=wioPlay(&waveiocbOut, hwnd); if(rc) wioOutError(rc); } } return 0; } // ------------------------------------------------- // Строка "Record!" // Запись wav-файла // ------------------------------------------------- case CM_CTLRECORD: { // Запись возможна только из состояния останова if(nMode == MODE_STOP) { nMode = MODE_RECORDING; InvalidateRect(hwnd, NULL, TRUE); // Требуется сохранить записанный файл fNeedSave = TRUE; // Запись файла wioRecord(&waveiocbIn, hwnd); } return 0; } // ------------------------------------------------- // Строка "Stop!" // Останов проигрывания или записи wav-файла // ------------------------------------------------- case CM_CTLSTOP: { if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED) { if(hWaveIn) { // Останавливаем запись rc=waveInReset(hWaveIn); if(rc) wioInError(rc); // Закрываем устройство записи rc=waveInClose(hWaveIn); if(rc) wioInError(rc); hWaveIn = 0; } } else if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED) { if(hWaveOut) { // Останавливаем проигрывание rc=waveOutReset(hWaveOut); if(rc) wioOutError(rc); // Закрываем устройство вывода rc=waveOutClose(hWaveOut); if(rc) wioOutError(rc); hWaveOut = 0; } } // Устанавливаем движок в начало полосы просмотра nPosition = 0; SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); // Новый режим nMode = MODE_STOP; InvalidateRect(hwnd, NULL, TRUE); return 0; } // ------------------------------------------------- // Строка "Pause!" // Временный останов проигрывания или // полный останов записи wav-файла // ------------------------------------------------- case CM_CTLPAUSE: { if(nMode == MODE_RECORDING) { // Останов записи rc=waveInStop(hWaveIn); if(rc) wioInError(rc); nMode = MODE_STOP; } else if(nMode == MODE_PLAYING) { // Временный останов проигрывания rc=waveOutPause(hWaveOut); if(rc) wioOutError(rc); nMode = MODE_PLAYINGPAUSED; } InvalidateRect(hwnd, NULL, TRUE); return 0; } // ------------------------------------------------- // Строка "Resume!" // Продолжение проигрывания после останова // ------------------------------------------------- case CM_CTLRESUME: { if(nMode == MODE_PLAYINGPAUSED) { rc=waveOutRestart(hWaveOut); if(rc) wioOutError(rc); nMode = MODE_PLAYING; InvalidateRect(hwnd, NULL, TRUE); } return 0; } // ------------------------------------------------- // Строка "Exit" меню "File" // Завершение работы приложения // ------------------------------------------------- case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } // ------------------------------------------------------------ // MM_WOM_DONE // Завершение проигрывания блока // ------------------------------------------------------------ case MM_WOM_DONE: { nMode = MODE_STOP; nPosition = 0; SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); InvalidateRect(hwnd, NULL, TRUE); // Удаляем блок из очереди проигрывания waveOutUnprepareHeader((HWAVEOUT)wParam, (LPWAVEHDR)lParam, sizeof(WAVEHDR)); // Останавливаем и закрываем устройство вывода waveOutReset((HWAVEOUT)wParam); waveOutClose((HWAVEOUT)wParam); hWaveOut = 0; return 0; } // ------------------------------------------------------------ // MM_WIM_DATA // Завершение записи блока // ------------------------------------------------------------ case MM_WIM_DATA: { nMode = MODE_STOP; nPosition = 0; SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); InvalidateRect(hwnd, NULL, TRUE); // Удаляем блок из очереди записи waveInUnprepareHeader((HWAVEIN)wParam, (LPWAVEHDR)lParam, sizeof(WAVEHDR)); // Сохраняем записанный блок в файле wioFileSave("RECORDED.WAV"); fNeedSave = FALSE; // Освобождаем буфера, связанные с блоком GlobalFreePtr(waveiocbIn.lpWaveHdr); GlobalFreePtr(waveiocbIn.lpData); // Останавливаем и закрываем устройство ввода waveInReset((HWAVEIN)wParam); waveInClose((HWAVEIN)wParam); hWaveIn = 0; return 0; } // ------------------------------------------------------------ // WM_TIMER // Сообщение от таймера // ------------------------------------------------------------ case WM_TIMER: { if(nMode == MODE_RECORDING) { // Определяем текущую позицию внутри проигрываемого блока mmtimeIn.wType = TIME_SAMPLES; waveInGetPosition(hWaveIn, (LPMMTIME)&mmtimeIn, sizeof(MMTIME)); // Вычисляем новое положение движка полосы просмотра nPosition = ((DWORD)SCROLL_SIZE * mmtimeIn.u.sample) / MAXSAMPLES; // Ограничиваем пределы изменения текущей // позиции значениями от 1 до SCROLL_SIZE if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE; if(nPosition < 1) nPosition = 1; // Устанавливаем ползунок полосы просмотра // в соответствии с новым значением текущей позиции SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); } else if(nMode == MODE_PLAYING) { // Определяем текущую позицию внутри записываемого блока mmtimeOut.wType = TIME_SAMPLES; waveOutGetPosition(hWaveOut, (LPMMTIME)&mmtimeOut, sizeof(MMTIME)); // Вычисляем новое положение движка полосы просмотра nPosition = ((DWORD)SCROLL_SIZE * mmtimeOut.u.sample) / (waveiocbOut.dwDataSize / waveiocbOut.wBytesPerSample); // Ограничиваем пределы изменения текущей // позиции значениями от 1 до SCROLL_SIZE if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE; if(nPosition < 1) nPosition = 1; // Устанавливаем ползунок полосы просмотра // в соответствии с новым значением текущей позиции SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); } return 0; } // ------------------------------------------------------------ // WM_DESTROY // Уничтожение главного окна приложения // ------------------------------------------------------------ case WM_DESTROY: { // Удаляем таймер и полосу просмотра KillTimer(hwnd, BEEP_TIMER); DestroyWindow(hScroll); // Если находимся в режиме записи, останавливаем // запись и закрываем устройство ввода if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED) { if(hWaveIn) { rc=waveInReset(hWaveIn); if(rc) wioInError(rc); rc=waveInClose(hWaveIn); if(rc) wioInError(rc); } } // Если запись началась, но еще не закончилась, после // остановки записи удаляем буфера if(fNeedSave) { GlobalFreePtr(waveiocbIn.lpWaveHdr); GlobalFreePtr(waveiocbIn.lpData); } else if(fFileLoaded) { // Если находимся в режиме проигрывания, останавливаем // запись и закрываем устройство вывода if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED) { if(hWaveOut) { rc=waveOutReset(hWaveOut); if(rc) wioOutError(rc); rc=waveOutUnprepareHeader(hWaveOut, waveiocbOut.lpWaveHdr, sizeof(WAVEHDR)); if(rc) wioOutError(rc); rc=waveOutClose(hWaveOut); if(rc) wioOutError(rc); } } nMode = MODE_STOP; // Освобождаем буфера GlobalFreePtr(waveiocbOut.lpWaveHdr); GlobalFreePtr(waveiocbOut.lpData); GlobalFreePtr(waveiocbOut.lpFmt); } PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }

После стандартной инициализации запускается цикл обработки сообщений. При создании главного окна приложения во время обработки сообщения WM_CREATE устанавливаются начальные значения переменных, создается таймер с периодом работы примерно 100 мс, создается и инициализируется полоса просмотра, которая используется как индикатор текущей позиции при записи и воспроизведении.

Обработчик сообщения WM_PAINT отображает в верхней части главного окна текстовую строку, соответствующую текущему режиму работы приложения (переменная nMode).

При выборе из меню "File" строки "Open" приложение проверяет, не был ли раньше загружен файл. Если файл был загружен и устройство вывода открыто, это устройство останавливается функцией waveOutReset и закрывается функцией waveOutClose. После этого освобождаются буфера, которые использовались для вывода.

Затем вызывается функция WAVELoad, определенная в нашем приложении в файле waveio.cpp (листинг 2.18). В переменной fFileLoaded устанавливается признак того, что файл загружен и готов для воспроизведения.

Для проигрывания файла нужно выбрать из главного меню приложения строку "Play!". Соответствующий обработчик проверит текущий режим работы приложения. Если wav-файл загружен, и приложение уже не находится в режиме воспроизведения, вызывается функция wioPlay, выполняющая проигрывание файла. Эта функция также определена в нашем приложении в файле waveio.cpp.

С помощью строки "Record!" главного меню приложения можно включить режим записи (если приложение находится в состоянии останова). При этом устанавливается флаг fNeedSave, который используется как признак необходимости сохранения записанных звуковых данных в wav-файле. Запись выполняется функцией wioRecord, определенной в файле waveio.cpp.

В любой момент времени можно остановить запись или воспроизведение, если из главного меню приложения выбрать строку "Stop!". При этом, если приложение находилось в режиме записи, выполняется сброс и закрытие устройства записи, а если в режиме воспроизведения - сброс и закрытие устройства воспроизведения. В любом случае движок полосы просмотра устанавливается в начальное, самое левое, положение, так как после сброса устройства начальная позиция равна нулю.

Строка "Pause!" главного меню приложения предназначена для останова записи и временного останова воспроизведения. В режиме записи устройство останавливается функцией waveInStop. В режиме воспроизведения вызывается функция waveOutPause, выполняющая временный останов.

Для продолжения воспроизведения после временного останова из главного меню приложения следует выбрать строку "Resume!". В этом случае будет вызвана функция waveOutRestart, которая возобновит работу устройства вывода.

При завершении процесса проигрывания блока главное окно приложения получит сообщение MM_WOM_DONE. Обработчик этого сообщения установит полосу просмотра в исходное состояние, удалит блок из очереди проигрывания, вызвав функцию waveOutUnprepareHeader, после чего остановит и закроет устройство вывода.

В режиме записи при достижении конца блока памяти или при останове записи главное окно приложения получит сообщение MM_WIM_DATA. Обработчик этого сообщения также установит полосу просмотра в исходное состояние и удалит блок из очереди записи, вызвав функцию waveInUnprepareHeader. Затем содержимое блока будет сохранено в wav-файле с именем recorded.wav, который будет создан или перезаписан в текущем каталоге. Для записи файла вызывается функция wioFileSave, определенная в файле waveio.cpp.

Далее освобождаются буфера, использовавшиеся при записи, устройство записи останавливается и закрывается.

Сообщения, поступающие от таймера, используются для обновления положения движка полосы просмотра в соответствии с текущей позицией устройства записи (в режиме записи) или устройства воспроизведения (в режиме воспроизведения).

В режиме записи с помощью функции waveInGetPosition определяется текущая позиция в формате TIME_SAMPLES: mmtimeIn.wType = TIME_SAMPLES; waveInGetPosition(hWaveIn, (LPMMTIME)&mmtimeIn, sizeof(MMTIME));

Новое положение движка полосы просмотра в режиме записи определяется на основании текущей позиции и максимального размера буфера записи MAXSAMPLES, заданного как константа: nPosition = ((DWORD)SCROLL_SIZE * mmtimeIn.u.sample) / MAXSAMPLES;

В режиме воспроизведения для вычисления положения движка используется размер загруженного wav-файла: nPosition = ((DWORD)SCROLL_SIZE * mmtimeOut.u.sample) / (waveiocbOut.dwDataSize / waveiocbOut.wBytesPerSample);

При завершении работы приложения обработчик сообщения WM_DESTROY удаляет таймер и полосу просмотра. Если работа приложения завершается во время записи, устройство записи останавливается и закрывается, а соответствующие глобальные буфера освобождаются. Аналогичные действия выполняются и в режиме воспроизведения.

Файл wave.hpp содержит определения символических имен констант (листинг 2.17).

Листинг 2.15. Файл wave\wave.hpp #define CM_HELPABOUT 301 #define CM_FILEEXIT 302 #define CM_FILEOPEN 303 #define CM_FILESAVEAS 304 #define CM_FILENEW 305 #define CM_CTLPLAY 401 #define CM_CTLRECORD 402 #define CM_CTLRESUME 403 #define CM_CTLPAUSE 404 #define CM_CTLSTOP 405

Исходные тексты всех функций, используемых для работы с wav-файлами на низком уровне, определены в файле waveio.cpp (листинг 2.18).

Листинг 2.18. Файл wave\waveio.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mmsystem.h> #include <mem.h> #pragma hdrstop #include "waveio.hpp" BOOL WAVEPlay(HWND); extern WAVEIOCB waveiocbOut; extern WAVEIOCB waveiocbIn; extern HWAVEOUT hWaveOut; extern HWAVEIN hWaveIn; extern int nMode; extern int nPosition; //----------------------------------------------------- // WAVELoad // Загрузка wav-файла для проигрывания //----------------------------------------------------- BOOL WAVELoad(LPWAVEIOCB lpwiocb) { BYTE szFileName[256]; OPENFILENAME ofn; int rc; BYTE szBuf[256]; // Проверяем наличие драйвера, способного выводить // звуковые файлы rc=waveOutGetNumDevs(); if(!rc) { MessageBox(NULL, (LPSTR)"Нет устройств для вывода звуковых файлов", "Wave Error", MB_OK | MB_ICONHAND); return FALSE; } // Выбираем wav-файл if(!wioSelectFile(szFileName)) return FALSE; // Открываем и загружаем в память выбранный файл rc = wioFileOpen(lpwiocb, (LPSTR)szFileName); if(rc == WIOERR_NOERROR) return TRUE; else if(rc == WIOERR_FILEERROR) lstrcpy(szBuf, "Ошибка при открытии файла"); else if(rc == WIOERR_BADFORMAT) lstrcpy(szBuf, "Неправильный или неподдерживаемый формат файла"); else if(rc == WIOERR_NOMEM) lstrcpy(szBuf, "Мало памяти"); else if(rc == WIOERR_READERROR) lstrcpy(szBuf, "Ошибка при чтении"); else lstrcpy(szBuf, "Неизвестная ошибка"); MessageBox(NULL, (LPSTR)szBuf, "Wave Error", MB_OK | MB_ICONHAND); return FALSE; } //----------------------------------------------------- // wioSelectFile // Выбор wav-файла //----------------------------------------------------- BOOL wioSelectFile(LPSTR lpszFileName) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Wave Files\0*.wav\0Any Files\0*.*\0"; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Копируем путь к выбранному файлу lstrcpy(lpszFileName, (LPSTR)szFile); return TRUE; } else return FALSE; } //--------------------------------------------------------- // wioFileOpen // Открытие и загрузка wav-файла //--------------------------------------------------------- int wioFileOpen(LPWAVEIOCB lpwiocb, LPSTR lpszFileName) { HMMIO hmmio; MMCKINFO ckRIFF, ckFMT; DWORD dwFmtSize; // Открываем wav-файл hmmio = mmioOpen((LPSTR)lpszFileName, NULL, MMIO_READ | MMIO_ALLOCBUF); if(!hmmio) return WIOERR_FILEERROR; // Ищем фрагмент "WAVE" memset(&ckRIFF, 0, sizeof(MMCKINFO)); ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E'); if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF)) { mmioClose(hmmio,0); return WIOERR_BADFORMAT; } // Ищем фрагмент "fmt " memset(&ckFMT, 0, sizeof(MMCKINFO)); ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' '); if(mmioDescend(hmmio, &ckFMT, &ckRIFF, MMIO_FINDCHUNK)) { mmioClose(hmmio,0); return WIOERR_BADFORMAT; } dwFmtSize = sizeof(PCMWAVEFORMAT); //ckFMT.cksize; // Получаем память для загрузки формата звукового файла lpwiocb->lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR, dwFmtSize); if(!lpwiocb->lpFmt) { mmioClose(hmmio,0); return WIOERR_NOMEM; } // Загружаем формат звукового файла if(mmioRead(hmmio, (HPSTR)lpwiocb->lpFmt, dwFmtSize) != (LONG)dwFmtSize) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_READERROR; } // Проверяем формат звукового файла if(lpwiocb->lpFmt->wf.wFormatTag != WAVE_FORMAT_PCM) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_BADFORMAT; } // Проверяем способность драйвера работать с указанным форматом if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC)) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_BADFORMAT; } // Вычисляем количество байт, необходимых для // хранения одной выборки звукового сигнала lpwiocb->wBitsPerSample = lpwiocb->lpFmt->wBitsPerSample; lpwiocb->wBytesPerSample = (waveiocbOut.wBitsPerSample/8) * waveiocbOut.lpFmt->wf.nChannels; // Ищем фрагмент "data" mmioAscend(hmmio, &ckFMT, 0); ckFMT.ckid = mmioFOURCC('d', 'a', 't', 'a'); if(mmioDescend(hmmio, &ckFMT, &ckRIFF, MMIO_FINDCHUNK)) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_BADFORMAT; } // Определяем размер фрагмента сегмента звуковых // данных и его смещение в wav-файле lpwiocb->dwDataSize = ckFMT.cksize; lpwiocb->dwDataOffset = ckFMT.dwDataOffset; // Проверяем, что файл не пуст if(lpwiocb->dwDataSize == 0L) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_BADFORMAT; } // Получаем память для заголовка блока lpwiocb->lpWaveHdr = (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR)); if(!lpwiocb->lpWaveHdr) return WIOERR_NOMEM; // Получаем память для звуковых данных lpwiocb->lpData = (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, lpwiocb->dwDataSize); if(!lpwiocb->lpData) return WIOERR_NOMEM; // Позиционирование на начало звуковых данных mmioSeek(hmmio, SEEK_SET, lpwiocb->dwDataOffset); // Читаем звуковые данные mmioRead(hmmio, lpwiocb->lpData, lpwiocb->dwDataSize); // Закрываем wav-файл mmioClose(hmmio,0); return WIOERR_NOERROR; } //--------------------------------------------------------- // wioPlay // Проигрывание загруженного блока звуковых данных //--------------------------------------------------------- int wioPlay(LPWAVEIOCB lpwiocb, HWND hwnd) { WORD rc; // Открываем устройство вывода rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC); if(rc) return rc; // Заполняем заголовок блока данных lpwiocb->lpWaveHdr->lpData = (LPSTR)lpwiocb->lpData; lpwiocb->lpWaveHdr->dwBufferLength = lpwiocb->dwDataSize; lpwiocb->lpWaveHdr->dwBytesRecorded = 0; lpwiocb->lpWaveHdr->dwFlags = 0; lpwiocb->lpWaveHdr->dwLoops = 0; lpwiocb->lpWaveHdr->dwUser = 0; lpwiocb->lpWaveHdr->lpNext = 0; lpwiocb->lpWaveHdr->reserved = 0; // Подготавливаем заголовок для вывода rc = waveOutPrepareHeader(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); if(rc) { GlobalFreePtr(lpwiocb->lpWaveHdr); return rc; } // Запускаем проигрывание блока rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); if(rc) { waveOutUnprepareHeader(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); GlobalFreePtr(lpwiocb->lpWaveHdr); return rc; } return 0; } //--------------------------------------------------------- // wioRecord // Запись звуковых данных //--------------------------------------------------------- int wioRecord(LPWAVEIOCB lpwiocb, HWND hwnd) { int rc; // Проверяем наличие драйвера, способного // выполнять запись звука rc = waveInGetNumDevs(); if(!rc) { MessageBox(NULL, (LPSTR)"Нет устройств для записи звуковых файлов", "Wave Error", MB_OK | MB_ICONHAND); return WIOERR_NODEVICE; } // Максимальный размер блока в байтах lpwiocb->dwDataSize = MAXSAMPLES; // Получаем память для заголовка блока lpwiocb->lpWaveHdr = (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR)); if(!lpwiocb->lpWaveHdr) return WIOERR_NOMEM; // Получаем память для блока звуковых данных lpwiocb->lpData = (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, lpwiocb->dwDataSize); if(!lpwiocb->lpData) return WIOERR_NOMEM; // Получаем память для блока формата lpwiocb->lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR, sizeof(WAVEFORMAT)); if(!lpwiocb->lpFmt) { return WIOERR_NOMEM; } // Заполняем блок формата. Наше приложение способно // записывать монофонические файлы в формате WAVE_FORMAT_PCM // с частотой дискретизации 22,05 Кгц lpwiocb->lpFmt->wf.wFormatTag = WAVE_FORMAT_PCM; lpwiocb->lpFmt->wf.nChannels = 1; lpwiocb->lpFmt->wf.nSamplesPerSec = 22050; lpwiocb->lpFmt->wf.nAvgBytesPerSec = 22050; lpwiocb->lpFmt->wf.nBlockAlign = 1; // Открываем устройство записи rc=waveInOpen(&hWaveIn, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC); if(rc) { wioInError(rc); return WIOERR_BADFORMAT; } // Заполняем заголовок блока lpwiocb->lpWaveHdr->lpData = (LPSTR)lpwiocb->lpData; lpwiocb->lpWaveHdr->dwBufferLength = lpwiocb->dwDataSize; lpwiocb->lpWaveHdr->dwFlags = 0L; lpwiocb->lpWaveHdr->dwLoops = 0L; lpwiocb->lpWaveHdr->dwUser = 0L; // Подготавливаем блок для записи rc = waveInPrepareHeader(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); if(rc) { wioInError(rc); GlobalFreePtr(lpwiocb->lpWaveHdr); return WIOERR_BADFORMAT; } // Передаем блок устройству записи rc = waveInAddBuffer(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); if(rc) { wioInError(rc); waveInUnprepareHeader(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR)); GlobalFreePtr(lpwiocb->lpWaveHdr); GlobalFreePtr(lpwiocb->lpFmt); GlobalFreePtr(lpwiocb->lpData); return WIOERR_ERROR; } // Запускаем запись rc = waveInStart(hWaveIn); if(rc) wioInError(rc); return TRUE; } //--------------------------------------------------------- // wioFileSave // Сохранение записанного звука в wav-файле //--------------------------------------------------------- BOOL wioFileSave(LPSTR szFileName) { DWORD dwDataSize; char szdata[] = "data"; HMMIO hFile; MMCKINFO ck; WORD wBitsPerSample = 8; char szfmt[] = "fmt "; DWORD dwFmtSize = sizeof(PCMWAVEFORMAT); // Создаем новый файл или перезаписываем существующий hFile = mmioOpen(szFileName, NULL, MMIO_CREATE | MMIO_READWRITE); if(hFile != NULL) { // Создаем заголовок wav-файла ck.ckid = MMIO_CREATERIFF; ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded + sizeof(PCMWAVEFORMAT) + 20; ck.fccType = mmioFOURCC('W', 'A', 'V', 'E'); mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF); // Записываем фрагмент "fmt " mmioWrite(hFile, (HPSTR)szfmt, 4); mmioWrite(hFile, (HPSTR)&dwFmtSize, sizeof(DWORD)); mmioWrite(hFile, (HPSTR)waveiocbIn.lpFmt, sizeof(WAVEFORMAT)); mmioWrite(hFile, (HPSTR)&wBitsPerSample, sizeof(WORD)); mmioWrite(hFile, (HPSTR)szdata, 4); dwDataSize = waveiocbIn.lpWaveHdr->dwBytesRecorded; mmioWrite(hFile, (HPSTR)&dwDataSize, sizeof(DWORD)); // Записываем данные mmioWrite(hFile, (HPSTR)waveiocbIn.lpData, waveiocbIn.lpWaveHdr->dwBytesRecorded); // Закрываем файл mmioClose(hFile, 0); return TRUE; } return FALSE; } //--------------------------------------------------------- // wioOutError // Вывод сообщения об ошибке в процессе проигрывания //--------------------------------------------------------- void wioOutError(int rc) { BYTE szBuf[MAXERRORLENGTH]; if(waveOutGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) == MMSYSERR_BADERRNUM) { lstrcpy(szBuf, "Unknown Error"); } MessageBox(NULL, (LPSTR)szBuf, "Wave Error", MB_OK | MB_ICONHAND); } //--------------------------------------------------------- // wioInError // Вывод сообщения об ошибке в процессе записи //--------------------------------------------------------- void wioInError(int rc) { BYTE szBuf[MAXERRORLENGTH]; if(waveInGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) == MMSYSERR_BADERRNUM) { lstrcpy(szBuf, "Unknown Error"); } MessageBox(NULL, (LPSTR)szBuf, "Wave Error", MB_OK | MB_ICONHAND); }

Функция WAVLoad предназначена для выбора и загрузки проигрываемого wav-файла.

В самом начале своей работы она определяет наличие драйвера, способного воспроизводить звуковую информацию, для чего вызывает функцию waveOutGetNumDevs. Если нужный драйвер есть, вызывается функция wioSelectFile, предоставляющая пользователю возможность выбрать файл при помощи стандартной диалоговой панели "Open". Выбранный файл открывается и загружается в память функцией wioFileOpen.

Функция wioSelectFile не имеет никаких особенностей. Для выбора файла в ней используется функция GetOpenFileName из библиотеки commdlg.dll. Путь к выбранному файлу копируется в буфер, адрес которого передается функции wioSelectFile в качестве единственного параметра.

Функция wioFileOpen выполняет всю работу по загрузке и анализу wav-файла.

Для открытия файла используется функция mmioOpen. При этом файл открывается на чтение с использованием буферизации, для чего в последнем параметре функции указаны флаги MMIO_READ и MMIO_ALLOCBUF.

Далее в wav-файле ищутся фрагменты "WAVE" и "fmt ", для чего используется функция mmioDescend: memset(&ckRIFF, 0, sizeof(MMCKINFO)); ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E'); if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF)) { mmioClose(hmmio,0); return WIOERR_BADFORMAT; } memset(&ckFMT, 0, sizeof(MMCKINFO)); ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' '); if(mmioDescend(hmmio, &ckFMT, &ckRIFF, MMIO_FINDCHUNK)) { mmioClose(hmmio,0); return WIOERR_BADFORMAT; }

После этого приложение заказывает память для структуры PCMWAVEFORMAT, которая находится во фрагменте "fmt " и содержит сведения о формате wav-файла. Загрузка данных в структуру выполняется функцией mmioRead: if(mmioRead(hmmio, (HPSTR)lpwiocb->lpFmt, dwFmtSize) != (LONG)dwFmtSize) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_READERROR; }

Далее проверяется формат звукового файла: поле wFormatTag структуры WAVEFORMAT должно содержать значение WAVE_FORMAT_PCM.

Для того чтобы определить, может ли установленный в системе драйвер звукового адаптера работать с форматом загруженного файла, вызывается функция waveOutOpen с флагом WAVE_FORMAT_QUERY: if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC)) { GlobalFreePtr(lpwiocb->lpFmt); mmioClose(hmmio,0); return WIOERR_BADFORMAT; }

Так как пользователь может загрузить wav-файл любого формата, как монофонический, так и стереофонический, с использованием 8 или 16 битов для представления одной выборки сигнала, приложение должно динамически определять количество байт памяти, необходимых для хранения одной выборки сигнала.

После анализа формата наше приложение ищет фрагмент "data", в котором находятся звуковые данные. Для поиска используется функция mmioDescend. После определения размера фрагмента данных приложение заказывает память для заголовка блока и самого блока, пользуясь макрокомандой GlobalAllocPtr.

Перед чтением звуковых данных в заказанный буфер выполняется позиционирование на начало звуковых данных, для чего используется функция mmioSeek. Чтение данных выполняется функцией mmioRead. Сразу после чтения файл закрывается функцией mmioClose, так как он нам больше не нужен.

Функция wioPlay проигрывает загруженный блок. Адрес блока и его заголовка передаются функции через первый параметр. Через второй параметр функция wioPlay получает идентификатор окна, в которое после завершения проигрывания поступит сообщение MM_WOM_DONE.

Прежде всего функция wioPlay открывает устройство вывода, вызывая для этого функцию waveOutOpen: rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt, (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);

В качестве номера устройства мы указали константу WAVE_MAPPER, поэтому будет выбрано любое устройство, подходящее для проигрывания данных указанного формата.

После этого функция wioPlay заполняет заголовок блока данных, записывая в него адрес и размер буфера, содержащего звуковые данные. Во все неиспользуемые поля записываются нулевые значения.

Затем заголовок блока подготавливается для вывода при помощи функции waveOutPrepareHeader: rc = waveOutPrepareHeader(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));

Для запуска процесса проигрывания вызывается функция waveOutWrite: rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));

Функция wioRecord запускает процесс записи.

В начале своей работы она проверяет, установлен ли в системе драйвер, способный выполнять запись звука, вызывая для этого функцию waveInGetNumDevs. Далее заказывается память для заголовка блока данных и самого блока данных. Так как приложение WAVE может записывать только монофонические wav-файлы с 8-битовым представлением звуковых данных, размер буфера для записи численно равен константе MAXSAMPLES (максимальный размер буфера в выборках сигнала).

Затем функция wioRecord заказывает память для блока формата и заполняет этот блок. Для простоты мы использовали только один формат, а именно монофонический 8-битовый формат с частотой дискретизации 22,05 Кгц.

После заполнения блока формата функция открывает устройство записи, заполняет заголовок блока. Заполненный заголовок подготавливается для записи, для чего вызывается функция waveInPrepareHeader.

Далее блок передается устройству записи функцией waveInAddBuffer: rc = waveInAddBuffer(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));

Для запуска записи вызывается функция waveInStart: rc = waveInStart(hWaveIn);

Функция wioFileSave сохраняет содержимое буфера записи в wav-файле.

Для создания нового или перезаписи существующего файла он открывается функцией mmioOpen с флагами MMIO_CREATE и MMIO_READWRITE.

Далее создается заголовок файла при помощи функции mmioCreateChunk: ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded + sizeof(PCMWAVEFORMAT) + 20; ck.fccType = mmioFOURCC('W', 'A', 'V', 'E'); mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF);

При этом в заголовке указывается длина с учетом объема записанных звуковых данных.

После этого с помощью функции mmioWrite в файл записывается фрагмент "fmt " и содержимое буфера звуковых данных. Файл закрывается функцией mmioClose.

Для обработки ошибок в процессе воспроизведения используется функция wioOutError, которая с помощью функции waveOutGetErrorText преобразует код ошибки, передаваемый через параметр, в текстовое сообщение и выводит его на экран. Аналогичные действия выполняются при записи функцией wioInError, преобразующей код ошибки в текстовую строку при помощи функции waveInGetErrorText.

Файл waveio.hpp (листинг 2.19) содержит определение структуры WAVEIOCB и указателей на нее, а также определения констант и прототипы функций. Структура WAVEIOCB используется функциями записи и проигрывания блоков звуковых данных.

Листинг 2.19. Файл wave\waveio.hpp #include <windows.h> #include <mmsystem.h> typedef struct tagWAVEIOCB { DWORD dwDataSize; DWORD dwDataOffset; PCMWAVEFORMAT FAR *lpFmt; LPWAVEHDR lpWaveHdr; HPSTR lpData; WORD wBitsPerSample; WORD wBytesPerSample; } WAVEIOCB, *PWAVEIOCB, FAR *LPWAVEIOCB; #define WIOERR_BASE (100) #define WIOERR_NOERROR (0) #define WIOERR_ERROR (WIOERR_BASE+1) #define WIOERR_BADHANDLE (WIOERR_BASE+2) #define WIOERR_BADFLAGS (WIOERR_BASE+3) #define WIOERR_BADPARAM (WIOERR_BASE+4) #define WIOERR_BADSIZE (WIOERR_BASE+5) #define WIOERR_FILEERROR (WIOERR_BASE+6) #define WIOERR_NOMEM (WIOERR_BASE+7) #define WIOERR_BADFILE (WIOERR_BASE+8) #define WIOERR_NODEVICE (WIOERR_BASE+9) #define WIOERR_BADFORMAT (WIOERR_BASE+10) #define WIOERR_ALLOCATED (WIOERR_BASE+11) #define WIOERR_NOTSUPPORTED (WIOERR_BASE+12) #define WIOERR_READERROR (WIOERR_BASE+13) #define MAXSAMPLES 1024000L #define MODE_STOP 0 #define MODE_PLAYING 1 #define MODE_RECORDING 2 #define MODE_PLAYINGPAUSED 3 #define MODE_RECORDINGPAUSED 4 BOOL wioSelectFile(LPSTR); int wioFileOpen(LPWAVEIOCB, LPSTR); int wioPlay(LPWAVEIOCB, HWND); int wioRecord(LPWAVEIOCB, HWND); BOOL wioFileSave(LPSTR szFileName); void wioOutError(int rc); void wioInError(int rc);

Файл определения ресурсов приложения WAVE представлен в листинге 2.20.

Листинг 2.20. Файл wave\wave.rc #include "wave.hpp" APPICON ICON "wave.ico" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&Open...", CM_FILEOPEN MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END MENUITEM "&Play!", CM_CTLPLAY MENUITEM "&Stop!", CM_CTLSTOP MENUITEM "Resu&me!", CM_CTLRESUME MENUITEM "P&ause!", CM_CTLPAUSE MENUITEM "&Record!", CM_CTLRECORD POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END

Файл определения модуля приложения WAVE вы сможете найти в листинге 2.21.

Листинг 2.21. Файл wave\wave.def NAME WAVE DESCRIPTION 'Приложение WAVE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 10240 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Содержание    Назад