На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела "Программирование звука"
0) Данный раздел предназначен для обсуждения проблем, возникающих при программировании задач, связанных с записью, обработкой, воспроизведением звука. Перед созданием темы подумайте, не будет ли она уместнее в разделах Разработка и тестирование программ, Наши исходники, а особенно Разовые заказы и подработки
1) На Раздел распространяются все Правила форума.Огромная просьба с ними внимательно ознакомиться.
2) Запрещается давать бессмысленные ответы вроде: "Снеси Мастдай", "ХП рулит", "Поставь Линукс" и т.д.
3) Запрещается создавать темы, в которых Вы намереваетесь получить ссылку на кряки, серийники и т.п. Также запрещено любое обсуждение p2p (peer-to-peer) сетей (BitTorrent, eDonkey и т.д.).
4) Реклама всякого рода пресекается беспощадно.
5) Используйте тэг [CODE] для выделения кода программы (непременно с указанием языка программирования - выбрать из списка. В противном случае бессмысленно!). Уважайте тех, кто будет читать ваш код!
6) Если решение вашей проблемы найдено, то не забываем помечать тему специальной функцией "Вопрос решён". Вам всего лишь требуется при написании последнего ответа поставить одну единственную галочку прямо над формой ответа.
7) Если вы хотите получить совет для конкретной платформы/языка программирования, обязательно укажите их в вопросе

8) Если не прикрепляются/не скачиваются файлы, читаем Не прикрепляется / не скачивается файл. Любые обсуждения в данном разделе проблем с приложением файлов считаются оффтопиком! Со всеми вытекающими.

9) NEW! Уважаемые новички! Мы приветствуем Ваше желание научить всех посетителей раздела правильному программированию. Но огромная просьба, перед тем, как писать поучения в старых (последний ответ - "старее" месяца, а особенно, если вопрошавший не появляется на форуме уже не первый месяц, в чем можно убедиться в его профиле) темах, хорошо подумать, будет ли кому-нибудь, кроме Вас cамих, это интересно. Попытки накрутки количества тематических сообщений за счёт поднятия древних неактуальных тем ("некрофилия") будут наказываться по велению левой пятки модераторского состава (см. пп.12, 13 Правил)



Нарушение Правил может повлечь наказание со стороны модераторов.



