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 17package com.android.usbtuner.exoplayer; 18 19import android.media.AudioFormat; 20import android.media.MediaCodec.CryptoException; 21import android.media.MediaDataSource; 22import android.os.Handler; 23import android.os.Looper; 24import android.support.annotation.IntDef; 25import android.view.Surface; 26 27import com.google.android.exoplayer.DummyTrackRenderer; 28import com.google.android.exoplayer.ExoPlaybackException; 29import com.google.android.exoplayer.ExoPlayer; 30import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; 31import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; 32import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; 33import com.google.android.exoplayer.TrackRenderer; 34import com.google.android.exoplayer.audio.AudioCapabilities; 35import com.google.android.exoplayer.audio.AudioTrack; 36import com.android.usbtuner.data.Cea708Data; 37import com.android.usbtuner.data.Cea708Data.CaptionEvent; 38import com.android.usbtuner.exoplayer.Cea708TextTrackRenderer.CcListener; 39import com.android.usbtuner.exoplayer.ac3.Ac3TrackRenderer; 40 41import java.lang.annotation.Retention; 42import java.lang.annotation.RetentionPolicy; 43 44/** 45 * MPEG-2 TS stream player implementation using ExoPlayer. 46 */ 47public 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 { 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 { 63 void onRenderers(String[][] trackNames, TrackRenderer[] renderers); 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 { 71 void onStateChanged(int generation, boolean playWhenReady, int playbackState); 72 void onError(int generation, Exception e); 73 void onVideoSizeChanged(int generation, int width, int height, 74 float pixelWidthHeightRatio); 75 void onDrawnToSurface(MpegTsPlayer player, Surface surface); 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 */ 86 void onEmitCaptionEvent(CaptionEvent event); 87 88 /** 89 * Notifies the discovered caption service number. 90 */ 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 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 161 public void setVideoEventListener(VideoEventListener videoEventListener) { 162 mVideoEventListener = videoEventListener; 163 } 164 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 173 public void setSurface(Surface surface) { 174 mSurface = surface; 175 pushSurface(false); 176 } 177 178 public Surface getSurface() { 179 return mSurface; 180 } 181 182 public void blockingClearSurface() { 183 mSurface = null; 184 pushSurface(true); 185 } 186 187 public String[] getTracks(int type) { 188 return mTrackNames == null ? null : mTrackNames[type]; 189 } 190 191 public int getSelectedTrackIndex(int type) { 192 return mSelectedTracks[type]; 193 } 194 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 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 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 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 253 public void setPlayWhenReady(boolean playWhenReady) { 254 mPlayer.setPlayWhenReady(playWhenReady); 255 } 256 257 public void seekTo(long positionMs) { 258 mPlayer.seekTo(positionMs); 259 } 260 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 272 @PlaybackState public int getPlaybackState() { 273 if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { 274 return STATE_PREPARING; 275 } 276 return mPlayer.getPlaybackState(); 277 } 278 279 public boolean isPlaying() { 280 @PlaybackState int state = getPlaybackState(); 281 return (state == STATE_READY || state == STATE_BUFFERING) 282 && mPlayer.getPlayWhenReady(); 283 } 284 285 public boolean isBuffering() { 286 return getPlaybackState() == STATE_BUFFERING; 287 } 288 289 public long getCurrentPosition() { 290 return mPlayer.getCurrentPosition(); 291 } 292 293 public long getDuration() { 294 return mPlayer.getDuration(); 295 } 296 297 public int getBufferedPercentage() { 298 return mPlayer.getBufferedPercentage(); 299 } 300 301 public boolean getPlayWhenReady() { 302 return mPlayer.getPlayWhenReady(); 303 } 304 305 public void setVolume(float volume) { 306 mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume); 307 } 308 309 public void setAudioTrack(boolean enable) { 310 mPlayer.sendMessage(mAudioRenderer, Ac3TrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0); 311 } 312 313 public boolean isAc3Playable() { 314 return mAudioCapabilities != null 315 && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3); 316 } 317 318 public void onAudioUnplayable() { 319 if (mListener != null) { 320 mListener.onAudioUnplayable(mPlayerGeneration); 321 } 322 } 323 324 /* package */ Looper getPlaybackLooper() { 325 return mPlayer.getPlaybackLooper(); 326 } 327 328 /* package */ Handler getMainHandler() { 329 return mMainHandler; 330 } 331 332 @Override 333 public void onPlayerStateChanged(boolean playWhenReady, int state) { 334 maybeReportPlayerState(); 335 } 336 337 @Override 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 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 354 public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, 355 long initializationDurationMs) { 356 // TODO 357 } 358 359 @Override 360 public void onDecoderInitializationError(DecoderInitializationException e) { 361 // Do nothing. 362 } 363 364 @Override 365 public void onAudioTrackInitializationError(AudioTrack.InitializationException e) { 366 onAudioUnplayable(); 367 } 368 369 @Override 370 public void onAudioTrackWriteError(AudioTrack.WriteException e) { 371 // Do nothing. 372 } 373 374 @Override 375 public void onCryptoError(CryptoException e) { 376 // Do nothing. 377 } 378 379 @Override 380 public void onPlayWhenReadyCommitted() { 381 // Do nothing. 382 } 383 384 @Override 385 public void onDrawnToSurface(Surface surface) { 386 if (mListener != null) { 387 mListener.onDrawnToSurface(this, surface); 388 } 389 } 390 391 @Override 392 public void onDroppedFrames(int count, long elapsed) { 393 // Do nothing. 394 } 395 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 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 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 438 public void emitEvent(CaptionEvent captionEvent) { 439 if (mVideoEventListener != null) { 440 mVideoEventListener.onEmitCaptionEvent(captionEvent); 441 } 442 } 443 444 @Override 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 455 public void cancel() { 456 canceled = true; 457 } 458 459 @Override 460 public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) { 461 if (!canceled) { 462 MpegTsPlayer.this.onRenderers(trackNames, renderers); 463 } 464 } 465 466 @Override 467 public void onRenderersError(Exception e) { 468 if (!canceled) { 469 MpegTsPlayer.this.onRenderersError(e); 470 } 471 } 472 } 473} 474