diff options
Diffstat (limited to 'media/java/android')
30 files changed, 2408 insertions, 2382 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index e69c456..e2770b4 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1688,11 +1688,19 @@ public class AudioManager { /** * Return a new audio session identifier not associated with any player or effect. - * It can for instance be used to create one of the {@link android.media.audiofx.AudioEffect} - * objects or specify a session for speech synthesis in - * {@link android.speech.tts.TextToSpeech.Engine}. + * An audio session identifier is a system wide unique identifier for a set of audio streams + * (one or more mixed together). + * <p>The primary use of the audio session ID is to associate audio effects to audio players, + * such as {@link MediaPlayer} or {@link AudioTrack}: all audio effects sharing the same audio + * session ID will be applied to the mixed audio content of the players that share the same + * audio session. + * <p>This method can for instance be used when creating one of the + * {@link android.media.audiofx.AudioEffect} objects to define the audio session of the effect, + * or to specify a session for a speech synthesis utterance + * in {@link android.speech.tts.TextToSpeech.Engine}. * @return a new unclaimed and unused audio session identifier, or {@link #ERROR} when the - * system failed to generate a new session. + * system failed to generate a new session, a condition in which audio playback or recording + * will subsequently fail as well. */ public int generateAudioSessionId() { int session = AudioSystem.newAudioSessionId(); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index bce4074..d002924 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -200,6 +200,7 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; private static final int MSG_SYSTEM_READY = 21; private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; + private static final int MSG_PERSIST_MICROPHONE_MUTE = 23; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -873,6 +874,10 @@ public class AudioService extends IAudioService.Stub { AudioSystem.setMasterMute(masterMute); broadcastMasterMuteStatus(masterMute); + boolean microphoneMute = + System.getIntForUser(cr, System.MICROPHONE_MUTE, 0, UserHandle.USER_CURRENT) == 1; + AudioSystem.muteMicrophone(microphoneMute); + // Each stream will read its own persisted settings // Broadcast the sticky intent @@ -1447,17 +1452,15 @@ public class AudioService extends IAudioService.Stub { if (mUseFixedVolume) { return; } - if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, Binder.getCallingUid(), callingPackage) != AppOpsManager.MODE_ALLOWED) { return; } - if (state != AudioSystem.getMasterMute()) { AudioSystem.setMasterMute(state); // Post a persist master volume msg sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1 - : 0, 0, null, PERSIST_DELAY); + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); sendMasterMuteUpdate(state, flags); } } @@ -1563,6 +1566,9 @@ public class AudioService extends IAudioService.Stub { } AudioSystem.muteMicrophone(on); + // Post a persist microphone msg. + sendMsg(mAudioHandler, MSG_PERSIST_MICROPHONE_MUTE, SENDMSG_REPLACE, on ? 1 + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); } /** @see AudioManager#getRingerMode() */ @@ -3486,13 +3492,28 @@ public class AudioService extends IAudioService.Stub { private void dump(PrintWriter pw) { pw.print(" Mute count: "); pw.println(muteCount()); + pw.print(" Max: "); + pw.println((mIndexMax + 5) / 10); pw.print(" Current: "); Set set = mIndex.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry entry = (Map.Entry)i.next(); - pw.print(Integer.toHexString(((Integer)entry.getKey()).intValue()) - + ": " + ((((Integer)entry.getValue()).intValue() + 5) / 10)+", "); + final int device = (Integer) entry.getKey(); + pw.print(Integer.toHexString(device)); + final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" + : AudioSystem.getOutputDeviceName(device); + if (!deviceName.isEmpty()) { + pw.print(" ("); + pw.print(deviceName); + pw.print(")"); + } + pw.print(": "); + final int index = (((Integer) entry.getValue()) + 5) / 10; + pw.print(index); + if (i.hasNext()) { + pw.print(", "); + } } } } @@ -3819,7 +3840,6 @@ public class AudioService extends IAudioService.Stub { @Override public void handleMessage(Message msg) { - switch (msg.what) { case MSG_SET_DEVICE_VOLUME: @@ -3851,7 +3871,7 @@ public class AudioService extends IAudioService.Stub { Settings.System.putIntForUser(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, msg.arg1, - UserHandle.USER_CURRENT); + msg.arg2); break; case MSG_PERSIST_RINGER_MODE: @@ -4046,6 +4066,12 @@ public class AudioService extends IAudioService.Stub { Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs, UserHandle.USER_CURRENT); break; + case MSG_PERSIST_MICROPHONE_MUTE: + Settings.System.putIntForUser(mContentResolver, + Settings.System.MICROPHONE_MUTE, + msg.arg1, + msg.arg2); + break; } } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e11aab1..9a76f94 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -371,6 +371,7 @@ public class AudioSystem public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc"; public static final String DEVICE_OUT_SPDIF_NAME = "spdif"; public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; + public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; public static String getOutputDeviceName(int device) { @@ -417,6 +418,8 @@ public class AudioSystem return DEVICE_OUT_SPDIF_NAME; case DEVICE_OUT_FM: return DEVICE_OUT_FM_NAME; + case DEVICE_OUT_AUX_LINE: + return DEVICE_OUT_AUX_LINE_NAME; case DEVICE_OUT_DEFAULT: default: return ""; diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index 2856edb..522e45d 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -123,7 +123,7 @@ public abstract class Image implements AutoCloseable { */ public abstract long getTimestamp(); - protected Rect mCropRect; + private Rect mCropRect; /** * Get the crop rectangle associated with this frame. diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index e64621c..3e8ee93 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -668,7 +668,7 @@ final public class MediaCodec { * Thrown when an internal codec error occurs. */ public final static class CodecException extends IllegalStateException { - public CodecException(int errorCode, int actionCode, String detailMessage) { + CodecException(int errorCode, int actionCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; mActionCode = actionCode; @@ -1732,7 +1732,7 @@ final public class MediaCodec { if (cropRect != null) { cropRect.offset(-xOffset, -yOffset); } - mCropRect = cropRect; + setCropRect(cropRect); // save offsets and info mXOffset = xOffset; diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index acb2186..323a3e3 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -70,7 +70,7 @@ public final class MediaCodecInfo { mIsEncoder = isEncoder; mCaps = new HashMap<String, CodecCapabilities>(); for (CodecCapabilities c: caps) { - mCaps.put(c.getMime(), c); + mCaps.put(c.getMimeType(), c); } } @@ -98,6 +98,43 @@ public final class MediaCodecInfo { return types; } + private static int checkPowerOfTwo(int value, String message) { + if ((value & (value - 1)) != 0) { + throw new IllegalArgumentException(message); + } + return value; + } + + private static class Feature { + public String mName; + public int mValue; + public boolean mDefault; + public Feature(String name, int value, boolean def) { + mName = name; + mValue = value; + mDefault = def; + } + } + + // COMMON CONSTANTS + private static final Range<Integer> POSITIVE_INTEGERS = + Range.create(1, Integer.MAX_VALUE); + private static final Range<Long> POSITIVE_LONGS = + Range.create(1l, Long.MAX_VALUE); + private static final Range<Rational> POSITIVE_RATIONALS = + Range.create(new Rational(1, Integer.MAX_VALUE), + new Rational(Integer.MAX_VALUE, 1)); + private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768); + private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960); + + // found stuff that is not supported by framework (=> this should not happen) + private static final int ERROR_UNRECOGNIZED = (1 << 0); + // found profile/level for which we don't have capability estimates + private static final int ERROR_UNSUPPORTED = (1 << 1); + // have not found any profile/level for which we don't have capability estimate + private static final int ERROR_NONE_SUPPORTED = (1 << 2); + + /** * Encapsulates the capabilities of a given codec component. * For example, what profile/level combinations it supports and what colorspaces @@ -222,17 +259,6 @@ public final class MediaCodecInfo { return checkFeature(name, mFlagsRequired); } - private static class Feature { - public String mName; - public int mValue; - public boolean mDefault; - public Feature(String name, int value, boolean def) { - mName = name; - mValue = value; - mDefault = def; - } - } - private static final Feature[] decoderFeatures = { new Feature(FEATURE_AdaptivePlayback, (1 << 0), true), new Feature(FEATURE_SecurePlayback, (1 << 1), false), @@ -312,1669 +338,1645 @@ public final class MediaCodecInfo { return true; } - // errors while reading profile levels - private int mError; - // found stuff that is not supported by framework (=> this should not happen) - private static final int ERROR_UNRECOGNIZED = (1 << 0); - // found profile/level for which we don't have capability estimates - private static final int ERROR_UNSUPPORTED = (1 << 1); - // have not found any profile/level for which we don't have capability estimate - private static final int ERROR_NONE_SUPPORTED = (1 << 2); - - - // UTILITY METHODS - private static final Range<Integer> POSITIVE_INTEGERS = - Range.create(1, Integer.MAX_VALUE); - private static final Range<Long> POSITIVE_LONGS = - Range.create(1l, Long.MAX_VALUE); - private static final Range<Rational> POSITIVE_RATIONALS = - Range.create(new Rational(1, Integer.MAX_VALUE), - new Rational(Integer.MAX_VALUE, 1)); - private static final Range<Integer> SIZE_RANGE = Range.create(1, 32768); - private static final Range<Integer> FRAME_RATE_RANGE = Range.create(0, 960); + // errors while reading profile levels - accessed from sister capabilities + int mError; private static final String TAG = "CodecCapabilities"; // NEW-STYLE CAPABILITIES + private AudioCapabilities mAudioCaps; + private VideoCapabilities mVideoCaps; + private EncoderCapabilities mEncoderCaps; + private MediaFormat mDefaultFormat; /** * Returns a MediaFormat object with default values for configurations that have * defaults. */ - public final MediaFormat getDefaultFormat() { + public MediaFormat getDefaultFormat() { return mDefaultFormat; } - private MediaFormat mDefaultFormat; /** * Returns the mime type for which this codec-capability object was created. */ - public final String getMime() { + public String getMimeType() { return mMime; } + private boolean isAudio() { + return mAudioCaps != null; + } + /** - * Returns the encoding capabilities or {@code null} if this is not an encoder. + * Returns the audio capabilities or {@code null} if this is not an audio codec. */ - public final EncoderCapabilities getEncoderCapabilities() { - return mEncoderCaps; + public AudioCapabilities getAudioCapabilities() { + return mAudioCaps; } - private EncoderCapabilities mEncoderCaps; private boolean isEncoder() { return mEncoderCaps != null; } /** - * A class that supports querying the encoding capabilities of a codec. + * Returns the encoding capabilities or {@code null} if this is not an encoder. */ - public static final class EncoderCapabilities { - /** - * Returns the supported range of quality values. - */ - public final Range<Integer> getQualityRange() { - return mQualityRange; - } - - /** - * Returns the supported range of encoder complexity values. - * <p> - * Some codecs may support multiple complexity levels, where higher - * complexity values use more encoder tools (e.g. perform more - * intensive calculations) to improve the quality or the compression - * ratio. Use a lower value to save power and/or time. - */ - public final Range<Integer> getComplexityRange() { - return mComplexityRange; - } + public EncoderCapabilities getEncoderCapabilities() { + return mEncoderCaps; + } - /** Constant quality mode */ - public static final int BITRATE_MODE_CQ = 0; - /** Variable bitrate mode */ - public static final int BITRATE_MODE_VBR = 1; - /** Constant bitrate mode */ - public static final int BITRATE_MODE_CBR = 2; - - private static final Feature[] bitrates = new Feature[] { - new Feature("VBR", BITRATE_MODE_VBR, true), - new Feature("CBR", BITRATE_MODE_CBR, false), - new Feature("CQ", BITRATE_MODE_CQ, false) - }; - - private static int parseBitrateMode(String mode) { - for (Feature feat: bitrates) { - if (feat.mName.equalsIgnoreCase(mode)) { - return feat.mValue; - } - } - return 0; - } + private boolean isVideo() { + return mVideoCaps != null; + } - /** - * Query whether a bitrate mode is supported. - */ - public final boolean isBitrateModeSupported(int mode) { - for (Feature feat: bitrates) { - if (mode == feat.mValue) { - return (mBitControl & (1 << mode)) != 0; - } - } - return false; - } + /** + * Returns the video capabilities or {@code null} if this is not a video codec. + */ + public VideoCapabilities getVideoCapabilities() { + return mVideoCaps; + } - private Range<Integer> mQualityRange; - private Range<Integer> mComplexityRange; - private CodecCapabilities mParent; + /** @hide */ + public CodecCapabilities dup() { + return new CodecCapabilities( + // clone writable arrays + Arrays.copyOf(profileLevels, profileLevels.length), + Arrays.copyOf(colorFormats, colorFormats.length), + isEncoder(), + mFlagsVerified, + mDefaultFormat, + mCapabilitiesInfo); + } - /* no public constructor */ - private EncoderCapabilities() { } + /** + * Retrieve the codec capabilities for a certain {@code mime type}, {@code + * profile} and {@code level}. If the type, or profile-level combination + * is not understood by the framework, it returns null. + */ + public static CodecCapabilities CreateFromProfileLevel( + String mime, int profile, int level) { + CodecProfileLevel pl = new CodecProfileLevel(); + pl.profile = profile; + pl.level = level; + MediaFormat defaultFormat = new MediaFormat(); + defaultFormat.setString(MediaFormat.KEY_MIME, mime); - /** @hide */ - public static EncoderCapabilities create( - MediaFormat info, CodecCapabilities parent) { - EncoderCapabilities caps = new EncoderCapabilities(); - caps.init(info, parent); - return caps; + CodecCapabilities ret = new CodecCapabilities( + new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, + 0 /* flags */, defaultFormat, new MediaFormat() /* info */); + if (ret.mError != 0) { + return null; } + return ret; + } + + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, + boolean encoder, int flags, + Map<String, Object>defaultFormatMap, + Map<String, Object>capabilitiesMap) { + this(profLevs, colFmts, encoder, flags, + new MediaFormat(defaultFormatMap), + new MediaFormat(capabilitiesMap)); + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - // no support for complexity or quality yet - mParent = parent; - mComplexityRange = Range.create(0, 0); - mQualityRange = Range.create(0, 0); - mBitControl = (1 << BITRATE_MODE_VBR); + private MediaFormat mCapabilitiesInfo; + + /* package private */ CodecCapabilities( + CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, + MediaFormat defaultFormat, MediaFormat info) { + final Map<String, Object> map = info.getMap(); + profileLevels = profLevs; + colorFormats = colFmts; + mFlagsVerified = flags; + mDefaultFormat = defaultFormat; + mCapabilitiesInfo = info; + mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); - applyLevelLimits(); - parseFromInfo(info); + if (mMime.toLowerCase().startsWith("audio/")) { + mAudioCaps = AudioCapabilities.create(info, this); + mAudioCaps.setDefaultFormat(mDefaultFormat); + } else if (mMime.toLowerCase().startsWith("video/")) { + mVideoCaps = VideoCapabilities.create(info, this); + } + if (encoder) { + mEncoderCaps = EncoderCapabilities.create(info, this); + mEncoderCaps.setDefaultFormat(mDefaultFormat); } - private void applyLevelLimits() { - String mime = mParent.getMime(); - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - mComplexityRange = Range.create(0, 8); - mBitControl = (1 << BITRATE_MODE_CQ); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - mBitControl = (1 << BITRATE_MODE_CBR); + for (Feature feat: getValidFeatures()) { + String key = MediaFormat.KEY_FEATURE_ + feat.mName; + Integer yesNo = (Integer)map.get(key); + if (yesNo == null) { + continue; + } else if (yesNo > 0) { + mFlagsRequired |= feat.mValue; + mDefaultFormat.setInteger(key, 1); + } else { + mFlagsSupported |= feat.mValue; + mDefaultFormat.setInteger(key, 1); } + // TODO restrict features by mFlagsVerified once all codecs reliably verify them } + } + } - private int mBitControl; - private Integer mDefaultComplexity; - private Integer mDefaultQuality; - private String mQualityScale; + /** + * A class that supports querying the audio capabilities of a codec. + */ + public static final class AudioCapabilities { + private static final String TAG = "AudioCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; - private void parseFromInfo(MediaFormat info) { - Map<String, Object> map = info.getMap(); + private int[] mSampleRates; + private Range<Integer>[] mSampleRateRanges; + private int mMaxInputChannelCount; - if (info.containsKey("complexity-range")) { - mComplexityRange = Utils - .parseIntRange(info.getString("complexity-range"), mComplexityRange); - // TODO should we limit this to level limits? - } - if (info.containsKey("quality-range")) { - mQualityRange = Utils - .parseIntRange(info.getString("quality-range"), mQualityRange); - } - if (info.containsKey("feature-bitrate-control")) { - for (String mode: info.getString("feature-bitrate-control").split(",")) { - mBitControl |= parseBitrateMode(mode); - } - } + private static final int MAX_INPUT_CHANNEL_COUNT = 30; - try { - mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); - } catch (NumberFormatException e) { } + /** + * Returns the range of supported bitrates in bits/second. + */ + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } - try { - mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); - } catch (NumberFormatException e) { } + /** + * Returns the array of supported sample rates if the codec + * supports only discrete values. Otherwise, it returns + * {@code null}. The array is sorted in ascending order. + */ + public int[] getSupportedSampleRates() { + return Arrays.copyOf(mSampleRates, mSampleRates.length); + } - mQualityScale = (String)map.get("quality-scale"); - } + /** + * Returns the array of supported sample rate ranges. The + * array is sorted in ascending order, and the ranges are + * distinct. + */ + public Range<Integer>[] getSupportedSampleRateRanges() { + return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); + } - private boolean supports( - Integer complexity, Integer quality, Integer profile) { - boolean ok = true; - if (ok && complexity != null) { - ok = mComplexityRange.contains(complexity); - } - if (ok && quality != null) { - ok = mQualityRange.contains(quality); - } - if (ok && profile != null) { - for (CodecProfileLevel pl: mParent.profileLevels) { - if (pl.profile == profile) { - profile = null; - break; - } - } - ok = profile == null; - } - return ok; - } + /** + * Returns the maximum number of input channels supported. The codec + * supports any number of channels between 1 and this maximum value. + */ + public int getMaxInputChannelCount() { + return mMaxInputChannelCount; + } - /** @hide */ - public void setDefaultFormat(MediaFormat format) { - // don't list trivial quality/complexity as default for now - if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) - && mDefaultQuality != null) { - format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); - } - if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) - && mDefaultComplexity != null) { - format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); - } - // bitrates are listed in order of preference - for (Feature feat: bitrates) { - if ((mBitControl & (1 << feat.mValue)) != 0) { - format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); - break; - } - } - } + /* no public constructor */ + private AudioCapabilities() { } + + /** @hide */ + public static AudioCapabilities create( + MediaFormat info, CodecCapabilities parent) { + AudioCapabilities caps = new AudioCapabilities(); + caps.init(info, parent); + return caps; + } + + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - final String mime = mParent.getMime(); + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); + mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT; + // mBitrateRange = Range.create(1, 320000); + mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; + mSampleRates = null; + } - Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); - if (mode != null && !isBitrateModeSupported(mode)) { + private boolean supports(Integer sampleRate, Integer inputChannels) { + // channels and sample rates are checked orthogonally + if (inputChannels != null && + (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) { + return false; + } + if (sampleRate != null) { + int ix = Utils.binarySearchDistinctRanges( + mSampleRateRanges, sampleRate); + if (ix < 0) { return false; } + } + return true; + } - Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); - if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { - Integer flacComplexity = - (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); - if (complexity == null) { - complexity = flacComplexity; - } else if (flacComplexity != null && complexity != flacComplexity) { - throw new IllegalArgumentException( - "conflicting values for complexity and " + - "flac-compression-level"); - } - } + /** + * Query whether the sample rate is supported by the codec. + */ + public boolean isSampleRateSupported(int sampleRate) { + return supports(sampleRate, null); + } - // other audio parameters - Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); - if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { - Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); - if (profile == null) { - profile = aacProfile; - } else if (aacProfile != null && aacProfile != profile) { - throw new IllegalArgumentException( - "conflicting values for profile and aac-profile"); - } + /** modifies rates */ + private void limitSampleRates(int[] rates) { + Arrays.sort(rates); + ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); + for (int rate: rates) { + if (supports(rate, null /* channels */)) { + ranges.add(Range.create(rate, rate)); } + } + mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); + createDiscreteSampleRates(); + } + + private void createDiscreteSampleRates() { + mSampleRates = new int[mSampleRateRanges.length]; + for (int i = 0; i < mSampleRateRanges.length; i++) { + mSampleRates[i] = mSampleRateRanges[i].getLower(); + } + } - Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); + /** modifies rateRanges */ + private void limitSampleRates(Range<Integer>[] rateRanges) { + sortDistinctRanges(rateRanges); + mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); - return supports(complexity, quality, profile); + // check if all values are discrete + for (Range<Integer> range: mSampleRateRanges) { + if (!range.getLower().equals(range.getUpper())) { + mSampleRates = null; + return; + } } - }; + createDiscreteSampleRates(); + } - /** - * A class that supports querying basic capabilities of a codec. - */ - public static class BaseCapabilities { - /** - * Returns the range of supported bitrates in bits/second. - */ - public final Range<Integer> getBitrateRange() { - return mBitrateRange; + private void applyLevelLimits() { + int[] sampleRates = null; + Range<Integer> sampleRateRange = null, bitRates = null; + int maxChannels = 0; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { + sampleRates = new int[] { + 8000, 11025, 12000, + 16000, 22050, 24000, + 32000, 44100, 48000 }; + bitRates = Range.create(8000, 320000); + maxChannels = 2; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(4750, 12200); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { + sampleRates = new int[] { 16000 }; + bitRates = Range.create(6600, 23850); + maxChannels = 1; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { + sampleRates = new int[] { + 7350, 8000, + 11025, 12000, 16000, + 22050, 24000, 32000, + 44100, 48000, 64000, + 88200, 96000 }; + bitRates = Range.create(8000, 510000); + maxChannels = 48; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { + bitRates = Range.create(32000, 500000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { + bitRates = Range.create(6000, 510000); + sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { + sampleRateRange = Range.create(1, 96000); + bitRates = Range.create(1, 10000000); + maxChannels = 8; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + sampleRateRange = Range.create(1, 655350); + // lossless codec, so bitrate is ignored + maxChannels = 255; + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(64000, 64000); + // platform allows multiple channels for this format + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + sampleRates = new int[] { 8000 }; + bitRates = Range.create(13000, 13000); + maxChannels = 1; + } else { + Log.w(TAG, "Unsupported mime " + mime); + mParent.mError |= ERROR_UNSUPPORTED; + } + + // restrict ranges + if (sampleRates != null) { + limitSampleRates(sampleRates); + } else if (sampleRateRange != null) { + limitSampleRates(new Range[] { sampleRateRange }); + } + applyLimits(maxChannels, bitRates); + } + + private void applyLimits(int maxInputChannels, Range<Integer> bitRates) { + mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount) + .clamp(maxInputChannels); + if (bitRates != null) { + mBitrateRange = mBitrateRange.intersect(bitRates); } + } - /** @hide */ - protected Range<Integer> mBitrateRange; + private void parseFromInfo(MediaFormat info) { + int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; + Range<Integer> bitRates = POSITIVE_INTEGERS; - /** @hide */ - protected CodecCapabilities mParent; + if (info.containsKey("sample-rate-ranges")) { + String[] rateStrings = info.getString("sample-rate-ranges").split(","); + Range<Integer>[] rateRanges = new Range[rateStrings.length]; + for (int i = 0; i < rateStrings.length; i++) { + rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); + } + limitSampleRates(rateRanges); + } + if (info.containsKey("max-channel-count")) { + maxInputChannels = Utils.parseIntSafely( + info.getString("max-channel-count"), maxInputChannels); + } + if (info.containsKey("bitrate-range")) { + bitRates = bitRates.intersect( + Utils.parseIntRange(info.getString("bitrate"), bitRates)); + } + applyLimits(maxInputChannels, bitRates); + } - /** @hide */ - protected BaseCapabilities() { + /** @hide */ + public void setDefaultFormat(MediaFormat format) { + // report settings that have only a single choice + if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { + format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); } + if (mMaxInputChannelCount == 1) { + // mono-only format + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + if (mSampleRates != null && mSampleRates.length == 1) { + format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + } + } - /** @hide */ - protected void init(MediaFormat info, CodecCapabilities parent) { - mParent = parent; - mBitrateRange = Range.create(0, Integer.MAX_VALUE); + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + Map<String, Object> map = format.getMap(); + Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); + Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); + if (!supports(sampleRate, channels)) { + return false; } + + // nothing to do for: + // KEY_CHANNEL_MASK: codecs don't get this + // KEY_IS_ADTS: required feature for all AAC decoders + return true; } + } + + /** + * A class that supports querying the video capabilities of a codec. + */ + public static final class VideoCapabilities { + private static final String TAG = "VideoCapabilities"; + private CodecCapabilities mParent; + private Range<Integer> mBitrateRange; + + private Range<Integer> mHeightRange; + private Range<Integer> mWidthRange; + private Range<Integer> mBlockCountRange; + private Range<Integer> mHorizontalBlockRange; + private Range<Integer> mVerticalBlockRange; + private Range<Rational> mAspectRatioRange; + private Range<Rational> mBlockAspectRatioRange; + private Range<Long> mBlocksPerSecondRange; + private Range<Integer> mFrameRateRange; + + private int mBlockWidth; + private int mBlockHeight; + private int mWidthAlignment; + private int mHeightAlignment; + private int mSmallerDimensionUpperLimit; /** - * A class that supports querying the video capabilities of a codec. + * Returns the range of supported bitrates in bits/second. */ - public static final class VideoCapabilities extends BaseCapabilities { - private static final String TAG = "VideoCapabilities"; - private Range<Integer> mHeightRange; - private Range<Integer> mWidthRange; - private Range<Integer> mBlockCountRange; - private Range<Integer> mHorizontalBlockRange; - private Range<Integer> mVerticalBlockRange; - private Range<Rational> mAspectRatioRange; - private Range<Rational> mBlockAspectRatioRange; - private Range<Long> mBlocksPerSecondRange; - private Range<Integer> mFrameRateRange; - - private int mBlockWidth; - private int mBlockHeight; - private int mWidthAlignment; - private int mHeightAlignment; - private int mSmallerDimensionUpperLimit; - - /** - * Returns the range of supported video widths. - */ - public final Range<Integer> getSupportedWidths() { - return mWidthRange; - } + public Range<Integer> getBitrateRange() { + return mBitrateRange; + } - /** - * Returns the range of supported video heights. - */ - public final Range<Integer> getSupportedHeights() { - return mHeightRange; - } + /** + * Returns the range of supported video widths. + */ + public Range<Integer> getSupportedWidths() { + return mWidthRange; + } - /** - * Returns the alignment requirement for video width. - */ - public final int getWidthAlignment() { - return mWidthAlignment; - } + /** + * Returns the range of supported video heights. + */ + public Range<Integer> getSupportedHeights() { + return mHeightRange; + } - /** - * Returns the alignment requirement for video height. - */ - public final int getHeightAlignment() { - return mHeightAlignment; - } + /** + * Returns the alignment requirement for video width (in pixels). + * + * This is a power-of-2 value that video width must be a + * multiple of. + */ + public int getWidthAlignment() { + return mWidthAlignment; + } - /** - * Return the upper limit on the smaller dimension of width or height. - * <p></p> - * Some codecs have a limit on the smaller dimension, whether it be - * the width or the height. E.g. a codec may only be able to handle - * up to 1920x1080 both in landscape and portrait mode (1080x1920). - * In this case the maximum width and height are both 1920, but the - * smaller dimension limit will be 1080. For other codecs, this is - * {@code Math.min(getSupportedWidths().getUpper(), - * getSupportedHeights().getUpper())}. - * - * @hide - */ - public int getSmallerDimensionUpperLimit() { - return mSmallerDimensionUpperLimit; - } + /** + * Returns the alignment requirement for video height (in pixels). + * + * This is a power-of-2 value that video height must be a + * multiple of. + */ + public int getHeightAlignment() { + return mHeightAlignment; + } - /** - * Returns the range of supported frame rates. - * <p> - * This is not a performance indicator. Rather, it expresses the - * limits specified in the coding standard, based on the complexities - * of encoding material for later playback at a certain frame rate, - * or the decoding of such material in non-realtime. - */ - public final Range<Integer> getSupportedFrameRates() { - return mFrameRateRange; - } + /** + * Return the upper limit on the smaller dimension of width or height. + * <p></p> + * Some codecs have a limit on the smaller dimension, whether it be + * the width or the height. E.g. a codec may only be able to handle + * up to 1920x1080 both in landscape and portrait mode (1080x1920). + * In this case the maximum width and height are both 1920, but the + * smaller dimension limit will be 1080. For other codecs, this is + * {@code Math.min(getSupportedWidths().getUpper(), + * getSupportedHeights().getUpper())}. + * + * @hide + */ + public int getSmallerDimensionUpperLimit() { + return mSmallerDimensionUpperLimit; + } - /** - * Returns the range of supported video widths for a video height. - * @param height the height of the video - */ - public final Range<Integer> getSupportedWidthsFor(int height) { - try { - Range<Integer> range = mWidthRange; - if (!mHeightRange.contains(height) - || (height % mHeightAlignment) != 0) { - throw new IllegalArgumentException("unsupported height"); - } - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - - // constrain by block count and by block aspect ratio - final int minWidthInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), - (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() - * heightInBlocks)); - final int maxWidthInBlocks = Math.min( - mBlockCountRange.getUpper() / heightInBlocks, - (int)(mBlockAspectRatioRange.getUpper().doubleValue() - * heightInBlocks)); - range = range.intersect( - (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, - maxWidthInBlocks * mBlockWidth); - - // constrain by smaller dimension limit - if (height > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } + /** + * Returns the range of supported frame rates. + * <p> + * This is not a performance indicator. Rather, it expresses the + * limits specified in the coding standard, based on the complexities + * of encoding material for later playback at a certain frame rate, + * or the decoding of such material in non-realtime. + */ + public Range<Integer> getSupportedFrameRates() { + return mFrameRateRange; + } - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() - * height), - (int)(mAspectRatioRange.getUpper().doubleValue() * height)); - return range; - } catch (IllegalArgumentException e) { - // should not be here - Log.w(TAG, "could not get supported widths for " + height , e); + /** + * Returns the range of supported video widths for a video height. + * @param height the height of the video + */ + public Range<Integer> getSupportedWidthsFor(int height) { + try { + Range<Integer> range = mWidthRange; + if (!mHeightRange.contains(height) + || (height % mHeightAlignment) != 0) { throw new IllegalArgumentException("unsupported height"); } - } - - /** - * Returns the range of supported video heights for a video width - * @param width the width of the video - */ - public final Range<Integer> getSupportedHeightsFor(int width) { - try { - Range<Integer> range = mHeightRange; - if (!mWidthRange.contains(width) - || (width % mWidthAlignment) != 0) { - throw new IllegalArgumentException("unsupported width"); - } - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - - // constrain by block count and by block aspect ratio - final int minHeightInBlocks = Math.max( - Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), - (int)Math.ceil(widthInBlocks / - mBlockAspectRatioRange.getUpper().doubleValue())); - final int maxHeightInBlocks = Math.min( - mBlockCountRange.getUpper() / widthInBlocks, - (int)(widthInBlocks / - mBlockAspectRatioRange.getLower().doubleValue())); - range = range.intersect( - (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, - maxHeightInBlocks * mBlockHeight); - - // constrain by smaller dimension limit - if (width > mSmallerDimensionUpperLimit) { - range = range.intersect(1, mSmallerDimensionUpperLimit); - } - - // constrain by aspect ratio - range = range.intersect( - (int)Math.ceil(width / - mAspectRatioRange.getUpper().doubleValue()), - (int)(width / mAspectRatioRange.getLower().doubleValue())); - return range; - } catch (IllegalArgumentException e) { - // should not be here - Log.w(TAG, "could not get supported heights for " + width , e); - throw new IllegalArgumentException("unsupported width"); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + + // constrain by block count and by block aspect ratio + final int minWidthInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), heightInBlocks), + (int)Math.ceil(mBlockAspectRatioRange.getLower().doubleValue() + * heightInBlocks)); + final int maxWidthInBlocks = Math.min( + mBlockCountRange.getUpper() / heightInBlocks, + (int)(mBlockAspectRatioRange.getUpper().doubleValue() + * heightInBlocks)); + range = range.intersect( + (minWidthInBlocks - 1) * mBlockWidth + mWidthAlignment, + maxWidthInBlocks * mBlockWidth); + + // constrain by smaller dimension limit + if (height > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); } + + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(mAspectRatioRange.getLower().doubleValue() + * height), + (int)(mAspectRatioRange.getUpper().doubleValue() * height)); + return range; + } catch (IllegalArgumentException e) { + // should not be here + Log.w(TAG, "could not get supported widths for " + height , e); + throw new IllegalArgumentException("unsupported height"); } + } - /** - * Returns the range of supported video frame rates for a video size. - * <p> - * This is not a performance indicator. Rather, it expresses the limits specified in - * the coding standard, based on the complexities of encoding material of a given - * size for later playback at a certain frame rate, or the decoding of such material - * in non-realtime. - - * @param width the width of the video - * @param height the height of the video - */ - public final Range<Double> getSupportedFrameRatesFor(int width, int height) { + /** + * Returns the range of supported video heights for a video width + * @param width the width of the video + */ + public Range<Integer> getSupportedHeightsFor(int width) { + try { Range<Integer> range = mHeightRange; - if (!supports(width, height, null)) { - throw new IllegalArgumentException("unsupported size"); + if (!mWidthRange.contains(width) + || (width % mWidthAlignment) != 0) { + throw new IllegalArgumentException("unsupported width"); + } + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + + // constrain by block count and by block aspect ratio + final int minHeightInBlocks = Math.max( + Utils.divUp(mBlockCountRange.getLower(), widthInBlocks), + (int)Math.ceil(widthInBlocks / + mBlockAspectRatioRange.getUpper().doubleValue())); + final int maxHeightInBlocks = Math.min( + mBlockCountRange.getUpper() / widthInBlocks, + (int)(widthInBlocks / + mBlockAspectRatioRange.getLower().doubleValue())); + range = range.intersect( + (minHeightInBlocks - 1) * mBlockHeight + mHeightAlignment, + maxHeightInBlocks * mBlockHeight); + + // constrain by smaller dimension limit + if (width > mSmallerDimensionUpperLimit) { + range = range.intersect(1, mSmallerDimensionUpperLimit); } - final int blockCount = - Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); - - return Range.create( - Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, - (double) mFrameRateRange.getLower()), - Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, - (double) mFrameRateRange.getUpper())); - } - /** - * Returns whether a given video size ({@code width} and - * {@code height}) and {@code frameRate} combination is supported. - */ - public final boolean areSizeAndRateSupported( - int width, int height, double frameRate) { - return supports(width, height, frameRate); + // constrain by aspect ratio + range = range.intersect( + (int)Math.ceil(width / + mAspectRatioRange.getUpper().doubleValue()), + (int)(width / mAspectRatioRange.getLower().doubleValue())); + return range; + } catch (IllegalArgumentException e) { + // should not be here + Log.w(TAG, "could not get supported heights for " + width , e); + throw new IllegalArgumentException("unsupported width"); } + } - /** - * Returns whether a given video size ({@code width} and - * {@code height}) is supported. - */ - public final boolean isSizeSupported(int width, int height) { - return supports(width, height, null); - } + /** + * Returns the range of supported video frame rates for a video size. + * <p> + * This is not a performance indicator. Rather, it expresses the limits specified in + * the coding standard, based on the complexities of encoding material of a given + * size for later playback at a certain frame rate, or the decoding of such material + * in non-realtime. - private final boolean supports( - Integer width, Integer height, Double rate) { - boolean ok = true; + * @param width the width of the video + * @param height the height of the video + */ + public Range<Double> getSupportedFrameRatesFor(int width, int height) { + Range<Integer> range = mHeightRange; + if (!supports(width, height, null)) { + throw new IllegalArgumentException("unsupported size"); + } + final int blockCount = + Utils.divUp(width, mBlockWidth) * Utils.divUp(height, mBlockHeight); + + return Range.create( + Math.max(mBlocksPerSecondRange.getLower() / (double) blockCount, + (double) mFrameRateRange.getLower()), + Math.min(mBlocksPerSecondRange.getUpper() / (double) blockCount, + (double) mFrameRateRange.getUpper())); + } - if (ok && width != null) { - ok = mWidthRange.contains(width) - && (width % mWidthAlignment == 0); - } - if (ok && height != null) { - ok = mHeightRange.contains(height) - && (height % mHeightAlignment == 0); - } + /** + * Returns whether a given video size ({@code width} and + * {@code height}) and {@code frameRate} combination is supported. + */ + public boolean areSizeAndRateSupported( + int width, int height, double frameRate) { + return supports(width, height, frameRate); + } + + /** + * Returns whether a given video size ({@code width} and + * {@code height}) is supported. + */ + public boolean isSizeSupported(int width, int height) { + return supports(width, height, null); + } + + private boolean supports( + Integer width, Integer height, Double rate) { + boolean ok = true; + + if (ok && width != null) { + ok = mWidthRange.contains(width) + && (width % mWidthAlignment == 0); + } + if (ok && height != null) { + ok = mHeightRange.contains(height) + && (height % mHeightAlignment == 0); + } + if (ok && rate != null) { + ok = mFrameRateRange.contains(Utils.intRangeFor(rate)); + } + if (ok && height != null && width != null) { + ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; + + final int widthInBlocks = Utils.divUp(width, mBlockWidth); + final int heightInBlocks = Utils.divUp(height, mBlockHeight); + final int blockCount = widthInBlocks * heightInBlocks; + ok = ok && mBlockCountRange.contains(blockCount) + && mBlockAspectRatioRange.contains( + new Rational(widthInBlocks, heightInBlocks)) + && mAspectRatioRange.contains(new Rational(width, height)); if (ok && rate != null) { - ok = mFrameRateRange.contains(Utils.intRangeFor(rate)); - } - if (ok && height != null && width != null) { - ok = Math.min(height, width) <= mSmallerDimensionUpperLimit; - - final int widthInBlocks = Utils.divUp(width, mBlockWidth); - final int heightInBlocks = Utils.divUp(height, mBlockHeight); - final int blockCount = widthInBlocks * heightInBlocks; - ok = ok && mBlockCountRange.contains(blockCount) - && mBlockAspectRatioRange.contains( - new Rational(widthInBlocks, heightInBlocks)) - && mAspectRatioRange.contains(new Rational(width, height)); - if (ok && rate != null) { - double blocksPerSec = blockCount * rate; - ok = mBlocksPerSecondRange.contains( - Utils.longRangeFor(blocksPerSec)); - } + double blocksPerSec = blockCount * rate; + ok = mBlocksPerSecondRange.contains( + Utils.longRangeFor(blocksPerSec)); } - return ok; } + return ok; + } - /** - * @hide - * @throws java.lang.ClassCastException */ - public boolean supportsFormat(MediaFormat format) { - final Map<String, Object> map = format.getMap(); - Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); - Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); - Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE); + /** + * @hide + * @throws java.lang.ClassCastException */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + Integer width = (Integer)map.get(MediaFormat.KEY_WIDTH); + Integer height = (Integer)map.get(MediaFormat.KEY_HEIGHT); + Double rate = (Double)map.get(MediaFormat.KEY_FRAME_RATE); - // we ignore color-format for now as it is not reliably reported by codec + // we ignore color-format for now as it is not reliably reported by codec - return supports(width, height, rate); - } + return supports(width, height, rate); + } - /* no public constructor */ - private VideoCapabilities() { } + /* no public constructor */ + private VideoCapabilities() { } - /** @hide */ - public static VideoCapabilities create( - MediaFormat info, CodecCapabilities parent) { - VideoCapabilities caps = new VideoCapabilities(); - caps.init(info, parent); - return caps; - } + /** @hide */ + public static VideoCapabilities create( + MediaFormat info, CodecCapabilities parent) { + VideoCapabilities caps = new VideoCapabilities(); + caps.init(info, parent); + return caps; + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - super.init(info, parent); - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); - updateLimits(); - } + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + mParent = parent; + initWithPlatformLimits(); + applyLevelLimits(); + parseFromInfo(info); + updateLimits(); + } - /** @hide */ - public Size getBlockSize() { - return new Size(mBlockWidth, mBlockHeight); - } + /** @hide */ + public Size getBlockSize() { + return new Size(mBlockWidth, mBlockHeight); + } - /** @hide */ - public Range<Integer> getBlockCountRange() { - return mBlockCountRange; - } + /** @hide */ + public Range<Integer> getBlockCountRange() { + return mBlockCountRange; + } - /** @hide */ - public Range<Long> getBlocksPerSecondRange() { - return mBlocksPerSecondRange; - } + /** @hide */ + public Range<Long> getBlocksPerSecondRange() { + return mBlocksPerSecondRange; + } - /** @hide */ - public Range<Rational> getAspectRatioRange(boolean blocks) { - return blocks ? mBlockAspectRatioRange : mAspectRatioRange; - } + /** @hide */ + public Range<Rational> getAspectRatioRange(boolean blocks) { + return blocks ? mBlockAspectRatioRange : mAspectRatioRange; + } - private void initWithPlatformLimits() { - mWidthRange = SIZE_RANGE; - mHeightRange = SIZE_RANGE; - mFrameRateRange = FRAME_RATE_RANGE; + private void initWithPlatformLimits() { + mBitrateRange = Range.create(0, Integer.MAX_VALUE); - mHorizontalBlockRange = SIZE_RANGE; - mVerticalBlockRange = SIZE_RANGE; + mWidthRange = SIZE_RANGE; + mHeightRange = SIZE_RANGE; + mFrameRateRange = FRAME_RATE_RANGE; - // full positive ranges are supported as these get calculated - mBlockCountRange = POSITIVE_INTEGERS; - mBlocksPerSecondRange = POSITIVE_LONGS; + mHorizontalBlockRange = SIZE_RANGE; + mVerticalBlockRange = SIZE_RANGE; - mBlockAspectRatioRange = POSITIVE_RATIONALS; - mAspectRatioRange = POSITIVE_RATIONALS; + // full positive ranges are supported as these get calculated + mBlockCountRange = POSITIVE_INTEGERS; + mBlocksPerSecondRange = POSITIVE_LONGS; - // YUV 4:2:0 requires 2:2 alignment - mWidthAlignment = 2; - mHeightAlignment = 2; - mBlockWidth = 2; - mBlockHeight = 2; - mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper(); - } + mBlockAspectRatioRange = POSITIVE_RATIONALS; + mAspectRatioRange = POSITIVE_RATIONALS; - private void parseFromInfo(MediaFormat info) { - final Map<String, Object> map = info.getMap(); - Size blockSize = new Size(mBlockWidth, mBlockHeight); - Size alignment = new Size(mWidthAlignment, mHeightAlignment); - Range<Integer> counts = null, widths = null, heights = null; - Range<Integer> frameRates = null; - Range<Long> blockRates = null; - Range<Rational> ratios = null, blockRatios = null; - - blockSize = Utils.parseSize(map.get("block-size"), blockSize); - alignment = Utils.parseSize(map.get("alignment"), alignment); - counts = Utils.parseIntRange(map.get("block-count-range"), null); - blockRates = - Utils.parseLongRange(map.get("blocks-per-second-range"), null); - { - Object o = map.get("size-range"); - Pair<Size, Size> sizeRange = Utils.parseSizeRange(o); - if (sizeRange != null) { - try { - widths = Range.create( - sizeRange.first.getWidth(), - sizeRange.second.getWidth()); - heights = Range.create( - sizeRange.first.getHeight(), - sizeRange.second.getHeight()); - } catch (IllegalArgumentException e) { - Log.w(TAG, "could not parse size range '" + o + "'"); - widths = null; - heights = null; - } - } - } - // for now this just means using the smaller max size as 2nd - // upper limit. - // for now we are keeping the profile specific "width/height - // in macroblocks" limits. - if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) { - if (widths != null) { - mSmallerDimensionUpperLimit = - Math.min(widths.getUpper(), heights.getUpper()); - widths = heights = widths.extend(heights); - } else { - Log.w(TAG, "feature can-swap-width-height is best used with size-range"); - mSmallerDimensionUpperLimit = - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); - mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); - } - } + // YUV 4:2:0 requires 2:2 alignment + mWidthAlignment = 2; + mHeightAlignment = 2; + mBlockWidth = 2; + mBlockHeight = 2; + mSmallerDimensionUpperLimit = SIZE_RANGE.getUpper(); + } - ratios = Utils.parseRationalRange( - map.get("block-aspect-ratio-range"), null); - blockRatios = Utils.parseRationalRange( - map.get("pixel-aspect-ratio-range"), null); - frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null); - if (frameRates != null) { + private void parseFromInfo(MediaFormat info) { + final Map<String, Object> map = info.getMap(); + Size blockSize = new Size(mBlockWidth, mBlockHeight); + Size alignment = new Size(mWidthAlignment, mHeightAlignment); + Range<Integer> counts = null, widths = null, heights = null; + Range<Integer> frameRates = null; + Range<Long> blockRates = null; + Range<Rational> ratios = null, blockRatios = null; + + blockSize = Utils.parseSize(map.get("block-size"), blockSize); + alignment = Utils.parseSize(map.get("alignment"), alignment); + counts = Utils.parseIntRange(map.get("block-count-range"), null); + blockRates = + Utils.parseLongRange(map.get("blocks-per-second-range"), null); + { + Object o = map.get("size-range"); + Pair<Size, Size> sizeRange = Utils.parseSizeRange(o); + if (sizeRange != null) { try { - frameRates = frameRates.intersect(FRAME_RATE_RANGE); + widths = Range.create( + sizeRange.first.getWidth(), + sizeRange.second.getWidth()); + heights = Range.create( + sizeRange.first.getHeight(), + sizeRange.second.getHeight()); } catch (IllegalArgumentException e) { - Log.w(TAG, "frame rate range (" + frameRates - + ") is out of limits: " + FRAME_RATE_RANGE); - frameRates = null; + Log.w(TAG, "could not parse size range '" + o + "'"); + widths = null; + heights = null; } } - - checkPowerOfTwo( - blockSize.getWidth(), "block-size width must be power of two"); - checkPowerOfTwo( - blockSize.getHeight(), "block-size height must be power of two"); - - checkPowerOfTwo( - alignment.getWidth(), "alignment width must be power of two"); - checkPowerOfTwo( - alignment.getHeight(), "alignment height must be power of two"); - - // update block-size and alignment - applyMacroBlockLimits( - Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, - Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), - alignment.getWidth(), alignment.getHeight()); - - if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { - // codec supports profiles that we don't know. - // Use supplied values clipped to platform limits - if (widths != null) { - mWidthRange = SIZE_RANGE.intersect(widths); - } - if (heights != null) { - mHeightRange = SIZE_RANGE.intersect(heights); - } - if (counts != null) { - mBlockCountRange = POSITIVE_INTEGERS.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = POSITIVE_LONGS.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); - } + } + // for now this just means using the smaller max size as 2nd + // upper limit. + // for now we are keeping the profile specific "width/height + // in macroblocks" limits. + if (Integer.valueOf(1).equals(map.get("feature-can-swap-width-height"))) { + if (widths != null) { + mSmallerDimensionUpperLimit = + Math.min(widths.getUpper(), heights.getUpper()); + widths = heights = widths.extend(heights); } else { - // no unsupported profile/levels, so restrict values to known limits - if (widths != null) { - mWidthRange = mWidthRange.intersect(widths); - } - if (heights != null) { - mHeightRange = mHeightRange.intersect(heights); - } - if (counts != null) { - mBlockCountRange = mBlockCountRange.intersect( - Utils.factorRange(counts, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRates != null) { - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - Utils.factorRange(blockRates, mBlockWidth * mBlockHeight - / blockSize.getWidth() / blockSize.getHeight())); - } - if (blockRatios != null) { - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - Utils.scaleRange(blockRatios, - mBlockHeight / blockSize.getHeight(), - mBlockWidth / blockSize.getWidth())); - } - if (ratios != null) { - mAspectRatioRange = mAspectRatioRange.intersect(ratios); - } - if (frameRates != null) { - mFrameRateRange = mFrameRateRange.intersect(frameRates); - } + Log.w(TAG, "feature can-swap-width-height is best used with size-range"); + mSmallerDimensionUpperLimit = + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper()); + mWidthRange = mHeightRange = mWidthRange.extend(mHeightRange); } - updateLimits(); } - private int checkPowerOfTwo(int value, String message) { - if ((value & (value - 1)) != 0) { - throw new IllegalArgumentException(message); + ratios = Utils.parseRationalRange( + map.get("block-aspect-ratio-range"), null); + blockRatios = Utils.parseRationalRange( + map.get("pixel-aspect-ratio-range"), null); + frameRates = Utils.parseIntRange(map.get("frame-rate-range"), null); + if (frameRates != null) { + try { + frameRates = frameRates.intersect(FRAME_RATE_RANGE); + } catch (IllegalArgumentException e) { + Log.w(TAG, "frame rate range (" + frameRates + + ") is out of limits: " + FRAME_RATE_RANGE); + frameRates = null; } - return value; } - private void applyBlockLimits( - int blockWidth, int blockHeight, - Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) { - checkPowerOfTwo(blockWidth, "blockWidth must be a power of two"); - checkPowerOfTwo(blockHeight, "blockHeight must be a power of two"); - - final int newBlockWidth = Math.max(blockWidth, mBlockWidth); - final int newBlockHeight = Math.max(blockHeight, mBlockHeight); - - // factor will always be a power-of-2 - int factor = - newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; - if (factor != 1) { - mBlockCountRange = Utils.factorRange(mBlockCountRange, factor); - mBlocksPerSecondRange = Utils.factorRange( - mBlocksPerSecondRange, factor); - mBlockAspectRatioRange = Utils.scaleRange( - mBlockAspectRatioRange, - newBlockHeight / mBlockHeight, - newBlockWidth / mBlockWidth); - mHorizontalBlockRange = Utils.factorRange( - mHorizontalBlockRange, newBlockWidth / mBlockWidth); - mVerticalBlockRange = Utils.factorRange( - mVerticalBlockRange, newBlockHeight / mBlockHeight); + checkPowerOfTwo( + blockSize.getWidth(), "block-size width must be power of two"); + checkPowerOfTwo( + blockSize.getHeight(), "block-size height must be power of two"); + + checkPowerOfTwo( + alignment.getWidth(), "alignment width must be power of two"); + checkPowerOfTwo( + alignment.getHeight(), "alignment height must be power of two"); + + // update block-size and alignment + applyMacroBlockLimits( + Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, + Long.MAX_VALUE, blockSize.getWidth(), blockSize.getHeight(), + alignment.getWidth(), alignment.getHeight()); + + if ((mParent.mError & ERROR_UNSUPPORTED) != 0) { + // codec supports profiles that we don't know. + // Use supplied values clipped to platform limits + if (widths != null) { + mWidthRange = SIZE_RANGE.intersect(widths); + } + if (heights != null) { + mHeightRange = SIZE_RANGE.intersect(heights); + } + if (counts != null) { + mBlockCountRange = POSITIVE_INTEGERS.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = POSITIVE_LONGS.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = POSITIVE_RATIONALS.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = POSITIVE_RATIONALS.intersect(ratios); + } + if (frameRates != null) { + mFrameRateRange = FRAME_RATE_RANGE.intersect(frameRates); + } + } else { + // no unsupported profile/levels, so restrict values to known limits + if (widths != null) { + mWidthRange = mWidthRange.intersect(widths); + } + if (heights != null) { + mHeightRange = mHeightRange.intersect(heights); + } + if (counts != null) { + mBlockCountRange = mBlockCountRange.intersect( + Utils.factorRange(counts, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRates != null) { + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + Utils.factorRange(blockRates, mBlockWidth * mBlockHeight + / blockSize.getWidth() / blockSize.getHeight())); + } + if (blockRatios != null) { + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + Utils.scaleRange(blockRatios, + mBlockHeight / blockSize.getHeight(), + mBlockWidth / blockSize.getWidth())); + } + if (ratios != null) { + mAspectRatioRange = mAspectRatioRange.intersect(ratios); } - factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; - if (factor != 1) { - counts = Utils.factorRange(counts, factor); - rates = Utils.factorRange(rates, factor); - ratios = Utils.scaleRange( - ratios, newBlockHeight / blockHeight, - newBlockWidth / blockWidth); + if (frameRates != null) { + mFrameRateRange = mFrameRateRange.intersect(frameRates); } - mBlockCountRange = mBlockCountRange.intersect(counts); - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); - mBlockWidth = newBlockWidth; - mBlockHeight = newBlockHeight; } + updateLimits(); + } - private void applyAlignment(int widthAlignment, int heightAlignment) { - checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two"); - checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two"); - - if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { - // maintain assumption that 0 < alignment <= block-size - applyBlockLimits( - Math.max(widthAlignment, mBlockWidth), - Math.max(heightAlignment, mBlockHeight), - POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS); - } + private void applyBlockLimits( + int blockWidth, int blockHeight, + Range<Integer> counts, Range<Long> rates, Range<Rational> ratios) { + checkPowerOfTwo(blockWidth, "blockWidth must be a power of two"); + checkPowerOfTwo(blockHeight, "blockHeight must be a power of two"); + + final int newBlockWidth = Math.max(blockWidth, mBlockWidth); + final int newBlockHeight = Math.max(blockHeight, mBlockHeight); + + // factor will always be a power-of-2 + int factor = + newBlockWidth * newBlockHeight / mBlockWidth / mBlockHeight; + if (factor != 1) { + mBlockCountRange = Utils.factorRange(mBlockCountRange, factor); + mBlocksPerSecondRange = Utils.factorRange( + mBlocksPerSecondRange, factor); + mBlockAspectRatioRange = Utils.scaleRange( + mBlockAspectRatioRange, + newBlockHeight / mBlockHeight, + newBlockWidth / mBlockWidth); + mHorizontalBlockRange = Utils.factorRange( + mHorizontalBlockRange, newBlockWidth / mBlockWidth); + mVerticalBlockRange = Utils.factorRange( + mVerticalBlockRange, newBlockHeight / mBlockHeight); + } + factor = newBlockWidth * newBlockHeight / blockWidth / blockHeight; + if (factor != 1) { + counts = Utils.factorRange(counts, factor); + rates = Utils.factorRange(rates, factor); + ratios = Utils.scaleRange( + ratios, newBlockHeight / blockHeight, + newBlockWidth / blockWidth); + } + mBlockCountRange = mBlockCountRange.intersect(counts); + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect(rates); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect(ratios); + mBlockWidth = newBlockWidth; + mBlockHeight = newBlockHeight; + } - mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); - mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); + private void applyAlignment(int widthAlignment, int heightAlignment) { + checkPowerOfTwo(widthAlignment, "widthAlignment must be a power of two"); + checkPowerOfTwo(heightAlignment, "heightAlignment must be a power of two"); - mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); - mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + if (widthAlignment > mBlockWidth || heightAlignment > mBlockHeight) { + // maintain assumption that 0 < alignment <= block-size + applyBlockLimits( + Math.max(widthAlignment, mBlockWidth), + Math.max(heightAlignment, mBlockHeight), + POSITIVE_INTEGERS, POSITIVE_LONGS, POSITIVE_RATIONALS); } - private void updateLimits() { - // pixels -> blocks <- counts - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Utils.factorRange(mWidthRange, mBlockWidth)); - mHorizontalBlockRange = mHorizontalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Utils.factorRange(mHeightRange, mBlockHeight)); - mVerticalBlockRange = mVerticalBlockRange.intersect( - Range.create( - mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), - mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); - mBlockCountRange = mBlockCountRange.intersect( - Range.create( - mHorizontalBlockRange.getLower() - * mVerticalBlockRange.getLower(), - mHorizontalBlockRange.getUpper() - * mVerticalBlockRange.getUpper())); - mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( - new Rational( - mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), - new Rational( - mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); - - // blocks -> pixels - mWidthRange = mWidthRange.intersect( - (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, - mHorizontalBlockRange.getUpper() * mBlockWidth); - mHeightRange = mHeightRange.intersect( - (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, - mVerticalBlockRange.getUpper() * mBlockHeight); - mAspectRatioRange = mAspectRatioRange.intersect( - new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), - new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); - - mSmallerDimensionUpperLimit = Math.min( - mSmallerDimensionUpperLimit, - Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); - - // blocks -> rate - mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( - mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), - mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); - mFrameRateRange = mFrameRateRange.intersect( - (int)(mBlocksPerSecondRange.getLower() - / mBlockCountRange.getUpper()), - (int)(mBlocksPerSecondRange.getUpper() - / (double)mBlockCountRange.getLower())); - } + mWidthAlignment = Math.max(widthAlignment, mWidthAlignment); + mHeightAlignment = Math.max(heightAlignment, mHeightAlignment); - private void applyMacroBlockLimits( - int maxHorizontalBlocks, int maxVerticalBlocks, - int maxBlocks, long maxBlocksPerSecond, - int blockWidth, int blockHeight, - int widthAlignment, int heightAlignment) { - applyAlignment(widthAlignment, heightAlignment); - applyBlockLimits( - blockWidth, blockHeight, Range.create(1, maxBlocks), - Range.create(1L, maxBlocksPerSecond), - Range.create( - new Rational(1, maxVerticalBlocks), - new Rational(maxHorizontalBlocks, 1))); - mHorizontalBlockRange = - mHorizontalBlockRange.intersect( - 1, maxHorizontalBlocks / (mBlockWidth / blockWidth)); - mVerticalBlockRange = - mVerticalBlockRange.intersect( - 1, maxVerticalBlocks / (mBlockHeight / blockHeight)); - } + mWidthRange = Utils.alignRange(mWidthRange, mWidthAlignment); + mHeightRange = Utils.alignRange(mHeightRange, mHeightAlignment); + } - private void applyLevelLimits() { - int maxBlocksPerSecond = 0; - int maxBlocks = 0; - int maxBps = 0; - int maxDPBBlocks = 0; - - int errors = ERROR_NONE_SUPPORTED; - CodecProfileLevel[] profileLevels = mParent.profileLevels; - String mime = mParent.getMime(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - maxDPBBlocks = 396; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, DPB = 0; - boolean supported = true; - switch (profileLevel.level) { - case CodecProfileLevel.AVCLevel1: - MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; - case CodecProfileLevel.AVCLevel1b: - MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; - case CodecProfileLevel.AVCLevel11: - MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; - case CodecProfileLevel.AVCLevel12: - MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; - case CodecProfileLevel.AVCLevel13: - MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; - case CodecProfileLevel.AVCLevel2: - MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; - case CodecProfileLevel.AVCLevel21: - MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; - case CodecProfileLevel.AVCLevel22: - MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel3: - MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; - case CodecProfileLevel.AVCLevel31: - MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; - case CodecProfileLevel.AVCLevel32: - MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; - case CodecProfileLevel.AVCLevel4: - MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel41: - MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; - case CodecProfileLevel.AVCLevel42: - MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; - case CodecProfileLevel.AVCLevel5: - MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; - case CodecProfileLevel.AVCLevel51: - MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; - case CodecProfileLevel.AVCLevel52: - MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.AVCProfileHigh: - BR *= 1250; break; - case CodecProfileLevel.AVCProfileHigh10: - BR *= 3000; break; - case CodecProfileLevel.AVCProfileExtended: - case CodecProfileLevel.AVCProfileHigh422: - case CodecProfileLevel.AVCProfileHigh444: - Log.w(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - // fall through - treat as base profile - case CodecProfileLevel.AVCProfileBaseline: - case CodecProfileLevel.AVCProfileMain: - BR *= 1000; break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - BR *= 1000; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR, maxBps); - maxDPBBlocks = Math.max(maxDPBBlocks, DPB); - } + private void updateLimits() { + // pixels -> blocks <- counts + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Utils.factorRange(mWidthRange, mBlockWidth)); + mHorizontalBlockRange = mHorizontalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mVerticalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mVerticalBlockRange.getLower())); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Utils.factorRange(mHeightRange, mBlockHeight)); + mVerticalBlockRange = mVerticalBlockRange.intersect( + Range.create( + mBlockCountRange.getLower() / mHorizontalBlockRange.getUpper(), + mBlockCountRange.getUpper() / mHorizontalBlockRange.getLower())); + mBlockCountRange = mBlockCountRange.intersect( + Range.create( + mHorizontalBlockRange.getLower() + * mVerticalBlockRange.getLower(), + mHorizontalBlockRange.getUpper() + * mVerticalBlockRange.getUpper())); + mBlockAspectRatioRange = mBlockAspectRatioRange.intersect( + new Rational( + mHorizontalBlockRange.getLower(), mVerticalBlockRange.getUpper()), + new Rational( + mHorizontalBlockRange.getUpper(), mVerticalBlockRange.getLower())); + + // blocks -> pixels + mWidthRange = mWidthRange.intersect( + (mHorizontalBlockRange.getLower() - 1) * mBlockWidth + mWidthAlignment, + mHorizontalBlockRange.getUpper() * mBlockWidth); + mHeightRange = mHeightRange.intersect( + (mVerticalBlockRange.getLower() - 1) * mBlockHeight + mHeightAlignment, + mVerticalBlockRange.getUpper() * mBlockHeight); + mAspectRatioRange = mAspectRatioRange.intersect( + new Rational(mWidthRange.getLower(), mHeightRange.getUpper()), + new Rational(mWidthRange.getUpper(), mHeightRange.getLower())); + + mSmallerDimensionUpperLimit = Math.min( + mSmallerDimensionUpperLimit, + Math.min(mWidthRange.getUpper(), mHeightRange.getUpper())); + + // blocks -> rate + mBlocksPerSecondRange = mBlocksPerSecondRange.intersect( + mBlockCountRange.getLower() * (long)mFrameRateRange.getLower(), + mBlockCountRange.getUpper() * (long)mFrameRateRange.getUpper()); + mFrameRateRange = mFrameRateRange.intersect( + (int)(mBlocksPerSecondRange.getLower() + / mBlockCountRange.getUpper()), + (int)(mBlocksPerSecondRange.getUpper() + / (double)mBlockCountRange.getLower())); + } - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; - boolean supported = true; - switch (profileLevel.profile) { - case CodecProfileLevel.MPEG4ProfileSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; - case CodecProfileLevel.MPEG4Level0b: - FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level4: - case CodecProfileLevel.MPEG4Level4a: - case CodecProfileLevel.MPEG4Level5: - // While MPEG4 SP does not have level 4 or 5, some vendors - // report it. Use the same limits as level 3, but mark as - // unsupported. - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileAdvancedSimple: - switch (profileLevel.level) { - case CodecProfileLevel.MPEG4Level0: - case CodecProfileLevel.MPEG4Level1: - FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; - case CodecProfileLevel.MPEG4Level2: - FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; - case CodecProfileLevel.MPEG4Level3: - FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; - // case CodecProfileLevel.MPEG4Level3b: - // TODO: MPEG4 level 3b is not defined in OMX - // MBPS = 11880; FS = 396; BR = 1500; break; - case CodecProfileLevel.MPEG4Level4: - case CodecProfileLevel.MPEG4Level4a: - // TODO: MPEG4 level 4a is not defined in spec - FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; - case CodecProfileLevel.MPEG4Level5: - FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; - default: - Log.w(TAG, "Unrecognized profile/level " - + profileLevel.profile + "/" - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - break; - case CodecProfileLevel.MPEG4ProfileMain: // 2-4 - case CodecProfileLevel.MPEG4ProfileNbit: // 2 - case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 - case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 - case CodecProfileLevel.MPEG4ProfileCore: // 1-2 - case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 - case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 - case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 - case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 - case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 - case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 - case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 - case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 - Log.i(TAG, "Unsupported profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNSUPPORTED; - supported = false; - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - if (supported) { - errors &= ~ERROR_NONE_SUPPORTED; - } - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); + private void applyMacroBlockLimits( + int maxHorizontalBlocks, int maxVerticalBlocks, + int maxBlocks, long maxBlocksPerSecond, + int blockWidth, int blockHeight, + int widthAlignment, int heightAlignment) { + applyAlignment(widthAlignment, heightAlignment); + applyBlockLimits( + blockWidth, blockHeight, Range.create(1, maxBlocks), + Range.create(1L, maxBlocksPerSecond), + Range.create( + new Rational(1, maxVerticalBlocks), + new Rational(maxHorizontalBlocks, 1))); + mHorizontalBlockRange = + mHorizontalBlockRange.intersect( + 1, maxHorizontalBlocks / (mBlockWidth / blockWidth)); + mVerticalBlockRange = + mVerticalBlockRange.intersect( + 1, maxVerticalBlocks / (mBlockHeight / blockHeight)); + } + + private void applyLevelLimits() { + int maxBlocksPerSecond = 0; + int maxBlocks = 0; + int maxBps = 0; + int maxDPBBlocks = 0; + + int errors = ERROR_NONE_SUPPORTED; + CodecProfileLevel[] profileLevels = mParent.profileLevels; + String mime = mParent.getMimeType(); + + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC)) { + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + maxDPBBlocks = 396; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, DPB = 0; + boolean supported = true; + switch (profileLevel.level) { + case CodecProfileLevel.AVCLevel1: + MBPS = 1485; FS = 99; BR = 64; DPB = 396; break; + case CodecProfileLevel.AVCLevel1b: + MBPS = 1485; FS = 99; BR = 128; DPB = 396; break; + case CodecProfileLevel.AVCLevel11: + MBPS = 3000; FS = 396; BR = 192; DPB = 900; break; + case CodecProfileLevel.AVCLevel12: + MBPS = 6000; FS = 396; BR = 384; DPB = 2376; break; + case CodecProfileLevel.AVCLevel13: + MBPS = 11880; FS = 396; BR = 768; DPB = 2376; break; + case CodecProfileLevel.AVCLevel2: + MBPS = 11880; FS = 396; BR = 2000; DPB = 2376; break; + case CodecProfileLevel.AVCLevel21: + MBPS = 19800; FS = 792; BR = 4000; DPB = 4752; break; + case CodecProfileLevel.AVCLevel22: + MBPS = 20250; FS = 1620; BR = 4000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel3: + MBPS = 40500; FS = 1620; BR = 10000; DPB = 8100; break; + case CodecProfileLevel.AVCLevel31: + MBPS = 108000; FS = 3600; BR = 14000; DPB = 18000; break; + case CodecProfileLevel.AVCLevel32: + MBPS = 216000; FS = 5120; BR = 20000; DPB = 20480; break; + case CodecProfileLevel.AVCLevel4: + MBPS = 245760; FS = 8192; BR = 20000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel41: + MBPS = 245760; FS = 8192; BR = 50000; DPB = 32768; break; + case CodecProfileLevel.AVCLevel42: + MBPS = 522240; FS = 8704; BR = 50000; DPB = 34816; break; + case CodecProfileLevel.AVCLevel5: + MBPS = 589824; FS = 22080; BR = 135000; DPB = 110400; break; + case CodecProfileLevel.AVCLevel51: + MBPS = 983040; FS = 36864; BR = 240000; DPB = 184320; break; + case CodecProfileLevel.AVCLevel52: + MBPS = 2073600; FS = 36864; BR = 240000; DPB = 184320; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = mFrameRateRange.intersect(12, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { - int maxWidth = 11, maxHeight = 9, maxRate = 15; - maxBlocks = 99; - maxBlocksPerSecond = 1485; - maxBps = 64000; - for (CodecProfileLevel profileLevel: profileLevels) { - int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0; - switch (profileLevel.level) { - case CodecProfileLevel.H263Level10: - FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level20: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level30: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level40: - // only supports CIF, 0..QCIF - FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level45: - // only implies level 10 support - FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; - case CodecProfileLevel.H263Level50: - // only supports 50fps for H > 15 - FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level60: - // only supports 50fps for H > 15 - FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; - case CodecProfileLevel.H263Level70: - // only supports 50fps for H > 30 - FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; - default: - Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile - + "/" + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.H263ProfileBackwardCompatible: - case CodecProfileLevel.H263ProfileBaseline: - case CodecProfileLevel.H263ProfileH320Coding: - case CodecProfileLevel.H263ProfileHighCompression: - case CodecProfileLevel.H263ProfileHighLatency: - case CodecProfileLevel.H263ProfileInterlace: - case CodecProfileLevel.H263ProfileInternet: - case CodecProfileLevel.H263ProfileISWV2: - case CodecProfileLevel.H263ProfileISWV3: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); - maxBlocks = Math.max(W * H, maxBlocks); - maxBps = Math.max(BR * 64000, maxBps); - maxWidth = Math.max(W, maxWidth); - maxHeight = Math.max(H, maxHeight); - maxRate = Math.max(FR, maxRate); + switch (profileLevel.profile) { + case CodecProfileLevel.AVCProfileHigh: + BR *= 1250; break; + case CodecProfileLevel.AVCProfileHigh10: + BR *= 3000; break; + case CodecProfileLevel.AVCProfileExtended: + case CodecProfileLevel.AVCProfileHigh422: + case CodecProfileLevel.AVCProfileHigh444: + Log.w(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + // fall through - treat as base profile + case CodecProfileLevel.AVCProfileBaseline: + case CodecProfileLevel.AVCProfileMain: + BR *= 1000; break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + BR *= 1000; } - applyMacroBlockLimits(maxWidth, maxHeight, - maxBlocks, maxBlocksPerSecond, - 16 /* blockWidth */, 16 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - mFrameRateRange = Range.create(1, maxRate); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) || - mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { - maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE; - - // TODO: set to 100Mbps for now, need a number for VPX - maxBps = 100000000; - - // profile levels are not indicative for VPx, but verify - // them nonetheless - for (CodecProfileLevel profileLevel: profileLevels) { - switch (profileLevel.level) { - case CodecProfileLevel.VP8Level_Version0: - case CodecProfileLevel.VP8Level_Version1: - case CodecProfileLevel.VP8Level_Version2: - case CodecProfileLevel.VP8Level_Version3: - break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.VP8ProfileMain: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } + if (supported) { errors &= ~ERROR_NONE_SUPPORTED; } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR, maxBps); + maxDPBBlocks = Math.max(maxDPBBlocks, DPB); + } - final int blockSize = - mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8; - applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, - maxBlocks, maxBlocksPerSecond, blockSize, blockSize, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { - maxBlocks = 36864; - maxBlocksPerSecond = maxBlocks * 15; - maxBps = 128000; - for (CodecProfileLevel profileLevel: profileLevels) { - double FR = 0; - int FS = 0; - int BR = 0; - switch (profileLevel.level) { - case CodecProfileLevel.HEVCMainTierLevel1: - case CodecProfileLevel.HEVCHighTierLevel1: - FR = 15; FS = 36864; BR = 128; break; - case CodecProfileLevel.HEVCMainTierLevel2: - case CodecProfileLevel.HEVCHighTierLevel2: - FR = 30; FS = 122880; BR = 1500; break; - case CodecProfileLevel.HEVCMainTierLevel21: - case CodecProfileLevel.HEVCHighTierLevel21: - FR = 30; FS = 245760; BR = 3000; break; - case CodecProfileLevel.HEVCMainTierLevel3: - case CodecProfileLevel.HEVCHighTierLevel3: - FR = 30; FS = 552960; BR = 6000; break; - case CodecProfileLevel.HEVCMainTierLevel31: - case CodecProfileLevel.HEVCHighTierLevel31: - FR = 33.75; FS = 983040; BR = 10000; break; - case CodecProfileLevel.HEVCMainTierLevel4: - FR = 30; FS = 2228224; BR = 12000; break; - case CodecProfileLevel.HEVCHighTierLevel4: - FR = 30; FS = 2228224; BR = 30000; break; - case CodecProfileLevel.HEVCMainTierLevel41: - FR = 60; FS = 2228224; BR = 20000; break; - case CodecProfileLevel.HEVCHighTierLevel41: - FR = 60; FS = 2228224; BR = 50000; break; - case CodecProfileLevel.HEVCMainTierLevel5: - FR = 30; FS = 8912896; BR = 25000; break; - case CodecProfileLevel.HEVCHighTierLevel5: - FR = 30; FS = 8912896; BR = 100000; break; - case CodecProfileLevel.HEVCMainTierLevel51: - FR = 60; FS = 8912896; BR = 40000; break; - case CodecProfileLevel.HEVCHighTierLevel51: - FR = 60; FS = 8912896; BR = 160000; break; - case CodecProfileLevel.HEVCMainTierLevel52: - FR = 120; FS = 8912896; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel52: - FR = 120; FS = 8912896; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel6: - FR = 30; FS = 35651584; BR = 60000; break; - case CodecProfileLevel.HEVCHighTierLevel6: - FR = 30; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCMainTierLevel61: - FR = 60; FS = 35651584; BR = 120000; break; - case CodecProfileLevel.HEVCHighTierLevel61: - FR = 60; FS = 35651584; BR = 480000; break; - case CodecProfileLevel.HEVCMainTierLevel62: - FR = 120; FS = 35651584; BR = 240000; break; - case CodecProfileLevel.HEVCHighTierLevel62: - FR = 120; FS = 35651584; BR = 800000; break; - default: - Log.w(TAG, "Unrecognized level " - + profileLevel.level + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - switch (profileLevel.profile) { - case CodecProfileLevel.HEVCProfileMain: - case CodecProfileLevel.HEVCProfileMain10: - break; - default: - Log.w(TAG, "Unrecognized profile " - + profileLevel.profile + " for " + mime); - errors |= ERROR_UNRECOGNIZED; - } - - /* DPB logic: - if (width * height <= FS / 4) DPB = 16; - else if (width * height <= FS / 2) DPB = 12; - else if (width * height <= FS * 0.75) DPB = 8; - else DPB = 6; - */ - + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, FS = 0, BR = 0, FR = 0, W = 0, H = 0; + boolean supported = true; + switch (profileLevel.profile) { + case CodecProfileLevel.MPEG4ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + FR = 15; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 64; break; + case CodecProfileLevel.MPEG4Level0b: + FR = 30; W = 11; H = 9; MBPS = 1485; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 128; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level4: + case CodecProfileLevel.MPEG4Level4a: + case CodecProfileLevel.MPEG4Level5: + // While MPEG4 SP does not have level 4 or 5, some vendors + // report it. Use the same limits as level 3, but mark as + // unsupported. + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 384; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileAdvancedSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG4Level0: + case CodecProfileLevel.MPEG4Level1: + FR = 30; W = 11; H = 9; MBPS = 2970; FS = 99; BR = 128; break; + case CodecProfileLevel.MPEG4Level2: + FR = 30; W = 22; H = 18; MBPS = 5940; FS = 396; BR = 384; break; + case CodecProfileLevel.MPEG4Level3: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 768; break; + // case CodecProfileLevel.MPEG4Level3b: + // TODO: MPEG4 level 3b is not defined in OMX + // MBPS = 11880; FS = 396; BR = 1500; break; + case CodecProfileLevel.MPEG4Level4: + case CodecProfileLevel.MPEG4Level4a: + // TODO: MPEG4 level 4a is not defined in spec + FR = 30; W = 44; H = 36; MBPS = 23760; FS = 792; BR = 3000; break; + case CodecProfileLevel.MPEG4Level5: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 8000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG4ProfileMain: // 2-4 + case CodecProfileLevel.MPEG4ProfileNbit: // 2 + case CodecProfileLevel.MPEG4ProfileAdvancedRealTime: // 1-4 + case CodecProfileLevel.MPEG4ProfileCoreScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileAdvancedCoding: // 1-4 + case CodecProfileLevel.MPEG4ProfileCore: // 1-2 + case CodecProfileLevel.MPEG4ProfileAdvancedCore: // 1-4 + case CodecProfileLevel.MPEG4ProfileSimpleScalable: // 0-2 + case CodecProfileLevel.MPEG4ProfileAdvancedScalable: // 1-3 + case CodecProfileLevel.MPEG4ProfileHybrid: // 1-2 + case CodecProfileLevel.MPEG4ProfileBasicAnimated: // 1-2 + case CodecProfileLevel.MPEG4ProfileScalableTexture: // 1 + case CodecProfileLevel.MPEG4ProfileSimpleFace: // 1-2 + case CodecProfileLevel.MPEG4ProfileSimpleFBA: // 1-2 + Log.i(TAG, "Unsupported profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNSUPPORTED; + supported = false; + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + if (supported) { errors &= ~ERROR_NONE_SUPPORTED; - maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); - maxBlocks = Math.max(FS, maxBlocks); - maxBps = Math.max(BR * 1000, maxBps); + } + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = mFrameRateRange.intersect(12, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263)) { + int maxWidth = 11, maxHeight = 9, maxRate = 15; + maxBlocks = 99; + maxBlocksPerSecond = 1485; + maxBps = 64000; + for (CodecProfileLevel profileLevel: profileLevels) { + int MBPS = 0, BR = 0, FR = 0, W = 0, H = 0; + switch (profileLevel.level) { + case CodecProfileLevel.H263Level10: + FR = 15; W = 11; H = 9; BR = 1; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level20: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level30: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 6; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level40: + // only supports CIF, 0..QCIF + FR = 30; W = 22; H = 18; BR = 32; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level45: + // only implies level 10 support + FR = 30; W = 11; H = 9; BR = 2; MBPS = W * H * FR; break; + case CodecProfileLevel.H263Level50: + // only supports 50fps for H > 15 + FR = 60; W = 22; H = 18; BR = 64; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level60: + // only supports 50fps for H > 15 + FR = 60; W = 45; H = 18; BR = 128; MBPS = W * H * 50; break; + case CodecProfileLevel.H263Level70: + // only supports 50fps for H > 30 + FR = 60; W = 45; H = 36; BR = 256; MBPS = W * H * 50; break; + default: + Log.w(TAG, "Unrecognized profile/level " + profileLevel.profile + + "/" + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.H263ProfileBackwardCompatible: + case CodecProfileLevel.H263ProfileBaseline: + case CodecProfileLevel.H263ProfileH320Coding: + case CodecProfileLevel.H263ProfileHighCompression: + case CodecProfileLevel.H263ProfileHighLatency: + case CodecProfileLevel.H263ProfileInterlace: + case CodecProfileLevel.H263ProfileInternet: + case CodecProfileLevel.H263ProfileISWV2: + case CodecProfileLevel.H263ProfileISWV3: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max(MBPS, maxBlocksPerSecond); + maxBlocks = Math.max(W * H, maxBlocks); + maxBps = Math.max(BR * 64000, maxBps); + maxWidth = Math.max(W, maxWidth); + maxHeight = Math.max(H, maxHeight); + maxRate = Math.max(FR, maxRate); + } + applyMacroBlockLimits(maxWidth, maxHeight, + maxBlocks, maxBlocksPerSecond, + 16 /* blockWidth */, 16 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + mFrameRateRange = Range.create(1, maxRate); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) || + mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9)) { + maxBlocks = maxBlocksPerSecond = Integer.MAX_VALUE; + + // TODO: set to 100Mbps for now, need a number for VPX + maxBps = 100000000; + + // profile levels are not indicative for VPx, but verify + // them nonetheless + for (CodecProfileLevel profileLevel: profileLevels) { + switch (profileLevel.level) { + case CodecProfileLevel.VP8Level_Version0: + case CodecProfileLevel.VP8Level_Version1: + case CodecProfileLevel.VP8Level_Version2: + case CodecProfileLevel.VP8Level_Version3: + break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.VP8ProfileMain: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + errors &= ~ERROR_NONE_SUPPORTED; + } + + final int blockSize = + mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8) ? 16 : 8; + applyMacroBlockLimits(Short.MAX_VALUE, Short.MAX_VALUE, + maxBlocks, maxBlocksPerSecond, blockSize, blockSize, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC)) { + maxBlocks = 36864; + maxBlocksPerSecond = maxBlocks * 15; + maxBps = 128000; + for (CodecProfileLevel profileLevel: profileLevels) { + double FR = 0; + int FS = 0; + int BR = 0; + switch (profileLevel.level) { + case CodecProfileLevel.HEVCMainTierLevel1: + case CodecProfileLevel.HEVCHighTierLevel1: + FR = 15; FS = 36864; BR = 128; break; + case CodecProfileLevel.HEVCMainTierLevel2: + case CodecProfileLevel.HEVCHighTierLevel2: + FR = 30; FS = 122880; BR = 1500; break; + case CodecProfileLevel.HEVCMainTierLevel21: + case CodecProfileLevel.HEVCHighTierLevel21: + FR = 30; FS = 245760; BR = 3000; break; + case CodecProfileLevel.HEVCMainTierLevel3: + case CodecProfileLevel.HEVCHighTierLevel3: + FR = 30; FS = 552960; BR = 6000; break; + case CodecProfileLevel.HEVCMainTierLevel31: + case CodecProfileLevel.HEVCHighTierLevel31: + FR = 33.75; FS = 983040; BR = 10000; break; + case CodecProfileLevel.HEVCMainTierLevel4: + FR = 30; FS = 2228224; BR = 12000; break; + case CodecProfileLevel.HEVCHighTierLevel4: + FR = 30; FS = 2228224; BR = 30000; break; + case CodecProfileLevel.HEVCMainTierLevel41: + FR = 60; FS = 2228224; BR = 20000; break; + case CodecProfileLevel.HEVCHighTierLevel41: + FR = 60; FS = 2228224; BR = 50000; break; + case CodecProfileLevel.HEVCMainTierLevel5: + FR = 30; FS = 8912896; BR = 25000; break; + case CodecProfileLevel.HEVCHighTierLevel5: + FR = 30; FS = 8912896; BR = 100000; break; + case CodecProfileLevel.HEVCMainTierLevel51: + FR = 60; FS = 8912896; BR = 40000; break; + case CodecProfileLevel.HEVCHighTierLevel51: + FR = 60; FS = 8912896; BR = 160000; break; + case CodecProfileLevel.HEVCMainTierLevel52: + FR = 120; FS = 8912896; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel52: + FR = 120; FS = 8912896; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel6: + FR = 30; FS = 35651584; BR = 60000; break; + case CodecProfileLevel.HEVCHighTierLevel6: + FR = 30; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCMainTierLevel61: + FR = 60; FS = 35651584; BR = 120000; break; + case CodecProfileLevel.HEVCHighTierLevel61: + FR = 60; FS = 35651584; BR = 480000; break; + case CodecProfileLevel.HEVCMainTierLevel62: + FR = 120; FS = 35651584; BR = 240000; break; + case CodecProfileLevel.HEVCHighTierLevel62: + FR = 120; FS = 35651584; BR = 800000; break; + default: + Log.w(TAG, "Unrecognized level " + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + switch (profileLevel.profile) { + case CodecProfileLevel.HEVCProfileMain: + case CodecProfileLevel.HEVCProfileMain10: + break; + default: + Log.w(TAG, "Unrecognized profile " + + profileLevel.profile + " for " + mime); + errors |= ERROR_UNRECOGNIZED; } - int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); - // CTBs are at least 8x8 - maxBlocks = Utils.divUp(maxBlocks, 8 * 8); - maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8); - maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8); - - applyMacroBlockLimits( - maxLengthInBlocks, maxLengthInBlocks, - maxBlocks, maxBlocksPerSecond, - 8 /* blockWidth */, 8 /* blockHeight */, - 1 /* widthAlignment */, 1 /* heightAlignment */); - } else { - Log.w(TAG, "Unsupported mime " + mime); - // using minimal bitrate here. should be overriden by - // info from media_codecs.xml - maxBps = 64000; - errors |= ERROR_UNSUPPORTED; + /* DPB logic: + if (width * height <= FS / 4) DPB = 16; + else if (width * height <= FS / 2) DPB = 12; + else if (width * height <= FS * 0.75) DPB = 8; + else DPB = 6; + */ + + errors &= ~ERROR_NONE_SUPPORTED; + maxBlocksPerSecond = Math.max((int)(FR * FS), maxBlocksPerSecond); + maxBlocks = Math.max(FS, maxBlocks); + maxBps = Math.max(BR * 1000, maxBps); } - mBitrateRange = Range.create(1, maxBps); - mParent.mError |= errors; - } - }; - VideoCapabilities mVideoCaps; + int maxLengthInBlocks = (int)(Math.sqrt(maxBlocks * 8)); + // CTBs are at least 8x8 + maxBlocks = Utils.divUp(maxBlocks, 8 * 8); + maxBlocksPerSecond = Utils.divUp(maxBlocksPerSecond, 8 * 8); + maxLengthInBlocks = Utils.divUp(maxLengthInBlocks, 8); - private boolean isVideo() { - return mVideoCaps != null; + applyMacroBlockLimits( + maxLengthInBlocks, maxLengthInBlocks, + maxBlocks, maxBlocksPerSecond, + 8 /* blockWidth */, 8 /* blockHeight */, + 1 /* widthAlignment */, 1 /* heightAlignment */); + } else { + Log.w(TAG, "Unsupported mime " + mime); + // using minimal bitrate here. should be overriden by + // info from media_codecs.xml + maxBps = 64000; + errors |= ERROR_UNSUPPORTED; + } + mBitrateRange = Range.create(1, maxBps); + mParent.mError |= errors; } + } + /** + * A class that supports querying the encoding capabilities of a codec. + */ + public static final class EncoderCapabilities { /** - * Returns the video capabilities or {@code null} if this is not a video codec. + * Returns the supported range of quality values. + * + * @hide */ - public final VideoCapabilities getVideoCapabilities() { - return mVideoCaps; + public Range<Integer> getQualityRange() { + return mQualityRange; } /** - * Retrieve the codec capabilities for a certain {@code mime type}, {@code - * profile} and {@code level}. If the type, or profile-level combination - * is not understood by the framework, it returns null. + * Returns the supported range of encoder complexity values. + * <p> + * Some codecs may support multiple complexity levels, where higher + * complexity values use more encoder tools (e.g. perform more + * intensive calculations) to improve the quality or the compression + * ratio. Use a lower value to save power and/or time. */ - public static final CodecCapabilities CreateFromProfileLevel( - String mime, int profile, int level) { - CodecProfileLevel pl = new CodecProfileLevel(); - pl.profile = profile; - pl.level = level; - MediaFormat defaultFormat = new MediaFormat(); - defaultFormat.setString(MediaFormat.KEY_MIME, mime); - - CodecCapabilities ret = new CodecCapabilities( - new CodecProfileLevel[] { pl }, new int[0], true /* encoder */, - 0 /* flags */, defaultFormat, new MediaFormat() /* info */); - if (ret.mError != 0) { - return null; - } - return ret; - } - - /* package private */ CodecCapabilities( - CodecProfileLevel[] profLevs, int[] colFmts, - boolean encoder, int flags, - Map<String, Object>defaultFormatMap, - Map<String, Object>capabilitiesMap) { - this(profLevs, colFmts, encoder, flags, - new MediaFormat(defaultFormatMap), - new MediaFormat(capabilitiesMap)); + public Range<Integer> getComplexityRange() { + return mComplexityRange; } - private MediaFormat mCapabilitiesInfo; - - /* package private */ CodecCapabilities( - CodecProfileLevel[] profLevs, int[] colFmts, boolean encoder, int flags, - MediaFormat defaultFormat, MediaFormat info) { - final Map<String, Object> map = info.getMap(); - profileLevels = profLevs; - colorFormats = colFmts; - mFlagsVerified = flags; - mDefaultFormat = defaultFormat; - mCapabilitiesInfo = info; - mMime = mDefaultFormat.getString(MediaFormat.KEY_MIME); - - if (mMime.toLowerCase().startsWith("audio/")) { - mAudioCaps = AudioCapabilities.create(info, this); - mAudioCaps.setDefaultFormat(mDefaultFormat); - } else if (mMime.toLowerCase().startsWith("video/")) { - mVideoCaps = VideoCapabilities.create(info, this); - } - if (encoder) { - mEncoderCaps = EncoderCapabilities.create(info, this); - mEncoderCaps.setDefaultFormat(mDefaultFormat); - } + /** Constant quality mode */ + public static final int BITRATE_MODE_CQ = 0; + /** Variable bitrate mode */ + public static final int BITRATE_MODE_VBR = 1; + /** Constant bitrate mode */ + public static final int BITRATE_MODE_CBR = 2; + + private static final Feature[] bitrates = new Feature[] { + new Feature("VBR", BITRATE_MODE_VBR, true), + new Feature("CBR", BITRATE_MODE_CBR, false), + new Feature("CQ", BITRATE_MODE_CQ, false) + }; - for (Feature feat: getValidFeatures()) { - String key = MediaFormat.KEY_FEATURE_ + feat.mName; - Integer yesNo = (Integer)map.get(key); - if (yesNo == null) { - continue; - } else if (yesNo > 0) { - mFlagsRequired |= feat.mValue; - mDefaultFormat.setInteger(key, 1); - } else { - mFlagsSupported |= feat.mValue; - mDefaultFormat.setInteger(key, 1); + private static int parseBitrateMode(String mode) { + for (Feature feat: bitrates) { + if (feat.mName.equalsIgnoreCase(mode)) { + return feat.mValue; } - // TODO restrict features by mFlagsVerified once all codecs reliably verify them } + return 0; } /** - * A class that supports querying the audio capabilities of a codec. + * Query whether a bitrate mode is supported. */ - public static final class AudioCapabilities extends BaseCapabilities { - private static final String TAG = "AudioCapabilities"; - - private int[] mSampleRates; - private Range<Integer>[] mSampleRateRanges; - private int mMaxInputChannelCount; - - private static final int MAX_INPUT_CHANNEL_COUNT = 30; - - /** - * Returns the array of supported sample rates if the codec - * supports only discrete values. Otherwise, it returns - * {@code null}. The array is sorted in ascending order. - */ - public final int[] getSupportedSampleRates() { - return Arrays.copyOf(mSampleRates, mSampleRates.length); + public boolean isBitrateModeSupported(int mode) { + for (Feature feat: bitrates) { + if (mode == feat.mValue) { + return (mBitControl & (1 << mode)) != 0; + } } + return false; + } - /** - * Returns the array of supported sample rate ranges. The - * array is sorted in ascending order, and the ranges are - * distinct. - */ - public final Range<Integer>[] getSupportedSampleRateRanges() { - return Arrays.copyOf(mSampleRateRanges, mSampleRateRanges.length); - } + private Range<Integer> mQualityRange; + private Range<Integer> mComplexityRange; + private CodecCapabilities mParent; - /** - * Returns the maximum number of input channels supported. The codec - * supports any number of channels between 1 and this maximum value. - */ - public final int getMaxInputChannelCount() { - return mMaxInputChannelCount; - } + /* no public constructor */ + private EncoderCapabilities() { } - /* no public constructor */ - private AudioCapabilities() { } + /** @hide */ + public static EncoderCapabilities create( + MediaFormat info, CodecCapabilities parent) { + EncoderCapabilities caps = new EncoderCapabilities(); + caps.init(info, parent); + return caps; + } - /** @hide */ - public static AudioCapabilities create( - MediaFormat info, CodecCapabilities parent) { - AudioCapabilities caps = new AudioCapabilities(); - caps.init(info, parent); - return caps; - } + /** @hide */ + public void init(MediaFormat info, CodecCapabilities parent) { + // no support for complexity or quality yet + mParent = parent; + mComplexityRange = Range.create(0, 0); + mQualityRange = Range.create(0, 0); + mBitControl = (1 << BITRATE_MODE_VBR); + + applyLevelLimits(); + parseFromInfo(info); + } - /** @hide */ - public void init(MediaFormat info, CodecCapabilities parent) { - super.init(info, parent); - initWithPlatformLimits(); - applyLevelLimits(); - parseFromInfo(info); + private void applyLevelLimits() { + String mime = mParent.getMimeType(); + if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { + mComplexityRange = Range.create(0, 8); + mBitControl = (1 << BITRATE_MODE_CQ); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) + || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { + mBitControl = (1 << BITRATE_MODE_CBR); } + } - private void initWithPlatformLimits() { - mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT; - // mBitrateRange = Range.create(1, 320000); - mSampleRateRanges = new Range[] { Range.create(8000, 96000) }; - mSampleRates = null; - } + private int mBitControl; + private Integer mDefaultComplexity; + private Integer mDefaultQuality; + private String mQualityScale; - private boolean supports(Integer sampleRate, Integer inputChannels) { - // channels and sample rates are checked orthogonally - if (inputChannels != null && - (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) { - return false; - } - if (sampleRate != null) { - int ix = Utils.binarySearchDistinctRanges( - mSampleRateRanges, sampleRate); - if (ix < 0) { - return false; - } - } - return true; - } + private void parseFromInfo(MediaFormat info) { + Map<String, Object> map = info.getMap(); - /** - * Query whether the sample rate is supported by the codec. - */ - public final boolean isSampleRateSupported(int sampleRate) { - return supports(sampleRate, null); + if (info.containsKey("complexity-range")) { + mComplexityRange = Utils + .parseIntRange(info.getString("complexity-range"), mComplexityRange); + // TODO should we limit this to level limits? } - - /** modifies rates */ - private void limitSampleRates(int[] rates) { - Arrays.sort(rates); - ArrayList<Range<Integer>> ranges = new ArrayList<Range<Integer>>(); - for (int rate: rates) { - if (supports(rate, null /* channels */)) { - ranges.add(Range.create(rate, rate)); - } - } - mSampleRateRanges = ranges.toArray(new Range[ranges.size()]); - createDiscreteSampleRates(); + if (info.containsKey("quality-range")) { + mQualityRange = Utils + .parseIntRange(info.getString("quality-range"), mQualityRange); } - - private void createDiscreteSampleRates() { - mSampleRates = new int[mSampleRateRanges.length]; - for (int i = 0; i < mSampleRateRanges.length; i++) { - mSampleRates[i] = mSampleRateRanges[i].getLower(); + if (info.containsKey("feature-bitrate-control")) { + for (String mode: info.getString("feature-bitrate-control").split(",")) { + mBitControl |= parseBitrateMode(mode); } } - /** modifies rateRanges */ - private void limitSampleRates(Range<Integer>[] rateRanges) { - sortDistinctRanges(rateRanges); - mSampleRateRanges = intersectSortedDistinctRanges(mSampleRateRanges, rateRanges); + try { + mDefaultComplexity = Integer.parseInt((String)map.get("complexity-default")); + } catch (NumberFormatException e) { } - // check if all values are discrete - for (Range<Integer> range: mSampleRateRanges) { - if (!range.getLower().equals(range.getUpper())) { - mSampleRates = null; - return; - } - } - createDiscreteSampleRates(); - } + try { + mDefaultQuality = Integer.parseInt((String)map.get("quality-default")); + } catch (NumberFormatException e) { } - private void applyLevelLimits() { - int[] sampleRates = null; - Range<Integer> sampleRateRange = null, bitRates = null; - int maxChannels = 0; - String mime = mParent.getMime(); - - if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG)) { - sampleRates = new int[] { - 8000, 11025, 12000, - 16000, 22050, 24000, - 32000, 44100, 48000 }; - bitRates = Range.create(8000, 320000); - maxChannels = 2; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(4750, 12200); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB)) { - sampleRates = new int[] { 16000 }; - bitRates = Range.create(6600, 23850); - maxChannels = 1; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC)) { - sampleRates = new int[] { - 7350, 8000, - 11025, 12000, 16000, - 22050, 24000, 32000, - 44100, 48000, 64000, - 88200, 96000 }; - bitRates = Range.create(8000, 510000); - maxChannels = 48; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) { - bitRates = Range.create(32000, 500000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) { - bitRates = Range.create(6000, 510000); - sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000 }; - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW)) { - sampleRateRange = Range.create(1, 96000); - bitRates = Range.create(1, 10000000); - maxChannels = 8; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC)) { - sampleRateRange = Range.create(1, 655350); - // lossless codec, so bitrate is ignored - maxChannels = 255; - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) - || mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(64000, 64000); - // platform allows multiple channels for this format - } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM)) { - sampleRates = new int[] { 8000 }; - bitRates = Range.create(13000, 13000); - maxChannels = 1; - } else { - Log.w(TAG, "Unsupported mime " + mime); - mParent.mError |= ERROR_UNSUPPORTED; - } + mQualityScale = (String)map.get("quality-scale"); + } - // restrict ranges - if (sampleRates != null) { - limitSampleRates(sampleRates); - } else if (sampleRateRange != null) { - limitSampleRates(new Range[] { sampleRateRange }); + private boolean supports( + Integer complexity, Integer quality, Integer profile) { + boolean ok = true; + if (ok && complexity != null) { + ok = mComplexityRange.contains(complexity); + } + if (ok && quality != null) { + ok = mQualityRange.contains(quality); + } + if (ok && profile != null) { + for (CodecProfileLevel pl: mParent.profileLevels) { + if (pl.profile == profile) { + profile = null; + break; + } } - applyLimits(maxChannels, bitRates); + ok = profile == null; } + return ok; + } - private void applyLimits(int maxInputChannels, Range<Integer> bitRates) { - mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount) - .clamp(maxInputChannels); - if (bitRates != null) { - mBitrateRange = mBitrateRange.intersect(bitRates); + /** @hide */ + public void setDefaultFormat(MediaFormat format) { + // don't list trivial quality/complexity as default for now + if (!mQualityRange.getUpper().equals(mQualityRange.getLower()) + && mDefaultQuality != null) { + format.setInteger(MediaFormat.KEY_QUALITY, mDefaultQuality); + } + if (!mComplexityRange.getUpper().equals(mComplexityRange.getLower()) + && mDefaultComplexity != null) { + format.setInteger(MediaFormat.KEY_COMPLEXITY, mDefaultComplexity); + } + // bitrates are listed in order of preference + for (Feature feat: bitrates) { + if ((mBitControl & (1 << feat.mValue)) != 0) { + format.setInteger(MediaFormat.KEY_BITRATE_MODE, feat.mValue); + break; } } + } - private void parseFromInfo(MediaFormat info) { - int maxInputChannels = MAX_INPUT_CHANNEL_COUNT; - Range<Integer> bitRates = POSITIVE_INTEGERS; + /** @hide */ + public boolean supportsFormat(MediaFormat format) { + final Map<String, Object> map = format.getMap(); + final String mime = mParent.getMimeType(); - if (info.containsKey("sample-rate-ranges")) { - String[] rateStrings = info.getString("sample-rate-ranges").split(","); - Range<Integer>[] rateRanges = new Range[rateStrings.length]; - for (int i = 0; i < rateStrings.length; i++) { - rateRanges[i] = Utils.parseIntRange(rateStrings[i], null); - } - limitSampleRates(rateRanges); - } - if (info.containsKey("max-channel-count")) { - maxInputChannels = Utils.parseIntSafely( - info.getString("max-channel-count"), maxInputChannels); - } - if (info.containsKey("bitrate-range")) { - bitRates = bitRates.intersect( - Utils.parseIntRange(info.getString("bitrate"), bitRates)); - } - applyLimits(maxInputChannels, bitRates); + Integer mode = (Integer)map.get(MediaFormat.KEY_BITRATE_MODE); + if (mode != null && !isBitrateModeSupported(mode)) { + return false; } - /** @hide */ - public void setDefaultFormat(MediaFormat format) { - // report settings that have only a single choice - if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) { - format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower()); - } - if (mMaxInputChannelCount == 1) { - // mono-only format - format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); - } - if (mSampleRates != null && mSampleRates.length == 1) { - format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRates[0]); + Integer complexity = (Integer)map.get(MediaFormat.KEY_COMPLEXITY); + if (MediaFormat.MIMETYPE_AUDIO_FLAC.equalsIgnoreCase(mime)) { + Integer flacComplexity = + (Integer)map.get(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL); + if (complexity == null) { + complexity = flacComplexity; + } else if (flacComplexity != null && complexity != flacComplexity) { + throw new IllegalArgumentException( + "conflicting values for complexity and " + + "flac-compression-level"); } } - /** @hide */ - public boolean supportsFormat(MediaFormat format) { - Map<String, Object> map = format.getMap(); - Integer sampleRate = (Integer)map.get(MediaFormat.KEY_SAMPLE_RATE); - Integer channels = (Integer)map.get(MediaFormat.KEY_CHANNEL_COUNT); - if (!supports(sampleRate, channels)) { - return false; + // other audio parameters + Integer profile = (Integer)map.get(MediaFormat.KEY_PROFILE); + if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mime)) { + Integer aacProfile = (Integer)map.get(MediaFormat.KEY_AAC_PROFILE); + if (profile == null) { + profile = aacProfile; + } else if (aacProfile != null && aacProfile != profile) { + throw new IllegalArgumentException( + "conflicting values for profile and aac-profile"); } - - // nothing to do for: - // KEY_CHANNEL_MASK: codecs don't get this - // KEY_IS_ADTS: required feature for all AAC decoders - return true; } - }; - - AudioCapabilities mAudioCaps; - private boolean isAudio() { - return mAudioCaps != null; - } - /** - * Returns the audio capabilities or {@code null} if this is not an audio codec. - */ - public final AudioCapabilities getAudioCapabilities() { - return mAudioCaps; - } + Integer quality = (Integer)map.get(MediaFormat.KEY_QUALITY); - /** @hide */ - public CodecCapabilities dup() { - return new CodecCapabilities( - // clone writable arrays - Arrays.copyOf(profileLevels, profileLevels.length), - Arrays.copyOf(colorFormats, colorFormats.length), - isEncoder(), - mFlagsVerified, - mDefaultFormat, - mCapabilitiesInfo); + return supports(complexity, quality, profile); } }; diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java index d74f22d..5084c5c 100644 --- a/media/java/android/media/MediaCodecList.java +++ b/media/java/android/media/MediaCodecList.java @@ -36,6 +36,8 @@ final public class MediaCodecList { /** * Count the number of available (regular) codecs. * + * @deprecated Use {@link #getCodecInfos} instead. + * * @see #REGULAR_CODECS */ public static final int getCodecCount() { @@ -49,6 +51,8 @@ final public class MediaCodecList { * Return the {@link MediaCodecInfo} object for the codec at * the given {@code index} in the regular list. * + * @deprecated Use {@link #getCodecInfos} instead. + * * @see #REGULAR_CODECS */ public static final MediaCodecInfo getCodecInfoAt(int index) { @@ -116,13 +120,22 @@ final public class MediaCodecList { /** * Use in {@link #MediaCodecList} to enumerate only codecs that are suitable - * for normal playback and recording. + * for regular (buffer-to-buffer) decoding or encoding. + * + * <em>NOTE:</em> These are the codecs that are returned prior to API 21, + * using the now deprecated static methods. */ public static final int REGULAR_CODECS = 0; /** * Use in {@link #MediaCodecList} to enumerate all codecs, even ones that are - * not suitable for normal playback or recording. + * not suitable for regular (buffer-to-buffer) decoding or encoding. These + * include codecs, for example, that only work with special input or output + * surfaces, such as secure-only or tunneled-only codecs. + * + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + * @see MediaCodecInfo.CodecCapabilities#FEATURE_SecurePlayback + * @see MediaCodecInfo.CodecCapabilities#FEATURE_TunneledPlayback */ public static final int ALL_CODECS = 1; diff --git a/media/java/android/media/MediaDescription.aidl b/media/java/android/media/MediaDescription.aidl new file mode 100644 index 0000000..6f934f7 --- /dev/null +++ b/media/java/android/media/MediaDescription.aidl @@ -0,0 +1,18 @@ +/* Copyright 2014, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.media; + +parcelable MediaDescription; diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java new file mode 100644 index 0000000..4399c0d --- /dev/null +++ b/media/java/android/media/MediaDescription.java @@ -0,0 +1,276 @@ +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Size; + +/** + * A simple set of metadata for a media item suitable for display. This can be + * created using the Builder or retrieved from existing metadata using + * {@link MediaMetadata#getDescription()}. + */ +public class MediaDescription implements Parcelable { + /** + * A unique persistent id for the content or null. + */ + private final String mMediaId; + /** + * A primary title suitable for display or null. + */ + private final CharSequence mTitle; + /** + * A subtitle suitable for display or null. + */ + private final CharSequence mSubtitle; + /** + * A description suitable for display or null. + */ + private final CharSequence mDescription; + /** + * A bitmap icon suitable for display or null. + */ + private final Bitmap mIcon; + /** + * A Uri for an icon suitable for display or null. + */ + private final Uri mIconUri; + /** + * Extras for opaque use by apps/system. + */ + private final Bundle mExtras; + + private MediaDescription(String mediaId, CharSequence title, CharSequence subtitle, + CharSequence description, Bitmap icon, Uri iconUri, Bundle extras) { + mMediaId = mediaId; + mTitle = title; + mSubtitle = subtitle; + mDescription = description; + mIcon = icon; + mIconUri = iconUri; + mExtras = extras; + } + + private MediaDescription(Parcel in) { + mMediaId = in.readString(); + mTitle = in.readCharSequence(); + mSubtitle = in.readCharSequence(); + mDescription = in.readCharSequence(); + mIcon = in.readParcelable(null); + mIconUri = in.readParcelable(null); + mExtras = in.readBundle(); + } + + /** + * Returns the media id or null. See + * {@link MediaMetadata#METADATA_KEY_MEDIA_ID}. + */ + public @Nullable String getMediaId() { + return mMediaId; + } + + /** + * Returns a title suitable for display or null. + * + * @return A title or null. + */ + public @Nullable CharSequence getTitle() { + return mTitle; + } + + /** + * Returns a subtitle suitable for display or null. + * + * @return A subtitle or null. + */ + public @Nullable CharSequence getSubtitle() { + return mSubtitle; + } + + /** + * Returns a description suitable for display or null. + * + * @return A description or null. + */ + public @Nullable CharSequence getDescription() { + return mDescription; + } + + /** + * Returns a bitmap icon suitable for display or null. + * + * @return An icon or null. + */ + public @Nullable Bitmap getIconBitmap() { + return mIcon; + } + + /** + * Returns a Uri for an icon suitable for display or null. + * + * @return An icon uri or null. + */ + public @Nullable Uri getIconUri() { + return mIconUri; + } + + /** + * Returns any extras that were added to the description. + * + * @return A bundle of extras or null. + */ + public @Nullable Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mMediaId); + dest.writeCharSequence(mTitle); + dest.writeCharSequence(mSubtitle); + dest.writeCharSequence(mDescription); + dest.writeParcelable(mIcon, flags); + dest.writeParcelable(mIconUri, flags); + dest.writeBundle(mExtras); + } + + @Override + public String toString() { + return mTitle + ", " + mSubtitle + ", " + mDescription; + } + + public static final Parcelable.Creator<MediaDescription> CREATOR = + new Parcelable.Creator<MediaDescription>() { + @Override + public MediaDescription createFromParcel(Parcel in) { + return new MediaDescription(in); + } + + @Override + public MediaDescription[] newArray(int size) { + return new MediaDescription[size]; + } + }; + + /** + * Builder for {@link MediaDescription} objects. + */ + public static class Builder { + private String mMediaId; + private CharSequence mTitle; + private CharSequence mSubtitle; + private CharSequence mDescription; + private Bitmap mIcon; + private Uri mIconUri; + private Bundle mExtras; + + /** + * Creates an initially empty builder. + */ + public Builder() { + } + + /** + * Sets the media id. + * + * @param mediaId The unique id for the item or null. + * @return this + */ + public Builder setMediaId(@Nullable String mediaId) { + mMediaId = mediaId; + return this; + } + + /** + * Sets the title. + * + * @param title A title suitable for display to the user or null. + * @return this + */ + public Builder setTitle(@Nullable CharSequence title) { + mTitle = title; + return this; + } + + /** + * Sets the subtitle. + * + * @param subtitle A subtitle suitable for display to the user or null. + * @return this + */ + public Builder setSubtitle(@Nullable CharSequence subtitle) { + mSubtitle = subtitle; + return this; + } + + /** + * Sets the description. + * + * @param description A description suitable for display to the user or + * null. + * @return this + */ + public Builder setDescription(@Nullable CharSequence description) { + mDescription = description; + return this; + } + + /** + * Sets the icon. + * + * @param icon A {@link Bitmap} icon suitable for display to the user or + * null. + * @return this + */ + public Builder setIconBitmap(@Nullable Bitmap icon) { + mIcon = icon; + return this; + } + + /** + * Sets the icon uri. + * + * @param iconUri A {@link Uri} for an icon suitable for display to the + * user or null. + * @return this + */ + public Builder setIconUri(@Nullable Uri iconUri) { + mIconUri = iconUri; + return this; + } + + /** + * Sets a bundle of extras. + * + * @param extras The extras to include with this description or null. + * @return this + */ + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + public MediaDescription build() { + return new MediaDescription(mMediaId, mTitle, mSubtitle, mDescription, mIcon, mIconUri, + mExtras); + } + } +} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index fd79495..2036533 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -120,19 +120,6 @@ public final class MediaFormat { private Map<String, Object> mMap; /** - * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities} - * feature name describing a required or optional feature for a codec capabilities - * query. - * The associated value is an integer, where non-0 value means the feature is - * requested to be present, while 0 value means the feature is requested to be not - * present. - * @see MediaCodecList#findDecoderForFormat - * @see MediaCodecList#findEncoderForFormat - * @see MediaCodecInfo.CodecCapabilities#isFormatSupported - */ - public static final String KEY_FEATURE_ = "feature-"; - - /** * A key describing the mime type of the MediaFormat. * The associated value is a string. */ @@ -422,6 +409,8 @@ public final class MediaFormat { * codec specific, but lower values generally result in more efficient * (smaller-sized) encoding. * + * @hide + * * @see MediaCodecInfo.CodecCapabilities.EncoderCapabilities#getQualityRange */ public static final String KEY_QUALITY = "quality"; @@ -510,6 +499,21 @@ public final class MediaFormat { } /** + * A key prefix used together with a {@link MediaCodecInfo.CodecCapabilities} + * feature name describing a required or optional feature for a codec capabilities + * query. + * The associated value is an integer, where non-0 value means the feature is + * requested to be present, while 0 value means the feature is requested to be not + * present. + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + * + * @hide + */ + public static final String KEY_FEATURE_ = "feature-"; + + /** * Returns the value of an integer key. */ public final int getInteger(String name) { @@ -559,6 +563,23 @@ public final class MediaFormat { } /** + * Returns whether a feature is to be enabled ({@code true}) or disabled + * ({@code false}). + * + * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature. + * + * @throws IllegalArgumentException if the feature was neither set to be enabled + * nor to be disabled. + */ + public boolean getFeatureEnabled(String feature) { + Integer enabled = (Integer)mMap.get(KEY_FEATURE_ + feature); + if (enabled == null) { + throw new IllegalArgumentException("feature is not specified"); + } + return enabled != 0; + } + + /** * Sets the value of an integer key. */ public final void setInteger(String name, int value) { @@ -594,6 +615,23 @@ public final class MediaFormat { } /** + * Sets whether a feature is to be enabled ({@code true}) or disabled + * ({@code false}). + * + * If {@code enabled} is {@code true}, the feature is requested to be present. + * Otherwise, the feature is requested to be not present. + * + * @param feature the name of a {@link MediaCodecInfo.CodecCapabilities} feature. + * + * @see MediaCodecList#findDecoderForFormat + * @see MediaCodecList#findEncoderForFormat + * @see MediaCodecInfo.CodecCapabilities#isFormatSupported + */ + public void setFeatureEnabled(String feature, boolean enabled) { + setInteger(KEY_FEATURE_ + feature, enabled ? 1 : 0); + } + + /** * Creates a minimal audio format. * @param mime The mime type of the content. * @param sampleRate The sampling rate of the content. diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index 74f7a96..b4e6033 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -17,15 +17,22 @@ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentProviderClient; +import android.content.ContentResolver; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.text.format.Time; import android.util.ArrayMap; import android.util.Log; +import android.util.Size; import android.util.SparseArray; import java.util.Set; @@ -119,7 +126,9 @@ public final class MediaMetadata implements Parcelable { public static final String METADATA_KEY_ART = "android.media.metadata.ART"; /** - * The artwork for the media as a Uri. + * The artwork for the media as a Uri formatted String. The artwork can be + * loaded using a combination of {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI"; @@ -130,7 +139,10 @@ public final class MediaMetadata implements Parcelable { public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART"; /** - * The artwork for the album of the media's original source as a Uri. + * The artwork for the album of the media's original source as a Uri + * formatted String. The artwork can be loaded using a combination of + * {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI"; @@ -181,13 +193,26 @@ public final class MediaMetadata implements Parcelable { = "android.media.metadata.DISPLAY_ICON"; /** - * An icon or thumbnail that is suitable for display to the user. When - * displaying more information for media described by this metadata the - * display description should be preferred to other fields when present. + * A Uri formatted String for an icon or thumbnail that is suitable for + * display to the user. When displaying more information for media described + * by this metadata the display description should be preferred to other + * fields when present. The icon can be loaded using a combination of + * {@link ContentResolver#openInputStream} and + * {@link BitmapFactory#decodeStream}. */ public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI"; + /** + * A String key for identifying the content. This value is specific to the + * service providing the content. If used, this should be a persistent + * unique key for the underlying content. It may be used with + * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} + * to initiate playback when provided by a {@link MediaBrowser} connected to + * the same app. + */ + public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID"; + private static final String[] PREFERRED_DESCRIPTION_ORDER = { METADATA_KEY_TITLE, METADATA_KEY_ARTIST, @@ -277,7 +302,7 @@ public final class MediaMetadata implements Parcelable { } private final Bundle mBundle; - private Description mDescription; + private MediaDescription mDescription; private MediaMetadata(Bundle bundle) { mBundle = new Bundle(bundle); @@ -406,11 +431,13 @@ public final class MediaMetadata implements Parcelable { * * @return A simple description of this metadata. */ - public @NonNull Description getDescription() { + public @NonNull MediaDescription getDescription() { if (mDescription != null) { return mDescription; } + String mediaId = getString(METADATA_KEY_MEDIA_ID); + CharSequence[] text = new CharSequence[3]; Bitmap icon = null; Uri iconUri = null; @@ -454,7 +481,15 @@ public final class MediaMetadata implements Parcelable { } } - mDescription = new Description(text[0], text[1], text[2], icon, iconUri); + MediaDescription.Builder bob = new MediaDescription.Builder(); + bob.setMediaId(mediaId); + bob.setTitle(text[0]); + bob.setSubtitle(text[1]); + bob.setDescription(text[2]); + bob.setIconBitmap(icon); + bob.setIconUri(iconUri); + mDescription = bob.build(); + return mDescription; } @@ -668,90 +703,4 @@ public final class MediaMetadata implements Parcelable { return new MediaMetadata(mBundle); } } - - /** - * A simple form of the metadata that can be used for display. - */ - public final class Description { - /** - * A primary title suitable for display or null. - */ - private final CharSequence mTitle; - /** - * A subtitle suitable for display or null. - */ - private final CharSequence mSubtitle; - /** - * A description suitable for display or null. - */ - private final CharSequence mDescription; - /** - * A bitmap icon suitable for display or null. - */ - private final Bitmap mIcon; - /** - * A Uri for an icon suitable for display or null. - */ - private final Uri mIconUri; - - /** - * Returns the best available title or null. - * - * @return A title or null. - */ - public @Nullable CharSequence getTitle() { - return mTitle; - } - - /** - * Returns the best available subtitle or null. - * - * @return A subtitle or null. - */ - public @Nullable CharSequence getSubtitle() { - return mSubtitle; - } - - /** - * Returns the best available description or null. - * - * @return A description or null. - */ - public @Nullable CharSequence getDescription() { - return mDescription; - } - - /** - * Returns the best available icon or null. - * - * @return An icon or null. - */ - public @Nullable Bitmap getIcon() { - return mIcon; - } - - /** - * Returns the best available icon Uri or null. - * - * @return An icon uri or null. - */ - public @Nullable Uri getIconUri() { - return mIconUri; - } - - private Description(CharSequence title, CharSequence subtitle, CharSequence description, - Bitmap icon, Uri iconUri) { - mTitle = title; - mSubtitle = subtitle; - mDescription = description; - mIcon = icon; - mIconUri = iconUri; - } - - @Override - public String toString() { - return mTitle + ", " + mSubtitle + ", " + mDescription; - } - } - } diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index a221104..7d075ba 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -115,6 +115,15 @@ public class Ringtone { } /** + * Returns the {@link AudioAttributes} used by this object. + * @return the {@link AudioAttributes} that were set with + * {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set. + */ + public AudioAttributes getAudioAttributes() { + return mAudioAttributes; + } + + /** * Returns a human-presentable title for ringtone. Looks in media * content provider. If not in either, uses the filename * diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 1c6d81f..debaf45 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -16,6 +16,7 @@ package android.media.browse; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -23,26 +24,27 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ParceledListSlice; -import android.content.res.Configuration; -import android.graphics.Bitmap; +import android.media.MediaDescription; import android.media.session.MediaSession; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; +import android.service.media.MediaBrowserService; +import android.service.media.IMediaBrowserService; +import android.service.media.IMediaBrowserServiceCallbacks; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Objects; -import java.util.Set; /** * Browses media content offered by a link MediaBrowserService. @@ -67,8 +69,6 @@ public final class MediaBrowser { private final Handler mHandler = new Handler(); private final ArrayMap<Uri,Subscription> mSubscriptions = new ArrayMap<Uri, MediaBrowser.Subscription>(); - private final SparseArray<IconRequest> mIconRequests = - new SparseArray<IconRequest>(); private int mState = CONNECT_STATE_DISCONNECTED; private MediaServiceConnection mServiceConnection; @@ -77,7 +77,6 @@ public final class MediaBrowser { private Uri mRootUri; private MediaSession.Token mMediaSessionToken; private Bundle mExtras; - private int mNextSeq; /** * Creates a media browser for the specified media browse service. @@ -363,49 +362,6 @@ public final class MediaBrowser { } /** - * Loads the icon of a media item. - * - * @param uri The uri of the Icon. - * @param width The preferred width of the icon in dp. - * @param height The preferred width of the icon in dp. - * @param callback The callback to receive the icon. - */ - public void loadIcon(final @NonNull Uri uri, final int width, final int height, - final @NonNull IconCallback callback) { - if (uri == null) { - throw new IllegalArgumentException("Icon uri cannot be null"); - } - if (callback == null) { - throw new IllegalArgumentException("Icon callback cannot be null"); - } - mHandler.post(new Runnable() { - @Override - public void run() { - for (int i = 0; i < mIconRequests.size(); i++) { - IconRequest existingRequest = mIconRequests.valueAt(i); - if (existingRequest.isSameRequest(uri, width, height)) { - existingRequest.addCallback(callback); - return; - } - } - final int seq = mNextSeq++; - IconRequest request = new IconRequest(seq, uri, width, height); - request.addCallback(callback); - mIconRequests.put(seq, request); - if (mState == CONNECT_STATE_CONNECTED) { - try { - mServiceBinder.loadIcon(seq, uri, width, height, mServiceCallbacks); - } catch (RemoteException e) { - // Process is crashing. We will disconnect, and upon reconnect we will - // automatically reload the icons. So nothing to do here. - Log.d(TAG, "loadIcon failed with RemoteException uri=" + uri); - } - } - } - }); - } - - /** * For debugging. */ private static String getStateLabel(int state) { @@ -461,18 +417,6 @@ public final class MediaBrowser { Log.d(TAG, "addSubscription failed with RemoteException parentUri=" + uri); } } - - for (int i = 0; i < mIconRequests.size(); i++) { - IconRequest request = mIconRequests.valueAt(i); - try { - mServiceBinder.loadIcon(request.mSeq, request.mUri, - request.mWidth, request.mHeight, mServiceCallbacks); - } catch (RemoteException e) { - // Process is crashing. We will disconnect, and upon reconnect we will - // automatically reload. So nothing to do here. - Log.d(TAG, "loadIcon failed with RemoteException request=" + request); - } - } } }); } @@ -515,7 +459,7 @@ public final class MediaBrowser { return; } - List<MediaBrowserItem> data = list.getList(); + List<MediaItem> data = list.getList(); if (DBG) { Log.d(TAG, "onLoadChildren for " + mServiceComponent + " uri=" + uri); } @@ -539,32 +483,6 @@ public final class MediaBrowser { }); } - private final void onLoadIcon(final IMediaBrowserServiceCallbacks callback, - final int seqNum, final Bitmap bitmap) { - mHandler.post(new Runnable() { - @Override - public void run() { - // Check that there hasn't been a disconnect or a different - // ServiceConnection. - if (!isCurrent(callback, "onLoadIcon")) { - return; - } - - IconRequest request = mIconRequests.get(seqNum); - if (request == null) { - Log.d(TAG, "onLoadIcon called for seqNum=" + seqNum + " request=" - + request + " but the request is not registered"); - return; - } - mIconRequests.delete(seqNum); - for (IconCallback IconCallback : request.getCallbacks()) { - IconCallback.onIconLoaded(request.mUri, bitmap); - } - } - }); - } - - /** * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not. */ @@ -600,130 +518,169 @@ public final class MediaBrowser { Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken); } + public static class MediaItem implements Parcelable { + private final int mFlags; + private final MediaDescription mDescription; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) + public @interface Flags { } - /** - * Callbacks for connection related events. - */ - public static class ConnectionCallback { /** - * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. + * Flag: Indicates that the item has children of its own. */ - public void onConnected() { + public static final int FLAG_BROWSABLE = 1 << 0; + + /** + * Flag: Indicates that the item is playable. + * <p> + * The Uri of this item may be passed to link android.media.session.MediaController#play(Uri) + * to start playing it. + * </p> + */ + public static final int FLAG_PLAYABLE = 1 << 1; + + /** + * Create a new MediaItem for use in browsing media. + * + * @param flags The flags for this item. + * @param description The description of the media, which must include a + * media id. + */ + public MediaItem(@Flags int flags, @NonNull MediaDescription description) { + if (description == null) { + throw new IllegalArgumentException("description cannot be null"); + } + if (TextUtils.isEmpty(description.getMediaId())) { + throw new IllegalArgumentException("description must have a non-empty media id"); + } + mFlags = flags; + mDescription = description; } /** - * Invoked when the client is disconnected from the media browser. + * Private constructor. */ - public void onConnectionSuspended() { + private MediaItem(Parcel in) { + mFlags = in.readInt(); + mDescription = MediaDescription.CREATOR.createFromParcel(in); } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mFlags); + mDescription.writeToParcel(out, flags); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("MediaItem{"); + sb.append("mFlags=").append(mFlags); + sb.append(", mDescription=").append(mDescription); + sb.append('}'); + return sb.toString(); + } + + public static final Parcelable.Creator<MediaItem> CREATOR = + new Parcelable.Creator<MediaItem>() { + @Override + public MediaItem createFromParcel(Parcel in) { + return new MediaItem(in); + } + + @Override + public MediaItem[] newArray(int size) { + return new MediaItem[size]; + } + }; + /** - * Invoked when the connection to the media browser failed. + * Gets the flags of the item. */ - public void onConnectionFailed() { + public @Flags int getFlags() { + return mFlags; } - } - /** - * Callbacks for subscription related events. - */ - public static abstract class SubscriptionCallback { /** - * Called when the list of children is loaded or updated. + * Returns whether this item is browsable. + * @see #FLAG_BROWSABLE */ - public void onChildrenLoaded(@NonNull Uri parentUri, - @NonNull List<MediaBrowserItem> children) { + public boolean isBrowsable() { + return (mFlags & FLAG_BROWSABLE) != 0; } /** - * Called when the Uri doesn't exist or other errors in subscribing. - * <p> - * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} - * called, because some errors may heal themselves. - * </p> + * Returns whether this item is playable. + * @see #FLAG_PLAYABLE */ - public void onError(@NonNull Uri uri) { + public boolean isPlayable() { + return (mFlags & FLAG_PLAYABLE) != 0; } - } - /** - * Callbacks for icon loading. - */ - public static abstract class IconCallback { /** - * Called when the icon is loaded. + * Returns the description of the media. */ - public void onIconLoaded(@NonNull Uri uri, @NonNull Bitmap bitmap) { + public @NonNull MediaDescription getDescription() { + return mDescription; } /** - * Called when the Uri doesn’t exist or the bitmap cannot be loaded. + * Returns the media id for this item. */ - public void onError(@NonNull Uri uri) { + public @NonNull String getMediaId() { + return mDescription.getMediaId(); } } - private static class IconRequest { - final int mSeq; - final Uri mUri; - final int mWidth; - final int mHeight; - final List<IconCallback> mCallbacks; + /** + * Callbacks for connection related events. + */ + public static class ConnectionCallback { /** - * Constructs an icon request. - * @param seq The unique sequence number assigned to the request by the media browser. - * @param uri The Uri for the icon. - * @param width The width for the icon. - * @param height The height for the icon. + * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed. */ - IconRequest(int seq, @NonNull Uri uri, int width, int height) { - if (uri == null) { - throw new IllegalArgumentException("Icon uri cannot be null"); - } - this.mSeq = seq; - this.mUri = uri; - this.mWidth = width; - this.mHeight = height; - mCallbacks = new ArrayList<IconCallback>(); + public void onConnected() { } /** - * Adds a callback to the icon request. - * If the callback already exists, it will not be added again. + * Invoked when the client is disconnected from the media browser. */ - public void addCallback(@NonNull IconCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null in IconRequest"); - } - if (!mCallbacks.contains(callback)) { - mCallbacks.add(callback); - } + public void onConnectionSuspended() { } /** - * Checks if the icon request has the same uri, width, and height as the given values. + * Invoked when the connection to the media browser failed. */ - public boolean isSameRequest(@Nullable Uri uri, int width, int height) { - return Objects.equals(mUri, uri) && mWidth == width && mHeight == height; + public void onConnectionFailed() { } + } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("IconRequest{"); - sb.append("uri=").append(mUri); - sb.append(", width=").append(mWidth); - sb.append(", height=").append(mHeight); - sb.append(", seq=").append(mSeq); - sb.append('}'); - return sb.toString(); + /** + * Callbacks for subscription related events. + */ + public static abstract class SubscriptionCallback { + /** + * Called when the list of children is loaded or updated. + */ + public void onChildrenLoaded(@NonNull Uri parentUri, + @NonNull List<MediaItem> children) { } /** - * Gets an unmodifiable view of the list of callbacks associated with the request. + * Called when the Uri doesn't exist or other errors in subscribing. + * <p> + * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe} + * called, because some errors may heal themselves. + * </p> */ - public List<IconCallback> getCallbacks() { - return Collections.unmodifiableList(mCallbacks); + public void onError(@NonNull Uri uri) { } } @@ -809,7 +766,7 @@ public final class MediaBrowser { } return true; } - }; + } /** * Callbacks from the service. @@ -852,14 +809,6 @@ public final class MediaBrowser { mediaBrowser.onLoadChildren(this, uri, list); } } - - @Override - public void onLoadIcon(final int seqNum, final Bitmap bitmap) { - MediaBrowser mediaBrowser = mMediaBrowser.get(); - if (mediaBrowser != null) { - mediaBrowser.onLoadIcon(this, seqNum, bitmap); - } - } } private static class Subscription { diff --git a/media/java/android/media/browse/MediaBrowserItem.java b/media/java/android/media/browse/MediaBrowserItem.java deleted file mode 100644 index 47ec46b..0000000 --- a/media/java/android/media/browse/MediaBrowserItem.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media.browse; - -import android.annotation.DrawableRes; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.net.Uri; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Describes a media item in the list of items offered by a {@link MediaBrowserService}. - */ -public final class MediaBrowserItem implements Parcelable { - private final Uri mUri; - private final int mFlags; - private final CharSequence mTitle; - private final CharSequence mSummary; - private final Uri mIconUri; - private final int mIconResourceId; - private final Bundle mExtras; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) - public @interface Flags { } - - /** - * Flag: Indicates that the item has children of its own. - */ - public static final int FLAG_BROWSABLE = 1 << 0; - - /** - * Flag: Indicates that the item is playable. - * <p> - * The Uri of this item may be passed to link android.media.session.MediaController#play(Uri) - * to start playing it. - * </p> - */ - public static final int FLAG_PLAYABLE = 1 << 1; - - /** - * Initialize a MediaBrowserItem object. - */ - private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title, - CharSequence summary, @Nullable Uri iconUri, int iconResourceId, Bundle extras) { - if (uri == null) { - throw new IllegalArgumentException("uri can not be null"); - } - if (title == null) { - throw new IllegalArgumentException("title can not be null"); - } - mUri = uri; - mFlags = flags; - mTitle = title; - mSummary = summary; - mIconUri = iconUri; - mIconResourceId = iconResourceId; - mExtras = extras; - } - - /** - * Private constructor. - */ - private MediaBrowserItem(Parcel in) { - mUri = Uri.CREATOR.createFromParcel(in); - mFlags = in.readInt(); - mTitle = in.readCharSequence(); - if (in.readInt() != 0) { - mSummary = in.readCharSequence(); - } else { - mSummary = null; - } - if (in.readInt() != 0) { - mIconUri = Uri.CREATOR.createFromParcel(in); - } else { - mIconUri = null; - } - mIconResourceId = in.readInt(); - if (in.readInt() != 0) { - mExtras = Bundle.CREATOR.createFromParcel(in); - } else { - mExtras = null; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - mUri.writeToParcel(out, flags); - out.writeInt(mFlags); - out.writeCharSequence(mTitle); - if (mSummary != null) { - out.writeInt(1); - out.writeCharSequence(mSummary); - } else { - out.writeInt(0); - } - if (mIconUri != null) { - out.writeInt(1); - mIconUri.writeToParcel(out, flags); - } else { - out.writeInt(0); - } - out.writeInt(mIconResourceId); - if (mExtras != null) { - out.writeInt(1); - mExtras.writeToParcel(out, flags); - } else { - out.writeInt(0); - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("MediaBrowserItem{"); - sb.append("mUri=").append(mUri); - sb.append(", mFlags=").append(mFlags); - sb.append(", mTitle=").append(mTitle); - sb.append(", mSummary=").append(mSummary); - sb.append(", mIconUri=").append(mIconUri); - sb.append(", mIconResourceId=").append(mIconResourceId); - sb.append('}'); - return sb.toString(); - } - - public static final Parcelable.Creator<MediaBrowserItem> CREATOR = - new Parcelable.Creator<MediaBrowserItem>() { - @Override - public MediaBrowserItem createFromParcel(Parcel in) { - return new MediaBrowserItem(in); - } - - @Override - public MediaBrowserItem[] newArray(int size) { - return new MediaBrowserItem[size]; - } - }; - - /** - * Gets the Uri of the item. - */ - public @NonNull Uri getUri() { - return mUri; - } - - /** - * Gets the flags of the item. - */ - public @Flags int getFlags() { - return mFlags; - } - - /** - * Returns whether this item is browsable. - * @see #FLAG_BROWSABLE - */ - public boolean isBrowsable() { - return (mFlags & FLAG_BROWSABLE) != 0; - } - - /** - * Returns whether this item is playable. - * @see #FLAG_PLAYABLE - */ - public boolean isPlayable() { - return (mFlags & FLAG_PLAYABLE) != 0; - } - - /** - * Gets the title of the item. - * @more - * The title will be shown as the first line of text when - * describing each item to the user. - */ - public @NonNull CharSequence getTitle() { - return mTitle; - } - - /** - * Gets summary of the item, or null if none. - * @more - * The summary will be shown as the second line of text when - * describing each item to the user. - */ - public @Nullable CharSequence getSummary() { - return mSummary; - } - - /** - * Gets the Uri of the icon. - */ - public @Nullable Uri getIconUri() { - return mIconUri; - } - - /** - * Gets the resource id of the icon. - */ - public @DrawableRes int getIconResourceId() { - return mIconResourceId; - } - - /** - * Gets additional service-specified extras about the - * item or its content, or null if none. - */ - public @Nullable Bundle getExtras() { - return mExtras; - } - - /** - * Builder for {@link MediaBrowserItem} objects. - */ - public static final class Builder { - private final Uri mUri; - private final int mFlags; - private final CharSequence mTitle; - private CharSequence mSummary; - private Uri mIconUri; - private int mIconResourceId; - private Bundle mExtras; - - /** - * Creates an item builder. - */ - public Builder(@NonNull Uri uri, @Flags int flags, @NonNull CharSequence title) { - if (uri == null) { - throw new IllegalArgumentException("uri can not be null"); - } - if (title == null) { - throw new IllegalArgumentException("title can not be null"); - } - mUri = uri; - mFlags = flags; - mTitle = title; - } - - /** - * Sets summary of the item, or null if none. - */ - public @NonNull Builder setSummary(@Nullable CharSequence summary) { - mSummary = summary; - return this; - } - - /** - * Sets the uri of the icon. - * <p> - * Either {@link #setIconUri(Uri)} or {@link #setIconResourceId(int)} should be called. - * If both are specified, the resource id will be used to load the icon. - * </p> - */ - public @NonNull Builder setIconUri(@Nullable Uri iconUri) { - mIconUri = iconUri; - return this; - } - - /** - * Sets the resource id of the icon. - * <p> - * Either {@link #setIconUri(Uri)} or {@link #setIconResourceId(int)} should be specified. - * If both are specified, the resource id will be used to load the icon. - * </p> - */ - public @NonNull Builder setIconResourceId(@DrawableRes int ResourceId) { - mIconResourceId = ResourceId; - return this; - } - - /** - * Sets additional service-specified extras about the - * item or its content. - */ - public @NonNull Builder setExtras(@Nullable Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Builds the item. - */ - public @NonNull MediaBrowserItem build() { - return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mIconUri, - mIconResourceId, mExtras); - } - } -} - diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl index 9911129..49087b0 100644 --- a/media/java/android/media/session/ISessionCallback.aidl +++ b/media/java/android/media/session/ISessionCallback.aidl @@ -30,7 +30,7 @@ oneway interface ISessionCallback { // These callbacks are for the TransportPerformer void onPlay(); - void onPlayUri(in Uri uri, in Bundle extras); + void onPlayFromMediaId(String uri, in Bundle extras); void onPlayFromSearch(String query, in Bundle extras); void onSkipToTrack(long id); void onPause(); diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl index 6b80477..d684688 100644 --- a/media/java/android/media/session/ISessionController.aidl +++ b/media/java/android/media/session/ISessionController.aidl @@ -51,9 +51,9 @@ interface ISessionController { // These commands are for the TransportControls void play(); - void playUri(in Uri uri, in Bundle extras); + void playFromMediaId(String uri, in Bundle extras); void playFromSearch(String string, in Bundle extras); - void skipToTrack(long id); + void skipToQueueItem(long id); void pause(); void stop(); void next(); diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index d7baaa9..9641f83 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -173,7 +173,7 @@ public final class MediaController { * * @return The current play queue or null. */ - public @Nullable List<MediaSession.Item> getQueue() { + public @Nullable List<MediaSession.QueueItem> getQueue() { try { ParceledListSlice queue = mSessionBinder.getQueue(); if (queue != null) { @@ -538,9 +538,9 @@ public final class MediaController { * @param queue A list of items in the current play queue. It should * include the currently playing item as well as previous and * upcoming items if applicable. - * @see MediaSession.Item + * @see MediaSession.QueueItem */ - public void onQueueChanged(@Nullable List<MediaSession.Item> queue) { + public void onQueueChanged(@Nullable List<MediaSession.QueueItem> queue) { } /** @@ -594,18 +594,19 @@ public final class MediaController { /** * Request that the player start playback for a specific {@link Uri}. * - * @param uri The uri of the requested media. + * @param mediaId The uri of the requested media. * @param extras Optional extras that can include extra information about the media item * to be played. */ - public void playUri(Uri uri, Bundle extras) { - if (uri == null) { - throw new IllegalArgumentException("You must specify a non-null Uri for playUri."); + public void playFromMediaId(String mediaId, Bundle extras) { + if (TextUtils.isEmpty(mediaId)) { + throw new IllegalArgumentException( + "You must specify a non-empty String for playFromMediaId."); } try { - mSessionBinder.playUri(uri, extras); + mSessionBinder.playFromMediaId(mediaId, extras); } catch (RemoteException e) { - Log.wtf(TAG, "Error calling play(" + uri + ").", e); + Log.wtf(TAG, "Error calling play(" + mediaId + ").", e); } } @@ -631,9 +632,9 @@ public final class MediaController { * Play an item with a specific id in the play queue. If you specify an * id that is not in the play queue, the behavior is undefined. */ - public void skipToItem(long id) { + public void skipToQueueItem(long id) { try { - mSessionBinder.skipToTrack(id); + mSessionBinder.skipToQueueItem(id); } catch (RemoteException e) { Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e); } @@ -904,7 +905,7 @@ public final class MediaController { @Override public void onQueueChanged(ParceledListSlice parceledQueue) { - List<MediaSession.Item> queue = parceledQueue.getList(); + List<MediaSession.QueueItem> queue = parceledQueue.getList(); MediaController controller = mController.get(); if (controller != null) { controller.postMessage(MSG_UPDATE_QUEUE, queue, null); @@ -960,7 +961,7 @@ public final class MediaController { mCallback.onMetadataChanged((MediaMetadata) msg.obj); break; case MSG_UPDATE_QUEUE: - mCallback.onQueueChanged((List<MediaSession.Item>) msg.obj); + mCallback.onQueueChanged((List<MediaSession.QueueItem>) msg.obj); break; case MSG_UPDATE_QUEUE_TITLE: mCallback.onQueueTitleChanged((CharSequence) msg.obj); diff --git a/media/java/android/media/session/MediaSession.aidl b/media/java/android/media/session/MediaSession.aidl index 0ad58c4..f657cef 100644 --- a/media/java/android/media/session/MediaSession.aidl +++ b/media/java/android/media/session/MediaSession.aidl @@ -16,4 +16,4 @@ package android.media.session; parcelable MediaSession.Token; -parcelable MediaSession.Track;
\ No newline at end of file +parcelable MediaSession.QueueItem;
\ No newline at end of file diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 1670097..ae8ce4b 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; +import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; @@ -38,6 +39,7 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; +import android.service.media.MediaBrowserService; import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -180,18 +182,26 @@ public final class MediaSession { * @param handler The handler that events should be posted on. */ public void setCallback(@Nullable Callback callback, @Nullable Handler handler) { - if (callback == null) { - mCallback = null; - return; - } synchronized (mLock) { - if (mCallback != null && mCallback.mCallback == callback) { - Log.w(TAG, "Tried to set same callback, ignoring"); + if (callback == null) { + if (mCallback != null) { + mCallback.mCallback.mSession = null; + } + mCallback = null; return; } + if (mCallback != null) { + if (mCallback.mCallback == callback) { + Log.w(TAG, "Tried to set same callback, ignoring"); + return; + } + // We're changing callbacks, clear the session from the old one. + mCallback.mCallback.mSession = null; + } if (handler == null) { handler = new Handler(); } + callback.mSession = this; CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(), callback); mCallback = msgHandler; @@ -418,9 +428,9 @@ public final class MediaSession { * * @param queue A list of items in the play queue. */ - public void setQueue(@Nullable List<Item> queue) { + public void setQueue(@Nullable List<QueueItem> queue) { try { - mBinder.setQueue(new ParceledListSlice<Item>(queue)); + mBinder.setQueue(new ParceledListSlice<QueueItem>(queue)); } catch (RemoteException e) { Log.wtf("Dead object in setQueue.", e); } @@ -478,8 +488,8 @@ public final class MediaSession { postToCallback(CallbackMessageHandler.MSG_PLAY); } - private void dispatchPlayUri(Uri uri, Bundle extras) { - postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras); + private void dispatchPlayFromMediaId(String mediaId, Bundle extras) { + postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras); } private void dispatchPlayFromSearch(String query, Bundle extras) { @@ -673,6 +683,7 @@ public final class MediaSession { * * @param mediaButtonIntent an intent containing the KeyEvent as an * extra + * @return True if the event was handled, false otherwise. */ public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) { if (mSession != null @@ -685,36 +696,43 @@ public final class MediaSession { case KeyEvent.KEYCODE_MEDIA_PLAY: if ((validActions & PlaybackState.ACTION_PLAY) != 0) { onPlay(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_PAUSE: if ((validActions & PlaybackState.ACTION_PAUSE) != 0) { onPause(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_NEXT: if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) { onSkipToNext(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) { onSkipToPrevious(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_STOP: if ((validActions & PlaybackState.ACTION_STOP) != 0) { onStop(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) { onFastForward(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_REWIND: if ((validActions & PlaybackState.ACTION_REWIND) != 0) { onRewind(); + return true; } break; case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: @@ -727,8 +745,10 @@ public final class MediaSession { | PlaybackState.ACTION_PAUSE)) != 0; if (isPlaying && canPause) { onPause(); + return true; } else if (!isPlaying && canPlay) { onPlay(); + return true; } break; } @@ -744,9 +764,10 @@ public final class MediaSession { } /** - * Override to handle requests to play a specific {@link Uri}. + * Override to handle requests to play a specific mediaId that was + * provided by your app's {@link MediaBrowserService}. */ - public void onPlayUri(Uri uri, Bundle extras) { + public void onPlayFromMediaId(String mediaId, Bundle extras) { } /** @@ -759,7 +780,7 @@ public final class MediaSession { * Override to handle requests to play an item with a given id from the * play queue. */ - public void onSkipToItem(long id) { + public void onSkipToQueueItem(long id) { } /** @@ -824,10 +845,6 @@ public final class MediaSession { */ public void onCustomAction(@NonNull String action, @Nullable Bundle extras) { } - - private void setSession(MediaSession session) { - mSession = session; - } } /** @@ -872,10 +889,10 @@ public final class MediaSession { } @Override - public void onPlayUri(Uri uri, Bundle extras) { + public void onPlayFromMediaId(String mediaId, Bundle extras) { MediaSession session = mMediaSession.get(); if (session != null) { - session.dispatchPlayUri(uri, extras); + session.dispatchPlayFromMediaId(mediaId, extras); } } @@ -990,125 +1007,59 @@ public final class MediaSession { } /** - * A single item that is part of the play queue. It contains information - * necessary to display a single item in the queue. + * A single item that is part of the play queue. It contains a description + * of the item and its id in the queue. */ - public static final class Item implements Parcelable { + public static final class QueueItem implements Parcelable { /** * This id is reserved. No items can be explicitly asigned this id. */ public static final int UNKNOWN_ID = -1; - private final MediaMetadata mMetadata; + private final MediaDescription mDescription; private final long mId; - private final Uri mUri; - private final Bundle mExtras; /** - * Create a new {@link MediaSession.Item}. + * Create a new {@link MediaSession.QueueItem}. * - * @param metadata The metadata for this item. + * @param description The {@link MediaDescription} for this item. * @param id An identifier for this item. It must be unique within the - * play queue. - * @param uri The uri for this item. - * @param extras A bundle of extras that can be used to add extra - * information about this item. + * play queue and cannot be {@link #UNKNOWN_ID}. */ - private Item(MediaMetadata metadata, long id, Uri uri, Bundle extras) { - mMetadata = metadata; + public QueueItem(MediaDescription description, long id) { + if (description == null) { + throw new IllegalArgumentException("Description cannot be null."); + } + if (id == UNKNOWN_ID) { + throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID"); + } + mDescription = description; mId = id; - mUri = uri; - mExtras = extras; } - private Item(Parcel in) { - mMetadata = MediaMetadata.CREATOR.createFromParcel(in); + private QueueItem(Parcel in) { + mDescription = MediaDescription.CREATOR.createFromParcel(in); mId = in.readLong(); - mUri = Uri.CREATOR.createFromParcel(in); - mExtras = in.readBundle(); } /** - * Get the metadata for this item. + * Get the description for this item. */ - public MediaMetadata getMetadata() { - return mMetadata; + public MediaDescription getDescription() { + return mDescription; } /** - * Get the id for this item. + * Get the queue id for this item. */ - public long getId() { + public long getQueueId() { return mId; } - /** - * Get the Uri for this item. - */ - public Uri getUri() { - return mUri; - } - - /** - * Get the extras for this item. - */ - public Bundle getExtras() { - return mExtras; - } - - /** - * Builder for {@link MediaSession.Item} objects. - */ - public static final class Builder { - private final MediaMetadata mMetadata; - private final long mId; - private final Uri mUri; - - private Bundle mExtras; - - /** - * Create a builder with the metadata, id, and uri already set. - */ - public Builder(MediaMetadata metadata, long id, Uri uri) { - if (metadata == null) { - throw new IllegalArgumentException( - "You must specify a non-null MediaMetadata to build an Item."); - } - if (uri == null) { - throw new IllegalArgumentException( - "You must specify a non-null Uri to build an Item."); - } - if (id == UNKNOWN_ID) { - throw new IllegalArgumentException( - "You must specify an id other than UNKNOWN_ID to build an Item."); - } - mMetadata = metadata; - mId = id; - mUri = uri; - } - - /** - * Set optional extras for the item. - */ - public MediaSession.Item.Builder setExtras(Bundle extras) { - mExtras = extras; - return this; - } - - /** - * Create the {@link Item}. - */ - public MediaSession.Item build() { - return new MediaSession.Item(mMetadata, mId, mUri, mExtras); - } - } - @Override public void writeToParcel(Parcel dest, int flags) { - mMetadata.writeToParcel(dest, flags); + mDescription.writeToParcel(dest, flags); dest.writeLong(mId); - mUri.writeToParcel(dest, flags); - dest.writeBundle(mExtras); } @Override @@ -1116,28 +1067,24 @@ public final class MediaSession { return 0; } - public static final Creator<MediaSession.Item> CREATOR - = new Creator<MediaSession.Item>() { + public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() { @Override - public MediaSession.Item createFromParcel(Parcel p) { - return new MediaSession.Item(p); + public MediaSession.QueueItem createFromParcel(Parcel p) { + return new MediaSession.QueueItem(p); } @Override - public MediaSession.Item[] newArray(int size) { - return new MediaSession.Item[size]; + public MediaSession.QueueItem[] newArray(int size) { + return new MediaSession.QueueItem[size]; } }; @Override public String toString() { - return "MediaSession.Item {" + - "Metadata=" + mMetadata + - ", Id=" + mId + - ", Uri=" + mUri + - ", Extras=" + mExtras + - " }"; + return "MediaSession.QueueItem {" + + "Description=" + mDescription + + ", Id=" + mId + " }"; } } @@ -1156,7 +1103,7 @@ public final class MediaSession { private class CallbackMessageHandler extends Handler { private static final int MSG_PLAY = 1; - private static final int MSG_PLAY_URI = 2; + private static final int MSG_PLAY_MEDIA_ID = 2; private static final int MSG_PLAY_SEARCH = 3; private static final int MSG_SKIP_TO_ITEM = 4; private static final int MSG_PAUSE = 5; @@ -1202,14 +1149,14 @@ public final class MediaSession { case MSG_PLAY: mCallback.onPlay(); break; - case MSG_PLAY_URI: - mCallback.onPlayUri((Uri) msg.obj, msg.getData()); + case MSG_PLAY_MEDIA_ID: + mCallback.onPlayFromMediaId((String) msg.obj, msg.getData()); break; case MSG_PLAY_SEARCH: mCallback.onPlayFromSearch((String) msg.obj, msg.getData()); break; case MSG_SKIP_TO_ITEM: - mCallback.onSkipToItem((Long) msg.obj); + mCallback.onSkipToQueueItem((Long) msg.obj); case MSG_PAUSE: mCallback.onPause(); break; diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 2ca97dd..267d1ff 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -36,95 +36,95 @@ public final class PlaybackState implements Parcelable { private static final String TAG = "PlaybackState"; /** - * Indicates this performer supports the stop command. + * Indicates this session supports the stop command. * * @see Builder#setActions(long) */ public static final long ACTION_STOP = 1 << 0; /** - * Indicates this performer supports the pause command. + * Indicates this session supports the pause command. * * @see Builder#setActions(long) */ public static final long ACTION_PAUSE = 1 << 1; /** - * Indicates this performer supports the play command. + * Indicates this session supports the play command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY = 1 << 2; /** - * Indicates this performer supports the rewind command. + * Indicates this session supports the rewind command. * * @see Builder#setActions(long) */ public static final long ACTION_REWIND = 1 << 3; /** - * Indicates this performer supports the previous command. + * Indicates this session supports the previous command. * * @see Builder#setActions(long) */ public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4; /** - * Indicates this performer supports the next command. + * Indicates this session supports the next command. * * @see Builder#setActions(long) */ public static final long ACTION_SKIP_TO_NEXT = 1 << 5; /** - * Indicates this performer supports the fast forward command. + * Indicates this session supports the fast forward command. * * @see Builder#setActions(long) */ public static final long ACTION_FAST_FORWARD = 1 << 6; /** - * Indicates this performer supports the set rating command. + * Indicates this session supports the set rating command. * * @see Builder#setActions(long) */ public static final long ACTION_SET_RATING = 1 << 7; /** - * Indicates this performer supports the seek to command. + * Indicates this session supports the seek to command. * * @see Builder#setActions(long) */ public static final long ACTION_SEEK_TO = 1 << 8; /** - * Indicates this performer supports the play/pause toggle command. + * Indicates this session supports the play/pause toggle command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY_PAUSE = 1 << 9; /** - * Indicates this performer supports the play from uri command. + * Indicates this session supports the play from media id command. * * @see Builder#setActions(long) */ - public static final long ACTION_PLAY_URI = 1 << 10; + public static final long ACTION_PLAY_FROM_MEDIA_ID = 1 << 10; /** - * Indicates this performer supports the play from search command. + * Indicates this session supports the play from search command. * * @see Builder#setActions(long) */ public static final long ACTION_PLAY_FROM_SEARCH = 1 << 11; /** - * Indicates this performer supports the skip to item command. + * Indicates this session supports the skip to queue item command. * * @see Builder#setActions(long) */ - public static final long ACTION_SKIP_TO_ITEM = 1 << 12; + public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12; /** * This is the default playback state and indicates that no media has been @@ -211,6 +211,14 @@ public final class PlaybackState implements Parcelable { public final static int STATE_SKIPPING_TO_NEXT = 10; /** + * State indicating the player is currently skipping to a specific item in + * the queue. + * + * @see Builder#setState + */ + public final static int STATE_SKIPPING_TO_QUEUE_ITEM = 11; + + /** * Use this value for the position to indicate the position is not known. */ public final static long PLAYBACK_POSITION_UNKNOWN = -1; @@ -374,6 +382,18 @@ public final class PlaybackState implements Parcelable { } /** + * Get the id of the currently active item in the queue. If there is no + * queue or a queue is not supported by the session this will be + * {@link MediaSession.QueueItem#UNKNOWN_ID}. + * + * @return The id of the currently active item in the queue or + * {@link MediaSession.QueueItem#UNKNOWN_ID}. + */ + public long getActiveQueueItemId() { + return mActiveItemId; + } + + /** * Get the {@link PlaybackState} state for the given * {@link RemoteControlClient} state. * @@ -716,7 +736,7 @@ public final class PlaybackState implements Parcelable { private long mActions; private CharSequence mErrorMessage; private long mUpdateTime; - private long mActiveItemId = MediaSession.Item.UNKNOWN_ID; + private long mActiveItemId = MediaSession.QueueItem.UNKNOWN_ID; /** * Creates an initially empty state builder. @@ -904,12 +924,12 @@ public final class PlaybackState implements Parcelable { /** * Set the active item in the play queue by specifying its id. The - * default value is {@link MediaSession.Item#UNKNOWN_ID} + * default value is {@link MediaSession.QueueItem#UNKNOWN_ID} * * @param id The id of the active item. * @return this */ - public Builder setActiveItem(long id) { + public Builder setActiveQueueItemId(long id) { mActiveItemId = id; return this; } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 20b8e7c..6ca794e 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -22,6 +22,7 @@ import android.media.tv.ITvInputClient; import android.media.tv.ITvInputHardware; import android.media.tv.ITvInputHardwareCallback; import android.media.tv.ITvInputManagerCallback; +import android.media.tv.TvContentRatingSystemInfo; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvStreamConfig; @@ -38,7 +39,7 @@ interface ITvInputManager { List<TvInputInfo> getTvInputList(int userId); TvInputInfo getTvInputInfo(in String inputId, int userId); - List<Uri> getTvContentRatingSystemXmls(int userId); + List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId); void registerCallback(in ITvInputManagerCallback callback, int userId); void unregisterCallback(in ITvInputManagerCallback callback, int userId); diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java index bbf7399..93b40fb 100644 --- a/media/java/android/media/tv/TvContentRating.java +++ b/media/java/android/media/tv/TvContentRating.java @@ -33,10 +33,9 @@ import java.util.Objects; * {@link #createRating TvContentRating.createRating} method with valid rating system string * constants. * <p> - * It is possible for a TV input to define its own content rating system by supplying a content - * rating system definition XML resource (see example below) and having the - * {@link android.R.attr#tvContentRatingDescription tvContentRatingDescription} attribute in - * {@link TvInputService#SERVICE_META_DATA} of the TV input point to it. + * It is possible for an application to define its own content rating system by supplying a content + * rating system definition XML resource (see example below) and declaring a broadcast receiver that + * filters {@link TvInputManager#ACTION_QUERY_CONTENT_RATING_SYSTEMS} in its manifest. * </p> * <h3> Example: Rating system definition for the TV Parental Guidelines</h3> * The following XML example shows how the TV Parental Guidelines in the United States can be @@ -127,7 +126,7 @@ import java.util.Objects; * code snippet: * </p> * <pre> - * String rating = TvContentRating.createRating( + * TvContentRating rating = TvContentRating.createRating( * "com.android.tv", * "US_TV", * "US_TV_PG", @@ -137,7 +136,7 @@ import java.util.Objects; * <table> * <tr> * <th>Constant Value</th> - * <th>Comment</th> + * <th>Description</th> * </tr> * <tr> * <td>com.android.tv</td> @@ -149,7 +148,7 @@ import java.util.Objects; * <table> * <tr> * <th>Constant Value</th> - * <th>Comment</th> + * <th>Description</th> * </tr> * <tr> * <td>AM_TV_RS</td> @@ -347,7 +346,7 @@ import java.util.Objects; * <tr> * <th>Rating System</th> * <th>Constant Value</th> - * <th>Comment</th> + * <th>Description</th> * </tr> * <tr> * <td valign="top" rowspan="6">AM_TV_RS</td> @@ -1420,7 +1419,7 @@ import java.util.Objects; * <tr> * <th>Rating System</th> * <th>Constant Value</th> - * <th>Comment</th> + * <th>Description</th> * </tr> * <tr> * <td valign="top" rowspan="6">NL_TV</td> diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.aidl b/media/java/android/media/tv/TvContentRatingSystemInfo.aidl new file mode 100644 index 0000000..957be62 --- /dev/null +++ b/media/java/android/media/tv/TvContentRatingSystemInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +parcelable TvContentRatingSystemInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java new file mode 100644 index 0000000..f2e5b08 --- /dev/null +++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.SystemApi; +import android.content.ContentResolver; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * TvContentRatingSystemInfo class provides information about a specific TV content rating system + * defined either by a system app or by a third-party app. + * + * @hide + */ +@SystemApi +public final class TvContentRatingSystemInfo implements Parcelable { + private final Uri mXmlUri; + + private final ApplicationInfo mApplicationInfo; + + /** + * Creates a TvContentRatingSystemInfo object with given resource ID and receiver info. + * + * @param xmlResourceId The ID of an XML resource whose root element is + * <code> <rating-system-definitions></code> + * @param applicationInfo Information about the application that provides the TV content rating + * system definition. + */ + public static final TvContentRatingSystemInfo createTvContentRatingSystemInfo(int xmlResourceId, + ApplicationInfo applicationInfo) { + Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(applicationInfo.packageName) + .appendPath(String.valueOf(xmlResourceId)) + .build(); + return new TvContentRatingSystemInfo(uri, applicationInfo); + } + + private TvContentRatingSystemInfo(Uri xmlUri, ApplicationInfo applicationInfo) { + mXmlUri = xmlUri; + mApplicationInfo = applicationInfo; + } + + /** + * Returns {@code true} if the TV content rating system is defined by a system app, + * {@code false} otherwise. + */ + public final boolean isSystemDefined() { + return (mApplicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + /** + * Returns the URI to the XML resource that defines the TV content rating system. + * + * TODO: Remove. Instead, parse the XML resource and provide an interface to directly access + * parsed information. + */ + public final Uri getXmlUri() { + return mXmlUri; + } + + /** + * Used to make this class parcelable. + * @hide + */ + public static final Parcelable.Creator<TvContentRatingSystemInfo> CREATOR = + new Parcelable.Creator<TvContentRatingSystemInfo>() { + @Override + public TvContentRatingSystemInfo createFromParcel(Parcel in) { + return new TvContentRatingSystemInfo(in); + } + + @Override + public TvContentRatingSystemInfo[] newArray(int size) { + return new TvContentRatingSystemInfo[size]; + } + }; + + private TvContentRatingSystemInfo(Parcel in) { + mXmlUri = in.readParcelable(null); + mApplicationInfo = in.readParcelable(null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mXmlUri, flags); + dest.writeParcelable(mApplicationInfo, flags); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 00c7ad4..7f1c304 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -680,7 +680,7 @@ public final class TvContract { * <p> * A value of 1 indicates the channel is included in the channel list that applications use * to browse channels, a value of 0 indicates the channel is not included in the list. If - * not specified, this value is set to 1 (browsable) by default. + * not specified, this value is set to 0 (not browsable) by default. * </p><p> * Type: INTEGER (boolean) * </p> diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 106e1dc..00183bb 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -18,7 +18,6 @@ package android.media.tv; import android.annotation.SystemApi; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -125,7 +124,6 @@ public final class TvInputInfo implements Parcelable { private String mLabel; private Uri mIconUri; private boolean mIsConnectedToHdmiSwitch; - private Uri mRatingSystemXmlUri; static { sHardwareTypeToTvInputType.put(TvInputHardwareInfo.TV_INPUT_TYPE_OTHER_HARDWARE, @@ -245,19 +243,6 @@ public final class TvInputInfo implements Parcelable { Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for " + si.name); } - int tvContentRatingDescription = sa.getResourceId( - com.android.internal.R.styleable.TvInputService_tvContentRatingDescription, -1); - if (tvContentRatingDescription != -1) { - input.mRatingSystemXmlUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(si.packageName) - .appendPath(Integer.toString(tvContentRatingDescription)) - .build(); - if (DEBUG) { - Log.d(TAG, "Content rating xml loaded. [" + tvContentRatingDescription - + "] for " + si.name); - } - } sa.recycle(); input.mLabel = label; @@ -367,15 +352,6 @@ public final class TvInputInfo implements Parcelable { } /** - * Returns the resource uri for the rating system xml of this TV input service. - * @hide - */ - @SystemApi - public Uri getRatingSystemXmlUri() { - return mRatingSystemXmlUri; - } - - /** * Returns {@code true} if this TV input is pass-though which does not have any real channels * in TvProvider. {@code false} otherwise. * @@ -508,7 +484,6 @@ public final class TvInputInfo implements Parcelable { dest.writeParcelable(mIconUri, flags); dest.writeString(mLabel); dest.writeByte(mIsConnectedToHdmiSwitch ? (byte) 1 : 0); - dest.writeParcelable(mRatingSystemXmlUri, flags); } private Drawable loadDefaultIcon(Context context) { @@ -581,7 +556,6 @@ public final class TvInputInfo implements Parcelable { mIconUri = in.readParcelable(null); mLabel = in.readString(); mIsConnectedToHdmiSwitch = in.readByte() == 1 ? true : false; - mRatingSystemXmlUri = in.readParcelable(null); } /** diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 13937e2..6d1f0e4 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -101,14 +101,58 @@ public final class TvInputManager { * {@link #isRatingBlocked}. */ public static final String ACTION_BLOCKED_RATINGS_CHANGED = - "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; + "android.media.tv.TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED"; /** * Broadcast intent action when the parental controls enabled state changes. For use with the * {@link #isParentalControlsEnabled}. */ public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = - "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; + "android.media.tv.TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED"; + + /** + * Broadcast intent action used to query available content rating systems. + * <p> + * The TV input manager service locates available content rating systems by querying broadcast + * receivers that are registered for this action. An application can offer additional content + * rating systems to the user by declaring a suitable broadcast receiver in its manifest. + * </p><p> + * Here is an example broadcast receiver declaration that an application might include in its + * AndroidManifest.xml to advertise custom content rating systems. The meta-data specifies a + * resource that contains a description of each content rating system that is provided by the + * application. + * <p><pre class="prettyprint"> + * {@literal + * <receiver android:name=".TvInputReceiver"> + * <intent-filter> + * <action android:name= + * "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS" /> + * </intent-filter> + * <meta-data + * android:name="android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS" + * android:resource="@xml/tv_content_rating_systems" /> + * </receiver>}</pre></p> + * In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an + * XML resource whose root element is <code><rating-system-definitions></code> that + * contains zero or more <code><rating-system-definition></code> elements. Each <code> + * <rating-system-definition></code> element specifies the ratings, sub-ratings and rating + * orders of a particular content rating system. + * </p> + * + * @see TvContentRating + */ + public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS = + "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS"; + + /** + * Content rating systems metadata associated with {@link #ACTION_QUERY_CONTENT_RATING_SYSTEMS}. + * <p> + * Specifies the resource ID of an XML resource that describes the content rating systems that + * are provided by the application. + * </p> + */ + public static final String META_DATA_CONTENT_RATING_SYSTEMS = + "android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS"; private final ITvInputManager mService; @@ -863,13 +907,13 @@ public final class TvInputManager { } /** - * Returns the list of xml resource uris for TV content rating systems. + * Returns the list of all TV content rating systems defined. * @hide */ @SystemApi - public List<Uri> getTvContentRatingSystemXmls() { + public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() { try { - return mService.getTvContentRatingSystemXmls(mUserId); + return mService.getTvContentRatingSystemList(mUserId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/media/java/android/media/browse/IMediaBrowserService.aidl b/media/java/android/service/media/IMediaBrowserService.aidl index 8acd724..2631ddd 100644 --- a/media/java/android/media/browse/IMediaBrowserService.aidl +++ b/media/java/android/service/media/IMediaBrowserService.aidl @@ -1,9 +1,9 @@ // Copyright 2014 Google Inc. All Rights Reserved. -package android.media.browse; +package android.service.media; import android.content.res.Configuration; -import android.media.browse.IMediaBrowserServiceCallbacks; +import android.service.media.IMediaBrowserServiceCallbacks; import android.net.Uri; import android.os.Bundle; @@ -18,6 +18,4 @@ oneway interface IMediaBrowserService { void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks); void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks); - void loadIcon(in int seqNum, in Uri uri, int width, int height, - IMediaBrowserServiceCallbacks callbacks); }
\ No newline at end of file diff --git a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl index 06fabcc..7702a50 100644 --- a/media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl +++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl @@ -1,6 +1,6 @@ // Copyright 2014 Google Inc. All Rights Reserved. -package android.media.browse; +package android.service.media; import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; @@ -24,5 +24,4 @@ oneway interface IMediaBrowserServiceCallbacks { void onConnect(in Uri root, in MediaSession.Token session, in Bundle extras); void onConnectFailed(); void onLoadChildren(in Uri uri, in ParceledListSlice list); - void onLoadIcon(int seqNum, in Bitmap bitmap); } diff --git a/media/java/android/media/browse/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 99126c9..4d6fd7b 100644 --- a/media/java/android/media/browse/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.media.browse; +package android.service.media; import android.annotation.IntDef; import android.annotation.NonNull; @@ -27,6 +27,8 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.media.browse.MediaBrowser; +import android.media.browse.MediaBrowser.MediaItem; import android.media.session.MediaSession; import android.net.Uri; import android.os.Binder; @@ -34,6 +36,9 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Handler; import android.os.RemoteException; +import android.service.media.IMediaBrowserService; +import android.service.media.IMediaBrowserServiceCallbacks; +import android.service.media.IMediaBrowserService.Stub; import android.util.ArrayMap; import android.util.Log; @@ -264,59 +269,6 @@ public abstract class MediaBrowserService extends Service { } }); } - - @Override - public void loadIcon(final int seq, final Uri uri, final int width, final int height, - final IMediaBrowserServiceCallbacks callbacks) { - if (uri == null) { - throw new IllegalStateException("loadIcon sent null list for uri " + uri); - } - mHandler.post(new Runnable() { - @Override - public void run() { - // In theory we could return a result to a new connection, but in practice - // the other side in MediaBrowser uses a new IMediaBrowserServiceCallbacks - // object every time it calls connect(), so as long as it does that we won't - // see results sent for the wrong connection. - final ConnectionRecord connection = mConnections.get(callbacks.asBinder()); - if (connection == null) { - if (DBG) { - Log.d(TAG, "Not loading bitmap for invalid connection. uri=" + uri); - } - return; - } - - final Result<Bitmap> result = new Result<Bitmap>(uri) { - @Override - void onResultSent(Bitmap bitmap) { - if (mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadIcon result for connection" - + " that has been disconnected. pkg=" + connection.pkg - + " uri=" + uri); - } - return; - } - - try { - callbacks.onLoadIcon(seq, bitmap); - } catch (RemoteException e) { - // The other side is in the process of crashing. - Log.w(TAG, "RemoteException in calling onLoadIcon", e); - } - } - }; - - onLoadIcon(uri, width, height, result); - - if (!result.isDone()) { - throw new IllegalStateException("onLoadIcon must call detach() or" - + " sendResult() before returning for package=" + connection.pkg - + " uri=" + uri); - } - } - }); - } } @Override @@ -372,26 +324,7 @@ public abstract class MediaBrowserService extends Service { * @return The list of children, or null if the uri is invalid. */ public abstract void onLoadChildren(@NonNull Uri parentUri, - @NonNull Result<List<MediaBrowserItem>> result); - - /** - * Called to get the icon of a particular media item. - * <p> - * Implementations must call result.{@link Result#sendResult result.sendResult} with the bitmap. - * If loading the bitmap will be an expensive operation that should be performed - * on another thread, result.{@link Result#detach result.detach} may be called before returning - * from this function, and then {@link Result#sendResult result.sendResult} called when - * the loading is complete. - * - * @param uri The uri of the media item. - * @param width The requested width of the icon in dp. - * @param height The requested height of the icon in dp. - * - * @return The file descriptor of the icon, which may then be loaded - * using a bitmap factory, or null if the item does not have an icon. - */ - public abstract void onLoadIcon(@NonNull Uri uri, int width, int height, - @NonNull Result<Bitmap> result); + @NonNull Result<List<MediaBrowser.MediaItem>> result); /** * Call to set the media session. @@ -478,9 +411,11 @@ public abstract class MediaBrowserService extends Service { * Callers must make sure that this connection is still connected. */ private void performLoadChildren(final Uri uri, final ConnectionRecord connection) { - final Result<List<MediaBrowserItem>> result = new Result<List<MediaBrowserItem>>(uri) { + final Result<List<MediaBrowser.MediaItem>> result + = new Result<List<MediaBrowser.MediaItem>>( + uri) { @Override - void onResultSent(List<MediaBrowserItem> list) { + void onResultSent(List<MediaBrowser.MediaItem> list) { if (list == null) { throw new IllegalStateException("onLoadChildren sent null list for uri " + uri); } @@ -492,7 +427,7 @@ public abstract class MediaBrowserService extends Service { return; } - final ParceledListSlice<MediaBrowserItem> pls = new ParceledListSlice(list); + final ParceledListSlice<MediaBrowser.MediaItem> pls = new ParceledListSlice(list); try { connection.callbacks.onLoadChildren(uri, pls); } catch (RemoteException ex) { |
