Загрузка и проигрывание WAV с помощью интерфейса низкого уровня

 

В примере звукозапись можно увидеть как релизуется запись, проигрывание записанного буфера и сохранение в файл WAV. Пример ниже реализует загрузку звука из файла и его воспроизведение. Данный пример (не весь полностью) я взял из книги Фроловых "Мультимедиа для Windows". Книга писалась еще для древних Windows, однако практически все в ней актульно по сей день. После переписывания исходника на freebasic, я столкнулся с глючностью некоторых функций, при работе на windows7. Так функция (или наверно правильней макрос) mmioFOURCC не возвращает значение, пришлось самому вбивать нужные значения. Другая функция mmioSeek разучилась позиционировать указатель по файлу. В принципе оно и понятно, ведь все функции , начинающиеся с преффикса mmio.. считаются устаревшими и давно не поддерживаются мелкософтом. В другом своем примере (выложу через несколько дней), я решил избавиться от функций mmio.. Там я делал загрузку данных из файла с помощью функций CreateFile, ReadFile и пр.

Платформа: Windows
Авторы: Братья Фроловы

#INCLUDE "windows.bi"
#INCLUDE "win/commdlg.bi"
#INCLUDE "win/mmsystem.bi"
#INCLUDE "crt.bi"

#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 WAVE_MAPPER    -1


Type WAVEIOCB
    As DWORD dwDataSize
    As DWORD dwDataOffset
    As WAVEFORMATEX Ptr lpFmt
    As LPWAVEHDR lpWaveHdr
    As HPSTR lpData
    As WORD wBitsPerSample
    As WORD wBytesPerSample
End Type


Dim Shared As WAVEIOCB waveiocbOut
Dim Shared As HWAVEOUT hWaveOut
Dim msg As MSG
Dim As WNDCLASSEX wc
Dim As String NameClass="MyClass"
Dim As HINSTANCE Hinst=GetModuleHandle(0)

'-----------------------------------------------------
' wioSelectFile
' Выбор wav-файла
'-----------------------------------------------------
Function wioSelectFile(Byref lpszFileName As LPSTR ) As BOOL

    Dim As OPENFILENAME ofn

    Dim As ZString*256 szFile,szFileTitle,szFilter = _
    !"Wave Files\0*.wav\0Any Files\0*.*\0"

    memset(@ofn, 0, Sizeof(OPENFILENAME))

    ' Инициализируем нужные нам поля
    ofn.lStructSize       = Sizeof(OPENFILENAME)
    ofn.lpstrFilter       = Strptr(szFilter)
    ofn.nFilterIndex      = 1
    ofn.lpstrFile         = Strptr(szFile)
    ofn.nMaxFile          = Sizeof(szFile)
    ofn.lpstrFileTitle    = Strptr(szFileTitle)
    ofn.nMaxFileTitle     = Sizeof(szFileTitle)
    ofn.Flags = OFN_PATHMUSTEXIST + OFN_FILEMUSTEXIST + OFN_HIDEREADONLY

    ' Выбираем входной файл
    If GetOpenFileName(@ofn) Then
        ' Копируем путь к выбранному файлу
        lstrcpy(lpszFileName, szFile)
        Return TRUE
    Else
        Return FALSE
    Endif
End Function

