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:
Sami Kalliomäki
2018-09-05 16:38:57 +02:00
committed by Commit Bot
parent 906add4b25
commit 389d2261c3
13 changed files with 258 additions and 184 deletions

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {