diff options
Diffstat (limited to 'media/java')
-rw-r--r-- | media/java/android/media/AudioDeviceInfo.java | 7 | ||||
-rw-r--r-- | media/java/android/media/AudioSystem.java | 10 | ||||
-rw-r--r-- | media/java/android/media/IRingtonePlayer.aidl | 3 | ||||
-rw-r--r-- | media/java/android/media/ImageUtils.java | 84 | ||||
-rw-r--r-- | media/java/android/media/ImageWriter.java | 24 | ||||
-rw-r--r-- | media/java/android/media/MediaCodec.java | 9 | ||||
-rw-r--r-- | media/java/android/media/Ringtone.java | 64 | ||||
-rw-r--r-- | media/java/android/media/session/MediaSessionManager.java | 36 | ||||
-rw-r--r-- | media/java/android/mtp/MtpStorage.java | 12 |
9 files changed, 217 insertions, 32 deletions
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 173d349..bdb1f58 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -105,6 +105,10 @@ public final class AudioDeviceInfo { * A device type describing the auxiliary line-level connectors. */ public static final int TYPE_AUX_LINE = 19; + /** + * A device type connected over IP. + */ + public static final int TYPE_IP = 20; private final AudioDevicePort mPort; @@ -250,6 +254,7 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPDIF, TYPE_LINE_DIGITAL); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_FM, TYPE_FM); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_AUX_LINE, TYPE_AUX_LINE); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO); @@ -266,6 +271,7 @@ public final class AudioDeviceInfo { INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_LINE, TYPE_LINE_ANALOG); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_SPDIF, TYPE_LINE_DIGITAL); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP); + INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP); // not covered here, legacy //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX @@ -292,6 +298,7 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE); + EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP); } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 373f3fd..acdadd7 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -317,6 +317,7 @@ public class AudioSystem public static final int DEVICE_OUT_FM = 0x100000; public static final int DEVICE_OUT_AUX_LINE = 0x200000; public static final int DEVICE_OUT_SPEAKER_SAFE = 0x400000; + public static final int DEVICE_OUT_IP = 0x800000; public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT; @@ -343,6 +344,7 @@ public class AudioSystem DEVICE_OUT_FM | DEVICE_OUT_AUX_LINE | DEVICE_OUT_SPEAKER_SAFE | + DEVICE_OUT_IP | DEVICE_OUT_DEFAULT); public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | @@ -381,6 +383,7 @@ public class AudioSystem public static final int DEVICE_IN_SPDIF = DEVICE_BIT_IN | 0x10000; public static final int DEVICE_IN_BLUETOOTH_A2DP = DEVICE_BIT_IN | 0x20000; public static final int DEVICE_IN_LOOPBACK = DEVICE_BIT_IN | 0x40000; + public static final int DEVICE_IN_IP = DEVICE_BIT_IN | 0x80000; public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT; public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | @@ -402,6 +405,7 @@ public class AudioSystem DEVICE_IN_SPDIF | DEVICE_IN_BLUETOOTH_A2DP | DEVICE_IN_LOOPBACK | + DEVICE_IN_IP | DEVICE_IN_DEFAULT); public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET; public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY | @@ -436,6 +440,7 @@ public class AudioSystem public static final String DEVICE_OUT_FM_NAME = "fm_transmitter"; public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line"; public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe"; + public static final String DEVICE_OUT_IP_NAME = "ip"; public static final String DEVICE_IN_COMMUNICATION_NAME = "communication"; public static final String DEVICE_IN_AMBIENT_NAME = "ambient"; @@ -456,6 +461,7 @@ public class AudioSystem public static final String DEVICE_IN_SPDIF_NAME = "spdif"; public static final String DEVICE_IN_BLUETOOTH_A2DP_NAME = "bt_a2dp"; public static final String DEVICE_IN_LOOPBACK_NAME = "loopback"; + public static final String DEVICE_IN_IP_NAME = "ip"; public static String getOutputDeviceName(int device) { @@ -506,6 +512,8 @@ public class AudioSystem return DEVICE_OUT_AUX_LINE_NAME; case DEVICE_OUT_SPEAKER_SAFE: return DEVICE_OUT_SPEAKER_SAFE_NAME; + case DEVICE_OUT_IP: + return DEVICE_OUT_IP_NAME; case DEVICE_OUT_DEFAULT: default: return Integer.toString(device); @@ -553,6 +561,8 @@ public class AudioSystem return DEVICE_IN_BLUETOOTH_A2DP_NAME; case DEVICE_IN_LOOPBACK: return DEVICE_IN_LOOPBACK_NAME; + case DEVICE_IN_IP: + return DEVICE_IN_IP_NAME; case DEVICE_IN_DEFAULT: default: return Integer.toString(device); diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 7c011e6..aa5fde3 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -25,9 +25,10 @@ import android.os.UserHandle; */ interface IRingtonePlayer { /** Used for Ringtone.java playback */ - void play(IBinder token, in Uri uri, in AudioAttributes aa); + void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping); void stop(IBinder token); boolean isPlaying(IBinder token); + void setPlaybackProperties(IBinder token, float volume, boolean looping); /** Used for Notification sound playback. */ void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa); diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java index 2763d1d..ba3949a 100644 --- a/media/java/android/media/ImageUtils.java +++ b/media/java/android/media/ImageUtils.java @@ -21,6 +21,8 @@ import android.graphics.PixelFormat; import android.media.Image.Plane; import android.util.Size; +import libcore.io.Memory; + import java.nio.ByteBuffer; /** @@ -109,12 +111,50 @@ class ImageUtils { ByteBuffer srcBuffer = null; ByteBuffer dstBuffer = null; for (int i = 0; i < srcPlanes.length; i++) { + int srcRowStride = srcPlanes[i].getRowStride(); + int dstRowStride = dstPlanes[i].getRowStride(); srcBuffer = srcPlanes[i].getBuffer(); + dstBuffer = dstPlanes[i].getBuffer(); + if (!(srcBuffer.isDirect() && dstBuffer.isDirect())) { + throw new IllegalArgumentException("Source and destination ByteBuffers must be" + + " direct byteBuffer!"); + } + if (srcPlanes[i].getPixelStride() != dstPlanes[i].getPixelStride()) { + throw new IllegalArgumentException("Source plane image pixel stride " + + srcPlanes[i].getPixelStride() + + " must be same as destination image pixel stride " + + dstPlanes[i].getPixelStride()); + } + int srcPos = srcBuffer.position(); srcBuffer.rewind(); - dstBuffer = dstPlanes[i].getBuffer(); dstBuffer.rewind(); - dstBuffer.put(srcBuffer); + if (srcRowStride == dstRowStride) { + // Fast path, just copy the content if the byteBuffer all together. + dstBuffer.put(srcBuffer); + } else { + // Source and destination images may have different alignment requirements, + // therefore may have different strides. Copy row by row for such case. + int srcOffset = srcBuffer.position(); + int dstOffset = dstBuffer.position(); + Size effectivePlaneSize = getEffectivePlaneSizeForImage(src, i); + int srcByteCount = effectivePlaneSize.getWidth() * srcPlanes[i].getPixelStride(); + for (int row = 0; row < effectivePlaneSize.getHeight(); row++) { + if (row == effectivePlaneSize.getHeight() - 1) { + // Special case for NV21 backed YUV420_888: need handle the last row + // carefully to avoid memory corruption. Check if we have enough bytes to + // copy. + int remainingBytes = srcBuffer.remaining() - srcOffset; + if (srcByteCount > remainingBytes) { + srcByteCount = remainingBytes; + } + } + directByteBufferCopy(srcBuffer, srcOffset, dstBuffer, dstOffset, srcByteCount); + srcOffset += srcRowStride; + dstOffset += dstRowStride; + } + } + srcBuffer.position(srcPos); dstBuffer.rewind(); } @@ -175,4 +215,44 @@ class ImageUtils { return (int)(width * height * estimatedBytePerPixel * numImages); } + + private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { + switch (image.getFormat()) { + case ImageFormat.YV12: + case ImageFormat.YUV_420_888: + case ImageFormat.NV21: + if (planeIdx == 0) { + return new Size(image.getWidth(), image.getHeight()); + } else { + return new Size(image.getWidth() / 2, image.getHeight() / 2); + } + case ImageFormat.NV16: + if (planeIdx == 0) { + return new Size(image.getWidth(), image.getHeight()); + } else { + return new Size(image.getWidth(), image.getHeight() / 2); + } + case PixelFormat.RGB_565: + case PixelFormat.RGBA_8888: + case PixelFormat.RGBX_8888: + case PixelFormat.RGB_888: + case ImageFormat.JPEG: + case ImageFormat.YUY2: + case ImageFormat.Y8: + case ImageFormat.Y16: + case ImageFormat.RAW_SENSOR: + case ImageFormat.RAW10: + return new Size(image.getWidth(), image.getHeight()); + case ImageFormat.PRIVATE: + return new Size(0, 0); + default: + throw new UnsupportedOperationException( + String.format("Invalid image format %d", image.getFormat())); + } + } + + private static void directByteBufferCopy(ByteBuffer srcBuffer, int srcOffset, + ByteBuffer dstBuffer, int dstOffset, int srcByteCount) { + Memory.memmove(dstBuffer, dstOffset, srcBuffer, srcOffset, srcByteCount); + } } diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 2ef2519..9fb3286 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -40,8 +40,8 @@ import java.util.List; * Several Android API classes can provide input {@link android.view.Surface * Surface} objects for ImageWriter to produce data into, including * {@link MediaCodec MediaCodec} (encoder), - * {@link android.hardware.camera2.CameraDevice CameraDevice} (reprocessing - * input), {@link ImageReader}, etc. + * {@link android.hardware.camera2.CameraCaptureSession CameraCaptureSession} + * (reprocessing input), {@link ImageReader}, etc. * </p> * <p> * The input Image data is encapsulated in {@link Image} objects. To produce @@ -64,7 +64,14 @@ import java.util.List; * {@link android.hardware.camera2.CameraDevice}) to consume the Images. If the * downstream components cannot consume the Images at least as fast as the * ImageWriter production rate, the {@link #dequeueInputImage} call will - * eventually block and the application will have to drop input frames. </p> + * eventually block and the application will have to drop input frames. + * </p> + * <p> + * If the consumer component that provided the input {@link android.view.Surface Surface} + * abandons the {@link android.view.Surface Surface}, {@link #queueInputImage queueing} + * or {@link #dequeueInputImage dequeueing} an {@link Image} will throw an + * {@link IllegalStateException}. + * </p> */ public class ImageWriter implements AutoCloseable { private final Object mListenerLock = new Object(); @@ -188,7 +195,9 @@ public class ImageWriter implements AutoCloseable { * @return The next available input Image from this ImageWriter. * @throws IllegalStateException if {@code maxImages} Images are currently * dequeued, or the ImageWriter format is - * {@link ImageFormat#PRIVATE PRIVATE}. + * {@link ImageFormat#PRIVATE PRIVATE}, or the input + * {@link android.view.Surface Surface} has been abandoned by the + * consumer component that provided the {@link android.view.Surface Surface}. * @see #queueInputImage * @see Image#close */ @@ -254,6 +263,11 @@ public class ImageWriter implements AutoCloseable { * * @param image The Image to be queued back to ImageWriter for future * consumption. + * @throws IllegalStateException if the image was already queued previously, + * or the image was aborted previously, or the input + * {@link android.view.Surface Surface} has been abandoned by the + * consumer component that provided the + * {@link android.view.Surface Surface}. * @see #dequeueInputImage() */ public void queueInputImage(Image image) { @@ -699,7 +713,7 @@ public class ImageWriter implements AutoCloseable { } private void clearSurfacePlanes() { - if (mIsImageValid) { + if (mIsImageValid && mPlanes != null) { for (int i = 0; i < mPlanes.length; i++) { if (mPlanes[i] != null) { mPlanes[i].clearBuffer(); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index a79dd04..5f60891 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -2029,12 +2029,21 @@ final public class MediaCodec { */ public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; + /** + * This indicates that decryption was attempted on a session that is + * not opened, which could be due to a failure to open the session, + * closing the session prematurely, or the session being reclaimed + * by the resource manager. + */ + public static final int ERROR_SESSION_NOT_OPENED = 5; + /** @hide */ @IntDef({ ERROR_NO_KEY, ERROR_KEY_EXPIRED, ERROR_RESOURCE_BUSY, ERROR_INSUFFICIENT_OUTPUT_PROTECTION, + ERROR_SESSION_NOT_OPENED, }) @Retention(RetentionPolicy.SOURCE) public @interface CryptoErrorCode {} diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 166ff38..faeebe6 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -76,6 +76,10 @@ public class Ringtone { .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); + // playback properties, use synchronized with mPlaybackSettingsLock + private boolean mIsLooping = false; + private float mVolume = 1.0f; + private final Object mPlaybackSettingsLock = new Object(); /** {@hide} */ public Ringtone(Context context, boolean allowRemote) { @@ -136,6 +140,52 @@ public class Ringtone { } /** + * @hide + * Sets the player to be looping or non-looping. + * @param looping whether to loop or not + */ + public void setLooping(boolean looping) { + synchronized (mPlaybackSettingsLock) { + mIsLooping = looping; + applyPlaybackProperties_sync(); + } + } + + /** + * @hide + * Sets the volume on this player. + * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0 + * corresponds to no attenuation being applied. + */ + public void setVolume(float volume) { + synchronized (mPlaybackSettingsLock) { + if (volume < 0.0f) { volume = 0.0f; } + if (volume > 1.0f) { volume = 1.0f; } + mVolume = volume; + applyPlaybackProperties_sync(); + } + } + + /** + * Must be called synchronized on mPlaybackSettingsLock + */ + private void applyPlaybackProperties_sync() { + if (mLocalPlayer != null) { + mLocalPlayer.setVolume(mVolume); + mLocalPlayer.setLooping(mIsLooping); + } else if (mAllowRemote && (mRemotePlayer != null)) { + try { + mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping); + } catch (RemoteException e) { + Log.w(TAG, "Problem setting playback properties: ", e); + } + } else { + Log.w(TAG, + "Neither local nor remote player available when applying playback properties"); + } + } + + /** * Returns a human-presentable title for ringtone. Looks in media * content provider. If not in either, uses the filename * @@ -221,6 +271,9 @@ public class Ringtone { try { mLocalPlayer.setDataSource(mContext, mUri); mLocalPlayer.setAudioAttributes(mAudioAttributes); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } mLocalPlayer.prepare(); } catch (SecurityException | IOException e) { @@ -257,8 +310,14 @@ public class Ringtone { } } else if (mAllowRemote && (mRemotePlayer != null)) { final Uri canonicalUri = mUri.getCanonicalUri(); + final boolean looping; + final float volume; + synchronized (mPlaybackSettingsLock) { + looping = mIsLooping; + volume = mVolume; + } try { - mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes); + mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping); } catch (RemoteException e) { if (!playFallbackRingtone()) { Log.w(TAG, "Problem playing ringtone: " + e); @@ -349,6 +408,9 @@ public class Ringtone { afd.getDeclaredLength()); } mLocalPlayer.setAudioAttributes(mAudioAttributes); + synchronized (mPlaybackSettingsLock) { + applyPlaybackProperties_sync(); + } mLocalPlayer.prepare(); startLocalPlayer(); afd.close(); diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 6ac0efb..2364a13 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -202,7 +202,8 @@ public final class MediaSessionManager { Log.w(TAG, "Attempted to add session listener twice, ignoring."); return; } - SessionsChangedWrapper wrapper = new SessionsChangedWrapper(sessionListener, handler); + SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener, + handler); try { mService.addSessionsListener(wrapper.mStub, notificationListener, userId); mListeners.put(sessionListener, wrapper); @@ -229,6 +230,8 @@ public final class MediaSessionManager { mService.removeSessionsListener(wrapper.mStub); } catch (RemoteException e) { Log.e(TAG, "Error in removeOnActiveSessionsChangedListener.", e); + } finally { + wrapper.release(); } } } @@ -317,11 +320,14 @@ public final class MediaSessionManager { public void onActiveSessionsChanged(@Nullable List<MediaController> controllers); } - private final class SessionsChangedWrapper { - private final OnActiveSessionsChangedListener mListener; - private final Handler mHandler; + private static final class SessionsChangedWrapper { + private Context mContext; + private OnActiveSessionsChangedListener mListener; + private Handler mHandler; - public SessionsChangedWrapper(OnActiveSessionsChangedListener listener, Handler handler) { + public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, + Handler handler) { + mContext = context; mListener = listener; mHandler = handler; } @@ -333,17 +339,25 @@ public final class MediaSessionManager { mHandler.post(new Runnable() { @Override public void run() { - ArrayList<MediaController> controllers - = new ArrayList<MediaController>(); - int size = tokens.size(); - for (int i = 0; i < size; i++) { - controllers.add(new MediaController(mContext, tokens.get(i))); + if (mListener != null) { + ArrayList<MediaController> controllers + = new ArrayList<MediaController>(); + int size = tokens.size(); + for (int i = 0; i < size; i++) { + controllers.add(new MediaController(mContext, tokens.get(i))); + } + mListener.onActiveSessionsChanged(controllers); } - mListener.onActiveSessionsChanged(controllers); } }); } } }; + + private void release() { + mContext = null; + mListener = null; + mHandler = null; + } } } diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java index 3641ff5..6ca442c 100644 --- a/media/java/android/mtp/MtpStorage.java +++ b/media/java/android/mtp/MtpStorage.java @@ -53,18 +53,6 @@ public class MtpStorage { return mStorageId; } - /** - * Generates a storage ID for storage of given index. - * Index 0 is for primary external storage - * - * @return the storage ID - */ - public static int getStorageIdForIndex(int index) { - // storage ID is 0x00010001 for primary storage, - // then 0x00020001, 0x00030001, etc. for secondary storages - return ((index + 1) << 16) + 1; - } - /** * Returns the file path for the storage unit's storage in the file system * |