From 1b968f72c9a5db683b2ac2a4bd411988fbf7c0d1 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov <lyubomir.marinov@jitsi.org> Date: Wed, 19 Mar 2014 08:15:00 +0200 Subject: [PATCH] Enhances the support for received H.264 SDP format parameters such as sprop-parameter-sets. --- src/org/jitsi/impl/neomedia/MediaUtils.java | 3 +- .../codec/video/h264/DePacketizer.java | 47 ++++++- .../neomedia/codec/video/h264/JNIDecoder.java | 121 +++++++++++++++++- .../neomedia/codec/video/h264/JNIEncoder.java | 19 ++- .../neomedia/codec/video/h264/Packetizer.java | 14 +- .../device/VideoMediaDeviceSession.java | 6 +- .../neomedia/format/VideoMediaFormatImpl.java | 14 +- 7 files changed, 196 insertions(+), 28 deletions(-) diff --git a/src/org/jitsi/impl/neomedia/MediaUtils.java b/src/org/jitsi/impl/neomedia/MediaUtils.java index cab8124d..25a9e0a5 100644 --- a/src/org/jitsi/impl/neomedia/MediaUtils.java +++ b/src/org/jitsi/impl/neomedia/MediaUtils.java @@ -211,7 +211,8 @@ public class MediaUtils /* H264 */ Map<String, String> h264FormatParams = new HashMap<String, String>(); - String packetizationMode = "packetization-mode"; + String packetizationMode + = VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP; Map<String, String> h264AdvancedAttributes = new HashMap<String, String>(); diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/DePacketizer.java b/src/org/jitsi/impl/neomedia/codec/video/h264/DePacketizer.java index 40663ff4..acdc5206 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/DePacketizer.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/DePacketizer.java @@ -14,6 +14,7 @@ import net.sf.fmj.media.*; import org.jitsi.impl.neomedia.codec.*; +import org.jitsi.impl.neomedia.format.*; import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.control.*; import org.jitsi.util.*; @@ -156,9 +157,9 @@ public class DePacketizer public DePacketizer() { super( - "H264 DePacketizer", - VideoFormat.class, - new VideoFormat[] { new VideoFormat(Constants.H264) }); + "H264 DePacketizer", + VideoFormat.class, + new VideoFormat[] { new VideoFormat(Constants.H264) }); List<Format> inputFormats = new ArrayList<Format>(); @@ -508,6 +509,46 @@ else if (nal_unit_type == 28) // FU-A Fragmentation unit (FU) return ret; } + /** + * {@inheritDoc} + * + * Makes sure that the format parameters of a + * <tt>ParameterizedVideoFormat</tt> input which are of no concern to this + * <tt>DePacketizer</tt> get passed on through the output to the next + * <tt>Codec</tt> in the codec chain (i.e. <tt>JNIDecoder</tt>). + */ + @Override + protected Format[] getMatchingOutputFormats(Format inputFormat) + { + Format[] matchingOutputFormats + = super.getMatchingOutputFormats(inputFormat); + + if ((matchingOutputFormats != null) + && (matchingOutputFormats.length != 0) + && (inputFormat instanceof ParameterizedVideoFormat)) + { + Map<String,String> fmtps + = ((ParameterizedVideoFormat) inputFormat) + .getFormatParameters(); + + if (fmtps != null) + { + fmtps.remove(VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP); + if (!fmtps.isEmpty()) + { + for (int i = 0; i < matchingOutputFormats.length; i++) + { + matchingOutputFormats[i] + = new ParameterizedVideoFormat( + Constants.H264, + fmtps); + } + } + } + } + return matchingOutputFormats; + } + /** * Appends {@link #outputPaddingSize} number of bytes to <tt>out</tt> * beginning at index <tt>outOffset</tt>. The specified <tt>out</tt> is diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java index 11fcb16e..160b8b29 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIDecoder.java @@ -7,16 +7,20 @@ package org.jitsi.impl.neomedia.codec.video.h264; import java.awt.*; +import java.io.*; import javax.media.*; import javax.media.format.*; +import net.iharder.*; import net.sf.fmj.media.*; import org.jitsi.impl.neomedia.codec.*; import org.jitsi.impl.neomedia.codec.video.*; +import org.jitsi.impl.neomedia.format.*; import org.jitsi.service.neomedia.codec.*; import org.jitsi.service.neomedia.control.*; +import org.jitsi.util.*; /** * Decodes H.264 NAL units and returns the resulting frames as FFmpeg @@ -35,6 +39,12 @@ public class JNIDecoder private static final VideoFormat[] DEFAULT_OUTPUT_FORMATS = new VideoFormat[] { new AVFrameFormat(FFmpeg.PIX_FMT_YUV420P) }; + /** + * The <tt>Logger</tt> used by the <tt>JNIDecoder</tt> class and its + * instances to print debug-related information. + */ + private static final Logger logger = Logger.getLogger(JNIDecoder.class); + /** * Plugin name. */ @@ -87,7 +97,19 @@ public class JNIDecoder */ public JNIDecoder() { - inputFormats = new VideoFormat[] { new VideoFormat(Constants.H264) }; + inputFormats + = new VideoFormat[] + { + /* + * Explicitly state both ParameterizedVideoFormat (to + * receive any format parameters which may be of concern + * to this JNIDecoder) and VideoFormat (to make sure + * that nothing breaks because of equality and/or + * matching tests involving ParameterizedVideoFormat). + */ + new ParameterizedVideoFormat(Constants.H264), + new VideoFormat(Constants.H264) + }; outputFormats = DEFAULT_OUTPUT_FORMATS; } @@ -183,14 +205,18 @@ public Format[] getSupportedOutputFormats(Format inputFormat) Format[] supportedOutputFormats; if (inputFormat == null) + { supportedOutputFormats = outputFormats; + } else { // mismatch input format if (!(inputFormat instanceof VideoFormat) || (AbstractCodec2.matches(inputFormat, inputFormats) == null)) - supportedOutputFormats = new Format[0]; + { + supportedOutputFormats = AbstractCodec2.EMPTY_FORMATS; + } else { // match input format @@ -200,6 +226,86 @@ public Format[] getSupportedOutputFormats(Format inputFormat) return supportedOutputFormats; } + /** + * Handles any format parameters of the input and/or output <tt>Format</tt>s + * with which this <tt>JNIDecoder</tt> has been configured. For example, + * takes into account the format parameter <tt>sprop-parameter-sets</tt> if + * it is specified by the input <tt>Format</tt>. + */ + private void handleFmtps() + { + try + { + + Format f = getInputFormat(); + + if (f instanceof ParameterizedVideoFormat) + { + ParameterizedVideoFormat pvf = (ParameterizedVideoFormat) f; + String spropParameterSets + = pvf.getFormatParameter( + VideoMediaFormatImpl.H264_SPROP_PARAMETER_SETS_FMTP); + + if (spropParameterSets != null) + { + ByteArrayOutputStream nals = new ByteArrayOutputStream(); + + for (String s : spropParameterSets.split(",")) + { + if ((s != null) && (s.length() != 0)) + { + byte[] nal = Base64.decode(s); + + if ((nal != null) && (nal.length != 0)) + { + nals.write(DePacketizer.NAL_PREFIX); + nals.write(nal); + } + } + } + if (nals.size() != 0) + { + // Add padding because it seems to be required by FFmpeg. + for (int i = 0; + i < FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE; + i++) + { + nals.write(0); + } + + /* + * In accord with RFC 6184 "RTP Payload Format for H.264 + * Video", place the NAL units conveyed by + * sprop-parameter-sets in the NAL unit stream to precede + * any other NAL units in decoding order. + */ + FFmpeg.avcodec_decode_video( + avctx, + avframe.getPtr(), + got_picture, + nals.toByteArray(), nals.size()); + } + } + } + + /* + * Because the handling of format parameter is new at the time of this + * writing and it currently handles only the format parameter + * sprop-parameter-sets the failed handling of which will be made + * visible later on anyway, do not let it kill this JNIDecoder. + */ + } + catch (Throwable t) + { + if (t instanceof InterruptedException) + Thread.currentThread().interrupt(); + else if (t instanceof ThreadDeath) + throw (ThreadDeath) t; + else + logger.error("Failed to handle format parameters", t); + } + } + /** * Inits the codec instances. * @@ -236,6 +342,13 @@ public synchronized void open() opened = true; super.open(); + + /* + * After this JNIDecoder has been opened, handle format parameters such + * as sprop-parameter-sets which require this JNIDecoder to be in the + * opened state. + */ + handleFmtps(); } /** @@ -265,7 +378,7 @@ public synchronized int process(Buffer in, Buffer out) // Ask FFmpeg to decode. got_picture[0] = false; - // TODO Take into account the offset of inputBuffer. + // TODO Take into account the offset of the input Buffer. FFmpeg.avcodec_decode_video( avctx, avframe.getPtr(), @@ -317,7 +430,9 @@ public synchronized int process(Buffer in, Buffer out) long pts = FFmpeg.AV_NOPTS_VALUE; // TODO avframe_get_pts(avframe); if (pts == FFmpeg.AV_NOPTS_VALUE) + { out.setTimeStamp(Buffer.TIME_UNKNOWN); + } else { out.setTimeStamp(pts); diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java index ea39d84e..5c598723 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/JNIEncoder.java @@ -132,12 +132,6 @@ public class JNIEncoder */ private static final Logger logger = Logger.getLogger(JNIEncoder.class); - /** - * The name of the format parameter which specifies the packetization mode - * of H.264 RTP payload. - */ - public static final String PACKETIZATION_MODE_FMTP = "packetization-mode"; - /** * Minimum interval between two PLI request processing (in milliseconds). */ @@ -166,10 +160,10 @@ public class JNIEncoder = { new ParameterizedVideoFormat( Constants.H264, - PACKETIZATION_MODE_FMTP, "0"), + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, "0"), new ParameterizedVideoFormat( Constants.H264, - PACKETIZATION_MODE_FMTP, "1") + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, "1") }; public static final int X264_KEYINT_MAX_INFINITE = 1 << 30; @@ -364,7 +358,8 @@ private Format[] getMatchingOutputFormats(Format inputFormat) Format.byteArray, frameRate, ParameterizedVideoFormat.toMap( - PACKETIZATION_MODE_FMTP, + VideoMediaFormatImpl + .H264_PACKETIZATION_MODE_FMTP, packetizationModes[index])); } return matchingOutputFormats; @@ -916,7 +911,11 @@ public Format setOutputFormat(Format format) if (fmtps == null) fmtps = new HashMap<String, String>(); if (packetizationMode != null) - fmtps.put(PACKETIZATION_MODE_FMTP, packetizationMode); + { + fmtps.put( + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, + packetizationMode); + } outputFormat = new ParameterizedVideoFormat( diff --git a/src/org/jitsi/impl/neomedia/codec/video/h264/Packetizer.java b/src/org/jitsi/impl/neomedia/codec/video/h264/Packetizer.java index 3aa682ba..59d41984 100644 --- a/src/org/jitsi/impl/neomedia/codec/video/h264/Packetizer.java +++ b/src/org/jitsi/impl/neomedia/codec/video/h264/Packetizer.java @@ -47,10 +47,10 @@ public class Packetizer = { new ParameterizedVideoFormat( Constants.H264_RTP, - JNIEncoder.PACKETIZATION_MODE_FMTP, "0"), + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, "0"), new ParameterizedVideoFormat( Constants.H264_RTP, - JNIEncoder.PACKETIZATION_MODE_FMTP, "1") + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, "1") }; /** @@ -150,7 +150,8 @@ private Format[] getMatchingOutputFormats(Format input) Format.byteArray, frameRate, ParameterizedVideoFormat.toMap( - JNIEncoder.PACKETIZATION_MODE_FMTP, + VideoMediaFormatImpl + .H264_PACKETIZATION_MODE_FMTP, packetizationMode)) }; } @@ -182,7 +183,7 @@ private String getPacketizationMode(Format format) if (format instanceof ParameterizedVideoFormat) packetizationMode = ((ParameterizedVideoFormat) format).getFormatParameter( - JNIEncoder.PACKETIZATION_MODE_FMTP); + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP); if (packetizationMode == null) packetizationMode = "0"; @@ -535,10 +536,11 @@ public Format setOutputFormat(Format format) fmtps = ((ParameterizedVideoFormat) format).getFormatParameters(); if (fmtps == null) fmtps = new HashMap<String, String>(); - if (fmtps.get(JNIEncoder.PACKETIZATION_MODE_FMTP) == null) + if (fmtps.get(VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP) + == null) { fmtps.put( - JNIEncoder.PACKETIZATION_MODE_FMTP, + VideoMediaFormatImpl.H264_PACKETIZATION_MODE_FMTP, getPacketizationMode(inputFormat)); } diff --git a/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java b/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java index 0fa92239..85eda9b0 100644 --- a/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java +++ b/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java @@ -1110,8 +1110,7 @@ protected void playerConfigureComplete(Processor player) SwScale playerScaler = null; if ((trackControls != null) && (trackControls.length != 0) - /* We do not add scaler nor key frames control on Android - for now */ + /* We don't add SwScale, KeyFrameControl on Android. */ && !OSUtils.IS_ANDROID) { String fmjEncoding = getFormat().getJMFEncoding(); @@ -1715,7 +1714,8 @@ protected Format setProcessorFormat( = (formatParameters == null) ? null : formatParameters.get( - JNIEncoder.PACKETIZATION_MODE_FMTP); + VideoMediaFormatImpl + .H264_PACKETIZATION_MODE_FMTP); encoder.setPacketizationMode(packetizationMode); } diff --git a/src/org/jitsi/impl/neomedia/format/VideoMediaFormatImpl.java b/src/org/jitsi/impl/neomedia/format/VideoMediaFormatImpl.java index fcb19dd5..15af476f 100644 --- a/src/org/jitsi/impl/neomedia/format/VideoMediaFormatImpl.java +++ b/src/org/jitsi/impl/neomedia/format/VideoMediaFormatImpl.java @@ -30,6 +30,16 @@ public class VideoMediaFormatImpl */ public static final double DEFAULT_CLOCK_RATE = 90000; + /** + * The name of the format parameter which specifies the packetization mode + * of H.264 RTP payload. + */ + public static final String H264_PACKETIZATION_MODE_FMTP + = "packetization-mode"; + + public static final String H264_SPROP_PARAMETER_SETS_FMTP + = "sprop-parameter-sets"; + /** * The clock rate of this <tt>VideoMediaFormat</tt>. */ @@ -218,7 +228,7 @@ public static boolean formatParametersAreEqual( if ("H264".equalsIgnoreCase(encoding) || "h264/rtp".equalsIgnoreCase(encoding)) { - String packetizationMode = "packetization-mode"; + String packetizationMode = H264_PACKETIZATION_MODE_FMTP; String pm1 = null; String pm2 = null; @@ -287,7 +297,7 @@ public static boolean formatParametersMatch( if ("H264".equalsIgnoreCase(encoding) || "h264/rtp".equalsIgnoreCase(encoding)) { - String packetizationMode = "packetization-mode"; + String packetizationMode = H264_PACKETIZATION_MODE_FMTP; String pm1 = (fmtps1 == null) ? null : fmtps1.get(packetizationMode); String pm2 -- GitLab