TheMaister Skrevet 6. mai 2010 Del Skrevet 6. mai 2010 (endret) Har jobba av og på noen måneder med dette prosjektet nå. Det består i hovedsak av å utvikle et simpelt bibliotek for lydavspilling (librsound, ala esound/libpulse), samt en server, rsd, som mottar lyden, og spiller den av så fort lyden er mottatt. I bunn er librsound utviklet med tanke på nettverk, da lyden ikke sendes direkte til lydkortet lokalt, som ville ha vært tilfellet med en "wrapper" (libao). Det er spesielt to ting som gjennom utviklingsprosessen har vist seg å være vanskelig. Latency: Når nettverk er med i bildet sier det seg selv at latency blir introdusert. I steden for å være ett element som forårsaker latency (lydkortet), vil det nå være 4 kilder som kan introdusere latency. (Buffer i librsound, nettverksbuffer for klient, nettverksbuffer for server, lydkortet i serveren). I dette prosjektet har jeg i steden for å fokusere på lav latency, som er svært vanskelig å få til å funke i alle tilfeller, satset på å gjøre opplevelsen "snappy". Det verste man kan oppleve ved musikkhøring eller tilsvarende er at det er en svært merkbar tidsforskjell mellom man trykker på "Play" eller "Stop", før man kan høre endringen. For å løse dette problemet benytter RSound seg av to nettverkstilkoblinger. En som sender lyddata, og en som er tilegnet kommandoer som "Stop". Nøyaktig beregning av latency: Dette er igjen et problem som er relatert til nettverk. En videoavspiller (MPlayer, VLC, etc) er veldig interessert i å vite hvor lang tid det tar fra programmet skriver lyd til lydbiblioteket til det faktisk høres av brukeren av programmet. Uten denne informasjon vil lyd og bilde ikke kunne være i sync. Ved normal bruk av lydbiblioteker kan man enkelt spørre lydkortet, siden det gjerne er eneste kilde til latency, men dette er ikke tilfellet for nettverk. En naiv måte å gjøre dette på er å bruke en nøyaktig timer på klientsiden, og dermed gjøre det mulig å "regne ut" latency. Dette var den originale tilnærmingen i librsound, og det funket overraskende bra, men det er fortsatt ikke en god nok løsning. Denne løsningen går ut i fra to ting: - Buffer underrun hos serveren skjer aldri. - Klokka på server og klient går akkurat like raskt. Disse faktorene kan elimineres ved å jevnlig spørre serveren om informasjon som gjør at man kan nøyaktig beregne latency. Denne informasjonen kan dermed sørge for at vi kan "justere" timeren vår ettersom tiden går. Vi kan heller ikke spørre serveren først når brukeren av biblioteket spør etter latency-informasjon, siden biblioteket skal kunne brukes uten noen form for "blocking", som ville ha drept så godt som alle videospillere der ute. Det gir ikke mening at det kanskje skal ta 10ms for brukeren å spørre etter latency, da ville kanskje videospilleren allerede ha måttet droppe en frame! Dermed kommer threads inn i bildet. Threads: libsound bruker én bakgrunnstråd for alt som skjer av nettverk. I mellom tråden og rsound-funksjonene finner vi en FIFO buffer (Queue i de fleste nyere programmeringsspråk). [bibliotek] -> [ Buffer ] -> [ Thread ] -> [ Nettverk ] -> [ Server ] -> [ Lydkort ] Denne bufferen sørger for at vi har noe deterministisk å forholde oss til som brukere av biblioteket. Threaden vil konstant forsøke å sende data, hvis det er noe i bufferen. Samtidig vil den oppdatere latency-timeren, slik at når et kall til rsd_delay() skjer, vil den forhåpentligvis ha en svært nøyaktig verdi tilgjengelig. Hvis feil på nettverket blir oppdaget dreper threaden seg selv, og framtidige kall til rsd_write() vil returnere med feil. E.g.: rsd_write() - Vi skriver til bufferen. Hvis dette skulle ha vært en simpel wrapper til send() ville vi måtte behandle nettverksfeil svært grundig. Dette hører ikke hjemme i et lydbibliotek. rsd_write() kan blokkere hvis man prøver å skrive mer data enn det som er ledig i bufferen. rsd_get_avail() - Siden vi nå opererer med en buffer kan vi enkelt spørre nøyaktig hvor mye plass det er ledig i bufferen. Mange videospillere krever en slik funksjon. Serveren: I typiske lydservere har det vært vanlig å mikse sammen lyd fra alle connections før én enkelt stream sendes til lydkortet. Dette er gjort av blant annet esound, pulseaudio. Hvorfor dette gjerne har blitt gjort er fordi enkelte legacy-systemer (OSSv3) støttet kun én enkelt lydstream av gangen. Noe annet som serveren må gjøre er å resample lyd, slik at lydformatet fra alle lydstrømmer er like før de mikses sammen til én enkelt stream. rsd (serveren til RSound) gjør ikke dette. I dag er det knapt et lydsystem som ikke støtter lydmiksing, det gir dermed liten mening å mikse sammen lyden i serveren. (Dette er hovedgrunnen til at PulseAudio ofte bruker latterlige mengder CPU-kraft på noe så simpelt som lyd! På netbooken min kan Pulse fort bruke 40% CPU pga dyr resampling.) Dette forenkler samtidig designet for serveren betraktelig, da hver enkelt tilkobling kan jobbe i sin helt egen thread, uten noen som helst kommunikasjon mellom de andre trådene. Det eneste problemet RSound dermed løser er å enkelt kunne sende lyd over nettverk. I motsetning gjør PulseAudio såpass mye mer at de ikke kan sammenliknes. Distribusjon: RSound er ganske portabelt. Det er skrevet i C99, og bruker POSIX-funksjonalitet. Dette skal i utgangspunktet funke uten (store) modifikasjoner på alle POSIX-støttende systemer med en fungerende C99-kompilator (GCC). Det er sålangt testet på: GNU/Linux - Arch Linux, Slackware 13.0, Ubuntu 10.04, Nintendo Wii (Arch Linux). FreeBSD 8.0 Cygwin 1.7 (MinGW/Win32 - Det er en native port av rsd, men ikke librsound) Programmer som er støttet av librsound: MPlayer - Testet i Linux, FreeBSD og Cygwin. MPD BSNES ALSA plugin - Funker med det aller meste jeg har testet. Dette inkluderer Spotify i Wine, Foobar2k i Wine, flash-plugin i browser, XBMC, MPlayer, MPD, osv. RoarAudio - En annen lydserver. Source: GIT-repo: git://github.com/Themaister/RSound.git RSound - 0.9 source rsd - win32 Patcher til MPD og MPlayer git://github.com/Themaister/alsa-plugins-rsound.git - ALSA-plugin. Kommentarer her: Kommentarer - RSound Endret 6. mai 2010 av TheMaister Lenke til kommentar
TheMaister Skrevet 16. mai 2010 Forfatter Del Skrevet 16. mai 2010 (endret) Har jobba med OSS (Open Sound System) emulering slik at OSS-programmer kan benytte seg av RSound. OSS baserer seg på rene system-kall som open(), write(), ioctl(), etc, så en emulering av dette systemet krever emulering av kernel-funksjonalitet i userspace. Ikke veldig vakkert, men forhåpentligvis fungerer det for de fleste programmer. For å få til dette uten å programmere kernel-moduler må man bruke LD_PRELOAD i Unix. Dette er morsomme (or farlige!) greier. Det lar én definere et dynamisk library som lastes inn aller først. Hvis symboler som open(), write(), etc, finnes i LD_PRELOADen, vil ikke disse lastes inn fra libc.so, men fra mitt eget emulerings-lib. Tenk på mulighetene dette gir! Dette er en veldig stor sikkerhetsrisiko, hvis man preloader et "fake" library som standard (uten at brukeren vet det) har man voila, en keylogger, f.eks. I mitt eget library kan jeg da dynamisk laste inn de "ekte" kallene til open(), write(), etc. For hvert kall til open() kan jeg se om programmet vil åpne en OSS device. Hvis det er tilfelle er det "bare" å reimplementere det meste av OSS sitt API Hvis ikke lar jeg bare den ekte open() behandle kallet. Endret 16. mai 2010 av TheMaister Lenke til kommentar
Anbefalte innlegg
Opprett en konto eller logg inn for å kommentere
Du må være et medlem for å kunne skrive en kommentar
Opprett konto
Det er enkelt å melde seg inn for å starte en ny konto!
Start en kontoLogg inn
Har du allerede en konto? Logg inn her.
Logg inn nå