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