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