Skip to content

Commit

Permalink
Merge pull request #35 from ittiam-systems:rtp-mpeg4
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 438000682
  • Loading branch information
icbaker committed Apr 6, 2022
2 parents 8089092 + ef9393a commit f48babb
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 6 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Expand Up @@ -36,6 +36,8 @@
dependency from the UI module to the ExoPlayer module. This is a
breaking change.
* RTSP:
* Add RTP reader for MPEG4
([#35](https://github.com/androidx/media/pull/35))
* Add RTP reader for HEVC
([#36](https://github.com/androidx/media/pull/36)).
* Add RTP reader for AMR. Currently only mono-channel, non-interleaved
Expand Down
Expand Up @@ -15,6 +15,8 @@
*/
package androidx.media3.common.util;

import static androidx.media3.common.util.Assertions.checkArgument;

import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
Expand All @@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
new String[] {"", "A", "B", "C"};

// MP4V-ES
private static final int VISUAL_OBJECT_LAYER = 1;
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00;

/**
* Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
Expand Down Expand Up @@ -72,6 +80,87 @@ public static boolean parseCea708InitializationData(List<byte[]> initializationD
&& initializationData.get(0)[0] == 1;
}

/**
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
*
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
* to parse.
* @return A pair of the video's width and height.
*/
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
byte[] videoSpecificConfig) {
int offset = 0;
boolean foundVOL = false;
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
while (offset + 3 < videoSpecificConfig.length) {
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
offset++;
continue;
}
foundVOL = true;
break;
}

checkArgument(foundVOL, "Invalid input: VOL not found.");

ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
// Skip the start codecs from the bitstream
scratchBits.skipBits((offset + 4) * 8);
scratchBits.skipBits(1); // random_accessible_vol
scratchBits.skipBits(8); // video_object_type_indication

if (scratchBits.readBit()) { // object_layer_identifier
scratchBits.skipBits(4); // video_object_layer_verid
scratchBits.skipBits(3); // video_object_layer_priority
}

int aspectRatioInfo = scratchBits.readBits(4);
if (aspectRatioInfo == EXTENDED_PAR) {
scratchBits.skipBits(8); // par_width
scratchBits.skipBits(8); // par_height
}

if (scratchBits.readBit()) { // vol_control_parameters
scratchBits.skipBits(2); // chroma_format
scratchBits.skipBits(1); // low_delay
if (scratchBits.readBit()) { // vbv_parameters
scratchBits.skipBits(79);
}
}

int videoObjectLayerShape = scratchBits.readBits(2);
checkArgument(
videoObjectLayerShape == RECTANGULAR,
"Only supports rectangular video object layer shape.");

checkArgument(scratchBits.readBit()); // marker_bit
int vopTimeIncrementResolution = scratchBits.readBits(16);
checkArgument(scratchBits.readBit()); // marker_bit

if (scratchBits.readBit()) { // fixed_vop_rate
checkArgument(vopTimeIncrementResolution > 0);
vopTimeIncrementResolution--;
int numBitsToSkip = 0;
while (vopTimeIncrementResolution > 0) {
numBitsToSkip++;
vopTimeIncrementResolution >>= 1;
}
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
}

checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerWidth = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit
int videoObjectLayerHeight = scratchBits.readBits(13);
checkArgument(scratchBits.readBit()); // marker_bit

scratchBits.skipBits(1); // interlaced

return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
}

/**
* Builds an RFC 6381 AVC codec string using the provided parameters.
*
Expand Down
Expand Up @@ -43,6 +43,7 @@ public final class RtpPayloadFormat {
private static final String RTP_MEDIA_AMR = "AMR";
private static final String RTP_MEDIA_AMR_WB = "AMR-WB";
private static final String RTP_MEDIA_MPEG4_GENERIC = "MPEG4-GENERIC";
private static final String RTP_MEDIA_MPEG4_VIDEO = "MP4V-ES";
private static final String RTP_MEDIA_H264 = "H264";
private static final String RTP_MEDIA_H265 = "H265";
private static final String RTP_MEDIA_PCM_L8 = "L8";
Expand All @@ -59,6 +60,7 @@ public static boolean isFormatSupported(MediaDescription mediaDescription) {
case RTP_MEDIA_AMR_WB:
case RTP_MEDIA_H264:
case RTP_MEDIA_H265:
case RTP_MEDIA_MPEG4_VIDEO:
case RTP_MEDIA_MPEG4_GENERIC:
case RTP_MEDIA_PCM_L8:
case RTP_MEDIA_PCM_L16:
Expand Down Expand Up @@ -86,10 +88,6 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
return MimeTypes.AUDIO_AMR_NB;
case RTP_MEDIA_AMR_WB:
return MimeTypes.AUDIO_AMR_WB;
case RTP_MEDIA_H264:
return MimeTypes.VIDEO_H264;
case RTP_MEDIA_H265:
return MimeTypes.VIDEO_H265;
case RTP_MEDIA_MPEG4_GENERIC:
return MimeTypes.AUDIO_AAC;
case RTP_MEDIA_PCM_L8:
Expand All @@ -99,6 +97,12 @@ public static String getMimeTypeFromRtpMediaType(String mediaType) {
return MimeTypes.AUDIO_ALAW;
case RTP_MEDIA_PCMU:
return MimeTypes.AUDIO_MLAW;
case RTP_MEDIA_H264:
return MimeTypes.VIDEO_H264;
case RTP_MEDIA_H265:
return MimeTypes.VIDEO_H265;
case RTP_MEDIA_MPEG4_VIDEO:
return MimeTypes.VIDEO_MP4V;
case RTP_MEDIA_VP8:
return MimeTypes.VIDEO_VP8;
default:
Expand Down
Expand Up @@ -25,6 +25,7 @@

import android.net.Uri;
import android.util.Base64;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
Expand All @@ -44,19 +45,42 @@
// Format specific parameter names.
private static final String PARAMETER_PROFILE_LEVEL_ID = "profile-level-id";
private static final String PARAMETER_SPROP_PARAMS = "sprop-parameter-sets";

private static final String PARAMETER_AMR_OCTET_ALIGN = "octet-align";
private static final String PARAMETER_AMR_INTERLEAVING = "interleaving";
private static final String PARAMETER_H265_SPROP_SPS = "sprop-sps";
private static final String PARAMETER_H265_SPROP_PPS = "sprop-pps";
private static final String PARAMETER_H265_SPROP_VPS = "sprop-vps";
private static final String PARAMETER_H265_SPROP_MAX_DON_DIFF = "sprop-max-don-diff";
private static final String PARAMETER_AMR_OCTET_ALIGN = "octet-align";
private static final String PARAMETER_AMR_INTERLEAVING = "interleaving";
private static final String PARAMETER_MP4V_CONFIG = "config";

/** Prefix for the RFC6381 codecs string for AAC formats. */
private static final String AAC_CODECS_PREFIX = "mp4a.40.";
/** Prefix for the RFC6381 codecs string for AVC formats. */
private static final String H264_CODECS_PREFIX = "avc1.";
/** Prefix for the RFC6416 codecs string for MPEG4V-ES formats. */
private static final String MPEG4_CODECS_PREFIX = "mp4v.";

private static final String GENERIC_CONTROL_ATTR = "*";
/**
* Default height for MP4V.
*
* <p>RFC6416 does not mandate codec specific data (like width and height) in the fmtp attribute.
* These values are taken from <a
* href=https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp;l=130
* >Android's software MP4V decoder</a>.
*/
private static final int DEFAULT_MP4V_WIDTH = 352;

/**
* Default height for MP4V.
*
* <p>RFC6416 does not mandate codec specific data (like width and height) in the fmtp attribute.
* These values are taken from <a
* href=https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mpeg4_h263/C2SoftMpeg4Dec.cpp;l=130
* >Android's software MP4V decoder</a>.
*/
private static final int DEFAULT_MP4V_HEIGHT = 288;

/**
* Default width for VP8.
Expand Down Expand Up @@ -156,6 +180,10 @@ public int hashCode() {
!fmtpParameters.containsKey(PARAMETER_AMR_INTERLEAVING),
"Interleaving mode is not currently supported.");
break;
case MimeTypes.VIDEO_MP4V:
checkArgument(!fmtpParameters.isEmpty());
processMPEG4FmtpAttribute(formatBuilder, fmtpParameters);
break;
case MimeTypes.VIDEO_H264:
checkArgument(!fmtpParameters.isEmpty());
processH264FmtpAttribute(formatBuilder, fmtpParameters);
Expand Down Expand Up @@ -214,6 +242,23 @@ private static void processAacFmtpAttribute(
AacUtil.buildAacLcAudioSpecificConfig(sampleRate, channelCount)));
}

private static void processMPEG4FmtpAttribute(
Format.Builder formatBuilder, ImmutableMap<String, String> fmtpAttributes) {
@Nullable String configInput = fmtpAttributes.get(PARAMETER_MP4V_CONFIG);
if (configInput != null) {
byte[] configBuffer = Util.getBytesFromHexString(configInput);
formatBuilder.setInitializationData(ImmutableList.of(configBuffer));
Pair<Integer, Integer> resolution =
CodecSpecificDataUtil.getVideoResolutionFromMpeg4VideoConfig(configBuffer);
formatBuilder.setWidth(resolution.first).setHeight(resolution.second);
} else {
// set the default width and height
formatBuilder.setWidth(DEFAULT_MP4V_WIDTH).setHeight(DEFAULT_MP4V_HEIGHT);
}
@Nullable String profileLevel = fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID);
formatBuilder.setCodecs(MPEG4_CODECS_PREFIX + (profileLevel == null ? "1" : profileLevel));
}

/** Returns H264/H265 initialization data from the RTP parameter set. */
private static byte[] getInitializationDataFromParameterSet(String parameterSet) {
byte[] decodedParameterNalData = Base64.decode(parameterSet, Base64.DEFAULT);
Expand Down
Expand Up @@ -47,6 +47,8 @@ public RtpPayloadReader createPayloadReader(RtpPayloadFormat payloadFormat) {
return new RtpH264Reader(payloadFormat);
case MimeTypes.VIDEO_H265:
return new RtpH265Reader(payloadFormat);
case MimeTypes.VIDEO_MP4V:
return new RtpMpeg4Reader(payloadFormat);
case MimeTypes.VIDEO_VP8:
return new RtpVp8Reader(payloadFormat);
default:
Expand Down

0 comments on commit f48babb

Please sign in to comment.