/** * Initializes a new <tt>RtxTransformer</tt> with a specific <tt>RtpChannel</tt>. * * @param channel the <tt>RtpChannel</tt> for the transformer. */ RtxTransformer(RtpChannel channel) { super(RTPPacketPredicate.INSTANCE); this.channel = channel; this.logger = Logger.getLogger(classLogger, channel.getContent().getConference().getLogger()); }
/** * A dialog dedicated to desktop streaming/sharing. Shows the possible screens to select from to use * for the streaming/sharing session. * * @author Yana Stamcheva */ public class SelectScreenDialog extends SIPCommDialog { /** Serial version UID. */ private static final long serialVersionUID = 0L; /** The object used for logging. */ private static final Logger logger = Logger.getLogger(SelectScreenDialog.class); /** The combo box containing screen choice. */ private final JComboBox deviceComboBox; /** The cancel button of this dialog. */ private final JButton cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL")); /** * The video <code>CaptureDeviceInfo</code> this instance started to create the preview of. * * <p>Because the creation of the preview is asynchronous, it's possible to request the preview of * one and the same device multiple times. Which may lead to failures because of, for example, * busy devices and/or resources (as is the case with LTI-CIVIL and video4linux2). */ private static MediaDevice videoDeviceInPreview; /** The selected media device. */ private MediaDevice selectedDevice; /** * Creates an instance of <tt>SelectScreenDialog</tt> by specifying the list of possible desktop * devices to choose from. * * @param desktopDevices the list of possible desktop devices to choose from */ public SelectScreenDialog(List<MediaDevice> desktopDevices) { setModal(true); setPreferredSize(new Dimension(400, 300)); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); deviceComboBox = new JComboBox(desktopDevices.toArray()); contentPane.add(deviceComboBox, BorderLayout.NORTH); deviceComboBox.setRenderer(new ComboRenderer()); contentPane.add(createPreview(deviceComboBox)); contentPane.add(createButtonsPanel(), BorderLayout.SOUTH); } /** * Returns the selected device. * * @return the selected device */ public MediaDevice getSelectedDevice() { return selectedDevice; } /** * Creates the buttons panel. * * @return the buttons panel */ private Component createButtonsPanel() { JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK")); okButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { selectedDevice = (MediaDevice) deviceComboBox.getSelectedItem(); dispose(); } }); buttonsPanel.add(okButton); cancelButton.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { selectedDevice = null; dispose(); } }); buttonsPanel.add(cancelButton); return buttonsPanel; } /** * Create preview component. * * @param comboBox the options. * @return the component. */ private static Component createPreview(final JComboBox comboBox) { final JComponent preview; JLabel noPreview = new JLabel(GuiActivator.getResources().getI18NString("impl.media.configform.NO_PREVIEW")); noPreview.setHorizontalAlignment(SwingConstants.CENTER); noPreview.setVerticalAlignment(SwingConstants.CENTER); preview = createVideoContainer(noPreview); preview.setPreferredSize(new Dimension(WIDTH, 280)); preview.setMaximumSize(new Dimension(WIDTH, 280)); final ActionListener comboBoxListener = new ActionListener() { public void actionPerformed(ActionEvent event) { MediaDevice device = (MediaDevice) comboBox.getSelectedItem(); if ((device != null) && device.equals(videoDeviceInPreview)) return; Exception exception; try { createPreview(device, preview); exception = null; } catch (IOException ex) { exception = ex; } catch (MediaException ex) { exception = ex; } if (exception != null) { logger.error("Failed to create preview for device " + device, exception); device = null; } videoDeviceInPreview = device; } }; comboBox.addActionListener(comboBoxListener); /* * We have to initialize the controls to reflect the configuration * at the time of creating this instance. Additionally, because the * video preview will stop when it and its associated controls * become unnecessary, we have to restart it when the mentioned * controls become necessary again. We'll address the two goals * described by pretending there's a selection in the video combo * box when the combo box in question becomes displayable. */ comboBox.addHierarchyListener( new HierarchyListener() { public void hierarchyChanged(HierarchyEvent event) { if (((event.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) && comboBox.isDisplayable()) { // let current changes end their execution // and after that trigger action on combobox SwingUtilities.invokeLater( new Runnable() { public void run() { comboBoxListener.actionPerformed(null); } }); } else { if (!comboBox.isDisplayable()) videoDeviceInPreview = null; } } }); return preview; } /** * Creates preview for the device(video) in the video container. * * @param device the device * @param videoContainer the container * @throws IOException a problem accessing the device. * @throws MediaException a problem getting preview. */ private static void createPreview(MediaDevice device, final JComponent videoContainer) throws IOException, MediaException { videoContainer.removeAll(); videoContainer.revalidate(); videoContainer.repaint(); if (device == null) return; Component c = (Component) GuiActivator.getMediaService() .getVideoPreviewComponent( device, videoContainer.getSize().width, videoContainer.getSize().height); videoContainer.add(c); } /** * Creates the video container. * * @param noVideoComponent the container component. * @return the video container. */ private static JComponent createVideoContainer(Component noVideoComponent) { return new VideoContainer(noVideoComponent, false); } /** Custom combo box renderer. */ private static class ComboRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); MediaDevice mediaDevice = (MediaDevice) value; Dimension screenSize = null; if (mediaDevice != null) screenSize = ((VideoMediaFormat) mediaDevice.getFormat()).getSize(); this.setText(screenSize.width + "x" + screenSize.height); return this; } } /** * Automatically press the cancel button when this dialog has been escaped. * * @param escaped indicates if this dialog has been closed by pressing the ESC key */ @Override protected void close(boolean escaped) { if (escaped) cancelButton.doClick(); } }
/** * Intercepts RTX (RFC-4588) packets coming from an {@link RtpChannel}, and removes their RTX * encapsulation. Allows packets to be retransmitted to a channel (using the RTX format if the * destination supports it). * * @author Boris Grozev * @author George Politis */ public class RtxTransformer extends SinglePacketTransformerAdapter implements TransformEngine { /** * The {@link Logger} used by the {@link RtxTransformer} class to print debug information. Note * that {@link Conference} instances should use {@link #logger} instead. */ private static final Logger classLogger = Logger.getLogger(RtxTransformer.class); /** The <tt>RtpChannel</tt> for the transformer. */ private RtpChannel channel; /** Maps an RTX SSRC to the last RTP sequence number sent with that SSRC. */ private final Map<Long, Integer> rtxSequenceNumbers = new HashMap<>(); /** The {@link Logger} to be used by this instance to print debug information. */ private final Logger logger; /** * The payload type number configured for RTX (RFC-4588), or -1 if none is configured (the other * end does not support rtx). */ private byte rtxPayloadType = -1; /** The "associated payload type" number for RTX. */ private byte rtxAssociatedPayloadType = -1; /** * Initializes a new <tt>RtxTransformer</tt> with a specific <tt>RtpChannel</tt>. * * @param channel the <tt>RtpChannel</tt> for the transformer. */ RtxTransformer(RtpChannel channel) { super(RTPPacketPredicate.INSTANCE); this.channel = channel; this.logger = Logger.getLogger(classLogger, channel.getContent().getConference().getLogger()); } /** Implements {@link PacketTransformer#transform(RawPacket[])}. {@inheritDoc} */ @Override public RawPacket reverseTransform(RawPacket pkt) { if (isRtx(pkt)) { pkt = deRtx(pkt); } return pkt; } /** * Determines whether {@code pkt} is an RTX packet. * * @param pkt the packet to check. * @return {@code true} iff {@code pkt} is an RTX packet. */ private boolean isRtx(RawPacket pkt) { byte rtxPt = rtxPayloadType; return rtxPt != -1 && rtxPt == pkt.getPayloadType(); } /** * Removes the RTX encapsulation from a packet. * * @param pkt the packet to remove the RTX encapsulation from. * @return the original media packet represented by {@code pkt}, or null if we couldn't * reconstruct the original packet. */ private RawPacket deRtx(RawPacket pkt) { boolean success = false; if (pkt.getPayloadLength() - pkt.getPaddingSize() < 2) { // We need at least 2 bytes to read the OSN field. if (logger.isDebugEnabled()) { logger.debug("Dropping an incoming RTX packet with padding only: " + pkt); } return null; } long mediaSsrc = getPrimarySsrc(pkt); if (mediaSsrc != -1) { if (rtxAssociatedPayloadType != -1) { int osn = pkt.getOriginalSequenceNumber(); // Remove the RTX header by moving the RTP header two bytes // right. byte[] buf = pkt.getBuffer(); int off = pkt.getOffset(); System.arraycopy(buf, off, buf, off + 2, pkt.getHeaderLength()); pkt.setOffset(off + 2); pkt.setLength(pkt.getLength() - 2); pkt.setSSRC((int) mediaSsrc); pkt.setSequenceNumber(osn); pkt.setPayloadType(rtxAssociatedPayloadType); success = true; } else { logger.warn( "RTX packet received, but no APT is defined. Packet " + "SSRC " + pkt.getSSRCAsLong() + ", associated media" + " SSRC " + mediaSsrc); } } // If we failed to handle the RTX packet, drop it. return success ? pkt : null; } /** Implements {@link TransformEngine#getRTPTransformer()}. */ @Override public PacketTransformer getRTPTransformer() { return this; } /** Implements {@link TransformEngine#getRTCPTransformer()}. */ @Override public PacketTransformer getRTCPTransformer() { return null; } /** * Returns the sequence number to use for a specific RTX packet, which is based on the packet's * original sequence number. * * <p>Because we terminate the RTX format, and with simulcast we might translate RTX packets from * multiple SSRCs into the same SSRC, we keep count of the RTX packets (and their sequence * numbers) which we sent for each SSRC. * * @param ssrc the SSRC of the RTX stream for the packet. * @return the sequence number which should be used for the next RTX packet sent using SSRC * <tt>ssrc</tt>. */ private int getNextRtxSequenceNumber(long ssrc) { Integer seq; synchronized (rtxSequenceNumbers) { seq = rtxSequenceNumbers.get(ssrc); if (seq == null) seq = new Random().nextInt(0xffff); else seq++; rtxSequenceNumbers.put(ssrc, seq); } return seq; } /** * Tries to find an SSRC paired with {@code ssrc} in an FID group in one of the channels from * {@link #channel}'s {@code Content}. Returns -1 on failure. * * @param pkt the {@code RawPacket} that holds the RTP packet for which to find a paired SSRC. * @return An SSRC paired with {@code ssrc} in an FID group, or -1. */ private long getRtxSsrc(RawPacket pkt) { StreamRTPManager receiveRTPManager = channel.getStream().getRTPTranslator().findStreamRTPManagerByReceiveSSRC(pkt.getSSRC()); MediaStreamTrackReceiver receiver = null; if (receiveRTPManager != null) { MediaStream receiveStream = receiveRTPManager.getMediaStream(); if (receiveStream != null) { receiver = receiveStream.getMediaStreamTrackReceiver(); } } if (receiver == null) { return -1; } RTPEncoding encoding = receiver.resolveRTPEncoding(pkt); if (encoding == null) { logger.warn( "encoding_not_found" + ",stream_hash=" + channel.getStream().hashCode() + " ssrc=" + pkt.getSSRCAsLong()); return -1; } return encoding.getRTXSSRC(); } /** * Retransmits a packet to {@link #channel}. If the destination supports the RTX format, the * packet will be encapsulated in RTX, otherwise, the packet will be retransmitted as-is. * * @param pkt the packet to retransmit. * @param after the {@code TransformEngine} in the chain of {@code TransformEngine}s of the * associated {@code MediaStream} after which the injection of {@code pkt} is to begin * @return {@code true} if the packet was successfully retransmitted, {@code false} otherwise. */ public boolean retransmit(RawPacket pkt, TransformEngine after) { boolean destinationSupportsRtx = rtxPayloadType != -1; boolean retransmitPlain; if (destinationSupportsRtx) { long rtxSsrc = getRtxSsrc(pkt); if (rtxSsrc == -1) { logger.warn( "Cannot find SSRC for RTX, retransmitting plain. " + "SSRC=" + pkt.getSSRCAsLong()); retransmitPlain = true; } else { retransmitPlain = !encapsulateInRtxAndTransmit(pkt, rtxSsrc, after); } } else { retransmitPlain = true; } if (retransmitPlain) { MediaStream mediaStream = channel.getStream(); if (mediaStream != null) { try { mediaStream.injectPacket(pkt, /* data */ true, after); } catch (TransmissionFailedException tfe) { logger.warn("Failed to retransmit a packet."); return false; } } } return true; } /** * Notifies this instance that the dynamic payload types of the associated {@link MediaStream} * have changed. */ public void onDynamicPayloadTypesChanged() { rtxPayloadType = -1; rtxAssociatedPayloadType = -1; MediaStream mediaStream = channel.getStream(); Map<Byte, MediaFormat> mediaFormatMap = mediaStream.getDynamicRTPPayloadTypes(); Iterator<Map.Entry<Byte, MediaFormat>> it = mediaFormatMap.entrySet().iterator(); while (it.hasNext() && rtxPayloadType == -1) { Map.Entry<Byte, MediaFormat> entry = it.next(); MediaFormat format = entry.getValue(); if (!Constants.RTX.equalsIgnoreCase(format.getEncoding())) { continue; } // XXX(gp) we freak out if multiple codecs with RTX support are // present. rtxPayloadType = entry.getKey(); rtxAssociatedPayloadType = Byte.parseByte(format.getFormatParameters().get("apt")); } } /** * Encapsulates {@code pkt} in the RTX format, using {@code rtxSsrc} as its SSRC, and transmits it * to {@link #channel} by injecting it in the {@code MediaStream}. * * @param pkt the packet to transmit. * @param rtxSsrc the SSRC for the RTX stream. * @param after the {@code TransformEngine} in the chain of {@code TransformEngine}s of the * associated {@code MediaStream} after which the injection of {@code pkt} is to begin * @return {@code true} if the packet was successfully retransmitted, {@code false} otherwise. */ private boolean encapsulateInRtxAndTransmit(RawPacket pkt, long rtxSsrc, TransformEngine after) { byte[] buf = pkt.getBuffer(); int len = pkt.getLength(); int off = pkt.getOffset(); byte[] newBuf = new byte[len + 2]; RawPacket rtxPkt = new RawPacket(newBuf, 0, len + 2); int osn = pkt.getSequenceNumber(); int headerLength = pkt.getHeaderLength(); int payloadLength = pkt.getPayloadLength(); // Copy the header. System.arraycopy(buf, off, newBuf, 0, headerLength); // Set the OSN field. newBuf[headerLength] = (byte) ((osn >> 8) & 0xff); newBuf[headerLength + 1] = (byte) (osn & 0xff); // Copy the payload. System.arraycopy(buf, off + headerLength, newBuf, headerLength + 2, payloadLength); MediaStream mediaStream = channel.getStream(); if (mediaStream != null) { rtxPkt.setSSRC((int) rtxSsrc); rtxPkt.setPayloadType(rtxPayloadType); // Only call getNextRtxSequenceNumber() when we're sure we're going // to transmit a packet, because it consumes a sequence number. rtxPkt.setSequenceNumber(getNextRtxSequenceNumber(rtxSsrc)); try { mediaStream.injectPacket(rtxPkt, /* data */ true, after); } catch (TransmissionFailedException tfe) { logger.warn("Failed to transmit an RTX packet."); return false; } } return true; } /** * Returns the SSRC paired with <tt>ssrc</tt> in an FID source-group, if any. If none is found, * returns -1. * * @return the SSRC paired with <tt>ssrc</tt> in an FID source-group, if any. If none is found, * returns -1. */ private long getPrimarySsrc(RawPacket pkt) { MediaStreamTrackReceiver receiver = channel.getStream().getMediaStreamTrackReceiver(); if (receiver == null) { if (logger.isDebugEnabled()) { logger.debug("Dropping an incoming RTX packet from an unknown source."); } return -1; } RTPEncoding encoding = receiver.resolveRTPEncoding(pkt); if (encoding == null) { if (logger.isDebugEnabled()) { logger.debug("Dropping an incoming RTX packet from an unknown source."); } return -1; } return encoding.getPrimarySSRC(); } }
/** * Implements <tt>MediaFormatFactory</tt> for the JMF <tt>Format</tt> types. * * @author Lyubomir Marinov */ public class MediaFormatFactoryImpl implements MediaFormatFactory { /** * The <tt>Logger</tt> used by the <tt>MediaFormatFactoryImpl</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(MediaFormatFactoryImpl.class); /** * Creates an unknown <tt>MediaFormat</tt>. * * @param type <tt>MediaType</tt> * @return unknown <tt>MediaFormat</tt> */ public MediaFormat createUnknownMediaFormat(MediaType type) { Format unknown = null; /* * FIXME Why is a VideoFormat instance created for MediaType.AUDIO and * an AudioFormat instance for MediaType.VIDEO? */ if (type.equals(MediaType.AUDIO)) unknown = new VideoFormat("unknown"); else if (type.equals(MediaType.VIDEO)) unknown = new AudioFormat("unknown"); return MediaFormatImpl.createInstance(unknown); } /** * Creates a <tt>MediaFormat</tt> for the specified <tt>encoding</tt> with default clock rate and * set of format parameters. If <tt>encoding</tt> is known to this <tt>MediaFormatFactory</tt>, * returns a <tt>MediaFormat</tt> which is either an <tt>AudioMediaFormat</tt> or a * <tt>VideoMediaFormat</tt> instance. Otherwise, returns <tt>null</tt>. * * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance if <tt>encoding</tt> is * known to this <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(String) */ public MediaFormat createMediaFormat(String encoding) { return createMediaFormat(encoding, CLOCK_RATE_NOT_SPECIFIED); } /** * Creates a <tt>MediaFormat</tt> for the specified RTP payload type with default clock rate and * set of format parameters. If <tt>rtpPayloadType</tt> is known to this * <tt>MediaFormatFactory</tt>, returns a <tt>MediaFormat</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance. Otherwise, returns * <tt>null</tt>. * * @param rtpPayloadType the RTP payload type of the <tt>MediaFormat</tt> to create * @return a <tt>MediaFormat</tt> with the specified <tt>rtpPayloadType</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance if * <tt>rtpPayloadType</tt> is known to this <tt>MediaFormatFactory</tt>; otherwise, * <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(byte) */ public MediaFormat createMediaFormat(byte rtpPayloadType) { /* * We know which are the MediaFormat instances with the specified * rtpPayloadType but we cannot directly return them because they do not * reflect the user's configuration with respect to being enabled and * disabled. */ for (MediaFormat rtpPayloadTypeMediaFormat : MediaUtils.getMediaFormats(rtpPayloadType)) { MediaFormat mediaFormat = createMediaFormat( rtpPayloadTypeMediaFormat.getEncoding(), rtpPayloadTypeMediaFormat.getClockRate()); if (mediaFormat != null) return mediaFormat; } return null; } /** * Creates a <tt>MediaFormat</tt> for the specified <tt>encoding</tt> with the specified * <tt>clockRate</tt> and a default set of format parameters. If <tt>encoding</tt> is known to * this <tt>MediaFormatFactory</tt>, returns a <tt>MediaFormat</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance. Otherwise, returns * <tt>null</tt>. * * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for * @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt> for * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt> and <tt>clockRate</tt> * which is either an <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance if * <tt>encoding</tt> is known to this <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(String, double) */ public MediaFormat createMediaFormat(String encoding, double clockRate) { return createMediaFormat(encoding, clockRate, 1); } /** * Creates a <tt>MediaFormat</tt> for the specified <tt>encoding</tt>, <tt>clockRate</tt> and * <tt>channels</tt> and a default set of format parameters. If <tt>encoding</tt> is known to this * <tt>MediaFormatFactory</tt>, returns a <tt>MediaFormat</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance. Otherwise, returns * <tt>null</tt>. * * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for * @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt> for * @param channels the number of available channels (1 for mono, 2 for stereo) if it makes sense * for the <tt>MediaFormat</tt> with the specified <tt>encoding</tt>; otherwise, ignored * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt>, <tt>clockRate</tt> and * <tt>channels</tt> and a default set of format parameters which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance if <tt>encoding</tt> is * known to this <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(String, double, int) */ public MediaFormat createMediaFormat(String encoding, double clockRate, int channels) { return createMediaFormat(encoding, clockRate, channels, null); } private MediaFormat createMediaFormat( String encoding, double clockRate, int channels, Map<String, String> fmtps) { for (MediaFormat format : getSupportedMediaFormats(encoding, clockRate)) { /* * The mediaType, encoding and clockRate properties are sure to * match because format is the result of the search for encoding and * clockRate. We just want to make sure that the channels and the * format parameters match. */ if (format.matches( format.getMediaType(), format.getEncoding(), format.getClockRate(), channels, fmtps)) return format; } return null; } /** * Creates a <tt>MediaFormat</tt> for the specified <tt>encoding</tt>, <tt>clockRate</tt> and set * of format parameters. If <tt>encoding</tt> is known to this <tt>MediaFormatFactory</tt>, * returns a <tt>MediaFormat</tt> which is either an <tt>AudioMediaFormat</tt> or a * <tt>VideoMediaFormat</tt> instance. Otherwise, returns <tt>null</tt>. * * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for * @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt> for * @param formatParams any codec specific parameters which have been received via SIP/SDP or * XMPP/Jingle * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt>, <tt>clockRate</tt> and set * of format parameters which is either an <tt>AudioMediaFormat</tt> or a * <tt>VideoMediaFormat</tt> instance if <tt>encoding</tt> is known to this * <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(String, double, Map, Map) */ public MediaFormat createMediaFormat( String encoding, double clockRate, Map<String, String> formatParams, Map<String, String> advancedParams) { return createMediaFormat(encoding, clockRate, 1, -1, formatParams, advancedParams); } /** * Creates a <tt>MediaFormat</tt> for the specified <tt>encoding</tt>, <tt>clockRate</tt>, * <tt>channels</tt> and set of format parameters. If <tt>encoding</tt> is known to this * <tt>MediaFormatFactory</tt>, returns a <tt>MediaFormat</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance. Otherwise, returns * <tt>null</tt>. * * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for * @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt> for * @param frameRate the frame rate in number of frames per second to create a <tt>MediaFormat</tt> * for * @param channels the number of available channels (1 for mono, 2 for stereo) if it makes sense * for the <tt>MediaFormat</tt> with the specified <tt>encoding</tt>; otherwise, ignored * @param formatParams any codec specific parameters which have been received via SIP/SDP or * XMPP/Jingle * @param advancedParams any parameters which have been received via SIP/SDP or XMPP/Jingle * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt>, <tt>clockRate</tt>, * <tt>channels</tt> and set of format parameters which is either an <tt>AudioMediaFormat</tt> * or a <tt>VideoMediaFormat</tt> instance if <tt>encoding</tt> is known to this * <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> * @see MediaFormatFactory#createMediaFormat(String, double, int, float, Map, Map) */ public MediaFormat createMediaFormat( String encoding, double clockRate, int channels, float frameRate, Map<String, String> formatParams, Map<String, String> advancedParams) { MediaFormat mediaFormat = createMediaFormat(encoding, clockRate, channels, formatParams); if (mediaFormat == null) return null; /* * MediaFormatImpl is immutable so if the caller wants to change the * format parameters and/or the advanced attributes, we'll have to * create a new MediaFormatImpl. */ Map<String, String> formatParameters = null; Map<String, String> advancedParameters = null; if ((formatParams != null) && !formatParams.isEmpty()) formatParameters = formatParams; if ((advancedParams != null) && !advancedParams.isEmpty()) advancedParameters = advancedParams; if ((formatParameters != null) || (advancedParameters != null)) { switch (mediaFormat.getMediaType()) { case AUDIO: mediaFormat = new AudioMediaFormatImpl( ((AudioMediaFormatImpl) mediaFormat).getFormat(), formatParameters, advancedParameters); break; case VIDEO: VideoMediaFormatImpl videoMediaFormatImpl = (VideoMediaFormatImpl) mediaFormat; /* * If the format of VideoMediaFormatImpl is * a ParameterizedVideoFormat, it's possible for the format * parameters of that ParameterizedVideoFormat and of the new * VideoMediaFormatImpl (to be created) to be out of sync. While * it's not technically perfect, it should be practically safe * for the format parameters which distinguish VideoFormats with * the same encoding and clock rate because mediaFormat has * already been created in sync with formatParams (with respect * to the format parameters which distinguish VideoFormats with * the same encoding and clock rate). */ mediaFormat = new VideoMediaFormatImpl( videoMediaFormatImpl.getFormat(), videoMediaFormatImpl.getClockRate(), frameRate, formatParameters, advancedParameters); break; default: mediaFormat = null; } } return mediaFormat; } /** * Creates a <tt>MediaFormat</tt> either for the specified <tt>rtpPayloadType</tt> or for the * specified <tt>encoding</tt>, <tt>clockRate</tt>, <tt>channels</tt> and set of format * parameters. If <tt>encoding</tt> is known to this <tt>MediaFormatFactory</tt>, ignores * <tt>rtpPayloadType</tt> and returns a <tt>MediaFormat</tt> which is either an * <tt>AudioMediaFormat</tt> or a <tt>VideoMediaFormat</tt> instance. If <tt>rtpPayloadType</tt> * is not {@link MediaFormat#RTP_PAYLOAD_TYPE_UNKNOWN} and <tt>encoding</tt> is <tt>null</tt>, * uses the encoding associated with <tt>rtpPayloadType</tt>. * * @param rtpPayloadType the RTP payload type to create a <tt>MediaFormat</tt> for; {@link * MediaFormat#RTP_PAYLOAD_TYPE_UNKNOWN} if <tt>encoding</tt> is not <tt>null</tt>. If * <tt>rtpPayloadType</tt> is not <tt>MediaFormat#RTP_PAYLOAD_TYPE_UNKNOWN</tt> and * <tt>encoding</tt> is not <tt>null</tt>, <tt>rtpPayloadType</tt> is ignored * @param encoding the well-known encoding (name) to create a <tt>MediaFormat</tt> for; * <tt>null</tt> * @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt> for * @param frameRate the frame rate in number of frames per second to create a <tt>MediaFormat</tt> * for * @param channels the number of available channels (1 for mono, 2 for stereo) if it makes sense * for the <tt>MediaFormat</tt> with the specified <tt>encoding</tt>; otherwise, ignored * @param formatParams any codec specific parameters which have been received via SIP/SDP or * XMPP/Jingle * @param advancedParams any parameters which have been received via SIP/SDP or XMPP/Jingle * @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt>, <tt>clockRate</tt>, * <tt>channels</tt> and set of format parameters which is either an <tt>AudioMediaFormat</tt> * or a <tt>VideoMediaFormat</tt> instance if <tt>encoding</tt> is known to this * <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt> */ public MediaFormat createMediaFormat( byte rtpPayloadType, String encoding, double clockRate, int channels, float frameRate, Map<String, String> formatParams, Map<String, String> advancedParams) { /* * If rtpPayloadType is specified, use it only to figure out encoding * and/or clockRate in case either one of them is unknown. */ if ((MediaFormat.RTP_PAYLOAD_TYPE_UNKNOWN != rtpPayloadType) && ((encoding == null) || (CLOCK_RATE_NOT_SPECIFIED == clockRate))) { MediaFormat[] rtpPayloadTypeMediaFormats = MediaUtils.getMediaFormats(rtpPayloadType); if (rtpPayloadTypeMediaFormats.length > 0) { if (encoding == null) encoding = rtpPayloadTypeMediaFormats[0].getEncoding(); // Assign or check the clock rate. if (CLOCK_RATE_NOT_SPECIFIED == clockRate) clockRate = rtpPayloadTypeMediaFormats[0].getClockRate(); else { boolean clockRateIsValid = false; for (MediaFormat rtpPayloadTypeMediaFormat : rtpPayloadTypeMediaFormats) if (rtpPayloadTypeMediaFormat.getEncoding().equals(encoding) && (rtpPayloadTypeMediaFormat.getClockRate() == clockRate)) { clockRateIsValid = true; break; } if (!clockRateIsValid) return null; } } } return createMediaFormat( encoding, clockRate, channels, frameRate, formatParams, advancedParams); } /** * Gets the <tt>MediaFormat</tt>s among the specified <tt>mediaFormats</tt> which have the * specified <tt>encoding</tt> and, optionally, <tt>clockRate</tt>. * * @param mediaFormats the <tt>MediaFormat</tt>s from which to filter out only the ones which have * the specified <tt>encoding</tt> and, optionally, <tt>clockRate</tt> * @param encoding the well-known encoding (name) of the <tt>MediaFormat</tt>s to be retrieved * @param clockRate the clock rate of the <tt>MediaFormat</tt>s to be retrieved; {@link * #CLOCK_RATE_NOT_SPECIFIED} if any clock rate is acceptable * @return a <tt>List</tt> of the <tt>MediaFormat</tt>s among <tt>mediaFormats</tt> which have the * specified <tt>encoding</tt> and, optionally, <tt>clockRate</tt> */ private List<MediaFormat> getMatchingMediaFormats( MediaFormat[] mediaFormats, String encoding, double clockRate) { /* * XXX Use String#equalsIgnoreCase(String) because some clients transmit * some of the codecs starting with capital letters. */ /* * As per RFC 3551.4.5.2, because of a mistake in RFC 1890 and for * backward compatibility, G.722 should always be announced as 8000 even * though it is wideband. So, if someone is looking for G722/16000, * then: Forgive them, for they know not what they do! */ if ("G722".equalsIgnoreCase(encoding) && (16000 == clockRate)) { clockRate = 8000; if (logger.isInfoEnabled()) logger.info("Suppressing erroneous 16000 announcement for G.722"); } List<MediaFormat> supportedMediaFormats = new ArrayList<MediaFormat>(); for (MediaFormat mediaFormat : mediaFormats) { if (mediaFormat.getEncoding().equalsIgnoreCase(encoding) && ((CLOCK_RATE_NOT_SPECIFIED == clockRate) || (mediaFormat.getClockRate() == clockRate))) { supportedMediaFormats.add(mediaFormat); } } return supportedMediaFormats; } /** * Gets the <tt>MediaFormat</tt>s supported by this <tt>MediaFormatFactory</tt> and the * <tt>MediaService</tt> associated with it and having the specified <tt>encoding</tt> and, * optionally, <tt>clockRate</tt>. * * @param encoding the well-known encoding (name) of the <tt>MediaFormat</tt>s to be retrieved * @param clockRate the clock rate of the <tt>MediaFormat</tt>s to be retrieved; {@link * #CLOCK_RATE_NOT_SPECIFIED} if any clock rate is acceptable * @return a <tt>List</tt> of the <tt>MediaFormat</tt>s supported by the <tt>MediaService</tt> * associated with this <tt>MediaFormatFactory</tt> and having the specified encoding and, * optionally, clock rate */ private List<MediaFormat> getSupportedMediaFormats(String encoding, double clockRate) { EncodingConfiguration encodingConfiguration = NeomediaServiceUtils.getMediaServiceImpl().getCurrentEncodingConfiguration(); List<MediaFormat> supportedMediaFormats = getMatchingMediaFormats( encodingConfiguration.getAllEncodings(MediaType.AUDIO), encoding, clockRate); if (supportedMediaFormats.isEmpty()) supportedMediaFormats = getMatchingMediaFormats( encodingConfiguration.getAllEncodings(MediaType.VIDEO), encoding, clockRate); return supportedMediaFormats; } }