user posted imageFAQ Раздела user posted imageПоиск в Разделе user posted imageMSDN Library Online | Ваше мнение о модераторах: user posted imageBarazuk user posted imageRikkie
Страницы: (2) [1] 2  все  ( Перейти к последнему сообщению )  
> Захват звука DirectSound
    Люди добрые, подскажите пожалуйста как реализовать захват звука через directsound для дальнейшей обработки (БПФ, умножение на sin/cos и тому подобное)
        вот у меня есть образец, только не могу в нем разобраться:
        ExpandedWrap disabled
                   //Подготовка открытия устройства записи звука
                    HWAVEIN hWaveIn;
                     HWND hWnd = NULL;
                      WAVEFORMATEX WaveFormat;
                       WAVEHDR WaveHdr;
          //Заполнение структуры WAVEFORMATEX
               WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
               WaveFormat.nChannels = 2;
               WaveFormat.nSamplesPerSec = 44100;
               WaveFormat.wBitsPerSample = 16;
               WaveFormat.nBlockAlign = (WaveFormat.nChannels*WaveFormat.wBitsPerSample)/8;
               WaveFormat.nAvgBytesPerSec = WaveFormat.nSamplesPerSec*WaveFormat.nBlockAlign;
               //WaveFormat.cbSize = 0; для формата РСМ строка не нужна
                    //Открытие устройства записи звука
               MMRESULT mmRes = waveInOpen(&hWaveIn, WAVE_MAPPER, &WaveFormat, (DWORD)hWnd, 0L, CALLBACK_WINDOW);
           //нижние 3 строки можно и нужно выкинуть. Нужны только на этапе отладки.
                //При успехе-сообщение: "Указанная команда выполнена"
                          char buff[100];
                           waveInGetErrorText(mmRes, buff, 100);
                           ShowMessage(buff);
          //------------------------------
                    //подготовка буфера для записи
                int t = 1; //время записи в секундах
                    int BufferSize = 0;
          //Заполнение структуры WAVEHDR
           BufferSize = WaveFormat.nBlockAlign*WaveFormat.nSamplesPerSec*t;
                 WaveHdr.lpData = (char*)malloc(BufferSize);
                  WaveHdr.dwBufferLength = BufferSize;
                  WaveHdr.dwFlags = 0;
               waveInPrepareHeader(hWaveIn, &WaveHdr, sizeof(WAVEHDR));
               waveInAddBuffer(hWaveIn, &WaveHdr, sizeof(WAVEHDR));
           //Начало записи звука. Источник звука определяется
          //настройками аудиопараметров (рег. громкости в Tray)
                        mmRes = waveInStart(hWaveIn);
            //нижние 2 строки можно и нужно выкинуть. Нужны только на этапе отладки.
                //При успехе-сообщение: "Указанная команда выполнена"
                     waveInGetErrorText(mmRes, buff, 100);
                        ShowMessage(buff);
          //------------------------------------
              // MM_WIM_DATA; строка оказалась не работающей
             waveInUnprepareHeader(hWaveIn, &WaveHdr, sizeof(WAVEHDR));
            //WaveHdr.lpData - указывают на буфер, где хранять записанные данные.
          //После вызова waveInUnprepareHeader ими можно распоряжаться
          //по собственному усмотрению
          //-------------------------------------
          //так мы и сделаем, перепишем содержимое буфера в бинарный файл
               int Hndl = FileCreate("d:\\Sound1.bin");
                  if(Hndl == -1)
                 Application->MessageBox("Не могу открыть файло",
                     "File Error", MB_OK);
                 else  {
               FileWrite(Hndl, WaveHdr.lpData, WaveHdr.dwBufferLength);
                              FileClose(Hndl);  }
           
          //---------------------------------------------
                        //а теперь сбросим устройство записи
                            waveInReset(hWaveIn);
                       //и освободим память из-под буфера
                            free(WaveHdr.lpData);
           
          //По окончанию записи следует закрыть аудиоустройство:
                       waveInClose(hWaveIn);
          //нижние 2 строки можно и нужно выкинуть. Нужны только на этапе отладки.
          //При успехе-сообщение: "Указанная команда выполнена"
                     waveInGetErrorText(mmRes, buff, 100);
                       ShowMessage(buff);
          }
           
           
          //---------------------------------------------------------------------------

        что нужно сделать чтоб получать непрерывный сигнал для обработки?
          Этот код использует ММЕ интерфейс, а не DS. Только Ваш образец нужно основательно обработать напильником. Если Вам нужен ММЕ вариант захвата звука, вопрос не раз обсуждался.
          http://yandex.ru/yandsearch?text=waveinope...urces.ru&lr=187
          Сообщение отредактировано: Prince -
            ступил... бывает. Вообщем в конечном итоге у меня должен получиться демодулятор сигнала ЧТ. Со звуком никогда раньше не работал. Как можно организовать наиболее эфективно захват звука со входа звуковой карты? Все это нужно организовать на с++ builder
              Цитата
              Вообщем в конечном итоге у меня должен получиться демодулятор сигнала ЧТ. Со звуком никогда раньше не работал.

              Тогда нужно разбираться с образцом.
                Поскольку автор темы потерялся, я, пожалуй, продолжу тему о захвате звука с помощью интерфейса DirectSound. На delphi.
                Исправления и дополнения, как всегда, приветствуются.

                Почему-то во всех источниках( например упомянутый http://www.helloworld.ru/texts/comp/games/...sound/index.htm), которые по видимому, являются вариантами одного и того же источника, упоминается о VxD драйверах, Win98, NT, и о том, что функции DirectSoundCapture эмулируются через ММЕ.
                Если я правильно понял, всё это в прошлом.

                Для записи используется устройство по умолчанию, но поскольку GUID-ы всех устройств при переборе заносятся в массив GUIDList, а ТDSRecorder имеет свойство GUID, добавить строчку в подпрограмму StartRecord труда не составит.
                Метод определения текущей позиции для чтения данных из буфера может быть и другим, в частности я не разбирался с GetCurrentPosition. А эвенты для нотификации позиции записи в буфере могут быть разные, а не один эвент для всех точек.
                Обработки ошибок, как всегда, практически нет.
                основной модуль программы:
                На форме один комбобокс, в который заносятся названия устройств, один TEdit, в котором отображается имя wav-файла, SaveDialog+button для выбора файла, в который будет производиться запись, и один button для запуска/останова процесса записи. Ну и две текстовые метки.
                user posted image
                ExpandedWrap disabled
                  unit Unit1;
                   
                  interface
                   
                  uses
                    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
                    directsound, StdCtrls, mmsystem, ExtCtrls, TeeProcs, TeEngine, Chart,
                    Series,unit2;
                   
                  const wm_dserror=wm_user+1;
                   
                  type
                    TForm1 = class(TForm)
                      RecStopButton: TButton;
                      devicesComboBox: TComboBox;
                      Label2: TLabel;
                      Label3: TLabel;
                      FileNameEdit: TEdit;
                      SaveDialog: TSaveDialog;
                      BrowsButton: TButton;
                      procedure RecStopButtonClick(Sender: TObject);
                      procedure FormCreate(Sender: TObject);
                      procedure FormClose(Sender: TObject; var Action: TCloseAction);
                      procedure BrowsButtonClick(Sender: TObject);
                    private
                      procedure DSMessage(var Msg:TMessage);message wm_dserror;
                      { Private declarations }
                    public
                      procedure startrecord;
                      procedure stoprecord;
                      { Public declarations }
                    end;
                   
                  var
                    Form1: TForm1;
                    GUIDList: array of PGuid;
                    fmt:twaveformatex;
                    ds:TDSRecorder;
                  implementation
                   
                  {$R *.DFM}
                   
                   
                  function EnumCallback(lpGuid: PGUID; lpstrDescription: PChar;
                        lpstrModule: PChar; lpContext: Pointer) : bool; stdcall;
                    var devices:tcombobox;
                  Begin
                  // - добавляю в комбобокс название устройства DirectSound
                    devices:=lpcontext;
                    devices.Items.Add(string(lpstrDescription));
                   
                  // - если есть указатель на GUID - запоминаю, для обращения к этому устройству
                  // - Устройство по умолчанию не использует GUID, для него lpGUID=nil
                    setlength(GUIDList,length(GUIDList)+1);
                      if lpguid<>nil then Begin
                                            new(GUIDList[length(GUIDList)-1]);
                                            GUIDList[length(GUIDList)-1]^:=lpguid^;
                                          End;
                   
                  End;
                   
                  procedure TForm1.DSMessage(var Msg:TMessage);
                  begin
                  application.messagebox(pchar(dserrorstring(msg.WParam)),'Ошибка',mb_OK);
                  end;
                   
                  procedure TForm1.startrecord;
                  Begin
                   
                   ds:=TDSRecorder.Create(true);
                   ds.HWNDOwner:=Handle;
                   with fmt do
                            Begin
                              wFormatTag:=wave_format_pcm;
                              cbSize:=0;
                              nChannels:=1;
                              nSamplesPerSec:=48000;
                              wBitsPerSample:=16;
                              nBlockAlign:=nChannels*wBitsPerSample shr 3;
                              nAvgBytesPerSec:=nSamplesPerSec*nBlockAlign;
                            end;
                   ds.Format:=@fmt;
                   ds.FileName:=FileNameEdit.Text;
                   ds.Resume;
                  end;
                   
                  procedure TForm1.stoprecord;
                  Begin
                   ds.Terminate;
                   ds.WaitFor;
                   ds.Free;
                   ds:=nil;
                  end;
                   
                   
                  procedure TForm1.RecStopButtonClick(Sender: TObject);
                  begin
                  if not assigned(ds) then startrecord;
                                      else stoprecord;
                                                    
                   
                  end;
                   
                  //******************************************************************************
                  procedure TForm1.FormCreate(Sender: TObject);
                  var res:hresult;
                  begin
                  SaveDialog.InitialDir:=extractfilepath(application.ExeName);
                  FileNameEdit.Text:=SaveDialog.InitialDir+'/Test.wav';
                   
                  // - нахожу все устройства DirectSound
                  // - название устройства заносится в комбобокс, а GUID, если имеется, в массив
                  // - GUIDList
                    res:=DirectSoundcaptureEnumerate(@EnumCallback, devicescombobox);
                   
                  // - если перебор устройств отработал с ошибкой - выход.
                  // - Запись будет невозможна, так как кнопка записи неактивна.
                    if res<>DS_OK then Begin
                                        dserrorstring(res);
                                        exit;
                                       End;
                   
                  // - Если есть хотя бы одно устройство DS имеется, устанавливаю в комбобоксе
                  // - активным первый элемент списка и активизирую кнопку записи
                    if devicescombobox.Items.Count>0 then Begin
                                                            DevicesComboBox.ItemIndex:=0;
                                                            RecStopButton.Enabled:=true;
                                                          End;
                  end;
                   
                  //******************************************************************************
                  procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
                  var i:integer;
                  begin
                  if ds<>nil then
                  stoprecord;
                  // - Освобождаю массив GUIDList.
                    if GUIDList<>nil then Begin
                                            for i:=0 to length(GUIDList)-1 do
                                            if GUIDList[i]<>nil then
                                             Begin
                                               dispose(GUIDList[i]);
                                               GUIDList[i]:=nil;
                                             End;
                                            finalize(GUIDList);
                                          End;  
                  end;
                   
                   
                   
                   
                  procedure TForm1.BrowsButtonClick(Sender: TObject);
                  begin
                  if SaveDialog.Execute then FileNameEdit.Text:=SaveDialog.FileName;
                  end;
                   
                  end.



                Поток, в котором собственно происходит запись:
                ExpandedWrap disabled
                  unit Unit2;
                   
                  interface
                   
                  uses
                    Classes,DirectSound,mmsystem,windows;
                  const
                  // - кусками такого размера будет производиться запись в файл
                        notifysize=8192;
                  // - а это количество точек(позиций) уведомления в буфере, от этого параметра зависит
                  // - размер буфера записи, он равен notifysize*notifycount
                        notifycount=5;
                  type
                    
                    TDSRecorder = class(TThread)
                    private
                      FResult         :HResult;
                      FGUID           :PGUID;
                      FFormat         :PWaveFormatEx;
                      FFileName       :String;
                      FDSCB           :iDirectSoundCaptureBuffer;
                      FDSC            :IDirectSoundCapture;
                      FDSCBDesc       :TDSCBUFFERDESC;
                      FHWNDOwner      :THandle;
                      FNotifyEvent    :Thandle;
                      FNotifies       :array of TDSBPositionNotify;
                      FDSNotify       :IDirectSoundNotify;
                      
                   
                        { Private declarations }
                    protected
                      procedure Execute; override;
                    public
                      property Format   :PWaveFormatEx    read FFormat    write FFormat;
                      property GUID     :PGUID            read FGUID      write FGUID;
                      property FileName :string           read FFileName  write FFileName;
                      property HWNDOwner:THandle          read FHWNDOwner write FHWNDOwner;
                    end;
                   
                  implementation
                   
                  function  mmiofourcc(ch1,ch2,ch3,ch4:char):fourcc;
                  begin
                  result:=byte(ch1) or byte(ch2)shl 8 or byte(ch3) shl 16 or byte(ch4)shl 24;
                  end;
                   
                   
                  { TDSRecorder }
                  procedure TDSRecorder.Execute;
                  var i                 :integer;
                      FmmckinfoSubchunk :MMCKINFO;
                      FmmckinfoParent   :MMCKINFO;
                      Fmmio             :HMMIO;
                      p1,p2             :pointer;
                      size1,size2       :cardinal;
                      FPosition         :DWord;
                  begin
                    { Place thread code here }
                  // - получаю ссылку на интерфейс DS
                  FResult:=DirectSoundCaptureCreate(FGUID,FDSC,nil);
                    if FResult<>DS_OK then Begin
                                            PostMessage(FHwndOwner,wm_dserror,FResult,0);
                                            exit;
                                           end;
                  // - подготавливаю свойства и формат для буфера захвата
                    with FDSCBDesc do Begin
                                        dwSize:=sizeof(TDSCBUFFERDESC);
                                        dwFlags:= 0;
                                        dwBufferBytes:=notifysize*notifycount;
                                        lpwfxFormat:=FFormat;
                                        dwFXCount:=0;
                                        dwReserved:=0;
                                        lpDSCFXDesc:=nil;
                                      End;
                  // - получаю ссылку на интерфейс буфера захвата
                  FResult:=FDSC.CreateCaptureBuffer(FDSCBDesc,FDSCB,nil);
                    if FResult<>DS_OK then Begin
                    PostMessage(FHwndOwner,wm_dserror,FResult,0);
                                           exit;
                                           end;
                  // - подготавливаю массив позиций буфера захвата, при достижении которых буду получать уведомление.
                  FNotifyEvent:=CreateEvent(nil,false,false,nil);
                  setlength(FNotifies,notifycount);
                  for i:=0 to length(FNotifies)-1 do Begin
                                                      FNotifies[i].dwOffset:=notifysize*(i+1)-1;
                                                      FNotifies[i].hEventNotify:=FNotifyEvent;
                                                     end;
                  // - установка позиций уведомления
                  FDSCB.QueryInterface(IDirectSoundNotify,FDSNotify);
                  if assigned(FDSNotify) then
                  FDSNotify.SetNotificationPositions(length(FNotifies),@FNotifies[0]);
                   
                  // - создаю заголовок wave-файла
                  Fmmio:=mmioopen(pchar(FFilename),nil,MMIO_CREATE or MMIO_WRITE);
                  FmmckinfoParent.fccType:=mmiofourcc('W','A','V','E');
                  mmiocreatechunk(Fmmio,@FmmckinfoParent,MMIO_CREATERIFF);
                  FmmckinfoSubchunk.ckid:=mmioFOURCC('f', 'm', 't', ' ');
                  FmmckinfoSubchunk.fccType:=mmiofourcc('W','A','V','E');
                  mmiocreatechunk(Fmmio,@FmmckinfoSubchunk,0);
                  mmiowrite( Fmmio,@FFormat^,sizeof(twaveformatex));
                  mmioascend(Fmmio,@FmmckinfoSubchunk,0);
                  FmmckinfoSubchunk.ckid:=mmioFOURCC('d', 'a', 't', 'a');
                  mmiocreatechunk(Fmmio,@FmmckinfoSubchunk,0);
                   
                  // - запускаю буфер в режиме циклической записи
                  FResult:=FDSCB.Start(DSBPLAY_LOOPING);
                  if FResult<>DS_OK then PostMessage(FHwndOwner,wm_dserror,FResult,0);
                   
                   
                  FPosition:=0;
                  //***************************************************************************************
                  // - основной цикл. чтение производится кусками размером notifysize при достижении
                  // - очередной точки уведомления, и size1 при вызове Lock будет возвращать это значение,
                  // - а size2 всегда будет 0-м. Так было задумано.
                  // - этот цикл может иметь много вариантов. Я его написал так, чтобы долго не мучаться.
                  while not terminated do begin
                  if waitforsingleobject(FNotifyEvent,500)=WAIT_TIMEOUT then continue;
                  size1:=0;size2:=0;
                  Fresult:=fdscb.Lock(Fposition,notifysize,p1,size1,p2,size2,0);
                  FPosition:=FPosition+notifysize;
                  // - Позиции уведосления принимают одни и те же значения:
                  // - 0, notifysize, notifysize*2..., notifysize*notifycount(0), notifysize,....
                  // - Поэтому и позиция чтения Fposition принимает те же значения.
                  // - Значение notifysize*notifycount соответствует позиции, следующей за концом буфера.
                  // - Поскольку буфер циклический,  реально это нулевая позиция.
                  if fposition>=FDSCBDesc.dwBufferBytes then  Fposition:=Fposition-FDSCBDesc.dwBufferBytes;
                  if FResult=DS_OK then Begin
                  // - Записываю очередную порцию данных в файл
                  if size1>0 then mmiowrite( Fmmio,p1,size1);
                  //if size2>0 then mmiowrite( Fmmio,p2,size2);
                  fdscb.Unlock(p1,size1,p2,size2);
                                        end;
                                         end;
                  //***************************************************************************************
                  // - закрываю все блоки wave-файла, и сам файл, при этом происходит корректировка размеров блоков(сhunk-ов)
                  mmioascend(Fmmio,@FmmckinfoSubchunk,0);
                  mmioascend(Fmmio,@FmmckinfoParent,0);
                  mmioclose(Fmmio,0);
                  // - останавливаю запись и освобождаю ресурсы
                  FResult:=FDSCB.Stop;
                  FDSNotify:=nil;
                  finalize(FNotifies);
                  CloseHandle(FNotifyEvent);
                  end;
                   
                  end.
                Сообщение отредактировано: Prince -
                  не понял, а как передается в записывающий код номер или имя устройства с которого надо делать запись? например после выбора его в комбобоксе

                  Добавлено
                  Дописал
                  ds.GUID:=GUIDList[cbbComboBox.ItemIndex];
                  Но все равно ничего не пишет в файл с микрофона.

                  Fresult:=fdscb.Lock(Fposition,notifysize,p1,size1,p2,size2,0);
                  FPosition:=FPosition+notifysize;
                  if fposition>=FDSCBDesc.dwBufferBytes then Fposition:=Fposition-FDSCBDesc.dwBufferBytes;
                  if FResult=DS_OK then Begin
                  здесь не попадает в запись в файл, потому что результат не DS_OK

                  Добавлено
                  Вываливается с кодом The parameter is incorrect
                  :no-sad:
                    Давно это было. Выкладывайте проект.
                      Вот такой код заработал


                      p1,p2 :PPointer;//pointer;
                      size1,size2 :PDWORD;//cardinal;

                      ...


                      size1:=nil;size2:=nil;
                      Fresult:=fdscb.Lock(Fposition,notifysize,@p1,@size1,@p2,@size2,0);
                      FPosition:=FPosition+notifysize;
                      if fposition>=FDSCBDesc.dwBufferBytes then Fposition:=Fposition-FDSCBDesc.dwBufferBytes;
                      if FResult=DS_OK then Begin
                      // - Записываю очередную порцию данных в файл
                      if size1<>nil then begin
                      mmiowrite(Fmmio,p1^,Integer(size1));
                      end;
                      Fresult:=fdscb.Unlock(p1,Cardinal(size1),p2,Cardinal(size2));
                      ...
                        Ужас. Выкладывайте проект.
                          Манера выкладывать не рабочий код это конечно жесть :)
                          :wacko: :lol:

                          Добавлено
                          Зачем выкладывать? Изменения я написал. :)
                          Это на ДельфиXe2. ДиректХ хедеры в ней включены кстати. :)
                            Цитата
                            Манера выкладывать не рабочий код это конечно жесть

                            Если вы про мой код, то он рабочий. Ваш теперь уже как бы и не нужен - теперь нужны прототипы функций из хидеров, вероятно, они отличаются от тех, что я некогда использовал для D7. Указатель на указатель и адрес от указателя на указатель - это ж ужас. Адрес указателя на dword тоже.
                            Нужно разбираться с типами данных. В смысле, разбирайтесь с типами данных. :-?
                            Сообщение отредактировано: Prince -
                              В частности
                              unit Winapi.DirectSound;
                              теперь такая
                              function Lock(dwOffset, dwBytes: DWORD; ppvAudioPtr1: PPointer; pdwAudioBytes1: PDWORD; ppvAudioPtr2: PPointer; pdwAudioBytes2: PDWORD; dwFlags: DWORD): HResult; stdcall;
                                Тогда
                                ExpandedWrap disabled
                                  p1,p2 :Pointer;//pointer;
                                  size1,size2 :DWORD;//cardinal;
                                   
                                  Fresult:=fdscb.Lock(Fposition,notifysize,@p1,@size1,@p2,@size2,0);


                                "Мой" прототип выглядит(ел) так:
                                ExpandedWrap disabled
                                  Lock(dwWriteCursor, dwWriteBytes: DWORD;
                                          var lplpvAudioPtr1: Pointer; var lpdwAudioBytes1: DWORD;
                                          var lplpvAudioPtr2: Pointer; var lpdwAudioBytes2: DWORD;
                                          dwFlags: DWORD) : HResult; stdcall;
                                Сообщение отредактировано: Prince -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0880 ]   [ 16 queries used ]   [ Generated: 25.04.24, 02:10 GMT ]