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