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