На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania 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
  
> Непрерывный поток звука
    Изучаю возможности программного вывода звука.
    С получением звука с микрофона вроде всё нормально, а вот вывод ни к чёрту.)
    Маленький кусок кода:

    ExpandedWrap disabled
      unit U4_1;
      interface
      uses
        Windows, Math, Messages, Classes, Forms, MMSystem;
      const
          discret = 8000;
          SizeBufOut = 400;
      type
        TForm1 = class(TForm)
          procedure FormCreate(Sender: TObject);
        public
          procedure WCard;
        end;
      var
         hMapFile:THandle;
         lpBaseAddress:PChar;
         Vdata: array [0..SizeBufOut-1] of integer;
          Form1: TForm1;
          WaveFormatEx: TWaveFormatEx;
          WaveOut:HWAVEOUT;
          hEvent: THandle;
          woh: WAVEHDR;
          Bits16: boolean;
          hBuf: THandle;
          BufHead: TWaveHdr;
      implementation
      {$R *.dfm}
       
      procedure TForm1.FormCreate(Sender: TObject);
      begin
          WCard;
      end;
       
      procedure TForm1.WCard;
      var
        BufLen: word;
        buf: pointer;
        n: word;
        begin
          bits16 := True;
          with WaveFormatEx do
          begin
            wFormatTag := WAVE_FORMAT_PCM;
            nChannels := 1;
            nSamplesPerSec := discret;
            wBitsPerSample := integer(Bits16) * 8 + 8;
            nBlockAlign := nChannels * (wBitsPerSample div 8);
            nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
          end;
          hEvent := CreateEvent(nil, false, false, nil);
          WaveOutOpen(Addr(WaveOut), 0, addr(WaveFormatEx), hEvent, 0,CALLBACK_EVENT);
          for n:=0 to SizeBufOut-1 do Vdata[n]:=RandomRange(1,1000);
          BufLen := WaveFormatEx.nBlockAlign * SizeBufOut;
          with BufHead do
          begin
            woh.lpData := addr(Vdata);
            woh.dwBufferLength := BufLen;
          end;
            waveOutPrepareHeader(WaveOut, addr(woh), sizeof(woh));
            waveOutWrite(WaveOut, addr(woh), sizeof(woh));
        end;
       
      end.

    Вроде звук одного блока получил, а вот как его зациклить (для начала), чтобы это был единый поток не получается.
      Цитата Ivan123 @
      чтобы это был единый поток не получается.

      Вопрос решён
        Вот что получилось кому интересно

        ExpandedWrap disabled
          unit U7_1;
           
          interface
           
          uses
            Windows, Math, Messages, Classes, Forms, MMSystem;
          procedure Execute;
          const
              discret = 8000;
              SizeBufOut = 400;
              MMFName: pAnsiChar = 'MP';
              a=0;
           
          type
            TForm1 = class(TForm)
              procedure FormCreate(Sender: TObject);
            public
              procedure WCard;
            end;
           
          var
              hMapFile:THandle;
              lpBaseAddress:PChar;
              Vdata: array [0..SizeBufOut-1] of smallint;
           
              Form1: TForm1;
              WaveFormatEx: TWaveFormatEx;
              WaveOut:HWAVEOUT;
              hEvent: THandle;
              woh: WAVEHDR;
              BufHead: TWaveHdr;
              n: word;
           
          implementation
           
          {$R *.dfm}
           
          procedure TForm1.FormCreate(Sender: TObject);
          begin
              WCard;
              Execute;
          end;
           
          procedure TForm1.WCard;
          var
            BufLen: word;
            n: word;
           
            begin
              with WaveFormatEx do
              begin
                wFormatTag := WAVE_FORMAT_PCM;
                nChannels := 1;
                nSamplesPerSec := discret;
                wBitsPerSample := 16;
                nBlockAlign := nChannels * (wBitsPerSample div 8);
              end;
           
              hMapFile := OpenFileMapping(FILE_MAP_ALL_ACCESS,False,MMFName);
              lpBaseAddress := MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1);
           
              hEvent := CreateEvent(nil, false, false, nil);
              WaveOutOpen(Addr(WaveOut), 0, addr(WaveFormatEx), hEvent, 0,CALLBACK_EVENT);
              waveOutPrepareHeader(WaveOut, addr(woh),SizeOf(woh));
              BufLen := WaveFormatEx.nBlockAlign * SizeBufOut;
              with BufHead do
                begin
                  woh.lpData := addr(Vdata);
                  woh.dwBufferLength := BufLen;
                end;
            end;
           
          procedure Execute;
            begin
              while a<1 do
                begin;
                  //CopyMemory(addr(Vdata), lpBaseAddress, SizeBufOut*2);
                  for n:=0 to SizeBufOut-1 do Vdata[n]:=RandomRange(1,32000);
                  waveOutWrite(WaveOut, addr(woh), SizeOf(woh));
                  waveOutReset(WaveOut);
                end;
            end;
           
          end.

        Прикреплённый файлПрикреплённый файлDF9.rar (171,62 Кбайт, скачиваний: 37)
          ExpandedWrap disabled
            type
              TForm1 = class(TForm)
                procedure FormCreate(Sender: TObject);
                procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
              private
                { Private declarations }
              public
                { Public declarations }
              end;
             
              const
                discret     = 8000;
                SizeBufOut  = 400;
                bufcount    = 3;
                play        = 1;
                stop        = 0;
             
            var
               Form1        : TForm1;
               WaveOut      : HWAVEOUT;
               mode         : integer;
               actbufcount  : cardinal;
             
            implementation
             
            {$R *.dfm}
             
            Procedure FillBuf(var buf);
              var
                ps  : psmallint;
                i   : cardinal;
             
            begin
              ps:=pointer(buf);
              for i:=0 to sizebufout-1 do
                begin
                  ps^:=Random(32000)-16000;
                  inc(ps);
                end;
             
            end;
             
            procedure waveOutProc( hwo:HWAVEOUT; uMsg,dwinstance,dwParam1,dwParam2:dword);stdcall;
            Begin
              case umsg of
                WOM_OPEN:;
                WOM_CLOSE:;
                WOM_DONE:
                    if mode<>stop
                           then
                            begin
                              fillbuf(PWaveHdr(dwParam1)^.lpData);
                              waveoutwrite(waveout,PWaveHdr(dwParam1),sizeof(TwaveHdr));
                            end
                            else
                            begin
                              waveoutunprepareheader(waveout,PWaveHdr(dwParam1),sizeof(TwaveHdr));
                              freemem(PWaveHdr(dwParam1)^.lpData);
                              freemem(PWaveHdr(dwParam1));
                              dec(actbufcount);
                            end;
              end;
            End;
             
            procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
            begin
            mode:=stop;
               repeat
                until actbufcount=0;
              waveoutclose(waveout);
              canclose:=true;
            end;
             
            procedure TForm1.FormCreate(Sender: TObject);
             
              var
                  fmt   : TWaveFormatEx;
                  pwhdr : PWaveHdr;
                  i     : cardinal;
             
            begin
              mode:=stop;
              actbufcount:=0;
                with fmt do
                begin
                  wFormatTag      := WAVE_FORMAT_PCM;
                  nChannels       := 1;
                  nSamplesPerSec  := discret;
                  wBitsPerSample  := 16;
                  nBlockAlign     := nChannels * (wBitsPerSample div 8);
                  nAvgBytesPerSec :=nBlockAlign*nSamplesPerSec;
                  cbsize          :=0;
                end;
             
                WaveOutOpen(@WaveOut, 0, @fmt, dword(@waveoutproc), 0,CALLBACK_FUNCTION);
                WaveOutPause(WaveOut);
             
             for i:=0 to bufcount-1 do
              begin
                pwhdr:=allocmem(sizeof(TWaveHdr));
                pwhdr.lpData:=allocmem(SizeBufOut*fmt.nBlockAlign);
                pwhdr.dwBufferLength:= SizeBufOut*fmt.nBlockAlign;
                waveOutPrepareHeader(WaveOut, pwhdr,SizeOf(TWaveHdr));
                fillbuf(pwhdr.lpData);
                waveoutwrite(waveout,pwhdr,sizeof(TwaveHdr));
                inc(actbufcount);
              end;
              mode:=play;
              waveoutrestart(waveout);
             
              //
            end;
          Человек человека понять не может.
            Спасибо за пример, наверно так профессионально.
            Я писал просто чтобы получить хоть какой положительный результат) у вас это произведение, учиться никогда не поздно.
              Пример самый обычный, для песочницы, для теста, для проверки идеи. Как один из возможных вариантов. Ваш пример совсем нехорош. Он не решает задачу, не обеспечивает непрерывность звукового потока.
              Человек человека понять не может.
                Цитата Ivan123 @
                не обеспечивает непрерывность звукового потока

                Согласен, но на тот момент задача стояла услышать программно любой звук, да и функции мультимедия я видел впервые, так что я только учусь и спасибо за помощь)
                  Цитата Prince @
                  Как один из возможных вариантов

                  Ещё один вопрос про ещё один вариант.
                  А как этот кусок будет выглядеть для консоли? К сожалению из консоли у меня никак не получается добраться до аудио буфера.
                  Или может какую литературу подскажите по теме?
                    Литература в FAQ раздела. Рекомендую книги Секунова и Кинтцеля.
                    Консольный вариант будет аналогичным. В консольном приложении сначала выполните код из метода create, потом будет пустой цикл, с выходом по нажатию клавиши, а за ним то, что в closequery.
                    Сообщение отредактировано: Prince -
                    Человек человека понять не может.
                      Цитата Prince @
                      bufcount = 3;

                      1. Можете пояснить зачем нужен этот цикл, и почему 3, не 1, не 4 и тд?

                      Если я правильно понимаю, чтобы просто разделить процесс записи в буфер и процесс воспроизведения из буфера, но тогда почему 3, а не 2?

                      2. И каков максимальный размер блока аудиоданных? Или для каждого устройства он разный и его надо предварительно определять? и сколько таких блоков (вернее какой суммарный объём байтов) можно запихнуть в этот буфер?
                      Сообщение отредактировано: Ivan123 -
                        1. Минимум необходимы 2 буфера, для обеспечения непрерывности звука. Они же могут быть и достаточны. Я выбрал 3, мне так больше нравится. Максимум...любое вменяемое количество, но ограниченное объемом памяти. 100500 - бессмысленно.

                        2. Максимальный размер блока [может быть] ограничен объемом памяти, организацией памяти, драйвером, разрядностью DWORD(?)... не проверял, потому как смысла нет.

                        Буфер - он же и есть блок данных. Размер выбираете сами. Снизу размер/длительность ограничены быстродействием железа и многопоточностью винды(разрешением системного таймера, например), сверху - объемом памяти.
                        На практике примерно 20 мс минимум, если повезет. Надежней 50 мс. Совсем хорошо - 100 мс. Зависит от задачи. Верхний предел 200-1000 мс. Можно больше, зависит от задачи.

                        Читайте литературу. Когда поймете принцип работы с блоками, размеры и количества встанут на свои места. Для них нет однозначных жестких ограничений. За исключением, пожалуй, минимального количества блоков. Хотя и это ограничение можно обойти. Но тогда потеряется простота работы с ними. Точнее, один буфер противоречит самой логике работы интерфейса.
                        Сообщение отредактировано: Prince -
                        Человек человека понять не может.
                          Я бы поставил штуки 4 общим объёмом в секунду, чтоб не париться, что называется (для надёжности).
                          По 50 мс как-то маловато (по мне), мало ли подвиснет система на десятые доли секунды.
                          Хотя вон ASIO работает по 32 семпла (1.5 мсек) и ничего, не глючит, а 64 – вообще огонь (хотя, там технология другая, тут буферы по 64 семпла вряд ли будут нормально звучать) :)
                          Тут действительно кто во что горазд и какие задачи. Может, у вас там целая эпопея делается длительностью в полсекунды перед чтением очередного блока :rolleyes:
                          vpmultishiftqb vscatterpf0dps vfmsubadd132pd vgatherpf1dps vpclmulhqlqdq vcmptrue_ussd vaeskeygenassist
                            Цитата Prince @
                            Минимум необходимы 2 буфера

                            Переписал свой код для захвата звука под консоль и с учётом ваших рекомендаций по структуре вывода белого шума (расположен выше)

                            ExpandedWrap disabled
                              procedure FillBuf(var buf);
                              var
                                ps      : psmallint;
                                i       : cardinal;
                              begin
                                ps:=pointer(buf);
                                for i:=0 to SizeBufIn-1 do
                                  begin
                                    ps^:=PData16(pwhdr.lpData)^[i];
                                    Vdata[i]:=ps^;
                                    inc(ps);
                                  end;
                              end;
                               
                               
                              procedure waveInProc( hwo:HWAVEIN; uMsg, dwinstance, dwParam1, dwParam2:dword); stdcall;
                              begin
                                if uMsg = WIM_DATA then
                                  begin
                                    fillbuf(PWaveHdr(dwParam1)^.lpData);
                                    WaveInAddBuffer(waveIn,PWaveHdr(dwParam1),sizeof(TwaveHdr));
                                  end;
                              end;
                               
                              procedure Create;
                              var
                                fmt   : TWaveFormatEx;
                                a,i   : cardinal;
                                buf   : psmallint;
                               
                              begin
                                  with fmt do
                                    begin
                                      wFormatTag      := WAVE_FORMAT_PCM;
                                      nChannels       := 1;
                                      nSamplesPerSec  := discret;
                                      wBitsPerSample  := 16;
                                      nBlockAlign     := nChannels * (wBitsPerSample div 8);
                                      nAvgBytesPerSec :=nBlockAlign * nSamplesPerSec;
                                    end;
                               
                               
                                  WaveInOpen(@WaveIn, WAVE_MAPPER, @fmt, dword(@waveInProc), 0, CALLBACK_FUNCTION);
                               
                                  for i:=0 to bufcount-1 do
                                    begin
                                      pwhdr:=allocmem(sizeof(TWaveHdr));
                                      pwhdr.lpData:=allocmem(SizeBufIn * fmt.nBlockAlign);
                                      pwhdr.dwBufferLength:= SizeBufIn * fmt.nBlockAlign;
                                      pwhdr.dwBytesRecorded:=0;
                                      pwhdr.dwFlags:= 0;
                                      WaveInPrepareHeader(WaveIn, pwhdr, SizeOf(TWaveHdr));
                                      fillbuf(pwhdr.lpData);
                                      WaveInAddBuffer(waveIn, pwhdr, sizeof(TwaveHdr));
                                    end;
                                  WaveInStart(WaveIn);
                                  a:=0;
                                  Repeat
                                    FillBuf(buf);
                                  until
                                    a > 0;
                              end;


                            И здесь у меня возникло два вопроса:
                            1. почему то у меня не получается организовать 2 буфера, вернее получается , но при первом цикле первый заполняется не полностью, а в последующем данные дублируются и во второй буфер. При bufcount=1 всё получается красиво с данными, но скорее всего это будет не непрерывный сигнал.
                            2. Вопрос касается другой темы, где я решал вопрос с микшером. Можно ли убить отправку данных на драйвер в этом коде каким нибудь хитрым способом, например заполнив перед WaveInAddBuffer буфер нулями.
                            Сообщение отредактировано: Ivan123 -
                              Цитата Ivan123 @
                              Переписал свой код для захвата звука под консоль и с учётом ваших рекомендаций по структуре вывода белого шума

                              Белый шум по определению так устроен, что на нём не будут заметны никакие сбои, он плохо подходит для тестирования непрерывного воспроизведения.
                                Что вы хотите получить в итоге. Куда сливается аудиопоток? В vdata...а дальше?
                                ExpandedWrap disabled
                                  procedure FillBuf(var buf);
                                  var
                                    ps      : psmallint;
                                    i       : cardinal;
                                  begin
                                    ps:=pointer(buf);
                                    for i:=0 to SizeBufIn-1 do
                                      begin
                                        ps^:=PData16(pwhdr.lpData)^[i]; //что оно должно делать??????
                                        Vdata[i]:=ps^;
                                        inc(ps);

                                Если вам нужно (для чего-то) переписать буфер в другой массив, используйте функцию Move;


                                ExpandedWrap disabled
                                   WaveInPrepareHeader(WaveIn, pwhdr, SizeOf(TWaveHdr));
                                          fillbuf(pwhdr.lpData);
                                          WaveInAddBuffer(waveIn, pwhdr, sizeof(TwaveHdr));

                                fillbuf здесь не к месту. Буфер ещё не заполнен данными аудио. Там пока нули, поскольку память выделялась через allocmem. Если бы память выделялась как-то иначе, возможно, был бы мусор. Но там еще нет аудио. Аудио ловится только в waveinproc по WIM_DATA и нигде больше.

                                Цитата
                                a:=0;
                                Repeat
                                FillBuf(buf);
                                until
                                a > 0;

                                Это вообще ерунда какая-то.
                                ExpandedWrap disabled
                                  readln;

                                заменит эту конструкцию. fillbuf снова не в тему, на выходе приложения черте-что окажется вместо непрерывного аудио потока.

                                Код неполный, но и так видно, что он работает неправильно.
                                Сообщение отредактировано: Prince -
                                Человек человека понять не может.
                                  Цитата Prince @
                                  используйте функцию Move

                                  Так во всём коде и есть, просто мне так удобней на этом этапе увидеть прямо здесь в программе, не прописывая огромные графические коды, сразу увидеть есть считывание данных или нет, появились какие то значения отличные от нуля или нет

                                  Цитата Prince @
                                  был бы мусор

                                  именно мусор я и не хотел увидеть вначале в vdata, а затем (по моей примитивной логике) vdata должна была бы начать заполняться данными
                                  Цитата Prince @
                                  readln;

                                  я уже писал что я не профессиональный программист, возможно если бы программированием занимался десяток лет, я бы и не задавал здесь столько вопросов. Я изучаю (пытаюсь понять) методику манипулирования процессами программной реализации записи и вывода звука. Не все его возможности и варианты реализации (на это у меня просто физически нет времени), а только в рамках узких векторов поставленной передо мной задачи. Поэтому как реализовать процесс непрерывной записи пока для меня загадка. Я пока физически не могу понять как происходит зацикливание процесса записи освобождение буферов, когда это начинается, чем контролируется, кто это регулирует, какие процессы на это влияют, а от каких вообще не зависит, и когда заканчивается. Поэтому и цикл применил самый примитивный, хотя скорее всего после WaveInStart он начинается и продолжается пока его принудительно не остановить. И как встроить в этот цикл свой кусок кода правильно я пока не знаю. Если бы я увидел структурную схему как это работает мне было бы значительно проще. Но пока я вижу только различные вариации выполнения разного вида задач.
                                  Вы и сами наверно не сразу пришли к пониманию процессов связанных со звуком (если судить сколько вы в этой теме даже по форуму). Я удивляюсь насколько у вас хватает терпения отвечать на одни и те же вопросы (в том числе и мои) в течении такого долгого времени. Многое из того что мне стало более менее понятно я почерпнул и ваших других тем 6-8 летней давности (одна проблема что кроме информации через поиск приходится читать и такую белиберду как пишу я сейчас).

                                  Добавлено
                                  ЗЫ. Хочу несколько слов о моём подходе к правилам построения кода (моего личного кода), который постоянно критикуется с точки зрения правильного построения. Открыл - закрой, зарезервировал - освободи, проверь то - проверь это, а вот если так - то тогда опа , а вот если не опа - тогда счастье. Все эти куски в своём коде, на своей машине, на этапе отладки, я просто убираю. Мне это просто мешает, когда программа никогда в эти области не попадает, а всё это постоянно мелькает перед глазами. Может и неправильный подход, но мне так удобней.
                                  Но всё равно хорошо что вы пишете как правильно). Вероятнее всего эти проверки понадобятся для запуска на другой машине, или в других каких случаях про какие я даже сейчас не знаю.

                                  И хотелось бы всё же получить ответ на вопрос - возможно ли после заполнения vdata блоком данных подменить их на 0 или 1, которые я всё равно не услышу на выходе. Вопрос из другой темы про вывод данных, но возможно его проще реализовать на входе, считать данные, но не отправлять их на выход?

                                  Добавлено
                                  Цитата Mikle @
                                  он плохо подходит для тестирования непрерывного воспроизведения

                                  Согласен полностью, но я нахожусь пока на начальном этапе воспроизведения звука, возможно когда нибудь я доберусь и до этих вопросов)
                                  Сообщение отредактировано: Ivan123 -
                                    Перед началом записи нужно поставить в очередь записи несколько буферов, при помощи waveinaddbuffer. Затем запускается процесс записи при помощи waveinstart. Начинается заполнение первого буфера в очереди. Когда он заполнен, вызывается waveinproc, куда передается адрес заголовка. В это время запись продолжается в следующий (в очереди) буфер. Именно поэтому буферов должно быть как минимум 2. В waveinproc выполняете операции с данными буфера, а затем снова ставите буфер в очередь. Очень простой механизм.

                                    Добавлено
                                    Цитата
                                    И хотелось бы всё же получить ответ на вопрос - возможно ли после заполнения vdata блоком данных подменить их на 0 или 1, которые я всё равно не услышу на выходе. Вопрос из другой темы про вывод данных, но возможно его проще реализовать на входе, считать данные, но не отправлять их на выход
                                    ?
                                    я не понимаю вопроса. Опишите задачу.
                                    Вы подсовываете системе буфер, система заполняет его данными и уведомляет вас о том, что он заполнен. И все. Что с этими данными делать(или ничего не делать) - система не знает. Все действия прописываете вы. Можете заменить данные, полученные от системы на что угодно. На 0 или на 1. В чем смысл?
                                    Сообщение отредактировано: Prince -
                                    Человек человека понять не может.
                                      Цитата Prince @
                                      я не понимаю вопроса. Опишите задачу

                                      Что означает ваш девиз - "Человек человека понять не может")? не тот ли это случай)?
                                      Но вас я вводе понял, теперь хотелось бы чтобы и вы меня поняли.
                                      Попробую ещё раз описать задачку "про яблоки", только с другими действующими лицами)
                                      1. Есть микрофон подключённый к компу к встроенному звуку. Ребёнок в него что то там поёт в виде караоке.
                                      2. Запускается 1 приложение Delphi (в нём WaveInStart) и извлекаем данные микрофона и отправляем их во второе приложение.
                                      3. Запускаем 2 приложение и получаем в нём данные первого приложения, проводим над ним какую нибудь математику и получаем новый сигнал, изменённый в отличии от основного с микрофона, или с какими нибудь звуковыми эффектами. Отправляем изменённые данные третьему приложению.
                                      4. Запускаем 3 приложение (в нём WaveInOpen), получаем данные от второго приложения, с линейного выхода этого же встроенного звука передаём его на вход усилителя мощности и выводим всё это в виде звука на колонки.(Получается та же песенка, но со звуковыми эффектами)).
                                      (Весь этот алгоритм естественно надуман, чтобы каждому было понятно о чём идёт речь, но он полностью описывает существующую проблему).
                                      Проблема: на выходе 3 приложения в колонках я слышу два сигнала и основной от микрофона, и изменённый из второго приложения. И есть ещё 1 нюанс: если уровень изменённого звука после 3 приложения можно регулировать системным микшером, то сигнал от микрофона никак микшером регулировать не получается, а только в настройках записи в системе, что конечно не очень хорошо, поскольку для наилучшего соотношения сигнал/шум необходимо чтобы амплитуда занимала весь динамический диапазон устройства (хотя здесь я могу ошибаться, поскольку пока это только интуиция и данных у меня таких нет).
                                      В принципе регулировать основной звук совсем и не обязательно, его нужно просто отключить.
                                      Всё это проверено на 4 машинах, с разными парами - наушник-микрофон, и разными кодами (особенно мне было интересно не со своими, а например с вашими, и которыми вы пару дней назад выкладывали и которые пару лет). Результат один и тот же (если в одном приложении запущена WaveInStart для какого то звукового устройства, то как только в любом другом приложении запустить WaveInOpen для этого же устройства, сигнал от микрофона передаётся напрямую передаётся на выход.
                                      Я попытался как можно подробней изложить проблему. Возможно это и не проблема, просто я не знаю некоторых тонкостей для её решения. Если мне кто-нибудь поможет в моей ситуации, буду весьма признателен.
                                      Сообщение отредактировано: Ivan123 -
                                        Цитата
                                        Ребёнок в него что то там поёт в виде караоке.

                                        Используется какой-либо софт для караоке? Или подобный? Или ситуация вообще надуманная?

                                        Цитата
                                        уровень изменённого звука после 3 приложения можно регулировать системным микшером, то сигнал от микрофона никак микшером регулировать не получается, а только в настройках записи в системе

                                        Так и есть/"должно быть" в 7.

                                        "Системный микшер" - это программный микшер. Его задача - передискретизация цифровых потоков от разных приложений, регулировка уровня цифровых потоков от разных приложений и смешивание цифровых потоков от разных приложений в один цифровой поток. В общем, программный микшер уровня ядра системы. С виртуального выхода этого софтового микшера, суммарный цифровой поток подаётся на ЦАП звуковой карты, а с выхода ЦАП сигнал, уже аналоговый, проходит на одну из линий микшера(аппаратного).
                                        "Устройства воспроизведения/записывающие устройства" в настройках, в системе - это как раз покоцаный 7-кой собственно аппаратный микшер звуковой карты. С недоразумениями в виде "прослушивать с данного устройства", и не работающим нормально аппаратным сквозным каналом. Регуляторы уровня микрофонного/линейного входов и пр. в настройках активны, но ничего не регулируют. Линии, вероятно, отключены самой 7. Но их, в принципе, может попытаться включить стороннее приложение.
                                        Нужно будет проверять.
                                        А для чего вам нужно регулировать сигнал микрофона где-то еще, помимо настроек записи? Вы ведь сигнал записываете, верно?

                                        Первое, второе, третье приложение - это одно приложение, всё реализуется в одном процессе.
                                        Открыть устройство записи, открыть устройство воспроизведения. Запуск записи и воспроизведения. Обрабатывается то, что прилетает от устройства записи и отправляется на устройство воспроизведения.
                                        Так реализуется программный сквозной канал. То же самое, что и "прослушивать с данного устройства" в 7-ке. Только с обработкой.
                                        Если вы слышите сигнал С задержкой(обработанный) и одновременно сигнал БЕЗ задержки(чистый), да, может быть включен сквозной канал(аппаратный) микшера. Если используется специальный софт для караоке, или подобный, то он может включать сквозной канал в микшере.
                                        Если оба сигнала с задержкой, ищите косяки в ваших приложениях. Но косяки ищите в любом случае. Те кусочки кода, что вы выкладывали - они были с ошибками, напрямую отражающимися в звуке.

                                        Ваш текст не прояснил причин проблемы, и самой проблемы. Приходится строить догадки и предположения.

                                        Пишем звук, изменяем его и выводим. В результате получается сквозной канал. Это то, что я понял. Понятная задача и понятно, как её решать.
                                        1,2,3 пункты в общих чертах понятны.
                                        Цитата
                                        4. Запускаем 3 приложение (в нём WaveInOpen), получаем данные от второго приложения, с линейного выхода этого же встроенного звука передаём его на вход усилителя мощности и выводим всё это в виде звука на колонки.(Получается та же песенка, но со звуковыми эффектами)).

                                        Точно waveINopen?

                                        И на всякий пожарный.
                                        Софтовый процесс записи-воспроизведения обязательно вносит задержку в тракт прохождения сигнала, равную как минимум длительности буфера, поэтому караоке с измененным голосом(например) как бы не совсем разумная идея. Петь, слыша свой голос(тем более измененный) с задержкой относительно музыки и своего живого голоса...психика как бы не расчитана на такое.
                                        Ну и вообще, караоке, сквозной канал - не ахти какая идея в домашних условиях. Разве что с гарнитурой. Но это тоже та ещё идея. Петь себе прямо в ухо...
                                        Сообщение отредактировано: Prince -
                                        Человек человека понять не может.
                                          Здесь когда-то очень давно выкладывал тестовый пример сквозного канала. Он рабочий, но его желательно аккуратно модернизировать до callback_function. И там некрасиво выполняется остановка записи/воспроизведения. При использовании callback_function, если оставить как есть, скорее всего будет утечка памяти, а может и в access violation вылететь.
                                          Сообщение отредактировано: Prince -
                                          Человек человека понять не может.
                                            Цитата Prince @
                                            тестовый пример сквозного канала

                                            Спасибо что не забываете про мои проблемы я как раз эти дни занимался именно этим (пытался написать объединённый код и для получения данных и для вывода данных, думал что это как то повлияет на уменьшение времени задержки). Может код выполнен и не профессионально, но в принципе результат меня устраивает.
                                            В коде можно легко вместо сигнала микрофона подставить белый шум или синус, но микрофон всегда пролазит на выход (на этот раз код полный :D ).
                                            ExpandedWrap disabled
                                              program DF15_IN_OUT;
                                              {$APPTYPE CONSOLE}
                                              uses
                                                Windows,SysUtils,MMSystem;
                                               
                                              const
                                                  discret = 8000;
                                                  SizeBufIn = 400;
                                                  SizeBufOut = SizeBufIn;
                                                  MMFName: pAnsiChar = 'MP';
                                                  bufcount = 3;
                                               
                                                  Freq = 1000;
                                                  Lev = 32000;
                                               
                                              var
                                                fmt: TWaveFormatEx;
                                                WaveIn: HWaveIn;
                                                WaveOut: HWaveOut;
                                                psIn,psOut  : psmallint;
                                               
                                                Mdata: array [0..SizeBufIn-1] of smallint;
                                                tPred: Double;
                                                SN,t: Double;
                                               
                                               
                                              procedure FillBufIn(var bufIn);
                                              var
                                                i   : cardinal;
                                               
                                              begin
                                                psIn := pointer(bufIn);
                                                for i:=0 to SizeBufIn  do
                                                  begin
                                                    //psIn^ := Random(32000)-16000;
                                                    //t := i/discret + tPred;
                                                    //psIn^ := Round(Lev*sin(SN*t));
                                               
                                                    Mdata[i] := psIn^;
                                                    inc(psIn);
                                                  end;
                                                  //tPred := t;
                                              end;
                                               
                                              procedure FillBufOut(var bufOut);
                                              var
                                                i   : cardinal;
                                               
                                              begin
                                                psOut:=pointer(bufOut);
                                                for i:=0 to sizebufout do
                                                  begin
                                                    psOut^ :=  Mdata[i];
                                                    inc(psOut);
                                                  end;
                                              end;
                                               
                                              procedure waveInProc( hwo:HWAVEIN; uMsg, dwinstance, dwParam1, dwParam2:dword); stdcall;
                                                begin
                                                  if uMsg = WIM_DATA then
                                                    begin
                                                      fillbufIn(PWaveHdr(dwParam1)^.lpData);
                                                      WaveInAddBuffer(WaveIn, PWaveHdr(dwParam1),SizeOf(TWaveHdr));
                                                   end;
                                                end;
                                               
                                              procedure waveOutProc( hwo:HWAVEOUT; uMsg, dwinstance, dwParam3, dwParam4:dword); stdcall;
                                                  begin
                                                    if uMsg = WOM_DONE then
                                                      begin
                                                        fillbufOut(PWaveHdr(dwParam3)^.lpData);
                                                        waveoutwrite(waveout,PWaveHdr(dwParam3),sizeof(TwaveHdr));
                                                     end;
                                                  end;
                                               
                                              procedure WCard;
                                              var
                                                pwhdrIn : PWaveHdr;
                                                pwhdrOut : PWaveHdr;
                                                i   : cardinal;
                                               
                                                begin
                                                  tPred := 0;
                                                  SN := 2*PI*Freq;
                                                  with fmt do
                                                  begin
                                                    wFormatTag := WAVE_FORMAT_PCM;
                                                    nChannels := 1;
                                                    nSamplesPerSec := discret;
                                                    wBitsPerSample := 16;
                                                    nBlockAlign := (nChannels * wBitsPerSample) div 8;
                                                    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
                                                    cbSize := 0;
                                                  end;
                                               
                                                  WaveInOpen(Addr(WaveIn), WAVE_MAPPER, @fmt, dword(@waveInProc), 0, CALLBACK_FUNCTION);
                                                  WaveOutOpen(@WaveOut, 0, @fmt, dword(@waveoutproc), 0, CALLBACK_FUNCTION);
                                                  WaveOutPause(WaveOut);
                                               
                                                  for i:=0 to bufcount-1 do
                                                    begin
                                                      pwhdrIn:=allocmem(sizeof(TWaveHdr));
                                                      pwhdrIn.lpData:=allocmem(SizeBufIn * fmt.nBlockAlign);
                                                      pwhdrIn.dwBufferLength:= SizeBufIn * fmt.nBlockAlign;
                                               
                                                      WaveInPrepareHeader(WaveIn, pwhdrIn, sizeof(TWaveHdr));
                                                      WaveInAddBuffer(WaveIn, pwhdrIn, sizeof(TWaveHdr));
                                                      
                                               
                                                      pwhdrOut:=allocmem(sizeof(TWaveHdr));
                                                      pwhdrOut.lpData:=allocmem(SizeBufOut * fmt.nBlockAlign);
                                                      pwhdrOut.dwBufferLength:= SizeBufOut * fmt.nBlockAlign;
                                                      waveOutPrepareHeader(WaveOut, pwhdrOut, SizeOf(TWaveHdr));
                                               
                                                      fillbufOut(pwhdrOut.lpData);
                                                      waveoutwrite(waveout, pwhdrOut, sizeof(TwaveHdr));
                                                    end;
                                                    WaveInStart(WaveIn);
                                                    waveoutrestart(waveout);
                                                end;
                                               
                                              begin
                                              { TODO -oUser -cConsole Main : Insert code here }
                                              WCard;
                                              readln;
                                              end.

                                            К сожалению, объединение кодов в одну программу никаких результатов не дало. И при разных приложениях и при совместном задержка весьма ощутима (порядка 0.2-0.4 сек на слух). Пока не понятно, можно ли с этим как нибудь побороться?
                                            Ну и конечно проблема с тем что на выходе наушников (а теперь ещё и колонок) я слышу основной сигнал микрофона. Никакие данные подсунуть вместо сигнала в буфер звуковой платы у меня не получилось ( возможно слишком мало знаний и опыта). А может нужны другие инструменты, отличные от API (ведь разделяют же сигнал на входе и на выходе такие программы как Skype, X-Lite и т.п.?. (Возможно параллельная звуковая плата и решила бы эту проблему (как уверяли здесь на форуме), но это не мой вариант, мне необходимо чтобы программа запускалась на любом компе).
                                            Правда остался ещё один непроверенный предложенный вариант -ХР, я до него ещё не добрался). Хотя если поразмыслить не заставишь же всех снести систему и установить анахронизм, но проверить есть смысл, для общего развития).
                                            Сообщение отредактировано: Ivan123 -
                                              Цитата
                                              Пока не понятно, можно ли с этим как нибудь побороться?

                                              Можно уменьшить, но задержка будет всегда.
                                              Цитата
                                              Ну и конечно проблема с тем что на выходе наушников (а теперь ещё и колонок) я слышу основной сигнал микрофона

                                              Не слышу.
                                              Цитата
                                              Никакие данные подсунуть вместо сигнала в буфер звуковой платы у меня не получилось

                                              Непонятно, какие данные и куда подсунуть. Вы ведь воспроизведите те данные, что записали. В чем проблема подсунуть...какие-то другие данные?
                                              Цитата
                                              Ну и конечно проблема с тем что на выходе наушников (а теперь ещё и колонок) я слышу основной сигнал микрофона

                                              Прикреплённый файлПрикреплённый файл____________________.jpg (93,75 Кбайт, скачиваний: 177)

                                              Кофигурация вашей системы может отличаться, но принцип тот же. Если слышите сквозняк - отключайте линии в "устройствах воспроизведения". Возможно, какая-то из них.

                                              Единственный вариант аппаратного сквозняка, который мне удалось получить - с внешнего микрофона. Встроенный микрофон 7-ка глушит (по воспроизведению), а line-in в "устройствах воспроизведения" вообще не отображается 7-кой, только "устройствах записи". В общем, сквозняки с линейного входа и встроенного микрофона не работают. Запись с линейного входа возможна только при выборе в "устройствах записи" "cтерео микшер". :facepalm:

                                              Вот как так можно было в 7-ке умудриться испоганить вполне логичную структуру звуковых устройств - не понимаю.
                                              В XP структура выглядела вполне понятно и логично. В системе был один или несколько микшеров. Каждый микшер имеет одну или несколько выходных линий. Каждая выходная линия подключается либо к АЦП(линия записи), либо к ЦАП(линия воспроизведения). С каждой выходной линией ассоциированы несколько входных: микрофон, линейный, и пр. К каждой линии прикручены контролы: фейдеры, переключатели. и пр.
                                              Функции микшера и записи/воспроизведения разнесены по независимым интерфейсам. Котлеты отдельно, мухи отдельно.

                                              В 7-ке... >:(
                                              Человек человека понять не может.
                                                Цитата Prince @
                                                В 7-ке

                                                Когда была 7 эти вопросы меня не волновали
                                                А сейчас у меня 10, и к сожалению ничего менять нельзя, корпоративный продукт.
                                                Сообщение отредактировано: Ivan123 -
                                                  Цитата Prince @
                                                  Прикреплённый файл

                                                  А вот за это отдельное спасибо, вроде уже пробовал эти рычаги, но их там 14 шт, возможно самый последний и не проверил, так что в одном из вариантов сквозняка нет. Хотя это и не самый лучший вариант (требует отдельного микрофона и отдельных наушников). Вот мои свойства (если интересно)
                                                  Прикреплённый файлПрикреплённый файл__________________________________.png (183,48 Кбайт, скачиваний: 167)

                                                  Добавлено
                                                  Цитата Prince @
                                                  Можно уменьшить, но задержка будет всегда

                                                  А откуда она берётся, вроде сквозняк без задержки проходит (неужели подготовка и считывание буферов приводит к такому крайне нехорошему эффекту, или многопотоковость самой системы?)
                                                  Интересно а в Skype, X-Lite она тоже есть? может потому что мы не слышим сквозняка, эффекта задержки никак не ощущается?
                                                  В оном из ваших ранних обсуждений на эту тему было такое предложение - установить наивысший приоритет для приложения, интересно работает этот метод? и если работает, то как это реализовать? Особенно это актуально для блока обработки. То летит, то замедляется настолько что становятся заметными девиации сигнала.
                                                  Сообщение отредактировано: Ivan123 -
                                                    Цитата
                                                    А откуда она берётся, вроде сквозняк без задержки проходит (неужели подготовка и считывание буферов приводит к такому крайне нехорошему эффекту, или многопотоковость самой системы?)

                                                    Частота дискретизации 8000 Гц. Длина буфера 400 отсчётов. Буфер заполняется в течении 400/8000 = 0.05 с. Это задержка накопления данных. Как минимум, оцифрованный сигнал будет задержан на эту величину. Точнее, приложение получит уведомление о том, что буфер заполнен, не раньше, чем через 0.05с. Дальше получение приложением wim_data, его обработка, постановка буфера в очередь воспроизведения. Всё это тоже займёт какое-то время.
                                                    Сквозняк - это коммутация аналоговых сигналов железом. Там задержке просто неоткуда взяться.
                                                    Процесс перевода аналогового сигнала в цифру и обратно без задержки, под виндой, да и не только, неосуществим. Можно достичь неких минимальных значений задержки, наверное. Ну и всё.
                                                    Для большинства приложений задержка вообще несущественна.

                                                    Цитата
                                                    Интересно а в Skype, X-Lite она тоже есть?

                                                    Да.
                                                    Цитата
                                                    может потому что мы не слышим сквозняка, эффекта задержки никак не ощущается?

                                                    Да.
                                                    Задержку легко услышать. Достаточно связаться по вайберу с абонентом в том же помещении и попросить его включить громкую связь.
                                                    У меня получилась задержка примерно в 1 секунду. Эта задержка формируется из нескольких составляющих. Сюда включается и время доставки пакетов и много чего, помимо накопления данных на передающей стороне. Аналогично можно и скайп проверить и пр.
                                                    Задержка хорошо ощущается в диалогах.

                                                    Цитата
                                                    В оном из ваших ранних обсуждений на эту тему было такое предложение - установить наивысший приоритет

                                                    Не имеет смысла. Задержка будет всегда.

                                                    Задержку в один или несколько семплов может давать проф. звуковое оборудование, где обработка сигнальным процессором идёт семпл в семпл.
                                                    А на ПК без задержки никак.

                                                    Цитата
                                                    Особенно это актуально для блока обработки. То летит, то замедляется настолько что становятся заметными девиации сигнала.

                                                    Непонятно.
                                                    Сообщение отредактировано: Prince -
                                                    Человек человека понять не может.
                                                      Цитата
                                                      Хотя это и не самый лучший вариант (требует отдельного микрофона и отдельных наушников)

                                                      Этого тоже не понял. Почему так? Отключение "микрофона" или чего другого в свойствах "динамиков", не должно бы влиять на уровень записи с той же линии. Эти настройки [должны быть] независимы. Попробуйте не выставлять уровень в ноль, а отключать линии, щелкая по кнопкам с изображением динамика. По хорошему, кнопка с динамиком [должна быть] эквивалентна контролу mute. Т.е., сигнал на выход не пройдет, но запись с линии возможна.
                                                      Сообщение отредактировано: Prince -
                                                      Человек человека понять не может.
                                                        Цитата Prince @
                                                        Достаточно связаться по вайберу с абонентом в том же помещении и попросить его включить громкую связь.

                                                        Ну да) плохо быть бестолковым (это я про себя), как до этого я не додумался, а может просто мне хотелось услышать да или нет)
                                                        Цитата Prince @
                                                        Непонятно.

                                                        Есть цикл обработки (приводить не буду он достаточно большой, ну и по другим причинам (пока ещё рано)), в цикле нет циклов преждевременного выхода, или любых других циклов с выходом по условию, каждый цикл проходит всю цепочку шагов во всех вложенных циклах. Все данные байт. После основного цикла счётчик времени. Так вот значения плавают иногда в 2-3 раза, а вначале запуска приложения и того больше. Идёт сквозной поток и непрерывный пересчёт выходного результата, а в это время система не дремлет.... И это у меня пока самая большая проблема.
                                                        Цитата Prince @
                                                        не должно бы влиять на уровень записи с той же линии

                                                        Возможно вы меня не совсем правильно поняли. Уровень записи и не меняется. На картинке может плохо видно. То про что вы говорите это отдельный динамик левое окно в самом низу (там есть что регулировать, у меня аж 14 регуляторов, и нужный самый последний в ряду (правое окно внизу)).
                                                        Я же имел наушники с микрофоном (самый верхний динамик левое окно), и регулировка у нег только одна (правое окно вверху), которая регулирует (или отключает) только сигнал прошедший через код, но не сквозняк.
                                                        Сообщение отредактировано: Ivan123 -
                                                          Цитата
                                                          Есть цикл обработки (приводить не буду он достаточно большой, ну и по другим причинам (пока ещё рано)), в цикле нет циклов преждевременного выхода, или любых других циклов с выходом по условию, каждый цикл проходит всю цепочку шагов во всех вложенных циклах. Все данные байт. После основного цикла счётчик времени. Так вот значения плавают иногда в 2-3 раза, а вначале запуска приложения и того больше. Идёт сквозной поток и непрерывный пересчёт выходного результата, а в это время система не дремлет.... И это у меня пока самая большая проблема.

                                                          Это нормально. Винда мнопопоточная. А как иначе. Значения времени вы не написали, поэтому не могу судить, насколько это много или мало.
                                                          Главное, чтобы время обработки не превышало длительности буфера.
                                                          Человек человека понять не может.
                                                            Цитата Prince @
                                                            Главное, чтобы время обработки не превышало длительности буфера.

                                                            Обработка может быть неимоверно большая (современный уровень развития микроэлектроники), поэтому этот уровень заставляет уменьшать её до размеров буфера. И это вторая проблема, хотя главная пока всё таки время :wall: Не знаю можно ли в какой нибудь винде отключить все фоновые процессы и приоритет оставить только на выполнении приложения Delphi? но это наверно тема для другого раздела форума.
                                                            Огромное спасибо за наставление на путь истинный, без вашей помощи по MMSystem я бы ещё долго пытался понять принципы захвата звука и его вывода, ну и тем более написать приличный (правильный) кусок кода. Возможно то что у меня в итоге получилось с точки зрения профессионального программирования и не совсем правильно или совсем не правильно, но тот результат в виде звуков, а самое главное цифр на выходе меня пока устраивает. Настало время соединить его с куском обработки. Это конечно займёт какой то кусок времени и со временем возникнут ещё вопросы. Ну а сейчас эту тему похоже надо закрывать. (Хотя один раз я это уже делал :D )
                                                            Сообщение отредактировано: Ivan123 -
                                                              Последние сообщения особо не читал, но по поводу караоке как вариант: https://medium.com/p/35c22bbc3cda/
                                                              Можно вообще VST-плагин написать и вставить в эту цепь. Либо использовать то, что уже есть.
                                                              А можно вообще через DAW прогонять и в реальном времени применять эффекты и выводить наружу.
                                                              vpmultishiftqb vscatterpf0dps vfmsubadd132pd vgatherpf1dps vpclmulhqlqdq vcmptrue_ussd vaeskeygenassist
                                                                Цитата

                                                                Обработка может быть неимоверно большая (современный уровень развития микроэлектроники),

                                                                На уровне современной микроэлектроники. За 0.05-0.1с процессор может много чего посчитать.Что ж за неимоверная обработка такая.
                                                                Человек человека понять не может.
                                                                  Цитата Prince @
                                                                  За 0.05-0.1с процессор может много чего посчитать

                                                                  не так уж и много)
                                                                  во первых ни одна выборка не должна пропасть даром, т.е. время одной обработки не должна превышать время одной выборки (это вы и сами знаете и про это писали)
                                                                  во вторых для получения наилучшего результата эту выборку приходится мельчить (я думаю вы это и сами понимаете, качественный продукт должен всегда к этому стремиться и это если есть какой то приличный результат на выходе)
                                                                  в третьих задачи, которые выполняют современные эвм работающие с огромными базами данных с одной стороны неимоверно гигантские по сравнению с возможностями человека, и в тоже время ничтожно малые по сравнению с возможностями человека :yes-sad: :no-sad:
                                                                  Сообщение отредактировано: Ivan123 -
                                                                    Цитата Prince @
                                                                    0.05-0.1с

                                                                    Хотелось бы ещё узнать информацию по такому очень важному вопросу. Есть ли на краях сэмпла как вы его называете искажения сигнала.
                                                                    Сообщение отредактировано: Ivan123 -
                                                                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                    0 пользователей:


                                                                    Рейтинг@Mail.ru
                                                                    [ Script Execution time: 0,2887 ]   [ 23 queries used ]   [ Generated: 22.10.19, 18:57 GMT ]