diff options
Diffstat (limited to 'media/java/android/media/MediaSync.java')
| -rw-r--r-- | media/java/android/media/MediaSync.java | 287 |
1 files changed, 193 insertions, 94 deletions
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java index 74a2fb2..c1f1a73 100644 --- a/media/java/android/media/MediaSync.java +++ b/media/java/android/media/MediaSync.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioTrack; +import android.media.PlaybackSettings; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -38,13 +39,13 @@ import java.util.List; * <p>MediaSync is generally used like this: * <pre> * MediaSync sync = new MediaSync(); - * sync.configureSurface(surface); + * sync.setSurface(surface); * Surface inputSurface = sync.createInputSurface(); * ... * // MediaCodec videoDecoder = ...; * videoDecoder.configure(format, inputSurface, ...); * ... - * sync.configureAudioTrack(audioTrack); + * sync.setAudioTrack(audioTrack); * sync.setCallback(new MediaSync.Callback() { * {@literal @Override} * public void onReturnAudioBuffer(MediaSync sync, ByteBuffer audioBuffer, int bufferIndex) { @@ -94,8 +95,8 @@ import java.util.List; * * </pre> * - * The client needs to configure corresponding sink (i.e., Surface and AudioTrack) based on - * the stream type it will play. + * The client needs to configure corresponding sink by setting the Surface and/or AudioTrack + * based on the stream type it will play. * <p> * For video, the client needs to call {@link #createInputSurface} to obtain a surface on * which it will render video frames. @@ -233,29 +234,33 @@ final public class MediaSync { } /** - * Configures the output surface for MediaSync. + * Sets the output surface for MediaSync. + * <p> + * Currently, this is only supported in the Initialized state. * * @param surface Specify a surface on which to render the video data. - * @throws IllegalArgumentException if the surface has been released, or is invalid. + * @throws IllegalArgumentException if the surface has been released, is invalid, * or can not be connected. - * @throws IllegalStateException if not in the Initialized state, or another surface - * has already been configured. + * @throws IllegalStateException if setting the surface is not supported, e.g. + * not in the Initialized state, or another surface has already been configured. */ - public void configureSurface(@Nullable Surface surface) { + public void setSurface(@Nullable Surface surface) { native_configureSurface(surface); } private native final void native_configureSurface(@Nullable Surface surface); /** - * Configures the audio track for MediaSync. + * Sets the audio track for MediaSync. + * <p> + * Currently, this is only supported in the Initialized state. * * @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 not in the Initialized state, or another audio track - * has already been configured. + * @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. */ - public void configureAudioTrack(@Nullable AudioTrack audioTrack) { + public void setAudioTrack(@Nullable AudioTrack audioTrack) { // AudioTrack has sanity check for configured sample rate. int nativeSampleRateInHz = (audioTrack == null ? 0 : audioTrack.getSampleRate()); @@ -271,7 +276,7 @@ final public class MediaSync { /** * Requests a Surface to use as the input. This may only be called after - * {@link #configureSurface}. + * {@link #setSurface}. * <p> * The application is responsible for calling release() on the Surface when * done. @@ -282,141 +287,235 @@ final public class MediaSync { public native final Surface createInputSurface(); /** - * Specifies resampling as audio mode for variable rate playback, i.e., - * resample the waveform based on the requested playback rate to get + * Resample audio data when changing playback speed. + * <p> + * Resample the waveform based on the requested playback rate to get * a new waveform, and play back the new waveform at the original sampling * frequency. - * When rate is larger than 1.0, pitch becomes higher. - * When rate is smaller than 1.0, pitch becomes lower. + * <p><ul> + * <li>When rate is larger than 1.0, pitch becomes higher. + * <li>When rate is smaller than 1.0, pitch becomes lower. + * </ul> */ - public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0; + public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2; /** - * Specifies time stretching as audio mode for variable rate playback. + * Time stretch audio when changing playback speed. + * <p> * Time stretching changes the duration of the audio samples without - * affecting its pitch. - * FIXME: implement time strectching. - * @hide + * affecting their pitch. This is only supported for a limited range + * of playback speeds, e.g. from 1/2x to 2x. If the rate is adjusted + * beyond this limit, the rate change will fail. */ public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1; + /** + * Time stretch audio when changing playback speed, and may mute if + * stretching is no longer supported. + * <p> + * Time stretching changes the duration of the audio samples without + * affecting their pitch. This is only supported for a limited range + * of playback speeds, e.g. from 1/2x to 2x. When it is no longer + * supported, the audio may be muted. Using this mode will not fail + * for non-negative playback rates. + */ + public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0; + /** @hide */ @IntDef( value = { + PLAYBACK_RATE_AUDIO_MODE_DEFAULT, + PLAYBACK_RATE_AUDIO_MODE_STRETCH, PLAYBACK_RATE_AUDIO_MODE_RESAMPLE, - PLAYBACK_RATE_AUDIO_MODE_STRETCH }) + }) @Retention(RetentionPolicy.SOURCE) public @interface PlaybackRateAudioMode {} /** - * Sets playback rate. It does same as {@link #setPlaybackRate(float, int)}, - * except that it always uses {@link #PLAYBACK_RATE_AUDIO_MODE_STRETCH} for audioMode. + * Sets playback rate and audio mode. * * @param rate the ratio between desired playback rate and normal one. 1.0 means normal - * playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback, - * while value between 0.0 and 1.0 for slower playback. + * playback speed. 0.0 means pause. Value larger than 1.0 means faster playback, + * while value between 0.0 and 1.0 for slower playback. <b>Note:</b> the normal rate + * does not change as a result of this call. To restore the original rate at any time, + * use 1.0. + * @param audioMode audio playback mode. Must be one of the supported + * audio modes. * * @throws IllegalStateException if the internal sync engine or the audio track has not * been initialized. - * TODO: unhide when PLAYBACK_RATE_AUDIO_MODE_STRETCH is supported. - * @hide + * @throws IllegalArgumentException if audioMode is not supported. */ - public void setPlaybackRate(float rate) { - setPlaybackRate(rate, PLAYBACK_RATE_AUDIO_MODE_STRETCH); + public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) { + PlaybackSettings rateSettings = new PlaybackSettings(); + rateSettings.allowDefaults(); + switch (audioMode) { + case PLAYBACK_RATE_AUDIO_MODE_DEFAULT: + rateSettings.setSpeed(rate).setPitch(1.0f); + break; + case PLAYBACK_RATE_AUDIO_MODE_STRETCH: + rateSettings.setSpeed(rate).setPitch(1.0f) + .setAudioFallbackMode(rateSettings.AUDIO_FALLBACK_MODE_FAIL); + break; + case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE: + rateSettings.setSpeed(rate).setPitch(rate); + break; + default: + { + final String msg = "Audio playback mode " + audioMode + " is not supported"; + throw new IllegalArgumentException(msg); + } + } + setPlaybackSettings(rateSettings); } /** - * Sets playback rate and audio mode. + * Sets playback rate using {@link PlaybackSettings}. + * <p> + * When using MediaSync with {@link AudioTrack}, set playback settings using this + * call instead of calling it directly on the track, so that the sync is aware of + * the settings change. + * <p> + * This call also works if there is no audio track. * - * <p> The supported audio modes are: - * <ul> - * <li> {@link #PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} - * </ul> - * - * @param rate the ratio between desired playback rate and normal one. 1.0 means normal - * playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback, - * while value between 0.0 and 1.0 for slower playback. - * @param audioMode audio playback mode. Must be one of the supported - * audio modes. + * @param settings the playback settings to use. {@link PlaybackSettings#getSpeed + * Speed} is the ratio between desired playback rate and normal one. 1.0 means + * normal playback speed. 0.0 means pause. Value larger than 1.0 means faster playback, + * while value between 0.0 and 1.0 for slower playback. <b>Note:</b> the normal rate + * does not change as a result of this call. To restore the original rate at any time, + * use speed of 1.0. * * @throws IllegalStateException if the internal sync engine or the audio track has not * been initialized. - * @throws IllegalArgumentException if audioMode is not supported. + * @throws IllegalArgumentException if the settings are not supported. */ - public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) { - if (!isAudioPlaybackModeSupported(audioMode)) { - final String msg = "Audio playback mode " + audioMode + " is not supported"; - throw new IllegalArgumentException(msg); - } - - int status = AudioTrack.SUCCESS; - if (mAudioTrack != null) { - int nativeSampleRateInHz = mAudioTrack.getSampleRate(); - int playbackSampleRate = (int)(rate * nativeSampleRateInHz + 0.5); - rate = playbackSampleRate / (float)nativeSampleRateInHz; - - try { - if (rate == 0.0) { - mAudioTrack.pause(); - } else { - status = mAudioTrack.setPlaybackRate(playbackSampleRate); - mAudioTrack.play(); + 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; } - } catch (IllegalStateException e) { - throw e; } - } - if (status != AudioTrack.SUCCESS) { - throw new IllegalArgumentException("Fail to set playback rate in audio track"); + 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 = rate; - } - if (mPlaybackRate != 0.0 && mAudioThread != null) { - postRenderAudio(0); + /** + * Gets the playback rate using {@link PlaybackSettings}. + * + * @return the playback rate being used. + * + * @throws IllegalStateException if the internal sync engine or the audio track has not + * been initialized. + */ + @NonNull + public PlaybackSettings getPlaybackSettings() { + if (mAudioTrack != null) { + return mAudioTrack.getPlaybackSettings(); + } else { + PlaybackSettings settings = new PlaybackSettings(); + settings.allowDefaults(); + settings.setSpeed(mPlaybackRate); + return settings; } - native_setPlaybackRate(mPlaybackRate); } private native final void native_setPlaybackRate(float rate); - /* - * Test whether a given audio playback mode is supported. - * TODO query supported AudioPlaybackMode from audio track. + /** + * Sets A/V sync mode. + * + * @param settings the A/V sync settings to apply + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + * @throws IllegalArgumentException if settings are not supported. */ - private boolean isAudioPlaybackModeSupported(int mode) { - return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE); + public native void setSyncSettings(@NonNull SyncSettings settings); + + /** + * Gets the A/V sync mode. + * + * @return the A/V sync settings + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + @NonNull + public native SyncSettings getSyncSettings(); + + /** + * Flushes all buffers from the sync object. + * <p> + * No callbacks are received for the flushed buffers. + * + * @throws IllegalStateException if the internal player engine has not been + * initialized. + */ + public void flush() { + synchronized(mAudioLock) { + mAudioBuffers.clear(); + mCallbackHandler.removeCallbacksAndMessages(null); + } + // TODO implement this for surface buffers. } /** * Get current playback position. * <p> - * The MediaTimestamp represents a clock ticking during media playback. It's represented - * by an anchor frame ({@link MediaTimestamp#mediaTimeUs} and {@link MediaTimestamp#nanoTime}) - * and clock speed ({@link MediaTimestamp#clockRate}). For continous playback with - * constant speed, its anchor frame doesn't change that often. Thereafter, it's recommended - * to not call this method often. + * The MediaTimestamp represents how the media time correlates to the system time in + * a linear fashion. It contains the media time and system timestamp of an anchor frame + * ({@link MediaTimestamp#mediaTimeUs} and {@link MediaTimestamp#nanoTime}) + * and the speed of the media clock ({@link MediaTimestamp#clockRate}). + * <p> + * During regular playback, the media time moves fairly constantly (though the + * anchor frame may be rebased to a current system time, the linear correlation stays + * steady). Therefore, this method does not need to be called often. * <p> * To help users to get current playback position, this method always returns the timestamp of * just-rendered frame, i.e., {@link System#nanoTime} and its corresponding media time. They * can be used as current playback position. * - * @param timestamp a reference to a non-null MediaTimestamp instance allocated - * and owned by caller. - * @return true if a timestamp is available, or false if no timestamp is available. - * If a timestamp if available, the MediaTimestamp instance is filled in with - * playback rate, together with the current media timestamp and the system nanoTime - * corresponding to the measured media timestamp. - * In the case that no timestamp is available, any supplied instance is left unaltered. + * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp + * is available, e.g. because the media sync has not been initialized. */ - public boolean getTimestamp(@NonNull MediaTimestamp timestamp) + @Nullable + public MediaTimestamp getTimestamp() { - if (timestamp == null) { - throw new IllegalArgumentException(); + try { + // TODO: create the timestamp in native + MediaTimestamp timestamp = new MediaTimestamp(); + if (native_getTimestamp(timestamp)) { + return timestamp; + } else { + return null; + } + } catch (IllegalStateException e) { + return null; } - return native_getTimestamp(timestamp); } private native final boolean native_getTimestamp(@NonNull MediaTimestamp timestamp); |
