diff options
Diffstat (limited to 'tuner/src/com/google/android/exoplayer')
-rw-r--r-- | tuner/src/com/google/android/exoplayer/MediaFormatUtil.java | 96 | ||||
-rw-r--r-- | tuner/src/com/google/android/exoplayer/MediaSoftwareCodecUtil.java | 273 |
2 files changed, 369 insertions, 0 deletions
diff --git a/tuner/src/com/google/android/exoplayer/MediaFormatUtil.java b/tuner/src/com/google/android/exoplayer/MediaFormatUtil.java new file mode 100644 index 00000000..c7571b23 --- /dev/null +++ b/tuner/src/com/google/android/exoplayer/MediaFormatUtil.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer; + +import android.support.annotation.Nullable; +import com.google.android.exoplayer.util.MimeTypes; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** {@link MediaFormat} creation helper util */ +public class MediaFormatUtil { + + /** + * Creates {@link MediaFormat} from {@link android.media.MediaFormat}. Since {@link + * com.google.android.exoplayer.TrackRenderer} uses {@link MediaFormat}, {@link + * android.media.MediaFormat} should be converted to be used with ExoPlayer. + */ + public static MediaFormat createMediaFormat(android.media.MediaFormat format) { + String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); + String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE); + int maxInputSize = + getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE); + int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH); + int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); + int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees"); + int channelCount = + getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); + int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); + int encoderDelay = getOptionalIntegerV16(format, "encoder-delay"); + int encoderPadding = getOptionalIntegerV16(format, "encoder-padding"); + ArrayList<byte[]> initializationData = new ArrayList<>(); + for (int i = 0; format.containsKey("csd-" + i); i++) { + ByteBuffer buffer = format.getByteBuffer("csd-" + i); + byte[] data = new byte[buffer.limit()]; + buffer.get(data); + initializationData.add(data); + buffer.flip(); + } + long durationUs = + format.containsKey(android.media.MediaFormat.KEY_DURATION) + ? format.getLong(android.media.MediaFormat.KEY_DURATION) + : C.UNKNOWN_TIME_US; + int pcmEncoding = + MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : MediaFormat.NO_VALUE; + MediaFormat mediaFormat = + new MediaFormat( + null, // trackId + mimeType, + MediaFormat.NO_VALUE, + maxInputSize, + durationUs, + width, + height, + rotationDegrees, + MediaFormat.NO_VALUE, + channelCount, + sampleRate, + language, + MediaFormat.OFFSET_SAMPLE_RELATIVE, + initializationData, + false, + MediaFormat.NO_VALUE, + MediaFormat.NO_VALUE, + pcmEncoding, + encoderDelay, + encoderPadding, + null, // projectionData + MediaFormat.NO_VALUE, + null // colorValue + ); + mediaFormat.setFrameworkFormatV16(format); + return mediaFormat; + } + + @Nullable + private static String getOptionalStringV16(android.media.MediaFormat format, String key) { + return format.containsKey(key) ? format.getString(key) : null; + } + + private static int getOptionalIntegerV16(android.media.MediaFormat format, String key) { + return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE; + } +} diff --git a/tuner/src/com/google/android/exoplayer/MediaSoftwareCodecUtil.java b/tuner/src/com/google/android/exoplayer/MediaSoftwareCodecUtil.java new file mode 100644 index 00000000..cf74f106 --- /dev/null +++ b/tuner/src/com/google/android/exoplayer/MediaSoftwareCodecUtil.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer; + +import android.annotation.TargetApi; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import com.google.android.exoplayer.util.MimeTypes; +import java.util.HashMap; + +/** + * Mostly copied from {@link com.google.android.exoplayer.MediaCodecUtil} in order to choose + * software codec over hardware codec. + */ +public class MediaSoftwareCodecUtil { + private static final String TAG = "MediaSoftwareCodecUtil"; + + /** + * Thrown when an error occurs querying the device for its underlying media capabilities. + * + * <p>Such failures are not expected in normal operation and are normally temporary (e.g. if the + * mediaserver process has crashed and is yet to restart). + */ + public static class DecoderQueryException extends Exception { + + private DecoderQueryException(Throwable cause) { + super("Failed to query underlying media codecs", cause); + } + } + + private static final HashMap<CodecKey, Pair<String, MediaCodecInfo.CodecCapabilities>> + sSwCodecs = new HashMap<>(); + + /** Gets information about the software decoder that will be used for a given mime type. */ + public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure) + throws DecoderQueryException { + // TODO: Add a test for this method. + Pair<String, MediaCodecInfo.CodecCapabilities> info = + getMediaSoftwareCodecInfo(mimeType, secure); + if (info == null) { + return null; + } + return new DecoderInfo(info.first, info.second); + } + + /** Returns the name of the software decoder and its capabilities for the given mimeType. */ + private static synchronized Pair<String, MediaCodecInfo.CodecCapabilities> + getMediaSoftwareCodecInfo(String mimeType, boolean secure) + throws DecoderQueryException { + CodecKey key = new CodecKey(mimeType, secure); + if (sSwCodecs.containsKey(key)) { + return sSwCodecs.get(key); + } + MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure); + Pair<String, MediaCodecInfo.CodecCapabilities> codecInfo = + getMediaSoftwareCodecInfo(key, mediaCodecList); + if (secure && codecInfo == null) { + // Some devices don't list secure decoders on API level 21. Try the legacy path. + mediaCodecList = new MediaCodecListCompatV16(); + codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); + if (codecInfo != null) { + Log.w( + TAG, + "MediaCodecList API didn't list secure decoder for: " + + mimeType + + ". Assuming: " + + codecInfo.first); + } + } + return codecInfo; + } + + private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo( + CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { + try { + return getMediaSoftwareCodecInfoInternal(key, mediaCodecList); + } catch (Exception e) { + // If the underlying mediaserver is in a bad state, we may catch an + // IllegalStateException or an IllegalArgumentException here. + throw new DecoderQueryException(e); + } + } + + private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfoInternal( + CodecKey key, MediaCodecListCompat mediaCodecList) { + String mimeType = key.mimeType; + int numberOfCodecs = mediaCodecList.getCodecCount(); + boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); + // Note: MediaCodecList is sorted by the framework such that the best decoders come first. + for (int i = 0; i < numberOfCodecs; i++) { + MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); + String codecName = info.getName(); + if (!info.isEncoder() + && codecName.startsWith("OMX.google.") + && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { + String[] supportedTypes = info.getSupportedTypes(); + for (String supportedType : supportedTypes) { + if (supportedType.equalsIgnoreCase(mimeType)) { + MediaCodecInfo.CodecCapabilities capabilities = + info.getCapabilitiesForType(supportedType); + boolean secure = + mediaCodecList.isSecurePlaybackSupported( + key.mimeType, capabilities); + if (!secureDecodersExplicit) { + // Cache variants for both insecure and (if we think it's supported) + // secure playback. + sSwCodecs.put( + key.secure ? new CodecKey(mimeType, false) : key, + Pair.create(codecName, capabilities)); + if (secure) { + sSwCodecs.put( + key.secure ? key : new CodecKey(mimeType, true), + Pair.create(codecName + ".secure", capabilities)); + } + } else { + // Only cache this variant. If both insecure and secure decoders are + // available, they should both be listed separately. + sSwCodecs.put( + key.secure == secure ? key : new CodecKey(mimeType, secure), + Pair.create(codecName, capabilities)); + } + if (sSwCodecs.containsKey(key)) { + return sSwCodecs.get(key); + } + } + } + } + } + sSwCodecs.put(key, null); + return null; + } + + private interface MediaCodecListCompat { + + /** Returns the number of codecs in the list. */ + int getCodecCount(); + + /** + * Returns the info at the specified index in the list. + * + * @param index The index. + */ + MediaCodecInfo getCodecInfoAt(int index); + + /** Returns whether secure decoders are explicitly listed, if present. */ + boolean secureDecodersExplicit(); + + /** + * Returns true if secure playback is supported for the given {@link + * android.media.MediaCodecInfo.CodecCapabilities}, which should have been obtained from a + * {@link MediaCodecInfo} obtained from this list. + */ + boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities); + } + + @TargetApi(21) + private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { + + private final int codecKind; + + private MediaCodecInfo[] mediaCodecInfos; + + public MediaCodecListCompatV21(boolean includeSecure) { + codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; + } + + @Override + public int getCodecCount() { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos.length; + } + + @Override + public MediaCodecInfo getCodecInfoAt(int index) { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos[index]; + } + + @Override + public boolean secureDecodersExplicit() { + return true; + } + + @Override + public boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { + return capabilities.isFeatureSupported( + MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); + } + + private void ensureMediaCodecInfosInitialized() { + if (mediaCodecInfos == null) { + mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); + } + } + } + + @SuppressWarnings("deprecation") + private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { + + @Override + public int getCodecCount() { + return MediaCodecList.getCodecCount(); + } + + @Override + public MediaCodecInfo getCodecInfoAt(int index) { + return MediaCodecList.getCodecInfoAt(index); + } + + @Override + public boolean secureDecodersExplicit() { + return false; + } + + @Override + public boolean isSecurePlaybackSupported( + String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { + // Secure decoders weren't explicitly listed prior to API level 21. We assume that + // a secure H264 decoder exists. + return MimeTypes.VIDEO_H264.equals(mimeType); + } + } + + private static final class CodecKey { + + public final String mimeType; + public final boolean secure; + + public CodecKey(String mimeType, boolean secure) { + this.mimeType = mimeType; + this.secure = secure; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); + result = 2 * result + (secure ? 0 : 1); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CodecKey)) { + return false; + } + CodecKey other = (CodecKey) obj; + return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; + } + } +} |