package com.android.tv.tuner.exoplayer; import android.content.Context; import android.media.MediaCodec; import android.os.Handler; import android.util.Log; import com.google.android.exoplayer.DecoderInfo; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.MediaCodecSelector; import com.google.android.exoplayer.MediaCodecUtil; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaSoftwareCodecUtil; import com.google.android.exoplayer.SampleSource; import com.android.tv.common.feature.CommonFeatures; import java.lang.reflect.Field; /** * MPEG-2 TS video track renderer */ public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { private static final String TAG = "MpegTsVideoTrackRender"; private static final int VIDEO_PLAYBACK_DEADLINE_IN_MS = 5000; // If DROPPED_FRAMES_NOTIFICATION_THRESHOLD frames are consecutively dropped, it'll be notified. private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10; private static final int MIN_HD_HEIGHT = 720; private static final String MIMETYPE_MPEG2 = "video/mpeg2"; private static Field sRenderedFirstFrameField; private final boolean mIsSwCodecEnabled; private boolean mCodecIsSwPreferred; private boolean mSetRenderedFirstFrame; static { // Remove the reflection below once b/31223646 is resolved. try { sRenderedFirstFrameField = MediaCodecVideoTrackRenderer.class.getDeclaredField( "renderedFirstFrame"); sRenderedFirstFrameField.setAccessible(true); } catch (NoSuchFieldException e) { // Null-checking for {@code sRenderedFirstFrameField} will do the error handling. } } public MpegTsVideoTrackRenderer(Context context, SampleSource source, Handler handler, MediaCodecVideoTrackRenderer.EventListener listener) { super(context, source, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_PLAYBACK_DEADLINE_IN_MS, handler, listener, DROPPED_FRAMES_NOTIFICATION_THRESHOLD); mIsSwCodecEnabled = CommonFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context); } @Override protected DecoderInfo getDecoderInfo(MediaCodecSelector codecSelector, String mimeType, boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException { try { if (mIsSwCodecEnabled && mCodecIsSwPreferred) { DecoderInfo swCodec = MediaSoftwareCodecUtil.getSoftwareDecoderInfo( mimeType, requiresSecureDecoder); if (swCodec != null) { return swCodec; } } } catch (MediaSoftwareCodecUtil.DecoderQueryException e) { } return super.getDecoderInfo(codecSelector, mimeType,requiresSecureDecoder); } @Override protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { mCodecIsSwPreferred = MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType) && holder.format.height < MIN_HD_HEIGHT; super.onInputFormatChanged(holder); } @Override protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { super.onDiscontinuity(positionUs); // Disabling pre-rendering of the first frame in order to avoid a frozen picture when // starting the playback. We do this only once, when the renderer is enabled at first, since // we need to pre-render the frame in advance when we do trickplay backed by seeking. if (!mSetRenderedFirstFrame) { setRenderedFirstFrame(true); mSetRenderedFirstFrame = true; } } private void setRenderedFirstFrame(boolean renderedFirstFrame) { if (sRenderedFirstFrameField != null) { try { sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame); } catch (IllegalAccessException e) { Log.w(TAG, "renderedFirstFrame is not accessible. Playback may start with a frozen" +" picture."); } } } }