воспроизведение звука через стандартный windows multimedia api, на низком уровне

Предыдущая тема Следующая тема Перейти вниз

воспроизведение звука через стандартный windows multimedia api, на низком уровне

Сообщение  electrik в Пн Ноя 08, 2010 8:39 pm

создатели сайтов посвященных FreeBasic, могут выкладывать данный исходник на своих ресурсах.
как то в теме
http://freebasic.justforum.net/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
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне

Сообщение  electrik в Пт Ноя 29, 2013 1:58 am

поповоду "дешёвых" флагов я все же не прав. поглядел я реализацию вывода звука в плеере mpg123, и понял, что разрывы с флагами, у меня были по той причине, что я не создавал очереди воспроизведения. знал о этой возможности, но все-же пытался ее избежать, лень было изобретать механизм очереди. там используются и флаги, и объекты события. будет время, постараюсь портировать под freebasic, интерфейс вывода из mpg123.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне

Сообщение  electrik в Ср Дек 04, 2013 8:11 pm

вот оно. переделал, только в виде класса. уж очень как-то на чистом си оно гружоно было. да и на FreeBasic через каллбэки было бы стремновато.
добавил свойства: 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
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне

Сообщение  electrik в Вт Апр 08, 2014 9:27 am

ух уж простите меня, если кто нахватался каких-то глюков при создании своих Wave файлов. я ввел вас в заблуждение с не правильным описанием заголовка. когда-то давно тупо скопипастил откуда-то, понадеялся на умных людей. не занимайтесь копипастом, проверяйте информацию. надеюсь, ща правильно всё.

вот новый:
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
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: воспроизведение звука через стандартный windows multimedia api, на низком уровне

Сообщение  Спонсируемый контент


Спонсируемый контент


Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения