1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tests.transcoding;
18 
19 import android.app.Activity;
20 import android.media.MediaCodec;
21 import android.media.MediaCodecInfo;
22 import android.media.MediaCodecInfo.CodecCapabilities;
23 import android.media.MediaCodecInfo.VideoCapabilities;
24 import android.media.MediaCodecList;
25 import android.media.MediaFormat;
26 import android.os.Bundle;
27 import android.util.Log;
28 import java.io.IOException;
29 import java.util.Vector;
30 
31 public class ResourcePolicyTestActivity extends Activity {
32     public static final int TYPE_NONSECURE = 0;
33     public static final int TYPE_SECURE = 1;
34     public static final int TYPE_MIX = 2;
35 
36     protected String TAG;
37     private static final int FRAME_RATE = 10;
38     private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
39     private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
40     private static final int TIMEOUT_MS = 5000;
41 
42     private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
43 
44     private class TestCodecCallback extends MediaCodec.Callback {
45         @Override
onInputBufferAvailable(MediaCodec codec, int index)46         public void onInputBufferAvailable(MediaCodec codec, int index) {
47             Log.d(TAG, "onInputBufferAvailable " + codec.toString());
48         }
49 
50         @Override
onOutputBufferAvailable( MediaCodec codec, int index, MediaCodec.BufferInfo info)51         public void onOutputBufferAvailable(
52                 MediaCodec codec, int index, MediaCodec.BufferInfo info) {
53             Log.d(TAG, "onOutputBufferAvailable " + codec.toString());
54         }
55 
56         @Override
onError(MediaCodec codec, MediaCodec.CodecException e)57         public void onError(MediaCodec codec, MediaCodec.CodecException e) {
58             Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
59         }
60 
61         @Override
onOutputFormatChanged(MediaCodec codec, MediaFormat format)62         public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
63             Log.d(TAG, "onOutputFormatChanged " + codec.toString());
64         }
65     }
66 
67     private MediaCodec.Callback mCallback = new TestCodecCallback();
68 
getTestFormat(CodecCapabilities caps, boolean securePlayback)69     private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
70         VideoCapabilities vcaps = caps.getVideoCapabilities();
71         int width = vcaps.getSupportedWidths().getLower();
72         int height = vcaps.getSupportedHeightsFor(width).getLower();
73         int bitrate = vcaps.getBitrateRange().getLower();
74 
75         MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
76         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
77         format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
78         format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
79         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
80         format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
81         return format;
82     }
83 
getTestCodecInfo(boolean securePlayback)84     private MediaCodecInfo getTestCodecInfo(boolean securePlayback) {
85         // Use avc decoder for testing.
86         boolean isEncoder = false;
87 
88         MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
89         for (MediaCodecInfo info : mcl.getCodecInfos()) {
90             if (info.isEncoder() != isEncoder) {
91                 continue;
92             }
93             CodecCapabilities caps;
94             try {
95                 caps = info.getCapabilitiesForType(MIME);
96                 boolean securePlaybackSupported =
97                         caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
98                 boolean securePlaybackRequired =
99                         caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback);
100                 if ((securePlayback && securePlaybackSupported)
101                         || (!securePlayback && !securePlaybackRequired)) {
102                     Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName());
103                 } else {
104                     Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName());
105                     continue;
106                 }
107             } catch (IllegalArgumentException e) {
108                 // mime is not supported
109                 continue;
110             }
111             return info;
112         }
113 
114         return null;
115     }
116 
allocateCodecs(int max)117     protected int allocateCodecs(int max) {
118         Bundle extras = getIntent().getExtras();
119         int type = TYPE_NONSECURE;
120         if (extras != null) {
121             type = extras.getInt("test-type", type);
122             Log.d(TAG, "type is: " + type);
123         }
124 
125         boolean shouldSkip = false;
126         boolean securePlayback;
127         if (type == TYPE_NONSECURE || type == TYPE_MIX) {
128             securePlayback = false;
129             MediaCodecInfo info = getTestCodecInfo(securePlayback);
130             if (info != null) {
131                 allocateCodecs(max, info, securePlayback);
132             } else {
133                 shouldSkip = true;
134             }
135         }
136 
137         if (!shouldSkip) {
138             if (type == TYPE_SECURE || type == TYPE_MIX) {
139                 securePlayback = true;
140                 MediaCodecInfo info = getTestCodecInfo(securePlayback);
141                 if (info != null) {
142                     allocateCodecs(max, info, securePlayback);
143                 } else {
144                     shouldSkip = true;
145                 }
146             }
147         }
148 
149         if (shouldSkip) {
150             Log.d(TAG, "test skipped as there's no supported codec.");
151             finishWithResult(RESULT_OK);
152         }
153 
154         Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
155         return mCodecs.size();
156     }
157 
allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback)158     protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
159         String name = info.getName();
160         CodecCapabilities caps = info.getCapabilitiesForType(MIME);
161         MediaFormat format = getTestFormat(caps, securePlayback);
162         MediaCodec codec = null;
163         for (int i = mCodecs.size(); i < max; ++i) {
164             try {
165                 Log.d(TAG, "Create codec " + name + " #" + i);
166                 codec = MediaCodec.createByCodecName(name);
167                 codec.setCallback(mCallback);
168                 Log.d(TAG, "Configure codec " + format);
169                 codec.configure(format, null, null, 0);
170                 Log.d(TAG, "Start codec " + format);
171                 codec.start();
172                 mCodecs.add(codec);
173                 codec = null;
174             } catch (IllegalArgumentException e) {
175                 Log.d(TAG, "IllegalArgumentException " + e.getMessage());
176                 break;
177             } catch (IOException e) {
178                 Log.d(TAG, "IOException " + e.getMessage());
179                 break;
180             } catch (MediaCodec.CodecException e) {
181                 Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
182                 break;
183             } finally {
184                 if (codec != null) {
185                     Log.d(TAG, "release codec");
186                     codec.release();
187                     codec = null;
188                 }
189             }
190         }
191     }
192 
finishWithResult(int result)193     protected void finishWithResult(int result) {
194         for (int i = 0; i < mCodecs.size(); ++i) {
195             Log.d(TAG, "release codec #" + i);
196             mCodecs.get(i).release();
197         }
198         mCodecs.clear();
199         setResult(result);
200         finish();
201         Log.d(TAG, "activity finished");
202     }
203 
doUseCodecs()204     private void doUseCodecs() {
205         int current = 0;
206         try {
207             for (current = 0; current < mCodecs.size(); ++current) {
208                 mCodecs.get(current).getName();
209             }
210         } catch (MediaCodec.CodecException e) {
211             Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
212             if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
213                 Log.d(TAG, "Remove codec " + current + " from the list");
214                 mCodecs.get(current).release();
215                 mCodecs.remove(current);
216                 mGotReclaimedException = true;
217                 mUseCodecs = false;
218             }
219             return;
220         }
221     }
222 
223     private Thread mWorkerThread;
224     private volatile boolean mUseCodecs = true;
225     private volatile boolean mGotReclaimedException = false;
useCodecs()226     protected void useCodecs() {
227         mWorkerThread = new Thread(new Runnable() {
228             @Override
229             public void run() {
230                 long start = System.currentTimeMillis();
231                 long timeSinceStartedMs = 0;
232                 while (mUseCodecs && (timeSinceStartedMs < TIMEOUT_MS)) {
233                     doUseCodecs();
234                     try {
235                         Thread.sleep(50 /* millis */);
236                     } catch (InterruptedException e) {
237                     }
238                     timeSinceStartedMs = System.currentTimeMillis() - start;
239                 }
240                 if (mGotReclaimedException) {
241                     Log.d(TAG, "Got expected reclaim exception.");
242                 }
243                 finishWithResult(RESULT_OK);
244             }
245         });
246         mWorkerThread.start();
247     }
248 
249     private static final int MAX_INSTANCES = 32;
250 
251     @Override
onCreate(Bundle savedInstanceState)252     protected void onCreate(Bundle savedInstanceState) {
253         TAG = "ResourcePolicyTestActivity";
254 
255         Log.d(TAG, "onCreate called.");
256         super.onCreate(savedInstanceState);
257 
258         if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
259             // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
260             //mWaitForReclaim = false;
261             Log.d(TAG, "Didn't hit resource limitation");
262         }
263 
264         useCodecs();
265     }
266 
267     @Override
onDestroy()268     protected void onDestroy() {
269         Log.d(TAG, "onDestroy called.");
270         super.onDestroy();
271     }
272 }
273