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 msg.append(key).append(TOKEN_SEPARATOR); 262 msg.append(valueTypeForKey).append(TOKEN_SEPARATOR); 263 if (valueTypeForKey == MediaFormat.TYPE_INTEGER) { 264 msg.append(format.getInteger(key)).append(TOKEN_SEPARATOR); 265 } else if (valueTypeForKey == MediaFormat.TYPE_FLOAT) { 266 msg.append(format.getFloat(key)).append(TOKEN_SEPARATOR); 267 } else if (valueTypeForKey == MediaFormat.TYPE_STRING) { 268 msg.append(format.getString(key)).append(TOKEN_SEPARATOR); 269 } else { 270 throw new RuntimeException("unrecognized Type for Key: " + key); 271 } 272 } 273 return msg.toString(); 274 } 275 276 @NonNull 277 @Override toString()278 public String toString() { 279 if (mMsg != null) return mMsg.toString(); 280 281 mMsg = new StringBuilder(); 282 mMsg.append(String.format("media type : %s, ", mMediaType)); 283 if (mIsAudio) { 284 mMsg.append(String.format("Sample rate : %d, ", mSampleRate)); 285 mMsg.append(String.format("Channel count : %d, ", mChannelCount)); 286 if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) { 287 mMsg.append(String.format("Compression level : %d, ", mCompressionLevel)); 288 } else { 289 mMsg.append(String.format("Bitrate : %d, ", mBitRate)); 290 } 291 if (mMediaType.equals(MediaFormat.MIMETYPE_AUDIO_AAC) && mProfile != -1) { 292 mMsg.append(String.format("Profile : %d, ", mProfile)); 293 } 294 mMsg.append(String.format("encoding : %d, ", mPcmEncoding)); 295 } else { 296 mMsg.append(String.format("Width : %d, ", mWidth)); 297 mMsg.append(String.format("Height : %d, ", mHeight)); 298 mMsg.append(String.format("Frame rate : %d, ", mFrameRate)); 299 mMsg.append(String.format("Bit rate : %d, ", mBitRate)); 300 mMsg.append(String.format("key frame interval : %f, ", mKeyFrameInterval)); 301 mMsg.append(String.format("max b frames : %d, ", mMaxBFrames)); 302 if (mBitRateMode >= 0) mMsg.append(String.format("bitrate mode : %d, ", mBitRateMode)); 303 if (mProfile >= 0) mMsg.append(String.format("profile : %x, ", mProfile)); 304 if (mLevel >= 0) mMsg.append(String.format("level : %x, ", mLevel)); 305 mMsg.append(String.format("color format : %x, ", mColorFormat)); 306 if (mColorFormat == COLOR_FormatSurface) { 307 mMsg.append(String.format("bit depth : %d, ", mInputBitDepth)); 308 } 309 if (mRange >= 0) mMsg.append(String.format("color range : %d, ", mRange)); 310 if (mStandard >= 0) mMsg.append(String.format("color standard : %d, ", mStandard)); 311 if (mTransfer >= 0) mMsg.append(String.format("color transfer : %d, ", mTransfer)); 312 } 313 if (!mFeatures.isEmpty()) { 314 mMsg.append(String.format("features : { ")); 315 for (Map.Entry<String, Boolean> entry : mFeatures.entrySet()) { 316 mMsg.append(String.format(entry.getKey() + " : " + entry.getValue() + ", ")); 317 } 318 mMsg.append(String.format("}")); 319 } 320 mMsg.append("\n"); 321 return mMsg.toString(); 322 } 323 324 public static class Builder implements Cloneable { 325 public String mMediaType; 326 327 // video params 328 public int mWidth = 352; 329 public int mHeight = 288; 330 public int mFrameRate = -1; 331 public int mBitRateMode = -1; 332 public float mKeyFrameInterval = 1.0f; 333 public int mMaxBFrames = 0; 334 public int mLevel = -1; 335 public int mColorFormat = COLOR_FormatYUV420Flexible; 336 public int mInputBitDepth = -1; 337 public int mRange = -1; 338 public int mStandard = -1; 339 public int mTransfer = -1; 340 341 // audio params 342 public int mSampleRate = 8000; 343 public int mChannelCount = 1; 344 public int mCompressionLevel = 5; 345 public int mPcmEncoding = AudioFormat.ENCODING_PCM_16BIT; 346 347 // feature list 348 public Map<String, Boolean> mFeatures = new HashMap<>(); 349 350 // common params 351 public int mProfile = -1; 352 public int mBitRate = 256000; 353 Builder(String mediaType)354 public Builder(String mediaType) { 355 mMediaType = mediaType; 356 } 357 setWidth(int width)358 public Builder setWidth(int width) { 359 this.mWidth = width; 360 return this; 361 } 362 setHeight(int height)363 public Builder setHeight(int height) { 364 this.mHeight = height; 365 return this; 366 } 367 setFrameRate(int frameRate)368 public Builder setFrameRate(int frameRate) { 369 this.mFrameRate = frameRate; 370 return this; 371 } 372 setBitRateMode(int bitRateMode)373 public Builder setBitRateMode(int bitRateMode) { 374 this.mBitRateMode = bitRateMode; 375 return this; 376 } 377 setKeyFrameInterval(float keyFrameInterval)378 public Builder setKeyFrameInterval(float keyFrameInterval) { 379 this.mKeyFrameInterval = keyFrameInterval; 380 return this; 381 } 382 setMaxBFrames(int maxBFrames)383 public Builder setMaxBFrames(int maxBFrames) { 384 this.mMaxBFrames = maxBFrames; 385 return this; 386 } 387 setLevel(int level)388 public Builder setLevel(int level) { 389 this.mLevel = level; 390 return this; 391 } 392 setColorFormat(int colorFormat)393 public Builder setColorFormat(int colorFormat) { 394 this.mColorFormat = colorFormat; 395 return this; 396 } 397 setInputBitDepth(int inputBitDepth)398 public Builder setInputBitDepth(int inputBitDepth) { 399 this.mInputBitDepth = inputBitDepth; 400 return this; 401 } 402 setRange(int range)403 public Builder setRange(int range) { 404 this.mRange = range; 405 return this; 406 } 407 setStandard(int standard)408 public Builder setStandard(int standard) { 409 this.mStandard = standard; 410 return this; 411 } 412 setTransfer(int transfer)413 public Builder setTransfer(int transfer) { 414 this.mTransfer = transfer; 415 return this; 416 } 417 setSampleRate(int sampleRate)418 public Builder setSampleRate(int sampleRate) { 419 this.mSampleRate = sampleRate; 420 return this; 421 } 422 setChannelCount(int channelCount)423 public Builder setChannelCount(int channelCount) { 424 this.mChannelCount = channelCount; 425 return this; 426 } 427 setCompressionLevel(int compressionLevel)428 public Builder setCompressionLevel(int compressionLevel) { 429 this.mCompressionLevel = compressionLevel; 430 return this; 431 } 432 setPcmEncoding(int pcmEncoding)433 public Builder setPcmEncoding(int pcmEncoding) { 434 this.mPcmEncoding = pcmEncoding; 435 return this; 436 } 437 setProfile(int profile)438 public Builder setProfile(int profile) { 439 this.mProfile = profile; 440 // encoder profile requires also level to be set prior to Android U, 441 // but this can be a default/unknown value. Setting this to 1 as all 442 // codecs use a value of 1 for lowest level. 443 if (mLevel < 0) { 444 mLevel = 1; 445 } 446 return this; 447 } 448 setBitRate(int bitRate)449 public Builder setBitRate(int bitRate) { 450 this.mBitRate = bitRate; 451 return this; 452 } 453 setFeature(String feature, boolean enable)454 public Builder setFeature(String feature, boolean enable) { 455 if (feature != null) { 456 this.mFeatures.put(feature, enable); 457 } 458 return this; 459 } 460 build()461 public EncoderConfigParams build() { 462 return new EncoderConfigParams(this); 463 } 464 465 @NonNull clone()466 public Builder clone() throws CloneNotSupportedException { 467 Builder builder = (Builder) super.clone(); 468 builder.mFeatures.clear(); 469 for (Map.Entry<String, Boolean> entry : mFeatures.entrySet()) { 470 String feature = entry.getKey(); 471 boolean enable = entry.getValue(); 472 builder.mFeatures.put(feature, enable); 473 } 474 return builder; 475 } 476 } 477 } 478