Home | History | Annotate | Download | only in media
      1 /*
      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 
     17 package android.media;
     18 
     19 import android.annotation.IntDef;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.media.AudioTrack;
     23 import android.media.PlaybackParams;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.util.Log;
     28 import android.view.Surface;
     29 
     30 import java.lang.annotation.Retention;
     31 import java.lang.annotation.RetentionPolicy;
     32 import java.nio.ByteBuffer;
     33 import java.util.concurrent.TimeUnit;
     34 import java.util.LinkedList;
     35 import java.util.List;
     36 
     37 /**
     38  * MediaSync class can be used to synchronously play audio and video streams.
     39  * It can be used to play audio-only or video-only stream, too.
     40  *
     41  * <p>MediaSync is generally used like this:
     42  * <pre>
     43  * MediaSync sync = new MediaSync();
     44  * sync.setSurface(surface);
     45  * Surface inputSurface = sync.createInputSurface();
     46  * ...
     47  * // MediaCodec videoDecoder = ...;
     48  * videoDecoder.configure(format, inputSurface, ...);
     49  * ...
     50  * sync.setAudioTrack(audioTrack);
     51  * sync.setCallback(new MediaSync.Callback() {
     52  *     {@literal @Override}
     53  *     public void onAudioBufferConsumed(MediaSync sync, ByteBuffer audioBuffer, int bufferId) {
     54  *         ...
     55  *     }
     56  * }, null);
     57  * // This needs to be done since sync is paused on creation.
     58  * sync.setPlaybackParams(new PlaybackParams().setSpeed(1.f));
     59  *
     60  * for (;;) {
     61  *   ...
     62  *   // send video frames to surface for rendering, e.g., call
     63  *   // videoDecoder.releaseOutputBuffer(videoOutputBufferIx, videoPresentationTimeNs);
     64  *   // More details are available as below.
     65  *   ...
     66  *   sync.queueAudio(audioByteBuffer, bufferId, audioPresentationTimeUs); // non-blocking.
     67  *   // The audioByteBuffer and bufferId will be returned via callback.
     68  *   // More details are available as below.
     69  *   ...
     70  *     ...
     71  * }
     72  * sync.setPlaybackParams(new PlaybackParams().setSpeed(0.f));
     73  * sync.release();
     74  * sync = null;
     75  *
     76  * // The following code snippet illustrates how video/audio raw frames are created by
     77  * // MediaCodec's, how they are fed to MediaSync and how they are returned by MediaSync.
     78  * // This is the callback from MediaCodec.
     79  * onOutputBufferAvailable(MediaCodec codec, int bufferId, BufferInfo info) {
     80  *     // ...
     81  *     if (codec == videoDecoder) {
     82  *         // surface timestamp must contain media presentation time in nanoseconds.
     83  *         codec.releaseOutputBuffer(bufferId, 1000 * info.presentationTime);
     84  *     } else {
     85  *         ByteBuffer audioByteBuffer = codec.getOutputBuffer(bufferId);
     86  *         sync.queueAudio(audioByteBuffer, bufferId, info.presentationTime);
     87  *     }
     88  *     // ...
     89  * }
     90  *
     91  * // This is the callback from MediaSync.
     92  * onAudioBufferConsumed(MediaSync sync, ByteBuffer buffer, int bufferId) {
     93  *     // ...
     94  *     audioDecoder.releaseBuffer(bufferId, false);
     95  *     // ...
     96  * }
     97  *
     98  * </pre>
     99  *
    100  * The client needs to configure corresponding sink by setting the Surface and/or AudioTrack
    101  * based on the stream type it will play.
    102  * <p>
    103  * For video, the client needs to call {@link #createInputSurface} to obtain a surface on
    104  * which it will render video frames.
    105  * <p>
    106  * For audio, the client needs to set up audio track correctly, e.g., using {@link
    107  * AudioTrack#MODE_STREAM}. The audio buffers are sent to MediaSync directly via {@link
    108  * #queueAudio}, and are returned to the client via {@link Callback#onAudioBufferConsumed}
    109  * asynchronously. The client should not modify an audio buffer till it's returned.
    110  * <p>
    111  * The client can optionally pre-fill audio/video buffers by setting playback rate to 0.0,
    112  * and then feed audio/video buffers to corresponding components. This can reduce possible
    113  * initial underrun.
    114  * <p>
    115  */
    116 public final class MediaSync {
    117     /**
    118      * MediaSync callback interface. Used to notify the user asynchronously
    119      * of various MediaSync events.
    120      */
    121     public static abstract class Callback {
    122         /**
    123          * Called when returning an audio buffer which has been consumed.
    124          *
    125          * @param sync The MediaSync object.
    126          * @param audioBuffer The returned audio buffer.
    127          * @param bufferId The ID associated with audioBuffer as passed into
    128          *     {@link MediaSync#queueAudio}.
    129          */
    130         public abstract void onAudioBufferConsumed(
    131                 @NonNull MediaSync sync, @NonNull ByteBuffer audioBuffer, int bufferId);
    132     }
    133 
    134     /** Audio track failed.
    135      * @see android.media.MediaSync.OnErrorListener
    136      */
    137     public static final int MEDIASYNC_ERROR_AUDIOTRACK_FAIL = 1;
    138 
    139     /** The surface failed to handle video buffers.
    140      * @see android.media.MediaSync.OnErrorListener
    141      */
    142     public static final int MEDIASYNC_ERROR_SURFACE_FAIL = 2;
    143 
    144     /**
    145      * Interface definition of a callback to be invoked when there
    146      * has been an error during an asynchronous operation (other errors
    147      * will throw exceptions at method call time).
    148      */
    149     public interface OnErrorListener {
    150         /**
    151          * Called to indicate an error.
    152          *
    153          * @param sync The MediaSync the error pertains to
    154          * @param what The type of error that has occurred:
    155          * <ul>
    156          * <li>{@link #MEDIASYNC_ERROR_AUDIOTRACK_FAIL}
    157          * <li>{@link #MEDIASYNC_ERROR_SURFACE_FAIL}
    158          * </ul>
    159          * @param extra an extra code, specific to the error. Typically
    160          * implementation dependent.
    161          */
    162         void onError(@NonNull MediaSync sync, int what, int extra);
    163     }
    164 
    165     private static final String TAG = "MediaSync";
    166 
    167     private static final int EVENT_CALLBACK = 1;
    168     private static final int EVENT_SET_CALLBACK = 2;
    169 
    170     private static final int CB_RETURN_AUDIO_BUFFER = 1;
    171 
    172     private static class AudioBuffer {
    173         public ByteBuffer mByteBuffer;
    174         public int mBufferIndex;
    175         long mPresentationTimeUs;
    176 
    177         public AudioBuffer(@NonNull ByteBuffer byteBuffer, int bufferId,
    178                            long presentationTimeUs) {
    179             mByteBuffer = byteBuffer;
    180             mBufferIndex = bufferId;
    181             mPresentationTimeUs = presentationTimeUs;
    182         }
    183     }
    184 
    185     private final Object mCallbackLock = new Object();
    186     private Handler mCallbackHandler = null;
    187     private MediaSync.Callback mCallback = null;
    188 
    189     private final Object mOnErrorListenerLock = new Object();
    190     private Handler mOnErrorListenerHandler = null;
    191     private MediaSync.OnErrorListener mOnErrorListener = null;
    192 
    193     private Thread mAudioThread = null;
    194     // Created on mAudioThread when mAudioThread is started. When used on user thread, they should
    195     // be guarded by checking mAudioThread.
    196     private Handler mAudioHandler = null;
    197     private Looper mAudioLooper = null;
    198 
    199     private final Object mAudioLock = new Object();
    200     private AudioTrack mAudioTrack = null;
    201     private List<AudioBuffer> mAudioBuffers = new LinkedList<AudioBuffer>();
    202     // this is only used for paused/running decisions, so it is not affected by clock drift
    203     private float mPlaybackRate = 0.0f;
    204 
    205     private long mNativeContext;
    206 
    207     /**
    208      * Class constructor. On creation, MediaSync is paused, i.e., playback rate is 0.0f.
    209      */
    210     public MediaSync() {
    211         native_setup();
    212     }
    213 
    214     private native final void native_setup();
    215 
    216     @Override
    217     protected void finalize() {
    218         native_finalize();
    219     }
    220 
    221     private native final void native_finalize();
    222 
    223     /**
    224      * Make sure you call this when you're done to free up any opened
    225      * component instance instead of relying on the garbage collector
    226      * to do this for you at some point in the future.
    227      */
    228     public final void release() {
    229         returnAudioBuffers();
    230         if (mAudioThread != null) {
    231             if (mAudioLooper != null) {
    232                 mAudioLooper.quit();
    233             }
    234         }
    235         setCallback(null, null);
    236         native_release();
    237     }
    238 
    239     private native final void native_release();
    240 
    241     /**
    242      * Sets an asynchronous callback for actionable MediaSync events.
    243      * <p>
    244      * This method can be called multiple times to update a previously set callback. If the
    245      * handler is changed, undelivered notifications scheduled for the old handler may be dropped.
    246      * <p>
    247      * <b>Do not call this inside callback.</b>
    248      *
    249      * @param cb The callback that will run. Use {@code null} to stop receiving callbacks.
    250      * @param handler The Handler that will run the callback. Use {@code null} to use MediaSync's
    251      *     internal handler if it exists.
    252      */
    253     public void setCallback(@Nullable /* MediaSync. */ Callback cb, @Nullable Handler handler) {
    254         synchronized(mCallbackLock) {
    255             if (handler != null) {
    256                 mCallbackHandler = handler;
    257             } else {
    258                 Looper looper;
    259                 if ((looper = Looper.myLooper()) == null) {
    260                     looper = Looper.getMainLooper();
    261                 }
    262                 if (looper == null) {
    263                     mCallbackHandler = null;
    264                 } else {
    265                     mCallbackHandler = new Handler(looper);
    266                 }
    267             }
    268 
    269             mCallback = cb;
    270         }
    271     }
    272 
    273     /**
    274      * Sets an asynchronous callback for error events.
    275      * <p>
    276      * This method can be called multiple times to update a previously set listener. If the
    277      * handler is changed, undelivered notifications scheduled for the old handler may be dropped.
    278      * <p>
    279      * <b>Do not call this inside callback.</b>
    280      *
    281      * @param listener The callback that will run. Use {@code null} to stop receiving callbacks.
    282      * @param handler The Handler that will run the callback. Use {@code null} to use MediaSync's
    283      *     internal handler if it exists.
    284      */
    285     public void setOnErrorListener(@Nullable /* MediaSync. */ OnErrorListener listener,
    286             @Nullable Handler handler) {
    287         synchronized(mOnErrorListenerLock) {
    288             if (handler != null) {
    289                 mOnErrorListenerHandler = handler;
    290             } else {
    291                 Looper looper;
    292                 if ((looper = Looper.myLooper()) == null) {
    293                     looper = Looper.getMainLooper();
    294                 }
    295                 if (looper == null) {
    296                     mOnErrorListenerHandler = null;
    297                 } else {
    298                     mOnErrorListenerHandler = new Handler(looper);
    299                 }
    300             }
    301 
    302             mOnErrorListener = listener;
    303         }
    304     }
    305 
    306     /**
    307      * Sets the output surface for MediaSync.
    308      * <p>
    309      * Currently, this is only supported in the Initialized state.
    310      *
    311      * @param surface Specify a surface on which to render the video data.
    312      * @throws IllegalArgumentException if the surface has been released, is invalid,
    313      *     or can not be connected.
    314      * @throws IllegalStateException if setting the surface is not supported, e.g.
    315      *     not in the Initialized state, or another surface has already been set.
    316      */
    317     public void setSurface(@Nullable Surface surface) {
    318         native_setSurface(surface);
    319     }
    320 
    321     private native final void native_setSurface(@Nullable Surface surface);
    322 
    323     /**
    324      * Sets the audio track for MediaSync.
    325      * <p>
    326      * Currently, this is only supported in the Initialized state.
    327      *
    328      * @param audioTrack Specify an AudioTrack through which to render the audio data.
    329      * @throws IllegalArgumentException if the audioTrack has been released, or is invalid.
    330      * @throws IllegalStateException if setting the audio track is not supported, e.g.
    331      *     not in the Initialized state, or another audio track has already been set.
    332      */
    333     public void setAudioTrack(@Nullable AudioTrack audioTrack) {
    334         native_setAudioTrack(audioTrack);
    335         mAudioTrack = audioTrack;
    336         if (audioTrack != null && mAudioThread == null) {
    337             createAudioThread();
    338         }
    339     }
    340 
    341     private native final void native_setAudioTrack(@Nullable AudioTrack audioTrack);
    342 
    343     /**
    344      * Requests a Surface to use as the input. This may only be called after
    345      * {@link #setSurface}.
    346      * <p>
    347      * The application is responsible for calling release() on the Surface when
    348      * done.
    349      * @throws IllegalStateException if not set, or another input surface has
    350      *     already been created.
    351      */
    352     @NonNull
    353     public native final Surface createInputSurface();
    354 
    355     /**
    356      * Sets playback rate using {@link PlaybackParams}.
    357      * <p>
    358      * When using MediaSync with {@link AudioTrack}, set playback params using this
    359      * call instead of calling it directly on the track, so that the sync is aware of
    360      * the params change.
    361      * <p>
    362      * This call also works if there is no audio track.
    363      *
    364      * @param params the playback params to use. {@link PlaybackParams#getSpeed
    365      *     Speed} is the ratio between desired playback rate and normal one. 1.0 means
    366      *     normal playback speed. 0.0 means pause. Value larger than 1.0 means faster playback,
    367      *     while value between 0.0 and 1.0 for slower playback. <b>Note:</b> the normal rate
    368      *     does not change as a result of this call. To restore the original rate at any time,
    369      *     use speed of 1.0.
    370      *
    371      * @throws IllegalStateException if the internal sync engine or the audio track has not
    372      *     been initialized.
    373      * @throws IllegalArgumentException if the params are not supported.
    374      */
    375     public void setPlaybackParams(@NonNull PlaybackParams params) {
    376         synchronized(mAudioLock) {
    377             mPlaybackRate = native_setPlaybackParams(params);;
    378         }
    379         if (mPlaybackRate != 0.0 && mAudioThread != null) {
    380             postRenderAudio(0);
    381         }
    382     }
    383 
    384     /**
    385      * Gets the playback rate using {@link PlaybackParams}.
    386      *
    387      * @return the playback rate being used.
    388      *
    389      * @throws IllegalStateException if the internal sync engine or the audio track has not
    390      *     been initialized.
    391      */
    392     @NonNull
    393     public native PlaybackParams getPlaybackParams();
    394 
    395     private native float native_setPlaybackParams(@NonNull PlaybackParams params);
    396 
    397     /**
    398      * Sets A/V sync mode.
    399      *
    400      * @param params the A/V sync params to apply
    401      *
    402      * @throws IllegalStateException if the internal player engine has not been
    403      * initialized.
    404      * @throws IllegalArgumentException if params are not supported.
    405      */
    406     public void setSyncParams(@NonNull SyncParams params) {
    407         synchronized(mAudioLock) {
    408             mPlaybackRate = native_setSyncParams(params);;
    409         }
    410         if (mPlaybackRate != 0.0 && mAudioThread != null) {
    411             postRenderAudio(0);
    412         }
    413     }
    414 
    415     private native float native_setSyncParams(@NonNull SyncParams params);
    416 
    417     /**
    418      * Gets the A/V sync mode.
    419      *
    420      * @return the A/V sync params
    421      *
    422      * @throws IllegalStateException if the internal player engine has not been
    423      * initialized.
    424      */
    425     @NonNull
    426     public native SyncParams getSyncParams();
    427 
    428     /**
    429      * Flushes all buffers from the sync object.
    430      * <p>
    431      * All pending unprocessed audio and video buffers are discarded. If an audio track was
    432      * configured, it is flushed and stopped. If a video output surface was configured, the
    433      * last frame queued to it is left on the frame. Queue a blank video frame to clear the
    434      * surface,
    435      * <p>
    436      * No callbacks are received for the flushed buffers.
    437      *
    438      * @throws IllegalStateException if the internal player engine has not been
    439      * initialized.
    440      */
    441     public void flush() {
    442         synchronized(mAudioLock) {
    443             mAudioBuffers.clear();
    444             mCallbackHandler.removeCallbacksAndMessages(null);
    445         }
    446         if (mAudioTrack != null) {
    447             mAudioTrack.pause();
    448             mAudioTrack.flush();
    449             // Call stop() to signal to the AudioSink to completely fill the
    450             // internal buffer before resuming playback.
    451             mAudioTrack.stop();
    452         }
    453         native_flush();
    454     }
    455 
    456     private native final void native_flush();
    457 
    458     /**
    459      * Get current playback position.
    460      * <p>
    461      * The MediaTimestamp represents how the media time correlates to the system time in
    462      * a linear fashion using an anchor and a clock rate. During regular playback, the media
    463      * time moves fairly constantly (though the anchor frame may be rebased to a current
    464      * system time, the linear correlation stays steady). Therefore, this method does not
    465      * need to be called often.
    466      * <p>
    467      * To help users get current playback position, this method always anchors the timestamp
    468      * to the current {@link System#nanoTime system time}, so
    469      * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
    470      *
    471      * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
    472      *         is available, e.g. because the media player has not been initialized.
    473      *
    474      * @see MediaTimestamp
    475      */
    476     @Nullable
    477     public MediaTimestamp getTimestamp()
    478     {
    479         try {
    480             // TODO: create the timestamp in native
    481             MediaTimestamp timestamp = new MediaTimestamp();
    482             if (native_getTimestamp(timestamp)) {
    483                 return timestamp;
    484             } else {
    485                 return null;
    486             }
    487         } catch (IllegalStateException e) {
    488             return null;
    489         }
    490     }
    491 
    492     private native final boolean native_getTimestamp(@NonNull MediaTimestamp timestamp);
    493 
    494     /**
    495      * Queues the audio data asynchronously for playback (AudioTrack must be in streaming mode).
    496      * If the audio track was flushed as a result of {@link #flush}, it will be restarted.
    497      * @param audioData the buffer that holds the data to play. This buffer will be returned
    498      *     to the client via registered callback.
    499      * @param bufferId an integer used to identify audioData. It will be returned to
    500      *     the client along with audioData. This helps applications to keep track of audioData,
    501      *     e.g., it can be used to store the output buffer index used by the audio codec.
    502      * @param presentationTimeUs the presentation timestamp in microseconds for the first frame
    503      *     in the buffer.
    504      * @throws IllegalStateException if audio track is not set or internal configureation
    505      *     has not been done correctly.
    506      */
    507     public void queueAudio(
    508             @NonNull ByteBuffer audioData, int bufferId, long presentationTimeUs) {
    509         if (mAudioTrack == null || mAudioThread == null) {
    510             throw new IllegalStateException(
    511                     "AudioTrack is NOT set or audio thread is not created");
    512         }
    513 
    514         synchronized(mAudioLock) {
    515             mAudioBuffers.add(new AudioBuffer(audioData, bufferId, presentationTimeUs));
    516         }
    517 
    518         if (mPlaybackRate != 0.0) {
    519             postRenderAudio(0);
    520         }
    521     }
    522 
    523     // When called on user thread, make sure to check mAudioThread != null.
    524     private void postRenderAudio(long delayMillis) {
    525         mAudioHandler.postDelayed(new Runnable() {
    526             public void run() {
    527                 synchronized(mAudioLock) {
    528                     if (mPlaybackRate == 0.0) {
    529                         return;
    530                     }
    531 
    532                     if (mAudioBuffers.isEmpty()) {
    533                         return;
    534                     }
    535 
    536                     AudioBuffer audioBuffer = mAudioBuffers.get(0);
    537                     int size = audioBuffer.mByteBuffer.remaining();
    538                     // restart audio track after flush
    539                     if (size > 0 && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
    540                         try {
    541                             mAudioTrack.play();
    542                         } catch (IllegalStateException e) {
    543                             Log.w(TAG, "could not start audio track");
    544                         }
    545                     }
    546                     int sizeWritten = mAudioTrack.write(
    547                             audioBuffer.mByteBuffer,
    548                             size,
    549                             AudioTrack.WRITE_NON_BLOCKING);
    550                     if (sizeWritten > 0) {
    551                         if (audioBuffer.mPresentationTimeUs != -1) {
    552                             native_updateQueuedAudioData(
    553                                     size, audioBuffer.mPresentationTimeUs);
    554                             audioBuffer.mPresentationTimeUs = -1;
    555                         }
    556 
    557                         if (sizeWritten == size) {
    558                             postReturnByteBuffer(audioBuffer);
    559                             mAudioBuffers.remove(0);
    560                             if (!mAudioBuffers.isEmpty()) {
    561                                 postRenderAudio(0);
    562                             }
    563                             return;
    564                         }
    565                     }
    566                     long pendingTimeMs = TimeUnit.MICROSECONDS.toMillis(
    567                             native_getPlayTimeForPendingAudioFrames());
    568                     postRenderAudio(pendingTimeMs / 2);
    569                 }
    570             }
    571         }, delayMillis);
    572     }
    573 
    574     private native final void native_updateQueuedAudioData(
    575             int sizeInBytes, long presentationTimeUs);
    576 
    577     private native final long native_getPlayTimeForPendingAudioFrames();
    578 
    579     private final void postReturnByteBuffer(@NonNull final AudioBuffer audioBuffer) {
    580         synchronized(mCallbackLock) {
    581             if (mCallbackHandler != null) {
    582                 final MediaSync sync = this;
    583                 mCallbackHandler.post(new Runnable() {
    584                     public void run() {
    585                         Callback callback;
    586                         synchronized(mCallbackLock) {
    587                             callback = mCallback;
    588                             if (mCallbackHandler == null
    589                                     || mCallbackHandler.getLooper().getThread()
    590                                             != Thread.currentThread()) {
    591                                 // callback handler has been changed.
    592                                 return;
    593                             }
    594                         }
    595                         if (callback != null) {
    596                             callback.onAudioBufferConsumed(sync, audioBuffer.mByteBuffer,
    597                                     audioBuffer.mBufferIndex);
    598                         }
    599                     }
    600                 });
    601             }
    602         }
    603     }
    604 
    605     private final void returnAudioBuffers() {
    606         synchronized(mAudioLock) {
    607             for (AudioBuffer audioBuffer: mAudioBuffers) {
    608                 postReturnByteBuffer(audioBuffer);
    609             }
    610             mAudioBuffers.clear();
    611         }
    612     }
    613 
    614     private void createAudioThread() {
    615         mAudioThread = new Thread() {
    616             @Override
    617             public void run() {
    618                 Looper.prepare();
    619                 synchronized(mAudioLock) {
    620                     mAudioLooper = Looper.myLooper();
    621                     mAudioHandler = new Handler();
    622                     mAudioLock.notify();
    623                 }
    624                 Looper.loop();
    625             }
    626         };
    627         mAudioThread.start();
    628 
    629         synchronized(mAudioLock) {
    630             try {
    631                 mAudioLock.wait();
    632             } catch(InterruptedException e) {
    633             }
    634         }
    635     }
    636 
    637     static {
    638         System.loadLibrary("media_jni");
    639         native_init();
    640     }
    641 
    642     private static native final void native_init();
    643 }
    644