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 }