Вопрос эмуляторописателям (или просто программистам)

Всё остальное

Postby Romanich » 06 Apr 2009, 08:01

есть основной цикл программы, подготавливающий буфер аудио-данных:

Code: Select all
while(1)
{
  MakeSoundBuffer(Buf);
  ...
}


есть обработчик прерывания звукового устройства (например аудиокодек. Прерывание по высокому уровню ножки DREQ - когда данные нужно зохавоть):

Code: Select all
EXEPTION Handler()
{
  for(i=0;i<32;i++) SPISend(Buf[i]);
  EOI();
}


тоесть Buf - буфер из 32 байт, по прерыванию засылаем 32 байта в mp3 декодер (по даташиту именно столько можно слать без опроса DREQ)

проблема в том что функция MakeSoundBuffer или остальная часть в программе в главном цикле - очень медленна - буфер не успевает построиться полностью, когда mp3-кодер его уже требует.

в итоге вместо непрерываного звука слышен хрип.

что делать?

как следует организовать программу по заполнению буфера/чтению из него?

32 байта это ведь мало?
[url]Прославился тем, что на аватаре[/url]
Romanich
долбоёб-гумасек
 
Posts: 1084
Joined: 22 Nov 2007, 15:12
Group: Registered users

Postby jdigreze » 06 Apr 2009, 09:55

Делай два буфера, с переключением между ними.
Т.е. когда заполнем буфер1, то в мп3 кидаем из буфера2, как только буфер1 заполнен, и данные из буфера2 перекинуты в мп3, делаем переглюк местами, начинаем заполнять буфер2, а из буфера1 по требованию мп3 шлем в мп3, потом опять проверка условия и возврат в начальное состояние.
Сколько меня не корми, волк всё равно в лес смотреть будет
jdigreze
 
Posts: 1478
Joined: 01 Aug 2008, 06:49
Location: Агбан
Group: Registered users

Postby lvd » 06 Apr 2009, 17:50

1. сделать буфер нормальной длины. Например, 512 байт, или 4 килобайта, или сколько не жалко.
2. научиться параллельно и независимо его заполнять и считывать. Почти то же самое (как уже jdigreze сказал) - сделать 2 буфера, пока один заполняется, другой считывается. Заполненный остаётся заполненным пока до него не доберётся считыватель, как это случится - начинает заполняться другой.
3. ОЧЕНЬ крепко подумать насчёт ДРЕК. В доках на вски говорится, что оно может дёргаться во время пересылки байтов. Не вызывает ли это лишние прерывания? Да и вообще, дёргаться каждые 32 байта в прерывание - ахтунг. Проц зафлуживается. Может, исхитриться и слать бОльшими кусками (глубина того буфера в ВСках около 4 кило, вродеб).
F̞͖̭̿̔ͯu̐̅cͬ̑ͩk̨̤̳͇̮̭̪̠̽̿̓̆ͭͩ ̷̩̰͎̩͓̘̾̀ͬ̊ͭ͛ͅda̝̺͙̬͎̝̾͟ ̰̜̝̯͉̯̖̓̎́ͨ̽ͫ͟f̟͇̭̀ͬͨͭ̐̚u̹̼̹̗̞͑̔͂͐̚cͭ̅̊̆̒̆ǩ̝̩̯́ͥ̔̍̑ḭ͓͍̳̬ͦ̽͂n͍͎͈̈̅ͩͬ ̊ͫ̂̾̑̈́f̲͚͉͓͗̋́ͧͦ̅ȗ͇̲̻͈̲̅̎͗͒ͭ͡c̬̟̠̹̯̈́ͩ͘ͅk̫̠̻̋͜a̲͒̾̇!͙͕̺͉̗̩̲̂̏̄̀
User avatar
lvd
 
Posts: 7262
Joined: 07 Apr 2007, 21:28
Group: Registered users

Postby deathsoft » 06 Apr 2009, 20:09

Сделать 1 кольцевой буфер (программный, либо аппаратный, если ДСП), заполнять буфер данными из основного цикла (большой порцией данных), читать из буфера по прерыванию. ОБЯЗАТЕЛЬНО сделать синхронизацию доступа к буферу между читателем и писателем. Принцип примерно тот же самый что изложено в предыдущих 2х постах, но буфер всего 1.
User avatar
deathsoft
 
