• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.tv.samples.sampletunertvinput;
2 
3 import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN;
4 
5 import android.content.Context;
6 import android.media.MediaCodec;
7 import android.media.MediaCodec.BufferInfo;
8 import android.media.MediaFormat;
9 import android.media.tv.tuner.dvr.DvrPlayback;
10 import android.media.tv.tuner.dvr.DvrSettings;
11 import android.media.tv.tuner.filter.AvSettings;
12 import android.media.tv.tuner.filter.Filter;
13 import android.media.tv.tuner.filter.FilterCallback;
14 import android.media.tv.tuner.filter.FilterEvent;
15 import android.media.tv.tuner.filter.MediaEvent;
16 import android.media.tv.tuner.filter.TsFilterConfiguration;
17 import android.media.tv.tuner.frontend.AtscFrontendSettings;
18 import android.media.tv.tuner.frontend.DvbtFrontendSettings;
19 import android.media.tv.tuner.frontend.FrontendSettings;
20 import android.media.tv.tuner.frontend.OnTuneEventListener;
21 import android.media.tv.tuner.Tuner;
22 import android.media.tv.TvInputService;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.HandlerExecutor;
26 import android.os.ParcelFileDescriptor;
27 import android.util.Log;
28 import android.view.Surface;
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.nio.ByteBuffer;
33 import java.util.ArrayDeque;
34 import java.util.ArrayList;
35 import java.util.Deque;
36 import java.util.List;
37 
38 
39 /** SampleTunerTvInputService */
40 public class SampleTunerTvInputService extends TvInputService {
41     private static final String TAG = "SampleTunerTvInput";
42     private static final boolean DEBUG = true;
43 
44     private static final int AUDIO_TPID = 257;
45     private static final int VIDEO_TPID = 256;
46     private static final int STATUS_MASK = 0xf;
47     private static final int LOW_THRESHOLD = 0x1000;
48     private static final int HIGH_THRESHOLD = 0x07fff;
49     private static final int FREQUENCY = 578000;
50     private static final int FILTER_BUFFER_SIZE = 16000000;
51     private static final int DVR_BUFFER_SIZE = 4000000;
52     private static final int INPUT_FILE_MAX_SIZE = 700000;
53     private static final int PACKET_SIZE = 188;
54 
55     private static final int TIMEOUT_US = 100000;
56     private static final boolean SAVE_DATA = true;
57     private static final String ES_PATH = "/data/local/tmp/test.es";
58     private static final MediaFormat VIDEO_FORMAT;
59 
60     static {
61         // format extracted for the specific input file
62         VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 320, 240);
VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1)63         VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1);
VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333)64         VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333);
VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32)65         VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32);
VIDEO_FORMAT.setInteger(MediaFormat.KEY_PROFILE, 65536)66         VIDEO_FORMAT.setInteger(MediaFormat.KEY_PROFILE, 65536);
67         ByteBuffer csd = ByteBuffer.wrap(
68                 new byte[] {0, 0, 0, 1, 103, 66, -64, 20, -38, 5, 7, -24, 64, 0, 0, 3, 0, 64, 0,
69                         0, 15, 35, -59, 10, -88});
70         VIDEO_FORMAT.setByteBuffer("csd-0", csd);
71         csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -50, 60, -128});
72         VIDEO_FORMAT.setByteBuffer("csd-1", csd);
73     }
74 
75     public static final String INPUT_ID =
76             "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService";
77     private String mSessionId;
78 
79     @Override
onCreateSession(String inputId, String sessionId)80     public TvInputSessionImpl onCreateSession(String inputId, String sessionId) {
81         TvInputSessionImpl session =  new TvInputSessionImpl(this);
82         if (DEBUG) {
83             Log.d(TAG, "onCreateSession(inputId=" + inputId + ", sessionId=" + sessionId + ")");
84         }
85         mSessionId = sessionId;
86         return session;
87     }
88 
89     @Override
onCreateSession(String inputId)90     public TvInputSessionImpl onCreateSession(String inputId) {
91         return new TvInputSessionImpl(this);
92     }
93 
94     class TvInputSessionImpl extends Session {
95 
96         private final Context mContext;
97         private Handler mHandler;
98 
99         private Surface mSurface;
100         private Filter mAudioFilter;
101         private Filter mVideoFilter;
102         private DvrPlayback mDvr;
103         private Tuner mTuner;
104         private MediaCodec mMediaCodec;
105         private Thread mDecoderThread;
106         private Deque<MediaEvent> mDataQueue;
107         private List<MediaEvent> mSavedData;
108         private boolean mDataReady = false;
109 
110 
TvInputSessionImpl(Context context)111         public TvInputSessionImpl(Context context) {
112             super(context);
113             mContext = context;
114         }
115 
116         @Override
onRelease()117         public void onRelease() {
118             if (DEBUG) {
119                 Log.d(TAG, "onRelease");
120             }
121             if (mDecoderThread != null) {
122                 mDecoderThread.interrupt();
123                 mDecoderThread = null;
124             }
125             if (mMediaCodec != null) {
126                 mMediaCodec.release();
127                 mMediaCodec = null;
128             }
129             if (mAudioFilter != null) {
130                 mAudioFilter.close();
131             }
132             if (mVideoFilter != null) {
133                 mVideoFilter.close();
134             }
135             if (mDvr != null) {
136                 mDvr.close();
137             }
138             if (mTuner != null) {
139                 mTuner.close();
140             }
141             mDataQueue = null;
142             mSavedData = null;
143         }
144 
145         @Override
onSetSurface(Surface surface)146         public boolean onSetSurface(Surface surface) {
147             if (DEBUG) {
148                 Log.d(TAG, "onSetSurface");
149             }
150             this.mSurface = surface;
151             return true;
152         }
153 
154         @Override
onSetStreamVolume(float v)155         public void onSetStreamVolume(float v) {
156             if (DEBUG) {
157                 Log.d(TAG, "onSetStreamVolume " + v);
158             }
159         }
160 
161         @Override
onTune(Uri uri)162         public boolean onTune(Uri uri) {
163             if (DEBUG) {
164                 Log.d(TAG, "onTune " + uri);
165             }
166             if (!initCodec()) {
167                 Log.e(TAG, "null codec!");
168                 return false;
169             }
170             mHandler = new Handler();
171             mDecoderThread =
172                     new Thread(
173                             this::decodeInternal,
174                             "sample-tuner-tis-decoder-thread");
175             mDecoderThread.start();
176             return true;
177         }
178 
179         @Override
onSetCaptionEnabled(boolean b)180         public void onSetCaptionEnabled(boolean b) {
181             if (DEBUG) {
182                 Log.d(TAG, "onSetCaptionEnabled " + b);
183             }
184         }
185 
audioFilter()186         private Filter audioFilter() {
187             Filter audioFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO,
188                     FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
189                     new FilterCallback() {
190                         @Override
191                         public void onFilterEvent(Filter filter, FilterEvent[] events) {
192                             if (DEBUG) {
193                                 Log.d(TAG, "onFilterEvent audio, size=" + events.length);
194                             }
195                             for (int i = 0; i < events.length; i++) {
196                                 if (DEBUG) {
197                                     Log.d(TAG, "events[" + i + "] is "
198                                             + events[i].getClass().getSimpleName());
199                                 }
200                             }
201                         }
202 
203                         @Override
204                         public void onFilterStatusChanged(Filter filter, int status) {
205                             if (DEBUG) {
206                                 Log.d(TAG, "onFilterEvent audio, status=" + status);
207                             }
208                         }
209                     });
210             AvSettings settings =
211                     AvSettings.builder(Filter.TYPE_TS, true).setPassthrough(false).build();
212             audioFilter.configure(
213                     TsFilterConfiguration.builder().setTpid(AUDIO_TPID)
214                             .setSettings(settings).build());
215             return audioFilter;
216         }
217 
videoFilter()218         private Filter videoFilter() {
219             Filter videoFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO,
220                     FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
221                     new FilterCallback() {
222                         @Override
223                         public void onFilterEvent(Filter filter, FilterEvent[] events) {
224                             if (DEBUG) {
225                                 Log.d(TAG, "onFilterEvent video, size=" + events.length);
226                             }
227                             for (int i = 0; i < events.length; i++) {
228                                 if (DEBUG) {
229                                     Log.d(TAG, "events[" + i + "] is "
230                                             + events[i].getClass().getSimpleName());
231                                 }
232                                 if (events[i] instanceof MediaEvent) {
233                                     MediaEvent me = (MediaEvent) events[i];
234                                     mDataQueue.add(me);
235                                     if (SAVE_DATA) {
236                                         mSavedData.add(me);
237                                     }
238                                 }
239                             }
240                         }
241 
242                         @Override
243                         public void onFilterStatusChanged(Filter filter, int status) {
244                             if (DEBUG) {
245                                 Log.d(TAG, "onFilterEvent video, status=" + status);
246                                 if (status == Filter.STATUS_DATA_READY) {
247                                     mDataReady = true;
248                                 }
249                             }
250                         }
251                     });
252             AvSettings settings =
253                     AvSettings.builder(Filter.TYPE_TS, false).setPassthrough(false).build();
254             videoFilter.configure(
255                     TsFilterConfiguration.builder().setTpid(VIDEO_TPID)
256                             .setSettings(settings).build());
257             return videoFilter;
258         }
259 
dvrPlayback()260         private DvrPlayback dvrPlayback() {
261             DvrPlayback dvr = mTuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler),
262                     status -> {
263                         if (DEBUG) {
264                             Log.d(TAG, "onPlaybackStatusChanged status=" + status);
265                         }
266                     });
267             int res = dvr.configure(
268                     DvrSettings.builder()
269                             .setStatusMask(STATUS_MASK)
270                             .setLowThreshold(LOW_THRESHOLD)
271                             .setHighThreshold(HIGH_THRESHOLD)
272                             .setDataFormat(DvrSettings.DATA_FORMAT_ES)
273                             .setPacketSize(PACKET_SIZE)
274                             .build());
275             if (DEBUG) {
276                 Log.d(TAG, "config res=" + res);
277             }
278             File file = new File(ES_PATH);
279             if (file.exists()) {
280                 try {
281                     dvr.setFileDescriptor(
282                             ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE));
283                 } catch (FileNotFoundException e) {
284                         Log.e(TAG, "Failed to create FD");
285                 }
286             } else {
287                 Log.w(TAG, "File not existing");
288             }
289             return dvr;
290         }
291 
tune()292         private void tune() {
293             DvbtFrontendSettings feSettings = DvbtFrontendSettings.builder()
294                     .setFrequency(FREQUENCY)
295                     .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_AUTO)
296                     .setBandwidth(DvbtFrontendSettings.BANDWIDTH_8MHZ)
297                     .setConstellation(DvbtFrontendSettings.CONSTELLATION_AUTO)
298                     .setHierarchy(DvbtFrontendSettings.HIERARCHY_AUTO)
299                     .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
300                     .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
301                     .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_AUTO)
302                     .setHighPriority(true)
303                     .setStandard(DvbtFrontendSettings.STANDARD_T)
304                     .build();
305             mTuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() {
306                 @Override
307                 public void onTuneEvent(int tuneEvent) {
308                     if (DEBUG) {
309                         Log.d(TAG, "onTuneEvent " + tuneEvent);
310                     }
311                     long read = mDvr.read(INPUT_FILE_MAX_SIZE);
312                     if (DEBUG) {
313                         Log.d(TAG, "read=" + read);
314                     }
315                 }
316             });
317             mTuner.tune(feSettings);
318         }
319 
initCodec()320         private boolean initCodec() {
321             if (mMediaCodec != null) {
322                 mMediaCodec.release();
323                 mMediaCodec = null;
324             }
325             try {
326                 mMediaCodec = MediaCodec.createDecoderByType("video/avc");
327                 mMediaCodec.configure(VIDEO_FORMAT, mSurface, null, 0);
328             } catch (IOException e) {
329                 Log.e(TAG, "Error: " + e.getMessage());
330             }
331 
332             if (mMediaCodec == null) {
333                 Log.e(TAG, "null codec!");
334                 notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_UNKNOWN);
335                 return false;
336             }
337             return true;
338         }
339 
decodeInternal()340         private void decodeInternal() {
341             mDataQueue = new ArrayDeque<>();
342             mSavedData = new ArrayList<>();
343             mTuner = new Tuner(mContext, mSessionId,
344                     TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
345 
346             mAudioFilter = audioFilter();
347             mVideoFilter = videoFilter();
348             mAudioFilter.start();
349             mVideoFilter.start();
350             // use dvr playback to feed the data on platform without physical tuner
351             mDvr = dvrPlayback();
352             tune();
353             mDvr.start();
354             mMediaCodec.start();
355 
356             try {
357                 while (!Thread.interrupted()) {
358                     if (!mDataReady) {
359                         Thread.sleep(100);
360                     }
361                     if (!mDataQueue.isEmpty()) {
362                         if (queueCodecInputBuffer(mDataQueue.getFirst())) {
363                             // data consumed, remove.
364                             mDataQueue.pollFirst();
365                         }
366                     } else if (SAVE_DATA) {
367                         mDataQueue.addAll(mSavedData);
368                     }
369                     releaseCodecOutputBuffer();
370                 }
371             } catch (Exception e) {
372                 Log.e(TAG, "Error: " + e.getMessage());
373             }
374         }
375 
queueCodecInputBuffer(MediaEvent mediaEvent)376         private boolean queueCodecInputBuffer(MediaEvent mediaEvent) {
377             int res = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
378             if (res >= 0) {
379                 ByteBuffer buffer = mMediaCodec.getInputBuffer(res);
380                 if (buffer == null) {
381                     throw new RuntimeException("Null decoder input buffer");
382                 }
383 
384                 ByteBuffer data = mediaEvent.getLinearBlock().map();
385                 int sampleSize = (int) mediaEvent.getDataLength();
386                 int offset = (int) mediaEvent.getOffset();
387                 long pts = mediaEvent.getPts();
388 
389                 if (offset > 0 && offset < data.limit()) {
390                     data.position(offset);
391                 } else {
392                     data.position(0);
393                 }
394 
395                 if (DEBUG) {
396                     Log.d(
397                         TAG,
398                         "Decoder: Send data to decoder."
399                             + " Sample size="
400                             + sampleSize
401                             + " pts="
402                             + pts
403                             + " limit="
404                             + data.limit()
405                             + " pos="
406                             + data.position()
407                             + " size="
408                             + (data.limit() - data.position()));
409                 }
410                 while (data.position() < data.limit()) {
411                     // fill codec input buffer
412                     buffer.put(data.get());
413                 }
414 
415                 mMediaCodec.queueInputBuffer(res, 0, sampleSize, pts, 0);
416             } else {
417                 Log.d(TAG, "queueCodecInputBuffer res=" + res);
418                 return false;
419             }
420             return true;
421         }
422 
releaseCodecOutputBuffer()423         private void releaseCodecOutputBuffer() {
424             // play frames
425             BufferInfo bufferInfo = new BufferInfo();
426             int res = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
427             if (res >= 0) {
428                 mMediaCodec.releaseOutputBuffer(res, true);
429                 notifyVideoAvailable();
430                 if (DEBUG) {
431                     Log.d(TAG, "notifyVideoAvailable");
432                 }
433             } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
434                 MediaFormat format = mMediaCodec.getOutputFormat();
435                 if (DEBUG) {
436                     Log.d(TAG, "releaseCodecOutputBuffer: Output format changed:" + format);
437                 }
438             } else if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
439                 if (DEBUG) {
440                     Log.d(TAG, "releaseCodecOutputBuffer: timeout");
441                 }
442             } else {
443                 if (DEBUG) {
444                     Log.d(TAG, "Return value of releaseCodecOutputBuffer:" + res);
445                 }
446             }
447         }
448 
449     }
450 }