'---------------------------------------------------------
'  wioFileOpen
'  Открытие и загрузка wav-файла
'---------------------------------------------------------
Function wioFileOpen(Byref lpwiocb As WAVEIOCB Ptr ,Byref lpszFileName As LPSTR ) As Integer

    Dim As HMMIO hmmio
    Dim As MMCKINFO ckRIFF, ckFMT
    Dim As Integer dwFmtSize

    ' Открываем wav-файл
    hmmio = mmioOpen(lpszFileName, NULL,_
    MMIO_READ + MMIO_ALLOCBUF)
    If hmmio = 0 Then Return WIOERR_FILEERROR

    ' Ищем фрагмент "WAVE"
    memset(@ckRIFF, 0, Sizeof(MMCKINFO))
    ckRIFF.fccType = &h45564157 ' "WAVE"

    If mmioDescend(hmmio, @ckRIFF, NULL, MMIO_FINDRIFF) Then
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    ' Ищем фрагмент "fmt "
    memset(@ckFMT, 0, Sizeof(MMCKINFO))
    ckFMT.ckid = &h20746D66 ' "fmt "

    If mmioDescend(hmmio,_
        @ckFMT, @ckRIFF, MMIO_FINDCHUNK) Then
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    dwFmtSize = Sizeof(WAVEFORMATEX)  'ckFMT.cksize;

    ' Получаем память для загрузки формата звукового файла
    lpwiocb->lpFmt = Cast(WAVEFORMATEX Ptr,Allocate(dwFmtSize))

    If lpwiocb->lpFmt = 0 Then
        mmioClose(hmmio,0)
        Return WIOERR_NOMEM
    Endif

    ' Загружаем формат звукового файла
    If mmioRead(hmmio, Cast(HPSTR,lpwiocb->lpFmt), dwFmtSize) <> dwFmtSize Then
        Deallocate(lpwiocb->lpFmt)
        mmioClose(hmmio,0)
        Return WIOERR_READERROR
    Endif

    ' Проверяем формат звукового файла
    If lpwiocb->lpFmt->wFormatTag <> WAVE_FORMAT_PCM Then
        Deallocate(lpwiocb->lpFmt)
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    ' Проверяем способность драйвера работать с указанным форматом
    If waveOutOpen(NULL, WAVE_MAPPER, Cast(Any Ptr,lpwiocb->lpFmt),_
        NULL, 0L, WAVE_FORMAT_QUERY + WAVE_ALLOWSYNC) Then
        Deallocate(lpwiocb->lpFmt)
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    ' Вычисляем количество байт, необходимых для
    ' хранения одной выборки звукового сигнала
    lpwiocb->wBitsPerSample = lpwiocb->lpFmt->wBitsPerSample
    lpwiocb->wBytesPerSample = _
    (waveiocbOut.wBitsPerSample/8) * waveiocbOut.lpFmt->nChannels

    ' Ищем фрагмент "data"
    ' Функция mmioFOURCC глючит на windows 7, из-за этого
    ' пришлось ckFMT.ckid прописывать вручную
    mmioAscend(hmmio, @ckFMT, 0)
    ckFMT.ckid = &h61746164 ' "data"

    If mmioDescend(hmmio, @ckFMT, @ckRIFF, MMIO_FINDCHUNK) Then
        Deallocate(lpwiocb->lpFmt)
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    ' Определяем размер фрагмента сегмента звуковых
    ' данных и его смещение в wav-файле
    lpwiocb->dwDataSize   = ckFMT.cksize
    lpwiocb->dwDataOffset = ckFMT.dwDataOffset

    ' Проверяем, что файл не пуст
    If lpwiocb->dwDataSize = 0L Then
        Deallocate(lpwiocb->lpFmt)
        mmioClose(hmmio,0)
        Return WIOERR_BADFORMAT
    Endif

    ' Получаем память для заголовка блока
    lpwiocb->lpWaveHdr = _
    Cast(LPWAVEHDR,Allocate(Sizeof(WAVEHDR)))
    If lpwiocb->lpWaveHdr = 0 Then
        Return WIOERR_NOMEM
    Endif

    ' Получаем память для звуковых данных
    lpwiocb->lpData = _
    Cast(HPSTR,Allocate(lpwiocb->dwDataSize))
    If lpwiocb->lpData = 0 Then
        Return WIOERR_NOMEM
    Endif

    ' Позиционирование на начало звуковых данных.
    ' Функция mmioSeek глючит на windows 7. 
    ' Не перемещает указатель на нужное смещение.
    ' И чтобы не передали в 3 параметре, всегда ставит его на 0),
    ' из-за этого пришлось делать лишний вызов mmioRead
    mmioSeek(hmmio, SEEK_SET, 0)
    ' Читаем звуковые данные
    mmioRead(hmmio, lpwiocb->lpData,ckFMT.dwDataOffset)
    ' Читаем звуковые данные
    mmioRead(hmmio, lpwiocb->lpData, lpwiocb->dwDataSize)

    ' Закрываем wav-файл
    mmioClose(hmmio,0)

    Return WIOERR_NOERROR

End Function

'-----------------------------------------------------
' WAVELoad
' Загрузка wav-файла для проигрывания
'-----------------------------------------------------
Function WAVELoad(Byref lpwiocb As WAVEIOCB Ptr ) As BOOL

    Dim As ZString*256 szFileName,szBuf
    Dim As Integer rc

    ' Проверяем наличие драйвера, способного выводить
    ' звуковые файлы
    rc=waveOutGetNumDevs()
    If (rc=0) Then
        MessageBox(NULL,_
        "Нет устройств для вывода звуковых файлов",_
        "Wave Error", MB_OK + MB_ICONHAND)
        Return FALSE
    Endif

    ' Выбираем wav-файл
    If wioSelectFile(szFileName) = 0 Then Return FALSE
    ' Открываем и загружаем в память выбранный файл
    rc = wioFileOpen(lpwiocb, szFileName)

    If rc = WIOERR_NOERROR Then
        Return TRUE
    Elseif rc = WIOERR_FILEERROR Then
        lstrcpy(szBuf, "Ошибка при открытии файла")
    Elseif rc = WIOERR_BADFORMAT Then
        lstrcpy(szBuf, "Неправильный или неподдерживаемый формат файла")
    Elseif rc = WIOERR_NOMEM Then
        lstrcpy(szBuf, "Мало памяти")
    Elseif rc = WIOERR_READERROR Then
        lstrcpy(szBuf, "Ошибка при чтении")
    Else
        lstrcpy(szBuf, "Неизвестная ошибка")
    Endif

    MessageBox(NULL, szBuf,_
    "Wave Error", MB_OK + MB_ICONHAND)
    Return FALSE
End Function

'---------------------------------------------------------
' wioPlay
' Проигрывание загруженного блока звуковых данных
'---------------------------------------------------------
Function wioPlay(Byref lpwiocb As WAVEIOCB Ptr,hwnd As HWND ) As Integer

    Dim As Short rc

    ' Открываем устройство вывода
    rc = waveOutOpen(@hWaveOut, WAVE_MAPPER,_
    Cast(Any Ptr,lpwiocb->lpFmt),_
    Cint(hwnd), 0, CALLBACK_WINDOW + WAVE_ALLOWSYNC)
    If rc Then Return rc

    ' Заполняем заголовок блока данных
    lpwiocb->lpWaveHdr->lpData          = 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 Then
        Deallocate(lpwiocb->lpWaveHdr)
        Return rc
    Endif


    ' Запускаем проигрывание блока
    rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, Sizeof(WAVEHDR))
    If rc Then
        waveOutUnprepareHeader(hWaveOut, lpwiocb->lpWaveHdr,_
        Sizeof(WAVEHDR))
        Deallocate(lpwiocb->lpWaveHdr)
        Return rc
    Endif

    Return 0

End Function

'---------------------------------------------------------
'  wioOutError
'  Вывод сообщения об ошибке в процессе проигрывания
'---------------------------------------------------------
Sub wioOutError(rc As Integer)

    Dim As ZString*MAXERRORLENGTH szBuf

    If waveOutGetErrorText(rc , szBuf , MAXERRORLENGTH) = _
        MMSYSERR_BADERRNUM Then
        lstrcpy(szBuf, "Unknown Error")
    Endif

    MessageBox(NULL, szBuf,_
    "Wave Error", MB_OK + MB_ICONHAND)
End Sub

' функция класса
Function wndproc(hwnd As HWND, msg As Uinteger,_
    wparam As WPARAM, lparam As LPARAM) As Integer
    
    Static As BOOL Flag
    Select Case msg
        Case WM_CREATE
            CreateWindowEx(0,"button","Open files Wav",WS_VISIBLE Or WS_CHILD,10,10,130,20,hwnd,Cast(HMENU,1),0,0)
            CreateWindowEx(0,"button","Play",WS_VISIBLE Or WS_CHILD,10,40,130,20,hwnd,Cast(HMENU,2),0,0)
        Case wm_command
            If Loword(wparam) = 1 Then
                flag = WAVELoad(@waveiocbOut)
            Elseif Loword(wparam) = 2 Then
                If flag=TRUE Then
                    wioPlay(@waveiocbOut,hwnd)
                Endif           
            Endif
        Case WM_DESTROY
            ' Останавливаем и закрываем устройство вывода
            waveOutUnprepareHeader(hWaveOut, waveiocbOut.lpWaveHdr,_
            Sizeof(WAVEHDR))
            waveOutReset(hWaveOut)
            waveOutClose(hWaveOut)
            Deallocate(waveiocbOut.lpFmt)
            PostQuitMessage(0)
    End Select
    Return DefWindowProc(hwnd,msg,wparam,lparam)
End Function
' Заполнение структуры WNDCLASSEX
With wc
    .cbSize=SizeOf(WNDCLASSEX)
    .style=CS_HREDRAW Or CS_VREDRAW
    .lpfnWndProc=@wndproc
    .hInstance=Hinst
    .hIcon=LoadIcon(0,IDI_QUESTION)
    .hCursor=LoadCursor(0,IDC_ARROW)
    .hbrBackground=Cast(HBRUSH,COLOR_WINDOW)
    .lpszClassName=StrPtr(NameClass)
    .hIconSm=.hIcon
End With
' Регистрация класса окна
If RegisterClassEx(@wc)=0 Then
    Print "Register error, press any key"
    Sleep
    End
Endif
'Создание окна
CreateWindowEx(0,NameClass,"Проигрывание Wav файла",_
WS_VISIBLE Or WS_OVERLAPPEDWINDOW,10,10,165,120,0,0,Hinst,0)
' Цикл сообщений
While GetMessage(@msg,0,0,0)
    TranslateMessage(@msg)
    DispatchMessage(@msg)
Wend