воспроизведение звука через стандартный windows multimedia api, на низком уровне
Страница 1 из 1
воспроизведение звука через стандартный windows multimedia api, на низком уровне
создатели сайтов посвященных FreeBasic, могут выкладывать данный исходник на своих ресурсах.
как то в теме
https://freebasic.forum2x2.ru/forum-f6/tema-t56.htm
я выкладывал исходник wave плеера. ну вот, он стал взрослее, и больше не использует для воспроизведения звука дешевые флаги и бесконечные циклы для их проверки, избавился от слееп. все гораздо проще, и лучше.
есть такая штука, называется "объект события", именно этим способом оно работает.
Объект события - это что-то вроде переключателя: у него есть только два состояния: вкл и выкл.
про объект события можно почитать tutorial'ы iczeliona.
вот сам исходник. можете сравнить с тем, ссылка на который приведена выше.
в отличии от версии с флагами, не должно быть разрывов в звуке. сами microsoft, рекомендуют использовать объект события.
избавился от crt - стандартные функции си, все на WinAPI.
*** наконец то исправлено, правильная проверка идентификаторов заголовка wave файла.
*** убраны безсмысленные обсчеты предыдущей позиции файла
*** оптимизирован цикл, убраны безсмысленные переменные endbuf
исправлено 24 сентября 2011
как то в теме
https://freebasic.forum2x2.ru/forum-f6/tema-t56.htm
я выкладывал исходник wave плеера. ну вот, он стал взрослее, и больше не использует для воспроизведения звука дешевые флаги и бесконечные циклы для их проверки, избавился от слееп. все гораздо проще, и лучше.
есть такая штука, называется "объект события", именно этим способом оно работает.
Объект события - это что-то вроде переключателя: у него есть только два состояния: вкл и выкл.
про объект события можно почитать tutorial'ы iczeliona.
вот сам исходник. можете сравнить с тем, ссылка на который приведена выше.
в отличии от версии с флагами, не должно быть разрывов в звуке. сами microsoft, рекомендуют использовать объект события.
избавился от crt - стандартные функции си, все на WinAPI.
*** наконец то исправлено, правильная проверка идентификаторов заголовка wave файла.
*** убраны безсмысленные обсчеты предыдущей позиции файла
*** оптимизирован цикл, убраны безсмысленные переменные endbuf
- Код:
'windows player(wplay)
'или wave play wplay - называйте как угодно
'плеер для проигрывания стандардных pcm wave файлов
'(c) electrik 2008-2011
'email: svalka-spb@yandex.ru
'web: http://svalka-spb.narod.ru
'плеер написан в процессе самообучения звуковой подсистемы winmm
'можно использовать как фоновый плеер
'плеер не имеет ни каких окон
'компилировать: fbc.exe -s gui wplay.bas
'использовать: wplay.exe имяФайла.wav
'если завис: убить диспетчером задач
#include once "windows.bi"
#include "win/mmsystem.bi"
#define WAVE_MAPPER -1
#define lenbuf 65536
type WaveHeader 'описатель заголовка wav файла
as zstring * 4 id_riff 'потпись riff
len_riff as integer 'размер оставшейся части файла без riff
'chuck
id_chuck as zstring * 4 'потпись wave
fmt as zstring * 4 'потпись fmt
len_chuck as integer 'размер заголовка id_riff+len_riff+id_chuck+fmt=16
'wave
WaveFormat as short 'формат данных, 1= pcm
channels as short 'число каналов
SamplesPerSec as integer 'частота дискретизации
bytes as integer 'частота выдачи байт
align as short 'выравнивание
bits as short 'число бит
'sample wave
samplewave as zstring * 4 'data 'потпись data
len_data as integer 'размер звуковых данных без заголовка
end type 'конец описателя заголовка wav файла
dim shared hFile as handle 'дескриптор файла
dim shared hEvent as HANDLE ' хэндл объекта события
dim shared hOut as hWaveOut 'тут будет лежать идентификатор устройства
dim shared wFormat as WaveFormatEx 'тип(описатель) wave формата
dim shared wHeader as WaveHeader 'описатель заголовка wav файла
dim shared wHdr(1) as wavehdr 'описатель заголовка буфера
dim shared buf(1) as zstring * lenbuf 'буферы для звуковых данных
dim shared bytes as integer 'число действительно прочитанных байт
dim shared FilePosition as integer 'хранение позиции в файле
dim shared argv as zstring * 260 ' коммандная командной строки
dim shared argc as integer ' количество параметров коммандной строки
argc=__fb_argc__ 'получим количество параметров командной строки
if argc =1 then 'если нет аргументов
messagebox(0,!"Использование: wplay.exe имя файла.wav.\nпрограмма поддерживает только Pcm wave файлы.","Справка",0)
end 1
end if
'соберем командную строку в одно целое
for i as integer =1 to argc - 1
lstrcat(argv,__fb_argv__[i])
lstrcat(argv," ")
next
'откроем файл только для чтения, из командной строки как бинарник
hFile = createfile(argv,&H80000000,&h1,0,OPEN_EXISTING,&h20,0)
'если файла не существует
if hFile = -1 then
messagebox(0,"Файл задан не правильно",@argv[0],0)
end 1
end if
'прочитаем заголовок wav файла
readfile(hFile,@wHeader,44,@bytes,0)
if bytes < 44 then
messagebox(0,"Файл меньше заголовка файла. возможно вы открываете не Wave файл или файл поврежден","Ошибка в файле",0)
end 1
end if
FilePosition+=bytes
if not CompareString(0,0,wHeader.id_riff,4, "RIFF",4) = 2 and not CompareString(0,0,wHeader.id_chuck,4,"WAVE",4) = 2 and _
not CompareString(0,0,wHeader.fmt,4,"fmt ",4) = 2 and not CompareString(0,0,wHeader.samplewave,4,"data",4) = 2 then
messagebox(0,"Заголовок поврежден или вы открываете не Pcm wave файл","Ошибка в заголовке файла",0)
end 1
end if
'описатель заголовка формата wave
wFormat.wFormatTag=wHeader.WaveFormat
wFormat.nChannels= wHeader.channels 'число каналов
wFormat.nSamplesPerSec = wHeader.SamplesPerSec 'частота дискретизации
wFormat.nAvgBytesPerSec = wHeader.bytes 'частота выдачи байт драйверу- можно забить
wFormat.nBlockAlign= wHeader.align 'выравнивание- вроде тоже можно забить
wFormat.wBitsPerSample = wHeader.bits 'число бит на семпл
'конец описателя заголовка formata wave
' создадим объект события
'первый параметр, хэндл события
hEvent = CreateEvent(NULL,false,false,NULL)
'откроем звуковое устройство. первый параметр функции waveOutOpen- это указатель
'на переменную hOut, и если с устройством нет проблем,
'то в переменную запишется идентификатор открытого устройства
'второй параметр, номер устройства. обычно туда пишут WAVE_MAPPER,
'для автоматического определения звуковухи по умолчанию.
'третий параметр, указатель на описатель заголовка wave формата.
'четвертый параметр, хэндл объекта события
'шестой параметр, задает флаг, общения с драйвером, в нашем случае- это CALLBACK_EVENT, объект события
if waveOutOpen(@hOut, -1,@wFormat,cptr(DWORD,hEvent),0,CALLBACK_EVENT) then
messagebox(0,"Устройство не поддерживает данный Wave формат, или проблема открытия устройства","ошибка waveOut",0)
end 1
end if
for numbuf as integer = 0 to 1 'счетчик
wHdr(numbuf).lpData = @buf(numbuf) 'указатель на буфер со звуковыми данными
wHdr(numbuf).dwBufferLength = lenbuf 'длина буфера
'подготовим заголовки для буферов,
'первый параметр, идентификатор устройства.
'второй параметр, указатель на заголовок для буфера.
'третий параметр, длина заголовка
if waveOutPrepareHeader(hOut,@wHdr(numbuf),32) then
messagebox(0,"Не могу создать заголовок","Ошибка подготовки заголовка",0)
end 1
end if
next 'пусть повторяет
while bytes > 0
for numbuf as integer = 0 to 1 'счетчик
readfile(hFile,@Buf(numbuf),lenbuf,@bytes,0)
if bytes = 0 then exit while ' если нечего читать из файла- выходим из цикла while
filePosition+=bytes ' добавляем к позиции файла число прочитанных байт
if filePosition >= wHeader.len_data+sizeof(wHeader) then ' если позиция файла >= длине звуковых данных + заголовок файла
wHdr(numbuf).dwBufferLength = bytes - (filePosition - wHeader.len_data) ' длина буфера =число прочитанных байт - (позиция файла - длина звуковых данных)
end if
'подаем драйверу всё подготовленное хозяйство.
'первый параметр, идентификатор устройства.
'второй параметр, указатель на заголовок для буфера.
'третий параметр, длина заголовка
WaveOutWrite(hOut,@wHdr(numbuf),32)
WaitForSingleObject(hEvent, INFINITE) ' ожидаем, пока драйвер проиграет буфер
next 'пусть повторяет
wend
CloseHandle(hFile) 'закроем файл
for numbuf as integer = 1 to 2 'счетчик
'снимем заголовки
'первый параметр, идентификатор устройства.
'второй параметр, указатель на заголовок для буфера.
'третий параметр, длина заголовка
WaveOutUnprepareHeader(hOut,@whdr(numbuf), 32)
next 'пусть повторяет
WaveOutClose(hOut) 'закроем устройство
WaitForSingleObject(hEvent, INFINITE) ' ждем пока не закроется устройство, это чтоб в конце не обрубилось воспроизведение
CloseHandle(hEvent) ' закроем хэндл объекта события
исправлено 24 сентября 2011
electrik- Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург
Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне
поповоду "дешёвых" флагов я все же не прав. поглядел я реализацию вывода звука в плеере mpg123, и понял, что разрывы с флагами, у меня были по той причине, что я не создавал очереди воспроизведения. знал о этой возможности, но все-же пытался ее избежать, лень было изобретать механизм очереди. там используются и флаги, и объекты события. будет время, постараюсь портировать под freebasic, интерфейс вывода из mpg123.
electrik- Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург
Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне
вот оно. переделал, только в виде класса. уж очень как-то на чистом си оно гружоно было. да и на FreeBasic через каллбэки было бы стремновато.
добавил свойства: rate,bits, channels.
есть только одна проблема. не воспроизводит самый последний буфер - если он короткий.
это их проблема, и она в комментах есть.
странно, mpg123, до сих пор выходит, а проблема с виндовым выводом с 2007 года не решилась.
решим - надо будет им отписаться.
а вообще winmm, глючная штука, я когда писал свой плеер, тоже парился с буферами.
добавил свойства: rate,bits, channels.
есть только одна проблема. не воспроизводит самый последний буфер - если он короткий.
это их проблема, и она в комментах есть.
странно, mpg123, до сих пор выходит, а проблема с виндовым выводом с 2007 года не решилась.
решим - надо будет им отписаться.
а вообще winmm, глючная штука, я когда писал свой плеер, тоже парился с буферами.
- Код:
/'
win32: audio output for Windows 32bit
copyright ?-2007 by the mpg123 project - free software under the terms of the LGPL 2.1
see COPYING and AUTHORS files in distribution or http:'mpg123.org
initially written (as it seems) by Tony Million
rewrite of basic functionality for callback-less and properly ringbuffered operation by ravenexp
*** rewrite for FreeBasic by Electrik
по фрагментам кода mpg123
сделал в виде класса
проблема с воспроизведением последнего буфера пока не решена
'/
#include "windows.bi"
#include "crt.bi"
#include "win/mmsystem.bi"
/'
Buffer size and number of buffers in the playback ring
NOTE: This particular num/size combination performs best under heavy
loads for my system, however this may not be true for any hardware/OS out there.
Generally, BUFFER_SIZE < 8k || NUM_BUFFERS > 16 || NUM_BUFFERS < 4 are not recommended.
'/
#define BUFFER_SIZE &H10000
#define NUM_BUFFERS 8 /' total 512k roughly 2.5 sec of CD quality sound '/
#define WAVE_MAPPER -1
#define ereturn(rv,s) fprintf(stderr,s):return rv
type WaveHeader 'описатель заголовка wav файла
as zstring * 4 id_riff
len_riff as integer
'chuck
id_chuck as zstring * 4
fmt as zstring * 4
len_chuck as integer
'wave
WaveFormat as short 'формат данных, 1= pcm
channels as short 'число каналов
SamplesPerSec as integer 'частота дискретизации
bytes as integer 'частота выдачи байт
align as short 'выравнивание
bits as short 'число бит
'sample wave
samplewave as zstring * 4 'data 'потпись data
len_data as integer 'размер звуковых данных без заголовка
end type 'конец описателя заголовка wav файла
/' Buffer ring queue state '/
type queue_state
as WAVEHDR buffer_headers(NUM_BUFFERS-1)
/' The next buffer to be filled and put in playback '/
as integer next_buffer
/' Buffer playback completion event '/
as HANDLE play_done_event
as HWAVEOUT waveout
end type
type audio_output_class
private:
as queue_state ptr state /' driver specific pointer '/
as integer init
public:
declare constructor
declare destructor
/' methods '/
declare function open() as integer
declare function write(byval buf as ubyte ptr , byval length as integer) as integer
declare sub flush()
declare function close() as integer
private:
as zstring ptr device
as integer bs /' bits '/
as integer sr /' sample rate '/
as integer ch /' number of channels '/
/' properties '/
public:
declare property bits() as integer
declare property bits(byref b as integer)
declare property rate() as integer
declare property rate(byref r as integer)
declare property channels() as integer
declare property channels(byref c as integer)
end type
constructor audio_output_class
state = 0
init = 0
bs = 0
sr = 0
ch = 0
end constructor
destructor audio_output_class
state = 0
init = 0
bs = 0
sr = 0
ch = 0
end destructor
function audio_output_class.open() as integer
dim as integer i = any
dim as MMRESULT res = any
dim as WAVEFORMATEX out_fmt = any
dim as UINT dev_id = any
if(sr = -1) then return 0
/' Allocate queue state struct for this device '/
state = CAllocate(1, sizeof(queue_state))
if(state = 0) then return -1
/' Allocate playback buffers '/
for i = 0 to NUM_BUFFERS-1
state->buffer_headers(i).lpData = allocate(BUFFER_SIZE)
if state->buffer_headers(i).lpData = 0 then
ereturn(-1, "Out of memory for playback buffers.")
end if
next
state->play_done_event = CreateEvent(0,FALSE,FALSE,0)
if(state->play_done_event = INVALID_HANDLE_VALUE) then return -1
/' FIXME: real device enumeration by capabilities? '/
dev_id = WAVE_MAPPER /' probably does the same thing '/
device = @"waveMapper"
out_fmt.wFormatTag = WAVE_FORMAT_PCM
out_fmt.wBitsPerSample = bs
out_fmt.nChannels = ch
out_fmt.nSamplesPerSec = sr
out_fmt.nBlockAlign = out_fmt.nChannels*out_fmt.wBitsPerSample/8
out_fmt.nAvgBytesPerSec = out_fmt.nBlockAlign*out_fmt.nSamplesPerSec
out_fmt.cbSize = 0
res = waveOutOpen(@state->waveout, dev_id, @out_fmt, _
cast(DWORD,state->play_done_event), 0, CALLBACK_EVENT)
select case res
case MMSYSERR_NOERROR
case MMSYSERR_ALLOCATED
ereturn(-1, "Audio output device is already allocated.")
case MMSYSERR_NODRIVER
ereturn(-1, "No device driver is present.")
case MMSYSERR_NOMEM
ereturn(-1, "Unable to allocate or lock memory.")
case WAVERR_BADFORMAT
ereturn(-1, "Unsupported waveform-audio format.")
case else
ereturn(-1, "Unable to open wave output device.")
end select
/' Reset event from the "device open" message '/
ResetEvent(state->play_done_event)
init = 1
function = 0
end function
/' Stores audio data to the fixed size buffers and pushes them into the playback queue.
I have one grief with that: The last piece of a track may not reach the output,
only full buffers sent... But we don't get smooth audio otherwise. '/
function audio_output_class.write(byval buf as ubyte ptr, byval length as integer) as integer
dim as MMRESULT res = any
dim as WAVEHDR ptr hdr = any
dim as integer rest_len = any /' Input data bytes left for next recursion. '/
dim as integer bufill = any /' Bytes we stuff into buffer now. '/
if(init = 0 OrElse state = 0) then return -1
if(buf = 0 OrElse length <= 0) then return 0
hdr = @state->buffer_headers(state->next_buffer)
/' Check buffer header and wait if it's being played.
Skip waiting if the buffer is not full yet '/
while(hdr->dwBufferLength = BUFFER_SIZE AndAlso (hdr->dwFlags and WHDR_DONE) = 0)
/' debug1("waiting for buffer %i...", state->next_buffer) '/
WaitForSingleObject(state->play_done_event, INFINITE)
wend
/' If it was a full buffer being played, clean up. '/
if(hdr->dwFlags and WHDR_DONE) then
waveOutUnprepareHeader(state->waveout, hdr, sizeof(WAVEHDR))
hdr->dwFlags = 0
hdr->dwBufferLength = 0
end if
/' Now see how much we want to stuff in and then stuff it in. '/
bufill = BUFFER_SIZE - hdr->dwBufferLength
if(length < bufill) then bufill = length
rest_len = length - bufill
memcpy(hdr->lpData + hdr->dwBufferLength, buf, bufill)
hdr->dwBufferLength += bufill
if(hdr->dwBufferLength = BUFFER_SIZE) then
/' Send the buffer out when it's full. '/
res = waveOutPrepareHeader(state->waveout, hdr, sizeof(WAVEHDR))
if(res <> MMSYSERR_NOERROR) then ereturn(-1, "Can't write to audio output device (prepare).")
res = waveOutWrite(state->waveout, hdr, sizeof(WAVEHDR))
if(res <> MMSYSERR_NOERROR) then ereturn(-1, "Can't write to audio output device.")
/' Cycle to the next buffer in the ring queue '/
state->next_buffer = (state->next_buffer + 1) mod NUM_BUFFERS
end if
/' I'd like to propagate error codes or something... but there are no catchable surprises left.
Anyhow: Here is the recursion that makes ravenexp happy-) '/
if(rest_len AndAlso write(buf + bufill, rest_len) < 0) then /' Write the rest. '/
return -1
else
return length
end if
end function
sub audio_output_class.flush()
dim as integer i = any, z = any
if(init = 0 OrElse state = 0) then return
/' FIXME: The very last output buffer is not played. This could be a problem on the feeding side. '/
i = 0
z = state->next_buffer - 1
for i = 0 to NUM_BUFFERS-1
if(state->buffer_headers(i).dwFlags and WHDR_DONE = 0) then
WaitForSingleObject(state->play_done_event, INFINITE)
end if
waveOutUnprepareHeader(state->waveout, @state->buffer_headers(i), sizeof(WAVEHDR))
state->buffer_headers(i).dwFlags = 0
state->buffer_headers(i).dwBufferLength = 0
z = (z + 1) mod NUM_BUFFERS
next
end sub
function audio_output_class.close() as integer
dim as integer i = any
if(init = 0 OrElse state = 0) then return -1
flush()
waveOutClose(state->waveout)
WaitForSingleObject(state->play_done_event, INFINITE)
CloseHandle(state->play_done_event)
for i = 0 to NUM_BUFFERS-1
deallocate(state->buffer_headers(i).lpData)
next
deallocate(state)
function = 0
end function
property audio_output_class.bits() as integer
bits = bs
end property
property audio_output_class.bits(byref b as integer)
bs = b
end property
property audio_output_class.rate() as integer
rate = sr
end property
property audio_output_class.rate(byref r as integer)
sr = r
end property
property audio_output_class.channels() as integer
channels = ch
end property
property audio_output_class.channels(byref c as integer)
ch = c
end property
' program start
dim as audio_output_class ao
dim as WaveHeader wHeader
dim as zstring * BUFFER_SIZE buf
dim as DWORD bytesRead
if __FB_ARGC__ = 1 then
? "Usage: File name"
end
end if
dim as HANDLE hFile = CreateFile(__FB_ARGV__[1],GENERIC_READ, _
FILE_SHARE_READ,NULL, OPEN_EXISTING, _
FILE_ATTRIBUTE_ARCHIVE,NULL)
if hFile = INVALID_HANDLE_VALUE then
? "error opening "
end 1
end if
'прочитаем заголовок wav файла
ReadFile(hFile,@wHeader,44,@bytesRead,0)
if bytesRead < 44 then
?"file too small"
end 1
end if
ao.bits = cast(integer,wHeader.Bits)
ao.channels = cast(integer,wHeader.Channels)
ao.rate = wHeader.SamplesPerSec
ao.open()
dim as integer lenbuf
while BytesRead
ReadFile(hFile,@buf,BUFFER_SIZE,@bytesRead,0)
if bytesread then
lenbuf = ao.write(@buf,bytesread)
end if
wend
ao.close()
sleep
electrik- Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург
Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне
ух уж простите меня, если кто нахватался каких-то глюков при создании своих Wave файлов. я ввел вас в заблуждение с не правильным описанием заголовка. когда-то давно тупо скопипастил откуда-то, понадеялся на умных людей. не занимайтесь копипастом, проверяйте информацию. надеюсь, ща правильно всё.
вот новый:
intteger исправлен на long, ибо в x64, такой заголовок будет не правильный.
вот новый:
intteger исправлен на long, ибо в x64, такой заголовок будет не правильный.
- Код:
type WaveHeader 'описатель заголовка wav файла
as zstring * 4 id_riff 'потпись riff
len_riff as long 'размер оставшейся части файла без "riff" и текущего поля "len_riff", тоесть -8 байт от файла
'chuck
id_chuck as zstring * 4 'потпись wave
fmt as zstring * 4 'потпись fmt
len_wave as long ' размер нижеследующего куска "wave Format" до "sample wave" без этого поля. в стандартном pcm длина wave format = 16 байт
'wave Format
WaveFormat as short 'формат данных, 1= pcm
channels as short 'число каналов: 1 - моно, 2 - стерео
SamplesPerSec as long 'частота дискретизации
bytes as long 'частота выдачи байт в секунду
align as short 'выравнивание
bits as short 'число бит на семпл
'sample wave
samplewave as zstring * 4 'data 'потпись data
len_data as long 'размер звуковых данных без заголовка
end type 'конец описателя заголовка wav файла
electrik- Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 43
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург
Страница 1 из 1
Права доступа к этому форуму:
Вы не можете отвечать на сообщения
|
|