Java Media Framework API Theoretischer Hintergrund und Anwendungen LVA: Java3D (Seminar) Thema: Java Media Framework API Autor: Daniel Birk Email: [email protected] Java3D (Seminar), Java Media Framework API Inhaltsverzeichnis: 1. Einleitung 2. Java Media Framework (JMF) API 3. High-Level Architektur und Erweiterungen 3.1 High-Level Architektur 3.1.1 Wiedergabe (engl. presentation) zeitbasierter Medien 3.1.2 Bearbeiten (engl. processing) zeitbasierter Medien 3.1.3 Aufnahme (engl. capturing) zeitbasierter Medien 3.2 Erweiterungen 4. Echtzeiterweiterung: JMF Real-Time Transport Protocol (RTP) API 4.1 Streaming-Media 4.2 Real-Time Transport Protocol 4.3 Architektur des JMF RTP API 4.3.1 Senden von Streaming-Media 4.3.2 Empfangen von Streaming-Media 5. Literaturstellen 6. Anhang Seite 2 Java3D (Seminar), Java Media Framework API 1. Einleitung Grundlage dieses Tutorials ist der Java Media Framework (JMF) API Guide v. 2.0 [1]. Zusätzliche Informationen und Hintergrundwissen stammen aus dem Entwicklerbereich der IBM Website [3]. Das Java Media Framework API erweitert die Funktionalität des Java SDK um weitreichende Multimediaeigenschaften. Es enthält Bibliotheken zum Einbinden zeitabhängiger Medien, wie Audio und Video. Das JMF API besteht seit Version 2.0 aus einer Kombination des Vorgängers, nämlich der Java Media Player API 1.0, und einer Echtzeiterweiterung, die früher unter dem Namen RTP Session Manager API verbreitet wurde. Inzwischen wurde die Version 2.1.1.b veröffentlicht. Diese Version bietet neben dem einfachen Abspielen auch das Aufnehmen und Bearbeiten verschiedener Medienformate. Aufgrund eines Lizenzstreits wurde, mit Ausnahme der Windowsplattformen, in der aktuellen Version die MP3-Funktionalität entfernt. Der zweite große Bestandteil des JMF API ist die Echtzeitfunktionalität. Mit ihr lassen sich zeitkritische Anwendungen, wie beispielsweise Audio- oder Videostreaming, in JavaApplikationen und Applets realisieren. 2. Java Media Framework (JMF) API Das JMF API ist eine komfortable Lösung im Umgang mit verschiedensten Mediendaten für die Programmiersprache Java. Dabei werden die Daten zunächst importiert, anschließend verarbeitet und wieder exportiert. Die importierten Daten können aus unterschiedlichen Quellen, wie z.B. Lesen einer Datei oder Empfang aus einem Netzwerk, stammen und werden deshalb allgemein als Input bezeichnet. In der Verarbeitungsphase können die importierten Daten komprimiert, konvertiert oder mit verschiedenen Filtern belegt werden. Der Output der Applikationen und Applets kann je nach Wunsch und Einsatzgebiet vom einfachen Darstellen auf einem Bildschirm bis hin zur Übertragung über ein Netz differieren. Abbildung 1 illustriert noch einmal den eben beschriebenen Verarbeitungsprozess. Seite 3 Java3D (Seminar), Java Media Framework API Abbildung 1 aus [1] Eine erste Unterscheidung ist die nach der dem Initiator. Je nachdem wie die Mediendaten zur Verfügung gestellt werden, unterscheidet man zwischen Push- und PullTransfer. Beim Push-Transfer dient in der Regel ein Server als Initiator, der die Daten sozusagen zum Client schiebt. Das RealTime Transport Protokoll (RTP) beispielsweise ist ein Push-Protokoll, das beim Streaming-Verfahren von Audio und Video eingesetzt wird. Ein weiteres Beispiel für Push-Transfer ist Video-On-Demand (VOD), das mit Hilfe des SGI MediaBase Protokoll realisiert wird. Im Gegensatz dazu ist beim Pull-Transfer in der Regel ein Client der Initiator, der die Mediendaten anfordert und auch kontrolliert. Beispiele hierfür sind das FILE- oder das HTTP-Protokoll. 3. High-Level Architektur und Erweiterungen 3.1 High-Level Architektur Die High-Level Architektur des JMF API bildet Komponenten ab, wie sie auch bei herkömmlichen analogen Verfahren verwendet werden. So speichert beispielsweise eine Videokamera die aufgenommen Daten auf einem Videoband, dass in einem Videorekorder über ein Fernsehgerät wiedergegeben werden kann. Die Videokamera ist in diesem Fall ein Aufnahmegerät (engl. capture device), das Videoband die Datenquelle (engl. data source) und der Videorekorder ist das Abspielgerät (engl. player). Die folgende Abbildung verdeutlicht diesen Sachverhalt. Seite 4 Java3D (Seminar), Java Media Framework API Abbildung 2 aus [1] Das JMF API ist stark an diesem Modell orientiert, so dass sich viele Begriffe in den Klassen- und Interfacebezeichnungen wieder finden. Daneben gibt es ein Zeitmodel, ein Eventmodel, ein Datenmodel und drei sog. Manager. Das Zeitmodel besteht aus einem Time Objekt und einem Clock Interface, dass alle Methoden zur Ermittlung der Zeit und zur Synchronisation definiert. Das Clock Interface bekommt seine Informationen wiederum von einer TimeBase, die meist auf der Systemzeit basiert. Das Eventmodel folgt den Regeln der JavaBeans. Ein Controller hat einen ControllerListener, der an ein Objekt gehängt werden kann. Tritt ein Event an diesem Objekt auf, so kann dies in der Methode ControllerUpdate behandelt werden. Alle Events des JMF API sind Unterklassen von MediaEvent. Das Datenmodell wird durch das DataSource Objekt repräsentiert. Dieses Objekt zeigt einerseits den Speicherort der Datenquelle an und andererseits das Protokoll und die Software, die nötig sind die Quelle abzuspielen. Weiterhin hat jede Datenquelle ein bestimmtes Format. Dadurch werden alle spezifischen Eigenheiten der einzelnen Formate vom JMF API verdeckt und man braucht sich als Entwickler nicht darum zu kümmern. Die drei Managerklassen sind Manager, CaptureDeviceManager und PlugInManager. Der Manager ist für Erstellung der Player, Processor und DataSource bzw. DataSink Objekte verantwortlich. Der CaptureDeviceManager verwaltet die verfügbaren Aufnahmegeräte, während der PlugInManager die installierten und verfügbaren PlugIns verwaltet. Seite 5 Java3D (Seminar), Java Media Framework API Dies sind die Hauptkomponenten der High-Level Architektur die nötig sind, um verschieden Medien zu präsentieren, zu bearbeiten oder aufzunehmen. Diese drei Bereiche werden im Folgenden näher vorgestellt. 3.1.1 Wiedergabe zeitbasierter Medien Das Abspielen zeitbasierter Medien geschieht mit Hilfe des Player Objekts. Der Player wird, wie oben erwähnt vom Manager erzeugt. Dies kann auf unterschiedliche Weise passieren. Zunächst ist wichtig zu verstehen, dass das Player Objekt verschiedene Phasen durchläuft, ehe es bereit ist die Quelle abzuspielen. Folgende Abbildung gibt den zeitlichen Verlauf dieser Phasen wieder. realize unrealized prefetch realizing realized prefetching RCE deallocate Stopped Started prefetched PFCE started SE deallocate, setMediaTime stop Events: RCE: RealizeCompleteEvent PFCE: PrefetchCompleteEvent SE: StopEvent Abbildung 3 aus [2] Zwei dieser Phasen haben besondere Bedeutung. Denn einige Methoden des Players können erst aufgerufen werden, wenn er sich im Zustand realized, andere wenn er sich im Zustand prefetched befindet. Eine komplette Übersicht darüber, wann welche Methoden aufgerufen werden können befindet sich im Anhang. Um nun ein Player Objekt zu erzeugen ruft man die Methode Player player = Manager.createPlayer(Parameter p); Seite 6 Java3D (Seminar), Java Media Framework API auf. Diese Methode stößt die Erzeugung aber lediglich an, überwacht jedoch nicht die einzelnen Phasen. Dafür muss man das ControllerListener Interface implementieren, dass in diesen Phasen die dazu passenden Events auslöst. Der Grund für diese Zerlegung des Erzeugungsprozesses ist, dass dieser von mehreren asynchron arbeitenden Threads übernommen wird, um nicht den Hauptthread für die Dauer der Erzeugung zu blockieren. Genau dies passiert beim Aufruf der Methode Player realizedplayer = Manager.createRealizedPlayer(Parameter p); was zu unerwünschten Ergebnissen führen kann. Dieser Ansatz hat aber den Vorteil, dass sich der Entwickler nicht um die Überwachung der Zustände kümmern muss. Folgende Abbildung zeigt einen Ausschnitt der Events, die ein Player Objekt erzeugt. Abbildung 4 aus [2] Die start() Methode des Players kann z.B. erst aufgerufen werden, wenn das RealizeCompleteEvent erzeugt wurde. Wenn der Datenstrom vor dem Abspielen gepuffert werden soll, wartet man damit bis das PrefetchCompleteEvent ausgelöst wurde. Weiterhin zeigt die Abbildung verschiedene Events, die auch durch den Benutzer erzeugt werden können. Zu nennen sind hierbei das StartEvent oder das StopByRequestEvent, die ausgelöst werden, wenn die Start- bzw. Pausetaste des Players gedrückt werden. Auch die Start- und Pausetasten müssen erst erzeugt werden. Sie gehören zu den Kontrollkomponenten eines Players. Um zu erfahren welche Komponenten ein Player Objekt unterstützt, ruft man die Methode Control[] controls = Player.getControls(); Seite 7 Java3D (Seminar), Java Media Framework API auf und erhält als Rückgabewert ein entsprechendes Array. Folgende Auflistung beschreibt die wichtigsten Komponenten, die die meisten Player unterstützen. Steuerkomponente mit Start- und Pausetaste: Component controlcomponenet = Player.getControlPanelComponent(); Anzeigebereich beim Abspielen von Videos: Component visualcomponent = Player.getVisualComponent(); Lautstärkeregler, bei Implementierung des GainControl Interfaces: Component gaincomponent = Manager.getGainControl().getControlComponent(); Der Rückgabewert ist jeweils eine AWT-Komponente, die man anschließend in einem Applet oder einer Applikation aufnehmen und darstellen kann. Der Parameter p, den die beiden Methoden zur Erzeugung der Player Objekte benötigen, kann entweder ein DataSource, ein MediaLocator oder ein URL Objekt sein, je nach Einsatzgebiet und Format. Letztendlich werden alle drei Objekte jedoch mit einer Pfadangabe erzeugt. 3.1.2 Bearbeiten zeitbasierter Medien Zum Bearbeiten von Mediendaten stellt das JMF API den Processor zur Verfügung. Seine Klasse ist von der Klasse Player abgeleitet, d.h. dass jeder Processor gleichzeitig auch ein Player ist. Genau wie das Player Objekt, durchläuft auch der Processor bei seiner Entstehung verschiedene Phasen. Bevor der Processor in die realizing Phase eintreten kann, muss er im Zustand configured sein. In dieser Phase wird für jede Spur einer Mediadatei ein sog. TrackControl Objekt generiert, mit dem verschiedene Parameter, wie z.B. das Outputformat, eingestellt werden. Ist diese Phase abgeschlossen wird ein ConfiguredCompleteEvent ausgelöst. Alle weiteren Schritte sind analog zum Player Objekt. Eine Abbildung aller Konfigurationsschritte des Processor ist im Anhang zu finden. Seite 8 Java3D (Seminar), Java Media Framework API Die genauen Konfigurationsschritte sind davon abhängig, was man mit dem Processor machen möchte. Nachstehende Auflistung gibt Aufschluss über die verschiedenen Möglichkeiten: a) Erzeugung des Processors mit Hilfe eines ProcessorModels b) Verwendung eines Plug-Ins (Codec und/oder Renderer) c) Ausgabeformat festlegen zu a) Erzeugung des Processors mit Hilfe eines ProcessorModels Bei der Erzeugung mit Hilfe eines ProcessorModels wird die schon von Player bekannte Methode aufgerufen, die einen „fertigen“ Processor erzeugt. Processor p = Manager.createRealizedProcessor(ProcessorModel pm); Um ein ProcessorModel zu erzeugen, braucht man eine Datenquelle, ein Array, das die für diese Quelle verfügbaren Formate enthält und einen ContentDescriptor, der den Typ der Zieldatei beschreibt. zu b) Verwendung eines Plug-Ins Jede Spur des Eingangsdatenstroms kann mit Hilfe eines installierten Plug-Ins entsprechend manipuliert werden. Dafür lässt man sich vom PlugInManager eine Liste aller installierten Plug-Ins generieren und ruft anschließend eine der Methoden setCodecChain(Condec[] c) oder setRenderer(Renderer r) für die jeweilige Spur, bzw. das jeweilige TrackControl Objekt auf. Auf diese Weise lässt sich z.B. das Audiosignal eines Videos separat vom eigentlichen Bildsignal in einem Arbeitsschritt bearbeiten. zu c) Ausgabeformat festlegen Um das Ausgabeformat einer Spur festzulegen ruft man trackcontrol.setFormat(Format f) am gewünschten TrackControl Objekt auf. Die Methode Format[] formats = trackcontrol.getSupportedFormats(); Seite 9 Java3D (Seminar), Java Media Framework API liefert ein Array aller verfügbaren Formate zurück. Außerdem kann man neben dem Format auch den Typ der Datei festlegen. Dies erreicht man mit den Methoden ContentDescriptor[] cd = processor.getSupportedContentDescriptors() und Processor.setContentDescriptor(ContentDescriptor cd), die am Processor Objekt aufgerufen werden. Hierbei muss beachtet werden, dass die Formate der einzelnen Spuren mit dem Typ der Datei, also dem ContentDescriptor kompatibel sind. 3.1.3 Aufnahme zeitbasierter Medien Das JMF API hat neben dem Abspielen und Bearbeiten von Mediendaten auch die Möglichkeit solche Daten von einem analogen Gerät, wie z.B. einem Mikrophon oder einer Videokamera, aufzunehmen. Dazu benötigt man die dritte Managerklasse, den CaptureDeviceManager. Dieser generiert ein CaptureDeviceInfo Objekt von der angegebenen Aufnahmequelle. Wenn der genaue Name der Quelle nicht bekannt ist, kann der CaptureDeviceManager eine Liste aller verfügbaren Quellen erstellen. Dabei muss lediglich das Format angegeben wie der Eingabestrom digitalisiert werden soll. Anschließend erzeugt man mit Hilfe des CaptureDeviceInfo Objektes ein MediaLoacator Objekt und mit diesem ein Player oder Processor Objekt. Folgende Prorammzeilen fassen diese Schritte noch einmal zusammen. CaptureDeviceInfo ci = CaptureDeviceManager.getDevice(„deviceName“); Alternativ: Vector v = CaptureDeviceManager.getDeviceList(new AudioFormat("linear", 44100, 16, 2)); CaptureDeviceInfo ci = (CaptureDeviceInfo)v.elementAt(1); Player/Processor p = Manager.createPlayer/Processor(ci.getLoacator()); Mit dem Player kann man den digitalisierten Eingabestrom natürlich nur wiedergeben. Will man die Daten jedoch weiterverarbeiten, wie z.B. komprimieren oder in eine Datei speichern, benötigt man immer das Processor Objekt. Nachdem der Processor erzeugt wurde lassen sich alle Manipulationen durchführen, die unter 3.1.2 Bearbeiten zeitbasierter Medien, angesprochen wurden. Um eine Datei zu schreiben, generiert der Processor vom Eingabegabestrom eine Datenquelle, die nötig ist, um später eine Seite 10 Java3D (Seminar), Java Media Framework API korrespondierende Datensenke zu erzeugen. Weiterhin muss ein ContentDescriptor gesetzt werden, der das Speicherformat beschreibt. Zum Schluss wird ein MediaLoactor Objekt erzeugt, das den Speicherort und den Dateinamen festlegt. Danach kann mit dem Schreiben kann begonnen werden. Die eben beschriebenen Schritte sind im Folgenden noch einmal, in zeitlich korrekter Abfolge der Anweisungen zusammengefasst. p.setContentDescriptor(FileTypeDescriptor ftd); DataSource source = p.getDataOutput(); MediaLocator dest = new MediaLocator(„protocol://file.ext“); DataSink filewriter = Manager.createDataSink(source, dest); filewriter.open(); Um die Größe der zu schreibenden Datei kontrollieren zu können, implementiert man das StreamWriterControl Interface und überschreibt die Methoden getControlComponent(); setStreamSizeLimit(long bytes); getStreamSize();. 3.2 Erweiterungen Unter Erweiterungen sind hier selbst implementierte Erweiterungen des JMF API gemeint. Hierdurch können eigene Formate und benutzerdefinierte Methoden zu deren Be- und Verarbeitung definiert werden. Dazu implementiert man je Zweck bestimmte Interfaces und überschreibt deren Methoden. Anschließend muss das neu geschriebene Plug-In noch registriert werden, damit es über den PlugInManager erreichbar wird. 4. Echtzeiterweiterung: JMF Real-Time Transport Protocol (RTP) API Die Echtzeiterweiterung des JMF API besteht aus den Packeten javax.media.rtp javax.media.rtp.event und javax.media.rtp.rtcp. Seite 11 Java3D (Seminar), Java Media Framework API Auch die Echtzeitfunktionalität kann durch selbst geschriebene Plug-Ins über den in 3.2 beschriebenen Mechanismus erweitert werden. 4.1 Streaming-Media Die Erweiterung des JMF API bezieht sich auf den Umgang mit Streaming-Media. Damit ist eine Technik gemeint, bei der Mediendaten, wie Audio oder Video, über ein Netzwerk übertragen werden und bei einem Client in Echtzeit wiedergegeben werden. Der Begriff schließt sowohl den Client, bei dem der Datenstrom empfangen und wiedergegeben wird, als auch den Server, der die Daten sendet, mit ein. Zwischen dem Server und den Clients bestehen dabei Ende-zu-Ende-Verbindungen, d.h. es gibt keine tatsächliche oder virtuelle Verbindung auf dem Übertragungsweg. Das besondere an dieser Technik ist, dass eine Datei bereits wiedergegeben werden kann, ohne vollständig übertragen worden zu sein. Außerdem kommt es nicht darauf, die Daten um jeden Preis fehlerfrei zu übertragen, sondern auf die Echtzeitfähigkeit des Systems. Protokolle wie HTTP oder FTP legen im Gegensatz dazu den größten Wert auf eine fehlerfreie Übertragung. Hierfür setzt es auf dem zuverlässigen TCP Protokoll auf. Das von der JMF API verwendete Protokoll ist das Real-Time Transport Protocol (RTP). 4.2 Real-Time Transport Protocol (RTP) Das Real-Time Transport Protocol (RTP) überträgt Daten mit Hilfe von Ende-zu-Ende Verbindungen in Echtzeit. Mit Echtzeit ist gemeint, sicherzustellen, dass ein konstanter Datendurchsatz erreicht wird und dabei bestimmte Zeitgrenzen eingehalten werden. RTP ist von der Architektur des Netzwerks und den eingesetzten Protokollen unabhängig, setzt jedoch meist auf dem „unzuverlässigen“ User Datagram Protocol (UDP) auf. Folgende Abbildung verdeutlicht die Architektur und Einordnung des RTP. Seite 12 Java3D (Seminar), Java Media Framework API Real-Time Media Framework and Applications Real-Time Control Protocol (RTCP) Real-Time Transport Protocol (RTP) Other Network Transport Protocols (TCP, ATM, ST-II, etc) UDP Internet Protocol (IP) Abbildung 5 aus [2] RTP kann für unicast und multicast Anwendungen eingesetzt werden. Beim unicast Verfahren werden einzelne Kopien der Originaldatei an die Teilnehmer versendet und ist daher nur für eine begrenzte Anzahl von Benutzern praktikabel. Im Gegensatz dazu übernimmt bei der multicast Übertragung der oder die Router die Dublizierung der Pakete, sodass eine sehr große Anzahl von Teilnehmern gleichzeitig bedient werden können. Aus der Abbildung wird ebenfalls ersichtlich, dass sich RTP einem zweiten Protokoll bedient, nämlich dem Real-Time Control Protocol (RTCP). Dieses Protokoll bietet dem Entwickler die Möglichkeit die Qualität der Datenströme zu identifizieren und zu überwachen. Dadurch wird eine Quality of Service Funktionalität erreicht. Die Übertragung der Mediendaten geschieht mit Hilfe von Sitzungen. Eine Sitzung ist durch eine Adresse und zwei Portnummer charakterisiert. Dabei wird ein Port für die eigentliche Mediendaten verwendet und ein zweiter für die Übertragung der Kontrollpakete des RTCP. 4.3 Architecktur des JMF RTP API Mit dem JMF RTP API kann die gesamte Funktionalität der JMF API bezogen auf Streaming-Media verwendet werden. So gliedert sich die Echtzeiterweiterung nahtlos in die High-Level Architektur des JMF API ein. Folgende Abbildung verdeutlicht die Einsatzmöglichkeiten des JMF RTP API. Seite 13 Java3D (Seminar), Java Media Framework API File Sender Processor Session Manager Capture Device Empfänger Netzwerk Session Manager DataSource DataSink Player Processor DataSink Abbildung 6 aus [2] Im Grunde sind es die gleichen wie auch beim JMF API mit der Einschränkung, dass die verwendeten Mediendaten mit Hilfe des RTP über ein Netzwerk übertragen werden. Diese Daten können auf Senderseite von einer Datei oder einem Aufnahmengerät kommen. Auf der Seite des Empfängers können die Daten entweder abgespielt oder in einer Datei gespeichert werden. Der unterste Zweig der Abbildung beim Empfänger zeigt einen Anwendungsfall, bei dem die empfangenen Daten zunächst als Input durch einen Processor laufen und anschließend in einer Datei gespeichert werden. Der Processor könnte die eingehenden Daten z.B. wiedergeben oder vor dem Speichern in ein anderes Format überführen. Bei der Übertragung mit Hilfe einer Sitzung, kann pro Sitzung nur ein Stream gesendet und empfangen werden. Deswegen ist ein sog. SessionManager nötig, sobald mehr als nur Stream übertragen werden soll. Der SessionManager des Senders startet für jeden Stream, der übertragen werden soll eine separate Sitzung. Auf Seite des Empfängers generiert der SessionManager für jeden erkannten Eingangsstream einen neuen Player oder Processor. 4.3.1 Übertragen von RTP Streaming-Media Um eine streamingfähige Datei vom Sender zum Empfänger zu übertragen sind eine Datenquelle (DataSource), ein MediaLocator und eine Datensenke (DataSink) notwendig. Der MediaLocator spezifiziert den Empfänger und den Typ der übertragenen Seite 14 Java3D (Seminar), Java Media Framework API Datei. Das Objekt wird mit einer URL erzeugt, die diese Angaben enthält. Sie könnte wie folgt aussehen: MediaLocator medialocator = new MediaLocator(„rtp://192.168.0.1:49150/audio“); Der IP-Adrsse des Empfängers geht dem Protokollname voraus, gefolgt von Portnummer und Medientyp. Die Datenquelle enthält Angaben zur Datei, die übertragenden werden soll. Der Processor wird mit Hilfe eines ProcessorModels, das auch die Datenquelle beinhaltet, erzeugt. Das ProcessorModel legt das Streamingfomat und den ContentDescriptor fest. Streamingformat und ContentDescriptor müssen zur Datenquelle kompatibel sein. Die JMF RTP API bietet eine Reihe vordefinierter Standardformate, die für die geläufigen Medientypen wie Audio und Video ausreichend sind. Die Datensenke wird am Ende vom Manager mit Hilfe des MediaLocators und dem Output des Processors erzeugt. Diese Datensenke ist als Repräsentation des Empfängers zu sehen. Nachdem die Operation DataSink ds = Manager.createDataSink(processoroutput, medialocator); ausgeführt wurde, kann der Processor gestartet, die Datenquelle geöffnet und gestartet und mit der Übertragung begonnen werden. Der eben beschriebene Weg ist nur praktikabel, wenn lediglich ein Stream übertragen werden soll. Eine Mediendatei kann jedoch mehrere Formate und damit mehrere Streams enthalten. So besteht z.B. eine Videodatei neben den Bildinformationen auch meistens aus einer Audiospur. Mit dem oben angesprochen Weg kann immer nur der erste erkannte Stream übertragen werden. Sind dagegen mehrere Streams gleichzeitig zu übertragen, benötigt man den oben angesprochenen SessionManager. Weiterhin bekommt man alle in einer Datei enthaltenen Streams über den Processor mit der Methode TrackControl[] trackControls = Processor.getTrackControls(); Alle in diesem Array enthaltenen TrackControl Objekte können anschließend aktiviert oder deaktiviert, sowie individuelle Einstellungen getroffen werden. Jedes TrackControl Objekt repräsentiert später einen Stream. Folgender Programmausschnitt beschreibt die Erzeugung des SessionManagers und der bzw. des Streams. Seite 15 Java3D (Seminar), Java Media Framework API SessionManager sm = new com.sun.media.rtp.RTPSessionMgr(); sm.createSendStream(processoroutput, 0); sm.initSession(); sm.startSession(); Der SesionManager ist ab diesem Zeitpunkt für die Übertragung verantwortlich. Mit Hilfe des zweiten Parameters der Funktion createSendStream, kann eingestellt werden, welcher Stream bzw. welches TrackControl Objekt übertragen werden soll. Steht dort eine eins, so bedeutet dies, dass der erste aktivierte Stream im Array trackControls übertragen werden soll. Steht, wie in diesem Fall eine null an dieser Stelle, so ist der tatsächlich übertragene Stream eine Mischung aller aktivierten TrackControl Objekte. Die Einzelstreams werden dabei mit Hilfe eines Multiplexers zu einem entsprechend kodierten Signal vereinigt und auf Seite des Empfängers wieder getrennt, sodass tatsächlich nur ein Stream übertragen wird, dieser jedoch aus Einzelstreams oder Spuren zusammengesetzt ist. Auf diese Weise können scheinbar mehrere Streams gleichzeitig übertragen und beim Empfänger synchronisiert abgespielt werden. 4.3.2 Empfangen von RTP Streaming-Media Das Empfangen von RTP Streaming-Media ist noch einfacher als das Senden. Dennoch muss man auch hier unterscheiden, zwischen einem einzigen Stream, der empfangen und wiedergegeben werden soll und mehreren parallelen Streams. Im ersten Fall benötigt man lediglich eine MediaLocator Objekt, das analog zum MediaLocator Objekt des Senders erstellt wird. Danach wird der Player mit Hilfe des Managers und des gerade erzeugten MediaLocators kreiert. Sobald der Player den ersten Stream entdeckt hat, löst er ein RealizeCompleteEvent aus und ist damit bereit die Quelle abzuspielen. Folgende Anweisungen fassen die eben beschriebenen Schritte zusammen: MediaLocator medialoc = new MediaLocator(„rtp://192.168.0.1:49150/audio“); Player player = Manager.createPlayer(medialoc); player.addControllerListener(ControllerListener); player.realize(); Seite 16 Java3D (Seminar), Java Media Framework API Sollen mehrere Streams einer Sitzung empfangen und wiedergegeben werden, braucht man auch hier den SessionManager. Diesem muss ein ReceiveStreamListener engehängt werden, damit neue eingehende Streams erkannt werden können. Jedes Mal, wenn nun ein neuer Stream erkannt wird, löst der Manager ein NewReceiveStreamEvent aus. Anhand des Streams kann ein DataSource Objekt gewonnen werden, dass an die Mananger Methode createPlayer übergeben wird. Auf diese Weise wird jedes Mal ein neuer Player erzeugt, wenn ein neuer Stream erkannt wird. Die folgenden Anweisungen verdeutlichen noch einmal, wie bei einem neu eintreffenden Stream einer Sitzung zu Verfahren ist. ReceiveStream stream = ((NewReceiveStreamEvent)event).getReceiveStream(); DataSource datasource = stream.getDataSource(); Player player = Manager.createPlayer(datasource); Dies waren die Grundlagen, um einen oder mehrere Streams einer Sitzung empfangen und abspielen zu können. Daneben gibt es eine Reihe weiterer Maßnahmen, die einerseits zu statistischen Zwecken gebraucht werden und andererseits die Anwendung robuster machen. So gibt es z.B. die Möglichkeit auf Veränderungen im Format zu reagieren. Dabei wird entweder ein FormatChangeEvent oder ein RemotePayloadChangeEvent ausgelöst. Im ersten Fall, ist zu prüfen, ob ein neuer Player erzeugt wurde. Ist dies der Fall, müssen die Referenzen seiner visuellen und Kontrollkomponenten auf das Player Objekt gesetzt werden. Im Falle der RemotePayloadChangeEvent, muss der alte Player freigegeben, alle Listener entfernt und ein neuer Player mit der gleichen Datenquelle erzeugt werden. 5. Literaturstellen [1]: Java Media Framework API Guide v. 2.0 [2]: vom Autor erstellte Bilder dessen Vorlage aus [1] stammt [3]: https://www6.software.ibm.com/developerworks/education/j-jmf/index.html Seite 17