Рассылка закрыта
При закрытии подписчики были переданы в рассылку "О карьере и профессиональном развитии IT-специалистов" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Программирование для начинающих и не только
Информационный Канал Subscribe.Ru |
Озвучиваем Delphi
Наверняка многие из пользователей ПК работали со звуком посредством того или иного приложения с довольно богатым набором методов его обработки, ровно как и некоторые программисты встраивали звуковые "заставки" в свои программки: Но это "встраивание" зачастую сводилось к простому или циклическому воспроизведению файлов WAV формата, и не давало никаких путей для полета программистской фантазии.
*
Но мы не потерпим никаких рамок, а поэтому откинем всевозможные предопределенные функции типа PlayFile или PlaySound подаренные нам разработчиками ОС и примемся за разработку собственного велосипеда.
С чего начать
Все детали необходимые нашего "велосипеда" содержаться в модуле mmsystem.pas. Здесь можно найти функции на любой вкус, от воспроизведения цифрового аудио звука, до MIDI и записи из микрофона. Но давайте обо всем по порядку. И дабы никого не пугать большим количеством функций выданных на-гора мы продемонстрируем только возможности системы по воспроизведению оцифрованного звука.
Итак для нашей демонстрации нам потребуется заготовка для воспроизведения, которую можно выцепить из любого WAV файла просто убрав оттуда всю системную информацию о частоте дискретизации, количестве каналов и разрядности звука. Не то чтобы она нам не понадобилась, просто мы ее будем использовать в другом месте. Я предлагаю вам воспользоваться заготовкой звука из файла "ding.wav" (22050Hz, Стерео, 16 бит), который можно найти здесь
Ну и для чистоты эксперимента я рекомендую предварительно закрыть все WinAmp-ы, Media Player-ы и другие примочки, которые воспроизводят звук.
За дело
К этому времени вы уже находитесь в Delphi с заготовкой нового приложения. Теперь вы можете разместить на форме кнопку, по нажатию которой и будет происходить задуманное. Собственно звук выводится при помощи функций расположенных в модуле mmsystem и потому для дальнейшей работы нам необходимо включить его в разделUses нашей программы. При воспроизведении звука мы воспользуемся такими функциями:
function waveOutOpen(lphWaveIn: PHWAVEIN; uDeviceID: UINT; lpFormatEx: PWaveFormatEx; dwCallback, dwInstance, dwFlags: DWORD): MMRESULT; stdcall;
Эта функция возвращает нам дескриптор устройства воспроизведения, с которым нам предстоит подальшая работа. Параметры этой функции имеют следующие предназначения:
LphWaveIn | Адрес куда будет возвращаться контекста нашего устройства воспроизведения. |
UDeviceID | номер устройства воспроизведения с системе, в нашем случае отставим его равным 0. Что приведет к открытию устройства по умолчанию. |
LpFormatEx | адрес структуры, которая содержит информацию о типе воспроизводимой информации (подробнее см. Ниже). |
DwCallback | дескриптор значение которого определяется в зависимости от dwFlags. (см. Таблицу 1). |
DwInstance | Пользовательская копия данных, которая передается в Callback-механизм. Этот параметр здесь нам не понадобится и должен быть равен 0. |
DwFlags | Флаги управления параметром dwCallback (см. Таблицу 1). |
По результатам работы функции мы можем оценить правильность наших настройки. Так если результат функции равен константе MMSYSERR_NOERROR (определенной в том же mmsystem.pas), то все прошло удачно и мы можем продолжать работу дальше. Если же результат отличен от указанного выше значения, то произошла одна из ошибок описанных в Таблице 2. Теперь рассмотрим подробнее структуру TWaveFormatEx:
Здесь:PWaveFormatEx = ^TWaveFormatEx; {$EXTERNALSYM tWAVEFORMATEX} tWAVEFORMATEX = packed record wFormatTag: Word; nChannels: Word; nSamplesPerSec: DWORD; nAvgBytesPerSec: DWORD; nBlockAlign: Word; wBitsPerSample: Word; cbSize: Word; end;
WFormatTag | тип аудиоданных содержит один из флагов WAVE_FORMAT; |
NChannels | количество каналов (1- моно, 2- стерео:..) |
NSamplesPerSecond | частота дискретизации. Если wFormatTag = WAVE_FORMAT_PCM, то тут могут содержаться следующие частоты: 8000,11025,22050,44100. |
NAvgBytesPerSec | среднее скорость передачи данных. При PCM формате данных (e.g. wFormatTag = WAVE_FORMAT_PCM). Вычисляется как произведение nSamplesPerSec и nBlockAlign. |
NBlockAlign | минимальный размер блока данных. Должен быть равен произведению nChannelsна wBitsPerSampleразделенных на 8 (количество бит в байте). |
WBitsPerSample | при PCM формате данных дожен быть равен 8 или 16. |
CbSize | Размер данной структуры в байтах ( SizeOf(TWaveFormatEx) ). |
Далее после успешного выполнения функции waveInOpen. Нам необходимо проинформировать систему о том, откуда будет воспроизводится звук т.е. задать адрес памяти по которому расположены аудиоданные требуемые для воспроизведения, а также подготовить из для передачи. Для этого мы используем функцию waveOutPrepareHeader:
function waveOutPrepareHeader(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT): MMRESULT; stdcall;
Здесь в качестве параметров передаются такие данные:
HWaveOut | - контекст аудиоустройства (параметра возвращенный при waveOutOpen). |
LpWaveOutHdr | - указатель на структуру, которая содержит все необходимую дополнительную информацию. |
USize | - размер структуры lpWaveOutHdr. |
Здесь:PWaveHdr = ^TWaveHdr; {$EXTERNALSYM wavehdr_tag} wavehdr_tag = record lpData: PChar; dwBufferLength: DWORD; dwBytesRecorded: DWORD; dwUser: DWORD; dwFlags: DWORD; dwLoops: DWORD; lpNext: PWaveHdr; reserved: DWORD; end; TWaveHdr = wavehdr_tag; {$EXTERNALSYM WAVEHDR} WAVEHDR = wavehdr_tag;
LpData | указатель на буффер, где хранятся подготовленные для воспроизведения данные. |
DwBufferLength | размер буфера в байтах |
DwBytesRecorded | используется при записи данных возвращает объем записанной информации. |
DwUser | не используется системой. |
DwFlags | флаги воспроизведения. В нашем случае - 0. |
DwLoops | количество повторов данного блока. В нашем случае - 0. |
LpNext, reserved | зарезервированы для системного пользования. |
Здесь интерес для нас представляют только поля lpDataи dwBufferSize, в которые мы и занесем информацию о буфере звуковых данных. Теперь посредством вызова waveOutPrepareBuffer мы подготавливаем к воспроизведению наши данные. А собственно воспроизведение осуществляется при помощи функции waveOutWrite:
параметры которой означают то же самое, что и в функции waveOutPrepareBuffer.function waveOutWrite(hWaveOut: HWAVEOUT; lpWaveOutHdr: PWaveHdr; uSize: UINT): MMRESULT; stdcall;
После воспроизведения буфера мы как приличные программисты должны закрыть всё открытое, что и делаем посредством функций waveOutUnPrepareHeader и waveOutClose в соответствующем порядке. Далее нам остается только очистить блоки памяти занятые собственно аудиоданными.
Теперь после такого небольшого экскурса в описания функций мы напишем реакцию нашей кнопки на нажатие:
procedure TForm1.Button1Click(Sender: TObject); var Evt:THandle; Fmt:tWAVEFORMATEX; Hdr:TWaveHdr; WO:HWAVEOUT; FS:TFileStream; Data:Pointer; Size:Integer; begin FS:=TFileStream.Create('Sound',fmOpenRead); Size:=FS.Size; GetMem(Data,Size); try FS.ReadBuffer(Data^,Size); FS.Free; Evt:=CreateEvent(nil,False,False,nil); ZeroMemory(@Fmt,SizeOf(Fmt)); ZeroMemory(@Hdr,SizeOf(Hdr)); Fmt.wFormatTag:=WAVE_FORMAT_PCM; Fmt.nChannels:=2; Fmt.nSamplesPerSec:=22050; Fmt.wBitsPerSample:=16; Fmt.cbSize:=SizeOf(Fmt); Fmt.nBlockAlign:=4; Fmt.nAvgBytesPerSec:=88200; if waveOutOpen(@WO,0,@Fmt,Evt,0,CALLBACK_EVENT)<>MMSYSERR_NOERROR then Halt(1); Hdr.lpData:=Data; Hdr.dwBufferLength:=Size; waveOutPrepareHeader(WO,@Hdr,SizeOf(Hdr)); ResetEvent(Evt); waveOutWrite(WO,@Hdr,SizeOf(Hdr)); WaitForSingleObject(Evt,Infinite); waveOutUnprepareHeader(WO,@Hdr,SizeOf(Hdr)); waveOutClose(WO); CloseHandle(Evt); finally FreeMem(Data,Size); end; end;
Здесь вам уже почти все знакомо кроме нескольких строк. Так сначала открывает наш подготовленный файл и считываем оттуда аудиоданные в специально выделенную область оперативной памяти, Далее мы создаем объект - событие (строка CreateEvent), который будет ответственным за реализацию задержки достаточной для воспроизведения звука. Далее мы обнуляем все структуры и заполняем их поля соответствующими значениями. После этого мы открываем наше устройство воспроизведения, делаем подготовку аудиоданных и их вывод на динамики. После этого мы через WaitForSingleObject реализуем задержку до окончания проигрывания (если этого не сделать то звука вообще не будет), и под конец закрываем все открытые контексты и очищаем аудио буферы.
Что еще можно сделать
Конечно сегодня никого уже нельзя удивить возможностью встраивания в приложения поддержки воспроизведения звука. Ведь для простого проигрывания файлов у нас есть стандартные функции расположенные все в том же mmsystem.pas. Но если присмотреться поближе, то эти функции предоставляют нам очень ограниченные возможности по воспроизведению звука (никакой суперсовременный формат вы им не воспроизведёте), в то же время приведенный выше код дает возможность работать с "сырым" звуком, без каких либо надстроек. Так
к примеру если заменить в вышеприведенном коде ту часть, которая считывает информацию из подготовленного файла в память, на код, который будет заполнять эту область шумами или же другими видами звуковых волн:
то тем самым мы сможем почти мгновенно получить все разнообразие звуков которое только возможно описать математическими формулами. Этот код также может стать наглядным (лучше сказать наушным) представлением какого-то курсового проекта по теории акустики или чего-то другого и у вас не будет необходимости залезать в дебри DirectInput для того чтобы похвастаться результатами своих исследований.Size:=88200; GetMem(Data,Size); try Ch:=Data; For i:=0 to 22050 do begin Ch^:=Char(Round(255*Sin(10/pi*i)*Cos(10/pi*i))); Inc(Ch); end;
Запись данных
Но кроме возможности воспроизведения всевозможных звуковых волн. Модуль mmsystem.pas содержит, как уже было сказано вначале статьи, и другие полезные вкусности. В частности здесь объявлены функции которые используются при записи звука с разных источников (микрофон, диск, линейный вход:.). И сейчас мы вкратце разберемся с техникой записи звука из микрофонного входа.
Для этого мы сначала как всегда делаем подготовительные работы т.е. выделяем необходимую область памяти, создаем событие и заполняем все структуры точно также, как и в примере с воспроизведением звука. Основное отличие здесь составляет только то, что вместо вызова функции waveOutOpen мы вызываем waveInOpen, где в качестве параметров передаются те же значения:
Fmt.wFormatTag:=WAVE_FORMAT_PCM; Fmt.nChannels:=1; Fmt.nSamplesPerSec:=11025; Fmt.wBitsPerSample:=8; Fmt.nAvgBytesPerSec:=11025; Fmt.cbSize:=sizeOf(Fmt); Fmt.nBlockAlign:=4; WaveInOpen(@WI,WAVE_MAPPER,@Fmt,Evt,0,CALLBACK_EVENT);
Константа WAVE_MAPPER - определена в mmsystem.pas и ее следует использовать если вы не знаете, с какого именно устройства нужно сделать запись. При указании этого влага система по выставленным в TWaveFormatEx параметрам определит какое устройство наиболее подходит для записи данных в этом формате и откроет его.
Далее как и в примере с воспроизведением звука, нам необходимо соответствующим образом заполнить структуру TWaveHdr, где заполнению в первую очередь подлежит поле lpData. Здесь должен быть указан адрес буфера в который будут помещены аудио данные записанные через микрофон. После этого мы как всегда должны подготовить буфер для записи, а дальше добавить его во внутрисистемный список для записи посредством функции waveInAddBuffer. Для непосредственной записи мы вызываем функцию waveInStart, которая сообщает
системе о том что уже пора начать записывать звук. Теперь мы делаем задержку для того чтоб заполнился весь буфер и после этого используя функции waveInStop и waveInReset мы останавливаем запись. Исходный код для всего выше сказанного приведен ниже:
Hdr.lpData:=@Data; Hdr.dwBufferLength:=SizeOf(Data); if waveInPrepareHeader(WI,@Hdr,SizeOf(HDR))<>MMSYSERR_NOERROR then raise EAbort.Create('Cannot prepare buffer'); waveInAddBuffer(WI,@Hdr,SizeOf(Hdr)); ResetEvent(Event); if waveInStart(WI)<>MMSYSERR_NOERROR then raise EAbort.Create('Cannot start recording'); WaitForSingleObject(Event,Infinite); waveInReset(WI); if waveInReset(WI)<>MMSYSERR_NOERROR then raise EAbort.Create('Cannot reset recording'); waveInStop(WI); if waveInStop(WI)<>MMSYSERR_NOERROR then raise EAbort.Create('Cannot stop recording'); waveInUnprepareHeader(WI,@HDR,SizeOf(Hdr));
После выполнения этого кода в массиве Data будет содержаться звук, который мы произнесем в микрофон.
И в завершении
В завершении хотелось бы отметить, что данный код для воспроизведения звука уже морально устарел, поскольку уже повсеместно используется технология DirectX, с ее огромными возможностями по выводу звука. Но все же mmsystem.pas все таки приберег несколько "козырных тузов" в рукаве, так я перерыл все файлы посвященные звуку в DirectX, но так и не сумел найти средства по записи звука, или воспроизведении MIDI данных. Может быть я действительно не очень глубоко копался, но так ли иначе сейчас для записи звука из микрофонного входа я пользуюсь функциями предоставляемыми именно этим модулем.
Таблица.1 Соотношения параметров переменных dwCallback и dwFlags | |
---|---|
DwCallback | dwFlags |
CALLBACK_EVENT | В dwCallback содержится идентификатор события возбуждаемого системой в процессе работы.(Используется нами в примере) |
CALLBACK_FUNCTION | В dwCallback содржится адрес процедуры, которой передается управлении в процессе работы программы |
CALLBACK_NULL | Нет механизма возврата обработанной информации (используется по умолчанию) |
CALLBACK_THREAD | В dwCallback содержится идентификатор потока, которому передается управление в процессе работы. |
CALLBACK_WINDOW | dwCallback содержит идентификатор окна, которому передается управление. |
Таблица 2. Типические ошибки возникающие при работе со звуком. | ||
---|---|---|
Идентификатор | Значение | Описание |
MMSYSERR_NOERROR | 0 | Нет ошибки, можно продолжать работу |
MMSYSERR_ERROR | 1 | Неопознанная ошибка |
MMSYSERR_BADDEVICEID | 2 | Неправильный идентификатор указывающий на ID устройства |
MMSYSERR_NOTENABLED | 3 | Невозможно активизировать драйвер |
MMSYSERR_ALLOCATED | 4 | Устройство уже выделено |
MMSYSERR_INVALHANDLE | 5 | Неправильный идентификатор устройства |
MMSYSERR_NODRIVER | 6 | Отсутствует соответствующий драйвер устройства |
MMSYSERR_NOMEM | 7 | Ошибка при выделении памяти |
MMSYSERR_NOTSUPPORTED | 8 | Данная функция не поддерживается |
MMSYSERR_BADERRNUM | 9 | Неправильный код ошибки. |
MMSYSERR_INVALFLAG | 10 | Указан неправильный флаг |
MMSYSERR_INVALPARAM | 11 | Функции передан неправильный параметр |
MMSYSERR_HANDLEBUSY | 12 | Идентификатор используется также и другим обработчиком (например потоком) |
MMSYSERR_INVALIDALIAS | 13 | Указанный синоним не найден |
MMSYSERR_BADDB | 14 | Ошибка в реестре |
MMSYSERR_KEYNOTFOUND | 15 | Ключ реестра не найден |
MMSYSERR_READERROR | 16 | Невозможно прочитать значение из реестра |
MMSYSERR_WRITEERROR | 17 | Невозможно записать значение в реестр |
MMSYSERR_DELETEERROR | 18 | Невозможно удалить значение и реестра |
MMSYSERR_VALNOTFOUND | 19 | Заданное значение не найдено в реестре |
MMSYSERR_NODRIVERCB | 20 | Драйвер не поддерживает операцию вызова DriverCallback |
Листинг
unit Main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs,mmsystem, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var Evt:THandle; Fmt:tWAVEFORMATEX; Hdr:TWaveHdr; WO:HWAVEOUT; FS:TFileStream; Data:Pointer; Size:Integer; Ch:PChar;i:integer;hz:Byte; begin Size:=88200; GetMem(Data,Size); hz:=10; try Ch:=Data; For i:=0 to 22050 do begin Ch^:=Char(Round(255*Sin(hz/pi*i)*Cos(hz/pi*i))); Inc(Ch); end; Evt:=CreateEvent(nil,False,False,nil); ZeroMemory(@Fmt,SizeOf(Fmt)); ZeroMemory(@Hdr,SizeOf(Hdr)); Fmt.wFormatTag:=WAVE_FORMAT_PCM; Fmt.nChannels:=1; Fmt.nSamplesPerSec:=22050; Fmt.wBitsPerSample:=8; Fmt.cbSize:=SizeOf(Fmt); Fmt.nBlockAlign:=4; Fmt.nAvgBytesPerSec:=22050; if waveOutOpen(@WO,0,@Fmt,Evt,0,CALLBACK_EVENT)<>MMSYSERR_NOERROR then Halt(1); Hdr.lpData:=Data; Hdr.dwBufferLength:=Size; waveOutPrepareHeader(WO,@Hdr,SizeOf(Hdr)); ResetEvent(Evt); waveOutWrite(WO,@Hdr,SizeOf(Hdr)); WaitForSingleObject(Evt,Infinite); waveOutUnprepareHeader(WO,@Hdr,SizeOf(Hdr)); waveOutClose(WO); finally FreeMem(Data,Size); end; end; end.
http://subscribe.ru/
http://subscribe.ru/feedback/ |
Подписан адрес: Код этой рассылки: comp.soft.prog.programmershelp |
Отписаться |
В избранное | ||