Posts: 4744
Joined: 07 Apr 2007, 00:58
Group: Registered users

Postby Romanich » 07 Apr 2009, 01:50

lvd wrote:ОЧЕНЬ крепко подумать насчёт ДРЕК. В доках на вски говорится, что оно может дёргаться во время пересылки байтов. Не вызывает ли это лишние прерывания? Да и вообще, дёргаться каждые 32 байта в прерывание - ахтунг. Проц зафлуживается. Может, исхитриться и слать бОльшими кусками (глубина того буфера в ВСках около 4 кило, вродеб).


согласно тем же докам - мы можем пропихать без опроса DREQ не более 32 байта. после этого DREQ=0, до тех пор пока часть не отиграет. Затем када снова DREQ=1 - кладем опять 32 байта... итд.

Даже если допустить дергание DREQ во время пересылки данных по SPI, ничего левого не будет - так как мы в высокоприоритетном обработчике прерывания с запрещенными вложенностями. Прерывание по высокому уровню.

Я так делал - и оно работает в эмуле GBC,NES с офигенной скоростью - тормозов нет. Ибо когда играют 32 байта - предостаточно времени на эмуляцию всего остального.

У мну 8 бит моно 22050Гц. 32 байта буфер , итого 22050/32=690 Гц - воспринимаем прерывание DREQ как прерывание таймера 690 Гц.

Буфер апдейтился прямо в обработчике прерывания.
----
Сейчас же я переделал по-другому:

Code: Select all
#define RATE 22050
#define SND_BUFSIZE 512

Code: Select all
  EMU:
    dos_update_input(); //Апдейтим кнопки
    system_frame();     //Фрейм эмуляции
    dos_update_video(); //Отрисовка на экран
    goto EMU;


Code: Select all
int system_frame(void)
{
   //Эмулируем...
   ...
   ...
   ...
   audio_update(); //Обновляем буфер когда сэмулировался фрейм(весь кадр)
}


Code: Select all
extern volatile u32 Ready; //Признак готовности свежих данных
extern volatile u32 Part; //Смещение которое устанавливает половинки буфера
extern volatile u8 SOUND_BUFFER[SND_BUFSIZE<<1]; //Аудиобуфер из 2-х половинок

void audio_update(void) //Обновляем данные
{
   Ready=0; //Данные ещё не готовы
   for(i=0;i<SND_BUFSIZE;i++) SOUND_BUFFER[Part+i]=f(i); //Строим волну в одной половинке буфера
   Ready=1; //Данные готовы
}


//Макрос посылки 32 байт в VS1003
#define Send32 \
{ \
register u32 i0=(u32)SOUND_BUFFER+(Part^SND_BUFSIZE)+(Kusok<<5); \
register u32 i1=i0+32; \
for(;i0<i1;i0++) SPI(*(u8*)i0); \
}

EX_INTERRUPT_HANDLER(FlagA_ISR) //Обрабоччег прерывания по DREQ
{
*pFIO_FLAG_C=0x0080; //Подтверждаем прерывание
Send32 //Посылаем 32 байта
Kusok++; //Инкрементируем на 32 байта в половинке буфера
if(Kusok==(SND_BUFSIZE>>5)) if(Ready) //Если последний кусок и данные новые поступали
{
Ready=0; //Готовность сбрасываем
Kusok=0; //Сначала в половинку
Part^=SND_BUFSIZE; //Меняем половинки
}
else Kusok=(SND_BUFSIZE>>5)-1; //В противном случае данные не поступали но не можем же мы заткнуться просто так - поэтому играем последнее!!!!
}


В общем у мну данные читаются быстрее, чем они подготавливаются!!!
Потому что выборка 22050 буфер 512 байт, это 22050/512=43 Гц - данные обновляются с меньшей частотой.
Отключаю отрисовку экрана - звук нормальный.
Отрисовка занимает примерно тоже время что и эмуляция цпу+железа

Поэтому сделано воспроизведение последнего куска 32 байта пока не прийдут новые данные.

В общем такое работает, но в некоторых играх - темп слегка растянут - что и нужно! Но там где где юзается сильно интенсивно звук и эмулируется много и сильно - данный метод работает не очень - много хрипа (особенно когда DAC)

