summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioDeviceInfo.java7
-rw-r--r--media/java/android/media/AudioSystem.java10
-rw-r--r--media/java/android/media/IRingtonePlayer.aidl3
-rw-r--r--media/java/android/media/ImageUtils.java84
-rw-r--r--media/java/android/media/ImageWriter.java24
-rw-r--r--media/java/android/media/MediaCodec.java9
-rw-r--r--media/java/android/media/Ringtone.java64
-rw-r--r--media/java/android/media/session/MediaSessionManager.java36
-rw-r--r--media/java/android/mtp/MtpStorage.java12
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
*