Create the VideoEncoderFactory and implement it.
Adds the VideoEncoderFactory interface and implements it for use with HardwareVideoEncoder. This uses MediaCodecVideoEncoder's initialization code as an example. BUG=webrtc:7760 Change-Id: I9fbc93ce9ac4ad866750a4386c4f15e800a3073e Reviewed-on: https://chromium-review.googlesource.com/530063 Commit-Queue: Bjorn Mellem <mellem@webrtc.org> Reviewed-by: Sami Kalliomäki <sakal@webrtc.org> Reviewed-by: Peter Thatcher <pthatcher@webrtc.org> Cr-Commit-Position: refs/heads/master@{#18636}
This commit is contained in:
@ -168,6 +168,7 @@ android_library("libjingle_peerconnection_java") {
|
|||||||
"api/org/webrtc/GlShader.java",
|
"api/org/webrtc/GlShader.java",
|
||||||
"api/org/webrtc/GlTextureFrameBuffer.java",
|
"api/org/webrtc/GlTextureFrameBuffer.java",
|
||||||
"api/org/webrtc/GlUtil.java",
|
"api/org/webrtc/GlUtil.java",
|
||||||
|
"api/org/webrtc/HardwareVideoEncoderFactory.java",
|
||||||
"api/org/webrtc/IceCandidate.java",
|
"api/org/webrtc/IceCandidate.java",
|
||||||
"api/org/webrtc/MediaCodecVideoDecoder.java",
|
"api/org/webrtc/MediaCodecVideoDecoder.java",
|
||||||
"api/org/webrtc/MediaCodecVideoEncoder.java",
|
"api/org/webrtc/MediaCodecVideoEncoder.java",
|
||||||
@ -198,6 +199,7 @@ android_library("libjingle_peerconnection_java") {
|
|||||||
"api/org/webrtc/VideoCodecStatus.java",
|
"api/org/webrtc/VideoCodecStatus.java",
|
||||||
"api/org/webrtc/VideoDecoder.java",
|
"api/org/webrtc/VideoDecoder.java",
|
||||||
"api/org/webrtc/VideoEncoder.java",
|
"api/org/webrtc/VideoEncoder.java",
|
||||||
|
"api/org/webrtc/VideoEncoderFactory.java",
|
||||||
"api/org/webrtc/VideoFileRenderer.java",
|
"api/org/webrtc/VideoFileRenderer.java",
|
||||||
"api/org/webrtc/VideoFrame.java",
|
"api/org/webrtc/VideoFrame.java",
|
||||||
"api/org/webrtc/VideoRenderer.java",
|
"api/org/webrtc/VideoRenderer.java",
|
||||||
@ -216,6 +218,8 @@ android_library("libjingle_peerconnection_java") {
|
|||||||
"src/java/org/webrtc/FramerateBitrateAdjuster.java",
|
"src/java/org/webrtc/FramerateBitrateAdjuster.java",
|
||||||
"src/java/org/webrtc/HardwareVideoEncoder.java",
|
"src/java/org/webrtc/HardwareVideoEncoder.java",
|
||||||
"src/java/org/webrtc/Histogram.java",
|
"src/java/org/webrtc/Histogram.java",
|
||||||
|
"src/java/org/webrtc/I420BufferImpl.java",
|
||||||
|
"src/java/org/webrtc/VideoCodecType.java",
|
||||||
"src/java/org/webrtc/WrappedNativeI420Buffer.java",
|
"src/java/org/webrtc/WrappedNativeI420Buffer.java",
|
||||||
"src/java/org/webrtc/YuvConverter.java",
|
"src/java/org/webrtc/YuvConverter.java",
|
||||||
]
|
]
|
||||||
@ -247,6 +251,7 @@ if (rtc_include_tests) {
|
|||||||
"instrumentationtests/src/org/webrtc/EglRendererTest.java",
|
"instrumentationtests/src/org/webrtc/EglRendererTest.java",
|
||||||
"instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java",
|
"instrumentationtests/src/org/webrtc/FileVideoCapturerTest.java",
|
||||||
"instrumentationtests/src/org/webrtc/GlRectDrawerTest.java",
|
"instrumentationtests/src/org/webrtc/GlRectDrawerTest.java",
|
||||||
|
"instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java",
|
||||||
"instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java",
|
"instrumentationtests/src/org/webrtc/MediaCodecVideoEncoderTest.java",
|
||||||
"instrumentationtests/src/org/webrtc/NetworkMonitorTest.java",
|
"instrumentationtests/src/org/webrtc/NetworkMonitorTest.java",
|
||||||
"instrumentationtests/src/org/webrtc/PeerConnectionTest.java",
|
"instrumentationtests/src/org/webrtc/PeerConnectionTest.java",
|
||||||
|
|||||||
@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
* 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.MediaCodecList;
|
||||||
|
import android.os.Build;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** Factory for android hardware video encoders. */
|
||||||
|
@SuppressWarnings("deprecation") // API 16 requires the use of deprecated methods.
|
||||||
|
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;
|
||||||
|
private static final int QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
private static final List<String> 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";
|
||||||
|
private static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
|
||||||
|
|
||||||
|
// Supported H264 profile ids and levels.
|
||||||
|
private static final String H264_PROFILE_CONSTRAINED_BASELINE = "4200";
|
||||||
|
private static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
|
||||||
|
private static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
|
||||||
|
private static final String H264_CONSTRAINED_BASELINE_3_1 =
|
||||||
|
H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;
|
||||||
|
private static final String H264_CONSTRAINED_HIGH_3_1 =
|
||||||
|
H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
|
||||||
|
|
||||||
|
private final boolean enableIntelVp8Encoder;
|
||||||
|
private final boolean enableH264HighProfile;
|
||||||
|
|
||||||
|
public HardwareVideoEncoderFactory(boolean enableIntelVp8Encoder, boolean enableH264HighProfile) {
|
||||||
|
this.enableIntelVp8Encoder = enableIntelVp8Encoder;
|
||||||
|
this.enableH264HighProfile = enableH264HighProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VideoEncoder createEncoder(VideoCodecInfo input) {
|
||||||
|
VideoCodecType type = VideoCodecType.valueOf(input.name);
|
||||||
|
MediaCodecInfo info = findCodecForType(type);
|
||||||
|
|
||||||
|
if (info == null) {
|
||||||
|
return null; // No support for this type.
|
||||||
|
}
|
||||||
|
|
||||||
|
String codecName = info.getName();
|
||||||
|
String mime = type.mimeType();
|
||||||
|
int colorFormat = selectColorFormat(SUPPORTED_COLOR_FORMATS, info.getCapabilitiesForType(mime));
|
||||||
|
|
||||||
|
return new HardwareVideoEncoder(codecName, type, colorFormat, getKeyFrameIntervalSec(type),
|
||||||
|
getForcedKeyFrameIntervalMs(type, codecName), createBitrateAdjuster(type, codecName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VideoCodecInfo[] getSupportedCodecs() {
|
||||||
|
List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>();
|
||||||
|
// Generate a list of supported codecs in order of preference:
|
||||||
|
// VP8, VP9, H264 (high profile), and H264 (baseline profile).
|
||||||
|
for (VideoCodecType type :
|
||||||
|
new VideoCodecType[] {VideoCodecType.VP8, VideoCodecType.VP9, VideoCodecType.H264}) {
|
||||||
|
MediaCodecInfo codec = findCodecForType(type);
|
||||||
|
if (codec != null) {
|
||||||
|
String name = type.name();
|
||||||
|
if (type == VideoCodecType.H264 && isH264HighProfileSupported(codec)) {
|
||||||
|
supportedCodecInfos.add(new VideoCodecInfo(0, name, getCodecProperties(type, true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedCodecInfos.add(new VideoCodecInfo(0, name, getCodecProperties(type, false)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaCodecInfo findCodecForType(VideoCodecType type) {
|
||||||
|
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 (!codecSupportsType(info, type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check for a supported color format.
|
||||||
|
if (selectColorFormat(SUPPORTED_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) {
|
||||||
|
switch (type) {
|
||||||
|
case VP8:
|
||||||
|
return isHardwareSupportedInCurrentSdkVp8(info);
|
||||||
|
case VP9:
|
||||||
|
return isHardwareSupportedInCurrentSdkVp9(info);
|
||||||
|
case H264:
|
||||||
|
return isHardwareSupportedInCurrentSdkH264(info);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info) {
|
||||||
|
String name = info.getName();
|
||||||
|
// QCOM Vp8 encoder is supported in KITKAT or later.
|
||||||
|
return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
|
// Exynos VP8 encoder is supported in M or later.
|
||||||
|
|| (name.startsWith(EXYNOS_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||||
|
// Intel Vp8 encoder is supported in LOLLIPOP or later, with the intel encoder enabled.
|
||||||
|
|| (name.startsWith(INTEL_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||||
|
&& enableIntelVp8Encoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHardwareSupportedInCurrentSdkVp9(MediaCodecInfo info) {
|
||||||
|
String name = info.getName();
|
||||||
|
return (name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX))
|
||||||
|
// Both QCOM and Exynos VP9 encoders are supported in N or later.
|
||||||
|
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) {
|
||||||
|
// First, H264 hardware might perform poorly on this model.
|
||||||
|
if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String name = info.getName();
|
||||||
|
// QCOM H264 encoder is supported in KITKAT or later.
|
||||||
|
return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
|
// Exynos H264 encoder is supported in LOLLIPOP or later.
|
||||||
|
|| (name.startsWith(EXYNOS_PREFIX)
|
||||||
|
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getKeyFrameIntervalSec(VideoCodecType type) {
|
||||||
|
switch (type) {
|
||||||
|
case VP8: // Fallthrough intended.
|
||||||
|
case VP9:
|
||||||
|
return 100;
|
||||||
|
case H264:
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unsupported VideoCodecType " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getForcedKeyFrameIntervalMs(VideoCodecType type, String codecName) {
|
||||||
|
if (type == VideoCodecType.VP8 && codecName.startsWith(QCOM_PREFIX)) {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
|
||||||
|
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||||
|
return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS;
|
||||||
|
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
|
||||||
|
return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS;
|
||||||
|
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
|
||||||
|
return QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Other codecs don't need key frame forcing.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitrateAdjuster createBitrateAdjuster(VideoCodecType type, String codecName) {
|
||||||
|
if (codecName.startsWith(EXYNOS_PREFIX)) {
|
||||||
|
if (type == VideoCodecType.VP8) {
|
||||||
|
// Exynos VP8 encoders need dynamic bitrate adjustment.
|
||||||
|
return new DynamicBitrateAdjuster();
|
||||||
|
} else {
|
||||||
|
// Exynos VP9 and H264 encoders need framerate-based bitrate adjustment.
|
||||||
|
return new FramerateBitrateAdjuster();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Other codecs don't need bitrate adjustment.
|
||||||
|
return new BaseBitrateAdjuster();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isH264HighProfileSupported(MediaCodecInfo info) {
|
||||||
|
return enableH264HighProfile && info.getName().startsWith(QCOM_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getCodecProperties(VideoCodecType type, boolean highProfile) {
|
||||||
|
switch (type) {
|
||||||
|
case VP8:
|
||||||
|
case VP9:
|
||||||
|
return new HashMap<String, String>();
|
||||||
|
case H264:
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
properties.put(H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
|
||||||
|
properties.put(H264_FMTP_PACKETIZATION_MODE, "1");
|
||||||
|
properties.put(H264_FMTP_PROFILE_LEVEL_ID,
|
||||||
|
highProfile ? H264_CONSTRAINED_HIGH_3_1 : H264_CONSTRAINED_BASELINE_3_1);
|
||||||
|
return properties;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported codec: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java
Normal file
20
webrtc/sdk/android/api/org/webrtc/VideoEncoderFactory.java
Normal file
@ -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 VideoEncoders. */
|
||||||
|
interface VideoEncoderFactory {
|
||||||
|
/** Creates an encoder for the given video codec. */
|
||||||
|
public VideoEncoder createEncoder(VideoCodecInfo info);
|
||||||
|
|
||||||
|
/** Enumerates the list of supported video codecs. */
|
||||||
|
public VideoCodecInfo[] getSupportedCodecs();
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.support.test.filters.SmallTest;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import org.chromium.base.test.BaseJUnit4ClassRunner;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
@RunWith(BaseJUnit4ClassRunner.class)
|
||||||
|
public class HardwareVideoEncoderTest {
|
||||||
|
final static String TAG = "MediaCodecVideoEncoderTest";
|
||||||
|
|
||||||
|
private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
|
||||||
|
private static final boolean ENABLE_H264_HIGH_PROFILE = true;
|
||||||
|
private static final VideoEncoder.Settings SETTINGS = new VideoEncoder.Settings(
|
||||||
|
1 /* core */, 640 /* width */, 480 /* height */, 300 /* kbps */, 30 /* fps */);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
public void testInitializeUsingYuvBuffer() {
|
||||||
|
HardwareVideoEncoderFactory factory =
|
||||||
|
new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
|
||||||
|
VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
|
||||||
|
if (supportedCodecs.length == 0) {
|
||||||
|
Log.w(TAG, "No hardware encoding support, skipping testInitializeUsingYuvBuffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
|
||||||
|
assertEquals(encoder.initEncode(SETTINGS, null), VideoCodecStatus.OK);
|
||||||
|
assertEquals(encoder.release(), VideoCodecStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
public void testEncodeYuvBuffer() throws InterruptedException {
|
||||||
|
HardwareVideoEncoderFactory factory =
|
||||||
|
new HardwareVideoEncoderFactory(ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
|
||||||
|
VideoCodecInfo[] supportedCodecs = factory.getSupportedCodecs();
|
||||||
|
if (supportedCodecs.length == 0) {
|
||||||
|
Log.w(TAG, "No hardware encoding support, skipping testEncodeYuvBuffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoEncoder encoder = factory.createEncoder(supportedCodecs[0]);
|
||||||
|
|
||||||
|
final long presentationTimestampUs = 20000;
|
||||||
|
final CountDownLatch encodeDone = new CountDownLatch(1);
|
||||||
|
|
||||||
|
VideoEncoder.Callback callback = new VideoEncoder.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onEncodedFrame(EncodedImage image, VideoEncoder.CodecSpecificInfo info) {
|
||||||
|
assertTrue(image.buffer.capacity() > 0);
|
||||||
|
assertEquals(image.encodedWidth, SETTINGS.width);
|
||||||
|
assertEquals(image.encodedHeight, SETTINGS.height);
|
||||||
|
assertEquals(image.captureTimeMs, presentationTimestampUs / 1000);
|
||||||
|
assertEquals(image.frameType, EncodedImage.FrameType.VideoFrameKey);
|
||||||
|
assertEquals(image.rotation, 0);
|
||||||
|
assertTrue(image.completeFrame);
|
||||||
|
|
||||||
|
encodeDone.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertEquals(encoder.initEncode(SETTINGS, callback), VideoCodecStatus.OK);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
assertEquals(encoder.release(), VideoCodecStatus.OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,15 +48,8 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
|
private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
|
||||||
private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
|
private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
|
||||||
|
|
||||||
// TODO(mellem): Maybe move mime types to the factory or a common location.
|
|
||||||
private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
|
|
||||||
private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
|
|
||||||
private static final String H264_MIME_TYPE = "video/avc";
|
|
||||||
private static final Set<String> SUPPORTED_MIME_TYPES =
|
|
||||||
new HashSet<>(Arrays.asList(VP8_MIME_TYPE, VP9_MIME_TYPE, H264_MIME_TYPE));
|
|
||||||
|
|
||||||
private final String codecName;
|
private final String codecName;
|
||||||
private final String mimeType;
|
private final VideoCodecType codecType;
|
||||||
private final int colorFormat;
|
private final int colorFormat;
|
||||||
private final ColorFormat inputColorFormat;
|
private final ColorFormat inputColorFormat;
|
||||||
// Base interval for generating key frames.
|
// Base interval for generating key frames.
|
||||||
@ -94,27 +87,23 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
private ByteBuffer configBuffer = null;
|
private ByteBuffer configBuffer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new HardwareVideoEncoder with the given codecName, mimeType, colorFormat, key frame
|
* Creates a new HardwareVideoEncoder with the given codecName, codecType, colorFormat, key frame
|
||||||
* intervals, and bitrateAdjuster.
|
* intervals, and bitrateAdjuster.
|
||||||
*
|
*
|
||||||
* @param codecName the hardware codec implementation to use
|
* @param codecName the hardware codec implementation to use
|
||||||
* @param mimeType MIME type of the codec's output; must be one of "video/x-vnd.on2.vp8",
|
* @param codecType the type of the given video codec (eg. VP8, VP9, or H264)
|
||||||
* "video/x-vnd.on2.vp9", or "video/avc"
|
|
||||||
* @param colorFormat color format used by the input buffer
|
* @param colorFormat color format used by the input buffer
|
||||||
* @param keyFrameIntervalSec interval in seconds between key frames; used to initialize the codec
|
* @param keyFrameIntervalSec interval in seconds between key frames; used to initialize the codec
|
||||||
* @param forceKeyFrameIntervalMs interval at which to force a key frame if one is not requested;
|
* @param forceKeyFrameIntervalMs interval at which to force a key frame if one is not requested;
|
||||||
* used to reduce distortion caused by some codec implementations
|
* used to reduce distortion caused by some codec implementations
|
||||||
* @param bitrateAdjuster algorithm used to correct codec implementations that do not produce the
|
* @param bitrateAdjuster algorithm used to correct codec implementations that do not produce the
|
||||||
* desired bitrates
|
* desired bitrates
|
||||||
* @throws IllegalArgumentException if either mimeType or colorFormat is unsupported
|
* @throws IllegalArgumentException if colorFormat is unsupported
|
||||||
*/
|
*/
|
||||||
public HardwareVideoEncoder(String codecName, String mimeType, int colorFormat,
|
public HardwareVideoEncoder(String codecName, VideoCodecType codecType, int colorFormat,
|
||||||
int keyFrameIntervalSec, int forceKeyFrameIntervalMs, BitrateAdjuster bitrateAdjuster) {
|
int keyFrameIntervalSec, int forceKeyFrameIntervalMs, BitrateAdjuster bitrateAdjuster) {
|
||||||
if (!SUPPORTED_MIME_TYPES.contains(mimeType)) {
|
|
||||||
throw new IllegalArgumentException("Unsupported MIME type: " + mimeType);
|
|
||||||
}
|
|
||||||
this.codecName = codecName;
|
this.codecName = codecName;
|
||||||
this.mimeType = mimeType;
|
this.codecType = codecType;
|
||||||
this.colorFormat = colorFormat;
|
this.colorFormat = colorFormat;
|
||||||
this.inputColorFormat = ColorFormat.valueOf(colorFormat);
|
this.inputColorFormat = ColorFormat.valueOf(colorFormat);
|
||||||
this.keyFrameIntervalSec = keyFrameIntervalSec;
|
this.keyFrameIntervalSec = keyFrameIntervalSec;
|
||||||
@ -150,7 +139,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.ERROR;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
MediaFormat format = MediaFormat.createVideoFormat(mimeType, width, height);
|
MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
|
||||||
format.setInteger(MediaFormat.KEY_BIT_RATE, adjustedBitrate);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, adjustedBitrate);
|
||||||
format.setInteger(KEY_BITRATE_MODE, VIDEO_ControlRateConstant);
|
format.setInteger(KEY_BITRATE_MODE, VIDEO_ControlRateConstant);
|
||||||
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
|
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
|
||||||
@ -369,7 +358,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
|
|
||||||
ByteBuffer frameBuffer;
|
ByteBuffer frameBuffer;
|
||||||
boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
|
||||||
if (isKeyFrame && mimeType.equals(H264_MIME_TYPE)) {
|
if (isKeyFrame && codecType == VideoCodecType.H264) {
|
||||||
Logging.d(TAG,
|
Logging.d(TAG,
|
||||||
"Prepending config frame of size " + configBuffer.capacity()
|
"Prepending config frame of size " + configBuffer.capacity()
|
||||||
+ " to output buffer with offset " + info.offset + ", size " + info.size);
|
+ " to output buffer with offset " + info.offset + ", size " + info.size);
|
||||||
|
|||||||
85
webrtc/sdk/android/src/java/org/webrtc/I420BufferImpl.java
Normal file
85
webrtc/sdk/android/src/java/org/webrtc/I420BufferImpl.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.nio.ByteBuffer;
|
||||||
|
import org.webrtc.VideoFrame.I420Buffer;
|
||||||
|
|
||||||
|
/** Implementation of an I420 VideoFrame buffer. */
|
||||||
|
class I420BufferImpl implements VideoFrame.I420Buffer {
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
private final int strideUV;
|
||||||
|
private final ByteBuffer y;
|
||||||
|
private final ByteBuffer u;
|
||||||
|
private final ByteBuffer v;
|
||||||
|
|
||||||
|
I420BufferImpl(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.strideUV = (width + 1) / 2;
|
||||||
|
int halfHeight = (height + 1) / 2;
|
||||||
|
this.y = ByteBuffer.allocateDirect(width * height);
|
||||||
|
this.u = ByteBuffer.allocateDirect(strideUV * halfHeight);
|
||||||
|
this.v = ByteBuffer.allocateDirect(strideUV * halfHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getDataY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getDataU() {
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getDataV() {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStrideY() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStrideU() {
|
||||||
|
return strideUV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStrideV() {
|
||||||
|
return strideUV;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public I420Buffer toI420() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void retain() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {}
|
||||||
|
}
|
||||||
28
webrtc/sdk/android/src/java/org/webrtc/VideoCodecType.java
Normal file
28
webrtc/sdk/android/src/java/org/webrtc/VideoCodecType.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/** Enumeration of supported video codec types. */
|
||||||
|
enum VideoCodecType {
|
||||||
|
VP8("video/x-vnd.on2.vp8"),
|
||||||
|
VP9("video/x-vnd.on2.vp9"),
|
||||||
|
H264("video/avc");
|
||||||
|
|
||||||
|
private final String mimeType;
|
||||||
|
|
||||||
|
private VideoCodecType(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
String mimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user