Add support for platform software video decoder implementations.
Also enables support for all hardware implementations. Renames HardwareVideoDecoderFactory to MediaCodecVideoDecoderFactory. Renames HardwareVideoDecoder to AndroidVideoDecoder. Bug: webrtc:8538 Change-Id: I9b351f387526af4da61fb07c07fb4285bd833e19 Reviewed-on: https://webrtc-review.googlesource.com/97680 Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Reviewed-by: Magnus Jedvert <magjed@webrtc.org> Commit-Queue: Sami Kalliomäki <sakal@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24586}
This commit is contained in:
committed by
Commit Bot
parent
906add4b25
commit
389d2261c3
@ -24,11 +24,15 @@ import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import org.webrtc.ThreadUtils.ThreadChecker;
|
||||
|
||||
/** Android hardware video decoder. */
|
||||
/**
|
||||
* Android hardware video decoder.
|
||||
*/
|
||||
@TargetApi(16)
|
||||
@SuppressWarnings("deprecation") // Cannot support API 16 without using deprecated methods.
|
||||
class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
private static final String TAG = "HardwareVideoDecoder";
|
||||
@SuppressWarnings("deprecation")
|
||||
// Cannot support API 16 without using deprecated methods.
|
||||
// TODO(sakal): Rename to MediaCodecVideoDecoder once the deprecated implementation is removed.
|
||||
class AndroidVideoDecoder implements VideoDecoder, VideoSink {
|
||||
private static final String TAG = "AndroidVideoDecoder";
|
||||
|
||||
// TODO(magjed): Use MediaFormat.KEY_* constants when part of the public API.
|
||||
private static final String MEDIA_FORMAT_KEY_STRIDE = "stride";
|
||||
@ -100,7 +104,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
// on the decoder thread.
|
||||
private boolean keyFrameRequired;
|
||||
|
||||
private final EglBase.Context sharedContext;
|
||||
private final @Nullable EglBase.Context sharedContext;
|
||||
// Valid and immutable while the decoder is running.
|
||||
@Nullable private SurfaceTextureHelper surfaceTextureHelper;
|
||||
@Nullable private Surface surface = null;
|
||||
@ -126,11 +130,14 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
// Valid and immutable while the decoder is running.
|
||||
@Nullable private MediaCodecWrapper codec = null;
|
||||
|
||||
HardwareVideoDecoder(MediaCodecWrapperFactory mediaCodecWrapperFactory, String codecName,
|
||||
VideoCodecType codecType, int colorFormat, EglBase.Context sharedContext) {
|
||||
AndroidVideoDecoder(MediaCodecWrapperFactory mediaCodecWrapperFactory, String codecName,
|
||||
VideoCodecType codecType, int colorFormat, @Nullable EglBase.Context sharedContext) {
|
||||
if (!isSupportedColorFormat(colorFormat)) {
|
||||
throw new IllegalArgumentException("Unsupported color format: " + colorFormat);
|
||||
}
|
||||
Logging.d(TAG,
|
||||
"ctor name: " + codecName + " type: " + codecType + " color format: " + colorFormat
|
||||
+ " context: " + sharedContext);
|
||||
this.mediaCodecWrapperFactory = mediaCodecWrapperFactory;
|
||||
this.codecName = codecName;
|
||||
this.codecType = codecType;
|
||||
@ -155,7 +162,9 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
// Internal variant is used when restarting the codec due to reconfiguration.
|
||||
private VideoCodecStatus initDecodeInternal(int width, int height) {
|
||||
decoderThreadChecker.checkIsOnValidThread();
|
||||
Logging.d(TAG, "initDecodeInternal");
|
||||
Logging.d(TAG,
|
||||
"initDecodeInternal name: " + codecName + " type: " + codecType + " width: " + width
|
||||
+ " height: " + height);
|
||||
if (outputThread != null) {
|
||||
Logging.e(TAG, "initDecodeInternal called while the codec is already running");
|
||||
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||
@ -295,7 +304,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
|
||||
@Override
|
||||
public String getImplementationName() {
|
||||
return "HWDecoder";
|
||||
return codecName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -358,7 +367,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
}
|
||||
|
||||
private Thread createOutputThread() {
|
||||
return new Thread("HardwareVideoDecoder.outputThread") {
|
||||
return new Thread("AndroidVideoDecoder.outputThread") {
|
||||
@Override
|
||||
public void run() {
|
||||
outputThreadChecker = new ThreadChecker();
|
||||
@ -30,6 +30,7 @@ class MediaCodecUtils {
|
||||
static final String INTEL_PREFIX = "OMX.Intel.";
|
||||
static final String NVIDIA_PREFIX = "OMX.Nvidia.";
|
||||
static final String QCOM_PREFIX = "OMX.qcom.";
|
||||
static final String[] SOFTWARE_IMPLEMENTATION_PREFIXES = {"OMX.google.", "OMX.SEC."};
|
||||
|
||||
// NV12 color format supported by QCOM codec, but not declared in MediaCodec -
|
||||
// see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* 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.QCOM_PREFIX;
|
||||
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCodecList;
|
||||
import android.os.Build;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Factory for decoders backed by Android MediaCodec API. */
|
||||
@SuppressWarnings("deprecation") // API level 16 requires use of deprecated methods.
|
||||
class MediaCodecVideoDecoderFactory implements VideoDecoderFactory {
|
||||
private static final String TAG = "MediaCodecVideoDecoderFactory";
|
||||
|
||||
private final @Nullable EglBase.Context sharedContext;
|
||||
private final String[] prefixWhitelist;
|
||||
private final String[] prefixBlacklist;
|
||||
|
||||
/**
|
||||
* MediaCodecVideoDecoderFactory will support codecs whitelisted excluding those blacklisted.
|
||||
*
|
||||
* @param sharedContext The textures generated will be accessible from this context. May be null,
|
||||
* this disables texture support.
|
||||
* @param prefixWhitelist List of codec prefixes to be whitelisted.
|
||||
* @param prefixBlacklist List of codec prefixes to be blacklisted.
|
||||
*/
|
||||
public MediaCodecVideoDecoderFactory(
|
||||
@Nullable EglBase.Context sharedContext, String[] prefixWhitelist, String[] prefixBlacklist) {
|
||||
this.sharedContext = sharedContext;
|
||||
this.prefixWhitelist = Arrays.copyOf(prefixWhitelist, prefixWhitelist.length);
|
||||
this.prefixBlacklist = Arrays.copyOf(prefixBlacklist, prefixBlacklist.length);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public VideoDecoder createDecoder(VideoCodecInfo codecType) {
|
||||
VideoCodecType type = VideoCodecType.valueOf(codecType.getName());
|
||||
MediaCodecInfo info = findCodecForType(type);
|
||||
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType());
|
||||
return new AndroidVideoDecoder(new MediaCodecWrapperFactoryImpl(), info.getName(), type,
|
||||
MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities),
|
||||
sharedContext);
|
||||
}
|
||||
|
||||
@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(
|
||||
name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)));
|
||||
}
|
||||
|
||||
supportedCodecInfos.add(new VideoCodecInfo(
|
||||
name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)));
|
||||
}
|
||||
}
|
||||
|
||||
return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
|
||||
}
|
||||
|
||||
private @Nullable 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 decoder 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) {
|
||||
String name = info.getName();
|
||||
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 isWhitelisted(name) && !isBlacklisted(name);
|
||||
}
|
||||
|
||||
private boolean isWhitelisted(String name) {
|
||||
for (String prefix : prefixWhitelist) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBlacklisted(String name) {
|
||||
for (String prefix : prefixBlacklist) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isH264HighProfileSupported(MediaCodecInfo info) {
|
||||
String name = info.getName();
|
||||
// Support H.264 HP decoding on QCOM chips for Android L and above.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && name.startsWith(QCOM_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
// Support H.264 HP decoding on Exynos chips for Android M and above.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && name.startsWith(EXYNOS_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,7 @@ import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Subset of methods defined in {@link android.media.MediaCodec} needed by
|
||||
* {@link HardwareVideoEncoder} and {@link HardwareVideoDecoder}. This interface
|
||||
* {@link HardwareVideoEncoder} and {@link AndroidVideoDecoder}. This interface
|
||||
* exists to allow mocking and using a fake implementation in tests.
|
||||
*/
|
||||
interface MediaCodecWrapper {
|
||||
|
||||
Reference in New Issue
Block a user