Последние записи
- Рандомное слайдшоу
- Событие для произвольной области внутри TImage
- Удаление папки с файлами
- Распечатка файла
- Преобразовать массив байт в вещественное число (single)
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
31st
Май
Отловить момент сворачивания окна
SERG1980:
Ну тогда в разделе Public пишем следующую строку:
procedure WMSysCommand(var Msg: TWMSysCommand);message WM_SYSCOMMAND;
далее после слова implementation описываем эту процедуруProcedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
begin
if msg.CmdType=SC_MINIMIZE then
begin
//здесь делаем что надо
end else inherited;
end;
31st
Расширение *.url в Windows
Нашел маленькую фишку, которую можно использовать в программах.
Создайте текстовый файл с любым содержанием.
смените ему разрешение на *.url
Вуаля – 50% пользователей не смогут его открыть.
Единственный метод – это запустить редактор и через “файл”-”открыть”.=) Enjoy.
P.S. Лично я так храню базу ссылок – и логично и практично
31st
Пароль на приложение в Delphi
Tform2 =class(Tform)
..........
private
i: integer;
end;
procedure TForm2.OnCreate(sender: TObject);
begin
self.i=0;
end;
procedure TForm2.Button1Click(Sender: TObject);
const
pas='159357';
var
n:integer;
begin
self.i:=self.i+1;
n:=3-self.i;
if edit1.Text=pas then
begin
hide;
free;
31st
Алгоритм Брезенхема
Рисование прямой методом Брезенхема.
procedure TForm1.Button1Click(Sender: TObject);
var
x1,x2,y1,y2,dx,dy,s1,s2,x,y,o,i,v,obmen : integer;
begin
x1:=strtoint(edit1.Text);
y1:=strtoint(edit2.Text);
x2:=strtoint(edit3.Text);
y2:=strtoint(edit4.Text);
x:=x1;
y:=y1;
dx:=(abs(x2-x1));
dy:=(abs(y2-y1));
if (x2-x1)<=0 then s1:=-1 else s1:=1;
if (y2-y1)<=0 then s2:=-1 else s2:=1;
if dy>dx then
begin
v:=dx;
dx:=dy;
dy:=v;
obmen:=1;
end
else obmen:=0;
o:=2*dy-dx;
for i := 1 to dx do
begin
form1.Canvas.Pixels[x,y]:=clBlack;
while o>=0 do
begin
if obmen=1 then
x:=x+s1
else
y:=y+s2;
o:=o-2*dx;
end;
if obmen=1 then y:=y+s2
else x:=x+x1;
o:=o+2*dy;
end;
//form1.refresh;
//self.Repaint;
end;
30th
Май
Как работать с графикой на канве в среде Дельфи
Здравствуйте, уважаемые читатели. Как и обещал, сегодня с вами мы подробно рассмотрим процедуры работы с графическими объектами, вынесенными в отдельный модуль, позволяющий использовать универсальные методы для создания движущихся изображений, находящихся в файлах, обычно в виде спрайтов…
Продолжение. Начало цикла смотрите в первом и втором выпусках журнала…
Владимир Дегтярь
DeKot degvv@mail.ru
Создание проекта с несколькими движущимися объектами. Урок 5
Создадим новый проект <Lesson 3> аналогично предыдущим. Введем в него новые движущиеся графические объекты (в папке <data> добавлено еще четыре звездолета. Размер каждого спрайта 100х80 pix. Новые звездолеты будут появляться по случайному закону (используем функцию Randomize) и двигаться будут сверху вниз. Вывод фона и основного звездолета ’ship1′ осуществляется также как и в предыдущем проекте <Lesson 2>. Для новых объектов вводим дополнительно BufShipR и BufPicR. Также объявим новые переменные – координаты вывода новых звездолетов и приращения этих координат.
Одновременно у нас будут отображаться основной звездолет ’ship 1′ и два из ’ship 2′ – ’ship 5′, выбираемые по случайному закону (см. листинг 1):
ЛИСТИНГ 1
Var
Form1: TForm1;
BufFon,BufFonDop,Buffer: TBitMap;
BufShip1,BufShipR: TBitMap; // буферы спрайтов
BufPicS1,BufPicR: TBitMap; // буферы изображений одного спрайта
xf,yf: integer; // координаты вывода общего буфера на форму
dyf: integer; // приращение изменения координаты yf по вертикали
xS1,yS1: integer; // координаты звездолета 'ship1'
dxS1,dyS1: integer; // приращение координат 'ship1' по гориз. и вертик.
xR1,yR1,xR2,yR2: integer; // координаты звездолетов 'ship2 - ship5'
dyR1,dxR2,dyR2: integer; // приращение координат 'ship2 - 5'
ns,nr1: byte; // номер спрайта и выбор ship2 - ship5
nr2: byte = 3;
implementation
В процедуре OnCreate формы проведем инициализацию буферов и введем начальные данные для переменных. Процедура DrawShipR(i,j: byte) для вывода новых объектов (’ship2′ – ’ship5′) имеет два
параметра: i (изменение номера рисунка в файле спрайтов) и j (переменная для номера файла спрайтов). Т.к. выбор файла спрайта происходит по передаваемому параметру j, то инициализация буфера BufShipR и загрузка в него файла спрайтов находится в процедуре DrawShipR(i,j: byte) (см. листинг 2):
ЛИСТИНГ 2
procedure DrawShip1(i: byte);
begin
// загрузка одного спрайта в буфер рисунка
BufPicS1.Canvas.CopyRect(bounds(0,0,BufPicS1.Width,
BufPicS1.Height),
BufShip1.Canvas,
bounds(i * 66,0,
BufPicS1.Width,
BufPicS1.Height));
BufPicS1.Transparent:= true; // зададим прозрачность фона рисунка спрайта
BufPicS1.TransparentColor:= BufPicS1.Canvas.Pixels[1,1];
end;
Все движения организованы в обработчике таймера. Звездолеты ’ship2′ — ’ship5′ выводятся в Buffer в координаты xR1, yR1 и xR2, yR2 вне видимого окна формы выше. В каждом такте таймера происходит приращение координат для одного dyR1 по вертикали, для другого dxR2 и dyR2 – по горизонтали и вертикали. После того, как объекты выходят за пределы видимого окна формы внизу, вызываются методы random( ) для задания новых координат xR1, xR2 и номера файла спрайта (nr1, nr2). Координаты yR1, yR2 привязаны к координате yS1 , так как ’ship1′ неподвижен в координатах окна формы. Функция
random ( 4 ) возвращает числа в дипазоне 0 .. 3, а файлы спрайтов встречных звездолетов имеют номера 2 .. 5. Поэтому в процедуре загрузки спрайтов BufShipR.LoadFromFile(’data/ship’ + IntToStr(j+2) + ‘.bmp’) номер загружаемого файла определяется как IntToStr(j + 2)… В остальном процедуры обработчиков таймера и нажатия клавиш не отличаются от проекта <Lesson 2>.
Использование универсального модуля для работы с графикой. Урок 6
Если рассмотреть внимательно код программы в проекте <Lesson 3>, можно заметить, что многие методы часто повторяются для разных графических объектов (создание буферов, загрузка изображений из файлов, копирование и т.п.). При этом для упрощения, я сознательно применил файлы спрайтов одинакового размера и с равным количеством рисунков в файлах. А если файлов спрайтов будет не пять, а больше и если количество рисунков в каждом файле будет разным? Придется значительно увеличивать код для
каждого вида спрайтов. Следовательно, необходимо оптимизировать код программы. Выход здесь в написании методов обработки объектов, не зависящих от количества объектов и применимых для разных изображений.
Даная задача реализована в отдельном модуле <LoadObjectToBufferMod>, позволяющий использовать универсальные методы для создания движущихся графических объектов (находящихся в файлах, обычно в виде спрайтов), имеющих различный размер и разное количество изображений отдельных рисунков.
Модуль находится в папке <Lesson 4> (см. ресурсы к статье). Принцип организации модуля следующий:
- вся работа с графическими объектами проводится через битовые образы TBitMap и области
копирования битовых образов TRect
- для работы с фоном используются процедуры InitFon (инициализация) и LoadFon (загрузка фона из
файлов)
- функция InitSprite предназначена для инициализации и загрузки рисунков спрайтов
- для вывода фона и изображений спрайтов использован общий буфер типа TBitMap
- в процедуре InitBuff происходит инициализация общего буфера, а в процедуре FreeBuff ”переустановка”,
т.е. уничтожение общего буфера и создание снова, но уже без изображений спрайтов
- в процедуре LoadBuff происходит наложение изображений спрайтов на фон
Подробно работа модуля показана ниже…
Применение модуля LoadObjectToBufferMod
1. procedure InitFon(nw, nh: byte; FileName: string)
Создаем дополнительный и основной буферы фона:
BufFonD:= TBitmap.Create;
BufFon := TBitmap.Create;
Далее загружаем рисунок одного из фонов в дополнительный буфер:
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
По загруженному рисунку получаем размер одного рисунка фона (см. рисунок 1):
Причем, размер буфера фона определяем как:
WF:= nw * WFD;
HF:= nh * HFD;
2. procedure LoadFon(xf, yf: integer; FileName: string)
Загружаем все рисунки фонов в буфер фона через дополнительный буфер (см. рисунок 2):
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
3. procedure initBuffer
Создаем основной буфер (Buffer) через который выводим спрайты на форму:
Buffer:= TBitmap.Create;
Размер основного буфера устанавливаем равным размеру буфера фона WF и HF. Загружаем в основной буфер весь фон (cм. рисунок 3):
Buffer.Canvas.Draw(0, 0, BufFon);
4. procedure FreeBuffer
Процедура уничтожаем основной буфер Buffer. Применяется когда необходимо убрать какой-либо спрайт с формы:
Buffer.Free;
Восстанавливаем-же основной буфер с фоном так:
InitBuffer;
Спрайты, которые должны оставаться на форме, следует перерисовать по новому (см. процедуру InitStprite)…
5. procedure InitStprite(SpriteName: string; N_goriz, N_vertic, N_stroka, N_kadr: byte): byte
Cоздаем буфер массива спрайтов и загружаем туда файл спрайтов (см. рисунок 4):
BufSprite:= TBitmap.Create; BufSprite.LoadFromFile(SpriteName) или LoadFromResourceName(hinstance.spritename);
Создаем буфер рисунка (одного спрайта):
BufPic:= TBitmap.Create;
Далее определяем размеры буферов массива спрайтов и буфера рисунка, а также области (типа TRoot) загрузки рисунка. Загружаем спрайт в буфер рисунка (см. рисунок 5):
BufPic.Canvas.CopyRect(RectPic.BufSprite.Canvas, rectSprite);
Задаем прозрачность рисунку:
BufPic.Transparent:= true;
Для вывода следующего спрайта функция возвращает (правильнее сказать – функция принимает значение = Result) значение следующего номера спрайта N_kadr:
Result:= N_kadr;
Уничтожаем буфер массива спрайтов
BufSprite.Free;
6. procedure LoadBuffer(xf, yf, xs, ys, bs: integer)
В этой процедуре на Buffer выводится участок фона с координатами ранее выведенного спрайта, а затем очередное положение спрайта. Определяем область фона и область дополнительного буфера (см. рисунок 6):
Выводим участок фона в буфер, т.е. затираем спрайт фоном (см. рисунок 7):
Выводим очередной спрайт в дополнительный буфер Buffer (см. рисунок 8):
Buffer.Canvas.StretchDraw(Bounds(xs, ys, WP + bs, HP + bs), BufPic);
Уничтожаем буфер рисунка:
BufPic.Free;
Далее в программе выводим дополнительный буфер Buffer на форму методом Draw:
Form1.Canvas.Draw(x, y, Buffer);
Битовые образы фона (BufFon и BufFonD инициализируются (создаются – Create) в программе проекта всего один раз при инициализации программы (обычно вызовом процедуры InitFon в событиях OnCreate или OnActivate). Методы InitBuff, FreeBuff, InitSprite, LoadBuf в программе вызываются неоднократно*. Соответственно и объекты Buffer, BufSprite, BufPic создаются многократно. Поэтому после окончания действия каждого из методов происходит уничтожение битовых образов Buffer, BufSprite, BufPic методом Free.
Комментарий автора.
Загрузку фона можно производить из n – количества файлов, c одинаковым размером не более 1024 х
1024. Для этого вызывать процедуру LoadFon n -раз для разных файлов FonName и изменяя
координаты xf и yf.
Модуль можно применять и для простых объектов (один рисунок в файле SpriteName). Присвойте
переменным N_goriz и N_vertic значения = 1. Если размер спрайта не изменяется, присвойте
переменной bs значение = 0.
Можно загружать рисунки из файлов .jpg. Для этого вместо TBitMap применять класс TJpegImage и в
разделах uses LoadObjectToBufferMod и uses Unit1 добавить модуль Jpeg.
Как работать с модулем?
Для этого необходимо выполнить следующие действия:
1. В процедуре FormActivate (можно в FormCreate) инициализируем буфер фона. Вызываем procedure InitFon(nw,nh: byte; FonName: string) с одним из файлов фонов:
. n раз вызываем procedure LoadFon(xf,yf: integer; FonName: string), последовательно прикрепляя рисунки фонов как бы друг к другу
. инициализируем дополнительный буфер Buffer, вызвав procedure InitBuff. Он получит размер равный сумме размеров всех файлов фонов.
2. Для вывода необходимых спрайтов в нужном месте программы вызываем function InitSprite(SpriteName: string; N_goriz,N_vertic,N_stroka, N_kadr: byte). Функция возвращает очередной номер спрайта для последующего вывода очередного спрайта. Этот номер (N_kadr) необходимо передавать в функцию при каждом ее вызове. Причем, функцию можно использовать для вывода нескольких спрайтов, не забывая передавать ей значение N_kadr для каждых спрайтов.
Далее, вызвав процедуру LoadBuff(xf,yf,xs,ys,bs: integer), выводим спрайт на канву дополнительного буфера поверх фона. Окончательный вывод дополнительного буфера на канву формы производим методом Draw().
Заключение
Рассматриваемые в данной статье проекты полностью приведены в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом (папка Lesson3). Продолжение наших уроков смотрите в следующем выпуске журнала «ПРОграммист»…
Комментарий автора.
Перед запуском в среде Дельфи скопируйте в папку с проектом папку data с графическими файлами.
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Как работать с графикой на канве в среде Дельфи
29th
Май
Что значат символы @ и ^ перед переменными в delphi и паскаль?
Эти символы в Дельфи и Паскале используются для работы с указателями (адресами памяти, где находится информация).
28th
Май
Как сделать стартовым окном Форму 2?
Sanprof:
установить “форму 2″ по умолчанию так (Project -> Options -> Вкладка Forms -> В комбобоксе с надписью Main Form выбираем нужную форму по умолчанию)
27th
Май
Передача звука по сети. Прототип VoIP телефона
Данная статья будет полезна начинающим программистам, которые никогда не имели дело со звуком и его передачей по сети. Смысл этой статьи заключается в изучении и применении: WINAPI функций ввода и вывода звука WaveIn() и WaveOut() в среде разработки Delphi 7.0, самих компонентов TIdUDPServerSocket и TIdUDPClientSocket. Данные компоненты можно найти в библиотеке Indy, которая в свою очередь находится в свободном распространении на просторах Internet’а.
Передача звука по сети. Прототип VoIP телефона
Уколов Александр Владимирович
by ImmortalAlexSan st_devil@mail.ru
Комментарий автора.
Если вы никогда не программировали в Delphi 7.0, версиями ниже или выше, если вы вообще никогда не программировали на подобных ЯВУ, то эта статья не для вас.
Введение
К написанию программы для передачи звука по сети меня побудило желание получить-таки зачет по УИРС (это что-то вроде НИР – научно исследовательской работы студента) у преподавателя, ведущего мой основной предмет, и являющимся моим дипломным руководителем. Перед тем как сесть за Delphi и начать набирать код, предварительно, я изучил кучу литературы в бумажном и электронном виде о принципах упаковки звука и его передачи, о функциях ввода и вывода в самом Delphi и многом другом [1, 2]. Именно ввод и вывод заставил меня задуматься о сложности преподносимого материала. Для человека, никогда не имевшего с этим дело, разобраться в этой области очень сложно, имея под рукой множество кода без комментариев с непонятными процедурами и функциями непонятного WIN API, а если эти процедуры и функции описаны, то это описание предназначено не для начинающих программистов, приходилось все додумывать самому: смотреть подноготную каждой процедуры, и методом проб и ошибок идти медленно, но уверенно к вершине созидания. Но в конечном итоге я добился поставленной цели. И сейчас, разложив всю информацию, предоставленную мне в кашеобразном виде, по полочкам, я готов поделиться своими знаниями с вами, дорогие читатели! Итак, приступим…
Средства разработки
Прежде всего, для работы нам понадобится:
. IDE Delphi версии 7.0 и выше
. Библиотека Indy для Delphi 7.0 (TIdUDPServerSocket и TIdUDPClientSocket) [3, 4]
. колонки и микрофон
Сразу же перейдем к практической части. По мере появления неизвестных функций и процедур в листинге, они будут незамедлительно описываться…
Практическая часть. Создадим клиента
Передача звука в моей программе осуществляется с клиента на сервер, т.е. в одном направлении. Клиент может только писать и передавать, сервер – только принимать и воспроизводить. Первым делом начнем писать клиент.
Для этого, создадим новый проект в Дельфи, разместим на форме кнопку TButton и изменим ее свойство Caption на «начать отправку». После чего, разместим на форме компонент из библиотеки Indy TIdUDPClientSocket (см. рисунок 1):
Так как тестирование программы будет проводиться на локальном компьютере, то изменим значение свойства Host компонента TIdUDPClientSocket на «localhost». Далее я просто перечислю свойства компонента и их значения, что должны быть установлены: Active (false), BroadCastEnabled (false), BufferSize (8192), Name (IdUDPClient1), Port (0), ReceiveTimeOut (-2), Tag (0).
Примечание: описание некоторых вышеуказанных свойств выходит за рамки данной статьи.
Теперь, нажимаем двойным щелчком по вынесенному на форму компоненту TButton и появится обработчик события Button1Click(), где Button1 – это значение свойства Name данного компонента. В этом обработчике пишем или копируем следующий код:
procedure TForm1.Button1Click(Sender: TObject);
begin
// если на кнопке написано «начать отправку» то
If button1.Caption='Начать отправку' then Begin // выполняем этот код, где:
// готовим заголовок для буфера, здесь WaveIn – переменная типа интегер, для
// указания идентификатора устройства ввода (микрофона например), @WaveHdr –
// указатель на структуру TWaveHdr, sizeof(Twavehdr) – размер данной структуры в байтах.
waveInPrepareHeader(waveIn,@WaveHdr,sizeof(Twavehdr));
// заносим данные в буфер
waveInAddBuffer(wavein,@WaveHdr,sizeof(TwaveHdr));
// активируем сокет клиента
IdUDPClient1.Active:= true;
// считываем данные с микрофона
waveInStart(waveIn);
// в едит для наглядности заносим количество записанных байт (делал для себя,
// чтобы проверять, пишется звук или нет)
Edit1.Text:= inttostr(WaveHdr.dwBufferLength);
// меняем название кнопки, чтобы создать возможность прервать отправку пакетов
button1.Caption:='Остановить отправку'
end else Begin // если название кнопки «остановить отправку» то
//переименовываем её
button1.Caption:='Начать отправку';
//закрываем сокет клиента
IdUDPClient1.Active:=false;
//разгружаем буфер
waveInUnprepareHeader(Wavein,@WaveHdr,sizeof(TwaveHdr));
// приостанавливаем считывание. ЗАМЕТЬТЕ! ПРИОСТАНАВЛИВАЕМ! Если мы
// напишем waveInClose(Wavein), то устройство будет закрыто, и при повторном
// нажатии на кнопку, не будет никакого результата.
waveInStop(Wavein);
// смотрим кол-во не записанных байт
Edit1.Text:=inttostr(Wavehdr.dwBytesRecorded);
end
end;
Вы спросите, а что же такое waveInPrepareHeader? Это функция, выполняющая подготовку буфера для операции загрузки данных. Общий вид:
function waveInPrepareHeader(
hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT
): MMRESULT; stdcall;
Здесь:
HWaveIn – идентификатор открытого устройства
LpWaveInHdr – адрес структуры WaveHdr
type TWaveHdr = record
lpData: PChar; { указатель на буфер}
dwBufferLength: DWORD; { длина буфера }
dwBytesRecorded: DWORD; { записанный байты }
dwUser: DWORD; { переменная для использования её пользователем }
dwFlags: DWORD; { флаги }
dwLoops: DWORD; { контролер }
lpNext: PWaveHdr; { переменная для драйвера }
reserved: DWORD; { переменная для драйвера }
end;
Здесь:
lpData – адрес буфера для загрузки данных
dwBufferLength – длина буфера в байтах
dwBytesRecorded – для режима загрузки данных определяет количество загруженных в буфер байт
dwUser – пользовательские данные
dwFlags – флаги. Могут иметь следующие значения: WHDR_DONE устанавливается
драйвером при завершении загрузки буфера данными
WHDR_PREPARED – устанавливается системой. Показывает готовность буфера к загрузке данных
WHDR_INQUEUE – устанавливается системой, когда буфер установлен в очередь
dwLoops – используется только при воспроизведении. При записи звука всегда 0
lpNext – зарезервировано
reserved – зарезервировано
uSize – размер структуры WaveHdr в байтах
Функция waveInPrepareHeader вызывается только один раз для каждого устанавливаемого в очередь загрузки буфера. Что такое waveInAddBuffer()? Функция waveInAddBuffer() ставит в очередь на загрузку данными буфер памяти. Когда буфер заполнен, система уведомляет об этом приложение:
function waveInAddBuffer(
hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT
): MMRESULT; stdcall;
Здесь:
hWaveIn – идентификатор открытого Waveform audio устройства ввода
lpWaveInHdr – адрес структуры TWaveHdr
uSize – размер WaveHdr в байтах
Что такое waveInStart(), waveInStop(), waveInClose()? Общий вид записи таков:
function waveInStart(hWaveIn: HWAVEIN): MMRESULT; stdcall;
waveInStop(), waveInClose() имеют совершенно одинаковый параметр – как и WaveInStart(), которую описывать не имеет смысла, ибо и так понятно, что она начинает считывать данные с устройства ввода, а вот waveInClose() закрывает устройство для записи, и его снова придется открывать с помощью WaveInOpen(), но об этом ниже… А вот waveInStop(), ставит запись как бы на паузу, и нам не надо повторно использовать WaveInOpen().
Что такое waveInUnprepareHeader? Функция аналогичная waveInPrepareHeader(), однако она возвращает выделенную память на буфер, т.е. как бы «уничтожая» его.
Как узнать, что уже можно передавать данные?
Мы разобрали некоторые функции WIN API, относящиеся к вводу данных. Не устали? Нет? Тогда двигаемся дальше! Создадим собственную процедуру для определения завершения передачи данных в блок памяти посредством WaveInAddBuffer(). А выглядит она так:
procedure TForm1.OnWaveMessage(var msg:TMessage);
begin
waveInPrepareHeader(waveIn,@WaveHdr,sizeof(Twavehdr));
waveInAddBuffer(wavein,@WaveHdr,sizeof(TwaveHdr));
// отправляем буфер на сервер, где WaveHdr.lpData^ - это ссылка на память, где
// хранятся считанные с микрофона данные, уже преобразованные в
// последовательность нулей и единиц, WaveHdr.dwBufferLength – длина буфера данных
idUDPClient1.Sendbuffer(WaveHdr.lpData^,WaveHdr.dwBufferLength);
// В переменную заносим количество отправленных байт
Bytes:=Bytes+WaveHdr.dwBufferLength;
// Формат строки. Посмотрите в google фразу format дельфи
Caption:=Format ('%u',[Bytes]);
UpDate
end;
В этой процедуре используются уже известные вам функции, по этому второй раз описывать их не будем. Пишем её сразу после строки {$R *.dfm}. А описываем эту процедуру в разделе private класса TForm1 как:
procedure OnWaveMessage(var msg:TMessage); message MM_WIM_DATA;
Эта процедура будет выполняться каждый раз как только передача данных в буфер будет завершена и система сгенерирует сообщение WIM_DATA. Заполним обработчик события формы OnClose():
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// завершаем все действия
Action:= caFree;
// деактивируем сокет
IdUDPClient1.Active:=false;
// закрываем, теперь уже совсем, устройство записи
waveInClose(Wavein);
end;
И конечно же, заполним обработчик события формы OnCreate():
procedure TForm1.FormCreate(Sender: TObject);
begin
// with – оператор, благодаря которому можно не писать переменные, а указывать
// сразу их свойства. В данном случае WaveFormat: TWAVEFORMATEX – отвечает
// за сигнал, т.е. за все его характеристики, описан ниже.
with waveformat do begin
nChannels:=1;
wFormatTag:=WAVE_FORMAT_PCM;
nSamplesPerSec:=8000;
wBitsPerSample:=8;
nBlockAlign:=1;
nAvgBytesPerSec:=8000;
cbSize:=0;
end;
// для удобства загоняем размер буфера в переменную, которую будем вызывать
bufsize:= waveformat.nAvgBytesPerSec*2 div 16;
// размеру буфера сокета присваиваем размер буфера bufsize
IdUDPClient1.BufferSize:=bufsize;
// waveInOpen опишем чуть ниже, как и обещал, WAVE_MAPPER – система
// сама выбирает устройство
waveInOpen(@Wavein,WAVE_MAPPER,addr(waveformat),self.Handle,0,CALLBACK_WINDOW);
// выделяем память под заголовок буфера данных
WaveHdr.lpData:=Pchar(GlobalAlloc(GMEM_FIXED, bufsize));
// присваиваем длину буфера TWaveHdr’у
WaveHdr.dwBufferLength:=bufsize;
// сбрасываем флаги
WaveHdr.dwFlags:=0;
// устанавливаем порт подключения для клиента
IdUDPClient1.Port:= 10090
end;
Что же такое WaveInOpen()?
Функция waveInOpen() открывает имеющееся устройство ввода Waveform Audio для оцифровки сигнала. Типичная ее структура выглядит следующим образом:
function waveInOpen(
lphWaveIn: PHWAVEIN;
uDeviceID: UINT;
lpFormatEx: PWaveFormatEx;
dwCallback,
dwInstance,
dwFlags: DWORD
): MMRESULT; stdcall;
Здесь:
lphWaveIn – указатель на идентификатор открытого Waveform audio устройства. Идентификатор используется после того, как устройство открыто, в других функциях Waveform audio;
uDeviceID – номер открываемого устройства (см. waveInGetNumDevs). Это может быть также идентификатор уже открытого ранее устройства. Вы можете использовать значение WAVE_MAPPER для того, чтобы функция автоматически выбрала совместимое с требуемым форматом данных устройство;
lpFormatEx - указатель на структуру типа TWaveFormatEx
type TWaveFormatEx = packed record
wFormatTag: Word; { format type }
nChannels: Word; { number of channels (i.e. mono, stereo, etc.) }
nSamplesPerSec: DWORD; { sample rate }
nAvgBytesPerSec: DWORD; { for buffer estimation }
nBlockAlign: Word; { block size of data }
wBitsPerSample: Word; { number of bits per sample of mono data }
cbSize: Word; { the count in bytes of the size of }
end;
В этой структуре значения полей следующие:
wFormatTag – формат Waveform audio. Мы будем использовать значение WAVE_FORMAT_PCM
(это означает импульсно-кодовая модуляция) другие возможные значения
смотрите в заголовочном файле MMREG.H;
nChannels – количество каналов. Обычно 1 (моно) или 2(стерео);
nSamplesPerSec – частота дискретизации. Для формата PCM – в классическом смысле, т.е.
количество выборок в секунду. Согласно теореме отсчетов должна вдвое
превышать частоту оцифровываемого сигнала. Обычно находится в диапазоне от
8000 до 44100 выборок в секунду;
nAvgBytesPerSec – средняя скорость передачи данных. Для PCM равна nSamplesPerSec*nBlockAlign;
nBlockAlign – для PCM равен (nChannels*wBitsPerSample)/8;
wBitsPerSample – количество бит в одной выборке. Для PCM равно 8 или 16;
cbSize – равно 0. Подробности в Microsoft Multimedia Programmer’s Reference;
dwCallback – адрес callback-функции, идентификатор окна или потока, вызываемого при
наступлении события;
dwInstance – пользовательский параметр в callback-механизме. Сам по себе не используется
dwFlags – флаги для открываемого устройства:CALLBACK_EVENT dwCallback-параметр –
код сообщения (an event handle);
CALLBACK_FUNCTION dwCallback – параметр – адрес процедуры-обработчика
CALLBACK_NULL dwCallback – параметр не используется
CALLBACK_THREAD dwCallback – параметр – идентификатор потока команд;
CALLBACK_WINDOW dwCallback – параметр – идентификатор окна
WAVE_FORMAT_DIRECT если указан этот флаг, ACM-драйвер не выполняет преобразование данных
WAVE_FORMAT_QUERY функция запрашивает устройство для определения
поддерживает ли оно указанный формат, но не открывает его
Мы использовали callback функцию в OnWaveMessage(). В последнюю очередь я опишу переменные, которые использовались:
type
TForm1 = class(TForm)
IdUDPClient1: TIdUDPClient;
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
procedure OnWaveMessage(var msg:TMessage); message MM_WIM_DATA;
{ Private declarations }
public
{ Public declarations }
Wavein:HWAVEIN;
WaveHdr:TWaveHdr;
bufsize:Cardinal;
end;
var
Form1: TForm1;
WaveDataLength:integer;
bytes:integer;
device:word;
waveformat: TWAVEFORMATEX;
a:integer;
Так же для работы программы необходимо добавить модуль MMSystem в раздел uses. Клиент готов! Как видите, не так страшен черт, как его малюют! Перед тем как перейти к написанию сервера, я бы вам настоятельно рекомендовал бы покопаться в генофонде всех выше описанных функций и самостоятельно глубже разобраться в том, как они устроены. Так для более углубленного изучения, советую переворошить содержимое таких компонентов из серии ACM как AcmIn, AcmOut. Только самообучением можно чего-нибудь добиться.
А что же сервер?
С чистой перед клиентом совестью, можем приступить к написанию сервера! Возможно, эта процедура покажется вам более сложной, но, разобравшись в ней, вы поймете, что это не так. Единственное, что работать мы будем не с одним буфером, а с восьмью, для удобства воспроизведения звука. В один записываем, воспроизводим, очищаем, готовим, записываем и т.д. по очереди каждый из восьми. Так же будет рассмотрена работа с флагами (dwflags) и приема потока данных (TMemoryStream) на сервер. Приступим, нетерпеливые мои!
Как обычно, создадим новый проект и вынесем на форму компонент TMemo (name=memo1) (опять же-таки я использовал его в целях определения получения потока данных, перегоняя его в шестнадцатиричный формат), кнопку TButton и IdUDPServerSocket (см. рисунок 2):
Пожалуй, начнем с простого. Напишем ниже приведенный код в обработчике события OnClose() формы:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// завершаем действия
Action:= caFree;
// выключаем сервер
IdUDPServer1.Active:= False
end;
Далее займемся обработчиком события OnClick() кнопки TButton1 (см. код):
procedure TForm1.Button1Click(Sender: TObject);
begin
If button1.Caption='Включить сервер' then Begin
// активируем сокет сервера
IdUDPServer1.Active:= true;
button1.Caption:='Выключить сервер'
end else begin
// деактивируем сокет сервера
IdUDPServer1.Active:= false;
button1.Caption:= 'Включить сервер'
end
end;
Теперь напишем процедуру, которую мы будем использовать для воспроизведения принятого звука:
procedure TForm1.playsound(s:Tstream); // получаемый поток
Var // переменная типа сообщения
msg:Tmessage;
begin
// пока а не равно нашему количеству буферов выполняем следующее
While a<>CWaveBufferCount do Begin
// проверку пользовательской установки на то, что буфер готов к записи
If FHeaders[a].dwUser=0 then begin
// записываем в буфер данные из потока, пришедшего от клиента
s.Read(Fheaders[a].lpdata^,bufsize);
// процедура waveOutPrepareHeader аналогична процедуре waveInPrepareHeader
waveOutPrepareHeader(WaveOut,@FHeaders[a],sizeof(FHeaders));
// Процедура waveOutWrite аналогична процедуре waveInAddBuffer, только она
// осуществляет воспроизведение данных из буфера
waveOutWrite(WaveOut,@FHeaders[a],sizeof(FHeaders));
memo1.Lines.Add('...Двоичный код потока...');
// обнуляем флаги буфера/ов в цикле
FHeaders[a].dwFlags:= 0;
// уже знакомая нам структура
With FHeaders[a] do begin
dwBufferLength:= bufsize;
dwBytesRecorded:= 0;
dwUser := 0;
dwLoops:= 1;
// А вот здесь мы присваиваем флагу только что воспроизведенного буфера
// значение, которое отвечает за то что буфер установлен в очередь, т.е. мы как бы
// циклично используем эти 8 буферов
dwFlags:= WHDR_INQUEUE
end;
// Увеличиваем индекс, чтобы перейти к следующему буферу
inc(a);
// соответственно после воспроизведения и подготовки нам больше не нужен цикл и
// мы выходим из него
exit;
end
end
end;
Процедура разобрана, осталось ей воспользоваться… Как это осуществить? Все просто, достаточно в обработчике события OnUDPRead() idUDPServerSocket-a написать следующий код:
procedure TForm1.IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
Begin
// если мы воспроизвели последний буфер то, начинаем всё сначала (с первого)
If a = CWaveBufferCount then
a:= 0;
//вызываем нашу процедуру, в скобках пишем наш поток, пришедший на сервер,
//смотрите процедуру сокета.
playsound(Adata);
// определяем сколько байт мы приняли
Bytes:=Bytes + aData.Size;
// показываем это в названии формы
Caption:= 'Принятых байт' + Format('%u', [Bytes]);
// обновляем форму
UpDate
end;
И не забыть при создании формы проинициализировать наши аудиоустройства. Для этого в обработчике OnCreate() формы запишем:
procedure TForm1.FormCreate(Sender: TObject);
begin
bytes:= 0;
WaveOut:= 0;
With WaveFormatOut do begin
nChannels:= 1;
wFormatTag:= WAVE_FORMAT_PCM;
nSamplesPerSec:= 8000;
wBitsPerSample:= 8;
nBlockAlign:= 1;
nAvgBytesPerSec:= 8000;
cbSize:= 0
end;
bufsize:= WaveFormatOut.nAvgBytesPerSec*2 div 16;
For a:= 0 to CWaveBufferCount-1 do
With FHeaders[a] do begin
dwFlags:= WHDR_INQUEUE;
dwBufferLength:= bufsize;
dwBytesRecorded:= 0;
dwUser:= 0;
dwLoops:= 1;
GetMem(Fheaders[a].lpData, bufsize);
end;
IdUDPServer1.BufferSize:= bufsize;
IdUDPServer1.DefaultPort:= 10090;
waveOutOpen(@WaveOut, WAVE_MAPPER, @WaveFormatOut, self.Handle, 0, CALLBACK_WINDOW);
end;
Уважаемые читатели, здесь я пишу без комментариев только для того, что дать вам возможность самим додуматься, что здесь к чему, это не так сложно, тем более, что вы это уже все знаете (мы с вами выше подробно разбирали эти аналогичные функции ввода и вывода и работы с сокетами).
Далее осталось описать переменные и константы:
Const
CwaveBufferCount = 8;
type
TForm1 = class(TForm)
IdUDPServer1: TIdUDPServer;
Button1: TButton;
Memo1: TMemo;
procedure IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
procedure playsound(s:Tstream);
private
hdr: PwaveHdr;
{ Private declarations }
public
{ Public declarations }
WaveOut:HWAVEOUT;
WaveHdrOut,WaveHdrOut2:TWaveHdr;
WaveFormatOut:tWAVEFORMATEX;
bufsize:word;
FBuffer:Pointer;
FSndBuffer:Pointer;
FHeaders:array[0..CWaveBufferCount-1] of TWAVEHDR;
FBufSize:Cardinal;
end;
var
Form1: TForm1;
bytes:Cardinal;
WaveOut: HWAVEOUT;
WaveHdrOut,WaveHdrOut2: TWaveHdr;
WaveFormatOut: tWAVEFORMATEX;
bufsize:word;
a:integer;
Я не стал описывать процедуру перегонки потока в HEX-формат, так как писал ради передачи данных в TMемо. В конце концов, вы сами запросто можете убрать ненужные строки, относящиеся к ней.
Заключение
Хочу заметить, что размеры буферов сокетов на сервере и клиенте должны быть равны размерам буферов структуры TWaveHdr, иначе вы не получите никаких звуков на выходе, кроме шипения с прерываниями, равными по длительности размеру вашего воспроизводимого буфера. Также для более быстрой реакции на события приема звука используйте меньшие размеры буферов, но и соответственно увеличьте их количество (8-ми вполне хватит). При желании, лучше использовать динамический.
Статья была написана специально для форума Клуба ПРОграммистов www.programmersforum.ru. Исходники тестового проекта (клиента и сервера) прилагаются в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом [5].
Выражаю огромную благодарность человеку, чей ник на вышеуказанном форуме raxp, который активно помогал мне в изучении этого материала кодами и советами.
Ресурсы
. Азбука WIN API http://letitbit.net/download/1868.1502ee9dae8ee96cec9816babb/Azbuka_WIN_API.rar.html
. Описание звуковых функций http://www.delphikingdom.com/asp/viewitem.asp?catalogid=213
. Репозитарий Indy 9: https://svn.atozed.com:444/svn/Indy9 (имя пользователя: Indy-Public-RO)
. Репозитарий Indy 10: https://svn.atozed.com:444/svn/Indy10 (имя пользователя: Indy-Public-RO)
. Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip
. Обсуждение на форуме разработки прототипа VoIP телефона
http://www.programmersforum.ru/showthread.php?t=91506
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Передача звука по сети. Прототип VoIP телефона
27th
Рассылка. Выпуск 60.
От ведущего рассылки.
Добрый вечер читатели рассылки. Сегодня выходит 60ый выпуск рассылки. В этом номере рассылки вы сможете прочитать обзор интересных тем с форума, а так же статьи из журнала ПРОграммист.
26th
Май
Чем плох тег center?
Alar:
Юные блогеры, критикуя блочную верстку, упоминают еще и старые надежные теги, например:
<center>Я ничего плохого не вижу в таблицах, понятно, что верстать весь сайт удобне в дивах:
<div>Так как блок можно отправить куда угодно
А что может быть плохого в тегах, которые просты в написании и поддерживаются всеми браузерами, причем одинаково.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет Microsoft Office Excel excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)