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 com.android.usbtuner.exoplayer;
18 
19 import android.media.AudioFormat;
20 import android.media.MediaCodec.CryptoException;
21 import android.media.MediaDataSource;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.support.annotation.IntDef;
25 import android.view.Surface;
26 
27 import com.google.android.exoplayer.DummyTrackRenderer;
28 import com.google.android.exoplayer.ExoPlaybackException;
29 import com.google.android.exoplayer.ExoPlayer;
30 import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
31 import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
32 import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
33 import com.google.android.exoplayer.TrackRenderer;
34 import com.google.android.exoplayer.audio.AudioCapabilities;
35 import com.google.android.exoplayer.audio.AudioTrack;
36 import com.android.usbtuner.data.Cea708Data;
37 import com.android.usbtuner.data.Cea708Data.CaptionEvent;
38 import com.android.usbtuner.exoplayer.Cea708TextTrackRenderer.CcListener;
39 import com.android.usbtuner.exoplayer.ac3.Ac3TrackRenderer;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 
44 /**
45  * MPEG-2 TS stream player implementation using ExoPlayer.
46  */
47 public class MpegTsPlayer implements ExoPlayer.Listener,
48         MediaCodecVideoTrackRenderer.EventListener, Ac3TrackRenderer.EventListener {
49     private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
50 
51     /**
52      * Interface definition for building specific track renderers.
53      */
54     public interface RendererBuilder {
buildRenderers(MpegTsPlayer mpegTsPlayer, MediaDataSource dataSource, RendererBuilderCallback callback)55         void buildRenderers(MpegTsPlayer mpegTsPlayer, MediaDataSource dataSource,
56                 RendererBuilderCallback callback);
57     }
58 
59     /**
60      * Interface definition for {@link RendererBuilder#buildRenderers} to notify the result.
61      */
62     public interface RendererBuilderCallback {
onRenderers(String[][] trackNames, TrackRenderer[] renderers)63         void onRenderers(String[][] trackNames, TrackRenderer[] renderers);
onRenderersError(Exception e)64         void onRenderersError(Exception e);
65     }
66 
67     /**
68      * Interface definition for a callback to be notified of changes in player state.
69      */
70     public interface Listener {
onStateChanged(int generation, boolean playWhenReady, int playbackState)71         void onStateChanged(int generation, boolean playWhenReady, int playbackState);
onError(int generation, Exception e)72         void onError(int generation, Exception e);
onVideoSizeChanged(int generation, int width, int height, float pixelWidthHeightRatio)73         void onVideoSizeChanged(int generation, int width, int height,
74                 float pixelWidthHeightRatio);
onDrawnToSurface(MpegTsPlayer player, Surface surface)75         void onDrawnToSurface(MpegTsPlayer player, Surface surface);
onAudioUnplayable(int generation)76         void onAudioUnplayable(int generation);
77     }
78 
79     /**
80      * Interface definition for a callback to be notified of changes on video display.
81      */
82     public interface VideoEventListener {
83         /**
84          * Notifies the caption event.
85          */
onEmitCaptionEvent(CaptionEvent event)86         void onEmitCaptionEvent(CaptionEvent event);
87 
88         /**
89          * Notifies the discovered caption service number.
90          */
onDiscoverCaptionServiceNumber(int serviceNumber)91         void onDiscoverCaptionServiceNumber(int serviceNumber);
92     }
93 
94     // Constants pulled into this class for convenience.
95     @IntDef({STATE_IDLE, STATE_PREPARING, STATE_BUFFERING, STATE_READY, STATE_ENDED})
96     @Retention(RetentionPolicy.SOURCE)
97     public @interface PlaybackState {}
98     public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
99     public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
100     public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
101     public static final int STATE_READY = ExoPlayer.STATE_READY;
102     public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
103 
104     public static final int RENDERER_COUNT = 3;
105     public static final int MIN_BUFFER_MS = 200;
106     public static final int MIN_REBUFFER_MS = 500;
107 
108     @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT})
109     @Retention(RetentionPolicy.SOURCE)
110     public @interface TrackType {}
111     public static final int TRACK_TYPE_VIDEO = 0;
112     public static final int TRACK_TYPE_AUDIO = 1;
113     public static final int TRACK_TYPE_TEXT = 2;
114 
115     @IntDef({RENDERER_BUILDING_STATE_IDLE, RENDERER_BUILDING_STATE_BUILDING,
116         RENDERER_BUILDING_STATE_BUILT})
117     @Retention(RetentionPolicy.SOURCE)
118     public @interface RendererBuildingState {}
119     private static final int RENDERER_BUILDING_STATE_IDLE = 1;
120     private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
121     private static final int RENDERER_BUILDING_STATE_BUILT = 3;
122 
123     private final RendererBuilder mRendererBuilder;
124     private final ExoPlayer mPlayer;
125     private final Handler mMainHandler;
126     private final int mPlayerGeneration;
127     private final AudioCapabilities mAudioCapabilities;
128 
129     private Listener mListener;
130     @RendererBuildingState private int mRendererBuildingState;
131     @PlaybackState private int mLastReportedPlaybackState;
132     private boolean mLastReportedPlayWhenReady;
133 
134     private Surface mSurface;
135     private InternalRendererBuilderCallback mBuilderCallback;
136     private TrackRenderer mVideoRenderer;
137     private TrackRenderer mAudioRenderer;
138 
139     private String[][] mTrackNames;
140     private int[] mSelectedTracks;
141 
142     private Cea708TextTrackRenderer mTextRenderer;
143     private CcListener mCcListener;
144     private VideoEventListener mVideoEventListener;
145 
MpegTsPlayer(int playerGeneration, RendererBuilder rendererBuilder, Handler handler, AudioCapabilities capabilities, Listener listener)146     public MpegTsPlayer(int playerGeneration, RendererBuilder rendererBuilder, Handler handler,
147             AudioCapabilities capabilities, Listener listener) {
148         mRendererBuilder = rendererBuilder;
149         mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS);
150         mPlayer.addListener(this);
151         mMainHandler = handler;
152         mPlayerGeneration = playerGeneration;
153         mAudioCapabilities = capabilities;
154         mLastReportedPlaybackState = STATE_IDLE;
155         mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
156         mSelectedTracks = new int[RENDERER_COUNT];
157         mCcListener = new MpegTsCcListener();
158         mListener = listener;
159     }
160 
setVideoEventListener(VideoEventListener videoEventListener)161     public void setVideoEventListener(VideoEventListener videoEventListener) {
162         mVideoEventListener = videoEventListener;
163     }
164 
setCaptionServiceNumber(int captionServiceNumber)165     public void setCaptionServiceNumber(int captionServiceNumber) {
166         mCaptionServiceNumber = captionServiceNumber;
167         if (mTextRenderer != null) {
168             mPlayer.sendMessage(mTextRenderer,
169                     Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
170         }
171     }
172 
setSurface(Surface surface)173     public void setSurface(Surface surface) {
174         mSurface = surface;
175         pushSurface(false);
176     }
177 
getSurface()178     public Surface getSurface() {
179         return mSurface;
180     }
181 
blockingClearSurface()182     public void blockingClearSurface() {
183         mSurface = null;
184         pushSurface(true);
185     }
186 
getTracks(int type)187     public String[] getTracks(int type) {
188         return mTrackNames == null ? null : mTrackNames[type];
189     }
190 
getSelectedTrackIndex(int type)191     public int getSelectedTrackIndex(int type) {
192         return mSelectedTracks[type];
193     }
194 
selectTrack(int type, int index)195     public void selectTrack(int type, int index) {
196         if (mSelectedTracks[type] == index) {
197             return;
198         }
199         mSelectedTracks[type] = index;
200         pushTrackSelection(type, true);
201     }
202 
prepare(MediaDataSource source)203     public void prepare(MediaDataSource source) {
204         if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
205             mPlayer.stop();
206         }
207         if (mBuilderCallback != null) {
208             mBuilderCallback.cancel();
209         }
210         mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
211         maybeReportPlayerState();
212         mBuilderCallback = new InternalRendererBuilderCallback();
213         mRendererBuilder.buildRenderers(this, source, mBuilderCallback);
214     }
215 
onRenderers(String[][] trackNames, TrackRenderer[] renderers)216     /* package */ void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
217         mBuilderCallback = null;
218 
219         // Normalize the results.
220         if (trackNames == null) {
221             trackNames = new String[RENDERER_COUNT][];
222         }
223         for (int i = 0; i < RENDERER_COUNT; i++) {
224             if (renderers[i] == null) {
225                 // Convert a null renderer to a dummy renderer.
226                 renderers[i] = new DummyTrackRenderer();
227             }
228         }
229         mVideoRenderer = renderers[TRACK_TYPE_VIDEO];
230         mAudioRenderer = renderers[TRACK_TYPE_AUDIO];
231         mTextRenderer = (Cea708TextTrackRenderer) renderers[TRACK_TYPE_TEXT];
232         mTextRenderer.setCcListener(mCcListener);
233         mPlayer.sendMessage(
234                 mTextRenderer, Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
235         mTrackNames = trackNames;
236         mRendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
237         pushSurface(false);
238         mPlayer.prepare(renderers);
239         pushTrackSelection(TRACK_TYPE_VIDEO, true);
240         pushTrackSelection(TRACK_TYPE_AUDIO, true);
241         pushTrackSelection(TRACK_TYPE_TEXT, true);
242     }
243 
onRenderersError(Exception e)244     /* package */ void onRenderersError(Exception e) {
245         mBuilderCallback = null;
246         if (mListener != null) {
247             mListener.onError(mPlayerGeneration, e);
248         }
249         mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
250         maybeReportPlayerState();
251     }
252 
setPlayWhenReady(boolean playWhenReady)253     public void setPlayWhenReady(boolean playWhenReady) {
254         mPlayer.setPlayWhenReady(playWhenReady);
255     }
256 
seekTo(long positionMs)257     public void seekTo(long positionMs) {
258         mPlayer.seekTo(positionMs);
259     }
260 
release()261     public void release() {
262         if (mBuilderCallback != null) {
263             mBuilderCallback.cancel();
264             mBuilderCallback = null;
265         }
266         mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
267         mSurface = null;
268         mListener = null;
269         mPlayer.release();
270     }
271 
getPlaybackState()272     @PlaybackState public int getPlaybackState() {
273         if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
274             return STATE_PREPARING;
275         }
276         return mPlayer.getPlaybackState();
277     }
278 
isPlaying()279     public boolean isPlaying() {
280         @PlaybackState int state = getPlaybackState();
281         return (state == STATE_READY || state == STATE_BUFFERING)
282                 && mPlayer.getPlayWhenReady();
283     }
284 
isBuffering()285     public boolean isBuffering() {
286         return getPlaybackState() == STATE_BUFFERING;
287     }
288 
getCurrentPosition()289     public long getCurrentPosition() {
290         return mPlayer.getCurrentPosition();
291     }
292 
getDuration()293     public long getDuration() {
294         return mPlayer.getDuration();
295     }
296 
getBufferedPercentage()297     public int getBufferedPercentage() {
298         return mPlayer.getBufferedPercentage();
299     }
300 
getPlayWhenReady()301     public boolean getPlayWhenReady() {
302         return mPlayer.getPlayWhenReady();
303     }
304 
setVolume(float volume)305     public void setVolume(float volume) {
306         mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume);
307     }
308 
setAudioTrack(boolean enable)309     public void setAudioTrack(boolean enable) {
310         mPlayer.sendMessage(mAudioRenderer, Ac3TrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0);
311     }
312 
isAc3Playable()313     public boolean isAc3Playable() {
314         return mAudioCapabilities != null
315                 && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3);
316     }
317 
onAudioUnplayable()318     public void onAudioUnplayable() {
319         if (mListener != null) {
320             mListener.onAudioUnplayable(mPlayerGeneration);
321         }
322     }
323 
getPlaybackLooper()324     /* package */ Looper getPlaybackLooper() {
325         return mPlayer.getPlaybackLooper();
326     }
327 
getMainHandler()328     /* package */ Handler getMainHandler() {
329         return mMainHandler;
330     }
331 
332     @Override
onPlayerStateChanged(boolean playWhenReady, int state)333     public void onPlayerStateChanged(boolean playWhenReady, int state) {
334         maybeReportPlayerState();
335     }
336 
337     @Override
onPlayerError(ExoPlaybackException exception)338     public void onPlayerError(ExoPlaybackException exception) {
339         mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
340         if (mListener != null) {
341             mListener.onError(mPlayerGeneration, exception);
342         }
343     }
344 
345     @Override
onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio)346     public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
347             float pixelWidthHeightRatio) {
348         if (mListener != null) {
349             mListener.onVideoSizeChanged(mPlayerGeneration, width, height, pixelWidthHeightRatio);
350         }
351     }
352 
353     @Override
onDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs)354     public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
355             long initializationDurationMs) {
356         // TODO
357     }
358 
359     @Override
onDecoderInitializationError(DecoderInitializationException e)360     public void onDecoderInitializationError(DecoderInitializationException e) {
361         // Do nothing.
362     }
363 
364     @Override
onAudioTrackInitializationError(AudioTrack.InitializationException e)365     public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
366         onAudioUnplayable();
367     }
368 
369     @Override
onAudioTrackWriteError(AudioTrack.WriteException e)370     public void onAudioTrackWriteError(AudioTrack.WriteException e) {
371         // Do nothing.
372     }
373 
374     @Override
onCryptoError(CryptoException e)375     public void onCryptoError(CryptoException e) {
376         // Do nothing.
377     }
378 
379     @Override
onPlayWhenReadyCommitted()380     public void onPlayWhenReadyCommitted() {
381         // Do nothing.
382     }
383 
384     @Override
onDrawnToSurface(Surface surface)385     public void onDrawnToSurface(Surface surface) {
386         if (mListener != null) {
387             mListener.onDrawnToSurface(this, surface);
388         }
389     }
390 
391     @Override
onDroppedFrames(int count, long elapsed)392     public void onDroppedFrames(int count, long elapsed) {
393         // Do nothing.
394     }
395 
maybeReportPlayerState()396     private void maybeReportPlayerState() {
397         boolean playWhenReady = mPlayer.getPlayWhenReady();
398         @PlaybackState int playbackState = getPlaybackState();
399         if (mLastReportedPlayWhenReady != playWhenReady
400                 || mLastReportedPlaybackState != playbackState) {
401             if (mListener != null) {
402                 if (playbackState == STATE_ENDED) {
403                     mListener.onStateChanged(mPlayerGeneration, playWhenReady, STATE_ENDED);
404                 }
405                 else if (playbackState == STATE_READY) {
406                     mListener.onStateChanged(mPlayerGeneration, playWhenReady, STATE_READY);
407                 }
408             }
409             mLastReportedPlayWhenReady = playWhenReady;
410             mLastReportedPlaybackState = playbackState;
411         }
412     }
413 
pushSurface(boolean blockForSurfacePush)414     private void pushSurface(boolean blockForSurfacePush) {
415         if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
416             return;
417         }
418 
419         if (blockForSurfacePush) {
420             mPlayer.blockingSendMessage(
421                     mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
422         } else {
423             mPlayer.sendMessage(
424                     mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
425         }
426     }
427 
pushTrackSelection(@rackType int type, boolean allowRendererEnable)428     private void pushTrackSelection(@TrackType int type, boolean allowRendererEnable) {
429         if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
430             return;
431         }
432         mPlayer.setSelectedTrack(type, allowRendererEnable ? 0 : -1);
433     }
434 
435     private class MpegTsCcListener implements CcListener {
436 
437         @Override
emitEvent(CaptionEvent captionEvent)438         public void emitEvent(CaptionEvent captionEvent) {
439             if (mVideoEventListener != null) {
440                 mVideoEventListener.onEmitCaptionEvent(captionEvent);
441             }
442         }
443 
444         @Override
discoverServiceNumber(int serviceNumber)445         public void discoverServiceNumber(int serviceNumber) {
446             if (mVideoEventListener != null) {
447                 mVideoEventListener.onDiscoverCaptionServiceNumber(serviceNumber);
448             }
449         }
450     }
451 
452     private class InternalRendererBuilderCallback implements RendererBuilderCallback {
453         private boolean canceled;
454 
cancel()455         public void cancel() {
456             canceled = true;
457         }
458 
459         @Override
onRenderers(String[][] trackNames, TrackRenderer[] renderers)460         public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
461             if (!canceled) {
462                 MpegTsPlayer.this.onRenderers(trackNames, renderers);
463             }
464         }
465 
466         @Override
onRenderersError(Exception e)467         public void onRenderersError(Exception e) {
468             if (!canceled) {
469                 MpegTsPlayer.this.onRenderersError(e);
470             }
471         }
472     }
473 }
474