diff options
Diffstat (limited to 'media')
42 files changed, 1233 insertions, 354 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index eeac69a..6eaf812 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -57,7 +57,8 @@ import java.util.Iterator; */ public class AudioManager { - private final Context mApplicationContext; + private Context mOriginalContext; + private Context mApplicationContext; private long mVolumeKeyUpTime; private final boolean mUseVolumeKeySounds; private final boolean mUseFixedVolume; @@ -621,14 +622,33 @@ public class AudioManager { * @hide */ public AudioManager(Context context) { - mApplicationContext = context; - mUseVolumeKeySounds = mApplicationContext.getResources().getBoolean( + setContext(context); + mUseVolumeKeySounds = getContext().getResources().getBoolean( com.android.internal.R.bool.config_useVolumeKeySounds); - mUseFixedVolume = mApplicationContext.getResources().getBoolean( + mUseFixedVolume = getContext().getResources().getBoolean( com.android.internal.R.bool.config_useFixedVolume); sAudioPortEventHandler.init(); } + private Context getContext() { + if (mApplicationContext == null) { + setContext(mOriginalContext); + } + if (mApplicationContext != null) { + return mApplicationContext; + } + return mOriginalContext; + } + + private void setContext(Context context) { + mApplicationContext = context.getApplicationContext(); + if (mApplicationContext != null) { + mOriginalContext = null; + } else { + mOriginalContext = context; + } + } + private static IAudioService getService() { if (sService != null) { @@ -663,7 +683,7 @@ public class AudioManager { * or {@link KeyEvent#KEYCODE_MEDIA_AUDIO_TRACK}. */ public void dispatchMediaKeyEvent(KeyEvent keyEvent) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); helper.sendMediaButtonEvent(keyEvent, false); } @@ -709,7 +729,7 @@ public class AudioManager { break; case KeyEvent.KEYCODE_VOLUME_MUTE: if (event.getRepeatCount() == 0) { - MediaSessionLegacyHelper.getHelper(mApplicationContext) + MediaSessionLegacyHelper.getHelper(getContext()) .sendVolumeKeyEvent(event, false); } break; @@ -737,7 +757,7 @@ public class AudioManager { mVolumeKeyUpTime = SystemClock.uptimeMillis(); break; case KeyEvent.KEYCODE_VOLUME_MUTE: - MediaSessionLegacyHelper.getHelper(mApplicationContext) + MediaSessionLegacyHelper.getHelper(getContext()) .sendVolumeKeyEvent(event, false); break; } @@ -783,7 +803,7 @@ public class AudioManager { IAudioService service = getService(); try { service.adjustStreamVolume(streamType, direction, flags, - mApplicationContext.getOpPackageName()); + getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in adjustStreamVolume", e); } @@ -813,7 +833,7 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustVolume(int direction, int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags); } @@ -842,7 +862,7 @@ public class AudioManager { * @see #isVolumeFixed() */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags); } @@ -850,7 +870,7 @@ public class AudioManager { public void setMasterMute(boolean mute, int flags) { IAudioService service = getService(); try { - service.setMasterMute(mute, flags, mApplicationContext.getOpPackageName()); + service.setMasterMute(mute, flags, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setMasterMute", e); } @@ -997,7 +1017,7 @@ public class AudioManager { } IAudioService service = getService(); try { - service.setRingerModeExternal(ringerMode, mApplicationContext.getOpPackageName()); + service.setRingerModeExternal(ringerMode, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setRingerMode", e); } @@ -1018,7 +1038,7 @@ public class AudioManager { public void setStreamVolume(int streamType, int index, int flags) { IAudioService service = getService(); try { - service.setStreamVolume(streamType, index, flags, mApplicationContext.getOpPackageName()); + service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setStreamVolume", e); } @@ -1331,7 +1351,7 @@ public class AudioManager { * @see #startBluetoothSco() */ public boolean isBluetoothScoAvailableOffCall() { - return mApplicationContext.getResources().getBoolean( + return getContext().getResources().getBoolean( com.android.internal.R.bool.config_bluetooth_sco_off_call); } @@ -1384,7 +1404,7 @@ public class AudioManager { IAudioService service = getService(); try { service.startBluetoothSco(mICallBack, - mApplicationContext.getApplicationInfo().targetSdkVersion); + getContext().getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { Log.e(TAG, "Dead object in startBluetoothSco", e); } @@ -1532,7 +1552,7 @@ public class AudioManager { public void setMicrophoneMute(boolean on){ IAudioService service = getService(); try { - service.setMicrophoneMute(on, mApplicationContext.getOpPackageName()); + service.setMicrophoneMute(on, getContext().getOpPackageName()); } catch (RemoteException e) { Log.e(TAG, "Dead object in setMicrophoneMute", e); } @@ -1963,7 +1983,7 @@ public class AudioManager { * Settings has an in memory cache, so this is fast. */ private boolean querySoundEffectsEnabled(int user) { - return Settings.System.getIntForUser(mApplicationContext.getContentResolver(), + return Settings.System.getIntForUser(getContext().getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0; } @@ -2375,7 +2395,7 @@ public class AudioManager { try { status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack, mAudioFocusDispatcher, getIdForAudioFocusListener(l), - mApplicationContext.getOpPackageName() /* package name */, flags, + getContext().getOpPackageName() /* package name */, flags, ap != null ? ap.cb() : null); } catch (RemoteException e) { Log.e(TAG, "Can't call requestAudioFocus() on AudioService:", e); @@ -2400,7 +2420,7 @@ public class AudioManager { .setInternalLegacyStreamType(streamType).build(), durationHint, mICallBack, null, AudioSystem.IN_VOICE_COMM_FOCUS_ID, - mApplicationContext.getOpPackageName(), + getContext().getOpPackageName(), AUDIOFOCUS_FLAG_LOCK, null /* policy token */); } catch (RemoteException e) { @@ -2469,7 +2489,7 @@ public class AudioManager { if (eventReceiver == null) { return; } - if (!eventReceiver.getPackageName().equals(mApplicationContext.getPackageName())) { + if (!eventReceiver.getPackageName().equals(getContext().getPackageName())) { Log.e(TAG, "registerMediaButtonEventReceiver() error: " + "receiver and context package names don't match"); return; @@ -2478,7 +2498,7 @@ public class AudioManager { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); // the associated intent will be handled by the component being registered mediaButtonIntent.setComponent(eventReceiver); - PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext, + PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); registerMediaButtonIntent(pi, eventReceiver); } @@ -2512,8 +2532,8 @@ public class AudioManager { Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter"); return; } - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext); - helper.addMediaButtonListener(pi, eventReceiver, mApplicationContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); + helper.addMediaButtonListener(pi, eventReceiver, getContext()); } /** @@ -2531,7 +2551,7 @@ public class AudioManager { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); // the associated intent will be handled by the component being registered mediaButtonIntent.setComponent(eventReceiver); - PendingIntent pi = PendingIntent.getBroadcast(mApplicationContext, + PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0/*requestCode, ignored*/, mediaButtonIntent, 0/*flags*/); unregisterMediaButtonIntent(pi); } @@ -2554,7 +2574,7 @@ public class AudioManager { * @hide */ public void unregisterMediaButtonIntent(PendingIntent pi) { - MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mApplicationContext); + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); helper.removeMediaButtonListener(pi); } @@ -2571,7 +2591,7 @@ public class AudioManager { if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) { return; } - rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext)); + rcClient.registerWithSession(MediaSessionLegacyHelper.getHelper(getContext())); } /** @@ -2586,7 +2606,7 @@ public class AudioManager { if ((rcClient == null) || (rcClient.getRcMediaIntent() == null)) { return; } - rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(mApplicationContext)); + rcClient.unregisterWithSession(MediaSessionLegacyHelper.getHelper(getContext())); } /** @@ -3280,7 +3300,7 @@ public class AudioManager { */ public void setRingerModeInternal(int ringerMode) { try { - getService().setRingerModeInternal(ringerMode, mApplicationContext.getOpPackageName()); + getService().setRingerModeInternal(ringerMode, getContext().getOpPackageName()); } catch (RemoteException e) { Log.w(TAG, "Error calling setRingerModeInternal", e); } diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index abb4257..a721923 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -51,5 +51,7 @@ public abstract class AudioManagerInternal { /** Called when internal ringer mode is evaluated, returns the new external ringer mode */ int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, int ringerModeExternal, VolumePolicy policy); + + boolean canVolumeDownEnterSilent(); } } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 201a796..472da02 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -26,6 +26,7 @@ import java.util.Iterator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.app.ActivityThread; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -378,7 +379,7 @@ public class AudioRecord int initResult = native_setup( new WeakReference<AudioRecord>(this), mAudioAttributes, mSampleRate, mChannelMask, mChannelIndexMask, mAudioFormat, mNativeBufferSizeInBytes, - session); + session, ActivityThread.currentOpPackageName()); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED @@ -1321,7 +1322,6 @@ public class AudioRecord return native_set_pos_update_period(periodInFrames); } - //-------------------------------------------------------------------------- // Explicit Routing //-------------------- @@ -1451,7 +1451,7 @@ public class AudioRecord private native final int native_setup(Object audiorecord_this, Object /*AudioAttributes*/ attributes, int sampleRate, int channelMask, int channelIndexMask, int audioFormat, - int buffSizeInBytes, int[] sessionId); + int buffSizeInBytes, int[] sessionId, String opPackageName); // TODO remove: implementation calls directly into implementation of native_release() private native final void native_finalize(); @@ -1499,5 +1499,4 @@ public class AudioRecord private static void loge(String msg) { Log.e(TAG, msg); } - } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 25e6594..3dae543 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -19,6 +19,7 @@ package android.media; import android.content.Context; import android.content.pm.PackageManager; import android.media.audiopolicy.AudioMix; +import android.util.Log; import java.util.ArrayList; @@ -32,6 +33,7 @@ import java.util.ArrayList; */ public class AudioSystem { + private static final String TAG = "AudioSystem"; /* These values must be kept in sync with system/audio.h */ /* * If these are modified, please also update Settings.System.VOLUME_SETTINGS @@ -224,6 +226,48 @@ public class AudioSystem } } + /** + * Handles events for the audio policy manager about dynamic audio policies + * @see android.media.audiopolicy.AudioPolicy + */ + public interface DynamicPolicyCallback + { + void onDynamicPolicyMixStateUpdate(String regId, int state); + } + + //keep in sync with include/media/AudioPolicy.h + private final static int DYNAMIC_POLICY_EVENT_MIX_STATE_UPDATE = 0; + + private static DynamicPolicyCallback sDynPolicyCallback; + + public static void setDynamicPolicyCallback(DynamicPolicyCallback cb) + { + synchronized (AudioSystem.class) { + sDynPolicyCallback = cb; + native_register_dynamic_policy_callback(); + } + } + + private static void dynamicPolicyCallbackFromNative(int event, String regId, int val) + { + DynamicPolicyCallback cb = null; + synchronized (AudioSystem.class) { + if (sDynPolicyCallback != null) { + cb = sDynPolicyCallback; + } + } + if (cb != null) { + switch(event) { + case DYNAMIC_POLICY_EVENT_MIX_STATE_UPDATE: + cb.onDynamicPolicyMixStateUpdate(regId, val); + break; + default: + Log.e(TAG, "dynamicPolicyCallbackFromNative: unknown event " + event); + } + } + } + + /* * Error codes used by public APIs (AudioTrack, AudioRecord, AudioManager ...) * Must be kept in sync with frameworks/base/core/jni/android_media_AudioErrors.h @@ -580,6 +624,9 @@ public class AudioSystem public static native int listAudioPatches(ArrayList<AudioPatch> patches, int[] generation); public static native int setAudioPortConfig(AudioPortConfig config); + // declare this instance as having a dynamic policy callback handler + private static native final void native_register_dynamic_policy_callback(); + // must be kept in sync with value in include/system/audio.h public static final int AUDIO_HW_SYNC_INVALID = 0; diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 6f1fd24..cb05cc5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -920,13 +920,7 @@ public class AudioTrack * @throws IllegalStateException if track is not initialized. */ public @NonNull PlaybackSettings getPlaybackSettings() { - float[] floatArray = new float[2]; - int[] intArray = new int[2]; - native_get_playback_settings(floatArray, intArray); - return new PlaybackSettings() - .setSpeed(floatArray[0]) - .setPitch(floatArray[1]) - .setAudioFallbackMode(intArray[0]); + return native_get_playback_settings(); } /** @@ -1340,21 +1334,7 @@ public class AudioTrack if (settings == null) { throw new IllegalArgumentException("settings is null"); } - float[] floatArray; - int[] intArray; - try { - floatArray = new float[] { - settings.getSpeed(), - settings.getPitch(), - }; - intArray = new int[] { - settings.getAudioFallbackMode(), - PlaybackSettings.AUDIO_STRETCH_MODE_DEFAULT, - }; - } catch (IllegalStateException e) { - throw new IllegalArgumentException(e); - } - native_set_playback_settings(floatArray, intArray); + native_set_playback_settings(settings); } @@ -2353,14 +2333,8 @@ public class AudioTrack private native final int native_set_playback_rate(int sampleRateInHz); private native final int native_get_playback_rate(); - // floatArray must be a non-null array of length >= 2 - // [0] is speed - // [1] is pitch - // intArray must be a non-null array of length >= 2 - // [0] is audio fallback mode - // [1] is audio stretch mode - private native final void native_set_playback_settings(float[] floatArray, int[] intArray); - private native final void native_get_playback_settings(float[] floatArray, int[] intArray); + private native final void native_set_playback_settings(@NonNull PlaybackSettings settings); + private native final @NonNull PlaybackSettings native_get_playback_settings(); private native final int native_set_marker_pos(int marker); private native final int native_get_marker_pos(); diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index d47bedc..6f7b583 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -664,11 +664,11 @@ final public class MediaCodec { if (!mHasSurface) { throw new IllegalStateException("codec was not configured for an output surface"); } - - // TODO implement this - throw new IllegalArgumentException("codec does not support this surface"); + native_setSurface(surface); } + private native void native_setSurface(@NonNull Surface surface); + /** * Create a persistent input surface that can be used with codecs that normally have an input * surface, such as video encoders. A persistent input can be reused by subsequent @@ -681,12 +681,20 @@ final public class MediaCodec { */ @NonNull public static Surface createPersistentInputSurface() { - // TODO implement this - return new PersistentSurface(); + return native_createPersistentInputSurface(); } static class PersistentSurface extends Surface { - PersistentSurface() {} + @SuppressWarnings("unused") + PersistentSurface() {} // used by native + + @Override + public void release() { + native_releasePersistentInputSurface(this); + super.release(); + } + + private long mPersistentObject; }; /** @@ -700,9 +708,17 @@ final public class MediaCodec { * {@link #createPersistentInputSurface}. */ public void usePersistentInputSurface(@NonNull Surface surface) { - throw new IllegalArgumentException("not implemented"); + if (!(surface instanceof PersistentSurface)) { + throw new IllegalArgumentException("not a PersistentSurface"); + } + native_usePersistentInputSurface(surface); } + @NonNull + private static native final PersistentSurface native_createPersistentInputSurface(); + private static native final void native_releasePersistentInputSurface(@NonNull Surface surface); + private native final void native_usePersistentInputSurface(@NonNull Surface surface); + private native final void native_setCallback(@Nullable Callback cb); private native final void native_configure( diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 974c9af..89d419a 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1733,6 +1733,72 @@ public final class MediaCodecInfo { maxBlocks, maxBlocksPerSecond, 16 /* blockWidth */, 16 /* blockHeight */, 1 /* widthAlignment */, 1 /* heightAlignment */); + } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2)) { + 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.MPEG2ProfileSimple: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 15000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2ProfileMain: + switch (profileLevel.level) { + case CodecProfileLevel.MPEG2LevelLL: + FR = 30; W = 22; H = 18; MBPS = 11880; FS = 396; BR = 4000; break; + case CodecProfileLevel.MPEG2LevelML: + FR = 30; W = 45; H = 36; MBPS = 48600; FS = 1620; BR = 15000; break; + case CodecProfileLevel.MPEG2LevelH14: + FR = 60; W = 90; H = 68; MBPS = 367200; FS = 6120; BR = 60000; break; + case CodecProfileLevel.MPEG2LevelHL: + FR = 60; W = 120; H = 68; MBPS = 489600; FS = 8160; BR = 80000; break; + default: + Log.w(TAG, "Unrecognized profile/level " + + profileLevel.profile + "/" + + profileLevel.level + " for " + mime); + errors |= ERROR_UNRECOGNIZED; + } + break; + case CodecProfileLevel.MPEG2Profile422: + case CodecProfileLevel.MPEG2ProfileSNR: + case CodecProfileLevel.MPEG2ProfileSpatial: + case CodecProfileLevel.MPEG2ProfileHigh: + 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); + } + 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_MPEG4)) { int maxWidth = 11, maxHeight = 9, maxRate = 15; maxBlocks = 99; @@ -2343,6 +2409,20 @@ public final class MediaCodecInfo { public static final int MPEG4Level4a = 0x40; public static final int MPEG4Level5 = 0x80; + // from OMX_VIDEO_MPEG2PROFILETYPE + public static final int MPEG2ProfileSimple = 0x00; + public static final int MPEG2ProfileMain = 0x01; + public static final int MPEG2Profile422 = 0x02; + public static final int MPEG2ProfileSNR = 0x03; + public static final int MPEG2ProfileSpatial = 0x04; + public static final int MPEG2ProfileHigh = 0x05; + + // from OMX_VIDEO_MPEG2LEVELTYPE + public static final int MPEG2LevelLL = 0x00; + public static final int MPEG2LevelML = 0x01; + public static final int MPEG2LevelH14 = 0x02; + public static final int MPEG2LevelHL = 0x03; + // from OMX_AUDIO_AACPROFILETYPE public static final int AACObjectMain = 1; public static final int AACObjectLC = 2; diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index a33fa59..6ec10c7 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -42,6 +42,7 @@ import android.system.OsConstants; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; +import android.widget.VideoView; import android.graphics.SurfaceTexture; import android.media.AudioManager; import android.media.MediaFormat; @@ -1953,21 +1954,16 @@ public class MediaPlayer implements SubtitleController.Listener TrackInfo(Parcel in) { mTrackType = in.readInt(); - // TODO: parcel in the full MediaFormat + // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat + // even for audio/video tracks, meaning we only set the mime and language. + String mime = in.readString(); String language = in.readString(); + mFormat = MediaFormat.createSubtitleFormat(mime, language); - if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT) { - mFormat = MediaFormat.createSubtitleFormat( - MEDIA_MIMETYPE_TEXT_SUBRIP, language); - } else if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { - String mime = in.readString(); - mFormat = MediaFormat.createSubtitleFormat(mime, language); + if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) { mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt()); mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt()); mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt()); - } else { - mFormat = new MediaFormat(); - mFormat.setString(MediaFormat.KEY_LANGUAGE, language); } } @@ -2126,6 +2122,43 @@ public class MediaPlayer implements SubtitleController.Listener mSubtitleController.setAnchor(anchor); } + /** + * The private version of setSubtitleAnchor is used internally to set mSubtitleController if + * necessary when clients don't provide their own SubtitleControllers using the public version + * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one). + */ + private synchronized void setSubtitleAnchor() { + if (mSubtitleController == null) { + final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread"); + thread.start(); + Handler handler = new Handler(thread.getLooper()); + handler.post(new Runnable() { + @Override + public void run() { + Context context = ActivityThread.currentApplication(); + mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer.this); + mSubtitleController.setAnchor(new Anchor() { + @Override + public void setSubtitleWidget(RenderingWidget subtitleWidget) { + } + + @Override + public Looper getSubtitleLooper() { + return Looper.getMainLooper(); + } + }); + thread.getLooper().quitSafely(); + } + }); + try { + thread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "failed to join SetSubtitleAnchorThread"); + } + } + } + private final Object mInbandSubtitleLock = new Object(); private SubtitleTrack[] mInbandSubtitleTracks; private int mSelectedSubtitleTrackIndex = -1; @@ -2386,24 +2419,14 @@ public class MediaPlayer implements SubtitleController.Listener fFormat.setString(MediaFormat.KEY_MIME, mime); fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); - Context context = ActivityThread.currentApplication(); // A MediaPlayer created by a VideoView should already have its mSubtitleController set. if (mSubtitleController == null) { - mSubtitleController = new SubtitleController(context, mTimeProvider, this); - mSubtitleController.setAnchor(new Anchor() { - @Override - public void setSubtitleWidget(RenderingWidget subtitleWidget) { - } - - @Override - public Looper getSubtitleLooper() { - return Looper.getMainLooper(); - } - }); + setSubtitleAnchor(); } if (!mSubtitleController.hasRendererFor(fFormat)) { // test and add not atomic + Context context = ActivityThread.currentApplication(); mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); } final SubtitleTrack track = mSubtitleController.addTrack(fFormat); diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 78fd9f0..206c171 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -16,6 +16,7 @@ package android.media; +import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.ActivityThread; import android.hardware.Camera; @@ -111,7 +112,8 @@ public class MediaRecorder /* Native setup requires a weak reference to our object. * It's easier to create it here than in C++. */ - native_setup(new WeakReference<MediaRecorder>(this), packageName); + native_setup(new WeakReference<MediaRecorder>(this), packageName, + ActivityThread.currentOpPackageName()); } /** @@ -141,22 +143,28 @@ public class MediaRecorder /** * Configures the recorder to use a persistent surface when using SURFACE video source. - * <p> May only be called after {@link #prepare} in lieu of {@link #getSurface}. - * Frames rendered to the Surface before {@link #start} will be discarded.</p> + * <p> May only be called before {@link #prepare}. If called, {@link #getSurface} should + * not be used and will throw IllegalStateException. Frames rendered to the Surface + * before {@link #start} will be discarded.</p> * @param surface a persistent input surface created by * {@link MediaCodec#createPersistentInputSurface} - * @throws IllegalStateException if it is called before {@link #prepare}, after - * {@link #stop}, or is called when VideoSource is not set to SURFACE. + * @throws IllegalStateException if it is called after {@link #prepare} and before + * {@link #stop}. * @throws IllegalArgumentException if the surface was not created by * {@link MediaCodec#createPersistentInputSurface}. * @see MediaCodec#createPersistentInputSurface * @see MediaRecorder.VideoSource */ - public void usePersistentSurface(Surface surface) { - throw new IllegalArgumentException("not implemented"); + public void usePersistentSurface(@NonNull Surface surface) { + if (!(surface instanceof MediaCodec.PersistentSurface)) { + throw new IllegalArgumentException("not a PersistentSurface"); + } + native_usePersistentSurface(surface); } + private native final void native_usePersistentSurface(@NonNull Surface surface); + /** * Sets a Surface to show a preview of recorded media (video). Calls this * before prepare() to make sure that the desirable preview display is @@ -1080,7 +1088,7 @@ public class MediaRecorder private static native final void native_init(); private native final void native_setup(Object mediarecorder_this, - String clientName) throws IllegalStateException; + String clientName, String opPackageName) throws IllegalStateException; private native final void native_finalize(); diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java index dc6760d..a5b0d39 100644 --- a/media/java/android/media/MediaSync.java +++ b/media/java/android/media/MediaSync.java @@ -199,6 +199,7 @@ final public class MediaSync { private final Object mAudioLock = new Object(); private AudioTrack mAudioTrack = null; private List<AudioBuffer> mAudioBuffers = new LinkedList<AudioBuffer>(); + // this is only used for paused/running decisions, so it is not affected by clock drift private float mPlaybackRate = 0.0f; private long mNativeContext; @@ -311,13 +312,13 @@ final public class MediaSync { * @throws IllegalArgumentException if the surface has been released, is invalid, * or can not be connected. * @throws IllegalStateException if setting the surface is not supported, e.g. - * not in the Initialized state, or another surface has already been configured. + * not in the Initialized state, or another surface has already been set. */ public void setSurface(@Nullable Surface surface) { - native_configureSurface(surface); + native_setSurface(surface); } - private native final void native_configureSurface(@Nullable Surface surface); + private native final void native_setSurface(@Nullable Surface surface); /** * Sets the audio track for MediaSync. @@ -327,21 +328,17 @@ final public class MediaSync { * @param audioTrack Specify an AudioTrack through which to render the audio data. * @throws IllegalArgumentException if the audioTrack has been released, or is invalid. * @throws IllegalStateException if setting the audio track is not supported, e.g. - * not in the Initialized state, or another audio track has already been configured. + * not in the Initialized state, or another audio track has already been set. */ public void setAudioTrack(@Nullable AudioTrack audioTrack) { - // AudioTrack has sanity check for configured sample rate. - int nativeSampleRateInHz = (audioTrack == null ? 0 : audioTrack.getSampleRate()); - - native_configureAudioTrack(audioTrack, nativeSampleRateInHz); + native_setAudioTrack(audioTrack); mAudioTrack = audioTrack; if (audioTrack != null && mAudioThread == null) { createAudioThread(); } } - private native final void native_configureAudioTrack( - @Nullable AudioTrack audioTrack, int nativeSampleRateInHz); + private native final void native_setAudioTrack(@Nullable AudioTrack audioTrack); /** * Requests a Surface to use as the input. This may only be called after @@ -349,7 +346,7 @@ final public class MediaSync { * <p> * The application is responsible for calling release() on the Surface when * done. - * @throws IllegalStateException if not configured, or another input surface has + * @throws IllegalStateException if not set, or another input surface has * already been created. */ @NonNull @@ -459,36 +456,11 @@ final public class MediaSync { * @throws IllegalArgumentException if the settings are not supported. */ public void setPlaybackSettings(@NonNull PlaybackSettings settings) { - float rate; - try { - rate = settings.getSpeed(); - - // rate is specified - if (mAudioTrack != null) { - try { - if (rate == 0.0) { - mAudioTrack.pause(); - } else { - mAudioTrack.setPlaybackSettings(settings); - mAudioTrack.play(); - } - } catch (IllegalStateException e) { - throw e; - } - } - - synchronized(mAudioLock) { - mPlaybackRate = rate; - } - if (mPlaybackRate != 0.0 && mAudioThread != null) { - postRenderAudio(0); - } - native_setPlaybackRate(mPlaybackRate); - } catch (IllegalStateException e) { - // rate is not specified; still, propagate settings to audio track - if (mAudioTrack != null) { - mAudioTrack.setPlaybackSettings(settings); - } + synchronized(mAudioLock) { + mPlaybackRate = native_setPlaybackSettings(settings);; + } + if (mPlaybackRate != 0.0 && mAudioThread != null) { + postRenderAudio(0); } } @@ -501,18 +473,9 @@ final public class MediaSync { * been initialized. */ @NonNull - public PlaybackSettings getPlaybackSettings() { - if (mAudioTrack != null) { - return mAudioTrack.getPlaybackSettings(); - } else { - PlaybackSettings settings = new PlaybackSettings(); - settings.allowDefaults(); - settings.setSpeed(mPlaybackRate); - return settings; - } - } + public native PlaybackSettings getPlaybackSettings(); - private native final void native_setPlaybackRate(float rate); + private native float native_setPlaybackSettings(@NonNull PlaybackSettings settings); /** * Sets A/V sync mode. @@ -523,7 +486,16 @@ final public class MediaSync { * initialized. * @throws IllegalArgumentException if settings are not supported. */ - public native void setSyncSettings(@NonNull SyncSettings settings); + public void setSyncSettings(@NonNull SyncSettings settings) { + synchronized(mAudioLock) { + mPlaybackRate = native_setSyncSettings(settings);; + } + if (mPlaybackRate != 0.0 && mAudioThread != null) { + postRenderAudio(0); + } + } + + private native float native_setSyncSettings(@NonNull SyncSettings settings); /** * Gets the A/V sync mode. @@ -598,7 +570,7 @@ final public class MediaSync { * @param sizeInBytes number of bytes to queue. * @param presentationTimeUs the presentation timestamp in microseconds for the first frame * in the buffer. - * @throws IllegalStateException if audio track is not configured or internal configureation + * @throws IllegalStateException if audio track is not set or internal configureation * has not been done correctly. */ public void queueAudio( @@ -606,7 +578,7 @@ final public class MediaSync { long presentationTimeUs) { if (mAudioTrack == null || mAudioThread == null) { throw new IllegalStateException( - "AudioTrack is NOT configured or audio thread is not created"); + "AudioTrack is NOT set or audio thread is not created"); } synchronized(mAudioLock) { diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java index 4e937a5..5add65a 100644 --- a/media/java/android/media/RemoteDisplay.java +++ b/media/java/android/media/RemoteDisplay.java @@ -37,17 +37,19 @@ public final class RemoteDisplay { private final CloseGuard mGuard = CloseGuard.get(); private final Listener mListener; private final Handler mHandler; + private final String mOpPackageName; private long mPtr; - private native long nativeListen(String iface); + private native long nativeListen(String iface, String opPackageName); private native void nativeDispose(long ptr); private native void nativePause(long ptr); private native void nativeResume(long ptr); - private RemoteDisplay(Listener listener, Handler handler) { + private RemoteDisplay(Listener listener, Handler handler, String opPackageName) { mListener = listener; mHandler = handler; + mOpPackageName = opPackageName; } @Override @@ -66,7 +68,8 @@ public final class RemoteDisplay { * @param listener The listener to invoke when displays are connected or disconnected. * @param handler The handler on which to invoke the listener. */ - public static RemoteDisplay listen(String iface, Listener listener, Handler handler) { + public static RemoteDisplay listen(String iface, Listener listener, Handler handler, + String opPackageName) { if (iface == null) { throw new IllegalArgumentException("iface must not be null"); } @@ -77,7 +80,7 @@ public final class RemoteDisplay { throw new IllegalArgumentException("handler must not be null"); } - RemoteDisplay display = new RemoteDisplay(listener, handler); + RemoteDisplay display = new RemoteDisplay(listener, handler, opPackageName); display.startListening(iface); return display; } @@ -113,7 +116,7 @@ public final class RemoteDisplay { } private void startListening(String iface) { - mPtr = nativeListen(iface); + mPtr = nativeListen(iface, mOpPackageName); if (mPtr == 0) { throw new IllegalStateException("Could not start listening for " + "remote display connection on \"" + iface + "\""); diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index a8b9686..b94a7e6 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -18,6 +18,7 @@ package android.media.audiofx; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.app.ActivityThread; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -395,7 +396,7 @@ public class AudioEffect { // native initialization int initResult = native_setup(new WeakReference<AudioEffect>(this), type.toString(), uuid.toString(), priority, audioSession, id, - desc); + desc, ActivityThread.currentOpPackageName()); if (initResult != SUCCESS && initResult != ALREADY_EXISTS) { Log.e(TAG, "Error code " + initResult + " when initializing AudioEffect."); @@ -1217,7 +1218,8 @@ public class AudioEffect { private static native final void native_init(); private native final int native_setup(Object audioeffect_this, String type, - String uuid, int priority, int audioSession, int[] id, Object[] desc); + String uuid, int priority, int audioSession, int[] id, Object[] desc, + String opPackageName); private native final void native_finalize(); diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 24c74ac..0fe7246 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -16,6 +16,7 @@ package android.media.audiofx; +import android.app.ActivityThread; import android.util.Log; import java.lang.ref.WeakReference; import android.os.Handler; @@ -206,7 +207,8 @@ public class Visualizer { synchronized (mStateLock) { mState = STATE_UNINITIALIZED; // native initialization - int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id); + int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id, + ActivityThread.currentOpPackageName()); if (result != SUCCESS && result != ALREADY_EXISTS) { Log.e(TAG, "Error code "+result+" when initializing Visualizer."); switch (result) { @@ -716,7 +718,8 @@ public class Visualizer { private native final int native_setup(Object audioeffect_this, int audioSession, - int[] id); + int[] id, + String opPackageName); private native final void native_finalize(); @@ -765,6 +768,5 @@ public class Visualizer { } } - } diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 6aa4d8a..4ffac6d 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -36,20 +36,30 @@ public class AudioMix { private int mRouteFlags; private String mRegistrationId; private int mMixType = MIX_TYPE_INVALID; - private int mMixState = MIX_STATE_DISABLED; + int mMixState = MIX_STATE_DISABLED; + int mCallbackFlags; /** * All parameters are guaranteed valid through the Builder. */ - private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags) { + private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags) { mRule = rule; mFormat = format; mRouteFlags = routeFlags; mRegistrationId = null; mMixType = rule.getTargetMixType(); + mCallbackFlags = callbackFlags; } - // ROUTE_FLAG_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h + // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined + // in frameworks/av/include/media/AudioPolicy.h + /** @hide */ + public final static int CALLBACK_FLAG_NOTIFY_ACTIVITY = 0x1; + // when adding new MIX_FLAG_* flags, add them to this mask of authorized masks: + private final static int CALLBACK_FLAGS_ALL = CALLBACK_FLAG_NOTIFY_ACTIVITY; + + // ROUTE_FLAG_* values: keep in sync with MIX_ROUTE_FLAG_* values defined + // in frameworks/av/include/media/AudioPolicy.h /** * An audio mix behavior where the output of the mix is sent to the original destination of * the audio signal, i.e. an output device for an output mix, or a recording for an input mix. @@ -161,6 +171,7 @@ public class AudioMix { private AudioMixingRule mRule = null; private AudioFormat mFormat = null; private int mRouteFlags = 0; + private int mCallbackFlags = 0; /** * @hide @@ -199,6 +210,22 @@ public class AudioMix { } /** + * @hide + * Only used by AudioPolicyConfig, not a public API. + * @param callbackFlags which callbacks are called from native + * @return the same Builder instance. + * @throws IllegalArgumentException + */ + public Builder setCallbackFlags(int flags) throws IllegalArgumentException { + if ((flags != 0) && ((flags & CALLBACK_FLAGS_ALL) == 0)) { + throw new IllegalArgumentException("Illegal callback flags 0x" + + Integer.toHexString(flags).toUpperCase()); + } + mCallbackFlags = flags; + return this; + } + + /** * Sets the {@link AudioFormat} for the mix. * @param format a non-null {@link AudioFormat} instance. * @return the same Builder instance. @@ -256,7 +283,7 @@ public class AudioMix { } mFormat = new AudioFormat.Builder().setSampleRate(rate).build(); } - return new AudioMix(mRule, mFormat, mRouteFlags); + return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags); } } } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index f128044..423b467 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -189,6 +189,12 @@ public class AudioPolicy { @SystemApi public AudioPolicy build() { + if (mStatusListener != null) { + // the AudioPolicy status listener includes updates on each mix activity state + for (AudioMix mix : mMixes) { + mix.mCallbackFlags |= AudioMix.CALLBACK_FLAG_NOTIFY_ACTIVITY; + } + } return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, mFocusListener, mStatusListener); } @@ -432,6 +438,18 @@ public class AudioPolicy { + afi.getClientId() + "wasNotified=" + wasNotified); } } + + public void notifyMixStateUpdate(String regId, int state) { + for (AudioMix mix : mConfig.getMixes()) { + if (mix.getRegistration().equals(regId)) { + mix.mMixState = state; + sendMsg(MSG_MIX_STATE_UPDATE, mix, 0/*ignored*/); + if (DEBUG) { + Log.v(TAG, "notifyMixStateUpdate: regId=" + regId + " state=" + state); + } + } + } + } }; //================================================== @@ -440,6 +458,7 @@ public class AudioPolicy { private final static int MSG_POLICY_STATUS_CHANGE = 0; private final static int MSG_FOCUS_GRANT = 1; private final static int MSG_FOCUS_LOSS = 2; + private final static int MSG_MIX_STATE_UPDATE = 3; private class EventHandler extends Handler { public EventHandler(AudioPolicy ap, Looper looper) { @@ -464,6 +483,11 @@ public class AudioPolicy { (AudioFocusInfo) msg.obj, msg.arg1 != 0); } break; + case MSG_MIX_STATE_UPDATE: + if (mStatusListener != null) { + mStatusListener.onMixStateUpdate((AudioMix) msg.obj); + } + break; default: Log.e(TAG, "Unknown event " + msg.what); } diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index 917e07b..252f5f4 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -59,6 +59,10 @@ public class AudioPolicyConfig implements Parcelable { mMixes.add(mix); } + public ArrayList<AudioMix> getMixes() { + return mMixes; + } + @Override public int hashCode() { return Objects.hash(mMixes); @@ -75,6 +79,8 @@ public class AudioPolicyConfig implements Parcelable { for (AudioMix mix : mMixes) { // write mix route flags dest.writeInt(mix.getRouteFlags()); + // write callback flags + dest.writeInt(mix.mCallbackFlags); // write mix format dest.writeInt(mix.getFormat().getSampleRate()); dest.writeInt(mix.getFormat().getEncoding()); @@ -96,6 +102,8 @@ public class AudioPolicyConfig implements Parcelable { // read mix route flags int routeFlags = in.readInt(); mixBuilder.setRouteFlags(routeFlags); + // read callback flags + mixBuilder.setCallbackFlags(in.readInt()); // read mix format int sampleRate = in.readInt(); int encoding = in.readInt(); diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl index c777c58..ad8af15 100644 --- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl +++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl @@ -25,4 +25,7 @@ oneway interface IAudioPolicyCallback { // callbacks for audio focus void notifyAudioFocusGrant(in AudioFocusInfo afi, int requestResult); void notifyAudioFocusLoss(in AudioFocusInfo afi, boolean wasNotified); + + // callback for mix activity status update + void notifyMixStateUpdate(in String regId, int state); } diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index af108eb..35374ed 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -69,6 +69,13 @@ public final class MidiDeviceInfo implements Parcelable { public static final String PROPERTY_PRODUCT = "product"; /** + * Bundle key for the device's version property. + * Used with the {@link android.os.Bundle} returned by {@link #getProperties} + * Matches the USB device version number for USB MIDI devices. + */ + public static final String PROPERTY_VERSION = "version"; + + /** * Bundle key for the device's serial number property. * Used with the {@link android.os.Bundle} returned by {@link #getProperties} * Matches the USB device serial number for USB MIDI devices. diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java index d069075..f8799d4 100644 --- a/media/java/android/media/midi/MidiReceiver.java +++ b/media/java/android/media/midi/MidiReceiver.java @@ -23,6 +23,10 @@ import java.io.IOException; */ abstract public class MidiReceiver { /** + * Although public, this method should be considered a private implementation + * detail. Client code should call {@link #send} or {@link #sendWithTimestamp} + * instead. + * * Called to pass MIDI data to the receiver. * May fail if count exceeds {@link #getMaxMessageSize}. * diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index a3442e3..95aaa7f 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -41,8 +41,9 @@ import com.android.internal.os.SomeArgs; public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback { private static final String TAG = "TvInputSessionWrapper"; - private static final int MESSAGE_HANDLING_DURATION_THRESHOLD_MILLIS = 50; - private static final int MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS = 2000; + private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 50; + private static final int EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS = 2000; + private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000; private static final int DO_RELEASE = 1; private static final int DO_SET_MAIN = 2; @@ -184,14 +185,18 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } } long duration = System.currentTimeMillis() - startTime; - if (duration > MESSAGE_HANDLING_DURATION_THRESHOLD_MILLIS) { + if (duration > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) { Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration=" + duration + "ms)"); - if (msg.what == DO_TUNE && duration > MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS) { + if (msg.what == DO_TUNE && duration > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) { throw new RuntimeException("Too much time to handle tune request. (" + duration - + "ms > " + MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS + "ms) " + + "ms > " + EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS + "ms) " + "Consider handling the tune request in a separate thread."); } + if (duration > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) { + throw new RuntimeException("Too much time to handle a request. (type=" + msg.what + + ", " + duration + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms)."); + } } } diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java index 966e41a..043b80e 100644 --- a/media/java/android/media/tv/TvContentRating.java +++ b/media/java/android/media/tv/TvContentRating.java @@ -171,6 +171,14 @@ import java.util.Objects; * <td>TV content rating system for Brazil</td> * </tr> * <tr> + * <td>CA_TV_EN</td> + * <td>TV content rating system for Canada (English)</td> + * </tr> + * <tr> + * <td>CA_TV_FR</td> + * <td>TV content rating system for Canada (French)</td> + * </tr> + * <tr> * <td>DVB</td> * <td>DVB content rating system</td> * </tr> @@ -195,6 +203,10 @@ import java.util.Objects; * <td>TV content rating system for Singapore</td> * </tr> * <tr> + * <td>US_MV</td> + * <td>Movie content rating system for the United States</td> + * </tr> + * <tr> * <td>US_TV</td> * <td>TV content rating system for the United States</td> * </tr> @@ -290,6 +302,60 @@ import java.util.Objects; * <td>Content suitable for viewers over the age of 18</td> * </tr> * <tr> + * <td valign="top" rowspan="7">CA_TV_EN</td> + * <td>CA_TV_EN_EXEMPT</td> + * <td>Exempt from ratings</td> + * </tr> + * <tr> + * <td>CA_TV_EN_C</td> + * <td>Suitable for children ages 2–7</td> + * </tr> + * <tr> + * <td>CA_TV_EN_C8</td> + * <td>Suitable for children ages 8 and older</td> + * </tr> + * <tr> + * <td>CA_TV_EN_G</td> + * <td>Suitable for the entire family</td> + * </tr> + * <tr> + * <td>CA_TV_EN_PG</td> + * <td>May contain moderate violence, profanity, nudity, and sexual references</td> + * </tr> + * <tr> + * <td>CA_TV_EN_14</td> + * <td>Intended for viewers ages 14 and older</td> + * </tr> + * <tr> + * <td>CA_TV_EN_18</td> + * <td>Intended for viewers ages 18 and older</td> + * </tr> + * <tr> + * <td valign="top" rowspan="6">CA_TV_FR</td> + * <td>CA_TV_FR_E</td> + * <td>Exempt from ratings</td> + * </tr> + * <tr> + * <td>CA_TV_FR_G</td> + * <td>Appropriate for all ages</td> + * </tr> + * <tr> + * <td>CA_TV_FR_8</td> + * <td>Appropriate for children 8</td> + * </tr> + * <tr> + * <td>CA_TV_FR_13</td> + * <td>Suitable for children 13</td> + * </tr> + * <tr> + * <td>CA_TV_FR_16</td> + * <td>Recommended for children over the age of 16</td> + * </tr> + * <tr> + * <td>CA_TV_FR_18</td> + * <td>Only to be viewed by adults</td> + * </tr> + * <tr> * <td valign="top" rowspan="15">DVB</td> * <td>DVB_4</td> * <td>Recommended for ages 4 and over</td> @@ -608,6 +674,27 @@ import java.util.Objects; * <td>Suitable for adults aged 21 and above</td> * </tr> * <tr> + * <td valign="top" rowspan="5">US_MV</td> + * <td>US_MV_G</td> + * <td>General audiences</td> + * </tr> + * <tr> + * <td>US_MV_PG</td> + * <td>Parental guidance suggested</td> + * </tr> + * <tr> + * <td>US_MV_PG13</td> + * <td>Parents strongly cautioned</td> + * </tr> + * <tr> + * <td>US_MV_R</td> + * <td>Restricted, under 17 requires accompanying parent or adult guardian</td> + * </tr> + * <tr> + * <td>US_MV_NC17</td> + * <td>No one 17 and under admitted</td> + * </tr> + * <tr> * <td valign="top" rowspan="6">US_TV</td> * <td>US_TV_Y</td> * <td>This program is designed to be appropriate for all children</td> @@ -696,10 +783,15 @@ public final class TvContentRating { private final int mHashCode; /** - * Rating constant denoting unrated content. + * Rating constant denoting unrated content. Used to handle the case where the content rating + * information is missing. + * + * <p>TV input services can call {@link TvInputManager#isRatingBlocked} with this constant to + * determine whether they should block unrated content. The subsequent call to + * {@link TvInputService.Session#notifyContentBlocked} with the same constant notifies + * applications that the current program content is blocked by parental controls. */ - public static final TvContentRating UNRATED = new TvContentRating("com.android.tv", "", - "UNRATED", null); + public static final TvContentRating UNRATED = new TvContentRating("null", "null", "null", null); /** * Creates a {@code TvContentRating} object with predefined content rating strings. diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 601fa45..dca0631 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1035,7 +1035,7 @@ public final class TvInputManager { /** * Checks whether a given TV content rating is blocked by the user. * - * @param rating The TV content rating to check. + * @param rating The TV content rating to check. Can be {@link TvContentRating#UNRATED}. * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise. */ public boolean isRatingBlocked(@NonNull TvContentRating rating) { @@ -1696,6 +1696,8 @@ public final class TvInputManager { * @param rate The ratio between desired playback rate and normal one. * @param audioMode Audio playback mode. Must be one of the supported audio modes: * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_DEFAULT} + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_STRETCH} * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} * </ul> */ diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 5156ae8..c1035b0 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -528,11 +528,12 @@ public abstract class TvInputService extends Service { * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input * service should block the content or not is determined by invoking * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)} - * with the content rating for the current program. Then the {@link TvInputManager} makes a - * judgment based on the user blocked ratings stored in the secure settings and returns the - * result. If the rating in question turns out to be blocked, the TV input service must - * immediately block the content and call this method with the content rating of the current - * program to prompt the PIN verification screen. + * with the content rating for the current program or {@link TvContentRating#UNRATED} in + * case the rating information is missing. Then the {@link TvInputManager} makes a judgment + * based on the user blocked ratings stored in the secure settings and returns the result. + * If the rating in question turns out to be blocked, the TV input service must immediately + * block the content and call this method with the content rating of the current program to + * prompt the PIN verification screen. * * <p>Each TV input service also needs to continuously listen to any changes made to the * parental controls settings by registering a broadcast receiver to receive @@ -540,7 +541,8 @@ public abstract class TvInputService extends Service { * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately * reevaluate the current program with the new parental controls settings. * - * @param rating The content rating for the current TV program. + * @param rating The content rating for the current TV program. Can be + * {@link TvContentRating#UNRATED}. * @see #notifyContentAllowed * @see TvInputManager */ @@ -891,6 +893,8 @@ public abstract class TvInputService extends Service { * @param rate The ratio between desired playback rate and normal one. * @param audioMode Audio playback mode. Must be one of the supported audio modes: * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_DEFAULT} + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_STRETCH} * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} * </ul> * @see #onTimeShiftResume diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index d248b12..7e64b17 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -457,6 +457,8 @@ public class TvView extends ViewGroup { * @param rate The ratio between desired playback rate and normal one. * @param audioMode Audio playback mode. Must be one of the supported audio modes: * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_DEFAULT} + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_STRETCH} * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} * </ul> */ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 5f586a9..f808c0d 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -39,7 +39,7 @@ #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaErrors.h> - +#include <media/stagefright/PersistentSurface.h> #include <nativehelper/ScopedLocalRef.h> #include <system/window.h> @@ -75,6 +75,14 @@ static struct ExceptionReason { jint reasonReclaimed; } gExceptionReason; +static struct { + jclass clazz; + jfieldID mLock; + jfieldID mPersistentObject; + jmethodID ctor; + jmethodID setNativeObjectLocked; +} gPersistentSurfaceClassInfo; + struct fields_t { jfieldID context; jmethodID postEventFromNativeID; @@ -87,6 +95,7 @@ struct fields_t { }; static fields_t gFields; +static const void *sRefBaseOwner; //////////////////////////////////////////////////////////////////////////////// @@ -245,11 +254,29 @@ status_t JMediaCodec::configure( return mCodec->configure(format, mSurfaceTextureClient, crypto, flags); } +status_t JMediaCodec::setSurface( + const sp<IGraphicBufferProducer> &bufferProducer) { + sp<Surface> client; + if (bufferProducer != NULL) { + client = new Surface(bufferProducer, true /* controlledByApp */); + } + status_t err = mCodec->setSurface(client); + if (err == OK) { + mSurfaceTextureClient = client; + } + return err; +} + status_t JMediaCodec::createInputSurface( sp<IGraphicBufferProducer>* bufferProducer) { return mCodec->createInputSurface(bufferProducer); } +status_t JMediaCodec::usePersistentInputSurface( + const sp<PersistentSurface> &surface) { + return mCodec->usePersistentInputSurface(surface); +} + status_t JMediaCodec::start() { return mCodec->start(); } @@ -592,8 +619,8 @@ static jthrowable createCodecException( break; } - // TODO: propagate reason from MediaCodec. - int reason = gExceptionReason.reasonHardware; + int reason = + (err == DEAD_OBJECT) ? gExceptionReason.reasonReclaimed : gExceptionReason.reasonHardware; return (jthrowable)env->NewObject(clazz.get(), ctor, err, actionCode, msgObj.get(), reason); } @@ -797,6 +824,10 @@ static jint throwExceptionAsNecessary( jniThrowException(env, "java/lang/IllegalStateException", msg); return 0; + case BAD_VALUE: + jniThrowException(env, "java/lang/IllegalArgumentException", msg); + return 0; + default: if (isCryptoError(err)) { throwCryptoException(env, err, msg); @@ -869,6 +900,149 @@ static void android_media_MediaCodec_native_configure( throwExceptionAsNecessary(env, err); } +static void android_media_MediaCodec_native_setSurface( + JNIEnv *env, + jobject thiz, + jobject jsurface) { + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + sp<IGraphicBufferProducer> bufferProducer; + if (jsurface != NULL) { + sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); + if (surface != NULL) { + bufferProducer = surface->getIGraphicBufferProducer(); + } else { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + "The surface has been released"); + return; + } + } + + status_t err = codec->setSurface(bufferProducer); + throwExceptionAsNecessary(env, err); +} + +sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface( + JNIEnv* env, jobject object) { + sp<PersistentSurface> persistentSurface; + + jobject lock = env->GetObjectField( + object, gPersistentSurfaceClassInfo.mLock); + if (env->MonitorEnter(lock) == JNI_OK) { + persistentSurface = reinterpret_cast<PersistentSurface *>( + env->GetLongField(object, + gPersistentSurfaceClassInfo.mPersistentObject)); + env->MonitorExit(lock); + } + env->DeleteLocalRef(lock); + + return persistentSurface; +} + +static jobject android_media_MediaCodec_createPersistentInputSurface( + JNIEnv* env, jclass /* clazz */) { + ALOGV("android_media_MediaCodec_createPersistentInputSurface"); + sp<PersistentSurface> persistentSurface = + MediaCodec::CreatePersistentInputSurface(); + + if (persistentSurface == NULL) { + return NULL; + } + + sp<Surface> surface = new Surface( + persistentSurface->getBufferProducer(), true); + if (surface == NULL) { + return NULL; + } + + jobject object = env->NewObject( + gPersistentSurfaceClassInfo.clazz, + gPersistentSurfaceClassInfo.ctor); + + if (object == NULL) { + if (env->ExceptionCheck()) { + ALOGE("Could not create PersistentSurface."); + env->ExceptionClear(); + } + return NULL; + } + + jobject lock = env->GetObjectField( + object, gPersistentSurfaceClassInfo.mLock); + if (env->MonitorEnter(lock) == JNI_OK) { + env->CallVoidMethod( + object, + gPersistentSurfaceClassInfo.setNativeObjectLocked, + (jlong)surface.get()); + env->SetLongField( + object, + gPersistentSurfaceClassInfo.mPersistentObject, + (jlong)persistentSurface.get()); + env->MonitorExit(lock); + } else { + env->DeleteLocalRef(object); + object = NULL; + } + env->DeleteLocalRef(lock); + + if (object != NULL) { + surface->incStrong(&sRefBaseOwner); + persistentSurface->incStrong(&sRefBaseOwner); + } + + return object; +} + +static void android_media_MediaCodec_releasePersistentInputSurface( + JNIEnv* env, jclass /* clazz */, jobject object) { + sp<PersistentSurface> persistentSurface; + + jobject lock = env->GetObjectField( + object, gPersistentSurfaceClassInfo.mLock); + if (env->MonitorEnter(lock) == JNI_OK) { + persistentSurface = reinterpret_cast<PersistentSurface *>( + env->GetLongField( + object, gPersistentSurfaceClassInfo.mPersistentObject)); + env->SetLongField( + object, + gPersistentSurfaceClassInfo.mPersistentObject, + (jlong)0); + env->MonitorExit(lock); + } + env->DeleteLocalRef(lock); + + if (persistentSurface != NULL) { + persistentSurface->decStrong(&sRefBaseOwner); + } + // no need to release surface as it will be released by Surface's jni +} + +static void android_media_MediaCodec_usePersistentInputSurface( + JNIEnv* env, jobject thiz, jobject object) { + ALOGV("android_media_MediaCodec_usePersistentInputSurface"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + if (codec == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return; + } + + sp<PersistentSurface> persistentSurface = + android_media_MediaCodec_getPersistentInputSurface(env, object); + + status_t err = codec->usePersistentInputSurface(persistentSurface); + if (err != NO_ERROR) { + throwExceptionAsNecessary(env, err); + } +} + static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env, jobject thiz) { ALOGV("android_media_MediaCodec_createInputSurface"); @@ -1471,6 +1645,29 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) { CHECK(field != NULL); gExceptionReason.reasonReclaimed = env->GetStaticIntField(clazz.get(), field); + + clazz.reset(env->FindClass("android/view/Surface")); + CHECK(clazz.get() != NULL); + + field = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;"); + CHECK(field != NULL); + gPersistentSurfaceClassInfo.mLock = field; + + jmethodID method = env->GetMethodID(clazz.get(), "setNativeObjectLocked", "(J)V"); + CHECK(method != NULL); + gPersistentSurfaceClassInfo.setNativeObjectLocked = method; + + clazz.reset(env->FindClass("android/media/MediaCodec$PersistentSurface")); + CHECK(clazz.get() != NULL); + gPersistentSurfaceClassInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + method = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(method != NULL); + gPersistentSurfaceClassInfo.ctor = method; + + field = env->GetFieldID(clazz.get(), "mPersistentObject", "J"); + CHECK(field != NULL); + gPersistentSurfaceClassInfo.mPersistentObject = field; } static void android_media_MediaCodec_native_setup( @@ -1521,6 +1718,17 @@ static JNINativeMethod gMethods[] = { { "native_reset", "()V", (void *)android_media_MediaCodec_reset }, + { "native_releasePersistentInputSurface", + "(Landroid/view/Surface;)V", + (void *)android_media_MediaCodec_releasePersistentInputSurface}, + + { "native_createPersistentInputSurface", + "()Landroid/media/MediaCodec$PersistentSurface;", + (void *)android_media_MediaCodec_createPersistentInputSurface }, + + { "native_usePersistentInputSurface", "(Landroid/view/Surface;)V", + (void *)android_media_MediaCodec_usePersistentInputSurface }, + { "native_setCallback", "(Landroid/media/MediaCodec$Callback;)V", (void *)android_media_MediaCodec_native_setCallback }, @@ -1530,6 +1738,10 @@ static JNINativeMethod gMethods[] = { "Landroid/media/MediaCrypto;I)V", (void *)android_media_MediaCodec_native_configure }, + { "native_setSurface", + "(Landroid/view/Surface;)V", + (void *)android_media_MediaCodec_native_setSurface }, + { "createInputSurface", "()Landroid/view/Surface;", (void *)android_media_MediaCodec_createInputSurface }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 9f2785a..bf61f42 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -33,6 +33,7 @@ struct AString; struct ICrypto; struct IGraphicBufferProducer; struct MediaCodec; +struct PersistentSurface; class Surface; struct JMediaCodec : public AHandler { @@ -53,7 +54,11 @@ struct JMediaCodec : public AHandler { const sp<ICrypto> &crypto, int flags); + status_t setSurface( + const sp<IGraphicBufferProducer> &surface); + status_t createInputSurface(sp<IGraphicBufferProducer>* bufferProducer); + status_t usePersistentInputSurface(const sp<PersistentSurface> &surface); status_t start(); status_t stop(); diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index 88a6771..59fb6d6 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -299,15 +299,16 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, return NULL; } - SkBitmap *bitmap = GraphicsJNI::getSkBitmap(env, jBitmap); + SkBitmap bitmap; + GraphicsJNI::getSkBitmap(env, jBitmap, &bitmap); - bitmap->lockPixels(); - rotate((uint16_t*)bitmap->getPixels(), + bitmap.lockPixels(); + rotate((uint16_t*)bitmap.getPixels(), (uint16_t*)((char*)videoFrame + sizeof(VideoFrame)), videoFrame->mWidth, videoFrame->mHeight, videoFrame->mRotationAngle); - bitmap->unlockPixels(); + bitmap.unlockPixels(); if (videoFrame->mDisplayWidth != videoFrame->mWidth || videoFrame->mDisplayHeight != videoFrame->mHeight) { diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 2c61779..5b55a61 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -174,6 +174,8 @@ static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStat } else { // Throw exception! if ( opStatus == (status_t) INVALID_OPERATION ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); + } else if ( opStatus == (status_t) BAD_VALUE ) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); } else if ( opStatus == (status_t) PERMISSION_DENIED ) { jniThrowException(env, "java/lang/SecurityException", NULL); } else if ( opStatus != (status_t) OK ) { @@ -442,8 +444,33 @@ android_media_MediaPlayer_setPlaybackSettings(JNIEnv *env, jobject thiz, jobject pbs.audioFallbackModeSet, pbs.audioRate.mFallbackMode, pbs.audioStretchModeSet, pbs.audioRate.mStretchMode); - // TODO: pass playback settings to mediaplayer when audiotrack supports it - process_media_player_call(env, thiz, mp->setPlaybackRate(pbs.audioRate.mSpeed), NULL, NULL); + AudioPlaybackRate rate; + status_t err = mp->getPlaybackSettings(&rate); + if (err == OK) { + bool updatedRate = false; + if (pbs.speedSet) { + rate.mSpeed = pbs.audioRate.mSpeed; + updatedRate = true; + } + if (pbs.pitchSet) { + rate.mPitch = pbs.audioRate.mPitch; + updatedRate = true; + } + if (pbs.audioFallbackModeSet) { + rate.mFallbackMode = pbs.audioRate.mFallbackMode; + updatedRate = true; + } + if (pbs.audioStretchModeSet) { + rate.mStretchMode = pbs.audioRate.mStretchMode; + updatedRate = true; + } + if (updatedRate) { + err = mp->setPlaybackSettings(rate); + } + } + process_media_player_call( + env, thiz, err, + "java/lang/IllegalStateException", "unexpected error"); } static jobject @@ -457,15 +484,9 @@ android_media_MediaPlayer_getPlaybackSettings(JNIEnv *env, jobject thiz) PlaybackSettings pbs; AudioPlaybackRate &audioRate = pbs.audioRate; - - audioRate.mSpeed = 1.0f; - audioRate.mPitch = 1.0f; - audioRate.mFallbackMode = AUDIO_TIMESTRETCH_FALLBACK_DEFAULT; - audioRate.mStretchMode = AUDIO_TIMESTRETCH_STRETCH_DEFAULT; - - // TODO: get this from mediaplayer when audiotrack supports it - // process_media_player_call( - // env, thiz, mp->getPlaybackSettings(&audioRate), NULL, NULL); + process_media_player_call( + env, thiz, mp->getPlaybackSettings(&audioRate), + "java/lang/IllegalStateException", "unexpected error"); ALOGV("getPlaybackSettings: %f %f %d %d", audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode); @@ -489,13 +510,35 @@ android_media_MediaPlayer_setSyncSettings(JNIEnv *env, jobject thiz, jobject set SyncSettings scs; scs.fillFromJobject(env, gSyncSettingsFields, settings); ALOGV("setSyncSettings: %d:%d %d:%d %d:%f %d:%f", - scs.syncSourceSet, scs.syncSource, - scs.audioAdjustModeSet, scs.audioAdjustMode, - scs.toleranceSet, scs.tolerance, - scs.frameRateSet, scs.frameRate); - - // TODO: pass sync settings to mediaplayer when it supports it - // process_media_player_call(env, thiz, mp->setSyncSettings(scs), NULL, NULL); + scs.syncSourceSet, scs.sync.mSource, + scs.audioAdjustModeSet, scs.sync.mAudioAdjustMode, + scs.toleranceSet, scs.sync.mTolerance, + scs.frameRateSet, scs.frameRate); + + AVSyncSettings avsync; + float videoFrameRate; + status_t err = mp->getSyncSettings(&avsync, &videoFrameRate); + if (err == OK) { + bool updatedSync = scs.frameRateSet; + if (scs.syncSourceSet) { + avsync.mSource = scs.sync.mSource; + updatedSync = true; + } + if (scs.audioAdjustModeSet) { + avsync.mAudioAdjustMode = scs.sync.mAudioAdjustMode; + updatedSync = true; + } + if (scs.toleranceSet) { + avsync.mTolerance = scs.sync.mTolerance; + updatedSync = true; + } + if (updatedSync) { + err = mp->setSyncSettings(avsync, scs.frameRateSet ? scs.frameRate : -1.f); + } + } + process_media_player_call( + env, thiz, err, + "java/lang/IllegalStateException", "unexpected error"); } static jobject @@ -508,21 +551,27 @@ android_media_MediaPlayer_getSyncSettings(JNIEnv *env, jobject thiz) } SyncSettings scs; - scs.syncSource = 0; // SYNC_SOURCE_DEFAULT - scs.audioAdjustMode = 0; // AUDIO_ADJUST_MODE_DEFAULT - scs.tolerance = 0.f; - scs.frameRate = 0.f; - - // TODO: get this from mediaplayer when it supports it - // process_media_player_call( - // env, thiz, mp->getSyncSettings(&scs), NULL, NULL); + scs.frameRate = -1.f; + process_media_player_call( + env, thiz, mp->getSyncSettings(&scs.sync, &scs.frameRate), + "java/lang/IllegalStateException", "unexpected error"); + ALOGV("getSyncSettings: %d %d %f %f", - scs.syncSource, scs.audioAdjustMode, scs.tolerance, scs.frameRate); + scs.sync.mSource, scs.sync.mAudioAdjustMode, scs.sync.mTolerance, scs.frameRate); + + // sanity check settings + if (scs.sync.mSource >= AVSYNC_SOURCE_MAX + || scs.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX + || scs.sync.mTolerance < 0.f + || scs.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } scs.syncSourceSet = true; scs.audioAdjustModeSet = true; scs.toleranceSet = true; - scs.frameRateSet = false; + scs.frameRateSet = scs.frameRate >= 0.f; return scs.asJobject(env, gSyncSettingsFields); } diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 8b7d40d..0044bed 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -29,8 +29,11 @@ #include <camera/ICameraService.h> #include <camera/Camera.h> #include <media/mediarecorder.h> +#include <media/stagefright/PersistentSurface.h> #include <utils/threads.h> +#include <ScopedUtfChars.h> + #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" @@ -46,6 +49,8 @@ using namespace android; // helper function to extract a native Camera object from a Camera Java object extern sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, struct JNICameraContext** context); +extern sp<PersistentSurface> +android_media_MediaCodec_getPersistentInputSurface(JNIEnv* env, jobject object); struct fields_t { jfieldID context; @@ -113,6 +118,12 @@ static sp<Surface> get_surface(JNIEnv* env, jobject clazz) return android_view_Surface_getSurface(env, clazz); } +static sp<PersistentSurface> get_persistentSurface(JNIEnv* env, jobject object) +{ + ALOGV("get_persistentSurface"); + return android_media_MediaCodec_getPersistentInputSurface(env, object); +} + // Returns true if it throws an exception. static bool process_media_recorder_call(JNIEnv *env, status_t opStatus, const char* exception, const char* message) { @@ -444,11 +455,13 @@ android_media_MediaRecorder_native_init(JNIEnv *env) static void android_media_MediaRecorder_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring packageName) + jstring packageName, jstring opPackageName) { ALOGV("setup"); - sp<MediaRecorder> mr = new MediaRecorder(); + ScopedUtfChars opPackageNameStr(env, opPackageName); + + sp<MediaRecorder> mr = new MediaRecorder(String16(opPackageNameStr.c_str())); if (mr == NULL) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; @@ -483,6 +496,18 @@ android_media_MediaRecorder_native_finalize(JNIEnv *env, jobject thiz) android_media_MediaRecorder_release(env, thiz); } +void android_media_MediaRecorder_usePersistentSurface( + JNIEnv* env, jobject thiz, jobject object) { + ALOGV("android_media_MediaRecorder_usePersistentSurface"); + + sp<MediaRecorder> mr = getMediaRecorder(env, thiz); + + sp<PersistentSurface> persistentSurface = get_persistentSurface(env, object); + + process_media_recorder_call(env, mr->usePersistentSurface(persistentSurface), + "java/lang/IllegalArgumentException", "native_usePersistentSurface failed."); +} + // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -506,8 +531,10 @@ static JNINativeMethod gMethods[] = { {"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset}, {"release", "()V", (void *)android_media_MediaRecorder_release}, {"native_init", "()V", (void *)android_media_MediaRecorder_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;)V", (void *)android_media_MediaRecorder_native_setup}, + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V", + (void *)android_media_MediaRecorder_native_setup}, {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, + {"native_usePersistentSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_usePersistentSurface }, }; // This function only registers the native methods, and is called from diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp index f192262..5c18901 100644 --- a/media/jni/android_media_MediaSync.cpp +++ b/media/jni/android_media_MediaSync.cpp @@ -21,6 +21,7 @@ #include "android_media_MediaSync.h" #include "android_media_AudioTrack.h" +#include "android_media_PlaybackSettings.h" #include "android_media_SyncSettings.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" @@ -29,6 +30,7 @@ #include <gui/Surface.h> +#include <media/AudioResamplerPublic.h> #include <media/AudioTrack.h> #include <media/stagefright/MediaClock.h> #include <media/stagefright/MediaSync.h> @@ -47,6 +49,7 @@ struct fields_t { }; static fields_t gFields; +static PlaybackSettings::fields_t gPlaybackSettingsFields; static SyncSettings::fields_t gSyncSettingsFields; //////////////////////////////////////////////////////////////////////////////// @@ -58,14 +61,12 @@ JMediaSync::JMediaSync() { JMediaSync::~JMediaSync() { } -status_t JMediaSync::configureSurface(const sp<IGraphicBufferProducer> &bufferProducer) { - return mSync->configureSurface(bufferProducer); +status_t JMediaSync::setSurface(const sp<IGraphicBufferProducer> &bufferProducer) { + return mSync->setSurface(bufferProducer); } -status_t JMediaSync::configureAudioTrack( - const sp<AudioTrack> &audioTrack, - int32_t nativeSampleRateInHz) { - return mSync->configureAudioTrack(audioTrack, nativeSampleRateInHz); +status_t JMediaSync::setAudioTrack(const sp<AudioTrack> &audioTrack) { + return mSync->setAudioTrack(audioTrack); } status_t JMediaSync::createInputSurface( @@ -73,14 +74,34 @@ status_t JMediaSync::createInputSurface( return mSync->createInputSurface(bufferProducer); } -status_t JMediaSync::setPlaybackRate(float rate) { - return mSync->setPlaybackRate(rate); -} - sp<const MediaClock> JMediaSync::getMediaClock() { return mSync->getMediaClock(); } +status_t JMediaSync::setPlaybackSettings(const AudioPlaybackRate& rate) { + return mSync->setPlaybackSettings(rate); +} + +void JMediaSync::getPlaybackSettings(AudioPlaybackRate* rate /* nonnull */) { + mSync->getPlaybackSettings(rate); +} + +status_t JMediaSync::setSyncSettings(const AVSyncSettings& syncSettings) { + return mSync->setSyncSettings(syncSettings); +} + +void JMediaSync::getSyncSettings(AVSyncSettings* syncSettings /* nonnull */) { + mSync->getSyncSettings(syncSettings); +} + +status_t JMediaSync::setVideoFrameRateHint(float rate) { + return mSync->setVideoFrameRateHint(rate); +} + +float JMediaSync::getVideoFrameRate() { + return mSync->getVideoFrameRate(); +} + status_t JMediaSync::updateQueuedAudioData( int sizeInBytes, int64_t presentationTimeUs) { return mSync->updateQueuedAudioData(sizeInBytes, presentationTimeUs); @@ -142,9 +163,9 @@ static void throwExceptionAsNecessary( } } -static void android_media_MediaSync_native_configureSurface( +static void android_media_MediaSync_native_setSurface( JNIEnv *env, jobject thiz, jobject jsurface) { - ALOGV("android_media_MediaSync_configureSurface"); + ALOGV("android_media_MediaSync_setSurface"); sp<JMediaSync> sync = getMediaSync(env, thiz); if (sync == NULL) { @@ -163,7 +184,7 @@ static void android_media_MediaSync_native_configureSurface( } } - status_t err = sync->configureSurface(bufferProducer); + status_t err = sync->setSurface(bufferProducer); if (err == INVALID_OPERATION) { throwExceptionAsNecessary( @@ -175,9 +196,9 @@ static void android_media_MediaSync_native_configureSurface( } } -static void android_media_MediaSync_native_configureAudioTrack( - JNIEnv *env, jobject thiz, jobject jaudioTrack, jint nativeSampleRateInHz) { - ALOGV("android_media_MediaSync_configureAudioTrack"); +static void android_media_MediaSync_native_setAudioTrack( + JNIEnv *env, jobject thiz, jobject jaudioTrack) { + ALOGV("android_media_MediaSync_setAudioTrack"); sp<JMediaSync> sync = getMediaSync(env, thiz); if (sync == NULL) { @@ -194,7 +215,7 @@ static void android_media_MediaSync_native_configureAudioTrack( } } - status_t err = sync->configureAudioTrack(audioTrack, nativeSampleRateInHz); + status_t err = sync->setAudioTrack(audioTrack); if (err == INVALID_OPERATION) { throwExceptionAsNecessary( @@ -287,29 +308,132 @@ static jlong android_media_MediaSync_native_getPlayTimeForPendingAudioFrames( return (jlong)playTimeUs; } -static void -android_media_MediaSync_setSyncSettings(JNIEnv *env, jobject thiz, jobject settings) -{ +static jfloat android_media_MediaSync_setPlaybackSettings( + JNIEnv *env, jobject thiz, jobject settings) { sp<JMediaSync> sync = getMediaSync(env, thiz); if (sync == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); - return; + return (jfloat)0.f; + } + + PlaybackSettings pbs; + pbs.fillFromJobject(env, gPlaybackSettingsFields, settings); + ALOGV("setPlaybackSettings: %d:%f %d:%f %d:%u %d:%u", + pbs.speedSet, pbs.audioRate.mSpeed, + pbs.pitchSet, pbs.audioRate.mPitch, + pbs.audioFallbackModeSet, pbs.audioRate.mFallbackMode, + pbs.audioStretchModeSet, pbs.audioRate.mStretchMode); + + AudioPlaybackRate rate; + sync->getPlaybackSettings(&rate); + bool updatedRate = false; + if (pbs.speedSet) { + rate.mSpeed = pbs.audioRate.mSpeed; + updatedRate = true; + } + if (pbs.pitchSet) { + rate.mPitch = pbs.audioRate.mPitch; + updatedRate = true; + } + if (pbs.audioFallbackModeSet) { + rate.mFallbackMode = pbs.audioRate.mFallbackMode; + updatedRate = true; + } + if (pbs.audioStretchModeSet) { + rate.mStretchMode = pbs.audioRate.mStretchMode; + updatedRate = true; + } + if (updatedRate) { + status_t err = sync->setPlaybackSettings(rate); + if (err != OK) { + throwExceptionAsNecessary(env, err); + return (jfloat)0.f; + } + } + + sp<const MediaClock> mediaClock = sync->getMediaClock(); + if (mediaClock == NULL) { + return (jfloat)0.f; + } + + return (jfloat)mediaClock->getPlaybackRate(); +} + +static jobject android_media_MediaSync_getPlaybackSettings( + JNIEnv *env, jobject thiz) { + sp<JMediaSync> sync = getMediaSync(env, thiz); + if (sync == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return NULL; + } + + PlaybackSettings pbs; + AudioPlaybackRate &audioRate = pbs.audioRate; + sync->getPlaybackSettings(&audioRate); + ALOGV("getPlaybackSettings: %f %f %d %d", + audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode); + + pbs.speedSet = true; + pbs.pitchSet = true; + pbs.audioFallbackModeSet = true; + pbs.audioStretchModeSet = true; + + return pbs.asJobject(env, gPlaybackSettingsFields); +} + +static jfloat android_media_MediaSync_setSyncSettings( + JNIEnv *env, jobject thiz, jobject settings) { + sp<JMediaSync> sync = getMediaSync(env, thiz); + if (sync == NULL) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return (jfloat)0.f; } SyncSettings scs; scs.fillFromJobject(env, gSyncSettingsFields, settings); ALOGV("setSyncSettings: %d:%d %d:%d %d:%f %d:%f", - scs.syncSourceSet, scs.syncSource, - scs.audioAdjustModeSet, scs.audioAdjustMode, - scs.toleranceSet, scs.tolerance, + scs.syncSourceSet, scs.sync.mSource, + scs.audioAdjustModeSet, scs.sync.mAudioAdjustMode, + scs.toleranceSet, scs.sync.mTolerance, scs.frameRateSet, scs.frameRate); - // TODO: pass sync settings to mediasync when it supports it + AVSyncSettings avsync; + sync->getSyncSettings(&avsync); + bool updatedSync = false; + status_t err = OK; + if (scs.syncSourceSet) { + avsync.mSource = scs.sync.mSource; + updatedSync = true; + } + if (scs.audioAdjustModeSet) { + avsync.mAudioAdjustMode = scs.sync.mAudioAdjustMode; + updatedSync = true; + } + if (scs.toleranceSet) { + avsync.mTolerance = scs.sync.mTolerance; + updatedSync = true; + } + if (updatedSync) { + err = sync->setSyncSettings(avsync); + } + + if (scs.frameRateSet && err == OK) { + err = sync->setVideoFrameRateHint(scs.frameRate); + } + if (err != OK) { + throwExceptionAsNecessary(env, err); + return (jfloat)0.f; + } + + sp<const MediaClock> mediaClock = sync->getMediaClock(); + if (mediaClock == NULL) { + return (jfloat)0.f; + } + + return (jfloat)mediaClock->getPlaybackRate(); } -static jobject -android_media_MediaSync_getSyncSettings(JNIEnv *env, jobject thiz) -{ +static jobject android_media_MediaSync_getSyncSettings(JNIEnv *env, jobject thiz) { sp<JMediaSync> sync = getMediaSync(env, thiz); if (sync == NULL) { throwExceptionAsNecessary(env, INVALID_OPERATION); @@ -317,21 +441,25 @@ android_media_MediaSync_getSyncSettings(JNIEnv *env, jobject thiz) } SyncSettings scs; - scs.syncSource = 0; // SYNC_SOURCE_DEFAULT - scs.audioAdjustMode = 0; // AUDIO_ADJUST_MODE_DEFAULT - scs.tolerance = 0.f; - scs.frameRate = 0.f; - - // TODO: get this from mediaplayer when it supports it - // process_media_player_call( - // env, thiz, mp->getSyncSettings(&scs), NULL, NULL); + sync->getSyncSettings(&scs.sync); + scs.frameRate = sync->getVideoFrameRate(); + ALOGV("getSyncSettings: %d %d %f %f", - scs.syncSource, scs.audioAdjustMode, scs.tolerance, scs.frameRate); + scs.sync.mSource, scs.sync.mAudioAdjustMode, scs.sync.mTolerance, scs.frameRate); + + // sanity check settings + if (scs.sync.mSource >= AVSYNC_SOURCE_MAX + || scs.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX + || scs.sync.mTolerance < 0.f + || scs.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) { + throwExceptionAsNecessary(env, INVALID_OPERATION); + return NULL; + } scs.syncSourceSet = true; scs.audioAdjustModeSet = true; scs.toleranceSet = true; - scs.frameRateSet = false; + scs.frameRateSet = scs.frameRate >= 0.f; return scs.asJobject(env, gSyncSettingsFields); } @@ -359,6 +487,7 @@ static void android_media_MediaSync_native_init(JNIEnv *env) { CHECK(gFields.mediaTimestampClockRateID != NULL); gSyncSettingsFields.init(env); + gPlaybackSettingsFields.init(env); } static void android_media_MediaSync_native_setup(JNIEnv *env, jobject thiz) { @@ -367,33 +496,18 @@ static void android_media_MediaSync_native_setup(JNIEnv *env, jobject thiz) { setMediaSync(env, thiz, sync); } -static void android_media_MediaSync_native_setPlaybackRate( - JNIEnv *env, jobject thiz, jfloat rate) { - sp<JMediaSync> sync = getMediaSync(env, thiz); - if (sync == NULL) { - throwExceptionAsNecessary(env, INVALID_OPERATION); - return; - } - - status_t err = sync->setPlaybackRate(rate); - if (err != NO_ERROR) { - throwExceptionAsNecessary(env, err); - return; - } -} - static void android_media_MediaSync_native_finalize(JNIEnv *env, jobject thiz) { android_media_MediaSync_release(env, thiz); } static JNINativeMethod gMethods[] = { - { "native_configureSurface", + { "native_setSurface", "(Landroid/view/Surface;)V", - (void *)android_media_MediaSync_native_configureSurface }, + (void *)android_media_MediaSync_native_setSurface }, - { "native_configureAudioTrack", - "(Landroid/media/AudioTrack;I)V", - (void *)android_media_MediaSync_native_configureAudioTrack }, + { "native_setAudioTrack", + "(Landroid/media/AudioTrack;)V", + (void *)android_media_MediaSync_native_setAudioTrack }, { "createInputSurface", "()Landroid/view/Surface;", (void *)android_media_MediaSync_createInputSurface }, @@ -416,11 +530,17 @@ static JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaSync_release }, - { "native_setPlaybackRate", "(F)V", (void *)android_media_MediaSync_native_setPlaybackRate }, + { "native_setPlaybackSettings", "(Landroid/media/PlaybackSettings;)F", + (void *)android_media_MediaSync_setPlaybackSettings }, + + { "getPlaybackSettings", "()Landroid/media/PlaybackSettings;", + (void *)android_media_MediaSync_getPlaybackSettings }, - { "setSyncSettings", "(Landroid/media/SyncSettings;)V", (void *)android_media_MediaSync_setSyncSettings}, + { "native_setSyncSettings", "(Landroid/media/SyncSettings;)F", + (void *)android_media_MediaSync_setSyncSettings }, - { "getSyncSettings", "()Landroid/media/SyncSettings;", (void *)android_media_MediaSync_getSyncSettings}, + { "getSyncSettings", "()Landroid/media/SyncSettings;", + (void *)android_media_MediaSync_getSyncSettings }, { "native_finalize", "()V", (void *)android_media_MediaSync_native_finalize }, }; diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h index cf81a72..fa5e5e0 100644 --- a/media/jni/android_media_MediaSync.h +++ b/media/jni/android_media_MediaSync.h @@ -18,11 +18,13 @@ #define _ANDROID_MEDIA_MEDIASYNC_H_ #include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MediaSync.h> #include <utils/Errors.h> #include <utils/RefBase.h> namespace android { +struct AudioPlaybackRate; class AudioTrack; struct IGraphicBufferProducer; struct MediaClock; @@ -31,18 +33,22 @@ class MediaSync; struct JMediaSync : public RefBase { JMediaSync(); - status_t configureSurface(const sp<IGraphicBufferProducer> &bufferProducer); - status_t configureAudioTrack( - const sp<AudioTrack> &audioTrack, int32_t nativeSampleRateInHz); + status_t setSurface(const sp<IGraphicBufferProducer> &bufferProducer); + status_t setAudioTrack(const sp<AudioTrack> &audioTrack); status_t createInputSurface(sp<IGraphicBufferProducer>* bufferProducer); status_t updateQueuedAudioData(int sizeInBytes, int64_t presentationTimeUs); - status_t setPlaybackRate(float rate); - status_t getPlayTimeForPendingAudioFrames(int64_t *outTimeUs); + status_t setPlaybackSettings(const AudioPlaybackRate& rate); + void getPlaybackSettings(AudioPlaybackRate* rate /* nonnull */); + status_t setSyncSettings(const AVSyncSettings& syncSettings); + void getSyncSettings(AVSyncSettings* syncSettings /* nonnull */); + status_t setVideoFrameRateHint(float rate); + float getVideoFrameRate(); + sp<const MediaClock> getMediaClock(); protected: diff --git a/media/jni/android_media_SyncSettings.cpp b/media/jni/android_media_SyncSettings.cpp index 2f0605e..5da35e7 100644 --- a/media/jni/android_media_SyncSettings.cpp +++ b/media/jni/android_media_SyncSettings.cpp @@ -57,9 +57,9 @@ void SyncSettings::fields_t::exit(JNIEnv *env) { } void SyncSettings::fillFromJobject(JNIEnv *env, const fields_t& fields, jobject settings) { - syncSource = env->GetIntField(settings, fields.sync_source); - audioAdjustMode = env->GetIntField(settings, fields.audio_adjust_mode); - tolerance = env->GetFloatField(settings, fields.tolerance); + sync.mSource = (AVSyncSource)env->GetIntField(settings, fields.sync_source); + sync.mAudioAdjustMode = (AVSyncAudioAdjustMode)env->GetIntField(settings, fields.audio_adjust_mode); + sync.mTolerance = env->GetFloatField(settings, fields.tolerance); frameRate = env->GetFloatField(settings, fields.frame_rate); int set = env->GetIntField(settings, fields.set); @@ -74,9 +74,9 @@ jobject SyncSettings::asJobject(JNIEnv *env, const fields_t& fields) { if (settings == NULL) { return NULL; } - env->SetIntField(settings, fields.sync_source, (jint)syncSource); - env->SetIntField(settings, fields.audio_adjust_mode, (jint)audioAdjustMode); - env->SetFloatField(settings, fields.tolerance, (jfloat)tolerance); + env->SetIntField(settings, fields.sync_source, (jint)sync.mSource); + env->SetIntField(settings, fields.audio_adjust_mode, (jint)sync.mAudioAdjustMode); + env->SetFloatField(settings, fields.tolerance, (jfloat)sync.mTolerance); env->SetFloatField(settings, fields.frame_rate, (jfloat)frameRate); env->SetIntField( settings, fields.set, diff --git a/media/jni/android_media_SyncSettings.h b/media/jni/android_media_SyncSettings.h index 586533f..23530db 100644 --- a/media/jni/android_media_SyncSettings.h +++ b/media/jni/android_media_SyncSettings.h @@ -19,13 +19,12 @@ #include "jni.h" +#include <media/stagefright/MediaSync.h> + namespace android { struct SyncSettings { - // keep this here until it is implemented - int syncSource; - int audioAdjustMode; - float tolerance; + AVSyncSettings sync; float frameRate; bool syncSourceSet; diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index c364d46..96b72a2 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -25,6 +25,8 @@ #include <android_runtime/AndroidRuntime.h> #include "media/AudioEffect.h" +#include <ScopedUtfChars.h> + using namespace android; #define AUDIOEFFECT_SUCCESS 0 @@ -249,7 +251,8 @@ android_media_AudioEffect_native_init(JNIEnv *env) static jint android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId, jobjectArray javadesc) + jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId, + jobjectArray javadesc, jstring opPackageName) { ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; @@ -267,6 +270,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t jstring jdescName; jstring jdescImplementor; + ScopedUtfChars opPackageNameStr(env, opPackageName); + if (type != NULL) { typeStr = env->GetStringUTFChars(type, NULL); if (typeStr == NULL) { // Out of memory @@ -312,6 +317,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t // create the native AudioEffect object lpAudioEffect = new AudioEffect(typeStr, + String16(opPackageNameStr.c_str()), uuidStr, priority, effectCallback, @@ -868,7 +874,7 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _ // Dalvik VM type signatures static JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_AudioEffect_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I", (void *)android_media_AudioEffect_native_setup}, {"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize}, {"native_release", "()V", (void *)android_media_AudioEffect_native_release}, diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp index 460277f..abc681e 100644 --- a/media/jni/audioeffect/android_media_Visualizer.cpp +++ b/media/jni/audioeffect/android_media_Visualizer.cpp @@ -26,6 +26,8 @@ #include <utils/threads.h> #include "media/Visualizer.h" +#include <ScopedUtfChars.h> + using namespace android; #define VISUALIZER_SUCCESS 0 @@ -331,7 +333,7 @@ static void android_media_visualizer_effect_callback(int32_t event, static jint android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jint sessionId, jintArray jId) + jint sessionId, jintArray jId, jstring opPackageName) { ALOGV("android_media_visualizer_native_setup"); visualizerJniStorage* lpJniStorage = NULL; @@ -339,6 +341,8 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th Visualizer* lpVisualizer = NULL; jint* nId = NULL; + ScopedUtfChars opPackageNameStr(env, opPackageName); + lpJniStorage = new visualizerJniStorage(); if (lpJniStorage == NULL) { ALOGE("setup: Error creating JNI Storage"); @@ -362,7 +366,8 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th } // create the native Visualizer object - lpVisualizer = new Visualizer(0, + lpVisualizer = new Visualizer(String16(opPackageNameStr.c_str()), + 0, android_media_visualizer_effect_callback, lpJniStorage, sessionId); @@ -662,7 +667,7 @@ android_media_setPeriodicCapture(JNIEnv *env, jobject thiz, jint rate, jboolean // Dalvik VM type signatures static JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_visualizer_native_init}, - {"native_setup", "(Ljava/lang/Object;I[I)I", + {"native_setup", "(Ljava/lang/Object;I[ILjava/lang/String;)I", (void *)android_media_visualizer_native_setup}, {"native_finalize", "()V", (void *)android_media_visualizer_native_finalize}, {"native_release", "()V", (void *)android_media_visualizer_native_release}, diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 25c6154..84ae3b4 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -514,10 +514,11 @@ static status_t decode(int fd, int64_t offset, int64_t length, if (strncmp(mime, "audio/", 6) == 0) { AMediaCodec *codec = AMediaCodec_createDecoderByType(mime); - if (AMediaCodec_configure(codec, format, - NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK - || AMediaCodec_start(codec) != AMEDIA_OK - || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) { + if (codec == NULL + || AMediaCodec_configure(codec, format, + NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK + || AMediaCodec_start(codec) != AMEDIA_OK + || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) { AMediaExtractor_delete(ex); AMediaCodec_delete(codec); AMediaFormat_delete(format); diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java index 8d194e5..63a8da7 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java @@ -47,6 +47,7 @@ import java.util.UUID; public final class BluetoothMidiDevice { private static final String TAG = "BluetoothMidiDevice"; + private static final boolean DEBUG = false; private static final int MAX_PACKET_SIZE = 20; @@ -152,8 +153,10 @@ public final class BluetoothMidiDevice { @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { -// logByteArray("Received ", characteristic.getValue(), 0, -// characteristic.getValue().length); + if (DEBUG) { + logByteArray("Received ", characteristic.getValue(), 0, + characteristic.getValue().length); + } mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver); } }; @@ -182,8 +185,10 @@ public final class BluetoothMidiDevice { byte[] writeBuffer = mWriteBuffers[count]; System.arraycopy(buffer, 0, writeBuffer, 0, count); mCharacteristic.setValue(writeBuffer); -// logByteArray("Sent ", mCharacteristic.getValue(), 0, -// mCharacteristic.getValue().length); + if (DEBUG) { + logByteArray("Sent ", mCharacteristic.getValue(), 0, + mCharacteristic.getValue().length); + } mBluetoothGatt.writeCharacteristic(mCharacteristic); } } @@ -259,14 +264,7 @@ public final class BluetoothMidiDevice { private static void logByteArray(String prefix, byte[] value, int offset, int count) { StringBuilder builder = new StringBuilder(prefix); for (int i = offset; i < count; i++) { - String hex = Integer.toHexString(value[i]); - int length = hex.length(); - if (length == 1) { - hex = "0x" + hex; - } else { - hex = hex.substring(length - 2, length); - } - builder.append(hex); + builder.append(String.format("0x%02X", value[i])); if (i != value.length - 1) { builder.append(", "); } diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java index 463edcf..99ea353 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java @@ -44,7 +44,7 @@ public class BluetoothPacketEncoder extends PacketEncoder { // timestamp for first message in current packet private int mPacketTimestamp; // current running status, or zero if none - private int mRunningStatus; + private byte mRunningStatus; private boolean mWritePending; @@ -56,12 +56,28 @@ public class BluetoothPacketEncoder extends PacketEncoder { public void onReceive(byte[] msg, int offset, int count, long timestamp) throws IOException { - int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK; - int status = msg[0] & 0xFF; - synchronized (mLock) { + int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK; + byte status = msg[offset]; + boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE); + boolean isSysExContinuation = ((status & 0x80) == 0); + + int bytesNeeded; + if (isSysExStart || isSysExContinuation) { + // SysEx messages can be split into multiple packets + bytesNeeded = 1; + } else { + bytesNeeded = count; + } + boolean needsTimestamp = (milliTimestamp != mPacketTimestamp); - int bytesNeeded = count; + if (isSysExStart) { + // SysEx start byte must be preceded by a timestamp + needsTimestamp = true; + } else if (isSysExContinuation) { + // SysEx continuation packets must not have timestamp byte + needsTimestamp = false; + } if (needsTimestamp) bytesNeeded++; // add one for timestamp byte if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte @@ -71,15 +87,12 @@ public class BluetoothPacketEncoder extends PacketEncoder { flushLocked(true); } - // write header if we are starting a new packet - if (mAccumulatedBytes == 0) { - // header byte with timestamp bits 7 - 12 - mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7)); - mPacketTimestamp = milliTimestamp; - needsTimestamp = true; + // write the header if necessary + if (appendHeader(milliTimestamp)) { + needsTimestamp = !isSysExContinuation; } - // write new timestamp byte and status byte if necessary + // write new timestamp byte if necessary if (needsTimestamp) { // timestamp byte with bits 0 - 6 of timestamp mAccumulationBuffer[mAccumulatedBytes++] = @@ -87,20 +100,55 @@ public class BluetoothPacketEncoder extends PacketEncoder { mPacketTimestamp = milliTimestamp; } - if (status != mRunningStatus) { - mAccumulationBuffer[mAccumulatedBytes++] = (byte)status; - if (MidiConstants.allowRunningStatus(status)) { - mRunningStatus = status; - } else if (MidiConstants.allowRunningStatus(status)) { - mRunningStatus = 0; + if (isSysExStart || isSysExContinuation) { + // MidiFramer will end the packet with SysEx End if there is one in the buffer + boolean hasSysExEnd = + (msg[offset + count - 1] == MidiConstants.STATUS_END_SYSEX); + int remaining = (hasSysExEnd ? count - 1 : count); + + while (remaining > 0) { + if (mAccumulatedBytes == mAccumulationBuffer.length) { + // write out our data if there is no more room + // if necessary, block until previous packet is sent + flushLocked(true); + appendHeader(milliTimestamp); + } + + int copy = mAccumulationBuffer.length - mAccumulatedBytes; + if (copy > remaining) copy = remaining; + System.arraycopy(msg, offset, mAccumulationBuffer, mAccumulatedBytes, copy); + mAccumulatedBytes += copy; + offset += copy; + remaining -= copy; } - } - // now copy data bytes - int dataLength = count - 1; - System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength); - // FIXME - handle long SysEx properly - mAccumulatedBytes += dataLength; + if (hasSysExEnd) { + // SysEx End command must be preceeded by a timestamp byte + if (mAccumulatedBytes + 2 > mAccumulationBuffer.length) { + // write out our data if there is no more room + // if necessary, block until previous packet is sent + flushLocked(true); + appendHeader(milliTimestamp); + } + mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp & 0x7F)); + mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX; + } + } else { + // Non-SysEx message + if (status != mRunningStatus) { + mAccumulationBuffer[mAccumulatedBytes++] = status; + if (MidiConstants.allowRunningStatus(status)) { + mRunningStatus = status; + } else if (MidiConstants.cancelsRunningStatus(status)) { + mRunningStatus = 0; + } + } + + // now copy data bytes + int dataLength = count - 1; + System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes, dataLength); + mAccumulatedBytes += dataLength; + } // write the packet if possible, but do not block flushLocked(false); @@ -108,6 +156,18 @@ public class BluetoothPacketEncoder extends PacketEncoder { } }; + private boolean appendHeader(int milliTimestamp) { + // write header if we are starting a new packet + if (mAccumulatedBytes == 0) { + // header byte with timestamp bits 7 - 12 + mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F)); + mPacketTimestamp = milliTimestamp; + return true; + } else { + return false; + } + } + // MidiFramer for normalizing incoming data private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java index 86c23c7..16b4c43 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java @@ -60,6 +60,7 @@ public class MediaNames { public static final String VIDEO_H264_AAC = "/sdcard/media_api/video/H264_320_AAC_64.3gp"; public static final String VIDEO_H264_AMR = "/sdcard/media_api/video/H264_320_AMRNB_6.3gp"; public static final String VIDEO_HEVC_AAC = "/sdcard/media_api/video/HEVC_320_AAC_128.mp4"; + public static final String VIDEO_MPEG2_AAC = "/sdcard/media_api/video/MPEG2_1500_AAC_128.mp4"; public static final String VIDEO_HIGHRES_H263 = "/sdcard/media_api/video/H263_500_AMRNB_12.3gp"; public static final String VIDEO_HIGHRES_MP4 = "/sdcard/media_api/video/H264_500_AAC_128.3gp"; public static final String VIDEO_WEBM = "/sdcard/media_api/video/big-buck-bunny_trailer.webm"; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java index e730329..563b0f3 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediarecorder/MediaRecorderTest.java @@ -27,6 +27,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.hardware.Camera; +import android.media.MediaCodec; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.MediaRecorder; @@ -225,10 +226,12 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra private boolean recordVideoFromSurface( int frameRate, int captureRate, int width, int height, - int videoFormat, int outFormat, String outFile, boolean videoOnly) { + int videoFormat, int outFormat, String outFile, boolean videoOnly, + Surface persistentSurface) { Log.v(TAG,"recordVideoFromSurface"); MediaRecorder recorder = new MediaRecorder(); int sleepTime = 33; // normal capture at 33ms / frame + Surface surface = null; try { if (!videoOnly) { recorder.setAudioSource(MediaRecorder.AudioSource.MIC); @@ -246,8 +249,15 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra if (!videoOnly) { recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); } + if (persistentSurface != null) { + Log.v(TAG, "using persistent surface"); + surface = persistentSurface; + recorder.usePersistentSurface(surface); + } recorder.prepare(); - Surface surface = recorder.getSurface(); + if (persistentSurface == null) { + surface = recorder.getSurface(); + } Paint paint = new Paint(); paint.setTextSize(16); @@ -283,11 +293,15 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra Log.v(TAG, "stop"); recorder.stop(); - recorder.release(); } catch (Exception e) { - Log.v("record video failed ", e.toString()); - recorder.release(); + Log.v(TAG, "record video failed: " + e.toString()); return false; + } finally { + recorder.release(); + // release surface if not using persistent surface + if (persistentSurface == null && surface != null) { + surface.release(); + } } return true; } @@ -550,7 +564,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra success = recordVideoFromSurface(frameRate, 0, 352, 288, codec, MediaRecorder.OutputFormat.THREE_GPP, filename, - k == 0 ? true : false /* videoOnly */); + k == 0 ? true : false /* videoOnly */, null); if (success) { success = validateVideo(filename, 352, 288); } @@ -564,6 +578,40 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra assertTrue("testSurfaceRecording", noOfFailure == 0); } + public void testPersistentSurfaceRecording() { + boolean success = false; + int noOfFailure = 0; + Surface surface = null; + try { + int codec = MediaRecorder.VideoEncoder.H264; + int frameRate = MediaProfileReader.getMaxFrameRateForCodec(codec); + surface = MediaCodec.createPersistentInputSurface(); + for (int k = 0; k < 2; k++) { + String filename = "/sdcard/surface_persistent" + k + ".3gp"; + + Log.v(TAG, "test persistent surface - round " + k); + success = recordVideoFromSurface(frameRate, 0, 352, 288, codec, + MediaRecorder.OutputFormat.THREE_GPP, filename, + true /* videoOnly */, surface); + if (success) { + success = validateVideo(filename, 352, 288); + } + if (!success) { + noOfFailure++; + } + } + } catch (Exception e) { + Log.v(TAG, e.toString()); + } finally { + if (surface != null) { + Log.v(TAG, "releasing persistent surface"); + surface.release(); + surface = null; + } + } + assertTrue("testPersistentSurfaceRecording", noOfFailure == 0); + } + // Test recording from surface source with/without audio public void testSurfaceRecordingTimeLapse() { boolean success = false; @@ -583,7 +631,7 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaFra success = recordVideoFromSurface( frameRate, captureRate, 352, 288, codec, MediaRecorder.OutputFormat.THREE_GPP, - filename, false /* videoOnly */); + filename, false /* videoOnly */, null); if (success) { success = validateVideo(filename, 352, 288); if (success) { diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java index 244b07f..c528165 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java @@ -432,7 +432,22 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med assertTrue("HEVC playback memory test", memoryResult); } - // Test case 4: Capture the memory usage after every 20 video only recorded + // Test case 4: Capture the memory usage after every 20 mpeg2 playback + @LargeTest + public void testMPEG2VideoPlaybackMemoryUsage() throws Exception { + boolean memoryResult = false; + + mStartPid = getMediaserverPid(); + for (int i = 0; i < NUM_STRESS_LOOP; i++) { + mediaStressPlayback(MediaNames.VIDEO_MPEG2_AAC); + getMemoryWriteToLog(i); + writeProcmemInfo(); + } + memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT); + assertTrue("MPEG2 playback memory test", memoryResult); + } + + // Test case 5: Capture the memory usage after every 20 video only recorded @LargeTest public void testH263RecordVideoOnlyMemoryUsage() throws Exception { if (mCamcorderProfile != null) { @@ -453,7 +468,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med } } - // Test case 5: Capture the memory usage after every 20 video only recorded + // Test case 6: Capture the memory usage after every 20 video only recorded @LargeTest public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception { if (mCamcorderProfile != null) { @@ -474,7 +489,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med } } - // Test case 6: Capture the memory usage after every 20 video and audio + // Test case 7: Capture the memory usage after every 20 video and audio // recorded @LargeTest public void testRecordVideoAudioMemoryUsage() throws Exception { @@ -496,7 +511,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med } } - // Test case 7: Capture the memory usage after every 20 audio only recorded + // Test case 8: Capture the memory usage after every 20 audio only recorded @LargeTest public void testRecordAudioOnlyMemoryUsage() throws Exception { boolean memoryResult = false; @@ -511,7 +526,7 @@ public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<Med assertTrue("audio record only memory test", memoryResult); } - // Test case 8: Capture the memory usage after every 20 camera preview + // Test case 9: Capture the memory usage after every 20 camera preview @LargeTest public void testCameraPreviewMemoryUsage() throws Exception { boolean memoryResult = false; diff --git a/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4 b/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4 Binary files differnew file mode 100644 index 0000000..33f66a0 --- /dev/null +++ b/media/tests/contents/media_api/video/MPEG2_1500_AAC_128.mp4 |