blob: e6bc10dce4e16bf345c6e2c269ad7072b82c6d1d [file] [log] [blame]
Wei Jia071a8b72015-03-09 16:38:25 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.annotation.IntDef;
Wei Jia217ec0a2015-04-09 18:48:18 -070020import android.annotation.NonNull;
Wei Jia071a8b72015-03-09 16:38:25 -070021import android.media.AudioTrack;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25import android.view.Surface;
26
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.nio.ByteBuffer;
30import java.util.LinkedList;
31import java.util.List;
32
33/**
34 * MediaSync class can be used to synchronously playback audio and video streams.
35 * It can be used to play audio-only or video-only stream, too.
36 *
37 * <p>MediaSync is generally used like this:
38 * <pre>
39 * MediaSync sync = new MediaSync();
40 * sync.configureSurface(surface);
41 * Surface inputSurface = sync.createInputSurface();
42 * ...
43 * // MediaCodec videoDecoder = ...;
44 * videoDecoder.configure(format, inputSurface, ...);
45 * ...
Wei Jiaba5997e52015-04-16 13:18:39 -070046 * sync.configureAudioTrack(audioTrack);
Wei Jia071a8b72015-03-09 16:38:25 -070047 * sync.setCallback(new MediaSync.Callback() {
Wei Jiaba5997e52015-04-16 13:18:39 -070048 * {@literal @Override}
Wei Jia071a8b72015-03-09 16:38:25 -070049 * public void onReturnAudioBuffer(MediaSync sync, ByteBuffer audioBuffer, int bufferIndex) {
50 * ...
51 * }
Wei Jia161537c2015-04-20 17:28:58 -070052 * }, null);
Wei Jia071a8b72015-03-09 16:38:25 -070053 * // This needs to be done since sync is paused on creation.
54 * sync.setPlaybackRate(1.0f, MediaSync.PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
55 *
56 * for (;;) {
57 * ...
58 * // send video frames to surface for rendering, e.g., call
59 * // videoDecoder.releaseOutputBuffer(videoOutputBufferIx, videoPresentationTimeNs);
60 * // More details are available as below.
61 * ...
62 * sync.queueAudio(audioByteBuffer, bufferIndex, size, audioPresentationTimeUs); // non-blocking.
63 * // The audioByteBuffer and bufferIndex will be returned via callback.
64 * // More details are available as below.
65 * ...
66 * ...
67 * }
68 * sync.setPlaybackRate(0.0f, MediaSync.PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
69 * sync.release();
70 * sync = null;
71 *
72 * // The following code snippet illustrates how video/audio raw frames are created by
73 * // MediaCodec's, how they are fed to MediaSync and how they are returned by MediaSync.
74 * // This is the callback from MediaCodec.
75 * onOutputBufferAvailable(MediaCodec codec, int bufferIndex, BufferInfo info) {
76 * // ...
77 * if (codec == videoDecoder) {
78 * // surface timestamp must contain media presentation time in nanoseconds.
79 * codec.releaseOutputBuffer(bufferIndex, 1000 * info.presentationTime);
80 * } else {
81 * ByteBuffer audioByteBuffer = codec.getOutputBuffer(bufferIndex);
82 * sync.queueByteBuffer(audioByteBuffer, bufferIndex, info.size, info.presentationTime);
83 * }
84 * // ...
85 * }
86 *
87 * // This is the callback from MediaSync.
88 * onReturnAudioBuffer(MediaSync sync, ByteBuffer buffer, int bufferIndex) {
89 * // ...
90 * audioDecoder.releaseBuffer(bufferIndex, false);
91 * // ...
92 * }
93 *
94 * </pre>
95 *
96 * The client needs to configure corresponding sink (i.e., Surface and AudioTrack) based on
97 * the stream type it will play.
98 * <p>
99 * For video, the client needs to call {@link #createInputSurface} to obtain a surface on
100 * which it will render video frames.
101 * <p>
102 * For audio, the client needs to set up audio track correctly, e.g., using {@link
103 * AudioTrack#MODE_STREAM}. The audio buffers are sent to MediaSync directly via {@link
104 * #queueAudio}, and are returned to the client via {@link Callback#onReturnAudioBuffer}
105 * asynchronously. The client should not modify an audio buffer till it's returned.
106 * <p>
107 * The client can optionally pre-fill audio/video buffers by setting playback rate to 0.0,
108 * and then feed audio/video buffers to corresponding components. This can reduce possible
109 * initial underrun.
110 * <p>
111 */
112final public class MediaSync {
113 /**
114 * MediaSync callback interface. Used to notify the user asynchronously
115 * of various MediaSync events.
116 */
117 public static abstract class Callback {
118 /**
119 * Called when returning an audio buffer which has been consumed.
120 *
121 * @param sync The MediaSync object.
122 * @param audioBuffer The returned audio buffer.
123 */
124 public abstract void onReturnAudioBuffer(
125 MediaSync sync, ByteBuffer audioBuffer, int bufferIndex);
126 }
127
128 private static final String TAG = "MediaSync";
129
130 private static final int EVENT_CALLBACK = 1;
131 private static final int EVENT_SET_CALLBACK = 2;
132
133 private static final int CB_RETURN_AUDIO_BUFFER = 1;
134
135 private static class AudioBuffer {
136 public ByteBuffer mByteBuffer;
137 public int mBufferIndex;
138 public int mSizeInBytes;
139 long mPresentationTimeUs;
140
141 public AudioBuffer(ByteBuffer byteBuffer, int bufferIndex,
142 int sizeInBytes, long presentationTimeUs) {
143 mByteBuffer = byteBuffer;
144 mBufferIndex = bufferIndex;
145 mSizeInBytes = sizeInBytes;
146 mPresentationTimeUs = presentationTimeUs;
147 }
148 }
149
150 private final Object mCallbackLock = new Object();
151 private Handler mCallbackHandler = null;
152 private MediaSync.Callback mCallback = null;
153
Wei Jia071a8b72015-03-09 16:38:25 -0700154 private Thread mAudioThread = null;
155 // Created on mAudioThread when mAudioThread is started. When used on user thread, they should
156 // be guarded by checking mAudioThread.
157 private Handler mAudioHandler = null;
158 private Looper mAudioLooper = null;
159
160 private final Object mAudioLock = new Object();
161 private AudioTrack mAudioTrack = null;
162 private List<AudioBuffer> mAudioBuffers = new LinkedList<AudioBuffer>();
163 private float mPlaybackRate = 0.0f;
164
165 private long mNativeContext;
166
167 /**
168 * Class constructor. On creation, MediaSync is paused, i.e., playback rate is 0.0f.
169 */
170 public MediaSync() {
171 native_setup();
172 }
173
174 private native final void native_setup();
175
176 @Override
177 protected void finalize() {
178 native_finalize();
179 }
180
181 private native final void native_finalize();
182
183 /**
184 * Make sure you call this when you're done to free up any opened
185 * component instance instead of relying on the garbage collector
186 * to do this for you at some point in the future.
187 */
188 public final void release() {
189 returnAudioBuffers();
190 if (mAudioThread != null) {
191 if (mAudioLooper != null) {
192 mAudioLooper.quit();
193 }
194 }
195 setCallback(null, null);
196 native_release();
197 }
198
199 private native final void native_release();
200
201 /**
202 * Sets an asynchronous callback for actionable MediaSync events.
203 * It shouldn't be called inside callback.
204 *
205 * @param cb The callback that will run.
206 * @param handler The Handler that will run the callback. Using null means to use MediaSync's
207 * internal handler if it exists.
208 */
209 public void setCallback(/* MediaSync. */ Callback cb, Handler handler) {
210 synchronized(mCallbackLock) {
211 if (handler != null) {
212 mCallbackHandler = handler;
213 } else {
214 Looper looper;
215 if ((looper = Looper.myLooper()) == null) {
216 looper = Looper.getMainLooper();
217 }
218 if (looper == null) {
219 mCallbackHandler = null;
220 } else {
221 mCallbackHandler = new Handler(looper);
222 }
223 }
224
225 mCallback = cb;
226 }
227 }
228
229 /**
230 * Configures the output surface for MediaSync.
231 *
232 * @param surface Specify a surface on which to render the video data.
233 * @throws IllegalArgumentException if the surface has been released, or is invalid.
234 * or can not be connected.
235 * @throws IllegalStateException if not in the Initialized state, or another surface
236 * has already been configured.
237 */
238 public void configureSurface(Surface surface) {
239 native_configureSurface(surface);
240 }
241
242 private native final void native_configureSurface(Surface surface);
243
244 /**
245 * Configures the audio track for MediaSync.
246 *
247 * @param audioTrack Specify an AudioTrack through which to render the audio data.
Wei Jiaba5997e52015-04-16 13:18:39 -0700248 * @throws IllegalArgumentException if the audioTrack has been released, or is invalid.
Wei Jia071a8b72015-03-09 16:38:25 -0700249 * @throws IllegalStateException if not in the Initialized state, or another audio track
250 * has already been configured.
251 */
Wei Jiaba5997e52015-04-16 13:18:39 -0700252 public void configureAudioTrack(AudioTrack audioTrack) {
253 // AudioTrack has sanity check for configured sample rate.
254 int nativeSampleRateInHz = (audioTrack == null ? 0 : audioTrack.getSampleRate());
255
Wei Jia071a8b72015-03-09 16:38:25 -0700256 native_configureAudioTrack(audioTrack, nativeSampleRateInHz);
257 mAudioTrack = audioTrack;
Wei Jiaba5997e52015-04-16 13:18:39 -0700258 if (audioTrack != null && mAudioThread == null) {
Wei Jia071a8b72015-03-09 16:38:25 -0700259 createAudioThread();
260 }
261 }
262
263 private native final void native_configureAudioTrack(
264 AudioTrack audioTrack, int nativeSampleRateInHz);
265
266 /**
267 * Requests a Surface to use as the input. This may only be called after
268 * {@link #configureSurface}.
269 * <p>
270 * The application is responsible for calling release() on the Surface when
271 * done.
272 * @throws IllegalStateException if not configured, or another input surface has
273 * already been created.
274 */
275 public native final Surface createInputSurface();
276
277 /**
278 * Specifies resampling as audio mode for variable rate playback, i.e.,
279 * resample the waveform based on the requested playback rate to get
280 * a new waveform, and play back the new waveform at the original sampling
281 * frequency.
282 * When rate is larger than 1.0, pitch becomes higher.
283 * When rate is smaller than 1.0, pitch becomes lower.
284 */
285 public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 0;
286
287 /**
288 * Specifies time stretching as audio mode for variable rate playback.
289 * Time stretching changes the duration of the audio samples without
290 * affecting its pitch.
291 * FIXME: implement time strectching.
292 * @hide
293 */
294 public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1;
295
296 /** @hide */
297 @IntDef(
298 value = {
299 PLAYBACK_RATE_AUDIO_MODE_RESAMPLE,
300 PLAYBACK_RATE_AUDIO_MODE_STRETCH })
301 @Retention(RetentionPolicy.SOURCE)
302 public @interface PlaybackRateAudioMode {}
303
304 /**
305 * Sets playback rate. It does same as {@link #setPlaybackRate(float, int)},
306 * except that it always uses {@link #PLAYBACK_RATE_AUDIO_MODE_STRETCH} for audioMode.
307 *
308 * @param rate the ratio between desired playback rate and normal one. 1.0 means normal
309 * playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback,
310 * while value between 0.0 and 1.0 for slower playback.
311 *
312 * @throws IllegalStateException if the internal sync engine or the audio track has not
313 * been initialized.
314 * TODO: unhide when PLAYBACK_RATE_AUDIO_MODE_STRETCH is supported.
315 * @hide
316 */
317 public void setPlaybackRate(float rate) {
318 setPlaybackRate(rate, PLAYBACK_RATE_AUDIO_MODE_STRETCH);
319 }
320
321 /**
322 * Sets playback rate and audio mode.
323 *
324 * <p> The supported audio modes are:
325 * <ul>
326 * <li> {@link #PLAYBACK_RATE_AUDIO_MODE_RESAMPLE}
327 * </ul>
328 *
329 * @param rate the ratio between desired playback rate and normal one. 1.0 means normal
330 * playback speed. 0.0 means stop or pause. Value larger than 1.0 means faster playback,
331 * while value between 0.0 and 1.0 for slower playback.
332 * @param audioMode audio playback mode. Must be one of the supported
333 * audio modes.
334 *
335 * @throws IllegalStateException if the internal sync engine or the audio track has not
336 * been initialized.
337 * @throws IllegalArgumentException if audioMode is not supported.
338 */
339 public void setPlaybackRate(float rate, @PlaybackRateAudioMode int audioMode) {
340 if (!isAudioPlaybackModeSupported(audioMode)) {
341 final String msg = "Audio playback mode " + audioMode + " is not supported";
342 throw new IllegalArgumentException(msg);
343 }
344
345 int status = AudioTrack.SUCCESS;
346 if (mAudioTrack != null) {
Wei Jiaba5997e52015-04-16 13:18:39 -0700347 int nativeSampleRateInHz = mAudioTrack.getSampleRate();
348 int playbackSampleRate = (int)(rate * nativeSampleRateInHz + 0.5);
349 rate = playbackSampleRate / (float)nativeSampleRateInHz;
Wei Jia071a8b72015-03-09 16:38:25 -0700350
351 try {
352 if (rate == 0.0) {
353 mAudioTrack.pause();
354 } else {
355 status = mAudioTrack.setPlaybackRate(playbackSampleRate);
356 mAudioTrack.play();
357 }
358 } catch (IllegalStateException e) {
359 throw e;
360 }
361 }
362
363 if (status != AudioTrack.SUCCESS) {
364 throw new IllegalArgumentException("Fail to set playback rate in audio track");
365 }
366
367 synchronized(mAudioLock) {
368 mPlaybackRate = rate;
369 }
370 if (mPlaybackRate != 0.0 && mAudioThread != null) {
371 postRenderAudio(0);
372 }
373 native_setPlaybackRate(mPlaybackRate);
374 }
375
376 private native final void native_setPlaybackRate(float rate);
377
378 /*
379 * Test whether a given audio playback mode is supported.
380 * TODO query supported AudioPlaybackMode from audio track.
381 */
382 private boolean isAudioPlaybackModeSupported(int mode) {
383 return (mode == PLAYBACK_RATE_AUDIO_MODE_RESAMPLE);
384 }
385
Wei Jia217ec0a2015-04-09 18:48:18 -0700386 /**
387 * Get current playback position.
388 * <p>
389 * The MediaTimestamp represents a clock ticking during media playback. It's represented
390 * by an anchor frame ({@link MediaTimestamp#mediaTimeUs} and {@link MediaTimestamp#nanoTime})
391 * and clock speed ({@link MediaTimestamp#clockRate}). For continous playback with
392 * constant speed, its anchor frame doesn't change that often. Thereafter, it's recommended
393 * to not call this method often.
394 * <p>
395 * To help users to get current playback position, this method always returns the timestamp of
396 * just-rendered frame, i.e., {@link System#nanoTime} and its corresponding media time. They
397 * can be used as current playback position.
398 *
399 * @param timestamp a reference to a non-null MediaTimestamp instance allocated
400 * and owned by caller.
401 * @return true if a timestamp is available, or false if no timestamp is available.
402 * If a timestamp if available, the MediaTimestamp instance is filled in with
403 * playback rate, together with the current media timestamp and the system nanoTime
404 * corresponding to the measured media timestamp.
405 * In the case that no timestamp is available, any supplied instance is left unaltered.
406 */
407 public boolean getTimestamp(@NonNull MediaTimestamp timestamp)
408 {
409 if (timestamp == null) {
410 throw new IllegalArgumentException();
411 }
412 return native_getTimestamp(timestamp);
413 }
414
415 private native final boolean native_getTimestamp(MediaTimestamp timestamp);
416
Wei Jia071a8b72015-03-09 16:38:25 -0700417 /**
418 * Queues the audio data asynchronously for playback (AudioTrack must be in streaming mode).
419 * @param audioData the buffer that holds the data to play. This buffer will be returned
420 * to the client via registered callback.
421 * @param bufferIndex the buffer index used to identify audioData. It will be returned to
422 * the client along with audioData. This helps applications to keep track of audioData.
423 * @param sizeInBytes number of bytes to queue.
424 * @param presentationTimeUs the presentation timestamp in microseconds for the first frame
425 * in the buffer.
426 * @throws IllegalStateException if audio track is not configured or internal configureation
427 * has not been done correctly.
428 */
429 public void queueAudio(
430 ByteBuffer audioData, int bufferIndex, int sizeInBytes, long presentationTimeUs) {
431 if (mAudioTrack == null || mAudioThread == null) {
432 throw new IllegalStateException(
433 "AudioTrack is NOT configured or audio thread is not created");
434 }
435
436 synchronized(mAudioLock) {
437 mAudioBuffers.add(new AudioBuffer(
438 audioData, bufferIndex, sizeInBytes, presentationTimeUs));
439 }
440
441 if (mPlaybackRate != 0.0) {
442 postRenderAudio(0);
443 }
444 }
445
446 // When called on user thread, make sure to check mAudioThread != null.
447 private void postRenderAudio(long delayMillis) {
448 mAudioHandler.postDelayed(new Runnable() {
449 public void run() {
450 synchronized(mAudioLock) {
451 if (mPlaybackRate == 0.0) {
452 return;
453 }
454
455 if (mAudioBuffers.isEmpty()) {
456 return;
457 }
458
459 AudioBuffer audioBuffer = mAudioBuffers.get(0);
460 int sizeWritten = mAudioTrack.write(
461 audioBuffer.mByteBuffer,
462 audioBuffer.mSizeInBytes,
463 AudioTrack.WRITE_NON_BLOCKING);
464 if (sizeWritten > 0) {
465 if (audioBuffer.mPresentationTimeUs != -1) {
466 native_updateQueuedAudioData(
467 audioBuffer.mSizeInBytes, audioBuffer.mPresentationTimeUs);
468 audioBuffer.mPresentationTimeUs = -1;
469 }
470
471 if (sizeWritten == audioBuffer.mSizeInBytes) {
472 postReturnByteBuffer(audioBuffer);
473 mAudioBuffers.remove(0);
474 if (!mAudioBuffers.isEmpty()) {
475 postRenderAudio(0);
476 }
477 return;
478 }
479
480 audioBuffer.mSizeInBytes -= sizeWritten;
481 }
482 // TODO: wait time depends on fullness of audio track.
483 postRenderAudio(10);
484 }
485 }
486 }, delayMillis);
487 }
488
489 private native final void native_updateQueuedAudioData(
490 int sizeInBytes, long presentationTimeUs);
491
492 private final void postReturnByteBuffer(final AudioBuffer audioBuffer) {
493 synchronized(mCallbackLock) {
494 if (mCallbackHandler != null) {
495 final MediaSync sync = this;
496 mCallbackHandler.post(new Runnable() {
497 public void run() {
498 synchronized(mCallbackLock) {
499 if (mCallbackHandler == null
500 || mCallbackHandler.getLooper().getThread()
501 != Thread.currentThread()) {
502 // callback handler has been changed.
503 return;
504 }
505 if (mCallback != null) {
506 mCallback.onReturnAudioBuffer(sync, audioBuffer.mByteBuffer,
507 audioBuffer.mBufferIndex);
508 }
509 }
510 }
511 });
512 }
513 }
514 }
515
516 private final void returnAudioBuffers() {
517 synchronized(mAudioLock) {
518 for (AudioBuffer audioBuffer: mAudioBuffers) {
519 postReturnByteBuffer(audioBuffer);
520 }
521 mAudioBuffers.clear();
522 }
523 }
524
525 private void createAudioThread() {
526 mAudioThread = new Thread() {
527 @Override
528 public void run() {
529 Looper.prepare();
530 synchronized(mAudioLock) {
531 mAudioLooper = Looper.myLooper();
532 mAudioHandler = new Handler();
533 mAudioLock.notify();
534 }
535 Looper.loop();
536 }
537 };
538 mAudioThread.start();
539
540 synchronized(mAudioLock) {
541 try {
542 mAudioLock.wait();
543 } catch(InterruptedException e) {
544 }
545 }
546 }
547
548 static {
549 System.loadLibrary("media_jni");
550 native_init();
551 }
552
553 private static native final void native_init();
554}