Receptia si prezentarea stream-urilor RTP
Player-ele si Processoarele JMF prevad mecanismele de prezentare, captura si conversie a datelor pentru stream-urile RTP.
Figura 8.25. Fluxul datelor spre receptia RTP
Un Player separat este folosit pentru fiecare stream receptionat de managerul de sesiune. Pentru a construi un Player pentru un stream RTP se foloseste mecanismul standard Manager createPlayer. Exista doua posibilitati:
se foloseste un MediaLocator care are parametrii unei sesiuni RTP si se construieste un Player prin apelarea Manager.createPlayer(MediaLocator;
se construieste un Player pentru un anumit ReceiveStream prin obtinerea DataSource-ului din stream-ul de date si prin transmiterea lui la Manager.createPlayer(DataSource).
Daca se foloseste un MediaLocator la construirea unui Player, se poate prezenta doar primul str 848j95i eam RTP care este detectat īn sesiune. Daca se doreste rularea de stream-uri multiple īntr-o sesiune, trebuie folosita SessionManager direct si trebuie construit un Player pentru fiecare ReceiveStream.
8.8.1. Crearea unui Player pentru o sesiune RTP
Cānd se foloseste un MediaLocator la constructia unui Player pentru o sesiune RTP, Manager-ul creaza un Player pentru primul stream detectat īn sesiune. Acest Player posteaza un eveniment RealizeCompleteEvent odata ce datele au fost detectate īn sesiune.
Prin "ascultarea" pentru aparitia evenimentului RealizeCompleteEvent, se poate determina daca au sosit date si daca Player-ul este capabil sa prezinte aceste date. Odata ce Player-ul posteaza acest eveniment, se pot obtine componentele lui vizuale si de control.
Un Player poate exporta un control specific RTP, RTPControl, care furnizeaza statistici si care poate fi folosit la īnregistrarea dinamica cu SessionManager-ul.
Exemplul 8.13. Crearea unui Player pentru o sesiune RTP |
String url= "rtp://224.144.251.104:49150/audio/1"; MediaLocator mrl= new MediaLocator(url); if (mrl == null) // Creeaza un Player pentru sesiune RTP curenta try catch (NoPlayerException e) catch (MalformedURLException e) catch (IOException e) if (player != null) } |
Cānd un Player posteaza un eveniment FormatChangeEvent, indica faptul ca datele utile au suferit o modificare. Player-ele construite cu un MediaLocator proceseaza aceste modificari automat. Īn majoritatea cazurilor, aceasta procesare implica construirea unui nou Player care sa manipuleze datele īn noul format.
Aplicatiile care prezinta stream-urile RTP trebuie sa "asculte" pentru evenimente FormatChangeEvents care pot raspunde daca un nou Player este creat.
Cānd un eveniment FormatChangeEvent este postat, trebuie verificat daca controlul obiectului si componentele vizuale ale Player-ului s-au modificat. Daca da, un nou Player trebuie construit iar referintele la vechiul Player trebuie īnlocuite cu cele la noul Player.
Exemplul 8.14. "Ascultarea" pentru modificari de format RTP |
public synchronized void controllerUpdate(ControllerEvent ce) framePanel.remove(oldVisualComp); vSize = visualComp.getPreferredSize(); vSize.width = (int)(vSize.width * defaultScale); vSize.height = (int)(vSize.height * defaultScale); framePanel.add(visualComp); visualComp.setBounds(0, 0, vSize.width, vSize.height); addPopupMenu(visualComp); } } Component oldComp = controlComp; controlComp = player.getControlPanelComponent(); if (controlComp != null) } } } } |
8.8.2. Crearea unui Player RTP pentru fiecare stream receptionat
Pentru a rula toate ReceiveStream-urile dintr-o sesiune, trebuie creat un Player separat pentru fiecare stream. Cānd un nou stream este creat, managerul de sesiune posteaza un eveniment NewReceiveStreamEvent. Īn general trebuie īnregistrat ca si ReceiveStreamListener si trebuie construit cāte un Player pentru fiecare ReceiveStream. Pentru a construi un Player, trebuie obtinuta DataSource-ul din ReceiveStream si transmisa la Manager.createPlayer.
Pentru a crea un Player pentru fiecare stream receptionat īntr-o sesiune trebuie:
setata sesiune RTP:
creat un SessionManager;
apelat RTPSessionMgr addReceiveStreamListener pentru a se īnregistra ca si listener;
porneste sesiunea RTP prin apelarea RTPSessionMgr startSession;
Exemplul 8.15. Setarea unei sesiuni RTP |
public SessionManager createManager(String address, int port, int ttl, boolean listener,boolean sendlistener) catch (SecurityException e) // creaza Session Address local SessionAddress localaddr = new SessionAddress(); try; mgr.initSession(localaddr, userdesclist, 0.05, 0.25); mgr.startSession(sessaddr,ttl,null); } catch (Exception e) return mgr; } |
Īn metoda ReceiveStreamListener update, trebuie "ascultat" pentru evenimentul NewReceiveStreamEvent, care indica ca un nou stream de date a fost detectat.
Cānd un eveniment NewReceiveStreamEvent este detectat, trebuie apelata metoda getReceiveStream pentru a obtine ReceiveStream.
Trebuie obtinuta DataSource-ul RTP din ReceiveStream prin apelarea metodei getDataSource cu un Format specific RTP. Spre exemplu, codarea pentru un player audio DVI va fi DVI_RTP.
Trebuie transmits DataSource-ul la Manager.createPlayer pentru a construi un Player. Pentru ca acesta sa fie construit cu succes, plug-in-urile pentru decodare si despachetare a datelor formatate RTP trebuie sa fie disponibile.
Exemplul 8.16. "Ascultarea" pentru NewReceiveStreamEvents |
public void update( ReceiveStreamEvent event) catch (Exception e) if (newplayer == null) return; playerlist.addElement(newplayer); newplayer.addControllerListener(this); // trimite acest player spre player GUI playerWindow = new RTPPlayerWindow( newplayer, cname); } } |
Daca datele utile dintr-un stream dintr-o sesiune RTP se modifica, ReceiveStream posteaza un eveniment RemotePayloadChangeEvent. De obicei, cānd datele sufera modificari, Player-ul existent nu va recunoaste noul format si JMF va "arunca" o eroare daca se īncearca prezentarea datelor. Pentru a evita aceasta, ReceiveStreamListener trebuie sa urmareasca pentru evenimente RemotePayloadChangeEvents. Cānd un astfel de eveniment este detectat, trebuie:
īnchis Player-ul curent;
retrase toate listener-ele care tineau de acel Player;
creat un nou Player cu aceeasi DataSource RTP;
obtinute Componentele vizuale si de control prntru noul Player;
adaugate listener-ele necesare pentru noul Player;
Exemplul 8.17. Tratarea modificarilor datelor utile din stream |
public void update(ReceiveStreamEvent event) newplayer.addControllerListener(listener); newplayer.realize(); // cand noul player este in starea realized , obtine // componentele lui vizuale si de control } catch (Exception e) } } } |
Buffer-ul de receptie RTP poate fi controlat prin BufferControl exportat de catre SessionManager. Acest control permite setarea a doi parametrii: lungimea buffer-ului si pragul (treshold).
Lungimea buffer-ului este marimea buffer-ului mentinut de catre receptor. Pragul este cantitatea minima de date care este stocata īn buffer īnainte de trimiterea datelor. Datele vor fi disponibile de la acest obiect cānd acest prag minim este atins. Daca datele din buffer nu ating acest prag, datele vor fi buffer-ate din nou pāna cānd pragul este atins.
Lungimea buffer-ului si valoarea de prag sunt specificate īn milisecunde. Numarul de pachete audio si de cadre video din buffer depinde de formatul streamului de la intrare. Fiecare stream de intrare mentine propriile valori implicite si maxime atāt pentru lungimea buffer-ului cāt si pentru pragul minim. Valorile implicite si maxime sunt independente de aplicatie.
Pentru a obtine BufferControl pentru o sesiune, trebuie apelata metoda getControl asupra SessionManager-ului. Se poate obtine o componenta GUI pentru un BufferControl prin apelarea getControlComponent.
8.8.3. Prezentarea stream-urilor RTP cu RTPSocket
RTP este un protocol independent de protocolul de transport folosit. Folosind RTPSocket, se pot receptiona stream-uri RTP din orice retea. Formatul socket-ului RTP este proiectat sa aiba un canal pentru date si unul de control.
Fiecare canal are un stream de intrare si unul de iesire pentru a transmite si a receptiona datele īn/din retea.
SessionManager-ul se asteapta sa primeasca pachete RTP individuale de la RTPSocket. Utilizatorii sunt responsabili de trimiterea pachetelor individuale spre RTPSocket.
Pentru a rula un stream RTP de la RTPSocket, trebuie transmis acest socket la Manager.createPlayer pentru a construi Player-ul. Alternativ, se poate construi un Player prin apelarea createPlayer(MediaLocator) si trecerea la MediaLocator cu un nou protocol care este o varianta a RTP, "rtpraw". Spre exemplu,
Manager.createPlayer(new MediaLocator("rtpraw://"));
Conform mecanismului de creere a unui Player JMF, Manger-ul va īncerca sa construiasca DataSource-ul:
<protocol package-prefix>.media.protocol.rtpraw.DataSource
Continutul RTPSocket trebuie setat pe rtpraw. Manager-ul va incerca sa creeze un Player de tipul <content-prefix>.media.content.rptraw.Handler si va seta RTPSocket-ul pe acesta.
Nota: RTPSocket-ul creat la <protocol package-prefix>
.media.protocol.rtpraw.DataSource
este
implementarea proprie a acestuia. Implementarea RTPSocket-ului este dependenta de protocolul de transport
folosit. Clasa RTPSocket trebuie
localizata la <protocol
package-prefix>.media.protocol.rtpraw.DataSource
iar canalele de date
si control trebuie setate sa arate ca si īn Exemplul 8.18.
Interfetele RTPControl pentru RTPSocket pot fi utilizate pentru a
adauga informatii dinamice manager-ului de sesiune RTP.
Exemplul 8.18 implementeaza o sesiune RTP peste un Player UDP care poate receptiona pachete RTP UDP si le poate transmite unui Player sau unui manager de sesiune. Acest exemplu foloseste interfetele definite īn javax.media.rtp.RTPSocket si clasele care apartin acesteia.
Exemplul 8.18. RTPSocketPlayer |
import java.io.*; import java.net.*; import java.util.*; import javax.media.*; import javax.media.format.*; import javax.media.protocol.*; import javax.media.rtp.*; import javax.media.rtp.event.*; import javax.media.rtp.rtcp.*; public class RTPSocketPlayer implements ControllerListener catch (NoPlayerException e) catch (IOException e) if (player != null) } public synchronized void controllerUpdate(ControllerEvent ce) } private DatagramSocket InitSocket(String sockaddress, int sockport) else return sock; } catch (SocketException e) catch (UnknownHostException e) catch (IOException e) } public class UDPHandler extends Thread implements PushSourceStream, OutputDataStream // threadul principal primeste pachete RTP din retea si transfera // aceste date handlerului de iesire a acestui stream public void run() try len = dp.getLength(); if (len > (maxsize >> 1)) maxsize = len << 1; } while (len >= dp.getData().length); }catch (Exception e) if (outputHandler != null) } } public void close() private void cleanup() // metodele pentru PushSourceStream public Object[] getControls() public Object getControl(String controlName) public ContentDescriptor getContentDescriptor() public long getContentLength() public boolean endOfStream() // metoda prin care datele sunt transferate din retea spre // managerul de sesiune public int read(byte buffer[], int offset, int length) public int getMinimumTransferSize() public void setTransferHandler(SourceTransferHandler transferHandler) // metode pentru OutputDataStream folosite de managerul de // sesiune pentru a transmite date in retea public int write(byte[] buffer, int offset, int length) catch (UnknownHostException e) DatagramPacket dp = new DatagramPacket( buffer, length, addr, myport); try catch (IOException e) return dp.getLength(); } } public static void main(String[] args) |
|