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