aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/source/FileTsStreamer.java
diff options
context:
space:
mode:
authorLive Channels Team <no-reply@google.com>2018-01-17 13:56:41 -0800
committerNick Chalko <nchalko@google.com>2018-01-18 01:51:43 +0000
commit944779887775bd950cf1abf348d2df461593f6ab (patch)
treef3c8bd17c7874f5cccb44d39f7b11b99376dfd03 /src/com/android/tv/tuner/source/FileTsStreamer.java
parent38fef3bf253578f518d1bc727da4afb263194398 (diff)
downloadTV-944779887775bd950cf1abf348d2df461593f6ab.tar.gz
Sync aosp code with internal version.
Project import generated by Copybara. PiperOrigin-RevId: 182265045 Change-Id: I32cd1a70947fd245f8f70c19eb1713943c7af0bc
Diffstat (limited to 'src/com/android/tv/tuner/source/FileTsStreamer.java')
-rw-r--r--src/com/android/tv/tuner/source/FileTsStreamer.java487
1 files changed, 0 insertions, 487 deletions
diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java
deleted file mode 100644
index f74274f4..00000000
--- a/src/com/android/tv/tuner/source/FileTsStreamer.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.source;
-
-import android.content.Context;
-import android.os.Environment;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import com.android.tv.Features;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.ChannelScanFileParser.ScanChannel;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.FileSourceEventDetector;
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Provides MPEG-2 TS stream sources for both channel scanning and channel playing from a local file
- * generated by capturing TV signal.
- */
-public class FileTsStreamer implements TsStreamer {
- private static final String TAG = "FileTsStreamer";
-
- private static final int TS_PACKET_SIZE = 188;
- private static final int TS_SYNC_BYTE = 0x47;
- private static final int MIN_READ_UNIT = TS_PACKET_SIZE * 10;
- private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~20KB
- private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 4000; // ~ 8MB
- private static final int PADDING_SIZE = MIN_READ_UNIT * 1000; // ~2MB
- private static final int READ_TIMEOUT_MS = 10000; // 10 secs.
- private static final int BUFFER_UNDERRUN_SLEEP_MS = 10;
- private static final String FILE_DIR =
- new File(Environment.getExternalStorageDirectory(), "Streams").getAbsolutePath();
-
- // Virtual frequency base used for file-based source
- public static final int FREQ_BASE = 100;
-
- private final Object mCircularBufferMonitor = new Object();
- private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE];
- private final FileSourceEventDetector mEventDetector;
- private final Context mContext;
-
- private long mBytesFetched;
- private long mLastReadPosition;
- private boolean mStreaming;
-
- private Thread mStreamingThread;
- private StreamProvider mSource;
-
- public static class FileDataSource extends TsDataSource {
- private final FileTsStreamer mTsStreamer;
- private final AtomicLong mLastReadPosition = new AtomicLong(0);
- private long mStartBufferedPosition;
-
- private FileDataSource(FileTsStreamer tsStreamer) {
- mTsStreamer = tsStreamer;
- mStartBufferedPosition = tsStreamer.getBufferedPosition();
- }
-
- @Override
- public long getBufferedPosition() {
- return mTsStreamer.getBufferedPosition() - mStartBufferedPosition;
- }
-
- @Override
- public long getLastReadPosition() {
- return mLastReadPosition.get();
- }
-
- @Override
- public void shiftStartPosition(long offset) {
- SoftPreconditions.checkState(mLastReadPosition.get() == 0);
- SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition());
- mStartBufferedPosition += offset;
- }
-
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- mLastReadPosition.set(0);
- return C.LENGTH_UNBOUNDED;
- }
-
- @Override
- public void close() {}
-
- @Override
- public int read(byte[] buffer, int offset, int readLength) throws IOException {
- int ret =
- mTsStreamer.readAt(
- mStartBufferedPosition + mLastReadPosition.get(),
- buffer,
- offset,
- readLength);
- if (ret > 0) {
- mLastReadPosition.addAndGet(ret);
- }
- return ret;
- }
- }
-
- /**
- * Creates {@link TsStreamer} for scanning & playing MPEG-2 TS file.
- *
- * @param eventListener the listener for channel & program information
- */
- public FileTsStreamer(EventDetector.EventListener eventListener, Context context) {
- mEventDetector =
- new FileSourceEventDetector(
- eventListener, Features.ENABLE_FILE_DVB.isEnabled(context));
- mContext = context;
- }
-
- @Override
- public boolean startStream(ScanChannel channel) {
- String filepath = new File(FILE_DIR, channel.filename).getAbsolutePath();
- mSource = new StreamProvider(filepath);
- if (!mSource.isReady()) {
- return false;
- }
- mEventDetector.start(mSource, FileSourceEventDetector.ALL_PROGRAM_NUMBERS);
- mSource.addPidFilter(TsParser.PAT_PID);
- mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID);
- if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) {
- mSource.addPidFilter(TsParser.DVB_EIT_PID);
- mSource.addPidFilter(TsParser.DVB_SDT_PID);
- }
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- return true;
- }
- mStreaming = true;
- }
-
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- Log.i(TAG, "tuneToChannel with: " + channel.getFilepath());
- mSource = new StreamProvider(channel.getFilepath());
- if (!mSource.isReady()) {
- return false;
- }
- mEventDetector.start(mSource, channel.getProgramNumber());
- mSource.addPidFilter(channel.getVideoPid());
- for (Integer i : channel.getAudioPids()) {
- mSource.addPidFilter(i);
- }
- mSource.addPidFilter(channel.getPcrPid());
- mSource.addPidFilter(TsParser.PAT_PID);
- mSource.addPidFilter(TsParser.ATSC_SI_BASE_PID);
- if (Features.ENABLE_FILE_DVB.isEnabled(mContext)) {
- mSource.addPidFilter(TsParser.DVB_EIT_PID);
- mSource.addPidFilter(TsParser.DVB_SDT_PID);
- }
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- return true;
- }
- mStreaming = true;
- }
-
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
-
- /**
- * Blocks the current thread until the streaming thread stops. In rare cases when the tuner
- * device is overloaded this can take a while, but usually it returns pretty quickly.
- */
- @Override
- public void stopStream() {
- synchronized (mCircularBufferMonitor) {
- mStreaming = false;
- mCircularBufferMonitor.notify();
- }
-
- try {
- if (mStreamingThread != null) {
- mStreamingThread.join();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- @Override
- public TsDataSource createDataSource() {
- return new FileDataSource(this);
- }
-
- /**
- * Returns the current buffered position from the file.
- *
- * @return the current buffered position
- */
- public long getBufferedPosition() {
- synchronized (mCircularBufferMonitor) {
- return mBytesFetched;
- }
- }
-
- /** Provides MPEG-2 transport stream from a local file. Stream can be filtered by PID. */
- public static class StreamProvider {
- private final String mFilepath;
- private final SparseBooleanArray mPids = new SparseBooleanArray();
- private final byte[] mPreBuffer = new byte[READ_BUFFER_SIZE];
-
- private BufferedInputStream mInputStream;
-
- private StreamProvider(String filepath) {
- mFilepath = filepath;
- open(filepath);
- }
-
- private void open(String filepath) {
- try {
- mInputStream = new BufferedInputStream(new FileInputStream(filepath));
- } catch (IOException e) {
- Log.e(TAG, "Error opening input stream", e);
- mInputStream = null;
- }
- }
-
- private boolean isReady() {
- return mInputStream != null;
- }
-
- /** Returns the file path of the MPEG-2 TS file. */
- public String getFilepath() {
- return mFilepath;
- }
-
- /** Adds a pid for filtering from the MPEG-2 TS file. */
- public void addPidFilter(int pid) {
- mPids.put(pid, true);
- }
-
- /** Returns whether the current pid filter is empty or not. */
- public boolean isFilterEmpty() {
- return mPids.size() == 0;
- }
-
- /** Clears the current pid filter. */
- public void clearPidFilter() {
- mPids.clear();
- }
-
- /**
- * Returns whether a pid is in the pid filter or not.
- *
- * @param pid the pid to check
- */
- public boolean isInFilter(int pid) {
- return mPids.get(pid);
- }
-
- /**
- * Reads from the MPEG-2 TS file to buffer.
- *
- * @param inputBuffer to read
- * @return the number of read bytes
- */
- private int read(byte[] inputBuffer) {
- int readSize = readInternal();
- if (readSize <= 0) {
- // Reached the end of stream. Restart from the beginning.
- close();
- open(mFilepath);
- if (mInputStream == null) {
- return -1;
- }
- readSize = readInternal();
- }
-
- if (mPreBuffer[0] != TS_SYNC_BYTE) {
- Log.e(TAG, "Error reading input stream - no TS sync found");
- return -1;
- }
- int filteredSize = 0;
- for (int i = 0, destPos = 0; i < readSize; i += TS_PACKET_SIZE) {
- if (mPreBuffer[i] == TS_SYNC_BYTE) {
- int pid = ((mPreBuffer[i + 1] & 0x1f) << 8) + (mPreBuffer[i + 2] & 0xff);
- if (mPids.get(pid)) {
- System.arraycopy(mPreBuffer, i, inputBuffer, destPos, TS_PACKET_SIZE);
- destPos += TS_PACKET_SIZE;
- filteredSize += TS_PACKET_SIZE;
- }
- }
- }
- return filteredSize;
- }
-
- private int readInternal() {
- int readSize;
- try {
- readSize = mInputStream.read(mPreBuffer, 0, mPreBuffer.length);
- } catch (IOException e) {
- Log.e(TAG, "Error reading input stream", e);
- return -1;
- }
- return readSize;
- }
-
- private void close() {
- try {
- mInputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "Error closing input stream:", e);
- }
- mInputStream = null;
- }
- }
-
- /**
- * Reads data from internal buffer.
- *
- * @param pos the position to read from
- * @param buffer to read
- * @param offset start position of the read buffer
- * @param amount number of bytes to read
- * @return number of read bytes when successful, {@code -1} otherwise
- * @throws IOException
- */
- public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException {
- synchronized (mCircularBufferMonitor) {
- long initialBytesFetched = mBytesFetched;
- while (mBytesFetched < pos + amount && mStreaming) {
- try {
- mCircularBufferMonitor.wait(READ_TIMEOUT_MS);
- } catch (InterruptedException e) {
- // Wait again.
- Thread.currentThread().interrupt();
- }
- if (initialBytesFetched == mBytesFetched) {
- Log.w(TAG, "No data update for " + READ_TIMEOUT_MS + "ms. returning -1.");
-
- // Returning -1 will make demux report EOS so that the input service can retry
- // the playback.
- return -1;
- }
- }
- if (!mStreaming) {
- Log.w(TAG, "Stream is already stopped.");
- return -1;
- }
- if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
- Log.e(TAG, "Demux is requesting the data which is already overwritten.");
- return -1;
- }
- int posInBuffer = (int) (pos % CIRCULAR_BUFFER_SIZE);
- int bytesToCopyInFirstPass = amount;
- if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
- bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
- }
- System.arraycopy(mCircularBuffer, posInBuffer, buffer, offset, bytesToCopyInFirstPass);
- if (bytesToCopyInFirstPass < amount) {
- System.arraycopy(
- mCircularBuffer,
- 0,
- buffer,
- offset + bytesToCopyInFirstPass,
- amount - bytesToCopyInFirstPass);
- }
- mLastReadPosition = pos + amount;
- mCircularBufferMonitor.notify();
- return amount;
- }
- }
-
- /**
- * Adds {@link ScanChannel} instance for local files.
- *
- * @param output a list of channels where the results will be placed in
- */
- public static void addLocalStreamFiles(List<ScanChannel> output) {
- File dir = new File(FILE_DIR);
- if (!dir.exists()) return;
-
- File[] tsFiles = dir.listFiles();
- if (tsFiles == null) return;
- int freq = FileTsStreamer.FREQ_BASE;
- for (File file : tsFiles) {
- if (!file.isFile()) continue;
- output.add(ScanChannel.forFile(freq, file.getName()));
- freq += 100;
- }
- }
-
- /**
- * A thread managing a circular buffer that holds stream data to be consumed by player. Keeps
- * reading data in from a {@link StreamProvider} to hold enough amount for buffering. Started
- * and stopped by {@link #startStream()} and {@link #stopStream()}, respectively.
- */
- private class StreamingThread extends Thread {
- @Override
- public void run() {
- byte[] dataBuffer = new byte[READ_BUFFER_SIZE];
-
- synchronized (mCircularBufferMonitor) {
- mBytesFetched = 0;
- mLastReadPosition = 0;
- }
-
- while (true) {
- synchronized (mCircularBufferMonitor) {
- while ((mBytesFetched - mLastReadPosition + PADDING_SIZE) > CIRCULAR_BUFFER_SIZE
- && mStreaming) {
- try {
- mCircularBufferMonitor.wait();
- } catch (InterruptedException e) {
- // Wait again.
- Thread.currentThread().interrupt();
- }
- }
- if (!mStreaming) {
- break;
- }
- }
-
- int bytesWritten = mSource.read(dataBuffer);
- if (bytesWritten <= 0) {
- try {
- // When buffer is underrun, we sleep for short time to prevent
- // unnecessary CPU draining.
- sleep(BUFFER_UNDERRUN_SLEEP_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- continue;
- }
-
- mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten);
-
- synchronized (mCircularBufferMonitor) {
- int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE);
- int bytesToCopyInFirstPass = bytesWritten;
- if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
- bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
- }
- System.arraycopy(
- dataBuffer, 0, mCircularBuffer, posInBuffer, bytesToCopyInFirstPass);
- if (bytesToCopyInFirstPass < bytesWritten) {
- System.arraycopy(
- dataBuffer,
- bytesToCopyInFirstPass,
- mCircularBuffer,
- 0,
- bytesWritten - bytesToCopyInFirstPass);
- }
- mBytesFetched += bytesWritten;
- mCircularBufferMonitor.notify();
- }
- }
-
- Log.i(TAG, "Streaming stopped");
- mSource.close();
- }
- }
-}