diff --git a/webrtc/sdk/android/BUILD.gn b/webrtc/sdk/android/BUILD.gn index b12746d518..adac2a8214 100644 --- a/webrtc/sdk/android/BUILD.gn +++ b/webrtc/sdk/android/BUILD.gn @@ -341,6 +341,7 @@ android_library("libjingle_peerconnection_java") { "api/org/webrtc/GlShader.java", "api/org/webrtc/GlTextureFrameBuffer.java", "api/org/webrtc/GlUtil.java", + "api/org/webrtc/HardwareVideoDecoderFactory.java", "api/org/webrtc/HardwareVideoEncoderFactory.java", "api/org/webrtc/IceCandidate.java", "api/org/webrtc/MediaCodecVideoDecoder.java", @@ -371,6 +372,7 @@ android_library("libjingle_peerconnection_java") { "api/org/webrtc/VideoCodecInfo.java", "api/org/webrtc/VideoCodecStatus.java", "api/org/webrtc/VideoDecoder.java", + "api/org/webrtc/VideoDecoderFactory.java", "api/org/webrtc/VideoEncoder.java", "api/org/webrtc/VideoEncoderFactory.java", "api/org/webrtc/VideoFileRenderer.java", @@ -393,6 +395,7 @@ android_library("libjingle_peerconnection_java") { "src/java/org/webrtc/HardwareVideoEncoder.java", "src/java/org/webrtc/Histogram.java", "src/java/org/webrtc/I420BufferImpl.java", + "src/java/org/webrtc/MediaCodecUtils.java", "src/java/org/webrtc/VideoCodecType.java", "src/java/org/webrtc/WrappedNativeI420Buffer.java", "src/java/org/webrtc/YuvConverter.java", @@ -426,6 +429,7 @@ if (rtc_include_tests) { "instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java", "instrumentationtests/src/org/webrtc/GlRectDrawerTest.java", "instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java", + "instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java", "instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java", "instrumentationtests/src/org/webrtc/NetworkMonitorTest.java", "instrumentationtests/src/org/webrtc/PeerConnectionTest.java", diff --git a/webrtc/sdk/android/api/org/webrtc/HardwareVideoDecoderFactory.java b/webrtc/sdk/android/api/org/webrtc/HardwareVideoDecoderFactory.java new file mode 100644 index 0000000000..256f99e026 --- /dev/null +++ b/webrtc/sdk/android/api/org/webrtc/HardwareVideoDecoderFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +import static org.webrtc.MediaCodecUtils.EXYNOS_PREFIX; +import static org.webrtc.MediaCodecUtils.INTEL_PREFIX; +import static org.webrtc.MediaCodecUtils.NVIDIA_PREFIX; +import static org.webrtc.MediaCodecUtils.QCOM_PREFIX; + +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecList; +import android.os.Build; + +/** Factory for Android hardware VideoDecoders. */ +@SuppressWarnings("deprecation") // API level 16 requires use of deprecated methods. +public class HardwareVideoDecoderFactory implements VideoDecoderFactory { + private static final String TAG = "HardwareVideoDecoderFactory"; + + @Override + public VideoDecoder createDecoder(String codecType) { + VideoCodecType type = VideoCodecType.valueOf(codecType); + MediaCodecInfo info = findCodecForType(type); + + if (info == null) { + return null; // No support for this codec type. + } + + CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType()); + return new HardwareVideoDecoder(info.getName(), type, + MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities)); + } + + private MediaCodecInfo findCodecForType(VideoCodecType type) { + // HW decoding is not supported on builds before KITKAT. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return null; + } + + for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { + MediaCodecInfo info = null; + try { + info = MediaCodecList.getCodecInfoAt(i); + } catch (IllegalArgumentException e) { + Logging.e(TAG, "Cannot retrieve encoder codec info", e); + } + + if (info == null || info.isEncoder()) { + continue; + } + + if (isSupportedCodec(info, type)) { + return info; + } + } + return null; // No support for this type. + } + + // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type. + private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecType type) { + if (!MediaCodecUtils.codecSupportsType(info, type)) { + return false; + } + // Check for a supported color format. + if (MediaCodecUtils.selectColorFormat( + MediaCodecUtils.DECODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType())) + == null) { + return false; + } + return isHardwareSupported(info, type); + } + + private boolean isHardwareSupported(MediaCodecInfo info, VideoCodecType type) { + String name = info.getName(); + switch (type) { + case VP8: + // QCOM, Intel, Exynos, and Nvidia all supported for VP8. + return name.startsWith(QCOM_PREFIX) || name.startsWith(INTEL_PREFIX) + || name.startsWith(EXYNOS_PREFIX) || name.startsWith(NVIDIA_PREFIX); + case VP9: + // QCOM and Exynos supported for VP9. + return name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX); + case H264: + // QCOM, Intel, and Exynos supported for H264. + return name.startsWith(QCOM_PREFIX) || name.startsWith(INTEL_PREFIX) + || name.startsWith(EXYNOS_PREFIX); + default: + return false; + } + } +} diff --git a/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java b/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java index adf16462e7..9b904431b5 100644 --- a/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java +++ b/webrtc/sdk/android/api/org/webrtc/HardwareVideoEncoderFactory.java @@ -10,6 +10,10 @@ package org.webrtc; +import static org.webrtc.MediaCodecUtils.EXYNOS_PREFIX; +import static org.webrtc.MediaCodecUtils.INTEL_PREFIX; +import static org.webrtc.MediaCodecUtils.QCOM_PREFIX; + import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities; @@ -26,11 +30,6 @@ import java.util.Map; public class HardwareVideoEncoderFactory implements VideoEncoderFactory { private static final String TAG = "HardwareVideoEncoderFactory"; - // Prefixes for supported hardware encoder component names. - private static final String QCOM_PREFIX = "OMX.qcom."; - private static final String EXYNOS_PREFIX = "OMX.Exynos."; - private static final String INTEL_PREFIX = "OMX.Intel."; - // Forced key frame interval - used to reduce color distortions on Qualcomm platforms. private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000; private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000; @@ -42,17 +41,6 @@ public class HardwareVideoEncoderFactory implements VideoEncoderFactory { private static final List H264_HW_EXCEPTION_MODELS = Arrays.asList("SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"); - // NV12 color format supported by QCOM codec, but not declared in MediaCodec - - // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h - private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; - - // Supported color formats, in order of preference. - private static final int[] SUPPORTED_COLOR_FORMATS = { - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, - MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, - COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m}; - // Keys for H264 VideoCodecInfo properties. private static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id"; private static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed"; @@ -86,7 +74,8 @@ public class HardwareVideoEncoderFactory implements VideoEncoderFactory { String codecName = info.getName(); String mime = type.mimeType(); - int colorFormat = selectColorFormat(SUPPORTED_COLOR_FORMATS, info.getCapabilitiesForType(mime)); + int colorFormat = MediaCodecUtils.selectColorFormat( + MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(mime)); return new HardwareVideoEncoder(codecName, type, colorFormat, getKeyFrameIntervalSec(type), getForcedKeyFrameIntervalMs(type, codecName), createBitrateAdjuster(type, codecName)); @@ -134,37 +123,18 @@ public class HardwareVideoEncoderFactory implements VideoEncoderFactory { // Returns true if the given MediaCodecInfo indicates a supported encoder for the given type. private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecType type) { - if (!codecSupportsType(info, type)) { + if (!MediaCodecUtils.codecSupportsType(info, type)) { return false; } // Check for a supported color format. - if (selectColorFormat(SUPPORTED_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType())) + if (MediaCodecUtils.selectColorFormat( + MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType())) == null) { return false; } return isHardwareSupportedInCurrentSdk(info, type); } - private Integer selectColorFormat(int[] supportedColorFormats, CodecCapabilities capabilities) { - for (int supportedColorFormat : supportedColorFormats) { - for (int codecColorFormat : capabilities.colorFormats) { - if (codecColorFormat == supportedColorFormat) { - return codecColorFormat; - } - } - } - return null; - } - - private boolean codecSupportsType(MediaCodecInfo info, VideoCodecType type) { - for (String mimeType : info.getSupportedTypes()) { - if (type.mimeType().equals(mimeType)) { - return true; - } - } - return false; - } - // Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the // current SDK. private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecType type) { diff --git a/webrtc/sdk/android/api/org/webrtc/VideoDecoderFactory.java b/webrtc/sdk/android/api/org/webrtc/VideoDecoderFactory.java new file mode 100644 index 0000000000..1c01503f6d --- /dev/null +++ b/webrtc/sdk/android/api/org/webrtc/VideoDecoderFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +/** Factory for creating VideoDecoders. */ +public interface VideoDecoderFactory { + /** + * Creates a VideoDecoder for the given codec. Supports the same codecs supported by + * VideoEncoderFactory. + */ + public VideoDecoder createDecoder(String codecType); +} diff --git a/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java b/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java index e7611ff33f..0ea7df6719 100644 --- a/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java +++ b/webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java @@ -11,7 +11,7 @@ package org.webrtc; /** Factory for creating VideoEncoders. */ -interface VideoEncoderFactory { +public interface VideoEncoderFactory { /** Creates an encoder for the given video codec. */ public VideoEncoder createEncoder(VideoCodecInfo info); diff --git a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java new file mode 100644 index 0000000000..34fc42352f --- /dev/null +++ b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +import static org.junit.Assert.assertEquals; + +import android.annotation.TargetApi; +import android.graphics.Matrix; +import android.support.test.filters.MediumTest; +import android.util.Log; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CountDownLatch; +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link HardwareVideoDecoder}. */ +@TargetApi(16) +@RunWith(BaseJUnit4ClassRunner.class) +public final class HardwareVideoDecoderTest { + private static final String TAG = "HardwareVideoDecoderTest"; + + private static final boolean ENABLE_INTEL_VP8_ENCODER = true; + private static final boolean ENABLE_H264_HIGH_PROFILE = true; + private static final VideoDecoder.Settings SETTINGS = + new VideoDecoder.Settings(1 /* core */, 640 /* width */, 480 /* height */); + + @Test + @MediumTest + public void testInitialize() { + HardwareVideoEncoderFactory encoderFactory = + new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE); + VideoCodecInfo[] supportedCodecs = encoderFactory.getSupportedCodecs(); + if (supportedCodecs.length == 0) { + Log.i(TAG, "No hardware encoding support, skipping testInitialize"); + return; + } + + HardwareVideoDecoderFactory decoderFactory = new HardwareVideoDecoderFactory(); + + VideoDecoder decoder = decoderFactory.createDecoder(supportedCodecs[0].name); + assertEquals(decoder.initDecode(SETTINGS, null), VideoCodecStatus.OK); + assertEquals(decoder.release(), VideoCodecStatus.OK); + } + + @Test + @MediumTest + public void testDecode() throws InterruptedException { + HardwareVideoEncoderFactory encoderFactory = + new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE); + VideoCodecInfo[] supportedCodecs = encoderFactory.getSupportedCodecs(); + if (supportedCodecs.length == 0) { + Log.i(TAG, "No hardware encoding support, skipping testEncodeYuvBuffer"); + return; + } + + // Set up the decoder. + HardwareVideoDecoderFactory decoderFactory = new HardwareVideoDecoderFactory(); + VideoDecoder decoder = decoderFactory.createDecoder(supportedCodecs[0].name); + + final long presentationTimestampUs = 20000; + + final CountDownLatch decodeDone = new CountDownLatch(1); + final AtomicReference decoded = new AtomicReference<>(); + VideoDecoder.Callback decodeCallback = new VideoDecoder.Callback() { + @Override + public void onDecodedFrame(VideoFrame frame, Integer decodeTimeMs, Integer qp) { + decoded.set(frame); + decodeDone.countDown(); + } + }; + assertEquals(decoder.initDecode(SETTINGS, decodeCallback), VideoCodecStatus.OK); + + // Set up an encoder to produce a valid encoded frame. + VideoEncoder encoder = encoderFactory.createEncoder(supportedCodecs[0]); + final CountDownLatch encodeDone = new CountDownLatch(1); + final AtomicReference encoded = new AtomicReference<>(); + VideoEncoder.Callback encodeCallback = new VideoEncoder.Callback() { + @Override + public void onEncodedFrame(EncodedImage image, VideoEncoder.CodecSpecificInfo info) { + encoded.set(image); + encodeDone.countDown(); + } + }; + assertEquals( + encoder.initEncode( + new VideoEncoder.Settings(1, SETTINGS.width, SETTINGS.height, 300, 30), encodeCallback), + VideoCodecStatus.OK); + + // First, encode a frame. + VideoFrame.I420Buffer buffer = new I420BufferImpl(SETTINGS.width, SETTINGS.height); + VideoFrame frame = + new VideoFrame(buffer, 0 /* rotation */, presentationTimestampUs * 1000, new Matrix()); + VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo( + new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey}); + + assertEquals(encoder.encode(frame, info), VideoCodecStatus.OK); + + ThreadUtils.awaitUninterruptibly(encodeDone); + + // Now decode the frame. + assertEquals( + decoder.decode(encoded.get(), new VideoDecoder.DecodeInfo(false, 0)), VideoCodecStatus.OK); + + ThreadUtils.awaitUninterruptibly(decodeDone); + + frame = decoded.get(); + assertEquals(frame.getRotation(), 0); + assertEquals(frame.getTimestampNs(), presentationTimestampUs * 1000); + assertEquals(frame.getTransformMatrix(), new Matrix()); + assertEquals(frame.getBuffer().getWidth(), SETTINGS.width); + assertEquals(frame.getBuffer().getHeight(), SETTINGS.height); + + assertEquals(decoder.release(), VideoCodecStatus.OK); + assertEquals(encoder.release(), VideoCodecStatus.OK); + } +} diff --git a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java index 3aea48193b..1363dc876d 100644 --- a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java +++ b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java @@ -38,13 +38,6 @@ class HardwareVideoDecoder implements VideoDecoder { private static final String MEDIA_FORMAT_KEY_CROP_TOP = "crop-top"; private static final String MEDIA_FORMAT_KEY_CROP_BOTTOM = "crop-bottom"; - // NV12 color format supported by QCOM codec, but not declared in MediaCodec - - // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h - private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01; - private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02; - private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03; - private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; - // MediaCodec.release() occasionally hangs. Release stops waiting and reports failure after // this timeout. private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; @@ -132,7 +125,7 @@ class HardwareVideoDecoder implements VideoDecoder { try { codec = MediaCodec.createByCodecName(codecName); } catch (IOException | IllegalArgumentException e) { - Logging.e(TAG, "Cannot create media decoder"); + Logging.e(TAG, "Cannot create media decoder " + codecName); return VideoCodecStatus.ERROR; } try { @@ -168,8 +161,8 @@ class HardwareVideoDecoder implements VideoDecoder { int size = frame.buffer.remaining(); if (size == 0) { - Logging.e(TAG, "decode() - input buffer empty"); - return VideoCodecStatus.ERR_PARAMETER; + Logging.e(TAG, "decode() - no input data"); + return VideoCodecStatus.ERROR; } // Load dimensions from shared memory under the dimension lock. @@ -215,13 +208,13 @@ class HardwareVideoDecoder implements VideoDecoder { index = codec.dequeueInputBuffer(0 /* timeout */); } catch (IllegalStateException e) { Logging.e(TAG, "dequeueInputBuffer failed", e); - return VideoCodecStatus.ERROR; + return VideoCodecStatus.FALLBACK_SOFTWARE; } if (index < 0) { // Decoder is falling behind. No input buffers available. // The decoder can't simply drop frames; it might lose a key frame. Logging.e(TAG, "decode() - no HW buffers available; decoder falling behind"); - return VideoCodecStatus.ERROR; + return VideoCodecStatus.FALLBACK_SOFTWARE; } ByteBuffer buffer; @@ -229,12 +222,12 @@ class HardwareVideoDecoder implements VideoDecoder { buffer = codec.getInputBuffers()[index]; } catch (IllegalStateException e) { Logging.e(TAG, "getInputBuffers failed", e); - return VideoCodecStatus.ERROR; + return VideoCodecStatus.FALLBACK_SOFTWARE; } if (buffer.capacity() < size) { Logging.e(TAG, "decode() - HW buffer too small"); - return VideoCodecStatus.ERROR; + return VideoCodecStatus.FALLBACK_SOFTWARE; } buffer.put(frame.buffer); @@ -245,7 +238,7 @@ class HardwareVideoDecoder implements VideoDecoder { } catch (IllegalStateException e) { Logging.e(TAG, "queueInputBuffer failed", e); decodeStartTimes.pollLast(); - return VideoCodecStatus.ERROR; + return VideoCodecStatus.FALLBACK_SOFTWARE; } if (keyFrameRequired) { keyFrameRequired = false; @@ -472,18 +465,12 @@ class HardwareVideoDecoder implements VideoDecoder { } private boolean isSupportedColorFormat(int colorFormat) { - switch (colorFormat) { - case CodecCapabilities.COLOR_FormatYUV420Planar: - case CodecCapabilities.COLOR_FormatYUV420SemiPlanar: - case CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar: - case COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka: - case COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka: - case COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka: - case COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m: + for (int supported : MediaCodecUtils.DECODER_COLOR_FORMATS) { + if (supported == colorFormat) { return true; - default: - return false; + } } + return false; } private static void copyI420(ByteBuffer src, int offset, VideoFrame.I420Buffer frameBuffer, @@ -505,15 +492,14 @@ class HardwareVideoDecoder implements VideoDecoder { copyPlane(src, vPos, uvStride, frameBuffer.getDataV(), 0, frameBuffer.getStrideV(), chromaWidth, chromaHeight); - // If the sliceHeight is odd, duplicate the last rows of chroma. Copy the last row of the U and - // V channels and append them at the end of each channel. + // If the sliceHeight is odd, duplicate the last rows of chroma. if (sliceHeight % 2 != 0) { int strideU = frameBuffer.getStrideU(); - int endU = chromaHeight * strideU; - copyRow(frameBuffer.getDataU(), endU - strideU, frameBuffer.getDataU(), endU, chromaWidth); + uPos = chromaHeight * strideU; + copyRow(frameBuffer.getDataU(), uPos - strideU, frameBuffer.getDataU(), uPos, chromaWidth); int strideV = frameBuffer.getStrideV(); - int endV = chromaHeight * strideV; - copyRow(frameBuffer.getDataV(), endV - strideV, frameBuffer.getDataV(), endV, chromaWidth); + vPos = chromaHeight * strideV; + copyRow(frameBuffer.getDataV(), vPos - strideV, frameBuffer.getDataV(), vPos, chromaWidth); } } diff --git a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java index 105458bd45..5e8725777c 100644 --- a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java +++ b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java @@ -36,10 +36,6 @@ class HardwareVideoEncoder implements VideoEncoder { // constant until API level 21. private static final String KEY_BITRATE_MODE = "bitrate-mode"; - // NV12 color format supported by QCOM codec, but not declared in MediaCodec - - // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h - private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; - private static final int MAX_VIDEO_FRAMERATE = 30; // See MAX_ENCODER_Q_SIZE in androidmediaencoder_jni.cc. @@ -133,8 +129,9 @@ class HardwareVideoEncoder implements VideoEncoder { lastKeyFrameMs = -1; - codec = createCodecByName(codecName); - if (codec == null) { + try { + codec = MediaCodec.createByCodecName(codecName); + } catch (IOException | IllegalArgumentException e) { Logging.e(TAG, "Cannot create media encoder " + codecName); return VideoCodecStatus.ERROR; } @@ -418,15 +415,6 @@ class HardwareVideoEncoder implements VideoEncoder { } } - private static MediaCodec createCodecByName(String codecName) { - try { - return MediaCodec.createByCodecName(codecName); - } catch (IOException | IllegalArgumentException e) { - Logging.e(TAG, "createCodecByName failed", e); - return null; - } - } - /** * Enumeration of supported color formats used for MediaCodec's input. */ @@ -463,7 +451,7 @@ class HardwareVideoEncoder implements VideoEncoder { return I420; case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar: - case COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m: + case MediaCodecUtils.COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m: return NV12; default: throw new IllegalArgumentException("Unsupported colorFormat: " + colorFormat); diff --git a/webrtc/sdk/android/src/java/org/webrtc/MediaCodecUtils.java b/webrtc/sdk/android/src/java/org/webrtc/MediaCodecUtils.java new file mode 100644 index 0000000000..80bfdd72b9 --- /dev/null +++ b/webrtc/sdk/android/src/java/org/webrtc/MediaCodecUtils.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodec; + +/** Container class for static constants and helpers used with MediaCodec. */ +class MediaCodecUtils { + private static final String TAG = "MediaCodecUtils"; + + // Prefixes for supported hardware encoder/decoder component names. + static final String EXYNOS_PREFIX = "OMX.Exynos."; + static final String INTEL_PREFIX = "OMX.Intel."; + static final String NVIDIA_PREFIX = "OMX.Nvidia."; + static final String QCOM_PREFIX = "OMX.qcom."; + + // NV12 color format supported by QCOM codec, but not declared in MediaCodec - + // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h + static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01; + static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02; + static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03; + static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; + + // Color formats supported by hardware decoder - in order of preference. + static final int[] DECODER_COLOR_FORMATS = new int[] {CodecCapabilities.COLOR_FormatYUV420Planar, + CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + MediaCodecUtils.COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, + MediaCodecUtils.COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka, + MediaCodecUtils.COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka, + MediaCodecUtils.COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m}; + + // Color formats supported by hardware encoder - in order of preference. + static final int[] ENCODER_COLOR_FORMATS = { + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar, + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar, + MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, + MediaCodecUtils.COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m}; + + static Integer selectColorFormat(int[] supportedColorFormats, CodecCapabilities capabilities) { + for (int supportedColorFormat : supportedColorFormats) { + for (int codecColorFormat : capabilities.colorFormats) { + if (codecColorFormat == supportedColorFormat) { + return codecColorFormat; + } + } + } + return null; + } + + static boolean codecSupportsType(MediaCodecInfo info, VideoCodecType type) { + for (String mimeType : info.getSupportedTypes()) { + if (type.mimeType().equals(mimeType)) { + return true; + } + } + return false; + } + + private MediaCodecUtils() { + // This class should not be instantiated. + } +}