• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.mediav2.common.cts;
18 
19 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
20 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
21 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar;
22 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
23 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
24 
25 import android.media.AudioFormat;
26 import android.media.MediaFormat;
27 
28 import androidx.annotation.NonNull;
29 
30 import java.util.HashMap;
31 import java.util.Map;
32 
33 /**
34  * Class to hold encoder configuration settings.
35  */
36 public class EncoderConfigParams {
37     public static final String TOKEN_SEPARATOR = "<>";
38 
39     public final boolean mIsAudio;
40     public final String mMediaType;
41 
42     // video params
43     public final int mWidth;
44     public final int mHeight;
45     public final int mFrameRate;
46     public final float mKeyFrameInterval;
47     public final int mMaxBFrames;
48     public final int mBitRateMode;
49     public final int mLevel;
50     public final int mColorFormat;
51     public final int mInputBitDepth;
52     public final int mRange;
53     public final int mStandard;
54     public final int mTransfer;
55 
56     // audio params
57     public final int mSampleRate;
58     public final int mChannelCount;
59     public final int mCompressionLevel;
60     public final int mPcmEncoding;
61 
62     // features list
63     public final Map<String, Boolean> mFeatures;
64 
65     // common params
66     public final int mProfile;
67     public final int mBitRate;
68 
69     Builder mBuilder;
70     MediaFormat mFormat;
71     StringBuilder mMsg;
72 
EncoderConfigParams(Builder cfg)73     private EncoderConfigParams(Builder cfg) {
74         if (cfg.mMediaType == null) {
75             throw new IllegalArgumentException("null media type");
76         }
77         mIsAudio = cfg.mMediaType.startsWith("audio/");
78         boolean mIsVideo = cfg.mMediaType.startsWith("video/");
79         if (mIsAudio == mIsVideo) {
80             throw new IllegalArgumentException("invalid media type, it is neither audio nor video");
81         }
82         mMediaType = cfg.mMediaType;
83         if (mIsAudio) {
84             if (cfg.mSampleRate <= 0 || cfg.mChannelCount <= 0) {
85                 throw new IllegalArgumentException("bad config params for audio component");
86             }
87             mSampleRate = cfg.mSampleRate;
88             mChannelCount = cfg.mChannelCount;
89             if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
90                 if (cfg.mCompressionLevel < 0 || cfg.mCompressionLevel > 8) {
91                     throw new IllegalArgumentException("bad compression level for flac component");
92                 }
93                 mCompressionLevel = cfg.mCompressionLevel;
94                 mBitRate = -1;
95             } else {
96                 if (cfg.mBitRate <= 0) {
97                     throw new IllegalArgumentException("bad bitrate value for audio component");
98                 }
99                 mBitRate = cfg.mBitRate;
100                 mCompressionLevel = -1;
101             }
102             if (cfg.mPcmEncoding != AudioFormat.ENCODING_PCM_FLOAT
103                     && cfg.mPcmEncoding != AudioFormat.ENCODING_PCM_16BIT) {
104                 throw new IllegalArgumentException("bad input pcm encoding for audio component");
105             }
106             if (cfg.mInputBitDepth != -1) {
107                 throw new IllegalArgumentException(
108                         "use pcm encoding to signal input attributes, don't use bitdepth");
109             }
110             mPcmEncoding = cfg.mPcmEncoding;
111             mProfile = cfg.mProfile;
112 
113             // satisfy Variable '*' might not have been initialized, unused by this media type
114             mWidth = 352;
115             mHeight = 288;
116             mFrameRate = -1;
117             mBitRateMode = -1;
118             mKeyFrameInterval = 1.0f;
119             mMaxBFrames = 0;
120             mLevel = -1;
121             mColorFormat = COLOR_FormatYUV420Flexible;
122             mInputBitDepth = -1;
123             mRange = -1;
124             mStandard = -1;
125             mTransfer = -1;
126         } else {
127             if (cfg.mWidth <= 0 || cfg.mHeight <= 0) {
128                 throw new IllegalArgumentException("bad config params for video component");
129             }
130             mWidth = cfg.mWidth;
131             mHeight = cfg.mHeight;
132             if (cfg.mFrameRate <= 0) {
133                 if (mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
134                     mFrameRate = 12;
135                 } else if (mMediaType.equals(MediaFormat.MIMETYPE_VIDEO_H263)) {
136                     mFrameRate = 12;
137                 } else {
138                     mFrameRate = 30;
139                 }
140             } else {
141                 mFrameRate = cfg.mFrameRate;
142             }
143             if (cfg.mBitRate <= 0) {
144                 throw new IllegalArgumentException("bad bitrate value for video component");
145             }
146             mBitRate = cfg.mBitRate;
147             mKeyFrameInterval = cfg.mKeyFrameInterval;
148             mMaxBFrames = cfg.mMaxBFrames;
149             mBitRateMode = cfg.mBitRateMode;
150             mProfile = cfg.mProfile;
151             mLevel = cfg.mLevel;
152             if (cfg.mColorFormat != COLOR_FormatYUV420Flexible
153                     && cfg.mColorFormat != COLOR_FormatYUVP010
154                     && cfg.mColorFormat != COLOR_FormatSurface
155                     && cfg.mColorFormat != COLOR_FormatYUV420SemiPlanar
156                     && cfg.mColorFormat != COLOR_FormatYUV420Planar) {
157                 throw new IllegalArgumentException("bad color format config for video component");
158             }
159             mColorFormat = cfg.mColorFormat;
160             if (cfg.mInputBitDepth != -1) {
161                 if (cfg.mColorFormat == COLOR_FormatYUV420Flexible && cfg.mInputBitDepth != 8) {
162                     throw new IllegalArgumentException(
163                             "bad bit depth configuration for COLOR_FormatYUV420Flexible");
164                 } else if (cfg.mColorFormat == COLOR_FormatYUV420SemiPlanar
165                         && cfg.mInputBitDepth != 8) {
166                     throw new IllegalArgumentException(
167                             "bad bit depth configuration for COLOR_FormatYUV420SemiPlanar");
168                 } else if (cfg.mColorFormat == COLOR_FormatYUV420Planar
169                         && cfg.mInputBitDepth != 8) {
170                     throw new IllegalArgumentException(
171                             "bad bit depth configuration for COLOR_FormatYUV420Planar");
172                 } else if (cfg.mColorFormat == COLOR_FormatYUVP010 && cfg.mInputBitDepth != 10) {
173                     throw new IllegalArgumentException(
174                             "bad bit depth configuration for COLOR_FormatYUVP010");
175                 } else if (cfg.mColorFormat == COLOR_FormatSurface && cfg.mInputBitDepth != 8
176                         && cfg.mInputBitDepth != 10) {
177                     throw new IllegalArgumentException(
178                             "bad bit depth configuration for COLOR_FormatSurface");
179                 }
180                 mInputBitDepth = cfg.mInputBitDepth;
181             } else if (cfg.mColorFormat == COLOR_FormatYUVP010) {
182                 mInputBitDepth = 10;
183             } else {
184                 mInputBitDepth = 8;
185             }
186             if (mProfile == -1) {
187                 if ((mColorFormat == COLOR_FormatSurface && mInputBitDepth == 10) || (mColorFormat
188                         == COLOR_FormatYUVP010)) {
189                     throw new IllegalArgumentException("If color format is configured to "
190                             + "COLOR_FormatSurface and bitdepth is set to 10 or color format is "
191                             + "configured to COLOR_FormatYUVP010 then profile needs to be"
192                             + " configured");
193                 }
194             }
195             mRange = cfg.mRange;
196             mStandard = cfg.mStandard;
197             mTransfer = cfg.mTransfer;
198 
199             // satisfy Variable '*' might not have been initialized, unused by this media type
200             mSampleRate = 8000;
201             mChannelCount = 1;
202             mCompressionLevel = 5;
203             mPcmEncoding = AudioFormat.ENCODING_INVALID;
204         }
205         mFeatures = cfg.mFeatures;
206         mBuilder = cfg;
207     }
208 
getBuilder()209     public Builder getBuilder() throws CloneNotSupportedException {
210         return mBuilder.clone();
211     }
212 
getFormat()213     public MediaFormat getFormat() {
214         if (mFormat != null) return new MediaFormat(mFormat);
215 
216         mFormat = new MediaFormat();
217         mFormat.setString(MediaFormat.KEY_MIME, mMediaType);
218         if (mIsAudio) {
219             mFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate);
220             mFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, mChannelCount);
221             if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
222                 mFormat.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, mCompressionLevel);
223             } else {
224                 mFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
225             }
226             if (mProfile >= 0 && mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
227                 mFormat.setInteger(MediaFormat.KEY_PROFILE, mProfile);
228                 mFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, mProfile);
229             }
230             mFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mPcmEncoding);
231         } else {
232             mFormat.setInteger(MediaFormat.KEY_WIDTH, mWidth);
233             mFormat.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
234             mFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
235             mFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
236             mFormat.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, mKeyFrameInterval);
237             mFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames);
238             if (mBitRateMode >= 0) mFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, mBitRateMode);
239             if (mProfile >= 0) mFormat.setInteger(MediaFormat.KEY_PROFILE, mProfile);
240             if (mLevel >= 0) mFormat.setInteger(MediaFormat.KEY_LEVEL, mLevel);
241             mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);
242             if (mRange >= 0) mFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, mRange);
243             if (mStandard >= 0) mFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, mStandard);
244             if (mTransfer >= 0) mFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, mTransfer);
245         }
246         for (Map.Entry<String, Boolean> entry : mFeatures.entrySet()) {
247             mFormat.setFeatureEnabled(entry.getKey(), entry.getValue());
248         }
249         return new MediaFormat(mFormat);
250     }
251 
252     /**
253      * Converts MediaFormat object to a string. All Keys, ValueTypes, Values are concatenated with
254      * a separator and sent for further usage.
255      */
serializeMediaFormat(MediaFormat format)256     public static String serializeMediaFormat(MediaFormat format) {
257         StringBuilder msg = new StringBuilder();
258         java.util.Set<String> keys = format.getKeys();
259         for (String key : keys) {
260             int valueTypeForKey = format.getValueTypeForKey(key);
261             if (valueTypeForKey == MediaFormat.TYPE_BYTE_BUFFER) continue;
262             msg.append(key).append(TOKEN_SEPARATOR);
263             msg.append(valueTypeForKey).append(TOKEN_SEPARATOR);
264             if (valueTypeForKey == MediaFormat.TYPE_INTEGER) {
265                 msg.append(format.getInteger(key)).append(TOKEN_SEPARATOR);
266             } else if (valueTypeForKey == MediaFormat.TYPE_LONG) {
267                 msg.append(format.getLong(key)).append(TOKEN_SEPARATOR);
268             } else if (valueTypeForKey == MediaFormat.TYPE_FLOAT) {
269                 msg.append(format.getFloat(key)).append(TOKEN_SEPARATOR);
270             } else if (valueTypeForKey == MediaFormat.TYPE_STRING) {
271                 msg.append(format.getString(key)).append(TOKEN_SEPARATOR);
272             } else {
273                 throw new RuntimeException("unrecognized Type for Key: " + key);
274             }
275         }
276         return msg.toString();
277     }
278 
279     @NonNull
280     @Override
toString()281     public String toString() {
282         if (mMsg != null) return mMsg.toString();
283 
284         mMsg = new StringBuilder();
285         mMsg.append(String.format("media type : %s, ", mMediaType));
286         if (mIsAudio) {
287             mMsg.append(String.format("Sample rate : %d, ", mSampleRate));
288             mMsg.append(String.format("Channel count : %d, ", mChannelCount));
289             if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
290                 mMsg.append(String.format("Compression level : %d, ", mCompressionLevel));
291             } else {
292                 mMsg.append(String.format("Bitrate : %d, ", mBitRate));
293             }
294             if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC) && mProfile != -1) {
295                 mMsg.append(String.format("Profile : %d, ", mProfile));
296             }
297             mMsg.append(String.format("encoding : %d, ", mPcmEncoding));
298         } else {
299             mMsg.append(String.format("Width : %d, ", mWidth));
300             mMsg.append(String.format("Height : %d, ", mHeight));
301             mMsg.append(String.format("Frame rate : %d, ", mFrameRate));
302             mMsg.append(String.format("Bit rate : %d, ", mBitRate));
303             mMsg.append(String.format("key frame interval : %f, ", mKeyFrameInterval));
304             mMsg.append(String.format("max b frames : %d, ", mMaxBFrames));
305             if (mBitRateMode >= 0) mMsg.append(String.format("bitrate mode : %d, ", mBitRateMode));
306             if (mProfile >= 0) mMsg.append(String.format("profile : %#x, ", mProfile));
307             if (mLevel >= 0) mMsg.append(String.format("level : %#x, ", mLevel));
308             mMsg.append(String.format("color format : %#x, ", mColorFormat));
309             if (mColorFormat == COLOR_FormatSurface) {
310                 mMsg.append(String.format("bit depth : %d, ", mInputBitDepth));
311             }
312             if (mRange >= 0) mMsg.append(String.format("color range : %d, ", mRange));
313             if (mStandard >= 0) mMsg.append(String.format("color standard : %d, ", mStandard));
314             if (mTransfer >= 0) mMsg.append(String.format("color transfer : %d, ", mTransfer));
315         }
316         if (!mFeatures.isEmpty()) {
317             mMsg.append(String.format("features : { "));
318             for (Map.Entry<String, Boolean> entry : mFeatures.entrySet()) {
319                 mMsg.append(String.format(entry.getKey() + " : " + entry.getValue() + ", "));
320             }
321             mMsg.append(String.format("}"));
322         }
323         mMsg.append("\n");
324         return mMsg.toString();
325     }
326 
327     public static class Builder implements Cloneable {
328         public String mMediaType;
329 
330         // video params
331         public int mWidth = 352;
332         public int mHeight = 288;
333         public int mFrameRate = -1;
334         public int mBitRateMode = -1;
335         public float mKeyFrameInterval = 1.0f;
336         public int mMaxBFrames = 0;
337         public int mLevel = -1;
338         public int mColorFormat = COLOR_FormatYUV420Flexible;
339         public int mInputBitDepth = -1;
340         public int mRange = -1;
341         public int mStandard = -1;
342         public int mTransfer = -1;
343 
344         // audio params
345         public int mSampleRate = 8000;
346         public int mChannelCount = 1;
347         public int mCompressionLevel = 5;
348         public int mPcmEncoding = AudioFormat.ENCODING_PCM_16BIT;
349 
350         // feature list
351         public Map<String, Boolean> mFeatures = new HashMap<>();
352 
353         // common params
354         public int mProfile = -1;
355         public int mBitRate = 256000;
356 
Builder(String mediaType)357         public Builder(String mediaType) {
358             mMediaType = mediaType;
359         }
360 
setWidth(int width)361         public Builder setWidth(int width) {
362             this.mWidth = width;
363             return this;
364         }
365 
setHeight(int height)366         public Builder setHeight(int height) {
367             this.mHeight = height;
368             return this;
369         }
370 
setFrameRate(int frameRate)371         public Builder setFrameRate(int frameRate) {
372             this.mFrameRate = frameRate;
373             return this;
374         }
375 
setBitRateMode(int bitRateMode)376         public Builder setBitRateMode(int bitRateMode) {
377             this.mBitRateMode = bitRateMode;
378             return this;
379         }
380 
setKeyFrameInterval(float keyFrameInterval)381         public Builder setKeyFrameInterval(float keyFrameInterval) {
382             this.mKeyFrameInterval = keyFrameInterval;
383             return this;
384         }
385 
setMaxBFrames(int maxBFrames)386         public Builder setMaxBFrames(int maxBFrames) {
387             this.mMaxBFrames = maxBFrames;
388             return this;
389         }
390 
setLevel(int level)391         public Builder setLevel(int level) {
392             this.mLevel = level;
393             return this;
394         }
395 
setColorFormat(int colorFormat)396         public Builder setColorFormat(int colorFormat) {
397             this.mColorFormat = colorFormat;
398             return this;
399         }
400 
setInputBitDepth(int inputBitDepth)401         public Builder setInputBitDepth(int inputBitDepth) {
402             this.mInputBitDepth = inputBitDepth;
403             return this;
404         }
405 
setRange(int range)406         public Builder setRange(int range) {
407             this.mRange = range;
408             return this;
409         }
410 
setStandard(int standard)411         public Builder setStandard(int standard) {
412             this.mStandard = standard;
413             return this;
414         }
415 
setTransfer(int transfer)416         public Builder setTransfer(int transfer) {
417             this.mTransfer = transfer;
418             return this;
419         }
420 
setSampleRate(int sampleRate)421         public Builder setSampleRate(int sampleRate) {
422             this.mSampleRate = sampleRate;
423             return this;
424         }
425 
setChannelCount(int channelCount)426         public Builder setChannelCount(int channelCount) {
427             this.mChannelCount = channelCount;
428             return this;
429         }
430 
setCompressionLevel(int compressionLevel)431         public Builder setCompressionLevel(int compressionLevel) {
432             this.mCompressionLevel = compressionLevel;
433             return this;
434         }
435 
setPcmEncoding(int pcmEncoding)436         public Builder setPcmEncoding(int pcmEncoding) {
437             this.mPcmEncoding = pcmEncoding;
438             return this;
439         }
440 
setProfile(int profile)441         public Builder setProfile(int profile) {
442             this.mProfile = profile;
443             // encoder profile requires also level to be set prior to Android U,
444             // but this can be a default/unknown value. Setting this to 1 as all
445             // codecs use a value of 1 for lowest level.
446             if (mLevel < 0) {
447                 mLevel = 1;
448             }
449             return this;
450         }
451 
setBitRate(int bitRate)452         public Builder setBitRate(int bitRate) {
453             this.mBitRate = bitRate;
454             return this;
455         }
456 
setFeature(String feature, boolean enable)457         public Builder setFeature(String feature, boolean enable) {
458             if (feature != null) {
459                 this.mFeatures.put(feature, enable);
460             }
461             return this;
462         }
463 
build()464         public EncoderConfigParams build() {
465             return new EncoderConfigParams(this);
466         }
467 
468         @NonNull
clone()469         public Builder clone() throws CloneNotSupportedException {
470             Builder builder = (Builder) super.clone();
471             builder.mFeatures.clear();
472             for (Map.Entry<String, Boolean> entry : mFeatures.entrySet()) {
473                 String feature = entry.getKey();
474                 boolean enable = entry.getValue();
475                 builder.mFeatures.put(feature, enable);
476             }
477             return builder;
478         }
479     }
480 }
481