Поясните как можно сделать лучше...
Last edited by Romanich on 07 Apr 2009, 01:55, edited 1 time in total.
[url]Прославился тем, что на аватаре[/url]
Romanich
долбоёб-гумасек
 
Posts: 1084
Joined: 22 Nov 2007, 15:12
Group: Registered users

Postby lvd » 08 Apr 2009, 22:13

Ниасилел. Ты говоришь, что у тебе звук выигрывается из буферов быстре, чем за фрейм эмуляции генериццо? Так сделай, чтоб такого не было, то бишь засинхри эмуль от звука, а не от чего-либо ещё. На амиге, например, можно синхриться и от видео, а частоту звука динамически менять и подгонять. А на пц без вариантов - звук. Подозреваю, что и на твоей бряцалке тоже.
Другое дело, что играть пцм через мр3-декодер - извращение. Как раз потому, что жёстко засинхриться нет возможности - всегда есть те самые 32 байта, к тому же вообще байты для синхронизации руками считать нужно, и точности лучше +-32 байт не достичь. Поставь обычный дельто-сигмо-цап, заюзай ДСПшный вывод в него и би хеппи...
F̞͖̭̿̔ͯu̐̅cͬ̑ͩk̨̤̳͇̮̭̪̠̽̿̓̆ͭͩ ̷̩̰͎̩͓̘̾̀ͬ̊ͭ͛ͅda̝̺͙̬͎̝̾͟ ̰̜̝̯͉̯̖̓̎́ͨ̽ͫ͟f̟͇̭̀ͬͨͭ̐̚u̹̼̹̗̞͑̔͂͐̚cͭ̅̊̆̒̆ǩ̝̩̯́ͥ̔̍̑ḭ͓͍̳̬ͦ̽͂n͍͎͈̈̅ͩͬ ̊ͫ̂̾̑̈́f̲͚͉͓͗̋́ͧͦ̅ȗ͇̲̻͈̲̅̎͗͒ͭ͡c̬̟̠̹̯̈́ͩ͘ͅk̫̠̻̋͜a̲͒̾̇!͙͕̺͉̗̩̲̂̏̄̀
User avatar
lvd
 
Posts: 7262
Joined: 07 Apr 2007, 21:28
Group: Registered users

Postby Romanich » 09 Apr 2009, 03:36

lvd wrote:то бишь засинхри эмуль от звука, а не от чего-либо ещё.


попытаемся smile

lvd wrote:Другое дело, что играть пцм через мр3-декодер - извращение


не я первый так делаю

lvd wrote:Поставь обычный дельто-сигмо-цап, заюзай ДСПшный вывод в него и би хеппи...


поздно
Last edited by Romanich on 09 Apr 2009, 03:37, edited 1 time in total.
[url]Прославился тем, что на аватаре[/url]
Romanich
долбоёб-гумасек
 
Posts: 1084
Joined: 22 Nov 2007, 15:12
Group: Registered users

Postby Romanich » 09 Apr 2009, 06:50

Обдумав, придумал следующее.

Так как хочу 8 бит моно 22050, то несложно посчитать длину буфера в байтах: 22050/60=367,5байт. 60 - частота одного фрейма NTSC. Округляем в большую сторону чтоб кратно 32 было: 384 байта - длина половинки буфера.

Далее в памяти имеем буфер из двух половинок 2*384=768 байт. Это 24 куска по 32 байта. Присваиваем указателю проигрывания куска Kusok=12. Разрешаем прерывание. Пока будут играться куски 12..23, мы должны проапдейтить другую половинку (куски 0..11). Как только указатель проигрвания проиграл кусок 23, надо закольцевать: Kusok=0.

Теперь по синхронизации. Если Kusok=12 или Kusok=0, то разрешаем фрейм эмуляции, свопая естественно половинки буфера.

Как я это вижу:

Code: Select all
Kusok=12; //первоначально
Part=384;

SPI_Send32Byte(u32 Kusok)
{
  for(i=0;i<32;i++) SPI(Buffer[32*Kusok+i]);
}

IRQ_Handler()
{
  if((Kusok==0)||(Kusok==12))
  {
   emuflag=1; //Как только начали играть новый кусок, разрешаем эмуляцию
   Part^=384;
  }
  SPI_Send32Byte(Kusok);  //Посылаем кусок
  Kusok++;                        //Увеличиваем для следущего раза
  if(Kusok==24) Kusok=0;   //Если конец, то в начало
}

