diff --git a/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoEncoder.java b/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoEncoder.java index c2666a419f..b3decbea9f 100644 --- a/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoEncoder.java +++ b/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoEncoder.java @@ -70,6 +70,7 @@ public class MediaCodecVideoEncoder { private MediaCodec mediaCodec; private ByteBuffer[] outputBuffers; private EglBase14 eglBase; + private int profile; private int width; private int height; private Surface inputSurface; @@ -79,6 +80,9 @@ public class MediaCodecVideoEncoder { private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9"; private static final String H264_MIME_TYPE = "video/avc"; + private static final int VIDEO_AVCProfileHigh = 8; + private static final int VIDEO_AVCLevel3 = 0x100; + // Type of bitrate adjustment for video encoder. public enum BitrateAdjustmentType { // No adjustment - video encoder has no known bitrate problem. @@ -92,6 +96,25 @@ public class MediaCodecVideoEncoder { DYNAMIC_ADJUSTMENT } + // Should be in sync with webrtc::H264::Profile. + public static enum H264Profile { + CONSTRAINED_BASELINE(0), + BASELINE(1), + MAIN(2), + CONSTRAINED_HIGH(3), + HIGH(4); + + private final int value; + + H264Profile(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + // Class describing supported media codec properties. private static class MediaCodecProperties { public final String codecPrefix; @@ -143,6 +166,13 @@ public class MediaCodecVideoEncoder { private static final MediaCodecProperties[] h264HwList = new MediaCodecProperties[] {qcomH264HwProperties, exynosH264HwProperties}; + // List of supported HW H.264 high profile encoders. + private static final MediaCodecProperties exynosH264HighProfileHwProperties = + new MediaCodecProperties( + "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT); + private static final MediaCodecProperties[] h264HighProfileHwList = + new MediaCodecProperties[] {exynosH264HighProfileHwProperties}; + // List of devices with poor H.264 encoder quality. // HW H.264 encoder on below devices has poor bitrate control - actual // bitrates deviates a lot from the target value. @@ -234,6 +264,11 @@ public class MediaCodecVideoEncoder { && (findHwEncoder(H264_MIME_TYPE, h264HwList, supportedColorList) != null); } + public static boolean isH264HighProfileHwSupported() { + return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE) + && (findHwEncoder(H264_MIME_TYPE, h264HighProfileHwList, supportedColorList) != null); + } + public static boolean isVp8HwSupportedUsingTextures() { return !hwEncoderDisabledTypes.contains(VP8_MIME_TYPE) && (findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedSurfaceColorList) != null); @@ -380,12 +415,14 @@ public class MediaCodecVideoEncoder { } } - boolean initEncode(VideoCodecType type, int width, int height, int kbps, int fps, + boolean initEncode(VideoCodecType type, int profile, int width, int height, int kbps, int fps, EglBase14.Context sharedContext) { final boolean useSurface = sharedContext != null; - Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + ". @ " + kbps - + " kbps. Fps: " + fps + ". Encode from texture : " + useSurface); + Logging.d(TAG, + "Java initEncode: " + type + ". Profile: " + profile + " : " + width + " x " + height + + ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " + useSurface); + this.profile = profile; this.width = width; this.height = height; if (mediaCodecThread != null) { @@ -394,6 +431,7 @@ public class MediaCodecVideoEncoder { EncoderProperties properties = null; String mime = null; int keyFrameIntervalSec = 0; + boolean configureH264HighProfile = false; if (type == VideoCodecType.VIDEO_CODEC_VP8) { mime = VP8_MIME_TYPE; properties = findHwEncoder( @@ -408,6 +446,16 @@ public class MediaCodecVideoEncoder { mime = H264_MIME_TYPE; properties = findHwEncoder( H264_MIME_TYPE, h264HwList, useSurface ? supportedSurfaceColorList : supportedColorList); + if (profile == H264Profile.CONSTRAINED_HIGH.getValue()) { + EncoderProperties h264HighProfileProperties = findHwEncoder(H264_MIME_TYPE, + h264HighProfileHwList, useSurface ? supportedSurfaceColorList : supportedColorList); + if (h264HighProfileProperties != null) { + Logging.d(TAG, "High profile H.264 encoder supported."); + configureH264HighProfile = true; + } else { + Logging.d(TAG, "High profile H.264 encoder requested, but not supported. Use baseline."); + } + } keyFrameIntervalSec = 20; } if (properties == null) { @@ -453,6 +501,10 @@ public class MediaCodecVideoEncoder { format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); format.setInteger(MediaFormat.KEY_FRAME_RATE, targetFps); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); + if (configureH264HighProfile) { + format.setInteger("profile", VIDEO_AVCProfileHigh); + format.setInteger("level", VIDEO_AVCLevel3); + } Logging.d(TAG, " Format: " + format); mediaCodec = createByCodecName(properties.codecName); this.type = type; @@ -711,6 +763,12 @@ public class MediaCodecVideoEncoder { outputBuffers[result].position(info.offset); outputBuffers[result].limit(info.offset + info.size); configData.put(outputBuffers[result]); + // Log few SPS header bytes to check profile and level. + String spsData = ""; + for (int i = 0; i < (info.size < 8 ? info.size : 8); i++) { + spsData += Integer.toHexString(configData.get(i) & 0xff) + " "; + } + Logging.d(TAG, spsData); // Release buffer back. mediaCodec.releaseOutputBuffer(result, false); // Query next output. diff --git a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java index f1259f5628..fdc7a2993d 100644 --- a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java +++ b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java @@ -30,6 +30,7 @@ import org.webrtc.MediaCodecVideoEncoder.OutputBufferInfo; @RunWith(BaseJUnit4ClassRunner.class) public class MediaCodecVideoEncoderTest { final static String TAG = "MediaCodecVideoEncoderTest"; + final static int profile = MediaCodecVideoEncoder.H264Profile.CONSTRAINED_BASELINE.getValue(); @Test @SmallTest @@ -40,7 +41,7 @@ public class MediaCodecVideoEncoderTest { } MediaCodecVideoEncoder encoder = new MediaCodecVideoEncoder(); assertTrue(encoder.initEncode( - MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, 640, 480, 300, 30, null)); + MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, 640, 480, 300, 30, null)); encoder.release(); } @@ -53,8 +54,8 @@ public class MediaCodecVideoEncoderTest { } EglBase14 eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN); MediaCodecVideoEncoder encoder = new MediaCodecVideoEncoder(); - assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, 640, 480, - 300, 30, eglBase.getEglBaseContext())); + assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, + 640, 480, 300, 30, eglBase.getEglBaseContext())); encoder.release(); eglBase.release(); } @@ -68,11 +69,11 @@ public class MediaCodecVideoEncoderTest { } MediaCodecVideoEncoder encoder = new MediaCodecVideoEncoder(); assertTrue(encoder.initEncode( - MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, 640, 480, 300, 30, null)); + MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, 640, 480, 300, 30, null)); encoder.release(); EglBase14 eglBase = new EglBase14(null, EglBase.CONFIG_PLAIN); - assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, 640, 480, - 300, 30, eglBase.getEglBaseContext())); + assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, + 640, 480, 300, 30, eglBase.getEglBaseContext())); encoder.release(); eglBase.release(); } @@ -92,8 +93,8 @@ public class MediaCodecVideoEncoderTest { MediaCodecVideoEncoder encoder = new MediaCodecVideoEncoder(); - assertTrue(encoder.initEncode( - MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, width, height, 300, 30, null)); + assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, + width, height, 300, 30, null)); ByteBuffer[] inputBuffers = encoder.getInputBuffers(); assertNotNull(inputBuffers); assertTrue(min_size <= inputBuffers[0].capacity()); @@ -144,8 +145,8 @@ public class MediaCodecVideoEncoderTest { MediaCodecVideoEncoder encoder = new MediaCodecVideoEncoder(); - assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, width, - height, 300, 30, eglOesBase.getEglBaseContext())); + assertTrue(encoder.initEncode(MediaCodecVideoEncoder.VideoCodecType.VIDEO_CODEC_VP8, profile, + width, height, 300, 30, eglOesBase.getEglBaseContext())); assertTrue( encoder.encodeTexture(true, oesTextureId, RendererCommon.identityMatrix(), presentationTs)); GlUtil.checkNoGLES2Error("encodeTexture"); diff --git a/webrtc/sdk/android/src/jni/androidmediaencoder_jni.cc b/webrtc/sdk/android/src/jni/androidmediaencoder_jni.cc index 39c1e96cbe..3f778a68e1 100644 --- a/webrtc/sdk/android/src/jni/androidmediaencoder_jni.cc +++ b/webrtc/sdk/android/src/jni/androidmediaencoder_jni.cc @@ -274,6 +274,7 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder { // value and the next Encode() call being ignored. bool drop_next_input_frame_; bool scale_; + webrtc::H264::Profile profile_; // Global references; must be deleted in Release(). std::vector input_buffers_; webrtc::H264BitstreamParser h264_bitstream_parser_; @@ -335,12 +336,10 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni, jclass j_output_buffer_info_class = FindClass(jni, "org/webrtc/MediaCodecVideoEncoder$OutputBufferInfo"); - j_init_encode_method_ = GetMethodID( - jni, - *j_media_codec_video_encoder_class_, - "initEncode", - "(Lorg/webrtc/MediaCodecVideoEncoder$VideoCodecType;" - "IIIILorg/webrtc/EglBase14$Context;)Z"); + j_init_encode_method_ = + GetMethodID(jni, *j_media_codec_video_encoder_class_, "initEncode", + "(Lorg/webrtc/MediaCodecVideoEncoder$VideoCodecType;" + "IIIIILorg/webrtc/EglBase14$Context;)Z"); j_get_input_buffers_method_ = GetMethodID( jni, *j_media_codec_video_encoder_class_, @@ -419,6 +418,16 @@ int32_t MediaCodecVideoEncoder::InitEncode( ALOGD << "InitEncode request: " << init_width << " x " << init_height; ALOGD << "Encoder automatic resize " << (scale_ ? "enabled" : "disabled"); + // Check allowed H.264 profile + profile_ = webrtc::H264::Profile::kProfileBaseline; + if (codec_type == kVideoCodecH264) { + const rtc::Optional profile_level_id = + webrtc::H264::ParseSdpProfileLevelId(codec_.params); + RTC_DCHECK(profile_level_id); + profile_ = profile_level_id->profile; + ALOGD << "H.264 profile: " << profile_; + } + return InitEncodeInternal( init_width, init_height, codec_settings->startBitrate, codec_settings->maxFramerate, codec_settings->expect_encode_from_texture); @@ -531,7 +540,7 @@ int32_t MediaCodecVideoEncoder::InitEncodeInternal(int width, const VideoCodecType codec_type = GetCodecType(); ALOGD << "InitEncodeInternal Type: " << static_cast(codec_type) << ", " << width << " x " << height << ". Bitrate: " << kbps - << " kbps. Fps: " << fps; + << " kbps. Fps: " << fps << ". Profile: " << profile_ << "."; if (kbps == 0) { kbps = last_set_bitrate_kbps_; } @@ -570,8 +579,8 @@ int32_t MediaCodecVideoEncoder::InitEncodeInternal(int width, jobject j_video_codec_enum = JavaEnumFromIndexAndClassName( jni, "MediaCodecVideoEncoder$VideoCodecType", codec_type); const bool encode_status = jni->CallBooleanMethod( - *j_media_codec_video_encoder_, j_init_encode_method_, - j_video_codec_enum, width, height, kbps, fps, + *j_media_codec_video_encoder_, j_init_encode_method_, j_video_codec_enum, + profile_, width, height, kbps, fps, (use_surface ? egl_context_ : nullptr)); if (!encode_status) { ALOGE << "Failed to configure encoder."; @@ -1252,7 +1261,7 @@ MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() CHECK_EXCEPTION(jni); if (is_vp8_hw_supported) { ALOGD << "VP8 HW Encoder supported."; - supported_codecs_.push_back(cricket::VideoCodec("VP8")); + supported_codecs_.push_back(cricket::VideoCodec(cricket::kVp8CodecName)); } bool is_vp9_hw_supported = jni->CallStaticBooleanMethod( @@ -1261,7 +1270,7 @@ MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() CHECK_EXCEPTION(jni); if (is_vp9_hw_supported) { ALOGD << "VP9 HW Encoder supported."; - supported_codecs_.push_back(cricket::VideoCodec("VP9")); + supported_codecs_.push_back(cricket::VideoCodec(cricket::kVp9CodecName)); } supported_codecs_with_h264_hp_ = supported_codecs_;