Java Media Framework - Universität Paderborn

Werbung
Universität Paderborn
Fachbereich 17 – Mathematik/Informatik
Arbeitsgruppe Softwaretechnik
Prof. Dr. W. Schäfer
Warburger Str. 100
33098 Paderborn
Java Media Framework
Seminarausarbeitung
im Rahmen der Projektgruppe
Entwicklung eines verteilten
multimedia Systems mit Hilfe
von Design Pattern
im Projekt
von
Philipp Hoven
Peter-Hille-Weg 11 A5.2
33098 Paderborn
Paderborn, August 2001
Inhaltsverzeichnis
1 Einführung
2
1.1 Motivation
2
1.2 Java Media APIs
3
2 Java Media Framework
4
2.1 Architektur
4
2.1.1 Zeitmodell
5
2.1.2 Manager
5
2.1.3 Ereignismodell
6
2.1.4 Datenmodell
7
2.1.5 Controls, Controller & ControllerEvents
9
2.2 Präsentation
10
2.3 Verarbeitung
12
2.4 Aufnahme
15
2.5 Erweiterung
16
3 Real-Time Transfer Protcol
17
4 Literaturverzeichnis
19
1
1 Einführung
1.1 Motivation
Das Java Media Framework ermöglicht Javaprogrammen und Applets die Aufnahme, Verarbeitung und
Wiedergabe von Multimediadaten, ohne dadurch die Plattformunabhängigkeit aufzugeben. Die erste
Version, auch Java Media Player genannt, beschränkte sich allein auf das Abspielen von Multimediadaten.
Erst die zweite Version ermöglicht die Aufnahme und Bearbeitung dieser Daten. Außerdem erlaubt sie über
Plug-Ins eine Erweiterung und Anpassung an die eigenen Anforderungen.
In der aktuellen Version unterstützt JMF folgende Videoformate: Cinepak, MPEG-1, H.261, H.263, JPEG,
Indeo.
Unterstützte Audioformate sind: PCM, Mu-Law, ADPCM (DVI, IMA4), MPEG-1, MPEG Layer 3, GSM, G.723.
Diese Formate unterscheiden sich sowohl im Hinblick auf die Qualität der Daten, als auch durch die
Anforderungen an CPU und Bandbreite. So sparen komprimierte Daten wie MPEG-1 Bandbreite, müssen
dafür aber aufwändig von der CPU bearbeitet werden. Des weiteren leidet die Qualität unter der
Kompression.
1.2 Java Media APIs
Das Java Media Framework ist Teil der Java Media APIs. Neben dem JMF gibt noch folgende APIs:
Java Sound API
Das Java Sound API ist eine umfassende Audioerweiterung für Java, die sowohl plattformunabhängig als
auch weitgehend geräteunabhängig ist. Erreicht wird dies durch eine fast vollständige Emulation von
Soundkarten, die lediglich auf die Hardware eines Digital-Analog-Wandlers angewiesen ist. Neben den
Standardfunktionen wie dem Abspielen von 8- und 16bit-Samples, unterstützt das API auch Softwaremixing
in bis zu 32 Kanälen sowie eine Software-Wavetable im ‘General MIDI’-Standard.
Java 2D API
Die Klassen des Java 2D API sind Erweiterungen der Packages java.awt und java.awt.image. Es beinhaltet
Möglichkeiten zur Bildbearbeitung. Dabei sind auch Operationen wie geometrische Verformung, Verwischen
und Schärfen möglich. Auch erlaubt es Farbkorrekturen und eine Anpassung des Farbraums.
2
Java 3D API
Die Java 3D API bietet die Möglichkeit dreidimensionale geometrische Objekte zu erstellen und zu
manipulieren. Die Implementierung ist teilweise an plattformspezifische APIs wie Direct3D[3] oder
OpenGL[4] angelehnt. Die Java 3D API beinhaltet auch eine Erweiterung für das Java Sound API. So sind
unter anderem Effekte wie Pseudo-Raumklang, Hall und einfache Positionierung der Klangquelle im
virtuellen Raum möglich.
Java Advanced Imaging API
Das Java Advanced Imaging API bietet Funktionen zur Bildbearbeitung, die über die Fähigkeiten von Java
2D hinausgehen und diese erweitern. Die Fähigkeiten reichen von Bildteilungsfunktionen bis zur
Frequenzmanipulation der Bilder. Die API wurde auf spezielle Bedürfnisse zugeschnitten. So können
beispielsweise in der Medizin zu dunkle Röntgenbilder nachbearbeitet werden.
Java Telephony API
JTAPI erweitert Java um die Möglichkeit telefonbasierte Anwendungen zu realisieren. Neben einfachen
Grundfunktionen wie Abheben, Auflegen und Wählen. Außer diesem Kern gibt es Erweiterungen, die
speziellere Methoden implementieren (z.B. Programmierung von Telefonzentralen). Das Java Telephony
API kann auch als Interface zu bereits existierenden Telefon-APIs (z.B. SunXTL, TSAPI, und TAPI)
verwendet werden.
Java Shared Data Toolkit API
Das Java Shared Data Toolkit API erlaubt es, verteilte Anwendungen mit gemeinsamen Daten wie schwarze
Bretter zu entwickeln. Auch können Daten für Gruppenprojekte bereitgestellt und verwaltet werden.
Java Speech API
Das Java Speech API ermöglicht Spracherkennung sowie Sprachsynthese unter Java. Die
Spracherkennung wird mit Hilfe von Sprachgrammatiken realisiert.
Dabei gibt es zum einen die regelbasierte Grammatik, welche die erkannten Daten auf ihre Richtigkeit
überprüft. Zum anderen diktatoptimierte Grammatik, die auf die Eigenheiten gesprochener Sprache
zugeschnitten ist. Sie werden mit Java Speech Grammar Format beschrieben.
Die Sprachsynthese bietet Kontrolle über die Sprachausgabe. Als Format für die Sprachattribute wird die
Java Synthesis Markup Language (JSML) verwendet. Dabei handelt es sich um eine HTML-ähnliche
Sprache, die Attribute wie Ausprache, Betonung, Satzmelodie und Pausen beschreibt.
3
Java Animation API
Das Java Animation API besteht aus den Hauptteilen Sprites und Skipte. Sprites sind die grundlegenden
Elemente einer Animation. Das Sprite-Paket enthält Mechanismen zur Kollisionsabfrage, Verkettung von
Sprites, einfache Effekte und Überblendungen. Java 2D und das Abstract Windows Toolkit (AWT)
übernehmen die low-level-Funktionen des Sprite-Paketes. Skripte beschreiben das Verhalten der Sprites in
vorbestimmten Situationen. Sie können auch konkrete Zeitpläne enthalten. Die Java Animation API
verwendet zur Synchronisation von Spriteaktionen, Skriptabläufen und Soundsignalen das Java Media
Framework. Dieses stellt auch einen Player für Skripte zur Verfügung.
2. Java Media Framework
2.1 Architektur
Die Architektur auf der obersten Ebene von JMF ist vergleichbar mit der eines Videorekorders. Eine Kamera
nimmt den Film, Audio- und Videodaten gebündelt, auf. Der Videorekorder spielt diese ab und leitet die
aufbereiteten Daten an Ausgabegeräte wie Fernseher und Lautsprecher weiter. Dabei ist eine Manipulation
der Daten möglich: Zeitlupe oder Umformung in ein anderes Videoformat (NTSC statt PAL).
Genau dieses Modell nutzt auch das Java Media Framework API. Zur Aufnahme und Wiedergabe von
Audio- und Videodaten benötigt JMF entsprechende Ein- und Ausgabegeräte wie Mikophon, Kamera,
Bildschirm und Lautsprecher. Datenquellen und Player sind integrale Bestandteile der high-level API, welche
die Aufnahme, Wiedergabe und Bearbeitung der Daten verwaltet. JMF stellt jedoch auch eine low-level API
zur Verfügung, die das Einfügen von eigenen Erweiterungen und Komponenten zur Datenbearbeitung
erlaubt. Diese Trennung in verschiedene Schichten hält die einzelnen Schichten übersichtlich, erleichtert die
Benutzung des APIs und erhält die Flexibilität und Erweiterbarkeit des Gesamtsystems.
4
2.1.1 Zeitmodell
JMF verwendet ein Zeitmodell mit einer Auflösung im Nanosekundenbereich. Alle Klassen, die das
Zeitmodell von JMF unterstützen, müssen das Interface Clock implementieren. Das Interface Clock stellt die
Basisoperationen zur Synchronisation und Zeitmessung zur Verfügung, die benötigt werden, um die
Wiedergabe des Datenstroms zu kontrollieren. Clock benötigt für seine Arbeit eine unabhängige
Referenzzeit. Diese wird von TimeBase-Objekten zur Verfügung gestellt, welche die Zeit ihrerseits von der
Systemzeit beziehen. Die Zeit von TimeBase-Zeit kann weder angehalten noch zurückgesetzt werden; sie
läuft unabhängig immer weiter.
Um der aktuellen Zeitpunkt im Datenstrom bestimmen zu können, verwendet Clock die sogenannte „media
time“. Diese setzt sich aus mehreren Komponenten zusammen:
Die TimeBaseStartTime gibt die absolute Zeit – laut TimeBase – zu Beginn der Wiedergabe an.
Die „media start time“ gibt die Position des Punktes im Datenstrom an, an dem die Wiedergabe beginnt.
Die Abspielrate gibt an mit welcher Geschwindigkeit sich Clock durch den Datenstrom bewegt. Dabei stellt
1.0 die normale Abspielrate dar. Alles darüber hinaus entspricht schnellem Vorlauf, so zeigt die Abspielrate
2.0 eine doppelte Geschwindigkeit an. Negative Wert deuten auf ein Rückwärtsabspielen hin.
Die aktuelle „media time“ wird während des Abspielens wie folgt errechnet:
MediaTime = MediaStartTime + Abspielrate * (TimeBaseTime – TimeBaseStartTime)
Wird die Wiedergabe angehalten, so bleibt auch die „media time“ stehen. Die TimeBase-Zeit dagegen läuft
weiter. Wenn die Wiedergabe weiterläuft, wird die „media time“ an die TimeBase-Zeit angepasst.
Ist die Länge eines Datenstroms bekannt, so implementieren die Medien-Objekte das Interface Duration. Sie
sind dann in der Lage, diese Information bereit zu stellen.
2.1.2 Manager
Das Java Media Framework API besteht im wesentlichen aus einer Reihe von Interfaces, die das Verhalten
und die Interaktion von Objekten festlegen, die zur Aufnahme, Bearbeitung und Wiedergabe von
zeitbasierten Medien dienen.
Zwischen den Interfaces und den sie implementierenden Klassen liegt eine Schicht aus sogenannten
Managern. Diese ermöglichen eine einfache und nahtlose Integration von eigenen Entwicklungen.
5
Das Java Media Framework kennt vier verschiedene Managertypen:
1. Manager: Dient zur Erzeugung von Objekten der Klassen Player, Processors, DataSources und
DataSinks.
2. PackageManager: Listet alle Packages auf, in denen JMF-Klassen enthalten sind.
3. CaptureDeviceManager: Enthält eine Liste aller verfügbaren Aufnahmegeräte.
4. Plug-InManager: Verwaltet eine Liste aller verfügbaren JMF Plug-Ins, wie beispielsweise
(De-)Multiplexer, Codecs, Effekte und Renderer.
Eigene Implementierungen, welche den Funktionsumfang von JMF erweitern, können durch Anmelden beim
Plug-InManager zur Verfügung gestellt werde. Eigene Packages werden beim PackageManager
angemeldet.
2.1.3 Ereignismodell
Für die Arbeit mit zeitbasierten Daten ist es wichtig, dass die Anwendung über Ereignisse informiert wird, um
unmittelbar reagieren zu können. Zu diesem Zweck lösen JMF-Objekte MediaEvents aus. MediaEvent
besitzt mehrere Unterklassen, um verschiedene Ereignisse wie AudioDeviceUnavailableEvent,
DataSinkEvent oder PrefetchCompleteEvent erkennen zu können. Für jeden Typ von JMF-Objekten, die
MediaEvents auslösen können, hält das JMF ein entsprechendes Listener-Interface bereit. Sobald sich eine
Eigenschaft der überwachten Objekte ändert, wird ein entsprechendes MediaEvent ausgelöst. Diese
funktioniert analog zum Listener-Mechanismus in Swing oder AWT.
Die Listener werden durch den Aufruf der Methode addListener des zu überwachenden Objektes an dieses
gebunden.
6
2.1.4 Datenmodell
DataSource-Objekte kapseln alle relevanten Informationen über eine Datenquelle, wie den Ort der Daten
und das verwendete Protokoll. Wurden sie einmal für eine bestimmte Quelle erstellt, können sie nicht für
eine andere verwendet werden. Datenquellen werden über eine URL oder einen JMF MediaLocator
identifiziert. Der MediaLocator kann von einer URL abgeleitet werden und lässt sich auch ohne ein
spezifisches Protokoll anlegen.
DataSource-Objekte verwalten eine Menge von SourceStream-Objekten. Im Normalfall nutzen Datenquellen
ein Byte-Array zur Datenübertragung; aber es existiert auch eine gepufferte Variante, bei der Buffer-Objekte
für den Transfer benutzt werden.
JMF unterscheidet zwischen sogenannten Push- und Pull-Datenquellen.
Bei den Push-Datenquellen wird die Datenübertragung serverseitig initialisiert und überwacht. Zu den PushDatenquellen zählen neben broadcast und mulicast-Medien auch Video-on-Demand (VoD). Ein Protokoll für
broadcast-Medien ist das Real-time Transport Protocol (RTP). JMF kennt Push-Datenquellen sowohl
gepuffert als auch ungepuffert.
Die sogenannten Pull-Datenquellen werden vom Client initialisiert und überwacht. Verbreitete Protokolle für
diesen Bereich sich HTTP und FILE. Auch hier existieren zwei Varianten: Gepuffert und Ungepuffert.
Spezielle Datenquellen
Zur einfacheren Handhabung von Datenquellen bietet JMF noch zwei besondere Typen an: Kopierbare
Datenquellen (cloneable data sources) und Gemischte Datenquellen (merging data sources).
7
Kopierbare Datenquellen
Durch das Konzept der “cloneable data sources“ können Kopien einer Datenquelle – sowohl push- als auch
pull-Datenquelle – erzeugt werden. Um dies zu erreichen wird die Datenquelle durch den Aufruf von
Manager.createCloneableDataSource gekapselt werden. Die Kommunikation sollte dann nicht mit
ursprüngliche Datenquelle erfolgen, sondern nur noch mit der “cloneable DataSource“ und den Kopien.
Kopierbare Datenquellen müssen die Methode createClone vom Interface SourceCloneable implementieren.
Durch den Aufruf dieser Methode kann eine beliebige Anzahl von Kopien erzeugt werden.
Die Kopien können durch die kopierbare Datenquelle kontrolliert werden. Alle Befehle an diese werden an
die Kopien weitergereicht. Die Eigenschaften der Kopien können sich von denen des Originals
unterscheiden.
Gemischte Datenquellen
Eine Gemischte Datenquelle kann genutzt werden um mehrere Datenquellen zu einer einzigen zu
verschmelzen. Auf diese Weise können mehrere Datenquellen gleichzeitig kontrolliert werden. Wird für die
gemischte Datenquelle start oder stop aufgerufen, so wird dies an die gekapselten Datenquellen
weitergeleitet.
Um mehrere Datenquellen zu verschmelzen muss die Methode Manager.createMergingDataSource
aufgerufen werden. Die zu verschmelzenden Datenquellen werden dabei in Form eines Array übergeben.
Damit dies funktioniert, müssen dabei alle vom selben Typ sein: push- und pull-Datenquellen können nicht
gemischt werden. Die Länge der gemischten Datenquelle entspricht dem Maximum der Längen der
verwendeten Datenquellen. Der Datentyp der gemischten Datenquelle ist application/mixed-media.
Formate
Format-Objekte kapseln genaue Informationen über ein Medienformat. Sie beinhalten jedoch keine Zeitoder Kodierungsspezifischen Informationen, sondern nur den Namen der Kodierung und benötigte Daten.
Die wichtigsten Unterklassen von Format sind AudioFormat und VideoFormat. AudioFormat beschreibt
Attribute wie Samplerate, Bits pro Sample und Anzahl der Kanäle. Die Klasse VideoFormat selbst hat
zahlreiche Unterkassen, die ihrerseits Formate wie RGB, YUV oder H261 beschreiben.
Ändert sich während der Übertragung der Daten das Format, so wird ein FormatChangeEvent ausgelöst, um
die Anwendung darüber zu benachrichtigen.
Datensenke (Datasink)
MediaHandler ist das grundlegende Interface, wenn es um das Lesen und Verwalten von Daten aus einer
Datenquelle geht. Neben Player und Processor ist DataSink ein weiteres wichtiges Unterinterface. DataSink
ist das grundlegende Interface für Objekte, die Mediendaten aus einer Datenquelle lesen und an ein
bestimmtes Ziel weiterleiten. Diese wird durch einen MediaLocator bestimmt. Die Daten können in eine Datei
geschrieben werden oder über ein Netzwerk gesendet werden. Beim Erzeugen einer Datensenke werden
8
nur Datenquelle und Ziel in Form eines MediaLocators angegeben. Bevor eine Datensenke mit datasink.start
gestartet werden kann, muss sie mit datasink.open geöffnet werden. Dabei wird versucht auf das Ziel
zuzugreifen. Fehlschläge provozieren eine IOException. Analog dazu muss die Datensenke geschlossen
werden. Wurde diese zuvor nicht mit datasink.stop angehalten, so bricht close die Datenübertragung ab.
(siehe auch Kapitel 2.4: Speichern von Mediendaten)
2.1.5 Controls, Controller & ControllerEvents
JMF stellt das Interface Control zur Verfügung, um die Attribute von Objekten abfragen und setzen zu
können. Mit getControlComponent erhält der Benutzer eine Component (aus dem AWT-Package) und kann
an dieser die Attribute ändern. Viele JMF-Objekte implementieren das Interface Controls. Bei diesem reicht
es den Namen der Klasse oder des Interface beim Aufruf von getControl zu übergeben und man erhält ein
entsprechendes Objekt (sofern vorhanden), das die Attribute kontrolliert. Diese ist vor allem dann praktisch,
wenn zur Laufzeit nicht feststeht, ob ein bestimmtes Interface unterstützt wird.
Control besitzt zahlreiche Unterinterfaces. So kann mit Hilfe von CachingControl der Fortschritt eines
Downloads angezeigt werden oder die Lautstärke kann mit GainControl beeinflusst werden. Unterstützt ein
Player oder Prozessor diese Funktionalitäten, so kann er sie durch Implementieren der entsprechenden
Interfaces dem Benutzer zur Verfügung stellen.
9
Da diese Interfaces alle unterschiedliche Aufgaben erfüllen, reicht getControlComponent als einzige
Methode nicht aus. Deshalb haben sie eigene Methoden um den Zugriff auf ihre entsprechenden Attribute zu
ermöglichen.
Ein Player stellt die Methoden getVisualComponent und getControlPanelComponent bereit, um getrennt auf
die visuelle Komponente und die Kontrollkomponente zugreifen zu können. Es ist aber auch möglich eigene
Kontrollkomponenten zu implementieren und als ControllerListener für einen Player zu registrieren.
2.2
Präsentation (Player)
Die Präsentation im JMF wird durch Controller-Interfaces modelliert. Diese Controller bestimmen die
grundlegenden Zustände und Kontrollmechanismen für Objekte die zeitbasierte Daten aufnehmen,
verarbeiten oder darstellen. Sie definieren die einzelnen Phasen vor der eigentlichen Darstellung der Daten,
die Operationen, die in bestimmten Phasen ausgeführt werden müssen oder können. Des weiteren legen sie
die Bedingungen für die Übergänge zwischen den Zuständen fest. Controller sind in der Lage verschiedene
MediaEvents auszulösen, um über ihre inneren Zustände zu informieren. Dazu benötigen sie eine
Implementierung des ControllerListener-Interface. Die wichtigsten Controller sind Player und Prozessoren.
Player lesen die Daten aus einer Datenquelle und präsentieren diese dann per Bildschirm und Lautsprecher.
Sie bieten keinerlei Möglichkeit auf die Art der Verarbeitung und Darstellung der Daten Einfluss zu nehmen.
Allerdings stellen sie eine einfache Benutzerschnittstelle zur Verfügung. Diese beschränkt sich auf
Grundfunktionen wie das Starten und Anhalten der Wiedergabe.
Zustände/Events
Zwischen Instanzierung und Starten eines Players müssen noch bestimmte Voraussetzungen erfüllt sein.
Ein Player kennt sechs verschieden Zustände, die er annehmen kann. Dazu wurde der Stopped-Zustand,
der aus dem Clock-Interface stammt, in fünf weitere unterteilt. Dies erleichtert die Verwaltung der
Resourcen. Wird ein Player neu erzeugt, so befindet er sich im Zustand Unrealized. Er besitzt zu diesem
10
Zeitpunkt keine Informationen über die Mediendaten. Wird realize aufgerufen, so geht der Player in den
Zustand Realizing über. Hier bestimmt der Player seine Anforderungen an die benötigten Resourcen und
belegt jene, die nur einmal angefordert werden müssen. Ausgeschlossen sich allerdings exklusiv genutzte
Resourcen zur Präsentation der Daten.
Der Zwischenschritt über Realizing ist nötig, da die Aktivitäten potenziell Zeit brauchen und es durch lange
Antwortzeiten in einem Netzwerk zu Verzögerungen kommen kann. Eine Abfrage des aktuellen Zustandes
zu diesem Zeitpunkt würde dann ein falsches Ergebnis liefern, da der Player sich zwischen zwei Zuständen
befände.
Im Zustand Realized kennt der Player alle benötigten Resourcen und Informationen über den Medientyp. Da
dem Player bekannt ist, wie die Daten darzustellen sich, kann er die visuelle und Kontrollkomponente
bereitstellen. Die Verbindungen zu anderen Objekten sind aufgebaut, aber der Player blockiert keine
Resourcen, die andere Player vom Starten abhalten.
In der prefetching-Phase füllt der Player seinen Puffer und reserviert sich die exklusiv genutzten Resourcen.
Diese Phase kann mehrmals erfolgen. Dies wird nötig wenn die Position im Mediendatenstrom oder die
Abspielrate verändert wird. Dann muss der Puffer neu gefüllt und/oder seine Größe angepasst werden.
Sind diese Aktionen ausgeführt, gelangt der Player in den Zustand Prefetched. Der Player ist nun startbereit.
start überführt der Player in den Zustand Started. Die Clock des Players läuft und die “media time“ wird
berechnet.
Die TransitionEvents sind von Bedeutung, da nicht in jedem Zustand jede beliebige Methode des Players
aufgerufen werden kann.
Player erzeugen
Player werden indirekt mittels eines Managers erzeugt. Dazu dienen die Methoden createPlayer und
createProcessor. Letzter Aufruf ist zulässig, da Prozessoren die Aufgaben von Playern übernehmen können.
Der Manager benötigt zur Erzeugung eines Players – oder Prozessors – eine Datenquelle in Form einer
URL oder eines MediaLocators. Dabei muss bei Verwendung einer URL ein entsprechender
URLStreamHandler installiert sein.
11
Realized-Player erzeugen
Da auf einen neu erzeugten und damit im Zustand Unrealized befindlichen Player keine sinnvollen Methoden
angewandt werden können ist es von Vorteil, wenn der Player automatisch in den Zustand Realized
gebracht wird. Eine Möglichkeit, dies zu erreichen ist die Methode Manager.createRealizedPlayer. Diese
erzeugt ein einem einzigen Schritt einen Realized-Player, indem die den Player bis zum Erreichen dieses
Zustandes blockiert. Äquivalent dazu existiert eine Methode Manager.createRealizedProcessor. Das
Blockieren kann zu unerwünschten Nebeneffekten führen. So unterbrechen die Aufrufe von Applet.start und
Applet.stop die Konstruktion nicht.
Player erzeugen mittels ProcessorModel
Ein ProcessorModel legt die Anforderungen an Ein- und Ausgabe einer Prozessors. Der Manager erzeugt
dann allein einen entsprechenden Prozessor. Dieses ProcessorModel wird dann beim Aufruf von
Manager.createRealizedProcessor übergeben. Im nachfolgenden Beispiel ist das Ausgabeformat mit null
spezifiziert. Dies bedeutet, dass die Daten direkt an Ausgabegeräte wie Bildschirm und Lautsprecher
weitergeleitet werden. Dieser Prozessor fungiert damit als Player. Das Eingabeformat ist ein IMA4-kodierter
Stereo-Audiotrack mit einer Samplerate von 44.1 kHz und einer Samplegröße von 16 Bit.
AudioFormat afs[] = new AudioFormat[1];
afs[0] = new AudioFormat("ima4", 44100, 16, 2);
player = Manager.createRealizedProcessor(new ProcessorModel(afs, null));
Da dem Prozessor keine Datenquelle übergeben wird, sucht der Manager selbsttätig nach einem AudioAufnahmegerät. Dabei nutzt er den CaptureDeviceManager. Mittels eines ProcessorModels kann kein
weiterer Einfluss auf das Innere des Prozessors genommen werde.
2.3
Verarbeitung (Processor)
Ein Prozessor ist ein erweiterter Player, der Daten aus einer Datenquelle liest, nach benutzerdefinierten
Vorgaben bearbeitet und die Daten dann beispielsweise als neue Datenquelle zur Verfügung stellt. Dort
können sie von einem Player oder einem weiteren Prozessor aufgenommen und weiterverwendet werden.
Zustände
Neben den sechs bekannten Zuständen kennt der Prozessor noch zwei weitere. Es handelt sich dabei um
Configuring und Configured, die zwischen Unrealized und Realizing liegen. Wird ein Prozessor erzeugt,
befindet er sich im Zustand Unrealized. Durch den Aufruf von configure gelangt der Prozessor in den
Zustand Configuring. In diesem baut er die Verbindung zur Datenquelle auf, demuliplext die eingelesenen
Daten und wertet die Informationen bezüglich des Formats aus. Solange er im Zustand Configured bleibt,
12
kann die Methode getTrackControls aufgerufen werden, um die TrackControl-Objekte der einzelnen Spuren
im Datenstrom zu erhalten. Diese Objekte ermöglichen eine genau Kontrolle über die Bearbeitung der
einzelnen Daten. So kann hier unter anderem das Ausgabeformat der Daten spezifiziert werden.
Realize kann aber auch direkt im Zustand Unrealized aufgerufen werde. Dann werden Configuring und
Configured übersprungen und der Prozessor geht direkt in den Zustand Realizing über. In diesem Fall
werden die Standardwerte für die TrackControl-Objekte verwendet und es gibt keine Möglichkeit mehr diese
zu beeinflussen, außer der Prozessor wird durch Deallocate zurück in den Zustand Unrealized gezwungen.
Üblicherweise schlägt ein Aufruf der TrackControl-Objekte im Realized-Zustand fehl und es wird eine
FormatChangeException ausgelöst.
Ab hier erfolgen die Zustandswechsel analog zu denen der Player.
Stufen & Plug-Ins
Im Gegensatz zum Player kann beim Prozessor die Art der Datenmanipulation vom Entwickler oder
Benutzer – falls eine entsprechende Benutzerschnittstelle existiert – festgelegt werden. Dadurch wird die
Anwendung von Plug-Ins in Echtzeit möglich.
JMF unterscheidet fünf Typen von Plug-Ins:
Multiplexer verschmelzen mehrere Tracks zu einem einzigen Datenstrom.
Demultiplexer zerlegen einen Mediendatenstrom wie MPEG, Quicktime oder WAV in einzelne Tracks; z.B.
für Audio, Video, Midi.
Renderer stellen die Daten direkt über Bildschirm oder Lautsprecher für den Benutzer dar. Sie
repräsentieren das letzte Glied der Verarbeitungskette.
Codecs (de)kodieren Datenströme und ändern dabei das Format.
Effekte wirken auf den Inhalt der Tracks. Sie ändern jedoch nicht das Format.
13
Die Verarbeitung der Daten geschieht dabei in verschiedenen Stufen:
Zuerst wird der eingehende Datenstrom von einem Demultiplexer zerlegt. Dieses Plug-In wird automatisch
angewendet. Das erste Effekt-Plug-In könnte an dieser Stelle die Farbpalette des Videotracks ändern. Die
Transformation in das MPEG-Format übernimmt ein Codec-Plug-In. Wird ein Datenstrom von einem
unkomprimiertem Format in ein komprimiertes umgewandelt, so spricht man von kodieren. Umgekehrt wird
das dekomprimieren als Dekodieren bezeichnet. Der Datenstrom wird von einem zweiten Effekt-Plug-In
bearbeitet, das beispielsweise die Fragmente des MPEG-Formats entfernen könnte. Nun können die Tracks
mittels eines Multiplexers einem weiteren Prozessor oder Player zur Verfügung gestellt werden oder über
Renderer-Plug-Ins direkt dargestellt werden.
Die Stufen zwischen den (De)Multiplexer-Plug-Ins können natürlich auch anders aussehen. So muss nicht
zwingend eine Transformation erfolgen. Ein Prozessor kann sich dann auf inhaltliche Änderungen
beschränken.
Erstellen und Kontrollieren von Prozessoren
Es gibt mehrere Arten, um einen Prozessor zu konfigurieren:
Die einfachste Methode dazu ist ein ProcessorModel, dem nur Ein- und Ausgabeformat der Daten mitgeteilt
werden. Wird es als Parameter beim Aufruf von Manager.createRealizedProcessor verwendet, erledigt diese
Methode alles, was nötigt ist um einen entsprechenden Prozessor zu erzeugen. Durch eigene Unterklassen
kann der Einfluss auf die Formate der einzelnen Tracks vergrößert werden.
Mit Hilfe der Methode setFormat kann das Format der einzelnen Tracks festgelegt werden. Die Methode
getSupportedFormats listet alle unterstützen Formate auf.
Effekt- und Codec-Plug-Ins können mit der Methode setCodecChain bestimmt werden. Dabei werden die
Plug-Ins in einem Array vom Typ Codec übergeben. Der Prozessor versucht die Plug-Ins in der
vorgegebenen Reihenfolge in den Datenfluss einzureihen. Da Effect eine Unterklasse von Codec ist, lassen
sich hier auch Effekt-Plug-Ins verwenden.
Mittles der setRenderer-Methode kann ein Renderer-Plug-In festgelegt werden, welches die Daten dann
14
über Lautsprecher oder Lautsprecher ausgibt. Renderer hat eine Unterkasse VideoRenderer, über welche
die Daten mit Hilfe des AWT-Frameworks dargestellt werden können.
Die Prozessor-Methode setContentDescriptor spezifiziert das Ausgabeformat des gemultiplexten
Datenstroms.
2.4
Aufnahme
Zur Aufnahme von Mediendaten werden Aufnahmegeräte wie Mikrophon oder TV/Video-Karten benötigt.
Solche Geräte werden allgemein als Datenquellen bezeichnet. Jeder Art von Datenquelle ist zur Aufnahme
geeignet: PushDataSource, PushBufferDataSource, PullDataSource, und PullBufferDataSource. Mit einem
Player können die aufgenommenen Daten nur Wiedergegeben werden. Sollen sie bearbeitet oder
gespeichert werden, so ist die Verwendung eines Prozessors unumgänglich.
Um Mediendaten aufzunehmen, muss
ein Aufnahmegerät mit Hilfe des CaptureDeviceManagers gefunden werden,
ein CaptureDeviceInfo-Objekt für dieses Gerät erstellt werden,
eine Datenquelle aus dem MediaLocator erzeugt werden,
ein Player oder Prozessor erzeugt werden, der diese Datenquelle nutzt,
der Player oder Prozessor gestartet werden.
Zugriff auf Aufnahmegeräte
Alle verfügbaren Aufnahmegeräte werden bei dem sogenannten CaptureDeviceManager registriert. Der
Aufruf von CaptureDeviceManager.getDeviceList liefert einen Vektor mit CaptureDeviceInfo-Objekten der
Geräte, die das übergebene Format unterstützen. Zu jedem Gerät existiert ein solches Objekt, das Auskunft
über den Namen, den MediaLocator und die unterstützen Formate gibt. Diese erhält man durch einen Aufruf
von CaptureDeviceManager.getDevice:
CaptureDeviceInfo deviceInfo = CaptureDeviceManager.getDevice("deviceName");
Mit Hilfe des MediaLocators der in diesem CaptureDeviceInfo-Objekt enthalten ist, kann direkt ein Player
erzeugt werden:
Player player = Manager.createPlayer(deviceInfo.getLocator());
Kontrolle der Aufnahme
Aufnahmegeräte verfügen im Allgemeinen über implementierungsabhänige Attribute, welche zur Kontrolle
genutzt werden können. JMF stellt zwei Kontrollkomponenten zur Verfügung: PortControl und
15
MonitorControl. PortControl ermöglicht die Auswahl, von welchem Port die Daten aufgenommen werden
sollen. MonitorControl ermöglicht die Kontrolle über Attribute wie die Framerate. Zusätzliche
Kontrollkomponenten – wie bei allen Prozessoren – mit getControlComponent ansprechen. Auch die visuelle
Komponente und die Kontrollkomponente lassen sich wie gewohnt anzeigen.
Component controlPanel, visualComponent;
if ((controlPanel = player.getControlPanelComponent()) != null)
add(controlPanel);
if ((visualComponent = player.getVisualComponent()) != null)
add(visualComponent);
Speichern von Mediendaten
Sollen die Mediendaten in einer Datei gespeichert werden, so eignet sich dafür eine Datensenke. Man kann
diese mittels Manager.createDataSink erzeugen, wobei eine Datenquelle und ein MediaLocator als Ziel
angegeben werden. Bevor der vorgeschaltete Prozessor gestartet werden kann, muss die Datensenke
geöffnet und gestartet werden. Dann kann der Prozessor beginnen seine Ausgabe, also die Datenquelle der
Datensenke, zu produzieren. Ist die Aufnahme beendet, kann der Prozessor gestoppt und dann geschlossen
werden. Die Datensenke kann geschlossen werden, sobald eine EndOfStreamEvent ausgelöst wurde.
DataSink sink;
MediaLocator destination = new MediaLocator("file://myfile.wav");
try {
sink = Manager.createDataSink(player.getDataOutput(), destination);
sink.open();
sink.start();
}
catch (Exception) {}
2.5
Erweiterung
Plug-Ins
Bei der Implementierung eigener Plug-Ins kann man direkt auf die Mediendaten zugegreifen, die der
Prozessor bearbeiten soll.
Wird das Demuliplexer-Interface implementiert, kann man festlegen wie die einzelnen Tracks aus dem
Datenstrom herausgelöst werden sollen.
16
Durch Implementieren des Codec-Interface, kann man die Art der (De)Kodierung oder Transformation von
einem Format in ein anders bestimmen.
Die Implementierung des Multiplexer-Interfaces ermöglicht eine genaue Kontrolle darüber, wie einzelne
Tracks zu einem Strom werden.
Das Implementieren des Effect-Interface erlaubt eine beliebige Bearbeitung der Daten.
Wird das Renderer-Interface implementiert, kann man die Darstellung der Daten durch ein Ausgabegerät
festlegen.
MediaHandlers
Um dem JMF eine größere Flexibilität zu verleihen, können die Interfaces MediaHandler, Controller, Player,
Processor, DataSource, und DataSink selbst implementiert werden. Die Einschränkungen der
untergeordneten Interfaces können so umgangen werden. So könnte beispielsweise ein Controller
entstehen, der gänzlich andere zeitbasierte Medien handhaben kann. Da Player-, Processor- und
DataSource-Objekte mit Hilfe eines Managers erzeugt werden, können eigene Implementierungen nahtlos in
das JMF integriert werden.
CaptureDeviceInfo
Der Hersteller eines Aufnahmegerätes ist dafür verantwortlich, dass zu einem Gerät ein CaptureDeviceInfoObjekt existiert. Wird ein Gerät installiert, wird es mit addDevice beim CaptureDeviceManager registriert.
3
Real-Time Transfer Protocol
RTP stellt einen end-to-end-Netzwerkdienst zur Echtzeit-Übertragung von Daten zur Verfügung. RTP ist
unabhängig von Netzwerk und Transportprotokoll. Es kann sowohl in Unicast- als auch in MulticastNetzwerken eingesetzt werden. In Unicast-Netzwerken werden die Datenpakete für jedes Ziel je einmal
verschickt. In einem Multicast-Netzwerk liegt es in der Verantwortung des Netzwerkes, dass ein einmalig
verschicktes Paket seine Ziele erreicht.
RTP ermöglicht es den den Typ der übertragenen Daten zu bestimmen, die Pakete in die richtige
Reihenfolge zu bringen und Medieströme aus verschiedenen Quellen zu synchronisieren. Für RTPDatenpakete kann nicht garantiert werden, das sie in der richtigen Reihenfolge beim Empfänger ankommen
– oder überhaupt. Es liegt in der Verantwortung des Empfängers die Reihenfolge zu rekonstruieren und
fehlende Pakete zu bemerken.
RTP-Sitzungen
Eine RTP-Sitzung ist eine Menge von Anwendungen, die über RTP miteinander kommunizieren. Sie wird
17
durch eine Netzwerkadresse und ein Paar Ports identifiziert. Die verschiedenen Medietypen werden in
getrennten Sitzungen übertragen. So kann bei bei einer Videokonferenz bei Mangel an Bandbreite nur der
Audioteil empfangen werden.
Diese Sitzungen werden von einem SessionManager koordiniert. Der SessionManager ist die lokale
Repräsentation der RTP-Sitzung. Der RTCP Kontrollkanal wird ebenfalls vom SessionManager überwacht.
Das Interface definiert Methoden um RTP-Sitzungen zu initialisieren und an ihnen teilzunehmen.
RTPEvents & Listener
RTP nutzt die sogenannten RTPEvents zur Benachrichtigung über Zustände von Sitzungen und
Datenströmen. Jedes RTPEvent ist auch ein MediaEvent. RTPEvents lassen sich in vier Teilbereiche
einteilen. Jeder verfügt über entsprechende Listener.
SessionListener: Empfängt Meldungen über Änderungen der Zustände von Sitzungen.
SendStreamListener: Empfängt Meldungen über Änderungen der Zustände des übertragenen Datenstroms.
ReceiveStreamListener: Empfängt Meldungen über Änderungen der Zustände des empfangenen
Datenstroms.
RemoteListener: Empfängt Meldungen über Events von anderen Sitzungsteilnehmern.
RTP Daten
Die Datenströme innerhalb einer Sitzung liegen in Form von RTPStream-Objekten vor. Dabei werden zwei
Arten unterschieden: ReceiveStream und SendStream. Jeder Strom hat seine eigene gepufferte
Datenquelle. Für ReceiveStreams ist dies immer PushBufferDataSource.
Senden und Empfangen
Um einen einzelnen RTP-Strom zu empfangen, reicht ein Player aus. Dieser benötigt nur einen
MediaLocator, da die Koordination mehrerer Ströme entfällt. Der MediaLocator einer RTP-Sitzung hat
folgende Form:
rtp://address:port[:ssrc]/content-type/[ttl]
Sobald mehr als ein Strom empfangen werden soll, wird ein Sessionmanager zur Verwaltung benötigt.
Dieser schickt eine Benachrichtigung, sobald ein neuer Strom der Sitzung hinzugefügt wird und erzeugt pro
Strom einen entsprechenden Player.
Sollen Daten über ein Netzwerk gesendet werden, wird ein Prozessor benötigt, der die Daten aufbereitet in
dem er sie beispielsweise komprimiert. Die daraus entstehende Datenquelle wird an den Aufruf von
createSendStream übergeben. Die Datenübertragung wird durch die Methoden des SendStream-Objektes
gesteuert.
18
4 Literaturverzeichnis
[1] Sun Microsystems, Java Media Framework API Guide, 1999
http://java.sun.com/products/java-media/jmf/2.1.1/guide/
[2] Sun Microsystems, Java Media Framework API, 1999
http://java.sun.com/products/java-media/jmf/2.1.1/apidocs/
[3] Microsoft, DirectX
http://www.microsoft.com/directx/
[4] Silicon Graphics, OpenGL
http://www.sgi.com/software/opengl/
19
Herunterladen