while(1)
{
  if(emuflag) //Для синхронизации
  {
   emuflag=0;
   emuM68K(); //CPU
   emuZ80();  //coCPU
   emuVDP(); //VDP
   emuInput(); //Joystick
   emuSound(); //Тут апдейт буфера
   draw_frame(); //Рисуем на экране то что вышло
  }
}

emuSound()
{
  for(i=0;i<384;i++) BUFFER[Part+i]=FM_PSG_DAC(); //Строим половинку из новых данных
}


При этом вся бодяга :

Code: Select all
emuflag=0;
  emuM68K(); //CPU
  emuZ80();  //coCPU
  emuVDP(); //VDP
  emuInput(); //Joystick
  emuSound(); //Тут апдейт буфера
  draw_frame(); //Рисуем на экране то что вышло


должна успевать выполняться за 16.(6) мс - иначе произойдет порча буфера, а если будет быстрее выполняться - будут задержки.

Поэтому необходим тюнинг системы - варьируя размер буфера - мы можем подогнать нормальное звучание при максимально возможном ФПС'е:

FPS=22050/Buffer_length

Для 43 FPS к примеру нужен буфер в 512*2 байт

Есть другой способ - понизить рейт (взять не 22050, а 16000. Но на 11025 уже не очень хорошо играет).

И ещё - у VS1003 DREQ уходит в 1 когда внешний буфер 32 байт закончен
Но это не означает что она прекратила звучать, всё сделано с расчётом на то что есть ещё время чтоб доложить (слава яйцам - щелчков не будет!) новых 32 байта
Это лично сам проверил и убедился!
Саундблястеры в этом плане сосут wink
[url]Прославился тем, что на аватаре[/url]
Romanich
долбоёб-гумасек
 
Posts: 1084
Joined: 22 Nov 2007, 15:12
Group: Registered users

Postby Romanich » 10 Apr 2009, 03:48

вчера проверил - работает!
только происходит уравниловка FPS'а всех игр. Там где можно быстрее - будет медленно, там где под завязку - оптимально.

40 FPS - не больше (максимальное значение с которым звук не тормозит в тяжелых играх типа: Contra Hard Corps, Comix Zone итп.)

всё-таки эмуляция мегодрайва дохуа ресурсов сжирает...
Last edited by Romanich on 10 Apr 2009, 03:50, edited 1 time in total.
[url]Прославился тем, что на аватаре[/url]
Romanich
долбоёб-гумасек
 
Posts: 1084
Joined: 22 Nov 2007, 15:12
Group: Registered users

Postby lvd » 10 Apr 2009, 11:41

Romanich wrote:только происходит уравниловка FPS'а всех игр. Там где можно быстрее - будет медленно, там где под завязку - оптимально.

Чо-то ниасилел - как это?
Понятно, что если проц дохломощный - то смд середины 80х годов он ниасилет. Но зачем требовать повышения фпс от лёгких игр?... Фпс должен быть ровно таким же, как и на реал железе, и точка. Ускорятельства - только по специальному желанию юзера.
F̞͖̭̿̔ͯu̐̅cͬ̑ͩk̨̤̳͇̮̭̪̠̽̿̓̆ͭͩ ̷̩̰͎̩͓̘̾̀ͬ̊ͭ͛ͅda̝̺͙̬͎̝̾͟ ̰̜̝̯͉̯̖̓̎́ͨ̽ͫ͟f̟͇̭̀ͬͨͭ̐̚u̹̼̹̗̞͑̔͂͐̚cͭ̅̊̆̒̆ǩ̝̩̯́ͥ̔̍̑ḭ͓͍̳̬ͦ̽͂n͍͎͈̈̅ͩͬ ̊ͫ̂̾̑̈́f̲͚͉͓͗̋́ͧͦ̅ȗ͇̲̻͈̲̅̎͗͒ͭ͡c̬̟̠̹̯̈́ͩ͘ͅk̫̠̻̋͜a̲͒̾̇!͙͕̺͉̗̩̲̂̏̄̀
User avatar
lvd
 
Posts: 7262
Joined: 07 Apr 2007, 21:28
Group: Registered users

Next

Return to Other

Who is online

Users browsing this forum: No registered users and 5 guests

cron