Low cost multirate video transcoder met parallelle

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,&timestamp);
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.