1 /* 2 * Copyright (C) 2017 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 package com.android.tv.tuner.exoplayer; 17 18 import android.content.Context; 19 import android.media.MediaCodec; 20 import android.os.Handler; 21 import android.util.Log; 22 import com.android.tv.tuner.features.TunerFeatures; 23 import com.google.android.exoplayer.DecoderInfo; 24 import com.google.android.exoplayer.ExoPlaybackException; 25 import com.google.android.exoplayer.MediaCodecSelector; 26 import com.google.android.exoplayer.MediaCodecUtil; 27 import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; 28 import com.google.android.exoplayer.MediaFormatHolder; 29 import com.google.android.exoplayer.MediaSoftwareCodecUtil; 30 import com.google.android.exoplayer.SampleSource; 31 import java.lang.reflect.Field; 32 33 /** MPEG-2 TS video track renderer */ 34 public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer { 35 private static final String TAG = "MpegTsVideoTrackRender"; 36 37 private static final int VIDEO_PLAYBACK_DEADLINE_IN_MS = 5000; 38 // If DROPPED_FRAMES_NOTIFICATION_THRESHOLD frames are consecutively dropped, it'll be notified. 39 private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10; 40 private static final int MIN_HD_HEIGHT = 720; 41 private static final String MIMETYPE_MPEG2 = "video/mpeg2"; 42 private static Field sRenderedFirstFrameField; 43 44 private final boolean mIsSwCodecEnabled; 45 private boolean mCodecIsSwPreferred; 46 private boolean mSetRenderedFirstFrame; 47 48 static { 49 // Remove the reflection below once b/31223646 is resolved. 50 try { 51 sRenderedFirstFrameField = 52 MediaCodecVideoTrackRenderer.class.getDeclaredField("renderedFirstFrame"); 53 sRenderedFirstFrameField.setAccessible(true); 54 } catch (NoSuchFieldException e) { 55 // Null-checking for {@code sRenderedFirstFrameField} will do the error handling. 56 } 57 } 58 MpegTsVideoTrackRenderer( Context context, SampleSource source, Handler handler, MediaCodecVideoTrackRenderer.EventListener listener)59 public MpegTsVideoTrackRenderer( 60 Context context, 61 SampleSource source, 62 Handler handler, 63 MediaCodecVideoTrackRenderer.EventListener listener) { 64 super( 65 context, 66 source, 67 MediaCodecSelector.DEFAULT, 68 MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 69 VIDEO_PLAYBACK_DEADLINE_IN_MS, 70 handler, 71 listener, 72 DROPPED_FRAMES_NOTIFICATION_THRESHOLD); 73 mIsSwCodecEnabled = TunerFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context); 74 } 75 76 @Override getDecoderInfo( MediaCodecSelector codecSelector, String mimeType, boolean requiresSecureDecoder)77 protected DecoderInfo getDecoderInfo( 78 MediaCodecSelector codecSelector, String mimeType, boolean requiresSecureDecoder) 79 throws MediaCodecUtil.DecoderQueryException { 80 try { 81 if (mIsSwCodecEnabled && mCodecIsSwPreferred) { 82 DecoderInfo swCodec = 83 MediaSoftwareCodecUtil.getSoftwareDecoderInfo( 84 mimeType, requiresSecureDecoder); 85 if (swCodec != null) { 86 return swCodec; 87 } 88 } 89 } catch (MediaSoftwareCodecUtil.DecoderQueryException e) { 90 } 91 return super.getDecoderInfo(codecSelector, mimeType, requiresSecureDecoder); 92 } 93 94 @Override onInputFormatChanged(MediaFormatHolder holder)95 protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { 96 mCodecIsSwPreferred = 97 MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType) 98 && holder.format.height < MIN_HD_HEIGHT; 99 super.onInputFormatChanged(holder); 100 } 101 102 @Override 103 protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { 104 super.onDiscontinuity(positionUs); 105 // Disabling pre-rendering of the first frame in order to avoid a frozen picture when 106 // starting the playback. We do this only once, when the renderer is enabled at first, since 107 // we need to pre-render the frame in advance when we do trickplay backed by seeking. 108 if (!mSetRenderedFirstFrame) { 109 setRenderedFirstFrame(true); 110 mSetRenderedFirstFrame = true; 111 } 112 } 113 114 private void setRenderedFirstFrame(boolean renderedFirstFrame) { 115 if (sRenderedFirstFrameField != null) { 116 try { 117 sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame); 118 } catch (IllegalAccessException e) { 119 Log.w( 120 TAG, 121 "renderedFirstFrame is not accessible. Playback may start with a frozen" 122 + " picture."); 123 } 124 } 125 } 126 } 127