Powered by TCPDF (www.tcpdf.org) Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en Architectuur Valentin Vaerwyckweg 1 – 9000 Gent Low cost multirate video transcoder met parallelle hardwareversnelling Masterproef voorgedragen tot het behalen van het diploma van Master in de industriële wetenschappen: informatica Wouter DERYCKERE Promotoren: ing. Wim VAN DEN BREEN ir. Paul BECUE (Televic conference) dr. ir. Pieter DEMUYTERE (Televic conference) Powered by TCPDF (www.tcpdf.org) Academiejaar 2013–2014 Faculteit Ingenieurswetenschappen en Architectuur Valentin Vaerwyckweg 1 – 9000 Gent Low cost multirate video transcoder met parallelle hardwareversnelling Masterproef voorgedragen tot het behalen van het diploma van Master in de industriële wetenschappen: informatica Wouter DERYCKERE Promotoren: ing. Wim VAN DEN BREEN ir. Paul BECUE (Televic conference) dr. ir. Pieter DEMUYTERE (Televic conference) Woord vooraf Een masterproef is nooit het werk van een persoon alleen. Daarom zou ik graag verschillende mensen willen bedanken voor hun bijdrage aan dit project. Ten eerste wil ik mijn stagebedrijf Televic en zijn medewerkers bedanken voor de kans die ze mij hebben gegeven. Toen ik hen voor de eerste keer contacteerde werd ik met open armen ontvangen. Persoonlijk kon ik mij geen betere stageplaats voorstellen. Alle communicatie was perfect, het aangeboden projectvoorstel was uitdagend en nooit werd ik aanzien als een gratis werkkracht. Ook het feit dat ze speciaal materiaal hebben aangekocht is een blijk van groot vertrouwen tegenover hun studenten. In het bijzonder wil ik Paul Becue en Pieter Demuytere, mijn begeleiders bij Televic, bedanken. Altijd stonden zij voor mij klaar indien ik vragen had of wanneer ik de mening van een expert zocht. Het waren ook zij die verantwoordelijk waren voor de goede opvolging van dit project. Ook mijn interne begeleider, Wim Van Den Breen, verdient ook een dankbetuiging voor de correcte opvolging van mijn masterproef en het nalezen van deze scriptie. Daarnaast wil ik ook nog mijn ouders en mijn vriendin bedanken. Niet alleen omdat ze mij de kans hebben gegeven om deze opleiding te volgen maar ook voor de morele steun in mindere tijden. Als laatste heb ik ook nog een dankwoord voor mijn medestagiaires bij Televic die zorgden voor de goede sfeer op de werkvloer. Samen lachen, een babbeltje slaan maar ook het delen van frustraties. Dankzij hen vertrok ik elke dag met plezier richting Televic. 4 Abstract Deze scriptie is het naslagwerk van mijn masterproef over de implementatie van een video transcoder cluster aan de hand van de Raspberry Pi. Buiten de theoretische uitleg over o.a. codecs en protocollen wordt ook de effectieve implementatie besproken. Deze transcoder cluster is gebouwd om live videobeelden te streamen naar verschillende mobiele toestellen. Elke transcoder in de cluster gebruikt andere parameters zodat er uit het systeem parallel verschillende videostreams komen met elk een andere kwaliteit en resolutie. Zo kan elke gebruiker de beste stream selecteren. De implementatie zelf is volledig gebeurd in C++. Het aanspreken van de componenten in de GPU gebeurt via OpenMAX. De clusterlogica gebruikt een master-slave structuur en communiceert via TCP-berichten. Dit project concludeert dat het mogelijk is om met een kleine investering toch een cluster te bouwen die diverse hoge kwaliteit videostreams kan aanbieden. Dit met een minimale vertraging. 5 Inhoudsopgave Woord vooraf 4 Abstract 5 Lijst met figuren 9 1 Inleiding 10 2 Het concept 11 3 Van 3.1 3.2 3.3 3.4 bits tot videostream Kleurruimte: RGB en YUV . . . . . . . . . . Resolutie en framerate . . . . . . . . . . . . . Videocodecs . . . . . . . . . . . . . . . . . . . 3.3.1 MJPEG . . . . . . . . . . . . . . . . . 3.3.1.1 JPEG-codec . . . . . . . . . 3.3.1.2 JIF . . . . . . . . . . . . . . 3.3.2 H.264 . . . . . . . . . . . . . . . . . . 3.3.2.1 H.264 frames . . . . . . . . . 3.3.2.2 H.264 profielen en levels . . . 3.3.2.3 H.264 levels . . . . . . . . . . 3.3.2.4 SODB, RBSP en NAL units 3.3.2.5 SPS en PPS . . . . . . . . . Bestandsformaten en media containers . . . . 4 Netwerk en stream protocollen 4.1 Netwerklaag: IP multicast . . . . . . . . . . 4.2 Transportlaag: TCP . . . . . . . . . . . . . 4.3 Transportlaag: UDP . . . . . . . . . . . . . 4.4 Transportlaag: UDP jumbograms . . . . . . 4.5 Applicatielaag: RTP . . . . . . . . . . . . . 4.5.1 RFC 2435: JPEG over RTP . . . . . 4.5.1.1 RTP-JPEG-header . . . . . 4.5.1.2 Restart header . . . . . . . 4.5.1.3 Quantization Table header 4.5.1.4 C++: RTP-JPEG-Decoder 4.5.2 RFC 3984: H.264 over RTP . . . . . 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 15 15 16 16 17 17 18 18 19 20 24 25 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 26 27 27 27 28 30 30 31 31 32 34 INHOUDSOPGAVE 7 4.5.2.1 RTP H.264 header . . . . . . . 4.5.2.2 FU header . . . . . . . . . . . 4.5.2.3 Samenvatting . . . . . . . . . . 4.5.2.4 C++: RTP H264 encoder . . . 4.6 Applicatielaag: RTSP . . . . . . . . . . . . . . 4.7 Session description protocol . . . . . . . . . . . 4.8 Applicatielaag: RTCP . . . . . . . . . . . . . . 4.9 Applicatielaag: HTTP progressive downloading 4.10 Applicatielaag: HTTP Live Streaming . . . . . 4.11 Native ondersteuning door mobile devices . . . 5 GStreamer 5.1 Principe . . . . . . . . . . . . . . 5.2 Voorbeelden . . . . . . . . . . . . 5.2.1 GStreamer streamservers 5.2.2 GStreamer cli¨ents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 35 36 36 39 41 42 42 43 44 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 45 47 47 47 6 Raspberry Pi 7 OpenMAX 7.1 Algemeen . . . . . . . . . . . . . . . 7.2 Flags . . . . . . . . . . . . . . . . . . 7.3 Tunnels . . . . . . . . . . . . . . . . 7.4 Callbacks . . . . . . . . . . . . . . . 7.5 ILclient vs native OpenMAX . . . . 7.5.1 Implementatie in C++ . . . . 7.5.1.1 Opstarten . . . . . . 7.5.1.2 Ophalen en wijzigen 7.5.1.3 Opzetten tunnel . . 49 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . van eigenschappen . . . . . . . . . . . 8 Implementatie van de transcoder 8.1 Algemene structuur . . . . . . . . . . . . . . . . . . . . 8.2 Parameters video encoder . . . . . . . . . . . . . . . . . 8.3 Fase 1: RTPToFile . . . . . . . . . . . . . . . . . . . . 8.4 Fase 2: Implementatie van de transcoder . . . . . . . . 8.4.1 Implementatie 1: Frame per frame . . . . . . . 8.5 Implementatie 2: Frame per frame met timeout . . . . 8.6 Implementatie 3: Meerdere frames . . . . . . . . . . . 8.7 Implementatie 3: Events . . . . . . . . . . . . . . . . . 8.7.1 Performantie: delay . . . . . . . . . . . . . . . . 8.7.1.1 Meten van delay . . . . . . . . . . . . 8.7.1.2 Meetresultaten . . . . . . . . . . . . . . 8.7.1.3 Oplopende delay bij andere resolutie . . 8.7.1.4 Oplopende delay: het Resize component 8.7.1.5 Encoder . . . . . . . . . . . . . . . . . . 8.7.1.6 Oplopende delay: het probleem . . . . . 8.8 Implementatie 4: Events zonder oplopende delay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 51 53 54 54 55 55 56 56 57 . . . . . . . . . . . . . . . . 59 59 61 62 62 63 65 65 67 69 69 70 70 70 70 71 75 INHOUDSOPGAVE 8.8.1 Performantie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Implementatie 5: Performantie update RTSP sink . . . . . . . . . . . . . . . 75 77 GStreamer en OpenMAX 9.1 Performantie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 78 8.9 9 8 10 Clustering 10.1 Opstelling . . . . . . . . . . . . . . . . . . 10.2 Opstelling adaptive bitrate streaming . . . 10.3 Implementatie adaptive bitrate streaming 10.4 Javascript bandwidth sensing . . . . . . . 10.5 Implementatie protocol . . . . . . . . . . . . . . . . 81 81 81 82 83 83 11 Beoordeling van het systeem 11.1 Delay van het netwerk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Bottleneck 1: ethernet poort van de Raspberry Pi . . . . . . . . . . . . . . . 11.3 Bottleneck 2: CPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 86 86 87 12 Conclusie 89 Bibliografie 90 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lijst met figuren 2.1 Schematische voorstelling van het concept . . . . . . . . . . . . . . . . . . . . 12 3.1 3.2 3.3 3.4 3.5 3.6 JPEG coding (HCL Technologies Ltd, Geciteerd april 2014) . . . . . . . . . . H.264 profielen (Ozer, 2009) . . . . . . . . . . . . . . . . . . . . . . . . . . . RBSP (Raw Byte Sequence Payload) (codesequoia, Geciteerd april 2014) . . NAL Unit (codesequoia, Geciteerd april 2014) . . . . . . . . . . . . . . . . . . NALByteStream (codesequoia, Geciteerd april 2014) . . . . . . . . . . . . . . Sequence parameter set (International telecomunication union, 2010, Tabel 7.3.2.1.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 20 21 23 23 7.1 OpenMAX states (The Khronos Group Inc, 2005) 52 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 Structuur applicatie . . . . . . . . . . . . . . . . . . . . . Quantization Parameter (Pixeltool, Geciteerd april 2014) Rate controller (Pixeltool, Geciteerd april 2014) . . . . . . Delay afhankelijk van de uitvoerresolutie . . . . . . . . . . CPU idle time . . . . . . . . . . . . . . . . . . . . . . . . Oplopende delay bij de crop of resize functie . . . . . . . . Delay van de encoder bij verschillende bitrates . . . . . . Delay van de encoder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 61 62 71 72 73 74 76 9.1 9.2 OpenMAX: Delay van de transcoder . . . . . . . . . . . . . . . . . . . . . . . OpenMAX: Framerate van de transcoder . . . . . . . . . . . . . . . . . . . . . 79 80 11.1 CPU idle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Hoofdstuk 1 Inleiding Mobile devices, zoals een smartphone of tablet, zijn de dag van vandaag niet meer weg te denken uit onze maatschappij. Omwille van hun beperkte schermresolutie en rekenkracht is een livestream verzorgen voor zo’n toestel niet eenvoudig. Ten eerste is er de beperkte bandbreedte van draadloze communicatie kanalen zoals 3G en WIFI. Daarnaast is er ook de beperkte rekenkracht om bijvoorbeeld videobeelden te decomprimeren. Een bijkomend probleem is de beperkte native ondersteuning van protocollen en media codecs. Om een goede live ervaring te garanderen voor elk mobiel toestel moet een streamserver dus meerdere streams aanbieden met verschillende parameters zoals kwaliteit en beeldresolutie. Dit is bijvoorbeeld mogelijk bij youtube waar alle filmpjes op voorhand gecodeerd werden met verschillende resoluties. Voor live beelden is het vooraf coderen niet mogelijk omdat er slechts een minimale vertraging getolereerd wordt tussen de realiteit en de getoonde videobeelden. Televic conference NV is expert in het ontwikkelen van verschillende conferentiesystemen. Dit gaat van draadloze microfonen tot volledige conferentiesystemen met live video streaming, stemmodule en de mogelijkheid om documenten onderling te delen. Omdat ook televic zoekt naar een oplossing om de ideale live ervaring naar mobiele toestellen te brengen, werd deze masterproef voorgesteld. Het project zelf is een Proof Of Concept, dus er wordt niet verwacht dat er een marktklaar product wordt ontwikkeld, maar wel om aan te tonen dat het project in praktijk te implementeren is. De beste manier om dit aan te tonen is het zelf ontwikkelen van een prototype. De eerste hoofdstukken van deze scriptie leggen de verschillende concepten, protocollen en media frameworks uit die gebruikt werden in dit project. Daarna volgt de uitleg over de effectieve implementatie waar zowel de code om te transcoderen als de clusterlogica zullen worden uitgelegd. Als laatste volgt de conclusie over de haalbaarheid van dit concept. 10 Hoofdstuk 2 Het concept Momenteel zijn er geen mogelijkheden om de live videobeelden uit het conferentiesysteem van Televic on the fly te coderen en te verzenden naar meerdere clients. Het coderen van deze live videostream is noodzakelijk aangezien het internet slechts een beperkte bandbreedte heeft. Het basisidee is om gebruik te maken van verschillende lowcost devices en deze te clusteren. Deze opstelling heeft twee grote voordelen in vergelijking met ´e´en grote transcoding server, namelijk de lage aankoopprijs en de mogelijkheid om dynamisch het systeem uit te breiden. Ook de gebruikskosten liggen een stuk lager dan bij ´e´en zware transcoding server omdat er meerdere processoren gebruikt worden die een veel lager verbruik hebben. Elke System on a Chip (SoC) zal onafhankelijk van de andere SoCs de inkomende stream coderen. De codeerparameters, zoals bitrate en resolutie, zullen bij elke SoC anders zijn. Hierdoor krijgen we verschillende streams die verschillen in kwaliteit en dus ook in benodigde bandbreedte. Daarna zal elk mobiel toestel die live wil meekijken onderzocht worden door het systeem. Deze zal de best mogelijke stream voor het toestel selecteren en die zenden naar de client. Een ideale stream wordt ervaren indien het beeld vloeiend afspeelt en de vertraging tussen de realiteit en de ontvangen beelden nihil is. Voor dit project is latency dus enorm belangrijk. Om dit te garanderen moet er gebruik worden gemaakt van hardware acceleration. Dit wil zeggen dat de wiskundige berekeningen die nodig zijn voor te (de)coderen niet door de trage CPU maar door de snellere GPU worden gedaan. Als SoC werd de Raspberry Pi voorgesteld. Deze SoC heeft naast een CPU ook een zware GPU met ingebouwde hardware encoders en decoders. 11 HOOFDSTUK 2. HET CONCEPT Figure 2.1: Schematische voorstelling van het concept 12 Hoofdstuk 3 Van bits tot videostream In dit hoofdstuk gaan we dieper in op de algemene technieken die gebruikt worden bij een digitale progressive videostream. Al deze technieken kunnen onderverdeeld worden in verschillende lagen. Onderaan vinden we de colorspace of kleurruimte, dit is de methode die beschrijft hoe elke pixelkleur gedefinieerd wordt in het geheugen. In de volgende fase kan er optioneel een codec gebruikt worden die de frames comprimeert. Eenmaal we een stroom van frames hebben, kan deze gemultiplexd worden samen met de beschikbare audiostreams, ondertitels en andere metadata in een mediacontainer. Deze kan op zijn beurt op de schijf worden geplaatst of verzonden worden via een netwerkprotocol. 3.1 Kleurruimte: RGB en YUV We beginnen met de kleurruimte of colorspace (Merckx, 2009, Deel 1) (Fourcc, Geciteerd april 2014) (Microsoft, 2011). Elk frame dat uit een videostream komt is een onafhankelijke afbeelding. Een afbeelding bestaat uit een bepaalde resolutie van pixels. Er bestaan verschillende methodes om dat pixel om te zetten naar een bitwaarde. Om het kleur te defini¨eren moet de pixel gelokaliseerd worden in een bepaalde kleurruimte. Al deze kleurruimtes kan je verdelen onder meerdere categorie¨en, de twee bekendste zijn: RGB en YUV. Bij RGB wordt er een rood(R), groen(G) en blauw(B) kanaal uit de pixel gefilterd. Bij YUV is dit de helderheid (Y) en 2 kleurcomponenten (Chrominance U en V). Daarnaast bestaan er ook nog de YIQ (voorloper YUV) en CMYK (printers) kleurruimtes. De meeste pixelindelingen van de RGB-familie zijn gelijkaardig: per pixel worden er een paar bits gereserveerd die overeenkomen met de waarde van een van de drie kleurkanalen. Soms is er ook een alpha kanaal die de doorzichtigheid van de pixel bevat. Deze wordt aangeduid als alpha (A). Een paar bekende kleurruimtes zijn: • ”RGB 24” bevat 24 bits per pixel, namelijk 1 byte per kleurkanaal 13 HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 14 • ”RGB 16” bevat 5 bits voor rood, 6 bits voor groen en 5 bits voor blauw • ”ARGB 32” bevat 1 byte voor elk kanaal (alpha, rood, groen en blauw) Bij de YUV-collectie zijn er meer variaties mogelijk. Een packed formaat zet per pixel de Y-, U- en V-waarden na elkaar. Dit is vergelijkbaar met wat RGB doet. Bij een planar formaat zijn er 3 onafhankelijke kanalen met waarden. E´en voor alle Y-waarden, ´e´en voor alle U-waarden en ´e´en voor alle V-waarden. Meestal worden deze 3 kanalen daarna gewoon na elkaar geplaatst in een buffer. In OpenMAX (7) bestaan er ook semiplanar formaten. Deze hebben een rij voor alle Y-waarden, en ´e´en voor de U- en V-waarden. (Maemo, Geciteerd april 2014) Bij RGB wordt de pixelkleur gemaakt door 3 kleuren te mengen. Hoe groter de waarde, hoe groter dit kanaal zijn aanwezigheid mag tonen. Bij YUV is dit anders. Daar wordt de kleur aangeduid door de combinatie van de U- en V-waarden. Daarna wordt aan de hand van de Y-waarde de kleur lichter of donkerder gemaakt. YUV wordt vaak gecombineerd met downsampling. Dit is een eenvoudige manier van beeld compressie. Aangezien het menselijk oog minder gevoelig is aan chrominantieverschil, worden er voor de U- en V-waarden minder bits gebruikt dan voor de Y-waarde. Bij subsampling wordt niet voor elke individuele pixel een chrominatie waarde berekend. Een subsampling van twee betekend dat alleen de chrominantie waarden van de even pixels zullen gestockeerd worden. Subsampling kan je doen in een verticale en horizontale richting. Als voorbeeld nemen we het I420 formaat (of IYUV) dat gebruikt wordt in dit project. Y Sample periode V Sample periode U Sample periode Horizontaal 1 2 2 Verticaal 1 2 2 Deze tabel toont aan dat bij I420 alle Y-pixelwaarden gebruikt worden. Alleen indien de rij en kolom index even zijn dan zullen de U en V waarden ook gebruikt worden. Dit is een eenvoudige manier om het datagebruik te minimaliseren zonder aan groot kwaliteitsverlies te lijden. Als je weet dat het aantal pixels (de resolutie) in een afbeelding gelijk is aan de hoogte maal de breedte dan kun je de gemiddelde bits per pixel ratio berekenen voor de I420 kleurruimte: het aantal Y waarden is gelijk aan de hoogte maal de breedte want er is geen subsampling. Het aantal U en V waarden is gelijk aan de hoogte ∗ breedte . Als je veronderstelt dat elke waarde 2 2 exact 1 byte inneemt dan is het aantal bits per pixel gemakkelijk te berekenen. Namelijk: resolutie+ resolutie + resolutie 4 4 resolutie , wat neerkomt op 1.5 byte of 12 bits per pixel. De sample periode wordt vaak genoteerd aan de hand van het ”j:a:b” formaat. J is de referentiewaarde waar de andere waarden zullen aan gestaafd worden, meestal is dit 4. A is het aantal chrominantiewaarden in een even rij van J-pixels. B is het aantal chrominantiewaarden HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 15 in een oneven rij van J pixels lang. In ons voorbeeld zijn er in een even rij van vier pixels lang twee chrominantiewaarden. Een oneven rij van vier pixels lang bevat er geen. Dus kan dit formaat worden omschreven als YUV 4:2:0. Een ander aantal bekende YUV formaten zijn: • I420 (aka IYUV): YUV 4:2:0 formaat, gebruikt bij JPEG-codering • UYVY (aka Y422, UYNV, HDYC): een YUV 4:2:2 formaat, gebruikt bij MPEG-codering Meer informatie over andere YUV formaten kan gevonden worden op http://www.fourcc. org/yuv.php. 3.2 Resolutie en framerate De framerate van een videostream is het aantal frames die per seconde getoond worden. Dit is uitgedrukt in Frames Per Second (FPS). De resolutie is de breedte en de hoogte van het frame. In de onderstaande tabel staat een lijst van standaard resoluties met hun bijhorende aspect ratio. Naam VGA HD 720 HD 1080 4K UHDTV 8K UHDTV 3.3 Resolutie 640*480 1280*720 1920*1080 3840*2160 7680*4320 Aspect ratio 4:3 16:9 16:9 16:9 16:9 Videocodecs Een codec is een manier om data te comprimeren of te decomprimeren. Compressie wordt bijna altijd gebruikt om minder bandbreedte of schijfruimte te verbruiken. Zo heeft een raw 720p videobeeld dat een framerate van 30 FPS heeft en I420 als colorspace gebruikt, een bandbreedte nodig van 316Mbps. Deze waarde kan je bekomen door de bits per pixel te vermenigvuldigen met de resolutie en de framerate. Het dus onmogelijk om raw 720p te streamen over een normaal 100Mbps ethernet netwerk. Voor dit project maken we gebruik van 2 videocodecs: MJPEG en H.264. Aangezien het coderen en decoderen door externe bibliotheken of door de hardware wordt gedaan, zullen we niet dieper ingaan op de wiskundige bewerkingen van deze codecs. Toch is het nodig om de algemene methodes te begrijpen. HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 3.3.1 16 MJPEG De gemakkelijkste codec om te gebruiken is Motion JPEG (MJPEG), niet te verwarren met MPEG. Deze methode houdt in dat elk frame onafhankelijk van de andere frames gecodeerd wordt via de JPEG-codeer methode. JPEG zelf is een goede compressie codec om te gebruiken bij afbeeldingen. Toch is MJPEG een slechte codec voor video’s. De belangrijkste reden hiervoor is dat de compressie onafhankelijk is van de veranderingen tussen opeenvolgende frames. Zo zal de compressie van een film met altijd dezelfde frame enorm slecht zijn in vergelijking met bv MPEG1, MPEG2 of H.264 die wel rekening houden met variaties tussen opeenvolgende frames. Toch wordt deze codec vaak gebruikt. Het grootste voordeel aan MJPEG is zijn eenvoud. Zo moeten er geen vorige frames bijgehouden worden wat resulteert in een snellere en goedkopere decoder en encoder. De meeste USB-webcams hebben dan ook deze encoder aan boord. Dit is nodig omdat de bandbreedte van USB te laag is om er hoge kwaliteit videobeelden over te verzenden. Voor dit project wordt MJPEG gebruikt om 720p beelden aan te leveren over een 100Mbps netwerk. Aangezien JPEG geen vaste minimum compressiefactor aanbiedt, is het dan ook niet zeker dat de benodigde bandbreedte onder de 100Mbps blijft. 3.3.1.1 JPEG-codec JPEG gebruikt twee methodes om data te comprimeren. De belangrijkste is de mogelijkheid om de kwaliteit te verlagen, downsampling genoemd. Figure 3.1: JPEG coding (HCL Technologies Ltd, Geciteerd april 2014) Hoe de JPEG-encoder exact werkt kan je afleiden uit blokschema 3.1. In de eerste fase wordt de raw YUV-afbeelding opgedeeld in segmenten van acht op acht pixels. Deze array van waarden wordt daarna geconverteerd met een discrete cosinus transformatie (DCT). Dit houdt in dat de punten gemapt worden op een som van cosinussen. DCT is een HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 17 lossy compressiemethode. De volgende fase is de quantization fase. Elke waarde uit de matrix wordt gedeeld door een waarde uit de quantization matrix. Door gebruik te maken van een andere quantization tabel kan de kwaliteit van de afbeelding verlaagd worden. Dit resulteert in veel nullen in de rechteronderhoek van de matrix. Daarna worden er nog een paar andere compressietechnieken toegepast. De belangrijkste is de huffman codering. Deze compressiemethode stelt een frequentietabel op van alle mogelijke waarden die in de data voorkomen. Uit deze frequentietabel wordt dan een boomstructuur opgebouwd. De waarde die het meest voorkomt krijgt de kortste bitwaarde (0 of 1). Hoe minder frequent een waarde voorkomt, hoe langer de bitwaarde die gebruikt zal worden om de waarde voor te stellen. 3.3.1.2 JIF Uit de vorige afbeelding (3.1) is af te leiden dat een JPEG-bestand een samenraapsel is van de effectieve data, headers, quantization matrixen en huffman tabellen. Dit bestandsformaat wordt omschreven als de JPEG Interchange Format (JIF). Ook voor dit project moeten we onze data encapsuleren volgens het JIF-formaat. Het JIF-formaat wordt beschreven in de ”ISO/IEC 10918-1:1994” of de ”T.81 (09/92)” standaard. (itu-t81) Een JIF-bestand start altijd met een ”Start Of Frame marker”, deze kan je herkennen aan de startwaarde ”FF D8”. Daarna volgen er verschillende blokken met data. De eerste twee bytes van elk blok bevatten altijd een marker om aan te duiden wat de volgende data precies inhoudt. Zo heb je een marker voor een quantization tabel (FF DB), een huffman tabel (FF C4), enz. Alle mogelijke markers kan je terugvinden in de JPEG-standaard. Er zijn varianten op dit bestandsformaat zoals JFIF (ISO/IEC 10918-5 - JPEG Part 5) en EXIF (JEITA CP-3451). 3.3.2 H.264 H.264, ook gekend als AVC of MPEG-4 part 10, is de opvolger van H.263 en MPEG-2. Het is een onderdeel van de grotere MPEG-4 standaard (iso). Deze standaard bestaat uit verschillende parts die elk een bepaalde technologie omschrijven. Veel gebruikte parts zijn: • Part 10: H.264 videocodec • Part 12: Algemeen bestandsformaat, gebaseerd op Apple quicktime format • Part 14: Specifiek bestandsformaat voor MP4-bestanden HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 18 Al deze standaarden kan je terugvinden onder de ISO/IEC-norm nummer 14496. Voor sommige parts kan je de documentatie gratis downloaden zoals part 4, 5, 10, 12, 20, 22, 26, 27 en 28. De andere delen zijn betalend. Al de informatie over H.264 kan gevonden worden onder ”ISO/IEC 14496-10:2012” of ”T-REC-H.264-201003-I” (International telecomunication union, 2010). 3.3.2.1 H.264 frames Er bestaan 3 types van frames. Het type is afhankelijk van welke informatie er nodig is om het frame te kunnen (de)coderen. • I frame: is onafhankelijk van andere frames • P frame: is afhankelijk van vorige frames (Ook Delta frames genaamd) • B frame: is afhankelijk van voorgaande en opvolgende frames H.264 gebruikt altijd I- en P-frames. B-frames kunnen optioneel gebruikt worden. MJPEG daarentegen gebruikt alleen maar I-frames. Het is niet altijd nodig om een volledige frame te klasseren als een I, P of B frame. Zo is het bij sommige H.264 profielen (zie afbeelding 3.2) mogelijk om per deel van een frame (een slice genaamd) in te stellen wat zijn afhankelijkheid is met andere slicen. Dan spreken we van SI en SP slicen. 3.3.2.2 H.264 profielen en levels H.264 ondersteunt verschillende profielen. Een profiel is een verzameling van verschillende compressiemethodes die ondersteund worden. Hoe hoger het profiel, hoe meer compressietechnieken er kunnen gebruikt worden en hoe hoger dus de compressiefactor wordt. De volgende profielen bestaan voor H.264: • Baseline Profile (BP) • Extended Profile (XP) • Main Profile (MP) • High Profile (HiP) • High 10 Profile (Hi10P) HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 19 • High 4:2:2 Profile (Hi422P) • High 4:4:4 Predictive Profile (Hi444PP) Bron:(International telecomunication union, 2010) Het is belangrijk dat zowel de decoder als de encoder het gebruikte profiel ondersteunen. Omdat we een zo’n breed mogelijk spectrum van toestellen willen ondersteunen kiezen we voor het baseline profiel. Indien men zeker is dat alle clients een hoger profiel accepteren, dan kan er geopteerd worden om een hoger profiel te gebruiken. Deze zal immers een veel betere compressieratio behalen. In de tabel 3.2 staat een overzicht van alle mogelijke compressie methodes en welke profielen welke methoden ondersteunen. Figure 3.2: H.264 profielen (Ozer, 2009) Het is nuttig om sommige methodes en eigenschappen onder te loep te nemen aangezien deze de uitvoer kunnen be¨ınvloeden. 1. De bitdepth en het chromaformaat vertellen welke colorspace als uitvoer uit de decoder zal komen. 2. Een H.264 encoder kan in plaats van een volledig frame ook stukken van een frame of slices teruggeven. Indien de optie ”SI en SP” ondersteund wordt, dan is het mogelijk om in plaats van de frames, de slicen te kenmerken als SI of als SP slice. 3. B-frames zijn tevens een optie die gebruikt kan worden. HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 3.3.2.3 20 H.264 levels Naast het H.264 profiel moet er ook nog een H.264 level (International telecomunication union, 2010, P291) (Ozer, 2009) gekozen worden. Een level is een reeks van afspraken die het maximum bepalen van bepaalde parameters. Deze parameters zijn bijvoorbeeld de maximum video bitrate, de minimale compressie ratio en de maximale frame size. Net zoals bij H.264 profielen moeten zowel de encoder als de decoder het gebruikte level ondersteunen. De specifieke waarden voor elk level kunnen teruggevonden worden in de offici¨ele documentatie (International telecomunication union, 2010, P291). 3.3.2.4 SODB, RBSP en NAL units Een H.264 stream kan worden afgespeeld door de frames te concateneren. Dit komt omdat een raw H.264 frame niet bestaat. Er worden namelijk altijd extra velden toegevoegd aan de data. De zuivere H.264 data noemt men een SODB (String Of Data Bits). Zoals de naam doet vermoeden is dit een bitstream wat minder praktisch is als een bytestream. Daarom wordt deze bitstream geconverteerd tot een bytestream. Eerst wordt de SODB aangevuld met ´e´en stop bit (1), daarna volgen er verschillende nul bits tot de lengte van de SODB een veelvoud is van 8. Indien de SODB met stopbit exact een aantal bytes lang is, dan worden er geen extra nullen toegevoegd. Na de conversie heb je een RBSP (Raw Byte Sequence Payload). (International telecomunication union, 2010, p65) Figure 3.3: RBSP (Raw Byte Sequence Payload) (codesequoia, Geciteerd april 2014) De H.264 codec maakt gebruik van NAL-units. Network Abstraction Layer units zijn een encapsulatie van een RBSP-pakket voorgegaan door een NAL unit type. Dit is praktisch omdat de decoder zo kan weten welk soort data er in de NAL-unit zit: een I frame, een P frame, SPS, PPS, enzovoort. In de onderstaande tabel (International telecomunication union, 2010, tbl 7.1) staan alle NAL-types. De belangrijkste zijn in het vet gemarkeerd. HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM nal unit type 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16..18 19 20 21..23 24..31 21 NAL unit Unspecified Coded slice of a non-IDR picture Coded slice data partition A Coded slice data partition B Coded slice data partition C Coded slice of an IDR picture Supplemental enhancement information (SEI) Sequence parameter set Picture parameter set Access unit delimiter End of sequence End of stream Filler data Sequence parameter set extension Prefix NAL unit Subset sequence parameter set Reserved Coded slice of an auxiliary coded picture without partitioning Coded slice extension Reserved non-VCL non-VCL Unspecified non-VCL non-VCL NAL unit type 1 indiceert een P-frame. Type 5 een I-frame. Naast het toevoegen van een NAL-unit type, wordt er ook gezorgd voor het wegwerken van de verboden bytes. Indien de RBSP een bepaald bytepatroon bevat dat verboden is, dan zullen deze veranderd worden naar een gelijkaardig patroon die wel toegelaten is. De verboden bytepatronen worden gebruikt om te synchroniseren en mogen dus niet aanwezig zijn in de RBSP-data. De volgende bytepatronen zijn verboden en worden gewijzigd: - 0x000000 wordt 0x00000300 - 0x000001 wordt 0x00000301 - 0x000002 wordt 0x00000302 - 0x000003 wordt 0x00000303 Alhoewel 0x000003 niet gebruikt wordt om te synchroniseren is dit patroon ook verboden. Indien we dit byte patroon zouden laten staan, dan zou de decoder het verschil niet zien tussen een ’verbeterd’ byte patroon en een origineel omdat deze beiden zouden starten met 0x000003. HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 22 Figure 3.4: NAL Unit (codesequoia, Geciteerd april 2014) De laatste fase is het toevoegen van een startcode aan de NAL-units. Deze synchronisatie byte wordt gebruikt om het begin van de volgende NAL-unit aan te duiden. Dit bytepatroon is 0x000001 of 0x00000001. Aangezien dit startpatroon een verboden bytepatroon is, zal deze dus ook nooit voorkomen in de data zelf. (International telecomunication union, 2010, p66). Figure 3.5: NALByteStream (codesequoia, Geciteerd april 2014) 3.3.2.5 SPS en PPS In het overzicht van de NAL-unit types staan twee speciale NAL-units, namelijk de Sequence parameter set (SPS) en de Picture parameter set (PPS). Deze twee pakketten bevatten alle algemene informatie over de H.264 video stream en worden meestal eenmaal verzonden in het begin van de stream. De SODB van de SPS bevat de volgende velden: zie tabel 3.6. Merk op dat de SPS een variabele lengte heeft. Het ontleden van deze bitstream zou ons te ver afleiden maar het is toch noodzakelijk om een paar belangrijke velden te bekijken: profile idc en level idc: bevatten het profiel en het level van de H.264 stream. chroma format idc, bit depth luma minus8 en bit depth chroma minus8: Geven de YUV-colorspace terug. pic width in mbs minus1 en pic height in map units minus1: Geven de resolutie van de frames terug. Naast de SPS-informatie is er ook nog de PPS-informatie. Deze bitstream bevat meer informatie over de coderingseigenschappen van de stream. Meer informatie over de exacte velden kunnen teruggevonden worden in (International telecomunication union, 2010, Tabel 7.3.2.2). HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 23 Het is belangrijk om te weten wat de SPS en PPS informatie inhoudt. Zonder deze pakketten is het onmogelijk om een stream te decoderen. Gelukkig worden deze twee pakketten automatisch door de encoder aangemaakt en is het dus niet nodig om deze zelf bit voor bit te construeren. 3.4 Bestandsformaten en media containers Indien je een mediabestand wil maken met audiokanalen, videokanalen, ondertitels en metadata moeten al deze kanalen gemultiplexd worden tot ´e´en bestand. Dit is slechts optioneel. Zo kan je met VLC player perfect een H.264 videostream afspelen zonder dat deze in een container zit. Ook bieden streamprotocollen zoals RTP de functie aan om deze streams naast elkaar te verzenden. Dan is de client verantwoordelijk om deze onafhankelijke streams samen te voegen door hun timestamps te vergelijken. Een van de bekendste bestandsformaat is de MP4 container. Deze is net zoals H.264 een deel van de MPEG-4 standaard. Andere bekende zijn Quicktime File Format (.mov), Matroska (.mkv) en MPEG-TS (.ts). Elk bestandsformaat biedt ondersteuning voor een beperkt aantal codecs. Een overzicht is te vinden op http://en.wikipedia.org/wiki/Comparison_of_ container_formats. HOOFDSTUK 3. VAN BITS TOT VIDEOSTREAM 24 Figure 3.6: Sequence parameter set (International telecomunication union, 2010, Tabel 7.3.2.1.1) Hoofdstuk 4 Netwerk en stream protocollen Er bestaan verschillende protocollen om een mediastream te verzenden naar de verschillende clients. We overlopen laag per laag het TCP/IP model. 4.1 Netwerklaag: IP multicast De meeste communicatie gaat van ´e´en server naar ´e´en client, maar soms zijn er meerdere clients die dezelfde data willen ontvangen. Een mogelijke oplossing is dan om de pakketten te verzenden naar het broadcast adres, dit is het laatste IP-adres van een netwerk. Dit bericht zal dan ontvangen worden door alle clients die een IP-adres hebben in dezelfde IP-range. Broadcast berichten worden niet gerouteerd. Het grootste voordeel van broadcasten is dat elk pakket slechts eenmaal moet verzonden worden. Het grootste nadeel is dan weer dat er verschillende clients deze berichten zullen ontvangen terwijl deze de data niet nodig hebben. Een andere oplossing is gebruik te maken van een IP multicasting (RFC 1112). Bij IP-multicasting wordt als ontvanger IP een klasse D adres ingevuld (eerste 4 bits zijn 1110). Elke client die deze berichten wil ontvangen zend eerst een inschrijving over het netwerk. De routers in dit netwerk zijn verantwoordelijk dat elke ingeschreven client een kopie van de multicast berichten ontvangt. Multicasting is alleen mogelijk over een LAN-netwerk waarbij alle interne routers multicasting ondersteunen. In dit project wordt multicasting gebruikt om de live beelden naar alle transcoders te verzenden. De uitgaande streams naar alle verschillende clients zijn allemaal unicast berichten. Dit moet omdat de clients via het internet zijn verbonden. 25 HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 4.2 26 Transportlaag: TCP Omdat we moeten kunnen streamen over het internet, zijn er slechts twee mogelijke protocollen die we kunnen gebruiken op de transportlaag: TCP en UDP. Streamen over TCP is in praktijk mogelijk. Zo kan je een H.264 stream die uit een encoder komt encapsuleren en verzenden in een TCP-stream. TCP werkt met het principe van handshaking. Dit wil zeggen dat een verbinding tussen 2 apparaten wordt opgezet en wordt onderhouden. Hierdoor ondersteunt TCP retransmitting van verloren pakketten. Bij live streaming is deze techniek niet aan te raden aangezien er moet gewacht worden op een frame dat toch te laat zal arriveren. Bij een H.264 stream zou dit wel handig zijn omdat het droppen van een frame resulteert in een fout die ook nog zichtbaar is in de volgende frames. Het constant moeten wachten op acknowledgement berichten, het opnieuw verzenden van verloren pakketten en de handshaking zorgen ervoor dat er tijd en bandbreedte verspild wordt. TCP werkt ook niet goed samen met IP-multicast. (Panko, 2008, P363) 4.3 Transportlaag: UDP Helaas is ook het UDP-protocol niet ideaal. UDP-pakketten worden namelijk afzonderlijk van elkaar verzonden. Dit wil zeggen dat er geen mogelijkheid is tot fragmentatie van een frame over meerdere UDP-pakketten. Dus is het verplicht om exact ´e´en frame in ´e´en UDP-pakket te verzenden. Het ’lenght’ veld in de UDP-header is 16 bit lang. Dus de payload van een UDP-pakket kan maximaal 64KB zijn inclusief de UDP-header. Als je weet dat een raw 720p frame zo’n 921 600 bytes inneemt, dan kan je concluderen dat er een verplichte constante minimale compressiefactor van 15 op 1 moet zijn. Dit is niet te garanderen voor elke frame. Het is mogelijk om frames te fragmenteren indien er gebruikt wordt gemaakt van het bovenliggende RTP-protocol. 4.4 Transportlaag: UDP jumbograms Zoals omschreven in de vorige paragraaf is de maximale payload van een UDP-pakket gelimiteerd door de beperkte lengte van het ’lenght’ field in de UDP-header. De maximale payload is: 216 min de lengte van de UDP-header. Deze header is altijd 8 byte lang. Indien er gebruik wordt gemaakt van IPV4, dan is dit limiet nog kleiner. Een IPV4-header heeft ook een ’lenght’ veld van 16 bit lang. De maximale payload van een IPV4-pakket kan dus maximaal 216 − lenght(IP V 4header) zijn. Een IPV4-header is minimaal 20 bytes indien HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 27 er geen extra headers aanwezig zijn. Met deze informatie is het mogelijk om exact te bereken hoeveel payload een UDP-pakket kan bevatten indien deze over IPV4 wordt verzonden. 216 − lenght(IP V 4header) − lenght(U DP header) = 65536 − 20 − 8 = 65507bytes Deze limiet is te omzeilen door gebruik te maken van jumbograms (RFC 2675) (RFC, 1999) (niet te verwarren met jumbo frames). Jumbograms zijn alleen te gebruiken bij IPV6. Nu heeft ook IPV6 een lengte veld van 16 bit lang. Bij jumbograms wordt dit veld op 0 gezet en wordt er een extra jumbogram IPV6-header toegevoegd. Deze extra header bestaat uit een 32 bit ’lenght’ veld waarin de correcte lengte te vinden is. Dit laat toe om een IP-payload van 232 i.p.v. 216 bytes te verzenden. Het UDP lenght field wordt op 0 gezet. Dit veroorzaakt geen problemen aangezien de exacte lengte van het UDP-pakket kan berekend worden met de data uit de IPV6-header. Helaas is dit protocol niet bruikbaar voor dit project omdat niet alle routers en besturingssystemen dit ondersteunen. 4.5 Applicatielaag: RTP Het Real Time Protocol (RTP) werkt bovenop UDP en is ontworpen voor live streaming. Het grootste nadeel van UDP, namelijk de gelimiteerde frame size, lost RTP op door ´e´en frame te fragmenteren over verschillende UDP-pakketten. RTP laat verschillende soorten payloads toe. Zo is er een standaard voor H.264 over RTP (RFC 6184), JPEG over RTP (RFC 2435), MP3 over RTP (RFC 5219), enzovoort. Merk op dat het gebruik van een media container met RTP niet nodig is. Indien er een film wordt verzonden over RTP dan zijn er twee of meerdere onafhankelijke RTP-streams die elk verantwoordelijk zijn voor een kanaal (audio, video, ...). Deze streams worden dan naast elkaar verzonden naar de client. Zolang de ontvanger dit ondersteunt en de timestamps van de pakketten in orde zijn, zal de client een vloeiende film met beeld en geluid afspelen. Wanneer er meerdere paden zijn tussen de server en de client, dan is het mogelijk dat de volgorde van de fragmenten verstoord wordt. Daarom is het aan te raden om de pakketten te ordenen wanneer deze aankomen aan de client zijde. Ongeacht zijn payload heeft elk RTP-pakket altijd dezelfde header: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | contributing source (CSRC) identifiers | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 28 (RFC, 2003a) Deze header bevat de volgende velden: V Versie: 2bit: is altijd ’2’ P Padding: 1bit: Indien ’1’ is de data verlengd met nullen tot deze een vaste lengte heeft. De laatste byte vertelt dan hoeveel bytes er toegevoegd zijn (inclusief zichzelf). X Extension: 1bit: Een extra header is voorzien. CC CSRC count: 4bit: Aantal CSRC identifiers, zie CSRC. M Marker: 1bit: Duidt een belangrijk pakket aan. De exacte betekenis is afhankelijk van het onderliggende payload type. PT Payload Type: 7bit: Type van de payload. Een overzicht van de mogelijke payload types kan teruggevonden worden op http://tools.ietf.org/html/rfc3551#section-6. De meeste protocollen kiezen voor een dynamische payload type. Dit wil zeggen dat alle informatie over de payload gekend is door zowel de client als de server. Dit kan gebeuren door een SDP-bestand of door het RTSP-protocol. Sequence number Sequence number: 16bit: Identificatienummer van het RTP-pakket. Alle fragmenten van hetzelfde frame hebben toch een ander sequentienummer. De sequence number incrementeert altijd met 1. Timestamp Timestamp: 32bit: Het aantal ticks van een klok. De snelheid van de klok is gekend door zowel de client als de server. Voor H.264 video signalen is dit bijna altijd een 90 KHz klok. Alle pakketten die data bevatten van hetzelfde frame hebben dezelfde timestamp. SSRC synchronization source identifier: 32bit: Een random waarde om de RTP-stream te identificeren. Dit is nodig indien er meerdere RTP-streams aankomen op ´e´en machine (bv een RTP-videostream en een RTP-geluidsstream). CSRC CSRC list identifies: 32bit per item : Optioneel, kan maximaal 15 items bevatten. Het aantal CSRC-objecten is meegegeven in het CC-veld. Dit is een lijst van andere SSRCs die samen horen met deze stream. In de meeste gevallen is padding false, extensions false en zijn er geen CSRSs en dus is CC gelijk aan 0. Na de algemene RTP-header volgt de payload specifieke header. In dit project worden er twee types gebruikt: JPEG over RTP en H.264 over RTP. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 4.5.1 29 RFC 2435: JPEG over RTP RFC 2435 bespreekt het protocol om JPEG-gecodeerde frames te verzenden over RTP. Opeenvolgend bevat elk pakket dan de volgende stukken: 1. RTP-header 2. RTP-JPEG-header 3. Optioneel een restart marker 4. Optioneel een quantization table header 5. Payload De opbouw van de algemene RTP-header staat uitgelegd in de vorige paragraaf 4.5. Al deze informatie is te vinden in de RFC (1998b). 4.5.1.1 RTP-JPEG-header Elk RTP pakket met JPEG-data begint met een RTP-JPEG-header. Deze bevat de algemene informatie over de JPEG-payload. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type-specific | Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Q | Width | Height | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Type-specific Type-specific: 8 bits: Is afhankelijk van het ”type” veld en heeft meer specifiek het JPEG-type terug. Fragment offset Fragment offset: 24 bits: De offset van de JPEG-data. Wanneer de fragment offset gelijk is aan 0, dan bevat het RTP-pakket de eerste scan van de JPEG-frame. Type Type: 8 bits: Het JPEG-type van de afbeelding. Type 0-63 zijn gereserveerd. Types 64 tot en met 127 zijn identiek dezelfde types maar met restart headers. JPEG-types 128 tot en met 255 zijn dynamisch toe te wijzen. Q Q: 8 bits: Definitie van de quantization table. Een waarde tussen 0 en 127 wil zeggen dat een algoritme is gebruikt om de quantization tabel te berekenen. Welk algoritme er exact is gebruikt is afhankelijk van het JPEG-type. Een waarde tussen 128 en 255 betekent dat de quantization tabel wordt meegezonden na de RTP-JPEG-header. zie 4.5.1.3. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 30 Width Width: 8 bits: De breedte van de JPEG-frame. Height Height: 8 bits: De hoogte van de JPEG-frame. 4.5.1.2 Restart header Indien de JPEG-type tussen 64 en 127 ligt, dan is de JPEG-data gecodeerd met restart markers. Deze markers zijn nuttig voor wanneer de JPEG-decoder een fout tegenkomt. Dan kan hij doorschuiven naar de volgende slice aan de hand van deze marker. Omdat deze functie niet ondersteund wordt door onze JPEG-encoder, gaan we hier niet dieper op in. De restart header ziet er als volgt uit: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Restart Interval |F|L| Restart Count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4.5.1.3 Quantization Table header De quantization tabel header wordt alleen meegezonden met de eerste scan van een JPEG-frame. Dus wanneer de offset gelijk is aan 0. Indien het quanitization (Q) veld een waarde bevat tussen 0 en 127, dan kan je aan de hand van het JPEG-type de quantization tabel worden berekend. Indien de waarde tussen 128 en 255 ligt, dan moeten de quantization tabellen meegegeven worden in een Quantization Table Header. Het aantal quantization tabels die worden meegegeven is afhankelijk van het type. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | MBZ | Precision | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Quantization Table Data | | ... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ MBZ: 8bit: moet 0 zijn. Niet gebruikt. Lenght: 16bit: lengte in bytes van de Quantization Table Data (Header niet meegerekend). Kan 0 zijn indien er geen quantization table is meegegeven. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 31 Quantization Table Data: 0 of meerdere Quantization tabels. Precision: 8bit: Voor elke quantization table wordt er een bit gezet in het precisionveld. Voor de eerste tabel is dit het meest rechtse bit (LSB), voor de tweede tabel de tweede meest rechtse bit enz. Indien deze bit op 0 is ingesteld, dan is de co¨effici¨ent van de tabel te vinden in de laatste 8 bit van de 64 byte tabel. Indien de bit op ’1’ is gezet, dan zijn het de laatste 16 bit van de 128 byte tabel. 4.5.1.4 C++: RTP-JPEG-Decoder Voor dit project moet er een 720p beeld verzonden worden over een 100Mbps netwerk. Daarom is er geopteerd om de beelden te verzenden met JPEG over RTP. In plaats van een bestaande bibliotheek te gebruiken, is er beslist om alles volledig zelf te schrijven. De belangrijkste reden hiervoor is dat zelf geschreven code veel minder resources gebruikt. Dit is belangrijk indien deze beperkt zijn zoals bij een SoC. De implementatie zelf ondersteunt niet alle opties die de RFC beschrijft. Alleen de nodige headers zijn ge¨ımplementeerd: 1. Aangezien er slechts ´e´en weg is tussen de camera en de cluster is het onmogelijk dat de volgorde van de pakketten gewijzigd wordt. Er is toch een controle ge¨ımplementeerd indien pakketten niet in de juiste volgorde zouden aankomen, maar herordening gebeurt niet. Indien er toch iets mis is gegaan, dan zal de JPEG-decoder het volledige frame droppen. 2. Er worden twee quantization tables meegegeven. 3. Om de JPEG-data in een JIF-formaat te capsuleren wordt er gebruikgemaakt van de voorbeeldcode die vermeld staat in de RFC. (RFC, 1998b, Appendix B) De volgende code wordt gebruikt om de JPEG-data uit de RTP-frames te halen: Eerst worden de fragmentOffset en de timestamp uit de ruwe UDP data gefilterd: uint32_t fragmentOffset=0|(rawUDP[13]<<16 | rawUDP[14]<<8 | rawUDP[15]); uint32_t timestamp=(rawUDP[4] << 24 )| (rawUDP[5]<<16) | (rawUDP[6]<<8) | rawUDP[7]; int pakketSize = data->UDPPackets[data->UDPReader]->len; Er wordt gecontroleerd of de sequence nummer gelijk is aan het vorige sequence nummer vermeerderd met 1. Indien dit niet het geval is dan plaatsen we de ’initialised’ waarde terug op false. Het programma zal dan wachten tot het opnieuw een volledige JPEG frame kan samenstellen voordat het deze naar de decoder zendt. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 32 if (((rawUDP[2] << 8) | rawUDP[3]) != seq+1){ initialised=false; } seq=(rawUDP[2] << 8) | rawUDP[3]; Er zijn drie mogelijke payloads: De eerste scan van een frame, een tussenscan of de laatste scan. Een eerste scan kan herkend worden aan zijn offset-veld dat op ’0’ staat. Als de ’marker’ bit aan staat dan is de data de laatste scan. Indien het RTP-pakket het eerste deel bevat van een frame, dan moeten de JIF-headers aangemaakt worden. Indien het een tussenstuk is dan moet de JPEG-data gewoon gekopieerd worden. Als de JPEG-data de laatste scan bevat dan moet de code doorgezonden worden naar de decoder. Aangezien er fouten in de RTP offset kunnen zitten, is het nodig om toch nog eens te controleren dat de JIF al zijn headers gegenereerd heeft voordat de data wordt doorgezonden naar de decoder. Anders kan de JPEG-decoder een fatale fout maken, hij zal namelijk de ’random’ JPEG-data interpreteren als een JIF-header. De ’MakeHeader’ functie kan teruggevonden worden in de ”JPEG over RTP” RFC (RFC, 1998b, Appendix B) . int offset=20; if (fragmentOffset==0){ //make headers and write them! uint headerLen = MakeHeaders(jpegFrame->pBuffer, rawUDP[16],rawUDP[18],rawUDP[19], //new frame offset= 24 + (QTH header) + (QTH data) offset=24+(rawUDP[22]<<8 | rawUDP[23]); //write rest of frame memcpy(jpegFrame->pBuffer+headerLen, rawUDP+offset, pakketSize-offset); jpegFrame->nFilledLen = headerLen + (pakketSize-offset); ... initialised=true; }else{ if (initialised){ //add the next JPEG Scan to the buffer memcpy(jpegFrame->pBuffer+jpegFrame->nFilledLen, rawUDP+offset, pakketSize jpegFrame->nFilledLen += (pakketSize-offset); }else{ //headerpart is gone! } } if ((((rawUDP[1] & 128) >> 7)==1) && initialised){ //when marker is set, end of JPEG frame! stop=true; } HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 4.5.2 33 RFC 3984: H.264 over RTP Ook voor een H.264-payload is er een RTP-standaard voorzien. Dit protocol biedt veel meer functies aan dan de JPEG over RTP standaard. Al deze informatie kan teruggevonden worden op https://www.ietf.org/rfc/rfc3984.txt en https://tools.ietf.org/html/rfc6184. De RFC 6184 (RFC, 2011) is een herwerking van de RFC 3984 standaard. Onderliggend zijn er niet veel veranderingen en doet het er ook niet toe welke RFC er gebruikt wordt. Na de algemene RTP-header die hierboven vermeld werd, volgt de H.264 RTP-header. 4.5.2.1 RTP H.264 header +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |F|NRI| Type | +---------------+ F: 1bit: 0: De data bevat geen bit errors. 1: De data kan bit errors bevatten. NRI: 2 bit: de NAL reference id: Dit is een twee bit waarde die de prioriteit van het RTP-pakket aanduidt. Waarde 00 zegt het systeem dat de data onbelangrijk zijn voor de reconstructie van de H.264-stream. Elke waarde groter dan 00 eist dat het frame moet gelezen worden. In ons voorbeeld zijn alle NRI gelijk aan 01. De NRI moet gelijk zijn aan 00 indien het NAL-type gelijk is aan: 6, 9, 10, 11, of 12. TYPE: 5 bits: Het NAL-type. Er bestaan verschillende types van H.264-pakketten. In onderstaande tabel staat een overzicht van alle NAL-Types: NAL Type Packet Type name Section --------------------------------------------------------0 undefined 1-23 NAL unit Single NAL unit packet per H.264 5.6 24 STAP-A Single-time aggregation packet 5.7.1 25 STAP-B Single-time aggregation packet 5.7.1 26 MTAP16 Multi-time aggregation packet 5.7.2 27 MTAP24 Multi-time aggregation packet 5.7.2 28 FU-A Fragmentation unit 5.8 29 FU-B Fragmentation unit 5.8 30-31 undefined - De NAL types van 0-23 zijn reeds uitgelegd in het paragraaf 3.3.2.4. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 34 Voor dit project gebruiken we twee NAL-types: ”Single NAL unit packet” en ”FU-A”. Indien ons H.264 frame in ´e´en RTP pakket kan, dus moet het niet gefragmenteerd worden, dan nemen we het type van de oorspronkelijke frame (die altijd tussen 0 en 24 ligt). Deze wordt dan geplaatst in het type-veld. Daarna plaatsen we de H.264 data met optionele padding. Dit is een ”Single NAL unit packet”. Indien onze data te groot is om in ´e´en RTP pakket geplaatst te worden, dan wordt deze gefragmenteerd en wordt het type op FU-A (28) geplaatst. Dan volgt er een FU header en daarna pas de gefragmenteerde data. Een FU-B pakket heeft ook een FU header met daarna nog een extra DON (decoding order number) veld. Dit nummer kan gebruikt worden door gateways en routers die een RTP-pakket verder willen opsplitsen in twee pakketten zonder de volledige RTP-sequentienummer aan te moeten passen van het RTP-pakket en al de daaropvolgende RTP-pakketten. Een client moet dan gewoon de waarde van het RTP sequence nummer field en het DON field concateneren om het exacte sequentienummer te weten. De FU-A pakketten kunnen eventueel door een router omgezet worden naar FU-B pakketten indien de MTU van het ene netwerk groter is dan de MTU van het andere netwerk. STAP-A, STAP-B, MTAP16 en MTAP24 zijn types die toelaten om verschillende H.264 delen van opvolgende frames in ´e´en RTP-pakket te stoppen. Voor dit project worden deze types niet gebruikt. 4.5.2.2 FU header Deze header is alleen nodig indien het NAL-type ingesteld is op 28 (FU-A). De FU-header bestaat uit: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+-+ |S|E|R| Type | +---------------+ S: 1 bit: De starbit staat op 1 indien de payload het eerste deel van een frame bevat. E: 1 bit: De endbit staat op 1 indien de payload het laatste deel van een frame bevat. Deze bit is dus altijd identiek aan de RTP marker bit die ook aan staat indien de payload de laatste scan van een frame bevat. R: 1 bit: gereserveerde bit: altijd 0 Type: 5 bit: Het oorspronkelijke NAL-type. Deze waarde ligt tussen 0 en 24. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 4.5.2.3 35 Samenvatting Indien we een H.264 frame moeten fragmenteren, dan encapsuleren we de data in een FU-A-pakket die er als volgt uitziet: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |F|NRI| 28 (type) | S|E|R| Type | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | FU payload | | :.OPTIONAL RTP padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Indien het H.264 frame in ´e´en pakket kan, dan kan deze geplaatst worden in single NAL unit frame die er als volgt uit ziet: +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |F|NRI| Type | | +---------------+ | | FU payload | | :.OPTIONAL RTP padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 4.5.2.4 C++: RTP H264 encoder Voor we kunnen beslissen of we het H.264 frame moeten fragmenteren, moeten we eerst de maximale grootte van een UDP-pakket kennen. Een NAL-UNIT mag nooit de MTU overschrijven van zijn protocol. Een ethernetpakket heeft als MTU 1500 bytes. Gevolgd door de volgende headers: HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 36 • IP header: 20 byte • UDP header: 8 byte • RTP header:12 byte • H264 RTP: 1 byte • FU header: 1 byte Er resteert dus nog 1458 bytes voor de effectieve RTP-payload. Dit project heeft een ’sendRTPPacket’ functie die een H.264 frame over RTP verzendt. De data die aangeleverd wordt kan een volledig H.264 frame zijn, of een deel ervan. Het eerste probleem is het verwijderen van de H.264 startcode. Indien de lengte kleiner is dan 3 dan is het zeker dat er geen startcode aanwezig is en de data dus een deel is van het vorige frame. Daarna roept deze functie de ’processRTPPacket’ functie aan met het H.264 type, dat hij uit de startcode heeft gehaald, als argument. Indien er geen startcode aanwezig was dan wordt het type van het vorige deel gebruikt. void sendRTPPakket(char* RTP, uint32_t timeStamp,char* databuffer, unsigned int datalen, bool startbit, bool endbit, RTSPClient** RTPClients, int sock, int aantalClients){ static int vorigeTyp; if (datalen<3){ //too small to contain a H264 startcode processRTPPacket(RTP, timeStamp,databuffer,datalen, 0, startbit, endbit,RTPClients }else{ //search H264 startcode int offset=0; if (databuffer[0]==0 & databuffer[1]==0){ while ((databuffer[offset])!=0x0 || (databuffer[offset+1])!=0x1){ offset++; } } offset++; offset++; int type=(databuffer[offset])&31 ;//00011111 offset++; if (offset!=3){ // H264 startcode, send startcode processRTPPacket(RTP, timeStamp,databuffer+offset,datalen-offset, 0,startbit, vorigeTyp=type; }else{ HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 37 //No H264 startcode, is part of the previous slice, use previous type. processRTPPacket(RTP, timeStamp,databuffer,datalen, 0, startbit, endbit,RTPCli } } } De ’processRTPPacket’ functie fragmenteert (indien nodig) het frame en encapsuleert de data in een H.264-RTP-pakket. void processRTPPakket(char* RTP, uint32_t timeStamp,char* databuffer, unsigned int datalen, bool singleFrame, bool startbit, bool endbit, RTSPClient** RTPClients,int type, int sock, int aantalClients){ static int16_t seqNr; if (datalen > 1394){ //split int i=0; while (((i+1)*1394) < datalen){ processRTPPakket(RTP,timeStamp,databuffer+(i*1394) , 1394, 0, startbit, 0,RTPC startbit=0; i++; } processRTPPakket(RTP,timeStamp,databuffer+(i*1394) , datalen-(i*1394), 0, 0, endbi return; }else{ Indien de grootte kleiner is dan de limiet dan moet deze gecapsuleerd worden in een RTP-pakket: RTP[0]=128; //10000000, Version=10 , Padding=0, Extension=0, CC=0000 RTP[1]=96|endbit<<7;//type=96, marker bit RTP[2]=(seqNr&65280)>>8; RTP[3]=(seqNr&255); RTP[4]=(timeStamp >> 24) & 255; RTP[5]=(timeStamp >>16 ) & 255; RTP[6]=(timeStamp >>8) & 255; RTP[7]=(timeStamp & 255); //SSRC RTP[8]=1; RTP[9]=2; RTP[10]=3; HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 38 RTP[11]=4; //databuffer int ret; int offset; if (singleFrame){ RTP[12]=type|64; offset=13; }else{ RTP[12]=64|28; //0 10 TYPE(5bits) Type van FU-A is 28 RTP[13]=startbit<<7 | endbit<<6 | type; //S|E|R|Type offset=14; } Daarna wordt de ’RTP’-buffer en de data buffer naar alle toestellen verzonden over UDP. 4.6 Applicatielaag: RTSP RTSP (Real time streaming protocol, (RFC, 1998a)) wordt gezien als een zusterprotocol van RTP. RTP incapsuleert een mediastream en verzendt deze naar een IP-adres. RTSP daarentegen zorgt voor de communicatie tussen de clients en de RTP server. Hij verzorgt als het ware de authenticatie van nieuwe clients en onderhandelt de instellingen van de RTP-stream. Het protocol werkt net als HTTP met commando’s die verzonden worden over TCP en die resulteren in exact ´e´en antwoordbericht. De belangrijkste commando’s die kunnen verzonden worden zijn: OPTIONS, DESCRIBE, SETUP, PLAY, PAUSE en TEARDOWN. Een nieuwe client die een RTP-stream wil ontvangen zal dan ook deze commando’s in deze volgorde verzenden. RTSP-pakketten hebben steeds dezelfde structuur: OPTIONS rtsp://192.168.2.201 RTSP/1.0 CSeq: 1 User-Agent: LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23) De eerste regel bevat het RTSP-commando, de URL van de RTSP-stream en daarna de RTSP-versie. Daarna volgen een aantal ”Sleutel: waarde” parameters. Er wordt altijd een CSeq-waarde meegegeven! De reactie van de RTSP-server ziet er als volgt uit: HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 39 RTSP/1.0 200 OK Supported: play.basic, con.persistent Cseq: 1 Server: Wowza Media Server 3.6.2.18 build8031 Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, RECORD, GET_PARAMETER Cache-Control: no-cache Opnieuw is de eerste regel een uitzonderlijke regel. Deze bestaat uit de versie gevolgd door de statuscode. Merk op dat de statuscodes en de berichtindeling dezelfde zijn als bij het HTTP-protocol. Daarna volgen opnieuw een aantal ”sleutel: waarde” regels. Belangrijk is dat er altijd een CSeq-waarde wordt meegegeven. Deze waarde is dezelfde als de CSeq-parameter die werd meegezonden met de request. Na deze headers kan er nog data volgen. Dit kan indien er een ”content-lenght”-waarde aanwezig is. De delimiter tussen de headers en de data zijn twee newlines karakters. Nu we weten hoe het protocol algemeen zijn berichten opstelt kunnen we dieper ingaan op de verschillende commando’s en hun antwoord. Nadat de client een TCP-connectie heeft opgezet met de server via poort 554 (standaard) zendt deze zijn eerste bericht. De meeste mediaplayers volgen een vaste sequentie om dat te verzenden. Options: Dit commando vraagt welke RTSP-commando’s er ondersteund worden door de server. Deze stap kan eventueel overgeslagen worden indien de client veronderstelt dat de server alle mogelijke commando’s ondersteunt. Het describe commando vraagt de RTP-informatie op aan de server. De reactie bevat de omschrijving van de aangeboden stream in SDP-formaat (session description protocol 4.7). Deze data wordt niet meegegeven als parameter maar als extra data. Vanaf deze stap zendt de server ook een ”session”-waarde mee in de header. Alle volgende antwoorden zullen vanaf nu ook deze session-waarde vermelden. Daarna volgt de setup instructie. Deze zet de RTP-verbinding op tussen de client en de server. De client heeft de SDP informatie aanvaard en geeft informatie van zijn kant terug in de ”Transport”-parameter. Een voorbeeld hiervan is: Transport: RTP/AVP;unicast;client_port=53838-53839 De Transport waarde bevat opnieuw meerdere parameters die gescheiden zijn door een ’;’. ’RTP/AVP’ duidt aan dat RTP over UDP moet worden verzonden. Ook RTP over TCP wordt ondersteund om firewalls te omzeilen. De belangrijkste waarde is de ’client port’-parameter. Deze geeft twee poorten terug: de bestemming poort waar de RTP-pakketten naar toe moeten worden gezonden en de bron poort waarvan RTCP-pakketten kunnen komen (zie RTCP 4.8). Het antwoord op een setup bericht bevat ook dezelfde transport header die aangevuld wordt met nog een paar extra velden. HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 40 Transport: RTP/AVP;unicast;client_port=7008-7009;source=184.72.239.149; server_port=7512-7513;ssrc=0DCAB11F Deze extra velden zijn het bron IP-adres en de server poorten. De eerste ’server port’ is de bron poort vanwaar de RTP-pakketten zullen worden verzonden. De tweede is de RTCP-poort op de server naar waar RTCP-informatie kan worden verzonden. Ook volgt er nadien een SSRC-waarde. Zoals uitgelegd in het hoofdstuk RTP (4.5) is dit de identificatie van een RTP stream. Na een setup request zijn alle gegevens gekend om een RTP-stream te beginnen. Om deze stream te controleren kan de gebruiker drie commando’s zenden: Play, Pause en Teardown. Play zorgt ervoor dat de RTP-pakketten worden verzonden naar de client. Dit gebeurt tot de server een teardown commando ontvangt. Bij live streaming is het pause commando redelijk nutteloos, maar deze kan wel praktisch zijn bij een Video On Demand service. Er bestaan nog andere commando’s maar met deze selectie is de basis gelegd van een werkende RTSP-server. 4.7 Session description protocol Het session description protocol wordt gebruikt om meer informatie over ´e´en of meerdere streams te omschrijven. Het is een tekstuele notatie die omschreven is in RFC 4566 (RFC, 2006). Bij wijze van voorbeeld nemen we de SDP bestand die gebruikt wordt voor dit project. v=0 o=- 4064783057 1502438752 IN IP4 192.168.2.200 s=Televic RTSP\r\n" c=IN IP4 0.0.0.0 m=video 0 RTP/AVP 96 a=Width:integer;1280 a=Height:integer;720 a=cliprect:0,0,720,1280 a=rtpmap:96 H264/90000 a=fmtp:96 profile-level-id=428028; sprop-parameter-sets=J0KAKJWgKAv+WAeJE1A=,KM4CXIA= a=framerate:30.0 a=control:trackID=1 De exacte uitleg over elke parameter staat omschreven in de RFC (RFC, 2006). Indien een H.264-videostream omschreven wordt, dan is er meestal gebruikgemaakt van een dynamisch type. In ons geval omschrijft de SDP het dynamische type nummer 96. Deze gebruikt een klok van 90KHz om de timestamp te bepalen. Ook de framerate en de resolutie HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 41 worden vastgezet op respectievelijk 30FPS en 1280*720. De belangrijkste H.264-informatie is terug te vinden in het ’profile-level-id’ en de ’sprop-parameter-sets’ waarden. Het profile-level-id bevat de eerste, tweede en de derde byte van de SPS (zonder startcode of type) in hexadecimale notatie. De ’sprop-parameters-set’ bevatten de SPS en de PPS in base64 encodering (zonder startcode maar met type). Deze twee tekenreeksen worden gescheiden door een komma. 4.8 Applicatielaag: RTCP RTCP (Real-time Transport Control Protocol) is een zuster protocol naast RTP en RTSP. Deze verzendt statistieken over de stream. Zo kunnen jittering en synchronisatieproblemen gerapporteerd worden door ’Receiver reports’ te zenden naar de server. Net zoals alle andere protocollen is ook dit protocol omschreven in een RFC (RFC, 2003b). De uitleg over de effectieve pakketsamenstelling van een ’receiver report’ zou ons te ver leiden. Het is wel belangrijk om te weten dat een RR RTCP-bericht verzonden wordt door een media player naar een streamserver. Zo’n pakket bevat informatie zoals het aantal verloren pakketten, de fractie verloren pakketten, het hoogste ontvangen sequentienummer en de gedetecteerde jitter. 4.9 Applicatielaag: HTTP progressive downloading RTP heeft als grootste nadeel dat het geblokkeerd kan worden door firewalls. Indien UDP geblokkeerd wordt, kan RTP over TCP gebruikt worden. Helaas zijn er ook bepaalde firewalls die alleen HTTP-verkeer doorlaten. HTTP progressive downloading of HTTP progressive streaming is een simpele manier om dit probleem te omzeilen. Dit protocol downloadt gewoon de videostream naar de harde schijf over het HTTP protocol. De meeste mediaplayers kunnen deze stream al afspelen terwijl deze nog gedownload wordt. Het grootste nadeel aan dit protocol is niet het gebruik van TCP (4.2) maar wel dat er geen mogelijkheid is om door te spoelen zonder alle voorgaande frames te downloaden. Voor een Video On Demand service is dit onaanvaardbaar. Voor een live videostream is dit protocol wel bruikbaar. Door zijn eenvoud ondersteunen bijna alle mediaspelers dit protocol. In tegenstelling tot RTP is er bij dit protocol geen indicatie wanneer er een nieuwe frame begint. Daarom moeten JPEG-frames in een container verzonden worden. Zoals reeds aangegeven hoeft dit niet voor een zuivere H.264-videostream aangezien deze streams met startcodes werken om de verschillende frames te identificeren (3.3.2). HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 4.10 42 Applicatielaag: HTTP Live Streaming Het HTTP Live Streaming protocol (HLS)(Apple Inc., 2013) (Apple Inc. , Geciteerd april 2014) is een protocol dat gebaseerd is op het HTTP progressive downloading protocol maar dat het seek probleem oplost. Dit protocol is dan ook veel gebruikt bij video on demand services. HLS is nog niet goedgekeurd als standaard maar wordt wel al massaal veel gebruikt. De voornaamste reden hiervoor is omdat veel Apple producten dit protocol ondersteunen. Het basisidee is om de video in verschillende stukken te delen en deze apart als downloadbare content te publiceren op een webserver. Er is dan een m3u8 bestand dat aangeeft in welke stukken de mediastream is geknipt met een bijhorende link. Hiernaast is het ook mogelijk om verschillende streams te omschrijven met hun benodigde bandbreedte. Dan kan de client zelf kiezen welke stream hij afspeelt aan de hand van de beschikbare bandbreedte. Alhoewel dit protocol gebruikt wordt om live streams te verzorgen, is dit protocol hiervoor ongeschikt! De videobeelden worden namelijk ’live’ verknipt in stukken maar in feite download iedereen op hetzelfde moment hetzelfde stuk. Dit is normaal aangezien de stukken uit de toekomst nog niet gemaakt zijn, en de stukken uit het verleden voor een delay zorgen. Er ligt wel al een ’link’ naar deze stukken in het m3u8 bestand. Nu moeten HLS clients minimaal ´e´en segment volledig gedownload hebben voordat ze deze mogen afspelen. Meestal is dit een stuk van 10 seconden. Dit resulteert in een constante delay van 10 seconden. Standaard wordt elk stuk in een mpeg-ts container ge¨encapsuleerd. Deze container wordt ondersteund door alle iOS-apparaten. Als voorbeeld nemen we de live stream van deredactie.be. De m3u8-pagina ziet er als volgt uit: #EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000 http://live.stream.vrt.be/.../vrt_nieuws_live_ios.smil/chunklist-b800000.m3u8?wowzasession #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000 http://live.stream.vrt.be/.../vrt_nieuws_live_ios.smil/chunklist-b400000.m3u8?wowzasession Deze pagina linkt naar twee onafhankelijke streams. Deze zullen dezelfde beelden tonen maar met een andere bitrate. De client kan aan de hand van de bandwidth parameter beslissen welke hij zal afspelen. De gelinkte pagina’s zien er zo uit: #EXTM3U #EXT-X-VERSION:3 #EXT-X-ALLOW-CACHE:NO HOOFDSTUK 4. NETWERK EN STREAM PROTOCOLLEN 43 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:454 #EXTINF:10.0, media-b800000_454.ts?wowzasessionid=1308022554 #EXTINF:10.0, media-b800000_455.ts?wowzasessionid=1308022554 #EXTINF:10.0, media-b800000_456.ts?wowzasessionid=1308022554 ... ... Hier is het duidelijk dat elke stuk 10 seconden duurt. Indien deze pagina opnieuw geladen wordt dan zullen de nummers aangepast worden zodat het laatst aangemaakte deel vanboven vermeld staat als eerste deel. 4.11 Native ondersteuning door mobile devices De markt van de mobiele besturingssystemen wordt gedomineerd door twee besturingssystemen: iOS en Android. Deze twee besturingssystemen ondersteunen native enkele protocollen en media formaten. RTSP met RTP HTTP Progressive download HLS H264 Android JA JA Vanaf Android 3, MPEG-TS only Baseline only IOS (iphone 4) NEE JA JA Main 3.1 Voor dit project werd gekozen om met RTP en RTSP te werken aangezien dit formaat goed ondersteund wordt en met externe apps kan afgespeeld worden. Als videocodec is alleen H.264 baseline bruikbaar. De verdere gegevens over deze besturingssystemen kan gevonden worden op: http://developer. android.com/guide/appendix/media-formats.html voor android en http://support.apple. com/kb/SP587 voor IOS. Hoofdstuk 5 GStreamer GStreamer is een multimedia framework. Aan de hand van verschillende componenten (encoders, decoders, I/O-componenten, multiplexers, filters, ...) is het mogelijk om heel snel een werkende multimedia oplossing te ontwikkelen. GStreamer is cross platform en is beschikbaar onder de LPGL-licentie. Er zijn verschillende versies van GStreamer. Op moment van schrijven worden volop de versies 0.10 en 1.0 gebruikt. De nieuwste versie (1.2) is beschikbaar maar is nog niet verspreid onder de offici¨ele repositories. Een groot nadeel is dat de versies onderling niet compatibel zijn. Zo worden er per versie plug-ins van naam veranderd en veranderen er eigenschappen van componenten. Een ander groot nadeel is de relatief slechte documentatie. Op moment van schrijven is versie 1.2 de laatste nieuwe release maar is de documentatie nog geschreven voor versie 1.0 en sommige delen zelfs voor versie 0.10. Dit is ´e´en van de redenen waarom de developers gestart zijn met GStreamerSDK. Dit is de GStreamer 0.10 met een deftige documentatie. GStreamer kan op twee manieren worden aangesproken: via een programmeeromgeving of via de terminal. Zo kan je het compileren in een C++ programma of kan je snel een GStreamer server maken via de commandline. Dit heeft als grote voordeel dat je eerst een GStreamer ’tunnel’ kan proberen op de commandline en wanneer deze werkt deze kan omgezet worden naar een C++ programma. 5.1 Principe Zoals vermeld werkt GStreamer met een verzameling van plug-ins ( http://GStreamer. freedesktop.org/documentation/plugins.html ). De bedoeling is om een tunnel te maken met deze plug-ins. Een GStreamer-tunnel start altijd met een source-component(naam eindigt op src) die je dan piped naar verschillende andere componenten. Als laatste component word er altijd een sink geplaatst. 44 HOOFDSTUK 5. GSTREAMER 45 Elke component die geen sink of source element is, heeft dus ´e´en of meerdere in- en uitgangen. Een source element heeft geen ingangen en een sink element heeft geen uitgangen. In de opdrachtprompt kan je de tunnel oproepen door het gst-launch-1.0 (of gst-launch-0.10 voor versie 0.10 of de GStreamer SDK) commando uit te voeren met als enige argument de tekstuele omschrijving van een tunnel. Een tunnel is samengesteld door de namen van de gebruikte componenten gescheiden door een ”!”. Indien een component meerdere output eigenschappen heeft (denk maar aan een webcam die meerdere resoluties ondersteunt) dan kan je expliciet vermelden welke eigenschap je wil gebruiken door het plaatsen van een caps. Indien er geen ’caps’ aanwezig is dan zal GStreamer zelf proberen een goede uitgang te kiezen. Dit kan hij weten door de invoer eigenschappen van de volgende component te bekijken. De invoer en uitvoer eigenschappen van een component kunnen teruggevonden worden in de documentatie of via het ’gst-inspect’ commando. De laatste methode geniet de voorkeur aangezien deze meestal meer up-to-date is dan de documentatie die online te vinden is. Een tunnel die vaak gebruikt werd voor dit project is de onderstaande: gst-launch-1.0 -v v4l2src device=/dev/video0 ! video/x-raw,width=1280,height=720,framerate=30/1 ! videorate ! timeoverlay text="tijd:" ! jpegenc ! rtpjpegpay ! udpsink host=224.0.1.12 port=1234 multicast-iface=eth0 force-ipv4=true Als eerste component staat een Video For Linux 2 source component (V4l2src). Deze kan een door linux erkende webcam aanspreken. De gebruikte webcam ondersteunt de mogelijkheid om, in plaats van ruwe pixels, de data reeds als JPEG-gecodeerde data aan te leveren. Ook zijn er verschillende resoluties mogelijk. Dit resulteert dus in meerdere mogelijkheden als uitvoer voor het V4l2src component. Daarom is er een caps geplaatst om de juiste stream te selecteren. Indien er in de caps een resolutie zou geplaatst worden die niet door de hardware ondersteund wordt, dan wordt er een foutmelding gegenereerd. De raw 720p beelden worden daarna doorgestuurd naar een videorate component. Deze weet dat de framerate is ingesteld op 30fps, en zal dan ook proberen per 33ms een frame als uitvoer te zenden. Indien hij geen nieuwe frame heeft ontvangen, dan zal hij de vorige frame opnieuw verzenden. Deze frameratestabilisator is nodig om de metingen onafhankelijk te maken van de gebruikte webcam. De enige reden waarom we de ingebouwde JPEG-encoder van de webcam niet gebruiken is omdat we willen gebruikmaken van de timeoverlay component. Deze plaatst op elke inkomende frame de timestamp van dit moment. Dit kan gebruikt worden om later de totale delay te meten van de transcoder. Dit component aanvaardt alleen raw data als invoer. De ruwe beelden, met timestamp, worden daarna door een JPEGenc component gecodeerd volgens de JPEG-methode. RTPJPEGPay zal deze frames encapsuleren in een RTP-pakket (4.5.1) en deze worden daarna door de udpsink verzonden naar een multicastadres. HOOFDSTUK 5. GSTREAMER 5.2 46 Voorbeelden Voor dit project worden er verschillende GStreamer programma’s gebruikt. Deze zijn bijvoorbeeld verantwoordelijk om RTP-MJPEG-data aan te leveren aan de transcoder en de terugkerende stream af te spelen op het scherm. 5.2.1 GStreamer streamservers gst-launch-1.0 -v v4l2src device=/dev/video0 ! video/x-raw,width=1280,height=720,framerate=30/1 ! videorate ! timeoverlay text="tijd:" ! jpegenc ! rtpjpegpay ! udpsink host=224.0.1.12 port=1234 multicast-iface=eth0 force-ipv4=true Dit script wordt gebruikt om een timestamp (nauwkeurig tot op de milliseconde) te plaatsen op de live beelden die de webcam waarneemt. Deze worden dan ge¨encapsuleerd in een RTP-pakket en verzonden naar een multicastadres zodat alle transcoders deze kunnen ontvangen. gst-launch-1.0 -v filesrc location="big_buck_bunny_1080p_surround.avi" ! decodebin ! videoscale ! videorate ! video/x-raw,width=1280,height=720,framerate=30/1 ! timeoverlay text="tijd:" ! videoconvert ! jpegenc ! queue ! videorate ! image/jpeg,framerate=30/1 ! queue ! rtpjpegpay ! udpsink host=224.0.1.12 port=1234 multicast-iface=eth0 force-ipv4=true Dit script doet quasi hetzelfde als het vorige maar gebruikt in plaats van webcambeelden, een film van op de harde schijf. Omdat deze beelden in een AVI-container zitten, en H.264 gecodeerd zijn, moet deze datastream eerst gedecodeerd worden. Dit kan via een ”avidemux ! avdec h264” tunnel, maar gemakkelijker is om gebruik te maken van ”decodebin” die dit automatisch detecteert. De uitgaande stream is een raw pixel video stream. Met het videoscale commando kan elke ruwe frame geresized worden tot de gewenste grootte die meegegeven is in de caps. In dit voorbeeld gebruiken we twee videorate componenten. E´en voor en ´e´en na de JPEG-encoder. Dit komt omdat het coderen in JPEG niet met een constante snelheid gebeurd indien de beelden erg vari¨eren. Je kan dit zien als een dubbele stabilisator. 5.2.2 GStreamer cli¨ ents Omdat we meer controle en mogelijkheden willen dan met de standaard VLC media player, zijn er ook een paar media players gemaakt om aan de client-zijde de inkomende stream op het scherm te tonen. HOOFDSTUK 5. GSTREAMER 47 gst-launch-1.0 -v udpsrc multicast-group=224.0.1.12 port=1234 caps="application/x-rtp, media=(string)video,encoding-name=(string)JPEG" multicast-iface=eth0 ! rtpjpegdepay ! jpegdec ! videoconvert ! videoscale ! textoverlay text=SRV valignment=top ! fpsdisplaysink sync=false Dit script schrijft zich in op het multicast adres en ontvangt zo de JPEG over RTP-pakketten. Deze worden uit de RTP-pakketten gehaald en gedecodeerd. Daarna wordt er een tekst op de frames geplaatst (”SRV”) en worden deze via de fpsdisplaysink op het scherm getoond. Deze sink heeft als voordeel dat ook de gemiddelde framerate en de momentele framerate zichtbaar zijn. De eigenschap ”sync=false” vertelt dat er geen rekening moet worden gehouden met de timestamp die meegegeven worden in de RTP-header. Aangezien de timestamps correct gegenereerd werden door de streamserver is deze eigenschap in feite overbodig. gst-launch-1.0 -v udpsrc port=3000 ! application/x-rtp, payload=96 ! rtph264depay ! avdec_h264 ! fpsdisplaysink sync=false Indien er een videostream verzonden word naar een bepaald IP-adres en een poort, dan is het mogelijk om met bovenstaand script dit te tonen op het beeldscherm. Het enige verschil met het vorige programma is dat we nu avdec h264 moeten gebruiken i.p.v. jpegdec aangezien de uitgaande stream van onze transcoder een H.264-stream is. Ook hier kan eventueel een ”sync=false” parameter geplaatst worden indien de timestamps van de uitgaande RTP-pakketten niet volledig accuraat zijn. Hoofdstuk 6 Raspberry Pi Als SoC werd gekozen voor de Broadcom BCM2835. Deze processor werd beroemd als het systeem achter de Raspberry Pi. Specificaties van de CPU: • Single core van 700 MHz • ARM1176JZF-S • ARM 11 type (dus ARMv6 architectuur) • Opstartbaar via een SD kaart • 512MB RAM (gedeeld met GPU) • 100Mbps ethernet poort • 700 mA (3.5 W) stroomverbruikt • Twee USB poorten Aangezien de CPU een ARM-architectuur is, moeten alle programma’s gecompileerd worden voor deze CPU familie. Momenteel zijn er al een paar linux distributies beschikbaar. De meest gebruikte zijn debian en ArchLinux. Er is 512 MB RAM aanwezig op deze SoC. De oudere RPI (voor 15/10/2012) hebben slechts 256 MB RAM. Dit geheugen is gedeeld met de GPU. Een kanttekening moet wel gemaakt worden bij de twee USB poorten. De ethernet en deze USB-poorten zitten namelijk op dezelfde bus. Dit wil zeggen dat ook hun bandbreedte verdeeld is (ppumkin, 2013). Specificaties van de GPU: 48 HOOFDSTUK 6. RASPBERRY PI 49 R Multimedia Co-Processor • Dual Core VideoCore IV R 1.1/2.0 • Ondersteuning voor OpenGL-ES • HDMI uitgang Het belangrijkste voor dit project is de mogelijkheid om de ingebouwde encoders en decoders aan te spreken. Dit kan via OpenMAX (7). De volgende componenten worden ondersteund: Codec MPEG2 dec H263 dec H264 dec Ogg Theora dec VP6 / VP8 dec JPEG dec VC1 dec H264 enc Resize HW / SW HW HW HW SW SW SW HW HW Port IN 130 130 130 130 130 130 130 200 60 Port OUT 131 131 131 131 131 131 131 201 61 Licentie MPEG2 licentie (2.40 pound) Meegeleverde licentie Meegeleverde licentie Meegeleverde licentie License free License free VC-1 licentie (1.20 pound) Meegeleverde licentie Deze informatie is samengesteld uit verschillende bronnen aangezien er geen offici¨ele documentatie hierover beschikbaar is. (Gstreamer OpenMax plug-in team, Geciteerd april 2014) (CNXSoft, 2013) Het verschil tussen ”HW” en ”SW” is waar de code exact wordt uitgevoerd. Sommige encoders zijn namelijk volledig ge¨ıntegreerd in de GPU. Andere codecs zijn in de software geprogrammeerd maar gebruiken de GPU om de zware berekeningen te doen. De poorten die vermeld staan zijn de OpenMAX poorten 7. Qua licentiekosten zijn de ”license free codecs” gratis te gebruiken. De Raspberry Pi foundation heeft ervoor gekozen om de H.264 codec mee te leveren in de prijs van de Raspberry Pi. Er is ook de mogelijkheid om een VC1 of MPEG2 licentie te kopen zodat er bepaalde extra features beschikbaar worden. Deze sleutel is gebonden aan de serienummer van de SoC en is dus niet overdraagbaar. Hoofdstuk 7 OpenMAX Net zoals OpenGL, WebGL en OpenCL wordt OpenMAX beheerd door de Khronos group. Deze non-profit organisatie is een samenwerking tussen verschillende bedrijven om open standaarden te cre¨eren. Net zoals Adobe, canonical, EA en Google is broadcom ook lid van deze groep. Bij de ontwikkeling van de broadcom VideoCore IV is er dan ook voor geopteerd om deze standaard te gebruiken. Meer specifiek moet er gebruik worden gemaakt van de OpenMAX Integration Layer om te communiceren met de ingebouwde hardware componenten. Er is geen wijde ondersteuning voor systemen die OpenMAX ondersteunen. Op het moment van schrijven staan er slechts twee voorbeeldcodes online voor de Raspberry Pi: een H.264 encoder (hello encode) en een JPEG-decoder (hello jpeg). Hun code is beschikbaar op de github van het Raspberry Pi project. In het hoofdstuk ”Implementatie” wordt hier dieper op ingegaan. Broadcom voorziet een ”ILClient” library die een laag vormt tussen de applicatie en de OpenMAX core. Deze bibliotheek maakt het gemakkelijker om de OpenMAX-componenten aan te spreken door veel gebruikte sequenties van instructies te groeperen in ´e´en functie. Bijvoorbeeld het uitzetten van een poort kan in slechts ´e´en operatie in plaats van twee indien OpenMAX rechtstreeks gebruikt wordt. In dit hoofdstuk worden de algemene principes van OpenMAX IL uitgelegd zoals beschreven in hun specificatie. (The Khronos Group Inc, 2005) 7.1 Algemeen Elk component (decoders, encoders, resizers) die ge¨ımplementeerd zijn in de driver kunnen worden aangesproken via OpenMAX IL. Elke component heeft een aantal in en uitgangen die ge¨ıdentificeerd worden aan de hand van een poortnummer. Dit nummer is te vinden in de 50 HOOFDSTUK 7. OPENMAX 51 documentatie van de gebruikte SoC. Voor de Raspberry Pi is deze informatie te vinden op https://github.com/raspberrypi/firmware/tree/master/documentation/ilcomponents Elke component is altijd in een zekere staat (zie figuur 7.1). Figure 7.1: OpenMAX states (The Khronos Group Inc, 2005) Elke component start unloaded. Nadat deze aangemaakt wordt, zal deze naar de loaded staat gaan. Onmiddellijk daarna kan de staat manueel op idle gezet worden. In de idle stand kunnen alle parameters van een component geconfigureerd worden. Uitzonderlijk zijn er ook eigenschappen die kunnen veranderd worden in de executing staat. Nadat alle eigenschappen aanvaard zijn, kan het component in de executing staat worden geplaatst. In deze staat kan er data aangeleverd worden aan de component. In de executing staat is het ook mogelijk dat er callbacks worden gegenereerd bij bepaalde gebeurtenissen. Elke poort van een component is standaard disabled. Ook deze moeten enabled worden! De exacte volgorde om een component aan te maken is dus als volgt: 1. Initialisatie van de library 2. Aanmaken van het component 3. Zet staat op IDLE 4. Instellen van alle parameters 5. Aanzetten van de poorten 6. Zet component in executing staat Aanzetten van de poorten kan alleen indien de component zich in de idle toestand bevindt. Eenmaal alle poorten aan staan en de component in de executing toestand bevindt, kan er data in de component gepompt worden. Dit gebeurt door eerst de buffer, die gelinkt is aan de invoer poort, te vullen met data. Daarna moet het ’EmptyThisBuffer’ commando verzonden HOOFDSTUK 7. OPENMAX 52 worden naar de GPU. Deze zal de invoerbuffer in de GPU inladen. Eenmaal dit volledig gedaan is, is de nFilledLen (de gevulde lengte) van deze buffer 0 geworden. Er zal ook een EmptyBufferDone callback opgeroepen worden. Daarna moet de uitvoer opgehaald worden. Dit gebeurt door het ”FillThisBuffer” commando te verzenden samen met een link naar een uitvoerbuffer. Dit commando zal de data die klaar is kopi¨eren naar de buffer. Indien dit correct gebeurt dan zal het nFilledLenght veld van de uitvoerbuffer geen nul meer zijn en zullen de flags van deze buffer aangevuld worden door de hardware. Ook zal er een FillThisBufferDone callback gegenereerd worden. 7.2 Flags Elke buffer is van het type OMX BUFFERHEADERTYPE. Deze bezit naast een buffer ook een veld voor flags. Dit kunnen zowel flags zijn die manueel gezet worden op een invoerbuffer, als flags die door de hardware gezet worden op een uitvoerbuffer. Er zijn verschillende flags die een in- of uitvoerbuffer kunnen meekrijgen: OMX BUFFERFLAG EOS 0x00000001 Er is geen verdere data beschikbaar OMX BUFFERFLAG STARTTIME 0x00000002 Wordt gezet indien de buffer de starttime bevat OMX BUFFERFLAG DECODEONLY 0x00000004 Decodeer de meegegeven data maar zend deze niet door als uitvoer. Dit kan gebruikt worden indien je I en P frames hebt en je wil doorspoelen tot een bepaald frame. Dan moet je eerst al de voorgaande I en P frames decoderen zonder dat deze doorgezonden worden naar de uitvoer. OMX BUFFERFLAG DATACORRUPT 0x00000008 Kan als uitvoervlag worden gezet wanneer er iets fout is gelopen met de data. OMX BUFFERFLAG ENDOFFRAME 0x00000010 Deze buffer indiceert dat de data het laatste deel van een frame bevat. OMX BUFFERFLAG SYNCFRAME 0x00000020 Deze flag wordt gezet indien het frame niet afhankelijk is van andere frames (voorbeeld bij I Frames). OMX BUFFERFLAG EXTRADATA 0x00000040 Er is extra data op het einde van de buffer geplaatst. OMX BUFFERFLAG CODECCONFIG 0x00000080 Geeft aan dat de uitvoerbuffer codec-configuratie data bevat i.p.v. videodata (Bijvoorbeeld SPS of PPS). OMX BUFFERFLAG TIMESTAMPINVALID 0x00000100 Het nTimeStamp veld bevat HOOFDSTUK 7. OPENMAX 53 geen geldige waarde.(Nieuw vanaf OpenMAX IL V1.2) OMX BUFFERFLAG READONLY 0x00000200 De invoerbuffer die deze vlag heeft gezet, mag niet gewijzigd worden. OMX BUFFERFLAG ENDOFSUBFRAME 0x00000400 Deze uitvoervlag wordt gezet wanneer het einde van een subframe wordt gegeven.(Nieuw vanaf OpenMAX IL V1.2) OMX BUFFERFLAG SKIPFRAME 0x00000800 Dit frame werd overgeslaan. Dus het frame wordt ook niet gecodeerd.(Nieuw vanaf OpenMAX IL V1.2) Voorbeeld: Indien een I frame uit de H.264 encoder komt dan zijn de volgende flags gezet: EndOfSubframe, Syncframe, EndOfFrame en EndOfStream. Indien dit een P frame is dan zijn dezelfde frames gezet behalve de syncFrame flag. De uitvoer van een component moet niet altijd een volledige frame zijn. OpenMAX ondersteunt slicing zodat 1 frame met meerdere FillThisBuffer instructies uit het component moet worden gehaald. Slicing wordt niet ondersteund door elk component. Dit kan ook meegegeven worden aan de properties van een uitvoerpoort. 7.3 Tunnels Een feature die de eenvoud van OpenMAX ten goede komt is de integratie van tunnels. Deze worden gelegd tussen een uitvoer- en een invoerbuffer van verschillende componenten. Als een component dan een gedeelde buffer vult dan zal het andere component deze data automatisch binnenhalen. Zo is het even gemakkelijk om een alleenstaande component aan te spreken, als een volledige tunnel. De EmptyThisBuffer commando’s moeten dan geadresseerd worden aan het eerste component van de tunnel en de FillThisBuffer commando’s aan de laatste component van de tunnel. Het enige extra werk is het cre¨eren van deze tunnel. Tunnels hebben ook een veel betere performantie omdat er geen onnodige geheugenkopie¨en moeten worden gemaakt tussen de buffers in de GPU en de CPU. 7.4 Callbacks Een component kan verschillende callbacks terugzenden naar de ILCLient, deze zijn: EmptyBufferDone: Deze callback wordt opgeroepen wanneer de inputbuffer volledig ingeladen is in de hardware. HOOFDSTUK 7. OPENMAX 54 FillBufferDone: Deze callback wordt aangeroepen wanneer de uitvoerbuffer volledig weggeschreven is naar het CPU-geheugen. EventHandler: Deze callback wordt opgeroepen wanneer er een event optreedt in de component. Er worden twee pointers meegegeven als argument. Aan de hand van het eEvent-argument kan er gedetecteerd worden welk event er precies getriggerd. De twee andere pointers geven meer informatie over het event zelf. eEvent OMX EventCmdComplete OMX OMX OMX OMX OMX 7.5 EventError EventMark EventPortSettingsChanged EventBufferFlag EventResourcesAcquired nData1 OMX CommandStateSet OMX CommandFlush OMX CommandPortDisable OMX CommandPortEnable OMX CommandMarkBuffer Error code 0 port index port index 0 nData2 state reached Port index Port index Port index Port index 0 0 0 nFlags 0 ILclient vs native OpenMAX OpenMAX heeft in zijn documentatie een definitie gegeven van ”IL Client”, namelijk: The layer of software that invokes the methods of the core or component. The IL client may be a layer below the GUI application, such as GStreamer, or may be several layers below the GUI layer. In this document, the application refers to any software that invokes the OpenMAX IL methods. Broadcom heeft dit letterlijk genomen en leverde bij zijn SoC een C-module genoemd ”ilclient.c”. Deze bezit functies zodat veel gebruikte acties met minder code kunnen aangeroepen worden. Het is nog altijd mogelijk om rechtstreeks met OpenMAX te communiceren, maar het gaat veel eenvoudiger met de ILClient-bibliotheek. 7.5.1 Implementatie in C++ In dit project is er voor gekozen om zoveel mogelijk de functies van de ILClient bibliotheek te gebruiken. Dit geeft als voordeel dat er minder code nodig is om dezelfde uitvoer te bekomen. De ILCLient bibliotheek wordt eerst in C++ in het project ingeladen met de ’include’ functie. We kiezen ervoor om de broncode van de bibliotheek te gebruiken in plaats te linken naar de gecompileerde versie. In deze bronbestanden wordt gebruikgemaakt van een veld genoemd HOOFDSTUK 7. OPENMAX 55 ’private’. Dit moet veranderd worden indien de code gecompileerd wordt met een C++ compiler. Het linken naar de bronbestanden gebeurt aan de hand van onderstaande code: extern "C" { #include "bcm_host.h" #include "ilclient.h" } 7.5.1.1 Opstarten Voordat we de bibliotheek kunnen gebruiken moeten we een paar functies uitvoeren. Deze maken bijvoorbeeld een ’client’ struct aan en starten de nodige threads. bcm_host_init(); if ((client = ilclient_init()) == NULL) { print(0,"Error ilclient_init\n"); return -4; } if (OMX_Init() != OMX_ErrorNone) { ilclient_destroy(client); print(0,"Error OMX_Init\n"); return -3; } Daarna kunnen de componenten aangemaakt worden aan de hand van de ’ilclient create component’ functie en kunnen de eigenschappen ervan aangepast worden. 7.5.1.2 Ophalen en wijzigen van eigenschappen Het ophalen van properties gebeurt met de getParameter functie. Welke eigenschappen er per component beschikbaar zijn kan teruggevonden worden in de documentatie. Elke eigenschap van een component heeft buiten een naam ook een struct-type. Het is de bedoeling om een instantie van deze struct aan te maken, en de nSize, de nVersion en de nPortIndex velden in te vullen. Daarna kan de OMX getParameter functie de rest van de struct velden aanvullen met de gegevens uit de hardware. Het is verplicht om de vermelde drie velden correct in te vullen. Een voorbeeld is hieronder te vinden. OMX_PARAM_PORTDEFINITIONTYPE portdefEncoderOut; portdefEncoderOut.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); HOOFDSTUK 7. OPENMAX 56 portdefEncoderOut.nVersion.nVersion = OMX_VERSION; portdefEncoderOut.nPortIndex = ENCODER_OUT; int ret=OMX_GetParameter(ILC_GET_HANDLE(video_encoder), OMX_IndexParamPortDefinition, &portdefEncoderOut); if (ret!=0) { ... } print(0, portdefEncoderOut.nBufferSize); Instellingen veranderen gebeurt met de OMX SetParameter functie. Deze functie heeft dezelfde argumenten als OMX GetParameter. Elk veld uit de struct die meegegeven is wordt gekopieerd naar de GPU. Het is verplicht om elk veld van de meegegeven struct in te vullen. Het beste hiervoor is om eerst een Get te versturen, de gewenste parameter aan te passen in de struct, en de wijzigingen terug te zenden met de Set-functie. 7.5.1.3 Opzetten tunnel Zoals reeds beschreven in het hoofdstuk 7 (OpenMAX) kunnen er tunnels gelegd worden tussen de verschillende OpenMAX-componenten. Het belangrijkste om te weten is dat een tunnel pas gelegd kan worden indien de bron component reeds een uitvoer heeft gegenereerd. Je kan dit oplossen op twee verschillende manieren. De eenvoudigste manier is om eerst een EmptyThisBuffer commando aan te roepen zodat het eerste frame ingeladen wordt. Daarna kan de tunnel zonder probleem aangemaakt worden. Een andere manier is om een uitvoerparameter van de uitvoerpoort op automatisch in te stellen. Indien het eerste frame uit dit component komt dan zal OpenMAX automatisch deze uitvoer parameter aanpassen naar de juiste waarde. Tevens zal hij een OMX EventPortSettingsChanged callback genereren. Deze opgeroepen functie kan dan de tunnel aanmaken. Om de code zo eenvoudig mogelijk te houden is er voor dit project geopteerd om de eerste manier te gebruiken. De tunnel aanmaken kan rechtstreeks via OpenMAX, of via de ILCLient bibliotheek. Als je de code van de ILCLient-functie ’set tunnel’ bekijkt, dan kan je zien dat deze functie alleen de toestand van de poorten controleert, deze correct plaatst en dan de ’OMX SetupTunnel’ functie aanroept. Buiten het aanroepen van de ’set tunnel’ operatie moeten er eerst nog een paar andere dingen gebeuren. Het voornaamste is de PortDefinition ophalen van de uitvoerpoort van de eerste component en die wegschrijven naar de portdefinition van de tweede component. Zo zijn alle instellingen van de source en de sink van de tunnel gelijk. Een poortdefinitie bevat de instellingen van de buffer die aan een poort gelinkt zijn. Meer specifiek het aantal buffers, of deze actief zijn, welk type data er in de buffer aanwezig is, enzovoort. OMX_PARAM_PORTDEFINITIONTYPE portdef; portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE); portdef.nVersion.nVersion = OMX_VERSION; portdef.nPortIndex = DECODER_OUT; ret=OMX_GetParameter(ILC_GET_HANDLE(image_decoder), HOOFDSTUK 7. OPENMAX 57 OMX_IndexParamPortDefinition, &portdef); if (ret!=0){...} portdef.nPortIndex = RESIZE_IN; ret=OMX_SetParameter(ILC_GET_HANDLE(image_resize) ,OMX_IndexParamPortDefinition, &portdef); if (ret!=0){ ... } Nadat de poortdefinities van het begin en het einde van de tunnel gelijk zijn, dan kan de tunnel gelegd worden met de ’set tunnel’ functie. Daarna moet de staat (zie 7.1) van de sink component op ’Executing’ gezet worden. Hoe je exact de data inlaadt in de OpenMAX-componenten kan terug gevonden worden in hoofdstuk 8. Hoofdstuk 8 Implementatie van de transcoder Het belangrijkste deel van dit project is de implementatie van de transcoder. Zoals reeds aangegeven worden alle bewerkingen gedaan door de GPU van de Raspberry Pi. Hoe men deze aanspreekt staat uitgeschreven in hoofdstuk 7. In dit hoofdstuk gaan we dieper in op de verschillende fasen die zijn doorlopen om deze implementatie werkend en performant te krijgen. 8.1 Algemene structuur Alle implementaties uit dit hoofdstuk doen identiek hetzelfde werk, toch is de ene hier al sneller in dan de andere. De algemene structuur van dit project bestaat uit 3 threads en 2 ringbuffers (zie afbeelding 8.1). De eerste thread (RTPClient) haalt alle UDP-pakketten op die verzonden worden naar het multicast-adres 224.0.1.12 op poort 1234. Deze RTP-pakketten worden niet gedecapsuleerd maar worden in hun geheel in de eerste ringbuffer geplaatst. Die manier van werken is aan te raden om het aantal verloren UDP-pakketten te beperken. Figure 8.1: Structuur applicatie 58 HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 59 De tweede thread is de transcoder thread. Deze wacht tot er een nieuw pakket in de eerst ringbuffer wordt geplaatst en decapsulteert die. Hij blijft dit doen tot hij een volledig JPEG-frame heeft gereconstrueerd. Deze wordt dan gecapsuleerd in het JIF-formaat en wordt verzonden naar de GPU. Deze thread is tevens verantwoordelijk om de frames die uit de GPU terugkomen op te vangen en in de tweede ringbuffer te plaatsen. Dit zullen H.264 frames of delen hiervan zijn. De laatste thread is dan verantwoordelijk om de uitgaande stream te verzenden over het netwerk. Sink RTSP encapsuleert de uitgaande stream in het H.264 over RTP-formaat (zie paragraaf 4.5.2) en unicast deze data naar verschillende clients. 8.2 Parameters video encoder De H.264 encoder kan de kwaliteit van zijn uitgaande stream bepalen op twee verschillende methodes. Ofwel met een ratecontroller ofwel met een vaste quantization parameter. De eenvoudigste methode is gebruik te maken van een vaste parameter. Dit wil zeggen dat het aantal berekeningen per frame dezelfde zijn, ongeacht de complexiteit. Dit resulteert in een constante rekentijd maar ook in een variabele bitrate. Om het aantal berekeningen mee te geven aan de encoder, wordt er gebruikgemaakt van de Quantization Parameter. Figure 8.2: Quantization Parameter (Pixeltool, Geciteerd april 2014) Hoe hoger de QP, hoe meer berekeningen er gedaan worden op de afbeelding en hoe hoger de compressie wordt. Dit resulteert in een lagere bandbreedte. Indien het volgend frame complexer is, dan zal ook de compressiefactor afnemen en dus de bandbreedte verhogen. Een betere manier is om gebruik te maken van de rate controller. Dit stuk techniek is meestal ingebouwd in de encoder en past automatisch de quantization parameter aan indien er te weinig of teveel bandbreedte in gebruik is. Dit heeft als voordeel dat er een stabiele bitrate is ongeacht de complexiteit van de frames. De werktijd is dan wel afhankelijk van de complexiteit van de afbeelding. Voor dit project kiezen we voor het gebruik van een rate controller. De gewenste bandbreedte HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 60 Figure 8.3: Rate controller (Pixeltool, Geciteerd april 2014) moet dan meegegeven worden bij het opstarten van de encoder. OMX_VIDEO_PARAM_BITRATETYPE bitrateType; bitrateType.nSize = sizeof(OMX_VIDEO_PARAM_PORTFORMATTYPE); bitrateType.nVersion.nVersion = OMX_VERSION; bitrateType.eControlRate = OMX_Video_ControlRateConstant; bitrateType.nTargetBitrate = 2000000; bitrateType.nPortIndex = ENCODER_OUT; ret = OMX_SetParameter(ILC_GET_HANDLE(video_encoder), OMX_IndexParamVideoBitrate, &bitrate Indien we de Quantization tabel manueel willen instellen, dan gebruiken we dezelfde code maar de eControlRate staat dan op ’OMX Video ControlRateDisable’ en de nTargetBitrate op ’0’. Om de Quantization parameter zelf in te stellen moeten er drie eigenschappen worden aangepast: de VideoEncodeMinQuant, de videoEncodeMaxQuant en de VideoQuantization. Dit komt omdat een QP relatief is. Dus moet er een maximumwaarde, een minimumwaarde en een effectieve waarde worden meegegeven. Voor zowel I-, P- als B-frames moet de QP ingesteld zijn. Indien het gebruikte profiel geen B-frames ondersteunt, dan moet de QP voor B frames op 0 staan. Anders worden er fouten gegeneerd. De code om dit te implementeren is te vinden in het ’Transcoder.cpp’ bestand. 8.3 Fase 1: RTPToFile Het is onmogelijk om de transcoder te laten werken als er geen invoerdata is. Daarom is er eerst een RTPToFile-project gemaakt. Dit project aanvaardt een RTP-JPEG-stream en decapsuleert deze. De losse JPEG-frames worden dan weggeschreven als bestanden. Dit resulteert in 30 nieuwe foto’s per seconde wat voor een overbelasting zorgt van de interne SD-kaart. Daarom is het aan te raden de foto’s te zenden naar een USB-stick die aangesloten is op de Raspberry Pi. Er wordt nog geen threading toegepast in deze implementatie. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 8.4 61 Fase 2: Implementatie van de transcoder In de tweede fase worden de frames getranscodeerd via OpenMAX. Op de offici¨ele Raspberry Pi github staan drie voorbeeldprojecten die OpenMAX gebruiken. Hello encode : https://github.com/raspberrypi/firmware/blob/master/opt/vc/src/ hello_pi/hello_encode/encode.c Dit programma genereert een random YUV i420 frame en laat deze H.264 encoderen door de GPU. Merk op dat de manier om de GPU aan te spreken totaal niet performant is. Er werd immers geen gebruik gemaakt van events. Er wordt wel gebruikgemaakt van de ILClient bibliotheek wat resulteert in overzichtelijke en logische code. Hello JPEG : https://github.com/raspberrypi/firmware/blob/master/opt/vc/src/ hello_pi/hello_jpeg/jpeg.c Hello JPEG is een project van Matt Ownby met een lange historie. Het doel was om JPEG-bestanden te kunnen decoderen door de GPU. De oorspronkelijke versie (16 augustus 2012) maakte geen gebruik van de ILCLient en bestond uit een wirwar van zelfgeschreven klassen en events. De uitvoer van het project waren raw I420-frames. Dit werd later opgelost door de uitvoer van de component nog door het resize-component te verzenden. Dat component kan naast resizen en croppen ook de colorspace aanpassen. Er was ook geen mogelijkheid om grote JPEG-bestanden te decoderen. Op 22 augustus 2012 heeft Anthong Sale de code van Matt Ownby verbeterd. Hij maakte gebruik van de ILClient-bibliotheek van broadcom wat een enorme verbetering was qua overzichtelijkheid en eenvoud. Nu was er geen nood meer aan extra klassen of omslachtige OMX-operaties. Alhoewel de ILClient erg eenvoudig toelaat om events te registreren en te gebruiken, werd ook hier geen gebruik gemaakt van events. De code van Anthong Sale werd daarna tevens op de offici¨ele Raspberry Pi Github geplaatst. Het volledige topic over dit project kan gevonden worden op het Raspberry Pi forum (http: //www.raspberrypi.org/phpBB3/viewtopic.php?t=15463). Hello video https://github.com/raspberrypi/firmware/blob/master/opt/vc/src/hello_ pi/hello_video/video.c Dit project speelt een video (MJPEG of H.264) af op het scherm dat verbonden is via de HDMI aansluiting. Er worden 4 componenten gebruikt: Video decode, Video renderer, clock en een video scheduler. Puur technisch is dit project heel simpel, er moet namelijk geen rekening worden gehouden met de uitvoer van de tunnel. Deze wordt namelijk rechtstreeks van de GPU naar de HDMI-uitgang gestuurd. Dit project was wel handig aangezien het gebruikmaakt van OpenMAX-tunnels tussen de verschillende componenten. We kunnen besluiten dat er wel bruikbare code is die gebruikmaakt van de ILClient-bibliotheek. Het opmerkelijkste is wel dat er niemand de ingebouwde events, die ingewerkt zitten in de ILClient-bibliotheek, gebruikt. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 8.4.1 62 Implementatie 1: Frame per frame Al heel snel was er een werkende transcoder ge¨ımplementeerd. Helaas was deze niet in staat om aan 30 FPS te verwerken. De transcoder thread maakt drie componenten aan (image decoder, resize en video encode) en legt twee tunnels tussen de componenten. Ook worden de eigenschappen zoals de encoder-bitrate en de uitvoerresolutie ingesteld. De reden van de slechte performantie is de manier waarop de frames ingeladen en afgewerkt worden. Dit gebeurt namelijk frame per frame. Dit wil zeggen dat het volgende frame pas in de tunnel wordt ingeladen wanneer de vorige volledig afgewerkt is. Dit is niet performant omdat een component niets doet indien het frame in een andere component zit. Indien alle drie de componenten dezelfde verwerkingstijd per frame hebben, dan zou dit betekenen dat elk component slechts 1/3 van de tijd werkt en 2/3 van de tijd idle is. De functie construeert eerst een JPEG-frame uit de RTP-pakketten die in de ringbuffer zitten (getFrame). Daarna worden deze naar de ingang van de image decoder gezonden via het EmptyThisBuffer commando. De OpenMAX tunnel kan gelegd worden nadat voor de eerste keer een invoerbuffer geledigd is. Daarna wordt er gewacht tot de invoerbuffer volledig ingeladen is in de GPU. Dit kan gedetecteerd worden omdat de lengte van de invoerbuffer dan op 0 wordt gezet door de hardware. getFrame(pBufHeader,&size,frame,×tamp); int ret = OMX_EmptyThisBuffer(ILC_GET_HANDLE(image_decoder),pBufHeader); if (ret != OMX_ErrorNone) printf("Error sending ’Empty input buffer’ commando %x \n", ret); if (firstRun){ //tunnel can only produced after EmptyThisBuffer is send setupTunnel_resize(); setupTunnel_H264(); printf("Tunneling is enabled \n"); firstRun=false; } // wait for input buffer to empty while (pBufHeader->nFilledLen != 0){ usleep(1000); } Wanneer de data in de GPU is ingeladen, kan het FillThisBuffer commando worden verzonden. Deze functie zal het volgende frame dat uit de tunnel komt wegschrijven naar de vermelde uitvoer buffer. Ook hier pollen we tot de uitvoerbuffer gevuld is. Het FillThisBuffer en het EmptyThisBuffer commando zijn non-blocking. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 63 // Send het "Fill the buffer" command. De video_encoder moet zijn outputbuffer wegschrijv ret = OMX_FillThisBuffer(ILC_GET_HANDLE(video_encoder), output); if (ret != OMX_ErrorNone) printf("Error Filling output buffer %x \n",ret); // wait for outputbuffer to fill before beginnen executing new frame (Niet doen indien fir while(output->nFilledLen == 0) { usleep(100); } // Schrijf de output buffer weg fwrite(output->pBuffer, 1, output->nAllocLen, FDST); 8.5 Implementatie 2: Frame per frame met timeout De vorige implementatie is niet performant en resulteert ook na een paar frames in een eindeloze lus. Dit komt omdat de H.264-encoder ervoor kan kiezen om frames te droppen. De volgende implementatie heeft daarom een ingebouwd time-out mechanisme om deze fout op te vangen. ... ret = OMX_FillThisBuffer(ILC_GET_HANDLE(video_encoder), output); if (ret != OMX_ErrorNone){ ... } int timeoutcounter=0; while (output->nFilledLen == 0 && timeoutcounter<TIME_OUT_ENCODER_MS ) { usleep(1000); //1ms timeoutcounter++; } if (timeoutcounter==TIME_OUT_ENCODER_MS){ print(0, "H264 frame dropped, encoder time out!"); data->UDPReader==data->UDPWriter; }else{ //frame received, write it to file ... } De lus die verantwoordelijk is om te wachten tot de uitvoerbuffer gevuld is, heeft nu een extra stopvoorwaarde. Na 40ms (TIME OUT ENCODER MS) zal deze lus sowieso gestopt worden. Daarna wordt er gecontroleerd of er in die tijd een buffer gevuld is. Indien dit het geval is, kan deze weggeschreven worden. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 8.6 64 Implementatie 3: Meerdere frames Het grootste nadeel van de vorige implementatie is de slechte performantie. Er wordt namelijk enorm veel tijd verspild met het wachten op de afwerking van het vorige frame, of het oproepen van een time-out. Daarom voorziet deze oplossing de mogelijkheid om meerdere frames in de tunnel te transcoderen op hetzelfde ogenblik. Omdat de kans bestaat dat er twee frames op hetzelfde moment moeten worden ingeladen, is het nodig om meerdere invoerbuffers te voorzien. Dit vereist een paar aanpassingen. Vooraleer moeten er meerdere invoerbuffers gealloceerd worden in het CPU- en GPU-geheugen. Deze manier van werken wordt ook gebruikt in het Hello encode voorbeeld. Bij het opstarten wordt er gemeld aan de Image Decoder component dat hij meerdere invoerbuffers bezit. Dit moet ingesteld worden in de portdefinition parameter. Bij het transcoderen zelf zijn er geen wachtlussen meer. Er is maar ´e´en blocking functie, namelijke de getFrame functie die moet wachten tot het volgende JPEG-frame volledig is ontvangen. while (stopThread==false){ //selecteer volgende buffer OMX_BUFFERHEADERTYPE *pBufHeader = input[inputBuffer]; inputBuffer++; inputBuffer=inputBuffer%DECODER_INBUFFER_COUNT; fout: //haal JPEG frame op getFrame(pBufHeader,frame); int tmp=pBufHeader->nFilledLen; //Er zit data in de uitvoerbuffer if (!firstRun && output->nFilledLen!=0){ if (output->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { //CodecConfig gegevens (SPS of PPS) if (output->nFlags == 0x480){ //sps memcpy(data->sps->pBuffer,output->pBuffer, output->nFilled data->sps->nFilledLen=output->nFilledLen; data->sps->nTimeStamp=pBufHeader->nTimeStamp; }else if (output->nFlags==0x490){ //pps memcpy(data->pps->pBuffer,output->pBuffer, output->nFilled data->pps->nFilledLen=output->nFilledLen; data->pps->nTimeStamp=pBufHeader->nTimeStamp; } HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 65 }else{ //Er zit een frame in de uitvoer //schrijft weg ... ... } output->nFilledLen = 0; } //Indien de buffer leeg is, laad volgende JPEG if (firstRun || output->nFilledLen==0){ int ret = OMX_EmptyThisBuffer(ILC_GET_HANDLE(image_decoder),pBufHeader); if (ret != OMX_ErrorNone){ print(0,"Error sending ’Empty input buffer’ commando "); print(0,ret); goto fout; } if (firstRun){ setupTunnel_resize(); setupTunnel_H264(); firstRun=false; } ret = OMX_FillThisBuffer(ILC_GET_HANDLE(video_encoder), output); if (ret != OMX_ErrorNone){ print(0,"Error Filling output buffer"); print(0,ret); print(0,"\n"); } } } Dit resulteert in een snelle transcoder die veel fouten genereert. Ook dit is geen deftige manier om de transcoder te gebruiken. 8.7 Implementatie 3: Events In de documentatie van de OpenMAX-standaard wordt het bestaan van events uitgelegd. Helaas is er nergens een implementatie te vinden die deze events gebruikt in samenwerking met de ILClient-bibliotheek. Omdat de broncode van de IlClient-bibliotheek publiek beschikbaar is, was het mogelijk om dit zelf uit te zoeken. (https://github.com/raspberrypi/firmware/ tree/master/documentation/ilcomponents). De IlClient maakt gebruik van callbacks. Er wordt een pointer die verwijst naar een functie in het geheugen bijgehouden. Op het moment dat er een gebeurtenis plaatsvindt zal de HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 66 thread deze functie uitvoeren. Alle callbacks worden uitgevoerd door de ILCS Host thread die beheerd wordt door de bibliotheek. Eerst moeten de juiste functies aangemaakt worden. In dit voorbeeld registreren we de drie functies: void* emptyBufferDoneEvent(void *d, COMPONENT_T *comp); void* FillBufferDoneEvent(void *d, COMPONENT_T *comp); void* errorEvent(void *d,COMPONENT_T *comp, OMX_ERRORTYPE error); Deze drie functies zullen worden opgeroepen wanneer de overeenkomstige gebeurtenissen plaatsvinden. De eerste parameter geeft een pointer terug naar keuze. In die implementatie wordt er een een pointer gebruikt die verwijst naar een object met pointers naar alle belangrijke functies en variabelen. Deze drie functies moeten eerst geregistreerd worden. Dit gebeurt met de onderstaande code. ilclient_set_fill_buffer_done_callback(client, FillBufferDoneEvent, d); ilclient_set_empty_buffer_done_callback(client, emptyBufferDoneEvent, d); ilclient_set_error_callback(client, errorEvent, d); De transcodeer functie wordt nu een stuk korter. OMX_BUFFERHEADERTYPE *pBufHeader = input[inputBuffer]; inputBuffer++; inputBuffer=inputBuffer%DECODER_INBUFFER_COUNT; getFrame(pBufHeader,frame); //nTimestamp is filled but the transcoder drops the va int ret = OMX_EmptyThisBuffer(ILC_GET_HANDLE(image_decoder),pBufHeader); if (ret != OMX_ErrorNone){ print(0,"Error sending ’Empty input buffer’ commando "); print(0,ret); print(0,"\n"); } if (firstRun){ setupTunnel_resize(); setupTunnel_H264(); d->output=output; d->data=data; d->video_encoder=video_encoder; ilclient_set_fill_buffer_done_callback(client, FillBufferDoneEvent, d); ilclient_set_empty_buffer_done_callback(client, emptyBufferDoneEvent, d); firstRun=false; HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 67 } if (ret != OMX_ErrorNone){ print(0,"Error Filling output buffer"); print(0,ret); print(0,"\n"); } frame++; Indien er een EmptyThisBufferDone event genereerd wordt, dan moet het FillThisBuffer commando worden aangeroepen. void* emptyBufferDoneEvent(void *d, COMPONENT_T *comp){ FillBufferDoneData *d2=(FillBufferDoneData*)d; OMX_BUFFERHEADERTYPE *output=d2->output; int ret = OMX_FillThisBuffer(ILC_GET_HANDLE(d2->video_encoder), output); if (ret != OMX_ErrorNone){ print(0,"Error Filling output buffer"); print(0,ret); print(0,"\n"); } } Wanneer er een FillThisBufferDone getriggerd wordt, dan is de uitvoerbuffer zeker gevuld. De data uit deze uitvoerbuffer moet dan weggeschreven worden naar de tweede ringbuffer. void* FillBufferDoneEvent(void *d, COMPONENT_T *comp){ FillBufferDoneData *d2=(FillBufferDoneData*)d; OMX_BUFFERHEADERTYPE *output=d2->output; ... ... memcpy(...,output->pBuffer, output->nFilledLen); ... } Deze implementatie kan zonder fouten transcoderen aan 30 FPS. 8.7.1 Performantie: delay Delay is de vertraging tussen de werkelijkheid en de beelden die de gebruiker ziet op zijn scherm. De delay neemt toe na elke actie, zo is er een delay van de camera, een netwerkdelay, vertraging door een buffer enzovoort. Voor dit project is de totale delay van groot belang. Het zal immers gebruikt worden voor het transcoden van conferentiegesprekken. De vorige implementatie van de transcoder resulteert in een vloeiende 30 FPS stream. In deze sectie gaan we dieper in op de vertraging van deze implementatie. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 8.7.1.1 68 Meten van delay Omdat we de vertraging tussen de beelden voor en na de transcoder willen weten, wordt er een extra laptop aangesloten op het netwerk. Op deze laptop luisteren we naar de JPEG over RTP-stream die verzonden wordt door de camera. Er wordt ook een verzoek verzonden naar de transcoder zodat de laptop ook deze uitgaande stream ontvangt. Daarna worden deze tweede beelden naast elkaar gezet. Om een zo objectief mogelijke delay te meten, wordt er een timestamp geplaatst op de beelden die de webcam verlaten. Deze is nauwkeurig tot op de milliseconde. Als we nu het scherm van de laptop bevriezen, dan zien we tweede beelden met daarop de overeenkomstige timestamps. Als we dan het verschil tussen deze getallen nemen, dan weten we de vertraging tussen de inkomende en de uitgaande beelden van de transcoder. 8.7.1.2 Meetresultaten Al snel is te merken dat de de latency toeneemt. Deze latency wordt veroorzaakt door de GPU en is dus afhankelijk van de OpenMAX-instellingen. In dit hoofdstuk gaan we dieper in op de probleemstelling van de toenemende delay en van de performantie van de GPU. 8.7.1.3 Oplopende delay bij andere resolutie Zoals zichtbaar in grafiek 8.4 is de delay stabiel indien de uitvoerresolutie is ingesteld op 1280x720. Dit is dezelfde resolutie die gebruikt wordt als invoerresolutie. De ander geteste resoluties resulteren in een oplopende delay. 8.7.1.4 Oplopende delay: het Resize component Omdat de delay pas oploopt indien de resize component moet werken, gaan we eerst dieper in op dit component. Aangezien er geen informatie te vinden is over de resize OpenMAX component zijn er eerst een paar algemene testen gedaan. Aan de hand van grafiek 8.5 bekijken we of de berekeningen van de resize component in de hardware of in de software gebeurt. Aangezien er geen verschil is in CPU-gebruik tussen de opstelling met en de opstelling zonder resize component kunnen we besluiten dat alle berekeningen gedaan worden op de GPU. Om te bewijzen dat de oplopende delay niet door de resize component komt is er nog een extra meting uitgevoerd. Resizen van frames kan erg intensief zijn indien er een zwaar algoritme wordt gebruikt. Om te bewijzen dat dit niet het geval is in deze implementatie vergelijken we het resize algoritme met het crop algoritme. Het croppen van een frame is immers niet intensief. Dit is alleen een geheugen kopie van een HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 69 Figure 8.4: Delay afhankelijk van de uitvoerresolutie Figure 8.5: CPU idle time bepaalde pixelrange. Uit de grafiek 8.6 kunnen we dus besluiten dat de oplopende delay niet veroorzaakt wordt door de resize component. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 70 Figure 8.6: Oplopende delay bij de crop of resize functie 8.7.1.5 Encoder Uit het vorige hoofdstuk kunnen we besluiten dat de resize component niet verantwoordelijk is voor de vermelde fout. Ook de decoder gaat vrijuit aangezien deze voor alle streams hetzelfde werkt moet doen ongeacht de uitvoerresolutie. Dus kunnen we besluiten dat de oplopende delay veroorzaakt wordt door de H.264 encoder. Om dit te staven kan er gekeken worden naar grafiek 8.7. Deze drie streams hebben dezelfde invoer maar toch is de delay van de uitvoer groter indien de kwaliteit hoger ingesteld is. 8.7.1.6 Oplopende delay: het probleem In tegenstelling tot wat de vorige metingen ons doen geloven ligt het probleem niet in de hardware, maar in de overgang van de GPU naar de CPU. Indien er een vertraging is van 30 frames (dus 1 seconde aan beelden) dan moeten deze 30 frames ook ergens opgeslaan worden. Het klopt dat deze frames ergens in de OpenMAX tunnel staan te wachten. Indien de delay toeneemt, dan neemt dus ook het aantal frames dat in de GPU zitten toe. De OpenMAX-componenten kunnen slicing ondersteunen. Dit wil zeggen dat er geen volledig frame in de buffer zit, maar slechts een deel van een frame. Uit de documentatie (Broadcom, 2014) blijkt dat de encoder (video encode) geen slicing ondersteunt voor zijn invoer. Elk pakket moet dus een volledig raw frame zijn. Voor de uitvoer is dit niet het geval. Zo HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 71 Figure 8.7: Delay van de encoder bij verschillende bitrates is het mogelijk dat een H.264 frame in verschillende stukken uit de encoder komt. Hier situeert zich het probleem. Stel dat er ´e´en frame wordt ingeladen in de GPU door het EmptyThisBuffer commando dan zal de EmptyBufferDone callback aangeroepen worden indien het frame volledig ingeladen is. Deze callback zal op zijn beurt het FillThisBuffer commando aanroepen. Alhoewel de encoder de uitvoer in meerdere stukken zal teruggeven, wordt de FillBufferDone callback slechts eenmaal uitgevoerd. Het eerste deel van het frame zal in deze functie weggeschreven worden naar het geheugen. De overige delen van het frame blijven in de GPU. Vanaf dit punt is onze delay permanent verhoogt met ´e´en of meerdere delen van een frame. Het volgende frame (of een deel hiervan) dat uit de hardware komt, zal nu achteraan deze wachtrij geplaatst worden. En bij een FillBufferDone callback zal het eerste deel uit deze wachtrij verzonden worden naar de CPU. Nu is het opmerkelijk dat we toch geen oplopende delay hadden in de eerste grafiek 8.4. Dit komt omdat de berekeningen te zwaar waren voor de encoder. Indien de encoder niet snel genoeg is om het aantal benodigde berekeningen te doen, zal hij ervoor kiezen om het frame te droppen. Dit wil zeggen dat er een FillThisBuffer-commando aangeroepen werd die niet zal resulteren in een FillBufferDone-callback. Wel zal dit resulteren in een afname van de wachtrij (zoals hierboven omgeschreven) met ´e´en deel van een frame. Hoe meer frames de encoder dropt, hoe sneller de wachtrij opnieuw afneemt. In het geval van de eerste grafiek worden er dus meer frames gedropt dan dat er frames in meerdere stukken worden verdeeld. HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 8.8 72 Implementatie 4: Events zonder oplopende delay Deze implementatie is identiek aan de vorige maar lost de fout van de oplopende delay op. Dit kan heel eenvoudig door opnieuw het FillThisBuffer commando op te roepen indien het frame nog niet volledig teruggezonden is naar de CPU of wanneer er codec-informatie (SPS of PPS) teruggegeven wordt. int len=output->nFilledLen; SlaveSettings* data = SlaveSettings::getInstance(); ... if (output->nFlags & OMX_BUFFERFLAG_CODECCONFIG) { print(1,"Receive codec information from the encoder:"); .. int ret = OMX_FillThisBuffer(ILC_GET_HANDLE(d2->video_encoder), output); if (ret != OMX_ErrorNone){ print(0,"Error Filling output buffer");print(0,ret,true);print(0,"\n"); } }else{ //Next pakket is H.264 data ... if (!(output->nFlags & 1)){ int ret = OMX_FillThisBuffer(ILC_GET_HANDLE(d2->video_encoder), output); if (ret != OMX_ErrorNone){ print(0,"Error Filling output buffer"); print(0,ret,true);print(0, } } .. } 8.8.1 Performantie Deze implementatie lost het probleem van de oplopende delay op. Zoals te zien is op grafiek 8.8 is de delay stabiel. Normaal ligt deze constant tussen de 33 en de 66ms. Dit is een delay van ´e´en of twee frames indien de framerate 30 FPS bedraagt. Op het moment dat de encoder overbelast is, neemt de framerate af en verhoogt de delay. Toch blijft deze delay onder de halve seconde. Op grafiek 8.8 staan de resultaten van drie metingen. De eerste testopstelling heeft als uitvoer een resolutie van 640x360. In tegenstelling tot de vorige metingen blijft de delay stabiel. Ook wanneer we de resolutie verhogen naar 1280x720. Op de derde meting is de uitgaande bitrate ingesteld op 20Mbps. Dit wil zeggen dat de encoder 20Mbit mag gebruiken om 30 frames te encoderen. Dit resulteert in een enorm lange verwerkingstijd per frame voor de encoder aangezien deze de beste kwaliteit wil leveren. Omdat hij zijn interne buffers niet wil laten vollopen dropt hij af en toe een frame. Dit resulteert in een lagere framerate. 20Mbps is voor de Raspberry Pi ook de maximale bitrate HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 73 die ingesteld kan worden in de H.264 encoder. Deze optie is dan ook niet bedoeld voor live transcoding, maar voor traag transcoderen van statische media op een maximale kwaliteit. De tweede meting, met een bitrate van 2Mbps en 720p resolutie, heeft een stabiele delay en biedt een enorm hoge kwaliteit aan. Deze stream is ideaal om te gebruiken als livestream. Figure 8.8: Delay van de encoder 8.9 Implementatie 5: Performantie update RTSP sink Zoals hierboven vermeld bestaat het oorspronkelijk programma uit drie threads en twee ringbuffers. Deze opstelling is ideaal wanneer de uitvoer slechts naar ´e´en client moet worden verzonden. Indien er meerdere unicast clients de stream willen ontvangen dan zijn er drie mogelijkheden. De eerste oplossing is het behouden van de drie threads en in de sink thread itereren over de gewenste clients. Dit heeft als voordeel dat de fragmentatie en encapsulatie van de frames slechts ´e´en keer hoeft te gebeuren. Een tweede oplossing maakt per client een aparte thread aan. Deze ”client threads” lezen allemaal onafhankelijk uit dezelfde ringbuffer. Elke thread is dan zelf verantwoordelijk voor de encapsulatie en fragmentatie van de frames. Een hybride methode is ook mogelijk. Dan wordt er ´e´en thread gemaakt die de data fragmenteert, encapsuleert en wegschrijft naar een nieuwe ringbuffer. Dan wordt er nog steeds een thread per client aangemaakt die dan de data uit deze nieuwe ringbuffer verzendt naar een client. De grootste bottleneck van de sink thread(s) is de RTP-timestamp. Dit veld moet aanwezig zijn in elk verzonden RTP-pakket en bevat het aantal ticks dat een 90KHZ klok al heeft getriggerd bij het verlaten van het frame uit de encoder. Bepaalde mediaspelers (zoals VLC) zijn erg gevoelig en skippen RTP-pakketten die een paar milliseconden te laat aankomen. Dit is de voornaamste reden dat de eerste methode een slecht resultaat levert! De laatste client HOOFDSTUK 8. IMPLEMENTATIE VAN DE TRANSCODER 74 uit de lijst zal immers altijd een te late timestamp ontvangen. Dit resulteert in erg schokkerig beeld. De eerste client in de lijst merkt hier niets van want zijn timestamps zijn altijd correct. De derde methode heeft ook een paar grote nadelen. Vooreerst is er het extra geheugen dat nodig is voor de derde ringbuffer. Daarnaast wordt de CPU enorm belast omdat er een volledige geheugenkopie wordt gedaan tussen de tweede en de derde ringbuffer. Het encapsuleren zelf is niet CPU-intensief. Het vult de eerste 14 bytes van een ”header buffer” met statische data en verzendt deze over een socket. Daarna wordt de H.264 data uit de buffer ook verzonden over deze socket. Er gebeuren dus geen overbodige memcopy-opdrachten. Het fragmenteren gebeurt in een lus die een aantal pointers correct plaatst. Ook hier gebeuren er geen zware operaties. Voor dit project werd dan ook voor de tweede methode gekozen. Hoofdstuk 9 GStreamer en OpenMAX Zoals reeds aangegeven in het hoofdstuk 5 is het GStreamer framework enorm geschikt om eenvoudig media-applicaties te bouwen. Toen ik aan dit project begon, bestond er nog geen integratie tussen GStreamer en OpenMAX. Op 22 maart 2013 werd de eerste versie van de OpenMAX GStreamer plug-in (gst-omx) vrijgegeven. Deze plug-in moest zelf nog gecompileerd worden voor elk systeem. Verschillende mensen van de Raspberry Pi foundation hebben zich dan ook gemotiveerd om de binaries te publiceren op een private repositorie. Deze plug-ins werken, maar hebben een enorm nadeel: er bestaat nog geen integratie voor de OpenMAX tunnels. Op 14 maart 2014 werd er een RFC ingediend om deze feature te integreren. Zolang deze feature niet ge¨ıntegreerd is zal GStreamer nooit dezelfde performantie halen. Elke component zal in plaats van de data rechtstreeks te zenden naar de volgende component in de GPU, de data kopi¨eren naar het CPU geheugen. Daarna zal hij dit geheugen opnieuw kopi¨eren naar het volgende component etc. De opvolging van de RFC kan gelezen worden op http://comments.gmane.org/gmane.comp.video.GStreamer.bugs/125580. Een ander probleem is het ontbreken van een RTSP-server-implementatie. Dit werd op 25 februari 2014 opgelost door een ”RTSP server sink” te implementeren voor het GStreamer framework. 9.1 Performantie Omdat het gebruik van de GStreamer OpenMAX plug-in enkele voordelen heeft, zijn er ook enkele testen gedaan omtrent de performantie. We implementeren in een terminal een eenvoudig transcoder-script zonder de resize component. gst-launch-1.0 -vvv udpsrc multicast-group=224.0.1.12 port=1234 caps="application/x-rtp, media=(string)video,encoding-name=(string)JPEG" multicast-interface=eth0 ! rtpjpegdepay ! omxmjpegdec ! omxh264enc ! 75 HOOFDSTUK 9. GSTREAMER EN OPENMAX 76 "video/x-h264,profile=baseline" ! rtph264pay ! udpsink host=192.168.2.200 port=3000 Zoals besproken in hoofdstuk 5 piped dit programma een OpenMAX-JPEG-decoder aan een OpenMax-H.264-encoder. De performantie van dit programma is gewoonweg slecht. In ons voorbeeld was het zelfs onmogelijk om het demofilmpje ”Big big bunny” te transcoden in 720p. Webcambeelden daarentegen kunnen wel getranscodeerd worden. Dit resulteert wel in een slechte delay zoals te zien is in afbeelding 9.1. Ook de framerate ligt enorm laag (Tussen de 14 en de 5 FPS) zoals te zien is in grafiek 9.2. Figure 9.1: OpenMAX: Delay van de transcoder HOOFDSTUK 9. GSTREAMER EN OPENMAX Figure 9.2: OpenMAX: Framerate van de transcoder 77 Hoofdstuk 10 Clustering In dit hoofdstuk gaan we dieper in op het tweede luik van de masterproef: de clustering. Het is de bedoeling dat de verschillende Raspberry Pi’s allemaal dezelfde invoer ontvangen en deze transcoderen naar een H.264-videostream. Alle encoders, die parallel dezelfde berekeningen doen, gebruiken wel een andere encoder bitrate. Zo is het mogelijk om een een goede stream aan te bieden aan een breed spectrum van mobiele toestellen. Zo zal een smartphone met een lage bandbreedte verbinden met een encoder met lage bitrate. Ook implementeren we de mogelijkheid om de uitvoer resolutie aan te passen. Er is geen enkele bus tussen de streamservers die genoeg bandbreedte heeft om een raw 720p signaal te kunnen verwerken. Daarom moet elke server eerst de inkomende stream MJPEG decoderen. Elke server zal dus in de eerste fase hetzelfde werk doen. 10.1 Opstelling Als opstelling is er gekozen voor een master-slave configuratie. E´en Raspberry Pi wordt geselecteerd om als master te fungeren. Alle andere RPI zijn ’slaves’. De master is verantwoordelijk voor alle inkomende RTSP-berichten. Alle clients denken dus dat zij een stream zullen ontvangen van de master, dit is niet zo. De master zelf transcodeert niet! Hij ontvangt zelf de inkomende videostream niet. In plaats daarvan kan hij ´e´en van de slaves aanspreken om de transcoding uit te voeren en deze te verzenden naar de RTSP-client. 10.2 Opstelling adaptive bitrate streaming Aangezien we de inkomende stream transcoderen naar verschillende videokwaliteiten en resoluties, is er de mogelijkheid om adaptive bitrate streaming toe te passen. Dat wil zeggen dat de 78 HOOFDSTUK 10. CLUSTERING 79 stream kwaliteit automatisch aangepast wordt indien de bandbreedte afneemt. De abstracte voorstelling is als volgt: Indien er een RTSP ’play’ commando binnenkomt op de master, dan wordt dit commando doorgegeven aan een willekeurige slave. Deze heeft dus een willekeurige bitrate. De client op zijn beurt zendt periodiek RTCP-Receiver-Report-berichten (zie hoofdstuk 4.8) naar de master. Aan de hand van deze pakketten kan de master twee conclusies nemen: - De te benodigde bandbreedte is te hoog voor de client - De bitrate kan hoger voor deze client Indien het ”lost packet counts” veld uit het RTCP-pakket verhoogt is, dan duidt dit op de eerste situatie. Indien deze stabiel blijft en het ”jitter” veld laag blijft, dan duidt dit op de tweede situatie. Als een bepaalde situatie een paar keer na elkaar vastgesteld wordt, dan kan de master beslissen om de client aan een andere slave toe te wijzen met een andere bitrate-instelling. 10.3 Implementatie adaptive bitrate streaming Voor dit project mag er geen gebruik worden gemaakt van extra apps. Daarom zijn we aangewezen op de native media player van de Android toestel om deze adaptive streaming te implementeren. Android gebruikt multimediabibliotheken: opencore en stagefright. De opencore bibliotheek werd oorspronkelijk als framework gebruikt. Vanaf android versie 2 werd deze vervangen door stagefright (Wind river (2010)). In de broncode van de opencore bibliotheek is te zien hoe de RTCP-RR-pakketten worden samengesteld. Dit is te vinden in de ”EncodeReportBlock” functie. (https://android. googlesource.com/platform/external/opencore/+/refs/tags/android-2.2.1_r1/protocols/ rtp/src/rtcp_encoder.cpp) De stagefright bibliotheek daarentegen weigert om zinnige RTCP-berichten te verzenden. Zo worden bepaalde velden met 0 gevuld in plaats van een deftige waarde. De enige juist ingevulde velden zijn het hoogste sequentienummer en de timestamp wanneer deze ontvangen is. Deze code is tevens terug te vinden op de googlesource van het framework: https:// android.googlesource.com/platform/frameworks/av/+/tools_r22%5E2/media/libstagefright/ rtsp/ARTPSource.cpp. Zowel het ’jitter’-veld als de ’cumulative’ en ’fraction lost’-velden worden niet berekend en worden gevuld met de waarde ’0’. Aangezien het merendeel van de Android toestellen dus geen nuttige RTCP-RR-pakketten verzendt, is de implementatie van deze adaptive streaming dus niet mogelijk. HOOFDSTUK 10. 10.4 CLUSTERING 80 Javascript bandwidth sensing Om toch een hulpmiddel aan te bieden, kan er een javascript-script worden uitgevoerd om de beschikbare bandbreedte en de resolutie te berekenen. Aan de hand van deze gegevens kan de beste stream gekozen worden voor het toestel. Helaas is er geen mogelijkheid om deze stream dynamisch aan te passen aangezien de javascript code inactief wordt wanneer het streamen start en de media applicatie geopend wordt. Het script downloadt vanop de client vijf keer een afbeelding en meet de gemiddelde downloadtijd. Hieruit kan afgeleid worden wat de beschikbare bandbreedte is. function startMeasuring(som, id){ var imageAddr = "picture.jpg" + "?n=" + Math.random(); var startTime, endTime; var downloadSize = 420374; var download = new Image(); download.onload = function () { endTime = (new Date()).getTime(); var duration=(endTime - startTime) / 1000; som=som+((downloadSize*8) / duration / 1024 / 1024 ); id++; if (id<5){ startMeasuring(som,id); }else{ doneMeasuring((som/5).toFixed(2)); } } startTime = (new Date()).getTime(); download.src = imageAddr; } function doneMeasuring(bandwidth){ document.getElementById("wait").innerHTML="Bandwidth:" + bandwidth + "Mbps <br \> } Deze code plaatst op het scherm de beschikbare bandbreedte en de schermresolutie. 10.5 Implementatie protocol In deze paragraaf gaan we dieper in op de communicatie tussen de master en de slaves. HOOFDSTUK 10. CLUSTERING 81 Omdat er geen pakketten mogen verloren gaan, wordt er geopteerd om TCP-pakketten te gebruiken. En omdat het aantal slaves onbeperkt is, zal de master de TCP-server zijn en de slaves de TCP-clients. Zo is het mogelijk om on the fly transcoders bij te plaatsen. Het gebruikte protocol is gebaseerd op het HTTP-protocol. Zo is er altijd ´e´en zijde die een request zendt, en de andere reageert met een antwoord. Ook worden de pakketten gevuld met leesbare tekst in plaats van binaire informatie. Tevens wordt het newline karakter als delimiter gebruikt om verschillende velden aan te duiden. De twee grootste verschillen in vergelijking met het HTTP-protocol zijn dat de connecties open blijven gedurende de volledige sessie en dat een request verzonden wordt door de server in plaats van door de client. Elk bericht start met een type aanduiding. Dit type is exact 7 karakters lang. In tegenstelling tot alle andere velden is er geen newline karakter nodig na het aanduiden van de functie. De communicatie begint met het registeren van de slave aan de master. Dit gebeurt door de TCP-connectie op te starten. De master zal de uitvoerparameters voor deze slave ophalen en zendt deze naar de slave. De antwoord dat de client zal terugzenden is zijn SPS en PPS parameter. De master heeft deze twee waarden van elke slave nodig aangezien deze moeten vermeld worden in de SDP (zie hoofdstuk 4.7) die hij verzendt naar elke RTSP-client. Master->slave: welcome{WIDTH}\n {HEIGHT}\n {BITRATE}\n Slave->Master: {SPS}\n {PPS}\n Hierna worden er geen berichten meer verzonden totdat de RTSP-server een ’play’ commando ontvangt van een RTSP-client. Dit is het teken om aan een slave te melden dat hij een nieuwe RTP-client heeft. Dit pakket gebruikt newclnt als berichttype. De master zal als velden het IP-adres en de poort meegeven van de client naar waar de slave zijn uitgaande videostream moet verzenden. Als de slave de velden begrijpt en als de slave kan unicasten naar dit adres, dan zal hij antwoorden met een ’ok’ pakket. master->slave: newclnt{PORT}\n {IPADDRESS}\n {SESSIONID}\n Slave->master: ok\n HOOFDSTUK 10. CLUSTERING 82 Het derde type bericht dat kan verzonden worden is het delclnt bericht. Dit wordt verzonden vanaf de master indien deze een RTSP ”teardown” bericht krijgt van ´e´en van zijn clients. Indien een slave een ”delclnt” bericht ontvangt, dan zal hij zoeken naar de thread die verantwoordelijk is om deze videostream te verzenden naar die bepaalde client. Indien hij deze vindt dan zal hij die stoppen. Zowel de master als de slaves markeren een videosessie aan de hand van een sessionID. Deze wordt ook gebruikt bij de RTSP-berichten. Dit is de reden waarom er alleen een session-veld moet worden meegegeven bij een ”delclnt”-bericht. master->slave: delclnt{SESSIONID}\n slave->master: ok\n Hoofdstuk 11 Beoordeling van het systeem In dit hoofdstuk gaan we dieper in op de positieve en negatieve eigenschappen van het systeem. 11.1 Delay van het netwerk Alhoewel de transcoder enorm snel werkt, is toch de totale delay groter dan verwacht. Dit komt omdat de beelden die het systeem verlaten nog worden verzonden over een netwerk. Elke keer er een router, firewall of access point wordt overbrugt zal de delay merkelijk toenemen. Dit is helaas een factor waar dit project geen rekening mee kan houden. 11.2 Bottleneck 1: ethernet poort van de Raspberry Pi De Raspberry Pi bevat een 10/100 Mbps full duplex ethernet poort. Deze wordt enorm belast indien het aantal clients toeneemt. Daarom werd er een benchmark op die poort uitgevoerd. Alle testen werden gedaan met het iperf commando. Alle metingen op de Rasbian SoCs haalden een upload van 61 of 62 Mbps. Wat dus veel lager is dan de theoretische 100Mbps. De archlinux-distribute behaalde ook een upload van 64 Mbps. Deze resultaten worden bevestigd door de metingen die gedaan zijn door de Raspberry community. Hun metingen zijn te vinden op http://elinux.org/RPi_Performance#NIC. 83 HOOFDSTUK 11. 11.3 BEOORDELING VAN HET SYSTEEM 84 Bottleneck 2: CPU De grootste bottleneck in dit systeem is de CPU. De Raspberry Pi heeft slechts ´e´en cpu van 700Mhz. Elke thread maakt gebruik van wachtlussen die pollen tot het volgende frame klaar is. Deze lussen voeren de ”thread-yield” functie uit. Deze functie geeft zijn CPU-tijd door aan de volgende thread. Dit heeft wel als nadeel dat de CPU constant 100% belast is. Om te meten hoeveel CPU er ongeveer gebruikt wordt veranderen we alle yield functies door ’usleep(1)’. De resultaten van deze metingen zijn te zien in grafiek 11.1. Deze grafiek toont de belasting van de cpu aan de hand van het aantal 720p clients. Hieruit kunnen we besluiten dat vanaf drie clients de CPU volledig gesatureerd is. Dit wil niet zeggen dat er geen extra clients zich kunnen aanmelden, maar wel dat er geen zekerheid meer is dat elke client een vloeiende stream zal ontvangen. In praktijk kunnen er zich zo’n tien 720p clients aanmelden voordat er noemenswaardige problemen optreden. Figure 11.1: CPU idle Deze bottleneck kan eventueel verbeterd worden door gebruik te maken van interrupts in plaats van polling. Toch zal dit geen grote performantiewinsten opleveren. De meeste cpu instructies worden namelijk gebruikt bij het kopi¨eren van geheugenblokken. De RTPClient thread kopieert alle inkomende pakketten naar de eerste ringbuffer. Een 720p-RTP-JPEG-stream gebruikt ongeveer 20 tot 50Mb per seconde. Alle data wordt gekopieerd met een memcpy. De transcoder decapsuleerd deze data uit de ringbuffer en kopieert ongeveer evenveel data naar het GPU-geheugen. Hier hebben we dus opnieuw een memory copy functie die 20 tot 50Mbps verbruikt. De ILCS HOST thread handelt alle callbacks van de OpenMAX ILClient bibliotheek af en kopieert de H.264-data uit de GPU naar de tweede ringbuffer. Maximaal kan er een HOOFDSTUK 11. BEOORDELING VAN HET SYSTEEM 85 dataoverdracht zijn van 20Mb per seconde. Voor elke client is er ook nog een thread die de data fragmenteert en RTP encapsuleert. Ook hier kan er maximum 20Mb per seconde per thread verzonden worden. Hieruit kunnen we concluderen dat het gebruik van interrups slechts een kleine performantie winst kan leveren. Hoofdstuk 12 Conclusie Als conclusie kunnen we stellen dat de Raspberry Pi ongeacht zijn lage kost en lage performantie toch bruikbaar is om te gebruiken als transcoder. Aan de hand van een demo kan aangetoond worden dat het oorspronkelijk concept ge¨ımplementeerd kan worden. Qua transcoding toont de Raspberry Pi dat hij zijn mannetje kan staan. Het kan perfect een 720p-JPEG-stream transcoderen naar H.264 met een latency van ten minste 33ms. Toch heeft de hardware een paar beperkingen waar er rekening mee gehouden moet worden. De voornaamste bottlenecks zijn de lage bandbreedte van de NIC en de trage CPU. Deze zorgen ervoor dat het aantal HD clients beperkt is. Indien er minder hoge kwaliteit eisen worden gesteld is de Raspberry Pi perfect om te streamen naar meerdere clients. Ook kan de Raspberry Pi gebruikt worden als lowcost HD transcoder voor ´e´en client. Het clusteren van deze bordjes bleek geen probleem te zijn. Het implementeren van een adaptive streaming was helaas niet haalbaar door een technisch mankement bij het Android multimedia framework. Het is wel mogelijk om de benodigde bitstream manueel te selecteren. Ook is het mogelijk om de cluster te gebruiken als een load-balanced cluster. Dan streamen alle transcoders met dezelfde encoder properties. Het aantal toegelaten clients is dan afhankelijk van het aantal SoCs in de cluster en de gewenste kwaliteit. Dit systeem is ook dynamisch uitbreidbaar. Als eindresultaat kunnen we besluiten dat het mogelijk is om met een cluster van 5 Raspberry Pi’s zo’n 40 High Definition clients te bedienen van een hoge kwaliteit live videostream met een minimale delay. Indien we de kwaliteit of resolutie verlagen dan kan dit aantal met gemak verhoogd worden. 86 Bibliografie Apple Inc. (Geciteerd april 2014). Http live streaming. https://developer.apple.com/ streaming/. Apple Inc. (2013). Http live streaming draft. draft-pantos-http-live-streaming-12. http://tools.ietf.org/html/ Broadcom (2014). Vmcs-x openmax il components. http://htmlpreview.github. io/?https://github.com/raspberrypi/firmware/blob/master/documentation/ ilcomponents/index.html. CNXSoft (2013). ”raspberry pi now has experimental support for vp6, vp8, mjpeg and ogg theora video codecs”. URL "http://www.cnx-software.com/2013/01/26/ raspberry-pi-now-has-experimental-support-for-vp6-vp8-mjpeg-and-ogg-theora-video-codecs ". codesequoia (Geciteerd april 2014). H.264 stream structure. wordpress.com/2009/10/18/h-264-stream-structure/. http://codesequoia. Fourcc (Geciteerd april 2014). Yuv pixel formats. http://www.fourcc.org/yuv.php. Gstreamer OpenMax plug-in team (Geciteerd april 2014). File gstomx.conf. URL "http: //cgit.freedesktop.org/gstreamer/gst-omx/tree/config/rpi/gstomx.conf". HCL Technologies Ltd (Geciteerd april 2014). High level synthesis of jpeg application engine. http://www.design-reuse.com/articles/17954/jpeg-application-engine.html. International telecomunication union (2010). Advanced video coding for generic audiovisual services. h264. ITU-T T-REC-H.264-201003-I, TELECOMMUNICATION STANDARDIZATION SECTOR OF ITU, Geneva, Switzerland. itu-t81 (1993). Information technology digital compression and coding of continuous-tone still images requirements and guidelines. https://github.com/gitah/jpeg-codec/blob/ master/itu-t81.pdf. Maemo (Geciteerd april 2014). Maemo api: enum formattype. //maemo.org/api_refs/5.0/beta/libomxil-bellagio/group__iv.html# gc3ba1425e394263a432445a47baf90ca. K. Merckx (2009). Digitale Video. 978-90-456-4777-7. Easy computing. 87 http: BIBLIOGRAFIE 88 Microsoft (2011). Algemene informatie over pixel-indelingen. http://support.microsoft. com/kb/294880/nl. J. Ozer (2009). URL "http://www.streaminglearningcenter.com/articles/ producing-h264-video-for-flash-an-overview.html?page=2". R. R. Panko (2008). Business data networks and telecommunications. 978-0-13-500939-0. Pearson education, 7the edition edition. Pixeltool (Geciteerd april 2014). Dynamically adjust encoder parameters to achieve a target bitrate. URL "http://www.pixeltools.com/rate_control_paper.html". ppumkin (2013). Benchmarking raspberry pi. http://raspberrypi.stackexchange.com/ questions/1262/what-is-the-highest-performing-hardware-configuration. RFC (1998a). Real time streaming protocol (rtsp). RFC rfc2326, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/html/rfc2326". RFC (1998b). Rtp payload format for jpeg-compressed video. RFC rfc2435, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/ html/rfc2435". RFC (1999). Ipv6 jumbograms. RFC rfc2675, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/html/rfc2675". RFC (2003a). Rtp: A transport protocol for real-time applications. RFC rfc3550, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/ html/rfc3550". RFC (2003b). Rtp: A transport protocol for real-time applications. RFC rfc3550, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/ html/rfc3550". RFC (2006). Sdp: Session description protocol. RFC rfc4566, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/html/rfc4566". RFC (2011). Rtp payload format for h.264 video. RFC rfc6184, Internet Engineering Task Force, Californi¨e, Verenigde Staten. URL "http://tools.ietf.org/html/rfc6184". The Khronos Group Inc (2005). Openmax integration layer application programming interface specification. Khronos OMX IL 1.0, The Khronos Group Inc, Beaverton,USA. URL "http: //www.khronos.org/files/openmax_il_spec_1_0.pdf". Wind river (2010). ”android multimedia framework overview”. URL "http://lists.kde. org/?l=necessitas-devel&m=133612475802867&q=p3.
© Copyright 2024 ExpyDoc