На главную Наши проекты:
Журнал   ·   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
  
> Проблемы двойной буфферизации и WaveOut , Заикание звука
    Доброго всем времени суток!
    Пишу прогу для работы с файлами ImRe формата, на начальном этапе столкнулся с тем что при подаче данных в WaveOutWrite звук "заикается" несмотря на использование двойной буфферизации. Делаю следующе:

    1. Получаю контекст на устройство так:

    ExpandedWrap disabled
      procedure Tform1.Initsound(band:integer);
      var i:integer;
       
      begin
       
      // вормат выводных данных
       with header do begin
         wFormatTag := WAVE_FORMAT_PCM;
         nChannels := 1;
         nSamplesPerSec := band*2;
         wBitsPerSample := 16;
         nBlockAlign := nChannels * (wBitsPerSample div 8);
         nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
         cbSize := 0;
       end;
       
      // получаю контекст устройства и говорю ему передавать мессагу окну
       if WaveOutOpen(addr(waveOut), WAVE_MAPPER, @header,Handle, 0,CALLBACK_Window)<>0 then
        begin
        showmessage('Ошибка открытия звукового устройства!');
        exit;
        end;
       
       
      // создаю два буфера под вывод
      for i:=0 to 1 do begin
      pWaveBufHan[i] := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, soundbuflength*4);
      pWaveBuffer[i]:=GlobalLock(pWaveBufHan[i]);
       
        with waveoutHdr[i] do begin
         lpData := pwavebuffer[i];
         dwBufferLength :=soundbuflength*2;
          dwUser := 0;
          dwFlags := 0;
         dwLoops := 0;
       end;
       
      // инициирую заголовки
      if WaveOutPrepareHeader(waveOut, @waveoutHdr[i], sizeof(waveoutHdr))<>0 then
         begin
        showmessage('Ошибка инициализации звукового устройства!');
        exit;
        end;
      nspwbZero(pwaveBuffer[i],soundbuflength); // обнуляю буферы
      end;
       
      Psigtosound:=nspvMalloc(soundbuflength); // буфер понадобится для считывания квадратур из файла
       
       
      //закидываю первый буфер в устройство чтобы начать получать MM_WOM_DONE
      waveOutWrite(waveOut, @waveoutHdr[1], sizeof(waveoutHdr));
       bufnum:=0;
      end;


    2. Обрабатываю мессаги MM_WOM_DONE через procedure SoundComplite (var MSG:Tmessage); message mm_wom_done;
    (пробовал hook-ом; - проблемы те же, да в принципе и смысл тот же)

    ExpandedWrap disabled
      // при получении MM_WOM_DONE
      procedure  Tform1.SoundComplite(var msg: Tmessage);
      var i:integer;
      begin
       //скидываю ранее сгенерированный буфер в устройство
       waveOutWrite(waveOut, @waveoutHdr[bufnum], sizeof(waveoutHdr));
       //переключаю счетчик на следующий буфер
       bufnum:=bufnum xor 1;
       
      // закидываю данные в новый буфер
      globalpos:=globalpos+soundbuflength  ; //позиция отсчета в файле
      if globalpos>= globalmax then // нет ли конца файла
        begin
        globalpos:=globalmax;
        play:=false;     // торможу воспроизведение
        form1.closesound;// и закрываю устройство
        end;
      afile.Position:=globalpos*4+110; // введено для расчета нужной позиции в filestream 109- величина заголовка 4-размер квадратурного отсчета Re:short Im:short
       
      Afile.Read(Psigtosound^,soundbuflength*4);
       
      // пока что делаю только выделение действительной части и пытаюсь ее воспроизводить
      // далее планируется здесь сделать небольшуюу обработку
      nspvbReal(Psigtosound,pwaveBuffer[bufnum],soundbuflength) ;
      // pwaveBuffer[bufnum] - готов при следующем вызове MM_WOM_DONE он будет подан в устройство
      end;

    Сама проблема заключается в том что звучание кратковременно прерываетя при смене буфера хотя двойная буферизация должна это устранять.

    Что характерно усли убрать строчку
    bufnum:=bufnum xor 1;
    то есть работать только с одним буфером - "заикание" (на слух) точно такое же как и с двумя буферами.

    раньше писал похожие вещи (делал нечто похожее на сетевое радио) - доооооолго мучался и сам не понял как но такие же заикания устранил, а вот как - хоть убей не помню :wall:

    Заранее благодарен если у кого найдется время посмотреть.

    Добавлено
    Небольшое уточнение - использовал буфферы разного размера от 1 до 24 Кб заикания такие же только происходят реже с увеличением размера буфера
      Очевидных ошибок невидно. Разве что в nspvbReal происходит разрыв фазы.
      Я бы число буферов увеличил бы. Запас на всякий пожарный случай.
        имхо, вы где-то накосячили с размерами массивов.
        Выделяете в памяти soundbuflength*4 байт
        ExpandedWrap disabled
          pWaveBufHan[i] := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, soundbuflength*4);

        а драйверу отдаёте половину
        ExpandedWrap disabled
            with waveoutHdr[i] do begin
             lpData := pwavebuffer[i];
             dwBufferLength :=soundbuflength*2;

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

        ExpandedWrap disabled
          procedure  Tform1.SoundComplite(var msg: Tmessage);
          var i:integer;
          begin
           //скидываю ранее сгенерированный буфер в устройство
           waveOutWrite(waveOut, @waveoutHdr[bufnum], sizeof(waveoutHdr));
           //переключаю счетчик на следующий буфер
           bufnum:=bufnum xor 1;

        Это ошибочный подход.
        MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.
        Если вы ставите в очередь несколько буферов(2/3/40/100), неважно, на каждый MM_WOM_DONE вы получаете от драйвера один заголовок буфера, костылей в виде левых индексов не требуется.
        Цитата
        от 1 до 24 Кб

        Я бы рекомендовал "безопасную" (с точки зрения возможных затыков воспроизведения при недостаточной производительности системы) длительность буфера в 100 и более мс.
        Цитата
        CALLBACK_Window

        Нехорошо для практической реализации, но для примера некритично.
        Сообщение отредактировано: Prince -
          Цитата Prince @
          Это ошибочный подход.
          MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.

          А почему МС так не делает?
          http://msdn.microsoft.com/ru-ru/library/wi...3(v=vs.85).aspx
          Правда разработчики VCL с вами согласны.

          Цитата Prince @
          Я бы рекомендовал "безопасную" (с точки зрения возможных затыков воспроизведения при недостаточной производительности системы) длительность буфера в 100 и более мс.

          А откуда число 100 мс? Откуда дровишки? Хотелось бы 20 мс.
          Сообщение отредактировано: Pavia -
            Цитата
            А почему МС так не делает?

            Потому что то пример, потому что кто-то в МС был не в курсе, что в ихних месседжах передаётся? :-?
            Цитата
            А откуда число 100 мс? Откуда дровишки? Хотелось бы 20 мс.

            По опыту, на процах от 500 МГц до современных. Хотелось бы, но когда оно реально надо, т.е., такой мелкий буфер.
            Сообщение отредактировано: Prince -
              Цитата Prince @
              Хотелось бы, когда оно реально надо, т.е., такой мелкий буфер.

              RFC хочет 20 мс. Вот и спросил реально на waveOutWrite получить? Или куда копать?
                :scratch:
                На новых машинах и новых осях нужно проверять.
                На XP, со скрипом, абстрактный 20 мс буфер получить можно.
                Цитата
                Или куда копать?

                А суть задачи?

                По большей части интуитивно, всё, что требует реакции на интервалах, сравнимых с квантом времени, отведенным процессу, или меньше, считаю не надёжным(на уровне приложения), независимо от интерфейса. Драйвер вполне успевает на уровне ядра переключать 2 буфера без разрывов аудиопотока, но приложение ещё должно и успевать их заполнять(обрабатывать) своевременно.
                Сообщение отредактировано: Prince -
                  Цитата Pavia @
                  Я бы число буферов увеличил бы. Запас на всякий пожарный случай.

                  Пробовал - ситуация та же и как писал даже если оставить только один буфер ничего не меняется :-?

                  Добавлено
                  Цитата Prince @
                  имхо, вы где-то накосячили с размерами массивов.

                  Да есть такое должно быть не *4 а *2, но больше - не меньше проблема точно не в этом.

                  Добавлено
                  Цитата Prince @
                  Также и при заполнении буфера, размеры буфера и границы фактических данных в нём могут не совпадать, проверяйте.

                  Это ошибочный подход.
                  MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.

                  А в каком параметре мессаги или шапки должно храниться количество воспроизведенных реально сэмплов - помню с микрофоном такая же беда была что устройство не успевало заполнить весь буфер и скидывало то что есть.

                  Добавлено
                  Цитата Prince @
                  Нехорошо для практической реализации, но для примера некритично

                  Использование программы планируется на высокопроизводительных системах поэтому очень большие буферы делать смысла нет, чтобы не создавать пользователю ощущуние "задумчивости" компа.

                  А какой подход будет правильнее в практике вместо callback-window. Поток писать не хочется синхронизировать не удобно убудет с учётом обработки данных

                  Добавлено
                  Цитата Prince @
                  Драйвер вполне успевает на уровне ядра переключать 2 буфера без разрывов аудиопотока, но приложение ещё должно и успевать их заполнять(обрабатывать) своевременно.

                  На примере использовал частоду вывода 8000 Гц и буфер 4К то есть у приложения почти пол-секунды на заполнение буфера, при отсутствии сложных вычислений (а их там нет) даже слабенькая система должна успевать.

                  Я так понимаю копать действительно нужно в направлении размера фактически воспроизведенных данных на момент mm_wom_done, вот только где их взять?
                    Цитата
                    проблема точно не в этом

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

                    В направлении поиска ошибок в программе. :yes:
                    Цитата
                    А в каком параметре мессаги или шапки должно храниться количество воспроизведенных реально сэмплов - помню с микрофоном такая же беда была что устройство не успевало заполнить весь буфер и скидывало то что есть.

                    Реально воспроизведенных семплов - такого понятия, в контексте обработки mm_wom_done, нет. Сколько БАЙТ вы указали в dwBufferLength(это количество должно быть кратным размеру nBlockAlign структуры waveformatex), столько буфер и отыграет, до того, как драйвер его вернёт приложению. Меньше (байт) он может воспроизвести в том случае, если вы выполнили Reset устройства воспроизведения, в этом случае все буферы, в том числе и "незавершенные", выбрасываются из очереди. Но такое действие означает, что вам, в общем, уже неважно, кто там сколько воспроизвёл, вы хотите прекратить воспроизведение.
                    При записи, да, есть возможность узнать, сколько реально было байт записано, но практически, эта возможность востребована, когда буфер выбрасывается из очереди "нештатно". В случае нормального последовательного обмена буферов, сколько вы запросили, столько и получаете на выходе.
                    Цитата
                    Использование программы планируется на высокопроизводительных системах поэтому очень большие буферы делать смысла нет, чтобы не создавать пользователю ощущуние "задумчивости" компа.

                    Пользователь ничего не ощутит. Нажал кнопку - началось воспроизведение, ещё раз - прекратилось. Буфер может быть хоть 1 секунда. Или я вас не понял.
                    Цитата
                    Поток писать не хочется синхронизировать не удобно убудет с учётом обработки данных

                    Поток или подпрограмма (она самой виндой выполняется в контексте отдельного потока, если не ошбаюсь). Неудобно синхронизировать -> менять структуру программы и способы обработки данных, чтобы было удобно.
                    Если вы гонитесь за минимальными задержками, то зачем к издержкам на обработку аудио добавлять издержки и задержки GUI и пр. GUI в какой-то момент может подвесить всю вашу обработку аудио, и разрывы в звуке будут следствием активных действий пользователя мышой или тяжёлого интерфейса, требующего постоянной прорисовки.
                    Сообщение отредактировано: Prince -
                      Цитата Prince @
                      Запишите звук,который выдаёт программа, и в звукоредакторе увидите, в каком месте разрывы, а потом и ищите, в чём причина(где ошибка). Но она точно не в "двойной буферизации".

                      Именно так сейчас и делаю - прямо с головы выдернули :)

                      Добавлено
                      Цитата Prince @
                      Нажал кнопку - началось воспроизведение, ещё раз - прекратилось. Буфер может быть хоть 1 секунда.

                      Не совсем так- перед воспроизведением исходные отсчеты звука будут обрабатываться в зависимости от параметров указанных пользователем. В случае буфера 1с реакция в динамиках на изменение параметров (в худшем случае) тоже будет около секунды- для пользователя это будет казаться лагами.

                      Спасибо за помощь. Как будет результат отпишу что за ошибка была
                        Цитата
                        В случае буфера 1с реакция в динамиках на изменение параметров (в худшем случае) тоже будет около секунды- для пользователя это будет казаться лагами.

                        А, в этом смысле. Но вы попробуйте 100 мс /50 мс. Мне кажется, для пользователя достаточно комфортно. :-?
                          Цитата Pavia @
                          Цитата Prince @
                          Хотелось бы, когда оно реально надо, т.е., такой мелкий буфер.

                          RFC хочет 20 мс. Вот и спросил реально на waveOutWrite получить? Или куда копать?

                          А всё-таки, что за задача? Спрашиваю для "поразмышлять над поисками возможных решений". Возможно, waveOutWrite вполне справится, если поэкспериментировать.
                          Сообщение отредактировано: Prince -
                            Prince
                            Задача. Демонстрация принципов работы со звуком в потоковом режиме. На примере голосового чата.
                            Способ синхронизации на основе TDM(считай жёсткое реальное время). Группа протоколов связи стандартная сцепка SIP+RTP. Сжатие стандартное, насколько понял rfc рекомендует делать запись фреймами по 20 мс.
                              Рекомендованые 20 мс - относительно безопасный размер фрейма, на случай пропадания пакета в системе передачи. Если фрейм потеряется, разборчивость речи не особо пострадает. Под этот размер фрейма заточены и кодеки, если не ошибаюсь.
                              Задержка в канале будет определяться временем накопления фрейма(20мс) на передающей стороне, задержкой кодирования/декодирования фрейма, задержкой передачи пакетов по сети(от пары мс до упора), размером приёмного буфера(джиттер-буфера) на приёмной стороне.
                              Регламентирована ли эта суммарная задержка, и в каких рекомендациях - не в курсе.

                              Вполне допустимо на передающей стороне использовать, допустим, 10 буферов по 20 мс. Неразрывность потока аудио на передающей стороне будет обеспечиваться количеством буферов. На приёмной тоже, но там процесс будет сложнее, за счёт неравномерной задержки при передаче пакетов по сети, нужно думать. А, и частоты дискретизации на передающей/приёмной стороне не совпадают, придётся как-то и с этим разбираться, при разработке общего механизма синхронизации.
                              В общем, wavein/waveout с 20 мс буферами можно бы попробовать, я бы сказал. :scratch:
                              Сообщение отредактировано: Prince -
                                Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

                                Цитата Prince @
                                Поток или подпрограмма (она самой виндой выполняется в контексте отдельного потока, если не ошбаюсь). Неудобно синхронизировать -> менять структуру программы и способы обработки данных, чтобы было удобно.



                                По поводу в потока - все верно Callback_window дает свою задержку связанную с очередью мессаг в винде, поэтому только поток если звук не короткий (больше допустимого размера буфера).
                                Переделал программу для синхрогизации с отдельным потоком и все заработало как часы. Пример брал тут

                                Добавлено
                                Всем спасибо за помощь - проблема снята.
                                Сообщение отредактировано: junkers1989 -
                                  Цитата
                                  Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

                                  Если буферов больше, чем 2(и они относительно "мелкие"), поток при получении управления, обрабатывает(может обработать) несколько мессаджей(буферов), которые пришли от звуковой подсистемы, пока поток "спал". Притом, что драйвер продолжает вовремя переключать относительно мелкие буферы. А вот если буферов всего 2, тогда будут разрывы, так как один буфер, допустим, обрабатывается приложением, а у драйвера в момент заполнения второго буфера не оказывается "резерва" для дальнейшей работы.

                                  Можно провести такой ээксперимент.
                                  Со входа звуковушки записываем в файл чистый синус. Запись при помощи wavein функций.
                                  Т.е., ставим сразу в очередь 100 буферов по 50 мс, дожидаемся, пока все они заполнятся и сбрасываем в файл. В редакторе смотрим, есть ли разрывы/глюки в записанном синусе. Уменьшаем длительность буферов и повторяем. И так до тех пор, пока не появятся разрывы. Одновременно смотрим на загрузку процессора, для наглядности.
                                  Сообщение отредактировано: Prince -
                                    Действительной если нагрузка в системе очень неравномерна, то смысл есть, а для "чистой" работы программы (когда другие процессы не грузят систему) - она должна справляться с двумя буферами, если нет то надо оптимизировать код.
                                      Цитата
                                      Действительной если нагрузка в системе очень неравномерна,

                                      Тут дело не столько в неравномерности, сколько в нереалтаймовости винды. Пока поток "спит", он ничего не может поделать с мессагами, приходящими в это время. Зато когда просыпается, может успеть за отведённое время разгрести "кучу бумаг", накопившихся, пока он спал.

                                      Добавлено
                                      Цитата
                                      она должна справляться с двумя буферами

                                      При достаточном размере буфера да. Почему я и рекомендовал 100 мс. Но потоковая модель работы с буферами не подразумевает в обязательном порядке, что в очереди "зациклены" n буферов одинакового размера. После обработки буфера он может уничтожаться, а в очередь по мере поступления могут добавляться новые(с выделением памяти, prepare), например прилетевшие по сети, причём вы заранее, допустим, не знаете, какой у них размер.
                                      Сообщение отредактировано: Prince -
                                        Согласен случаи могут быть разными, отсюда и подходы разные.
                                          ...для статистики, в свей программе использую 20мс буферы (8кHz 16bit stereo) в количестве 50шт (1сек) - waveOut/In отрабатывают замечательно.
                                            Цитата junkers1989 @
                                            Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

                                            Прошу прощения за ярый оффтоп, но у меня примерно такая-же ситуация. С той лишь разницей, что "щелчок" возникает при остановке воспроизведения аудио. Методом тыка удалось убрать эту проблему, но костыльным образом -- выводить программно звук в ноль, а потом уже останавливать. Мне стоит создать отдельную тему или эту ситуацию можно одним словом описать\решить?

                                            Добавлено
                                            Хотя наверное это уже некрофилизм темы, надеюсь простите :rolleyes:
                                              Цитата
                                              Прошу прощения за ярый оффтоп, но у меня примерно такая-же ситуация. С той лишь разницей, что "щелчок" возникает при остановке воспроизведения аудио. Методом тыка удалось убрать эту проблему, но костыльным образом -- выводить программно звук в ноль, а потом уже останавливать. Мне стоит создать отдельную тему или эту ситуацию можно одним словом описать\решить?

                                              Такая же? А какая была у ТС?
                                              Имхо, стоит создать новую тему.
                                              Чтобы ответить, нужны подробности, с частью кода и пояснениями.
                                              А "щёлчок" можно записать и приаттачить. Может, никакого щелчка и нет.
                                                Хоть на словах, как происходит "остановка воспроизведения"?
                                                  Меня интересуют общие причины из-за чего такое может случаться. А дальше я сам додумаюсь как поправить. Именно по этому я решил не создавать новую тему. Одна из причин - неполный буфер, но не в моём случае.


                                                  Цитата Prince @
                                                  Хоть на словах, как происходит "остановка воспроизведения"?

                                                  Общими словами сложно описать. Закрывается файловый стрим и очищается буфер.
                                                  Отсюда: https://github.com/jphp-compiler/jphp-audio...oTrack.java#L74
                                                  Идёт сюда: https://github.com/jphp-compiler/jphp-audio...Player.java#L76
                                                  Затем сюда: https://github.com/jphp-compiler/jphp-audio...Output.java#L70

                                                  Но я не думаю что это вообще хоть как-то поможет, скорее наоборот.

                                                  Добавлено
                                                  Цитата Serafim @
                                                  Одна из причин - неполный буфер, но не в моём случае.

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

                                                    Сначала хотя бы понять, что такое "такое". Можете приаттачить щелчок(а лучше 2 щелчка, 3 щелчка... :) ), желательно вместе с "до щелчка" и "после щелчка".
                                                    Цитата
                                                    Но я не думаю что это вообще хоть как-то поможет, скорее наоборот.

                                                    Пожалуй. Ни разу не waveout.
                                                    Сообщение отредактировано: Prince -
                                                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                    0 пользователей:


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