aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/exoplayer/MediaSoftwareCodecUtil.java
blob: 8c2509d4f2805b45d3ee394ee5758328eb09c027 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/*
 * 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;
        }

    }

}