aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner')
-rw-r--r--src/com/android/tv/tuner/ChannelScanFileParser.java101
-rw-r--r--src/com/android/tv/tuner/DvbDeviceAccessor.java223
-rw-r--r--src/com/android/tv/tuner/DvbTunerHal.java179
-rw-r--r--src/com/android/tv/tuner/TunerHal.java352
-rw-r--r--src/com/android/tv/tuner/TunerInputController.java480
-rw-r--r--src/com/android/tv/tuner/TunerPreferenceProvider.java203
-rw-r--r--src/com/android/tv/tuner/TunerPreferences.java428
-rw-r--r--src/com/android/tv/tuner/cc/CaptionLayout.java76
-rw-r--r--src/com/android/tv/tuner/cc/CaptionTrackRenderer.java344
-rw-r--r--src/com/android/tv/tuner/cc/CaptionWindowLayout.java650
-rw-r--r--src/com/android/tv/tuner/cc/Cea708Parser.java820
-rw-r--r--src/com/android/tv/tuner/data/Cea708Data.java320
-rw-r--r--src/com/android/tv/tuner/data/PsiData.java94
-rw-r--r--src/com/android/tv/tuner/data/PsipData.java820
-rw-r--r--src/com/android/tv/tuner/data/TunerChannel.java511
-rw-r--r--src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java302
-rw-r--r--src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java41
-rw-r--r--src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java552
-rw-r--r--src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java138
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java696
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java75
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java335
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java196
-rw-r--r--src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java101
-rw-r--r--src/com/android/tv/tuner/exoplayer/SampleExtractor.java136
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioClock.java107
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java70
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java129
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java176
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java235
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java735
-rw-r--r--src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java94
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java692
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java392
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java309
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java437
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java461
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java71
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java75
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java180
-rw-r--r--src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java147
-rw-r--r--src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java249
-rw-r--r--src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java205
-rw-r--r--src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl29
-rw-r--r--src/com/android/tv/tuner/layout/ScaledLayout.java274
-rw-r--r--src/com/android/tv/tuner/setup/ConnectionTypeFragment.java101
-rw-r--r--src/com/android/tv/tuner/setup/PostalCodeFragment.java178
-rw-r--r--src/com/android/tv/tuner/setup/ScanFragment.java523
-rw-r--r--src/com/android/tv/tuner/setup/ScanResultFragment.java127
-rw-r--r--src/com/android/tv/tuner/setup/TunerSetupActivity.java543
-rw-r--r--src/com/android/tv/tuner/setup/WelcomeFragment.java120
-rw-r--r--src/com/android/tv/tuner/source/FileTsStreamer.java484
-rw-r--r--src/com/android/tv/tuner/source/TsDataSource.java50
-rw-r--r--src/com/android/tv/tuner/source/TsDataSourceManager.java143
-rw-r--r--src/com/android/tv/tuner/source/TsStreamWriter.java237
-rw-r--r--src/com/android/tv/tuner/source/TsStreamer.java56
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamer.java408
-rw-r--r--src/com/android/tv/tuner/source/TunerTsStreamerManager.java304
-rw-r--r--src/com/android/tv/tuner/ts/SectionParser.java1759
-rw-r--r--src/com/android/tv/tuner/ts/TsParser.java520
-rw-r--r--src/com/android/tv/tuner/tvinput/ChannelDataManager.java734
-rw-r--r--src/com/android/tv/tuner/tvinput/EventDetector.java334
-rw-r--r--src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java249
-rw-r--r--src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java42
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerDebug.java150
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerRecordingSession.java104
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java662
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSession.java324
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerSessionWorker.java1754
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java174
-rw-r--r--src/com/android/tv/tuner/tvinput/TunerTvInputService.java123
-rw-r--r--src/com/android/tv/tuner/util/ByteArrayBuffer.java149
-rw-r--r--src/com/android/tv/tuner/util/ConvertUtils.java35
-rw-r--r--src/com/android/tv/tuner/util/GlobalSettingsUtils.java36
-rw-r--r--src/com/android/tv/tuner/util/Ints.java28
-rw-r--r--src/com/android/tv/tuner/util/PostalCodeUtils.java138
-rw-r--r--src/com/android/tv/tuner/util/StatusTextUtils.java119
-rw-r--r--src/com/android/tv/tuner/util/SystemPropertiesProxy.java77
-rw-r--r--src/com/android/tv/tuner/util/TisConfiguration.java22
-rw-r--r--src/com/android/tv/tuner/util/TunerInputInfoUtils.java115
80 files changed, 329 insertions, 23833 deletions
diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java
deleted file mode 100644
index a255de3e..00000000
--- a/src/com/android/tv/tuner/ChannelScanFileParser.java
+++ /dev/null
@@ -1,101 +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;
-
-import android.util.Log;
-
-import com.android.tv.tuner.data.nano.Channel;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Parses plain text formatted scan files, which contain the list of channels.
- */
-public class ChannelScanFileParser {
- private static final String TAG = "ChannelScanFileParser";
-
- public static final class ScanChannel {
- public final int type;
- public final int frequency;
- public final String modulation;
- public final String filename;
- /**
- * Radio frequency (channel) number specified at
- * https://en.wikipedia.org/wiki/North_American_television_frequencies
- * This can be {@code null} for cases like cable signal.
- */
- public final Integer radioFrequencyNumber;
-
- public static ScanChannel forTuner(int frequency, String modulation,
- Integer radioFrequencyNumber) {
- return new ScanChannel(Channel.TYPE_TUNER, frequency, modulation, null,
- radioFrequencyNumber);
- }
-
- public static ScanChannel forFile(int frequency, String filename) {
- return new ScanChannel(Channel.TYPE_FILE, frequency, "file:", filename, null);
- }
-
- private ScanChannel(int type, int frequency, String modulation, String filename,
- Integer radioFrequencyNumber) {
- this.type = type;
- this.frequency = frequency;
- this.modulation = modulation;
- this.filename = filename;
- this.radioFrequencyNumber = radioFrequencyNumber;
- }
- }
-
- /**
- * Parses a given scan file and returns the list of {@link ScanChannel} objects.
- *
- * @param is {@link InputStream} of a scan file. Each line matches one channel.
- * The line format of the scan file is as follows:<br>
- * "A &lt;frequency&gt; &lt;modulation&gt;".
- * @return a list of {@link ScanChannel} objects parsed
- */
- public static List<ScanChannel> parseScanFile(InputStream is) {
- BufferedReader in = new BufferedReader(new InputStreamReader(is));
- String line;
- List<ScanChannel> scanChannelList = new ArrayList<>();
- try {
- while ((line = in.readLine()) != null) {
- if (line.isEmpty()) {
- continue;
- }
- if (line.charAt(0) == '#') {
- // Skip comment line
- continue;
- }
- String[] tokens = line.split("\\s+");
- if (tokens.length != 3 && tokens.length != 4) {
- continue;
- }
- scanChannelList.add(ScanChannel.forTuner(Integer.parseInt(tokens[1]), tokens[2],
- tokens.length == 4 ? Integer.parseInt(tokens[3]) : null));
- }
- } catch (IOException e) {
- Log.e(TAG, "error on parseScanFile()", e);
- }
- return scanChannelList;
- }
-}
diff --git a/src/com/android/tv/tuner/DvbDeviceAccessor.java b/src/com/android/tv/tuner/DvbDeviceAccessor.java
deleted file mode 100644
index 4f5d8ee4..00000000
--- a/src/com/android/tv/tuner/DvbDeviceAccessor.java
+++ /dev/null
@@ -1,223 +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;
-
-import android.content.Context;
-import android.media.tv.TvInputManager;
-import android.os.ParcelFileDescriptor;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.android.tv.common.recording.RecordingCapability;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Provides with the file descriptors to access DVB device.
- */
-public class DvbDeviceAccessor {
- private static final String TAG = "DvbDeviceAccessor";
-
- @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DvbDevice {}
- public static final int DVB_DEVICE_DEMUX = 0; // TvInputManager.DVB_DEVICE_DEMUX;
- public static final int DVB_DEVICE_DVR = 1; // TvInputManager.DVB_DEVICE_DVR;
- public static final int DVB_DEVICE_FRONTEND = 2; // TvInputManager.DVB_DEVICE_FRONTEND;
-
- private static Method sGetDvbDeviceListMethod;
- private static Method sOpenDvbDeviceMethod;
-
- private final TvInputManager mTvInputManager;
-
- static {
- try {
- Class tvInputManagerClass = Class.forName("android.media.tv.TvInputManager");
- Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
- sGetDvbDeviceListMethod = tvInputManagerClass.getDeclaredMethod("getDvbDeviceList");
- sGetDvbDeviceListMethod.setAccessible(true);
- sOpenDvbDeviceMethod = tvInputManagerClass.getDeclaredMethod(
- "openDvbDevice", dvbDeviceInfoClass, Integer.TYPE);
- sOpenDvbDeviceMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Couldn't find class", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "Couldn't find method", e);
- }
- }
-
- public DvbDeviceAccessor(Context context) {
- mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- }
-
- public List<DvbDeviceInfoWrapper> getDvbDeviceList() {
- try {
- List<DvbDeviceInfoWrapper> wrapperList = new ArrayList<>();
- List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
- for (Object dvbDeviceInfo : dvbDeviceInfoList) {
- wrapperList.add(new DvbDeviceInfoWrapper(dvbDeviceInfo));
- }
- Collections.sort(wrapperList);
- return wrapperList;
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return null;
- }
-
- /**
- * Returns the number of currently connected DVB devices.
- */
- public int getNumOfDvbDevices() {
- List<DvbDeviceInfoWrapper> dvbDeviceList = getDvbDeviceList();
- return dvbDeviceList == null ? 0 : dvbDeviceList.size();
- }
-
- public boolean isDvbDeviceAvailable() {
- try {
- List dvbDeviceInfoList = (List) sGetDvbDeviceListMethod.invoke(mTvInputManager);
- return (!dvbDeviceInfoList.isEmpty());
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return false;
- }
-
- public ParcelFileDescriptor openDvbDevice(DvbDeviceInfoWrapper deviceInfo,
- @DvbDevice int device) {
- try {
- return (ParcelFileDescriptor) sOpenDvbDeviceMethod.invoke(
- mTvInputManager, deviceInfo.getDvbDeviceInfo(), device);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- }
- return null;
- }
-
- /**
- * Returns the current recording capability for USB tuner.
- * @param inputId the input id to use.
- */
- public RecordingCapability getRecordingCapability(String inputId) {
- List<DvbDeviceInfoWrapper> deviceList = getDvbDeviceList();
- // TODO(DVR) implement accurate capabilities and updating values when needed.
- return RecordingCapability.builder()
- .setInputId(inputId)
- .setMaxConcurrentPlayingSessions(1)
- .setMaxConcurrentTunedSessions(deviceList.size())
- .setMaxConcurrentSessionsOfAllTypes(deviceList.size() + 1)
- .build();
- }
-
- public static class DvbDeviceInfoWrapper implements Comparable<DvbDeviceInfoWrapper> {
- private static Method sGetAdapterIdMethod;
- private static Method sGetDeviceIdMethod;
- private final Object mDvbDeviceInfo;
- private final int mAdapterId;
- private final int mDeviceId;
- private final long mId;
-
- static {
- try {
- Class dvbDeviceInfoClass = Class.forName("android.media.tv.DvbDeviceInfo");
- sGetAdapterIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getAdapterId");
- sGetAdapterIdMethod.setAccessible(true);
- sGetDeviceIdMethod = dvbDeviceInfoClass.getDeclaredMethod("getDeviceId");
- sGetDeviceIdMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- Log.e(TAG, "Couldn't find class", e);
- } catch (NoSuchMethodException e) {
- Log.e(TAG, "Couldn't find method", e);
- }
- }
-
- public DvbDeviceInfoWrapper(Object dvbDeviceInfo) {
- mDvbDeviceInfo = dvbDeviceInfo;
- mAdapterId = initAdapterId();
- mDeviceId = initDeviceId();
- mId = (((long) getAdapterId()) << 32) | (getDeviceId() & 0xffffffffL);
- }
-
- public long getId() {
- return mId;
- }
-
- public int getAdapterId() {
- return mAdapterId;
- }
-
- private int initAdapterId() {
- try {
- return (int) sGetAdapterIdMethod.invoke(mDvbDeviceInfo);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- }
- return -1;
- }
-
- public int getDeviceId() {
- return mDeviceId;
- }
-
- private int initDeviceId() {
- try {
- return (int) sGetDeviceIdMethod.invoke(mDvbDeviceInfo);
- } catch (InvocationTargetException e) {
- Log.e(TAG, "Couldn't invoke", e);
- } catch (IllegalAccessException e) {
- Log.e(TAG, "Couldn't access", e);
- }
- return -1;
- }
-
- public Object getDvbDeviceInfo() {
- return mDvbDeviceInfo;
- }
-
- @Override
- public int compareTo(@NonNull DvbDeviceInfoWrapper another) {
- if (getAdapterId() != another.getAdapterId()) {
- return getAdapterId() - another.getAdapterId();
- }
- return getDeviceId() - another.getDeviceId();
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US, "DvbDeviceInfo {adapterId: %d, deviceId: %d}",
- getAdapterId(),
- getDeviceId());
- }
- }
-}
diff --git a/src/com/android/tv/tuner/DvbTunerHal.java b/src/com/android/tv/tuner/DvbTunerHal.java
deleted file mode 100644
index ea977230..00000000
--- a/src/com/android/tv/tuner/DvbTunerHal.java
+++ /dev/null
@@ -1,179 +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;
-
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import com.android.tv.tuner.DvbDeviceAccessor.DvbDeviceInfoWrapper;
-
-import java.util.List;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * A class to handle a hardware Linux DVB API supported tuner device.
- */
-public class DvbTunerHal extends TunerHal {
-
- private static final Object sLock = new Object();
- // @GuardedBy("sLock")
- private static final SortedSet<DvbDeviceInfoWrapper> sUsedDvbDevices = new TreeSet<>();
-
- private final DvbDeviceAccessor mDvbDeviceAccessor;
- private DvbDeviceInfoWrapper mDvbDeviceInfo;
-
- protected DvbTunerHal(Context context) {
- super(context);
- mDvbDeviceAccessor = new DvbDeviceAccessor(context);
- }
-
- @Override
- protected boolean openFirstAvailable() {
- List<DvbDeviceInfoWrapper> deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList();
- if (deviceInfoList == null || deviceInfoList.isEmpty()) {
- Log.e(TAG, "There's no dvb device attached");
- return false;
- }
- synchronized (sLock) {
- for (DvbDeviceInfoWrapper deviceInfo : deviceInfoList) {
- if (!sUsedDvbDevices.contains(deviceInfo)) {
- if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo);
- mDvbDeviceInfo = deviceInfo;
- sUsedDvbDevices.add(deviceInfo);
- getDeliverySystemTypeFromDevice();
- return true;
- }
- }
- }
- Log.e(TAG, "There's no available dvb devices");
- return false;
- }
-
- /**
- * Acquires the tuner device. The requested device will be locked to the current instance if
- * it's not acquired by others.
- *
- * @param deviceInfo a tuner device to open
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- protected boolean open(DvbDeviceInfoWrapper deviceInfo) {
- if (deviceInfo == null) {
- Log.e(TAG, "Device info should not be null");
- return false;
- }
- if (mDvbDeviceInfo != null) {
- Log.e(TAG, "Already acquired");
- return false;
- }
- List<DvbDeviceInfoWrapper> deviceInfoList = mDvbDeviceAccessor.getDvbDeviceList();
- if (deviceInfoList == null || deviceInfoList.isEmpty()) {
- Log.e(TAG, "There's no dvb device attached");
- return false;
- }
- for (DvbDeviceInfoWrapper deviceInfoWrapper : deviceInfoList) {
- if (deviceInfoWrapper.compareTo(deviceInfo) == 0) {
- synchronized (sLock) {
- if (sUsedDvbDevices.contains(deviceInfo)) {
- Log.e(TAG, deviceInfo + " is already taken");
- return false;
- }
- sUsedDvbDevices.add(deviceInfo);
- }
- if (DEBUG) Log.d(TAG, "Available device info: " + deviceInfo);
- mDvbDeviceInfo = deviceInfo;
- return true;
- }
- }
- Log.e(TAG, "There's no such dvb device attached");
- return false;
- }
-
- @Override
- public void close() {
- if (mDvbDeviceInfo != null) {
- if (isStreaming()) {
- stopTune();
- }
- nativeFinalize(mDvbDeviceInfo.getId());
- synchronized (sLock) {
- sUsedDvbDevices.remove(mDvbDeviceInfo);
- }
- mDvbDeviceInfo = null;
- }
- }
-
- @Override
- protected boolean isDeviceOpen() {
- return (mDvbDeviceInfo != null);
- }
-
- @Override
- protected long getDeviceId() {
- if (mDvbDeviceInfo != null) {
- return mDvbDeviceInfo.getId();
- }
- return -1;
- }
-
- @Override
- protected int openDvbFrontEndFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_FRONTEND);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- @Override
- protected int openDvbDemuxFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DEMUX);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- @Override
- protected int openDvbDvrFd() {
- if (mDvbDeviceInfo != null) {
- ParcelFileDescriptor descriptor = mDvbDeviceAccessor.openDvbDevice(
- mDvbDeviceInfo, DvbDeviceAccessor.DVB_DEVICE_DVR);
- if (descriptor != null) {
- return descriptor.detachFd();
- }
- }
- return -1;
- }
-
- /**
- * Gets the number of USB tuner devices currently present.
- */
- public static int getNumberOfDevices(Context context) {
- try {
- return (new DvbDeviceAccessor(context)).getNumOfDvbDevices();
- } catch (Exception e) {
- return 0;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java
deleted file mode 100644
index 1176cdf0..00000000
--- a/src/com/android/tv/tuner/TunerHal.java
+++ /dev/null
@@ -1,352 +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;
-
-import android.content.Context;
-import android.support.annotation.IntDef;
-import android.support.annotation.StringDef;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.Features;
-import com.android.tv.customization.TvCustomizationManager;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * A base class to handle a hardware tuner device.
- */
-public abstract class TunerHal implements AutoCloseable {
- protected static final String TAG = "TunerHal";
- protected static final boolean DEBUG = false;
-
- @IntDef({ FILTER_TYPE_OTHER, FILTER_TYPE_AUDIO, FILTER_TYPE_VIDEO, FILTER_TYPE_PCR })
- @Retention(RetentionPolicy.SOURCE)
- public @interface FilterType {}
- public static final int FILTER_TYPE_OTHER = 0;
- public static final int FILTER_TYPE_AUDIO = 1;
- public static final int FILTER_TYPE_VIDEO = 2;
- public static final int FILTER_TYPE_PCR = 3;
-
- @StringDef({ MODULATION_8VSB, MODULATION_QAM256 })
- @Retention(RetentionPolicy.SOURCE)
- public @interface ModulationType {}
- public static final String MODULATION_8VSB = "8VSB";
- public static final String MODULATION_QAM256 = "QAM256";
-
- @IntDef({ DELIVERY_SYSTEM_UNDEFINED, DELIVERY_SYSTEM_ATSC, DELIVERY_SYSTEM_DVBC,
- DELIVERY_SYSTEM_DVBS, DELIVERY_SYSTEM_DVBS2, DELIVERY_SYSTEM_DVBT,
- DELIVERY_SYSTEM_DVBT2 })
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeliverySystemType {}
- public static final int DELIVERY_SYSTEM_UNDEFINED = 0;
- public static final int DELIVERY_SYSTEM_ATSC = 1;
- public static final int DELIVERY_SYSTEM_DVBC = 2;
- public static final int DELIVERY_SYSTEM_DVBS = 3;
- public static final int DELIVERY_SYSTEM_DVBS2 = 4;
- public static final int DELIVERY_SYSTEM_DVBT = 5;
- public static final int DELIVERY_SYSTEM_DVBT2 = 6;
-
- @IntDef({ TUNER_TYPE_BUILT_IN, TUNER_TYPE_USB, TUNER_TYPE_NETWORK })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TunerType {}
- public static final int TUNER_TYPE_BUILT_IN = 1;
- public static final int TUNER_TYPE_USB = 2;
- public static final int TUNER_TYPE_NETWORK = 3;
-
- protected static final int PID_PAT = 0;
- protected static final int PID_ATSC_SI_BASE = 0x1ffb;
- protected static final int PID_DVB_SDT = 0x0011;
- protected static final int PID_DVB_EIT = 0x0012;
- protected static final int DEFAULT_VSB_TUNE_TIMEOUT_MS = 2000;
- protected static final int DEFAULT_QAM_TUNE_TIMEOUT_MS = 4000; // Some device takes time for
- // QAM256 tuning.
- @IntDef({
- BUILT_IN_TUNER_TYPE_LINUX_DVB
- })
- @Retention(RetentionPolicy.SOURCE)
- private @interface BuiltInTunerType {}
- private static final int BUILT_IN_TUNER_TYPE_LINUX_DVB = 1;
-
- private static Integer sBuiltInTunerType;
-
- protected @DeliverySystemType int mDeliverySystemType;
- private boolean mIsStreaming;
- private int mFrequency;
- private String mModulation;
-
- static {
- System.loadLibrary("tunertvinput_jni");
- }
-
- /**
- * Creates a TunerHal instance.
- * @param context context for creating the TunerHal instance
- * @return the TunerHal instance
- */
- @WorkerThread
- public synchronized static TunerHal createInstance(Context context) {
- TunerHal tunerHal = null;
- if (DvbTunerHal.getNumberOfDevices(context) > 0) {
- if (DEBUG) Log.d(TAG, "Use DvbTunerHal");
- tunerHal = new DvbTunerHal(context);
- }
- return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null;
- }
-
- /**
- * Gets the number of tuner devices currently present.
- */
- @WorkerThread
- public static Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
- if (useBuiltInTuner(context)) {
- if (getBuiltInTunerType(context) == BUILT_IN_TUNER_TYPE_LINUX_DVB) {
- return new Pair<>(TUNER_TYPE_BUILT_IN, DvbTunerHal.getNumberOfDevices(context));
- }
- } else {
- int usbTunerCount = DvbTunerHal.getNumberOfDevices(context);
- if (usbTunerCount > 0) {
- return new Pair<>(TUNER_TYPE_USB, usbTunerCount);
- }
- }
- return new Pair<>(null, 0);
- }
-
- /**
- * Check a delivery system is for DVB or not.
- */
- public static boolean isDvbDeliverySystem(@DeliverySystemType int deliverySystemType) {
- return deliverySystemType == DELIVERY_SYSTEM_DVBC
- || deliverySystemType == DELIVERY_SYSTEM_DVBS
- || deliverySystemType == DELIVERY_SYSTEM_DVBS2
- || deliverySystemType == DELIVERY_SYSTEM_DVBT
- || deliverySystemType == DELIVERY_SYSTEM_DVBT2;
- }
-
- /**
- * Returns if tuner input service would use built-in tuners instead of USB tuners or network
- * tuners.
- */
- static boolean useBuiltInTuner(Context context) {
- return getBuiltInTunerType(context) != 0;
- }
-
- private static @BuiltInTunerType int getBuiltInTunerType(Context context) {
- if (sBuiltInTunerType == null) {
- sBuiltInTunerType = 0;
- if (TvCustomizationManager.hasLinuxDvbBuiltInTuner(context)
- && DvbTunerHal.getNumberOfDevices(context) > 0) {
- sBuiltInTunerType = BUILT_IN_TUNER_TYPE_LINUX_DVB;
- }
- }
- return sBuiltInTunerType;
- }
-
- protected TunerHal(Context context) {
- mIsStreaming = false;
- mFrequency = -1;
- mModulation = null;
- }
-
- protected boolean isStreaming() {
- return mIsStreaming;
- }
-
- protected void getDeliverySystemTypeFromDevice() {
- if (mDeliverySystemType == DELIVERY_SYSTEM_UNDEFINED) {
- mDeliverySystemType = nativeGetDeliverySystemType(getDeviceId());
- }
- }
-
- /**
- * Returns {@code true} if this tuner HAL can be reused to save tuning time between channels
- * of the same frequency.
- */
- public boolean isReusable() {
- return true;
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- close();
- }
-
- protected native void nativeFinalize(long deviceId);
-
- /**
- * Acquires the first available tuner device. If there is a tuner device that is available, the
- * tuner device will be locked to the current instance.
- *
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- protected abstract boolean openFirstAvailable();
-
- protected abstract boolean isDeviceOpen();
-
- protected abstract long getDeviceId();
-
- /**
- * Sets the tuner channel. This should be called after acquiring a tuner device.
- *
- * @param frequency a frequency of the channel to tune to
- * @param modulation a modulation method of the channel to tune to
- * @param channelNumber channel number when channel number is already known. Some tuner HAL
- * may use channelNumber instead of frequency for tune.
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- public synchronized boolean tune(int frequency, @ModulationType String modulation,
- String channelNumber) {
- if (!isDeviceOpen()) {
- Log.e(TAG, "There's no available device");
- return false;
- }
- if (mIsStreaming) {
- nativeCloseAllPidFilters(getDeviceId());
- mIsStreaming = false;
- }
-
- // When tuning to a new channel in the same frequency, there's no need to stop current tuner
- // device completely and the only thing necessary for tuning is reopening pid filters.
- if (mFrequency == frequency && Objects.equals(mModulation, modulation)) {
- addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
- addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (isDvbDeliverySystem(mDeliverySystemType)) {
- addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
- addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
- }
- mIsStreaming = true;
- return true;
- }
- int timeout_ms = modulation.equals(MODULATION_8VSB) ? DEFAULT_VSB_TUNE_TIMEOUT_MS
- : DEFAULT_QAM_TUNE_TIMEOUT_MS;
- if (nativeTune(getDeviceId(), frequency, modulation, timeout_ms)) {
- addPidFilter(PID_PAT, FILTER_TYPE_OTHER);
- addPidFilter(PID_ATSC_SI_BASE, FILTER_TYPE_OTHER);
- if (isDvbDeliverySystem(mDeliverySystemType)) {
- addPidFilter(PID_DVB_SDT, FILTER_TYPE_OTHER);
- addPidFilter(PID_DVB_EIT, FILTER_TYPE_OTHER);
- }
- mFrequency = frequency;
- mModulation = modulation;
- mIsStreaming = true;
- return true;
- }
- return false;
- }
-
- protected native boolean nativeTune(long deviceId, int frequency,
- @ModulationType String modulation, int timeout_ms);
-
- /**
- * Sets a pid filter. This should be set after setting a channel.
- *
- * @param pid a pid number to be added to filter list
- * @param filterType a type of pid. Must be one of (FILTER_TYPE_XXX)
- * @return {@code true} if the operation was successful, {@code false} otherwise
- */
- public synchronized boolean addPidFilter(int pid, @FilterType int filterType) {
- if (!isDeviceOpen()) {
- Log.e(TAG, "There's no available device");
- return false;
- }
- if (pid >= 0 && pid <= 0x1fff) {
- nativeAddPidFilter(getDeviceId(), pid, filterType);
- return true;
- }
- return false;
- }
-
- protected native void nativeAddPidFilter(long deviceId, int pid, @FilterType int filterType);
- protected native void nativeCloseAllPidFilters(long deviceId);
- protected native void nativeSetHasPendingTune(long deviceId, boolean hasPendingTune);
- protected native int nativeGetDeliverySystemType(long deviceId);
-
- /**
- * Stops current tuning. The tuner device and pid filters will be reset by this call and make
- * the tuner ready to accept another tune request.
- */
- public synchronized void stopTune() {
- if (isDeviceOpen()) {
- if (mIsStreaming) {
- nativeCloseAllPidFilters(getDeviceId());
- }
- nativeStopTune(getDeviceId());
- }
- mIsStreaming = false;
- mFrequency = -1;
- mModulation = null;
- }
-
- public void setHasPendingTune(boolean hasPendingTune) {
- nativeSetHasPendingTune(getDeviceId(), hasPendingTune);
- }
-
- public int getDeliverySystemType() {
- return mDeliverySystemType;
- }
-
- protected native void nativeStopTune(long deviceId);
-
- /**
- * This method must be called after {@link TunerHal#tune} and before
- * {@link TunerHal#stopTune}. Writes at most maxSize TS frames in a buffer
- * provided by the user. The frames employ MPEG encoding.
- *
- * @param javaBuffer a buffer to write the video data in
- * @param javaBufferSize the max amount of bytes to write in this buffer. Usually this number
- * should be equal to the length of the buffer.
- * @return the amount of bytes written in the buffer. Note that this value could be 0 if no new
- * frames have been obtained since the last call.
- */
- public synchronized int readTsStream(byte[] javaBuffer, int javaBufferSize) {
- if (isDeviceOpen()) {
- return nativeWriteInBuffer(getDeviceId(), javaBuffer, javaBufferSize);
- } else {
- return 0;
- }
- }
-
- protected native int nativeWriteInBuffer(long deviceId, byte[] javaBuffer, int javaBufferSize);
-
- /**
- * Opens Linux DVB frontend device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbFrontEndFd() {
- return -1;
- }
-
- /**
- * Opens Linux DVB demux device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbDemuxFd() {
- return -1;
- }
-
- /**
- * Opens Linux DVB dvr device. This method is called from native JNI and used only for
- * DvbTunerHal.
- */
- protected int openDvbDvrFd() {
- return -1;
- }
-}
diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java
index e06b9b4a..02611bbf 100644
--- a/src/com/android/tv/tuner/TunerInputController.java
+++ b/src/com/android/tv/tuner/TunerInputController.java
@@ -17,6 +17,9 @@
package com.android.tv.tuner;
import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -24,10 +27,14 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
@@ -38,118 +45,132 @@ import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
-
-import com.android.tv.Features;
import com.android.tv.R;
+import com.android.tv.Starter;
import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.setup.TunerSetupActivity;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
+import com.android.tv.TvSingletons;
+import com.android.tv.common.BuildConfig;
+import com.android.tv.common.util.SystemPropertiesProxy;
+
+import com.android.tv.tuner.setup.BaseTunerSetupActivity;
+import com.android.tv.tuner.util.TunerInputInfoUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
- * Controls the package visibility of {@link TunerTvInputService}.
- * <p>
- * Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED},
- * {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED}
- * to update the connection status of the supported USB TV tuners.
+ * Controls the package visibility of {@link BaseTunerTvInputService}.
+ *
+ * <p>Listens to broadcast intent for {@link Intent#ACTION_BOOT_COMPLETED}, {@code
+ * UsbManager.ACTION_USB_DEVICE_ATTACHED}, and {@code UsbManager.ACTION_USB_DEVICE_ATTACHED} to
+ * update the connection status of the supported USB TV tuners.
*/
public class TunerInputController {
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final String TAG = "TunerInputController";
private static final String PREFERENCE_IS_NETWORK_TUNER_ATTACHED = "network_tuner";
private static final String SECURITY_PATCH_LEVEL_KEY = "ro.build.version.security_patch";
private static final String SECURITY_PATCH_LEVEL_FORMAT = "yyyy-MM-dd";
+ private static final String PLAY_STORE_LINK_TEMPLATE = "market://details?id=%s";
- /**
- * Action of {@link Intent} to check network connection repeatedly when it is necessary.
- */
- private static final String CHECKING_NETWORK_CONNECTION =
- "com.android.tv.action.CHECKING_NETWORK_CONNECTION";
+ /** Action of {@link Intent} to check network connection repeatedly when it is necessary. */
+ private static final String CHECKING_NETWORK_TUNER_STATUS =
+ "com.android.tv.action.CHECKING_NETWORK_TUNER_STATUS";
private static final String EXTRA_CHECKING_DURATION =
"com.android.tv.action.extra.CHECKING_DURATION";
+ private static final String EXTRA_DEVICE_IP = "com.android.tv.action.extra.DEVICE_IP";
private static final long INITIAL_CHECKING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
private static final long MAXIMUM_CHECKING_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final String NOTIFICATION_CHANNEL_ID = "tuner_discovery_notification";
+ // TODO: Load settings from XML file
private static final TunerDevice[] TUNER_DEVICES = {
new TunerDevice(0x2040, 0xb123, null), // WinTV-HVR-955Q
new TunerDevice(0x07ca, 0x0837, null), // AverTV Volar Hybrid Q
// WinTV-dualHD (bulk) will be supported after 2017 April security patch.
new TunerDevice(0x2040, 0x826d, "2017-04-01"), // WinTV-dualHD (bulk)
- // STOPSHIP: Add WinTV-soloHD (Isoc) temporary for test. Remove this after test complete.
new TunerDevice(0x2040, 0x0264, null),
};
private static final int MSG_ENABLE_INPUT_SERVICE = 1000;
private static final long DVB_DRIVER_CHECK_DELAY_MS = 300;
- /**
- * Checks status of USB devices to see if there are available USB tuners connected.
- */
- public static void onCheckingUsbTunerStatus(Context context, String action) {
- onCheckingUsbTunerStatus(context, action, new CheckDvbDeviceHandler());
- }
+ private final ComponentName usbTunerComponent;
+ private final ComponentName networkTunerComponent;
+ private final ComponentName builtInTunerComponent;
+ private final Map<TunerDevice, ComponentName> mTunerServiceMapping = new HashMap<>();
- private static void onCheckingUsbTunerStatus(Context context, String action,
- @NonNull CheckDvbDeviceHandler handler) {
- SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(context);
- if (TunerHal.useBuiltInTuner(context)) {
- enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN);
- return;
+ private final Map<ComponentName, String> mTunerApplicationNames = new HashMap<>();
+ private final Map<ComponentName, String> mNotificationMessages = new HashMap<>();
+ private final Map<ComponentName, Bitmap> mNotificationLargeIcons = new HashMap<>();
+
+ private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler(this);
+
+ public TunerInputController(ComponentName embeddedTuner) {
+ usbTunerComponent = embeddedTuner;
+ networkTunerComponent = usbTunerComponent;
+ builtInTunerComponent = usbTunerComponent;
+ for (TunerDevice device : TUNER_DEVICES) {
+ mTunerServiceMapping.put(device, usbTunerComponent);
}
- // Falls back to the below to check USB tuner devices.
- boolean enabled = isUsbTunerConnected(context);
+ }
+
+ /** Checks status of USB devices to see if there are available USB tuners connected. */
+ public void onCheckingUsbTunerStatus(Context context, String action) {
+ onCheckingUsbTunerStatus(context, action, mHandler);
+ }
+
+ private void onCheckingUsbTunerStatus(
+ Context context, String action, @NonNull CheckDvbDeviceHandler handler) {
+ Set<TunerDevice> connectedUsbTuners = getConnectedUsbTuners(context);
handler.removeMessages(MSG_ENABLE_INPUT_SERVICE);
- if (enabled) {
+ if (!connectedUsbTuners.isEmpty()) {
// Need to check if DVB driver is accessible. Since the driver creation
// could be happen after the USB event, delay the checking by
// DVB_DRIVER_CHECK_DELAY_MS.
- handler.sendMessageDelayed(handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
+ handler.sendMessageDelayed(
+ handler.obtainMessage(MSG_ENABLE_INPUT_SERVICE, context),
DVB_DRIVER_CHECK_DELAY_MS);
} else {
- if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
- // Since network tuner is attached, do not disable TunerTvInput,
- // just updates the TvInputInfo.
- TunerInputInfoUtils.updateTunerInputInfo(context);
- return;
- }
- enableTunerTvInputService(context, false, false, TextUtils
- .equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED) ?
- TunerHal.TUNER_TYPE_USB : null);
+ handleTunerStatusChanged(
+ context,
+ false,
+ connectedUsbTuners,
+ TextUtils.equals(action, UsbManager.ACTION_USB_DEVICE_DETACHED)
+ ? TunerHal.TUNER_TYPE_USB
+ : null);
}
}
- private static void onNetworkTunerChanged(Context context, boolean enabled) {
+ private void onNetworkTunerChanged(Context context, boolean enabled) {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences.contains(PREFERENCE_IS_NETWORK_TUNER_ATTACHED)
+ && sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
+ == enabled) {
+ // the status is not changed
+ return;
+ }
if (enabled) {
- // Network tuner detection is initiated by UI. So the app should not
- // be killed.
sharedPreferences.edit().putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, true).apply();
- enableTunerTvInputService(context, true, true, TunerHal.TUNER_TYPE_NETWORK);
} else {
- sharedPreferences.edit()
- .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false).apply();
- if(!isUsbTunerConnected(context) && !TunerHal.useBuiltInTuner(context)) {
- // Network tuner detection is initiated by UI. So the app should not
- // be killed.
- enableTunerTvInputService(context, false, true, TunerHal.TUNER_TYPE_NETWORK);
- } else {
- // Since USB tuner is attached, do not disable TunerTvInput,
- // just updates the TvInputInfo.
- TunerInputInfoUtils.updateTunerInputInfo(context);
- }
+ sharedPreferences
+ .edit()
+ .putBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)
+ .apply();
}
+ // Network tuner detection is initiated by UI. So the app should not
+ // be killed.
+ handleTunerStatusChanged(
+ context, true, getConnectedUsbTuners(context), TunerHal.TUNER_TYPE_NETWORK);
}
/**
@@ -158,66 +179,131 @@ public class TunerInputController {
* @param context {@link Context} instance
* @return {@code true} if any tuner device we support is plugged in
*/
- private static boolean isUsbTunerConnected(Context context) {
+ private Set<TunerDevice> getConnectedUsbTuners(Context context) {
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
Map<String, UsbDevice> deviceList = manager.getDeviceList();
String currentSecurityLevel =
SystemPropertiesProxy.getString(SECURITY_PATCH_LEVEL_KEY, null);
+ Set<TunerDevice> devices = new HashSet<>();
for (UsbDevice device : deviceList.values()) {
if (DEBUG) {
Log.d(TAG, "Device: " + device);
}
for (TunerDevice tuner : TUNER_DEVICES) {
- if (tuner.equals(device) && tuner.isSupported(currentSecurityLevel)) {
+ if (tuner.equalsTo(device) && tuner.isSupported(currentSecurityLevel)) {
Log.i(TAG, "Tuner found");
- return true;
+ devices.add(tuner);
}
}
}
- return false;
+ return devices;
+ }
+
+ private void handleTunerStatusChanged(
+ Context context,
+ boolean forceDontKillApp,
+ Set<TunerDevice> connectedUsbTuners,
+ Integer triggerType) {
+ Map<ComponentName, Integer> serviceToEnable = new HashMap<>();
+ Set<ComponentName> serviceToDisable = new HashSet<>();
+ serviceToDisable.add(builtInTunerComponent);
+ serviceToDisable.add(networkTunerComponent);
+ if (TunerFeatures.TUNER.isEnabled(context)) {
+ // TODO: support both built-in tuner and other tuners at the same time?
+ if (TunerHal.useBuiltInTuner(context)) {
+ enableTunerTvInputService(
+ context, true, false, TunerHal.TUNER_TYPE_BUILT_IN, builtInTunerComponent);
+ return;
+ }
+ SharedPreferences sharedPreferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences.getBoolean(PREFERENCE_IS_NETWORK_TUNER_ATTACHED, false)) {
+ serviceToEnable.put(networkTunerComponent, TunerHal.TUNER_TYPE_NETWORK);
+ }
+ }
+ for (TunerDevice device : TUNER_DEVICES) {
+ if (TunerFeatures.TUNER.isEnabled(context) && connectedUsbTuners.contains(device)) {
+ serviceToEnable.put(mTunerServiceMapping.get(device), TunerHal.TUNER_TYPE_USB);
+ } else {
+ serviceToDisable.add(mTunerServiceMapping.get(device));
+ }
+ }
+ serviceToDisable.removeAll(serviceToEnable.keySet());
+ for (ComponentName serviceComponent : serviceToEnable.keySet()) {
+ if (isTunerPackageInstalled(context, serviceComponent)) {
+ enableTunerTvInputService(
+ context,
+ true,
+ forceDontKillApp,
+ serviceToEnable.get(serviceComponent),
+ serviceComponent);
+ } else {
+ sendNotificationToInstallPackage(context, serviceComponent);
+ }
+ }
+ for (ComponentName serviceComponent : serviceToDisable) {
+ if (isTunerPackageInstalled(context, serviceComponent)) {
+ enableTunerTvInputService(
+ context, false, forceDontKillApp, triggerType, serviceComponent);
+ } else {
+ cancelNotificationToInstallPackage(context, serviceComponent);
+ }
+ }
}
/**
- * Enable/disable the component {@link TunerTvInputService}.
+ * Enable/disable the component {@link BaseTunerTvInputService}.
*
* @param context {@link Context} instance
* @param enabled {@code true} to enable the service; otherwise {@code false}
*/
- private static void enableTunerTvInputService(Context context, boolean enabled,
- boolean forceDontKillApp, Integer tunerType) {
+ private static void enableTunerTvInputService(
+ Context context,
+ boolean enabled,
+ boolean forceDontKillApp,
+ Integer tunerType,
+ ComponentName serviceComponent) {
if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled);
- PackageManager pm = context.getPackageManager();
- ComponentName componentName = new ComponentName(context, TunerTvInputService.class);
-
- // Don't kill app by enabling/disabling TvActivity. If LC is killed by enabling/disabling
- // TvActivity, the following pm.setComponentEnabledSetting doesn't work.
- ((TvApplication) context.getApplicationContext()).handleInputCountChanged(
- true, enabled, true);
- // Since PackageManager.DONT_KILL_APP delays the operation by 10 seconds
- // (PackageManagerService.BROADCAST_DELAY), we'd better avoid using it. It is used only
- // when the LiveChannels app is active since we don't want to kill the running app.
- int flags = forceDontKillApp
- || TvApplication.getSingletons(context).getMainActivityWrapper().isCreated()
- ? PackageManager.DONT_KILL_APP : 0;
- int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- if (newState != pm.getComponentEnabledSetting(componentName)) {
- // Send/cancel the USB tuner TV input setup notification.
- TunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
- // Enable/disable the USB tuner TV input.
- pm.setComponentEnabledSetting(componentName, newState, flags);
- if (!enabled && tunerType != null) {
- if (tunerType == TunerHal.TUNER_TYPE_USB) {
- Toast.makeText(context, R.string.msg_usb_tuner_disconnected,
- Toast.LENGTH_SHORT).show();
- } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
- Toast.makeText(context, R.string.msg_network_tuner_disconnected,
- Toast.LENGTH_SHORT).show();
+ PackageManager pm = context.getPackageManager();
+ int newState =
+ enabled
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ if (newState != pm.getComponentEnabledSetting(serviceComponent)) {
+ int flags = forceDontKillApp ? PackageManager.DONT_KILL_APP : 0;
+ if (serviceComponent.getPackageName().equals(context.getPackageName())) {
+ // Don't kill APP when handling input count changing. Or the following
+ // setComponentEnabledSetting() call won't work.
+ ((TvApplication) context.getApplicationContext())
+ .handleInputCountChanged(true, enabled, true);
+ // Bundled input. Don't kill app if LiveChannels app is active since we don't want
+ // to kill the running app.
+ if (TvSingletons.getSingletons(context).getMainActivityWrapper().isCreated()) {
+ flags |= PackageManager.DONT_KILL_APP;
+ }
+ // Send/cancel the USB tuner TV input setup notification.
+ BaseTunerSetupActivity.onTvInputEnabled(context, enabled, tunerType);
+ if (!enabled && tunerType != null) {
+ if (tunerType == TunerHal.TUNER_TYPE_USB) {
+ Toast.makeText(
+ context,
+ R.string.msg_usb_tuner_disconnected,
+ Toast.LENGTH_SHORT)
+ .show();
+ } else if (tunerType == TunerHal.TUNER_TYPE_NETWORK) {
+ Toast.makeText(
+ context,
+ R.string.msg_network_tuner_disconnected,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
}
}
+ // Enable/disable the USB tuner TV input.
+ pm.setComponentEnabledSetting(serviceComponent, newState, flags);
if (DEBUG) Log.d(TAG, "Status updated:" + enabled);
- } else if (enabled) {
+ } else if (enabled && serviceComponent.getPackageName().equals(context.getPackageName())) {
// When # of tuners is changed or the tuner input service is switching from/to using
// network tuners or the device just boots.
TunerInputInfoUtils.updateTunerInputInfo(context);
@@ -227,96 +313,179 @@ public class TunerInputController {
/**
* Discovers a network tuner. If the network connection is down, it won't repeatedly checking.
*/
- public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
- boolean runningInMainProcess =
- TvApplication.getSingletons(context).isRunningInMainProcess();
- SoftPreconditions.checkState(runningInMainProcess);
- if (!runningInMainProcess) {
- return;
- }
- executeNetworkTunerDiscoveryAsyncTask(context, 0);
+ public void executeNetworkTunerDiscoveryAsyncTask(final Context context) {
+ executeNetworkTunerDiscoveryAsyncTask(context, 0, 0);
}
/**
* Discovers a network tuner.
+ *
* @param context {@link Context}
- * @param repeatedDurationMs the time length to wait to repeatedly check network status to start
- * finding network tuner when the network connection is not available.
- * {@code 0} to disable repeatedly checking.
+ * @param repeatedDurationMs The time length to wait to repeatedly check network status to start
+ * finding network tuner when the network connection is not available. {@code 0} to disable
+ * repeatedly checking.
+ * @param deviceIp The previous discovered device IP, 0 if none.
*/
- private static void executeNetworkTunerDiscoveryAsyncTask(final Context context,
- final long repeatedDurationMs) {
- if (!Features.NETWORK_TUNER.isEnabled(context)) {
+ private void executeNetworkTunerDiscoveryAsyncTask(
+ final Context context, final long repeatedDurationMs, final int deviceIp) {
+ if (!TunerFeatures.NETWORK_TUNER.isEnabled(context)) {
return;
}
- new AsyncTask<Void, Void, Boolean>() {
- @Override
- protected Boolean doInBackground(Void... params) {
- if (isNetworkConnected(context)) {
+ final Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
+ networkCheckingIntent.setAction(CHECKING_NETWORK_TUNER_STATUS);
+ if (!isNetworkConnected(context) && repeatedDurationMs > 0) {
+ sendCheckingAlarm(context, networkCheckingIntent, repeatedDurationMs);
+ } else {
+ new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ Boolean result = null;
// Implement and execute network tuner discovery AsyncTask here.
- } else if (repeatedDurationMs > 0) {
- AlarmManager alarmManager =
- (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent networkCheckingIntent = new Intent(context, IntentReceiver.class);
- networkCheckingIntent.setAction(CHECKING_NETWORK_CONNECTION);
- networkCheckingIntent.putExtra(EXTRA_CHECKING_DURATION, repeatedDurationMs);
- PendingIntent alarmIntent = PendingIntent.getBroadcast(
- context, 0, networkCheckingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()
- + repeatedDurationMs, alarmIntent);
+ return result;
}
- return null;
- }
- @Override
- protected void onPostExecute(Boolean result) {
- if (result == null) {
- return;
+ @Override
+ protected void onPostExecute(Boolean foundNetworkTuner) {
+ if (foundNetworkTuner == null) {
+ return;
+ }
+ sendCheckingAlarm(
+ context,
+ networkCheckingIntent,
+ foundNetworkTuner ? INITIAL_CHECKING_DURATION_MS : repeatedDurationMs);
+ onNetworkTunerChanged(context, foundNetworkTuner);
}
- onNetworkTunerChanged(context, result);
- }
- }.execute();
+ }.execute();
+ }
}
private static boolean isNetworkConnected(Context context) {
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
+ private static void sendCheckingAlarm(Context context, Intent intent, long delayMs) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ intent.putExtra(EXTRA_CHECKING_DURATION, delayMs);
+ PendingIntent alarmIntent =
+ PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + delayMs,
+ alarmIntent);
+ }
+
+ private static boolean isTunerPackageInstalled(
+ Context context, ComponentName serviceComponent) {
+ try {
+ context.getPackageManager().getPackageInfo(serviceComponent.getPackageName(), 0);
+ return true;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private void sendNotificationToInstallPackage(Context context, ComponentName serviceComponent) {
+ if (!BuildConfig.ENG) {
+ return;
+ }
+ String applicationName = mTunerApplicationNames.get(serviceComponent);
+ if (applicationName == null) {
+ applicationName = context.getString(R.string.tuner_install_default_application_name);
+ }
+ String contentTitle =
+ context.getString(
+ R.string.tuner_install_notification_content_title, applicationName);
+ String contentText = mNotificationMessages.get(serviceComponent);
+ if (contentText == null) {
+ contentText = context.getString(R.string.tuner_install_notification_content_text);
+ }
+ Bitmap largeIcon = mNotificationLargeIcons.get(serviceComponent);
+ if (largeIcon == null) {
+ // TODO: Make a better default image.
+ largeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_store);
+ }
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
+ createNotificationChannel(context, notificationManager);
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(
+ Uri.parse(
+ String.format(
+ PLAY_STORE_LINK_TEMPLATE, serviceComponent.getPackageName())));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
+ builder.setAutoCancel(true)
+ .setSmallIcon(R.drawable.ic_launcher_s)
+ .setLargeIcon(largeIcon)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setCategory(Notification.CATEGORY_RECOMMENDATION)
+ .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
+ notificationManager.notify(serviceComponent.getPackageName(), 0, builder.build());
+ }
+
+ private static void cancelNotificationToInstallPackage(
+ Context context, ComponentName serviceComponent) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(serviceComponent.getPackageName(), 0);
+ }
+
+ private static void createNotificationChannel(
+ Context context, NotificationManager notificationManager) {
+ notificationManager.createNotificationChannel(
+ new NotificationChannel(
+ NOTIFICATION_CHANNEL_ID,
+ context.getResources()
+ .getString(R.string.ut_setup_notification_channel_name),
+ NotificationManager.IMPORTANCE_HIGH));
+ }
+
public static class IntentReceiver extends BroadcastReceiver {
- private final CheckDvbDeviceHandler mHandler = new CheckDvbDeviceHandler();
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent);
- TvApplication.setCurrentRunningProcess(context, true);
- if (!Features.TUNER.isEnabled(context)) {
- enableTunerTvInputService(context, false, false, null);
+ Starter.start(context);
+ TunerInputController tunerInputController =
+ TvSingletons.getSingletons(context).getTunerInputController();
+ if (!TunerFeatures.TUNER.isEnabled(context)) {
+ tunerInputController.handleTunerStatusChanged(
+ context, false, Collections.emptySet(), null);
return;
}
switch (intent.getAction()) {
case Intent.ACTION_BOOT_COMPLETED:
- executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS);
+ tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
+ context, INITIAL_CHECKING_DURATION_MS, 0);
+ // fall through
case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED:
case UsbManager.ACTION_USB_DEVICE_ATTACHED:
case UsbManager.ACTION_USB_DEVICE_DETACHED:
- onCheckingUsbTunerStatus(context, intent.getAction(), mHandler);
+ tunerInputController.onCheckingUsbTunerStatus(context, intent.getAction());
break;
- case CHECKING_NETWORK_CONNECTION:
- long repeatedDurationMs = intent.getLongExtra(EXTRA_CHECKING_DURATION,
- INITIAL_CHECKING_DURATION_MS);
- executeNetworkTunerDiscoveryAsyncTask(context,
- Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS));
+ case CHECKING_NETWORK_TUNER_STATUS:
+ long repeatedDurationMs =
+ intent.getLongExtra(
+ EXTRA_CHECKING_DURATION, INITIAL_CHECKING_DURATION_MS);
+ tunerInputController.executeNetworkTunerDiscoveryAsyncTask(
+ context,
+ Math.min(repeatedDurationMs * 2, MAXIMUM_CHECKING_DURATION_MS),
+ intent.getIntExtra(EXTRA_DEVICE_IP, 0));
break;
+ default: // fall out
}
}
}
/**
- * Simple data holder for a USB device. Used to represent a tuner model, and compare
- * against {@link UsbDevice}.
+ * Simple data holder for a USB device. Used to represent a tuner model, and compare against
+ * {@link UsbDevice}.
*/
private static class TunerDevice {
private final int vendorId;
@@ -331,7 +500,7 @@ public class TunerInputController {
this.minSecurityLevel = minSecurityLevel;
}
- private boolean equals(UsbDevice device) {
+ private boolean equalsTo(UsbDevice device) {
return device.getVendorId() == vendorId && device.getProductId() == productId;
}
@@ -354,10 +523,13 @@ public class TunerInputController {
}
private static class CheckDvbDeviceHandler extends Handler {
+
+ private final TunerInputController mTunerInputController;
private DvbDeviceAccessor mDvbDeviceAccessor;
- CheckDvbDeviceHandler() {
+ CheckDvbDeviceHandler(TunerInputController tunerInputController) {
super(Looper.getMainLooper());
+ this.mTunerInputController = tunerInputController;
}
@Override
@@ -369,9 +541,15 @@ public class TunerInputController {
mDvbDeviceAccessor = new DvbDeviceAccessor(context);
}
boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable();
- enableTunerTvInputService(
- context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null);
+ mTunerInputController.handleTunerStatusChanged(
+ context,
+ false,
+ enabled
+ ? mTunerInputController.getConnectedUsbTuners(context)
+ : Collections.emptySet(),
+ TunerHal.TUNER_TYPE_USB);
break;
+ default: // fall out
}
}
}
diff --git a/src/com/android/tv/tuner/TunerPreferenceProvider.java b/src/com/android/tv/tuner/TunerPreferenceProvider.java
deleted file mode 100644
index 3a3561b6..00000000
--- a/src/com/android/tv/tuner/TunerPreferenceProvider.java
+++ /dev/null
@@ -1,203 +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;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-
-/**
- * A content provider for storing the preferences. It's used across TV app and USB tuner TV input.
- */
-public class TunerPreferenceProvider extends ContentProvider {
- /** The authority of the provider */
- public static final String AUTHORITY = "com.android.tv.tuner.preferences";
-
- private static final String PATH_PREFERENCES = "preferences";
-
- private static final int DATABASE_VERSION = 1;
- private static final String DATABASE_NAME = "usbtuner_preferences.db";
- private static final String PREFERENCES_TABLE = "preferences";
-
- private static final int MATCH_PREFERENCE = 1;
- private static final int MATCH_PREFERENCE_KEY = 2;
-
- private static final UriMatcher sUriMatcher;
-
- private DatabaseOpenHelper mDatabaseOpenHelper;
-
- static {
- sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- sUriMatcher.addURI(AUTHORITY, "preferences", MATCH_PREFERENCE);
- sUriMatcher.addURI(AUTHORITY, "preferences/*", MATCH_PREFERENCE_KEY);
- }
-
- /**
- * Builds a Uri that points to a specific preference.
-
- * @param key a key of the preference to point to
- */
- public static Uri buildPreferenceUri(String key) {
- return Preferences.CONTENT_URI.buildUpon().appendPath(key).build();
- }
-
- /**
- * Columns definitions for the preferences table.
- */
- public interface Preferences {
-
- /**
- * The content:// style for the preferences table.
- */
- Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES);
-
- /**
- * The MIME type of a directory of preferences.
- */
- String CONTENT_TYPE = "vnd.android.cursor.dir/preferences";
-
- /**
- * The MIME type of a single preference.
- */
- String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences";
-
- /**
- * The ID of this preference.
- *
- * <p>This is auto-incremented.
- *
- * <p>Type: INTEGER
- */
- String _ID = "_id";
-
- /**
- * The key of this preference.
- *
- * <p>Should be unique.
- *
- * <p>Type: TEXT
- */
- String COLUMN_KEY = "key";
-
- /**
- * The value of this preference.
- *
- * <p>Type: TEXT
- */
- String COLUMN_VALUE = "value";
- }
-
- private static class DatabaseOpenHelper extends SQLiteOpenHelper {
- public DatabaseOpenHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + PREFERENCES_TABLE + " ("
- + Preferences._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
- + Preferences.COLUMN_KEY + " TEXT NOT NULL,"
- + Preferences.COLUMN_VALUE + " TEXT);");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // No-op
- }
- }
-
- @Override
- public boolean onCreate() {
- mDatabaseOpenHelper = new DatabaseOpenHelper(getContext());
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- int match = sUriMatcher.match(uri);
- if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) {
- throw new UnsupportedOperationException();
- }
- SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase();
- Cursor cursor = db.query(PREFERENCES_TABLE, projection, selection, selectionArgs,
- null, null, sortOrder);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- @Override
- public String getType(Uri uri) {
- switch (sUriMatcher.match(uri)) {
- case MATCH_PREFERENCE:
- return Preferences.CONTENT_TYPE;
- case MATCH_PREFERENCE_KEY:
- return Preferences.CONTENT_ITEM_TYPE;
- }
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- /**
- * Inserts a preference row into the preference table.
- *
- * If a key is already exists in the table, it removes the old row and inserts a new row.
- *
- * @param uri the URL of the table to insert into
- * @param values the initial values for the newly inserted row
- * @return the URL of the newly created row
- */
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- if (sUriMatcher.match(uri) != MATCH_PREFERENCE) {
- throw new UnsupportedOperationException();
- }
- return insertRow(uri, values);
- }
-
- private Uri insertRow(Uri uri, ContentValues values) {
- SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase();
-
- // Remove the old row.
- db.delete(PREFERENCES_TABLE, Preferences.COLUMN_KEY + " like ?",
- new String[]{values.getAsString(Preferences.COLUMN_KEY)});
-
- long rowId = db.insert(PREFERENCES_TABLE, null, values);
- if (rowId > 0) {
- Uri rowUri = buildPreferenceUri(values.getAsString(Preferences.COLUMN_KEY));
- getContext().getContentResolver().notifyChange(rowUri, null);
- return rowUri;
- }
-
- throw new SQLiteException("Failed to insert row into " + uri);
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java
deleted file mode 100644
index 11a6a969..00000000
--- a/src/com/android/tv/tuner/TunerPreferences.java
+++ /dev/null
@@ -1,428 +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;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.annotation.GuardedBy;
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.TunerPreferenceProvider.Preferences;
-import com.android.tv.tuner.util.TisConfiguration;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * A helper class for the USB tuner preferences.
- */
-public class TunerPreferences {
- private static final String TAG = "TunerPreferences";
-
- private static final String PREFS_KEY_CHANNEL_DATA_VERSION = "channel_data_version";
- private static final String PREFS_KEY_SCANNED_CHANNEL_COUNT = "scanned_channel_count";
- private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code";
- private static final String PREFS_KEY_SCAN_DONE = "scan_done";
- private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup";
- private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream";
- private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting";
- private static final String PREFS_KEY_TRICKPLAY_EXPIRED_MS = "trickplay_expired_ms";
-
- private static final String SHARED_PREFS_NAME = "com.android.tv.tuner.preferences";
-
- public static final int CHANNEL_DATA_VERSION_NOT_SET = -1;
-
- @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrickplaySetting {
- }
-
- /**
- * Trickplay setting is not changed by a user. Trickplay will be enabled in this case.
- */
- public static final int TRICKPLAY_SETTING_NOT_SET = -1;
-
- /**
- * Trickplay setting is disabled.
- */
- public static final int TRICKPLAY_SETTING_DISABLED = 0;
-
- /**
- * Trickplay setting is enabled.
- */
- public static final int TRICKPLAY_SETTING_ENABLED = 1;
-
- @GuardedBy("TunerPreferences.class")
- private static final Bundle sPreferenceValues = new Bundle();
- private static LoadPreferencesTask sLoadPreferencesTask;
- private static ContentObserver sContentObserver;
- private static TunerPreferencesChangedListener sPreferencesChangedListener = null;
-
- private static boolean sInitialized;
-
- /**
- * Listeners for TunerPreferences change.
- */
- public interface TunerPreferencesChangedListener {
- void onTunerPreferencesChanged();
- }
-
- /**
- * Initializes the USB tuner preferences.
- */
- @MainThread
- public static void initialize(final Context context) {
- if (sInitialized) {
- return;
- }
- sInitialized = true;
- if (useContentProvider(context)) {
- loadPreferences(context);
- sContentObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- loadPreferences(context);
- }
- };
- context.getContentResolver().registerContentObserver(
- TunerPreferenceProvider.Preferences.CONTENT_URI, true, sContentObserver);
- } else {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- getSharedPreferences(context);
- return null;
- }
- }.execute();
- }
- }
-
- /**
- * Releases the resources.
- */
- public static synchronized void release(Context context) {
- if (useContentProvider(context) && sContentObserver != null) {
- context.getContentResolver().unregisterContentObserver(sContentObserver);
- }
- setTunerPreferencesChangedListener(null);
- }
-
- /**
- * Sets the listener for TunerPreferences change.
- */
- public static void setTunerPreferencesChangedListener(
- TunerPreferencesChangedListener listener) {
- sPreferencesChangedListener = listener;
- }
-
- /**
- * Loads the preferences from database.
- * <p>
- * This preferences is used across processes, so the preferences should be loaded again when the
- * databases changes.
- */
- @MainThread
- public static void loadPreferences(Context context) {
- if (sLoadPreferencesTask != null
- && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
- sLoadPreferencesTask.cancel(true);
- }
- sLoadPreferencesTask = new LoadPreferencesTask(context);
- sLoadPreferencesTask.execute();
- }
-
- private static boolean useContentProvider(Context context) {
- // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access.
- return TisConfiguration.isPackagedWithLiveChannels(context);
- }
-
- public static synchronized int getChannelDataVersion(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION,
- CHANNEL_DATA_VERSION_NOT_SET);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION,
- CHANNEL_DATA_VERSION_NOT_SET);
- }
- }
-
- public static synchronized void setChannelDataVersion(Context context, int version) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_CHANNEL_DATA_VERSION, version)
- .apply();
- }
- }
-
- public static synchronized int getScannedChannelCount(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, 0);
- }
- }
-
- public static synchronized void setScannedChannelCount(Context context, int channelCount) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount)
- .apply();
- }
- }
-
- public static synchronized String getLastPostalCode(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE);
- } else {
- return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null);
- }
- }
-
- public static synchronized void setLastPostalCode(Context context, String postalCode) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode);
- } else {
- getSharedPreferences(context).edit()
- .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode).apply();
- }
- }
-
- public static synchronized boolean isScanDone(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, false);
- }
- }
-
- public static synchronized void setScanDone(Context context) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_SCAN_DONE, true);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_SCAN_DONE, true)
- .apply();
- }
- }
-
- public static synchronized boolean shouldShowSetupActivity(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, false);
- }
- }
-
- public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_LAUNCH_SETUP, need)
- .apply();
- }
- }
-
- public static synchronized long getTrickplayExpiredMs(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getLong(PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0);
- } else {
- return getSharedPreferences(context)
- .getLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, 0);
- }
- }
-
- public static synchronized void setTrickplayExpiredMs(Context context, long timeMs) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs);
- } else {
- getSharedPreferences(context).edit()
- .putLong(TunerPreferences.PREFS_KEY_TRICKPLAY_EXPIRED_MS, timeMs)
- .apply();
- }
- }
-
- public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
- } else {
- return getSharedPreferences(context)
- .getInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
- }
- }
-
- public static synchronized void setTrickplaySetting(Context context,
- @TrickplaySetting int trickplaySetting) {
- SoftPreconditions.checkState(sInitialized);
- SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET);
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting);
- } else {
- getSharedPreferences(context).edit()
- .putInt(TunerPreferences.PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting)
- .apply();
- }
- }
-
- public static synchronized boolean getStoreTsStream(Context context) {
- SoftPreconditions.checkState(sInitialized);
- if (useContentProvider(context)) {
- return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
- } else {
- return getSharedPreferences(context)
- .getBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, false);
- }
- }
-
- public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
- if (useContentProvider(context)) {
- setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
- } else {
- getSharedPreferences(context).edit()
- .putBoolean(TunerPreferences.PREFS_KEY_STORE_TS_STREAM, shouldStore)
- .apply();
- }
- }
-
- private static SharedPreferences getSharedPreferences(Context context) {
- return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- }
-
- private static synchronized void setPreference(Context context, String key, String value) {
- sPreferenceValues.putString(key, value);
- savePreference(context, key, value);
- }
-
- private static synchronized void setPreference(Context context, String key, int value) {
- sPreferenceValues.putInt(key, value);
- savePreference(context, key, Integer.toString(value));
- }
-
- private static synchronized void setPreference(Context context, String key, long value) {
- sPreferenceValues.putLong(key, value);
- savePreference(context, key, Long.toString(value));
- }
-
- private static synchronized void setPreference(Context context, String key, boolean value) {
- sPreferenceValues.putBoolean(key, value);
- savePreference(context, key, Boolean.toString(value));
- }
-
- private static void savePreference(final Context context, final String key,
- final String value) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- ContentResolver resolver = context.getContentResolver();
- ContentValues values = new ContentValues();
- values.put(Preferences.COLUMN_KEY, key);
- values.put(Preferences.COLUMN_VALUE, value);
- try {
- resolver.insert(Preferences.CONTENT_URI, values);
- } catch (Exception e) {
- SoftPreconditions.warn(TAG, "setPreference", "Error writing preference values",
- e);
- }
- return null;
- }
- }.execute();
- }
-
- private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
- private final Context mContext;
- private LoadPreferencesTask(Context context) {
- mContext = context;
- }
-
- @Override
- protected Bundle doInBackground(Void... params) {
- Bundle bundle = new Bundle();
- ContentResolver resolver = mContext.getContentResolver();
- String[] projection = new String[] { Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE };
- try (Cursor cursor = resolver.query(Preferences.CONTENT_URI, projection, null, null,
- null)) {
- if (cursor != null) {
- while (!isCancelled() && cursor.moveToNext()) {
- String key = cursor.getString(0);
- String value = cursor.getString(1);
- switch (key) {
- case PREFS_KEY_TRICKPLAY_EXPIRED_MS:
- bundle.putLong(key, Long.parseLong(value));
- break;
- case PREFS_KEY_CHANNEL_DATA_VERSION:
- case PREFS_KEY_SCANNED_CHANNEL_COUNT:
- case PREFS_KEY_TRICKPLAY_SETTING:
- try {
- bundle.putInt(key, Integer.parseInt(value));
- } catch (NumberFormatException e) {
- // Does nothing.
- }
- break;
- case PREFS_KEY_SCAN_DONE:
- case PREFS_KEY_LAUNCH_SETUP:
- case PREFS_KEY_STORE_TS_STREAM:
- bundle.putBoolean(key, Boolean.parseBoolean(value));
- break;
- case PREFS_KEY_LAST_POSTAL_CODE:
- bundle.putString(key, value);
- break;
- }
- }
- }
- } catch (Exception e) {
- SoftPreconditions.warn(TAG, "getPreference", "Error querying preference values", e);
- return null;
- }
- return bundle;
- }
-
- @Override
- protected void onPostExecute(Bundle bundle) {
- synchronized (TunerPreferences.class) {
- if (bundle != null) {
- sPreferenceValues.putAll(bundle);
- }
- }
- if (sPreferencesChangedListener != null) {
- sPreferencesChangedListener.onTunerPreferencesChanged();
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java
deleted file mode 100644
index a88538df..00000000
--- a/src/com/android/tv/tuner/cc/CaptionLayout.java
+++ /dev/null
@@ -1,76 +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.cc;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.layout.ScaledLayout;
-
-/**
- * Layout containing the safe title area that helps the closed captions look more prominent.
- * This is required by CEA-708B.
- */
-public class CaptionLayout extends ScaledLayout {
- // The safe title area has 10% margins of the screen.
- private static final float SAFE_TITLE_AREA_SCALE_START_X = 0.1f;
- private static final float SAFE_TITLE_AREA_SCALE_END_X = 0.9f;
- private static final float SAFE_TITLE_AREA_SCALE_START_Y = 0.1f;
- private static final float SAFE_TITLE_AREA_SCALE_END_Y = 0.9f;
-
- private final ScaledLayout mSafeTitleAreaLayout;
- private AtscCaptionTrack mCaptionTrack;
-
- public CaptionLayout(Context context) {
- this(context, null);
- }
-
- public CaptionLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CaptionLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mSafeTitleAreaLayout = new ScaledLayout(context);
- addView(mSafeTitleAreaLayout, new ScaledLayoutParams(
- SAFE_TITLE_AREA_SCALE_START_X, SAFE_TITLE_AREA_SCALE_END_X,
- SAFE_TITLE_AREA_SCALE_START_Y, SAFE_TITLE_AREA_SCALE_END_Y));
- }
-
- public void addOrUpdateViewToSafeTitleArea(CaptionWindowLayout captionWindowLayout,
- ScaledLayoutParams scaledLayoutParams) {
- int index = mSafeTitleAreaLayout.indexOfChild(captionWindowLayout);
- if (index < 0) {
- mSafeTitleAreaLayout.addView(captionWindowLayout, scaledLayoutParams);
- return;
- }
- mSafeTitleAreaLayout.updateViewLayout(captionWindowLayout, scaledLayoutParams);
- }
-
- public void removeViewFromSafeTitleArea(CaptionWindowLayout captionWindowLayout) {
- mSafeTitleAreaLayout.removeView(captionWindowLayout);
- }
-
- public void setCaptionTrack(AtscCaptionTrack captionTrack) {
- mCaptionTrack = captionTrack;
- }
-
- public AtscCaptionTrack getCaptionTrack() {
- return mCaptionTrack;
- }
-}
diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
deleted file mode 100644
index 24a0f354..00000000
--- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java
+++ /dev/null
@@ -1,344 +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.cc;
-
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Decodes and renders CEA-708.
- */
-public class CaptionTrackRenderer implements Handler.Callback {
- // TODO: Remaining works
- // CaptionTrackRenderer does not support the full spec of CEA-708. The remaining works are
- // described in the follows.
- // C0 Table: Backspace, FF, and HCR are not supported. The rule for P16 is not standardized but
- // it is handled as EUC-KR charset for korea broadcasting.
- // C1 Table: All styles of windows and pens except underline, italic, pen size, and pen offset
- // specified in CEA-708 are ignored and this follows system wide cc preferences for
- // look and feel. SetPenLocation is not implemented.
- // G2 Table: TSP, NBTSP and BLK are not supported.
- // Text/commands: Word wrapping, fonts, row and column locking are not supported.
-
- private static final String TAG = "CaptionTrackRenderer";
- private static final boolean DEBUG = false;
-
- private static final long DELAY_IN_MILLIS = TimeUnit.MILLISECONDS.toMillis(100);
-
- // According to CEA-708B, there can exist up to 8 caption windows.
- private static final int CAPTION_WINDOWS_MAX = 8;
- private static final int CAPTION_ALL_WINDOWS_BITMAP = 255;
-
- private static final int MSG_DELAY_CANCEL = 1;
- private static final int MSG_CAPTION_CLEAR = 2;
-
- private static final long CAPTION_CLEAR_INTERVAL_MS = 60000;
-
- private final CaptionLayout mCaptionLayout;
- private boolean mIsDelayed = false;
- private CaptionWindowLayout mCurrentWindowLayout;
- private final CaptionWindowLayout[] mCaptionWindowLayouts =
- new CaptionWindowLayout[CAPTION_WINDOWS_MAX];
- private final ArrayList<CaptionEvent> mPendingCaptionEvents = new ArrayList<>();
- private final Handler mHandler;
-
- public CaptionTrackRenderer(CaptionLayout captionLayout) {
- mCaptionLayout = captionLayout;
- mHandler = new Handler(this);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DELAY_CANCEL:
- delayCancel();
- return true;
- case MSG_CAPTION_CLEAR:
- clearWindows(CAPTION_ALL_WINDOWS_BITMAP);
- return true;
- }
- return false;
- }
-
- public void start(AtscCaptionTrack captionTrack) {
- if (captionTrack == null) {
- stop();
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Start captionTrack " + captionTrack.language);
- }
- reset();
- mCaptionLayout.setCaptionTrack(captionTrack);
- mCaptionLayout.setVisibility(View.VISIBLE);
- }
-
- public void stop() {
- if (DEBUG) {
- Log.d(TAG, "Stop captionTrack");
- }
- mCaptionLayout.setVisibility(View.INVISIBLE);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- }
-
- public void processCaptionEvent(CaptionEvent event) {
- if (mIsDelayed) {
- mPendingCaptionEvents.add(event);
- return;
- }
- switch (event.type) {
- case Cea708Parser.CAPTION_EMIT_TYPE_BUFFER:
- sendBufferToCurrentWindow((String) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_CONTROL:
- sendControlToCurrentWindow((char) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CWX:
- setCurrentWindowLayout((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_CLW:
- clearWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DSW:
- displayWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_HDW:
- hideWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_TGW:
- toggleWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLW:
- deleteWindows((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLY:
- delay((int) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DLC:
- delayCancel();
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_RST:
- reset();
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPA:
- setPenAttr((CaptionPenAttr) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPC:
- setPenColor((CaptionPenColor) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SPL:
- setPenLocation((CaptionPenLocation) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_SWA:
- setWindowAttr((CaptionWindowAttr) event.obj);
- break;
- case Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DFX:
- defineWindow((CaptionWindow) event.obj);
- break;
- }
- }
-
- // The window related caption commands
- private void setCurrentWindowLayout(int windowId) {
- if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
- return;
- }
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
- if (windowLayout == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "setCurrentWindowLayout to " + windowId);
- }
- mCurrentWindowLayout = windowLayout;
- }
-
- // Each bit of windowBitmap indicates a window.
- // If a bit is set, the window id is the same as the number of the trailing zeros of the bit.
- private ArrayList<CaptionWindowLayout> getWindowsFromBitmap(int windowBitmap) {
- ArrayList<CaptionWindowLayout> windows = new ArrayList<>();
- for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
- if ((windowBitmap & (1 << i)) != 0) {
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[i];
- if (windowLayout != null) {
- windows.add(windowLayout);
- }
- }
- }
- return windows;
- }
-
- private void clearWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.clear();
- }
- }
-
- private void displayWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.show();
- }
- }
-
- private void hideWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.hide();
- }
- }
-
- private void toggleWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- if (windowLayout.isShown()) {
- windowLayout.hide();
- } else {
- windowLayout.show();
- }
- }
- }
-
- private void deleteWindows(int windowBitmap) {
- if (windowBitmap == 0) {
- return;
- }
- for (CaptionWindowLayout windowLayout : getWindowsFromBitmap(windowBitmap)) {
- windowLayout.removeFromCaptionView();
- mCaptionWindowLayouts[windowLayout.getCaptionWindowId()] = null;
- }
- }
-
- public void clear() {
- mHandler.sendEmptyMessage(MSG_CAPTION_CLEAR);
- }
-
- public void reset() {
- mCurrentWindowLayout = null;
- mIsDelayed = false;
- mPendingCaptionEvents.clear();
- for (int i = 0; i < CAPTION_WINDOWS_MAX; ++i) {
- if (mCaptionWindowLayouts[i] != null) {
- mCaptionWindowLayouts[i].removeFromCaptionView();
- }
- mCaptionWindowLayouts[i] = null;
- }
- mCaptionLayout.setVisibility(View.INVISIBLE);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- }
-
- private void setWindowAttr(CaptionWindowAttr windowAttr) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setWindowAttr(windowAttr);
- }
- }
-
- private void defineWindow(CaptionWindow window) {
- if (window == null) {
- return;
- }
- int windowId = window.id;
- if (windowId < 0 || windowId >= mCaptionWindowLayouts.length) {
- return;
- }
- CaptionWindowLayout windowLayout = mCaptionWindowLayouts[windowId];
- if (windowLayout == null) {
- windowLayout = new CaptionWindowLayout(mCaptionLayout.getContext());
- }
- windowLayout.initWindow(mCaptionLayout, window);
- mCurrentWindowLayout = mCaptionWindowLayouts[windowId] = windowLayout;
- }
-
- // The job related caption commands
- private void delay(int tenthsOfSeconds) {
- if (tenthsOfSeconds < 0 || tenthsOfSeconds > 255) {
- return;
- }
- mIsDelayed = true;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DELAY_CANCEL),
- tenthsOfSeconds * DELAY_IN_MILLIS);
- }
-
- private void delayCancel() {
- mIsDelayed = false;
- processPendingBuffer();
- }
-
- private void processPendingBuffer() {
- for (CaptionEvent event : mPendingCaptionEvents) {
- processCaptionEvent(event);
- }
- mPendingCaptionEvents.clear();
- }
-
- // The implicit write caption commands
- private void sendControlToCurrentWindow(char control) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.sendControl(control);
- }
- }
-
- private void sendBufferToCurrentWindow(String buffer) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.sendBuffer(buffer);
- mHandler.removeMessages(MSG_CAPTION_CLEAR);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CAPTION_CLEAR),
- CAPTION_CLEAR_INTERVAL_MS);
- }
- }
-
- // The pen related caption commands
- private void setPenAttr(CaptionPenAttr attr) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenAttr(attr);
- }
- }
-
- private void setPenColor(CaptionPenColor color) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenColor(color);
- }
- }
-
- private void setPenLocation(CaptionPenLocation location) {
- if (mCurrentWindowLayout != null) {
- mCurrentWindowLayout.setPenLocation(location.row, location.column);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java b/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
deleted file mode 100644
index 6f42b506..00000000
--- a/src/com/android/tv/tuner/cc/CaptionWindowLayout.java
+++ /dev/null
@@ -1,650 +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.cc;
-
-import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.text.Layout.Alignment;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.StyleSpan;
-import android.text.style.SubscriptSpan;
-import android.text.style.SuperscriptSpan;
-import android.text.style.UnderlineSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.CaptioningManager;
-import android.view.accessibility.CaptioningManager.CaptionStyle;
-import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
-import android.widget.RelativeLayout;
-
-import com.google.android.exoplayer.text.CaptionStyleCompat;
-import com.google.android.exoplayer.text.SubtitleView;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.layout.ScaledLayout;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Layout which renders a caption window of CEA-708B. It contains a {@link SubtitleView} that
- * takes care of displaying the actual cc text.
- */
-public class CaptionWindowLayout extends RelativeLayout implements View.OnLayoutChangeListener {
- private static final String TAG = "CaptionWindowLayout";
- private static final boolean DEBUG = false;
-
- private static final float PROPORTION_PEN_SIZE_SMALL = .75f;
- private static final float PROPORTION_PEN_SIZE_LARGE = 1.25f;
-
- // The following values indicates the maximum cell number of a window.
- private static final int ANCHOR_RELATIVE_POSITIONING_MAX = 99;
- private static final int ANCHOR_VERTICAL_MAX = 74;
- private static final int ANCHOR_HORIZONTAL_4_3_MAX = 159;
- private static final int ANCHOR_HORIZONTAL_16_9_MAX = 209;
-
- // The following values indicates a gravity of a window.
- private static final int ANCHOR_MODE_DIVIDER = 3;
- private static final int ANCHOR_HORIZONTAL_MODE_LEFT = 0;
- private static final int ANCHOR_HORIZONTAL_MODE_CENTER = 1;
- private static final int ANCHOR_HORIZONTAL_MODE_RIGHT = 2;
- private static final int ANCHOR_VERTICAL_MODE_TOP = 0;
- private static final int ANCHOR_VERTICAL_MODE_CENTER = 1;
- private static final int ANCHOR_VERTICAL_MODE_BOTTOM = 2;
-
- private static final int US_MAX_COLUMN_COUNT_16_9 = 42;
- private static final int US_MAX_COLUMN_COUNT_4_3 = 32;
- private static final int KR_MAX_COLUMN_COUNT_16_9 = 52;
- private static final int KR_MAX_COLUMN_COUNT_4_3 = 40;
- private static final int MAX_ROW_COUNT = 15;
-
- private static final String KOR_ALPHABET =
- new String("\uAC00".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
- private static final float WIDE_SCREEN_ASPECT_RATIO_THRESHOLD = 1.6f;
-
- private CaptionLayout mCaptionLayout;
- private CaptionStyleCompat mCaptionStyleCompat;
-
- // TODO: Replace SubtitleView to {@link com.google.android.exoplayer.text.SubtitleLayout}.
- private final SubtitleView mSubtitleView;
- private int mRowLimit = 0;
- private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
- private final List<CharacterStyle> mCharacterStyles = new ArrayList<>();
- private int mCaptionWindowId;
- private int mCurrentTextRow = -1;
- private float mFontScale;
- private float mTextSize;
- private String mWidestChar;
- private int mLastCaptionLayoutWidth;
- private int mLastCaptionLayoutHeight;
- private int mWindowJustify;
- private int mPrintDirection;
-
- private class SystemWideCaptioningChangeListener extends CaptioningChangeListener {
- @Override
- public void onUserStyleChanged(CaptionStyle userStyle) {
- mCaptionStyleCompat = CaptionStyleCompat.createFromCaptionStyle(userStyle);
- mSubtitleView.setStyle(mCaptionStyleCompat);
- updateWidestChar();
- }
-
- @Override
- public void onFontScaleChanged(float fontScale) {
- mFontScale = fontScale;
- updateTextSize();
- }
- }
-
- public CaptionWindowLayout(Context context) {
- this(context, null);
- }
-
- public CaptionWindowLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CaptionWindowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- // Add a subtitle view to the layout.
- mSubtitleView = new SubtitleView(context);
- LayoutParams params = new RelativeLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- addView(mSubtitleView, params);
-
- // Set the system wide cc preferences to the subtitle view.
- CaptioningManager captioningManager =
- (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
- mFontScale = captioningManager.getFontScale();
- mCaptionStyleCompat =
- CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
- mSubtitleView.setStyle(mCaptionStyleCompat);
- mSubtitleView.setText("");
- captioningManager.addCaptioningChangeListener(new SystemWideCaptioningChangeListener());
- updateWidestChar();
- }
-
- public int getCaptionWindowId() {
- return mCaptionWindowId;
- }
-
- public void setCaptionWindowId(int captionWindowId) {
- mCaptionWindowId = captionWindowId;
- }
-
- public void clear() {
- clearText();
- hide();
- }
-
- public void show() {
- setVisibility(View.VISIBLE);
- requestLayout();
- }
-
- public void hide() {
- setVisibility(View.INVISIBLE);
- requestLayout();
- }
-
- public void setPenAttr(CaptionPenAttr penAttr) {
- mCharacterStyles.clear();
- if (penAttr.italic) {
- mCharacterStyles.add(new StyleSpan(Typeface.ITALIC));
- }
- if (penAttr.underline) {
- mCharacterStyles.add(new UnderlineSpan());
- }
- switch (penAttr.penSize) {
- case CaptionPenAttr.PEN_SIZE_SMALL:
- mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_SMALL));
- break;
- case CaptionPenAttr.PEN_SIZE_LARGE:
- mCharacterStyles.add(new RelativeSizeSpan(PROPORTION_PEN_SIZE_LARGE));
- break;
- }
- switch (penAttr.penOffset) {
- case CaptionPenAttr.OFFSET_SUBSCRIPT:
- mCharacterStyles.add(new SubscriptSpan());
- break;
- case CaptionPenAttr.OFFSET_SUPERSCRIPT:
- mCharacterStyles.add(new SuperscriptSpan());
- break;
- }
- }
-
- public void setPenColor(CaptionPenColor penColor) {
- // TODO: apply pen colors or skip this and use the style of system wide cc style as is.
- }
-
- public void setPenLocation(int row, int column) {
- // TODO: change the location of pen when window's justify isn't left.
- // According to the CEA708B spec 8.7, setPenLocation means set the pen cursor within
- // window's text buffer. When row > mCurrentTextRow, we add "\n" to make the cursor locate
- // at row. Adding white space to make cursor locate at column.
- if (mWindowJustify == CaptionWindowAttr.JUSTIFY_LEFT) {
- if (mCurrentTextRow >= 0) {
- for (int r = mCurrentTextRow; r < row; ++r) {
- appendText("\n");
- }
- if (mCurrentTextRow <= row) {
- for (int i = 0; i < column; ++i) {
- appendText(" ");
- }
- }
- }
- }
- mCurrentTextRow = row;
- }
-
- public void setWindowAttr(CaptionWindowAttr windowAttr) {
- // TODO: apply window attrs or skip this and use the style of system wide cc style as is.
- mWindowJustify = windowAttr.justify;
- mPrintDirection = windowAttr.printDirection;
- }
-
- public void sendBuffer(String buffer) {
- appendText(buffer);
- }
-
- public void sendControl(char control) {
- // TODO: there are a bunch of ASCII-style control codes.
- }
-
- /**
- * This method places the window on a given CaptionLayout along with the anchor of the window.
- * <p>
- * According to CEA-708B, the anchor id indicates the gravity of the window as the follows.
- * For example, A value 7 of a anchor id says that a window is align with its parent bottom and
- * is located at the center horizontally of its parent.
- * </p>
- * <h4>Anchor id and the gravity of a window</h4>
- * <table>
- * <tr>
- * <th>GRAVITY</th>
- * <th>LEFT</th>
- * <th>CENTER_HORIZONTAL</th>
- * <th>RIGHT</th>
- * </tr>
- * <tr>
- * <th>TOP</th>
- * <td>0</td>
- * <td>1</td>
- * <td>2</td>
- * </tr>
- * <tr>
- * <th>CENTER_VERTICAL</th>
- * <td>3</td>
- * <td>4</td>
- * <td>5</td>
- * </tr>
- * <tr>
- * <th>BOTTOM</th>
- * <td>6</td>
- * <td>7</td>
- * <td>8</td>
- * </tr>
- * </table>
- * <p>
- * In order to handle the gravity of a window, there are two steps. First, set the size of the
- * window. Since the window will be positioned at {@link ScaledLayout}, the size factors are
- * determined in a ratio. Second, set the gravity of the window. {@link CaptionWindowLayout} is
- * inherited from {@link RelativeLayout}. Hence, we could set the gravity of its child view,
- * {@link SubtitleView}.
- * </p>
- * <p>
- * The gravity of the window is also related to its size. When it should be pushed to a one of
- * the end of the window, like LEFT, RIGHT, TOP or BOTTOM, the anchor point should be a boundary
- * of the window. When it should be pushed in the horizontal/vertical center of its container,
- * the horizontal/vertical center point of the window should be the same as the anchor point.
- * </p>
- *
- * @param captionLayout a given {@link CaptionLayout}, which contains a safe title area
- * @param captionWindow a given {@link CaptionWindow}, which stores the construction info of the
- * window
- */
- public void initWindow(CaptionLayout captionLayout, CaptionWindow captionWindow) {
- if (DEBUG) {
- Log.d(TAG, "initWindow with "
- + (captionLayout != null ? captionLayout.getCaptionTrack() : null));
- }
- if (mCaptionLayout != captionLayout) {
- if (mCaptionLayout != null) {
- mCaptionLayout.removeOnLayoutChangeListener(this);
- }
- mCaptionLayout = captionLayout;
- mCaptionLayout.addOnLayoutChangeListener(this);
- updateWidestChar();
- }
-
- // Both anchor vertical and horizontal indicates the position cell number of the window.
- float scaleRow = (float) captionWindow.anchorVertical / (captionWindow.relativePositioning
- ? ANCHOR_RELATIVE_POSITIONING_MAX : ANCHOR_VERTICAL_MAX);
- float scaleCol = (float) captionWindow.anchorHorizontal /
- (captionWindow.relativePositioning ? ANCHOR_RELATIVE_POSITIONING_MAX
- : (isWideAspectRatio()
- ? ANCHOR_HORIZONTAL_16_9_MAX : ANCHOR_HORIZONTAL_4_3_MAX));
-
- // The range of scaleRow/Col need to be verified to be in [0, 1].
- // Otherwise a {@link RuntimeException} will be raised in {@link ScaledLayout}.
- if (scaleRow < 0 || scaleRow > 1) {
- Log.i(TAG, "The vertical position of the anchor point should be at the range of 0 and 1"
- + " but " + scaleRow);
- scaleRow = Math.max(0, Math.min(scaleRow, 1));
- }
- if (scaleCol < 0 || scaleCol > 1) {
- Log.i(TAG, "The horizontal position of the anchor point should be at the range of 0 and"
- + " 1 but " + scaleCol);
- scaleCol = Math.max(0, Math.min(scaleCol, 1));
- }
- int gravity = Gravity.CENTER;
- int horizontalMode = captionWindow.anchorId % ANCHOR_MODE_DIVIDER;
- int verticalMode = captionWindow.anchorId / ANCHOR_MODE_DIVIDER;
- float scaleStartRow = 0;
- float scaleEndRow = 1;
- float scaleStartCol = 0;
- float scaleEndCol = 1;
- switch (horizontalMode) {
- case ANCHOR_HORIZONTAL_MODE_LEFT:
- gravity = Gravity.LEFT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL);
- scaleStartCol = scaleCol;
- break;
- case ANCHOR_HORIZONTAL_MODE_CENTER:
- float gap = Math.min(1 - scaleCol, scaleCol);
-
- // Since all TV sets use left text alignment instead of center text alignment
- // for this case, we follow the industry convention if possible.
- int columnCount = captionWindow.columnCount + 1;
- if (isKoreanLanguageTrack()) {
- columnCount /= 2;
- }
- columnCount = Math.min(getScreenColumnCount(), columnCount);
- StringBuilder widestTextBuilder = new StringBuilder();
- for (int i = 0; i < columnCount; ++i) {
- widestTextBuilder.append(mWidestChar);
- }
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- paint.setTextSize(mTextSize);
- float maxWindowWidth = paint.measureText(widestTextBuilder.toString());
- float halfMaxWidthScale = mCaptionLayout.getWidth() > 0
- ? maxWindowWidth / 2.0f / (mCaptionLayout.getWidth() * 0.8f) : 0.0f;
- if (halfMaxWidthScale > 0f && halfMaxWidthScale < scaleCol) {
- // Calculate the expected max window size based on the column count of the
- // caption window multiplied by average alphabets char width, then align the
- // left side of the window with the left side of the expected max window.
- gravity = Gravity.LEFT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_NORMAL);
- scaleStartCol = scaleCol - halfMaxWidthScale;
- scaleEndCol = 1.0f;
- } else {
- // The gap will be the minimum distance value of the distances from both
- // horizontal end points to the anchor point.
- // If scaleCol <= 0.5, the range of scaleCol is [0, the anchor point * 2].
- // If scaleCol > 0.5, the range of scaleCol is [(1 - the anchor point) * 2, 1].
- // The anchor point is located at the horizontal center of the window in both
- // cases.
- gravity = Gravity.CENTER_HORIZONTAL;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER);
- scaleStartCol = scaleCol - gap;
- scaleEndCol = scaleCol + gap;
- }
- break;
- case ANCHOR_HORIZONTAL_MODE_RIGHT:
- gravity = Gravity.RIGHT;
- mSubtitleView.setTextAlignment(Alignment.ALIGN_OPPOSITE);
- scaleEndCol = scaleCol;
- break;
- }
- switch (verticalMode) {
- case ANCHOR_VERTICAL_MODE_TOP:
- gravity |= Gravity.TOP;
- scaleStartRow = scaleRow;
- break;
- case ANCHOR_VERTICAL_MODE_CENTER:
- gravity |= Gravity.CENTER_VERTICAL;
-
- // See the above comment.
- float gap = Math.min(1 - scaleRow, scaleRow);
- scaleStartRow = scaleRow - gap;
- scaleEndRow = scaleRow + gap;
- break;
- case ANCHOR_VERTICAL_MODE_BOTTOM:
- gravity |= Gravity.BOTTOM;
- scaleEndRow = scaleRow;
- break;
- }
- mCaptionLayout.addOrUpdateViewToSafeTitleArea(this, new ScaledLayout
- .ScaledLayoutParams(scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
- setCaptionWindowId(captionWindow.id);
- setRowLimit(captionWindow.rowCount);
- setGravity(gravity);
- setWindowStyle(captionWindow.windowStyle);
- if (mWindowJustify == CaptionWindowAttr.JUSTIFY_CENTER) {
- mSubtitleView.setTextAlignment(Alignment.ALIGN_CENTER);
- }
- if (captionWindow.visible) {
- show();
- } else {
- hide();
- }
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- int width = right - left;
- int height = bottom - top;
- if (width != mLastCaptionLayoutWidth || height != mLastCaptionLayoutHeight) {
- mLastCaptionLayoutWidth = width;
- mLastCaptionLayoutHeight = height;
- updateTextSize();
- }
- }
-
- private boolean isKoreanLanguageTrack() {
- return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().language != null
- && "KOR".compareToIgnoreCase(mCaptionLayout.getCaptionTrack().language) == 0;
- }
-
- private boolean isWideAspectRatio() {
- return mCaptionLayout != null && mCaptionLayout.getCaptionTrack() != null
- && mCaptionLayout.getCaptionTrack().wideAspectRatio;
- }
-
- private void updateWidestChar() {
- if (isKoreanLanguageTrack()) {
- mWidestChar = KOR_ALPHABET;
- } else {
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- Charset latin1 = Charset.forName("ISO-8859-1");
- float widestCharWidth = 0f;
- for (int i = 0; i < 256; ++i) {
- String ch = new String(new byte[]{(byte) i}, latin1);
- float charWidth = paint.measureText(ch);
- if (widestCharWidth < charWidth) {
- widestCharWidth = charWidth;
- mWidestChar = ch;
- }
- }
- }
- updateTextSize();
- }
-
- private void updateTextSize() {
- if (mCaptionLayout == null) return;
-
- // Calculate text size based on the max window size.
- StringBuilder widestTextBuilder = new StringBuilder();
- int screenColumnCount = getScreenColumnCount();
- for (int i = 0; i < screenColumnCount; ++i) {
- widestTextBuilder.append(mWidestChar);
- }
- String widestText = widestTextBuilder.toString();
- Paint paint = new Paint();
- paint.setTypeface(mCaptionStyleCompat.typeface);
- float startFontSize = 0f;
- float endFontSize = 255f;
- Rect boundRect = new Rect();
- while (startFontSize < endFontSize) {
- float testTextSize = (startFontSize + endFontSize) / 2f;
- paint.setTextSize(testTextSize);
- float width = paint.measureText(widestText);
- paint.getTextBounds(widestText, 0, widestText.length(), boundRect);
- float height = boundRect.height() + width - boundRect.width();
- // According to CEA-708B Section 9.13, the height of standard font size shouldn't taller
- // than 1/15 of the height of the safe-title area, and the width shouldn't wider than
- // 1/{@code getScreenColumnCount()} of the width of the safe-title area.
- if (mCaptionLayout.getWidth() * 0.8f > width
- && mCaptionLayout.getHeight() * 0.8f / MAX_ROW_COUNT > height) {
- startFontSize = testTextSize + 0.01f;
- } else {
- endFontSize = testTextSize - 0.01f;
- }
- }
- mTextSize = endFontSize * mFontScale;
- paint.setTextSize(mTextSize);
- float whiteSpaceWidth = paint.measureText(" ");
- mSubtitleView.setWhiteSpaceWidth(whiteSpaceWidth);
- mSubtitleView.setTextSize(mTextSize);
- }
-
- private int getScreenColumnCount() {
- float screenAspectRatio = (float) mCaptionLayout.getWidth() / mCaptionLayout.getHeight();
- boolean isWideAspectRationScreen = screenAspectRatio > WIDE_SCREEN_ASPECT_RATIO_THRESHOLD;
- if (isKoreanLanguageTrack()) {
- // Each korean character consumes two slots.
- if (isWideAspectRationScreen || isWideAspectRatio()) {
- return KR_MAX_COLUMN_COUNT_16_9 / 2;
- } else {
- return KR_MAX_COLUMN_COUNT_4_3 / 2;
- }
- } else {
- if (isWideAspectRationScreen || isWideAspectRatio()) {
- return US_MAX_COLUMN_COUNT_16_9;
- } else {
- return US_MAX_COLUMN_COUNT_4_3;
- }
- }
- }
-
- public void removeFromCaptionView() {
- if (mCaptionLayout != null) {
- mCaptionLayout.removeViewFromSafeTitleArea(this);
- mCaptionLayout.removeOnLayoutChangeListener(this);
- mCaptionLayout = null;
- }
- }
-
- public void setText(String text) {
- updateText(text, false);
- }
-
- public void appendText(String text) {
- updateText(text, true);
- }
-
- public void clearText() {
- mBuilder.clear();
- mSubtitleView.setText("");
- }
-
- private void updateText(String text, boolean appended) {
- if (!appended) {
- mBuilder.clear();
- }
- if (text != null && text.length() > 0) {
- int length = mBuilder.length();
- mBuilder.append(text);
- for (CharacterStyle characterStyle : mCharacterStyles) {
- mBuilder.setSpan(characterStyle, length, mBuilder.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- String[] lines = TextUtils.split(mBuilder.toString(), "\n");
-
- // Truncate text not to exceed the row limit.
- // Plus one here since the range of the rows is [0, mRowLimit].
- int startRow = Math.max(0, lines.length - (mRowLimit + 1));
- String truncatedText = TextUtils.join("\n", Arrays.copyOfRange(
- lines, startRow, lines.length));
- mBuilder.delete(0, mBuilder.length() - truncatedText.length());
- mCurrentTextRow = lines.length - startRow - 1;
-
- // Trim the buffer first then set text to {@link SubtitleView}.
- int start = 0, last = mBuilder.length() - 1;
- int end = last;
- while ((start <= end) && (mBuilder.charAt(start) <= ' ')) {
- ++start;
- }
- while (start - 1 >= 0 && start <= end && mBuilder.charAt(start - 1) != '\n') {
- --start;
- }
- while ((end >= start) && (mBuilder.charAt(end) <= ' ')) {
- --end;
- }
- if (start == 0 && end == last) {
- mSubtitleView.setPrefixSpaces(getPrefixSpaces(mBuilder));
- mSubtitleView.setText(mBuilder);
- } else {
- SpannableStringBuilder trim = new SpannableStringBuilder();
- trim.append(mBuilder);
- if (end < last) {
- trim.delete(end + 1, last + 1);
- }
- if (start > 0) {
- trim.delete(0, start);
- }
- mSubtitleView.setPrefixSpaces(getPrefixSpaces(trim));
- mSubtitleView.setText(trim);
- }
- }
-
- private static ArrayList<Integer> getPrefixSpaces(SpannableStringBuilder builder) {
- ArrayList<Integer> prefixSpaces = new ArrayList<>();
- String[] lines = TextUtils.split(builder.toString(), "\n");
- for (String line : lines) {
- int start = 0;
- while (start < line.length() && line.charAt(start) <= ' ') {
- start++;
- }
- prefixSpaces.add(start);
- }
- return prefixSpaces;
- }
-
- public void setRowLimit(int rowLimit) {
- if (rowLimit < 0) {
- throw new IllegalArgumentException("A rowLimit should have a positive number");
- }
- mRowLimit = rowLimit;
- }
-
- private void setWindowStyle(int windowStyle) {
- // TODO: Set other attributes of window style. Like fill opacity and fill color.
- switch (windowStyle) {
- case 2:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 3:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 4:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 5:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 6:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_CENTER;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- case 7:
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_TOP_TO_BOTTOM;
- break;
- default:
- if (windowStyle != 0 && windowStyle != 1) {
- Log.e(TAG, "Error predefined window style:" + windowStyle);
- }
- mWindowJustify = CaptionWindowAttr.JUSTIFY_LEFT;
- mPrintDirection = CaptionWindowAttr.PRINT_LEFT_TO_RIGHT;
- break;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java
deleted file mode 100644
index d0f6cf11..00000000
--- a/src/com/android/tv/tuner/cc/Cea708Parser.java
+++ /dev/null
@@ -1,820 +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.cc;
-
-import android.os.SystemClock;
-import android.support.annotation.IntDef;
-import android.util.Log;
-import android.util.SparseIntArray;
-
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.Cea708Data.CaptionColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
-import com.android.tv.tuner.data.Cea708Data.CaptionPenLocation;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindow;
-import com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
-import com.android.tv.tuner.data.Cea708Data.CcPacket;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.TreeSet;
-
-/**
- * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV.
- *
- * <p>ATSC DTV closed caption data are carried on picture user data of video streams.
- * This class starts to parse from picture user data payload, so extraction process of user_data
- * from video streams is up to outside of this code.
- *
- * <p>There are 4 steps to decode user_data to provide closed caption services.
- *
- * <h3>Step 1. user_data -&gt; CcPacket ({@link #parseClosedCaption} method)</h3>
- *
- * <p>First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a
- * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data
- * packets must be reassembled in the frame display order, CcPackets are reordered.
- *
- * <h3>Step 2. CcPacket -&gt; DTVCC packet ({@link #parseCcPacket} method)</h3>
- *
- * <p>Each cc_data packet has a one byte for declaring a type of itself and data validity, and the
- * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet.
- * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet
- * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has
- * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled.
- *
- * <h3>Step 3. DTVCC packet -&gt; Service Blocks ({@link #parseDtvCcPacket} method)</h3>
- *
- * <p>A DTVCC packet consists of multiple service blocks. Each service block represents a caption
- * track and has a service number, which ranges from 1 to 63, that denotes caption track identity.
- * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}.
- * Otherwise, just skip the other service blocks.
- *
- * <h3>Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX},
- * and {@link #parseExt1} methods)</h3>
- *
- * <p>Service block data is actual caption stream. it looks similar to telnet. It uses most parts of
- * ASCII table and consists of specially defined commands and some ASCII control codes which work
- * in a behavior slightly different from their original purpose. ASCII control codes and caption
- * commands are explicit instructions that control the state of a closed caption service and the
- * other ASCII and text codes are implicit instructions that send their characters to buffer.
- *
- * <p>There are 4 main code groups and 4 extended code groups. Both the range of code groups are the
- * same as the range of a byte.
- *
- * <p>4 main code groups: C0, C1, G0, G1
- * <br>4 extended code groups: C2, C3, G2, G3
- *
- * <p>Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group
- * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while
- * {@link #parseExt1} method maps on the extended code groups.
- *
- * <p>The main code groups:
- * <ul>
- * <li>C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA
- * standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc,
- * even for the alphanumeric characters instead of ASCII characters.</li>
- * <li>C1 - contains the caption commands. There are 3 categories of a caption command.</li>
- * <ul>
- * <li>Window commands: The window commands control a caption window which is addressable area being
- * with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)</li>
- * <li>Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)</li>
- * <li>Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)</li>
- * </ul>
- * <li>G0 - same as printable ASCII character set except music note character.</li>
- * <li>G1 - same as ISO 8859-1 Latin 1 character set.</li>
- * </ul>
- * <p>Most of the extended code groups are being skipped.
- *
- */
-public class Cea708Parser {
- private static final String TAG = "Cea708Parser";
- private static final boolean DEBUG = false;
-
- // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
- private static final int MAX_ALLOCATED_SIZE = 9600 / 8;
- private static final String MUSIC_NOTE_CHAR = new String(
- "\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
-
- // The following values are denoting the type of closed caption data.
- // See CEA-708B section 4.4.1.
- private static final int CC_TYPE_DTVCC_PACKET_START = 3;
- private static final int CC_TYPE_DTVCC_PACKET_DATA = 2;
-
- // The following values are defined in CEA-708B Figure 4 and 6.
- private static final int DTVCC_MAX_PACKET_SIZE = 64;
- private static final int DTVCC_PACKET_SIZE_SCALE_FACTOR = 2;
- private static final int DTVCC_EXTENDED_SERVICE_NUMBER_POINT = 7;
-
- // The following values are for seeking closed caption tracks.
- private static final int DISCOVERY_PERIOD_MS = 10000; // 10 sec
- private static final int DISCOVERY_NUM_BYTES_THRESHOLD = 10; // 10 bytes
- private static final int DISCOVERY_CC_SERVICE_NUMBER_START = 1; // CC1
- private static final int DISCOVERY_CC_SERVICE_NUMBER_END = 4; // CC4
-
- private final ByteArrayBuffer mDtvCcPacket = new ByteArrayBuffer(MAX_ALLOCATED_SIZE);
- private final TreeSet<CcPacket> mCcPackets = new TreeSet<>();
- private final StringBuffer mBuffer = new StringBuffer();
- private final SparseIntArray mDiscoveredNumBytes = new SparseIntArray(); // per service number
- private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
- private int mCommand = 0;
- private int mListenServiceNumber = 0;
- private boolean mDtvCcPacking = false;
- private boolean mFirstServiceNumberDiscovered;
-
- // Assign a dummy listener in order to avoid null checks.
- private OnCea708ParserListener mListener = new OnCea708ParserListener() {
- @Override
- public void emitEvent(CaptionEvent event) {
- // do nothing
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- // do nothing
- }
- };
-
- /**
- * {@link Cea708Parser} emits caption event of three different types.
- * {@link OnCea708ParserListener#emitEvent} is invoked with the parameter
- * {@link CaptionEvent} to pass all the results to an observer of the decoding process.
- *
- * <p>{@link CaptionEvent#type} determines the type of the result and
- * {@link CaptionEvent#obj} contains the output value of a caption event.
- * The observer must do the casting to the corresponding type.
- *
- * <ul><li>{@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer.
- * {@code obj} must be of {@link String}.</li>
- *
- * <li>{@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer.
- * {@code obj} must be of {@link Character}.</li>
- *
- * <li>{@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer.
- * {@code obj} must be {@code NULL}.</li></ul>
- */
- @IntDef({CAPTION_EMIT_TYPE_BUFFER, CAPTION_EMIT_TYPE_CONTROL, CAPTION_EMIT_TYPE_COMMAND_CWX,
- CAPTION_EMIT_TYPE_COMMAND_CLW, CAPTION_EMIT_TYPE_COMMAND_DSW, CAPTION_EMIT_TYPE_COMMAND_HDW,
- CAPTION_EMIT_TYPE_COMMAND_TGW, CAPTION_EMIT_TYPE_COMMAND_DLW, CAPTION_EMIT_TYPE_COMMAND_DLY,
- CAPTION_EMIT_TYPE_COMMAND_DLC, CAPTION_EMIT_TYPE_COMMAND_RST, CAPTION_EMIT_TYPE_COMMAND_SPA,
- CAPTION_EMIT_TYPE_COMMAND_SPC, CAPTION_EMIT_TYPE_COMMAND_SPL, CAPTION_EMIT_TYPE_COMMAND_SWA,
- CAPTION_EMIT_TYPE_COMMAND_DFX})
- @Retention(RetentionPolicy.SOURCE)
- public @interface CaptionEmitType {}
- public static final int CAPTION_EMIT_TYPE_BUFFER = 1;
- public static final int CAPTION_EMIT_TYPE_CONTROL = 2;
- public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3;
- public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5;
- public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6;
- public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10;
- public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14;
- public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15;
- public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16;
-
- public interface OnCea708ParserListener {
- void emitEvent(CaptionEvent event);
- void discoverServiceNumber(int serviceNumber);
- }
-
- public void setListener(OnCea708ParserListener listener) {
- if (listener != null) {
- mListener = listener;
- }
- }
-
- public void clear() {
- mDtvCcPacket.clear();
- mCcPackets.clear();
- mBuffer.setLength(0);
- mDiscoveredNumBytes.clear();
- mCommand = 0;
- mDtvCcPacking = false;
- }
-
- public void setListenServiceNumber(int serviceNumber) {
- mListenServiceNumber = serviceNumber;
- }
-
- private void emitCaptionEvent(CaptionEvent captionEvent) {
- // Emit the existing string buffer before a new event is arrived.
- emitCaptionBuffer();
- mListener.emitEvent(captionEvent);
- }
-
- private void emitCaptionBuffer() {
- if (mBuffer.length() > 0) {
- mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuffer.toString()));
- mBuffer.setLength(0);
- }
- }
-
- // Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method)
- public void parseClosedCaption(ByteBuffer data, long framePtsUs) {
- int ccCount = data.limit() / 3;
- byte[] ccBytes = new byte[3 * ccCount];
- for (int i = 0; i < 3 * ccCount; i++) {
- ccBytes[i] = data.get(i);
- }
- CcPacket ccPacket = new CcPacket(ccBytes, ccCount, framePtsUs);
- mCcPackets.add(ccPacket);
- }
-
- public boolean processClosedCaptions(long framePtsUs) {
- // To get the sorted cc packets that have lower frame pts than current frame pts,
- // the following offset divides off the lower side of the packets.
- CcPacket offsetPacket = new CcPacket(new byte[0], 0, framePtsUs);
- offsetPacket = mCcPackets.lower(offsetPacket);
- boolean processed = false;
- if (offsetPacket != null) {
- while (!mCcPackets.isEmpty() && offsetPacket.compareTo(mCcPackets.first()) >= 0) {
- CcPacket packet = mCcPackets.pollFirst();
- parseCcPacket(packet);
- processed = true;
- }
- }
- return processed;
- }
-
- // Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method)
- private void parseCcPacket(CcPacket ccPacket) {
- // For the details of cc packet, see ATSC TSG-676 - Table A8.
- byte[] bytes = ccPacket.bytes;
- int pos = 0;
- for (int i = 0; i < ccPacket.ccCount; ++i) {
- boolean ccValid = (bytes[pos] & 0x04) != 0;
- int ccType = bytes[pos] & 0x03;
-
- // The dtvcc should be considered complete:
- // - if either ccValid is set and ccType is 3
- // - or ccValid is clear and ccType is 2 or 3.
- if (ccValid) {
- if (ccType == CC_TYPE_DTVCC_PACKET_START) {
- if (mDtvCcPacking) {
- parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
- mDtvCcPacket.clear();
- }
- mDtvCcPacking = true;
- mDtvCcPacket.append(bytes[pos + 1]);
- mDtvCcPacket.append(bytes[pos + 2]);
- } else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) {
- mDtvCcPacket.append(bytes[pos + 1]);
- mDtvCcPacket.append(bytes[pos + 2]);
- }
- } else {
- if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA)
- && mDtvCcPacking) {
- mDtvCcPacking = false;
- parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
- mDtvCcPacket.clear();
- }
- }
- pos += 3;
- }
- }
-
- // Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method)
- private void parseDtvCcPacket(byte[] data, int limit) {
- // For the details of DTVCC packet, see CEA-708B Figure 4.
- int pos = 0;
- int packetSize = data[pos] & 0x3f;
- if (packetSize == 0) {
- packetSize = DTVCC_MAX_PACKET_SIZE;
- }
- int calculatedPacketSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR;
- if (limit != calculatedPacketSize) {
- return;
- }
- ++pos;
- int len = pos + calculatedPacketSize;
- while (pos < len) {
- // For the details of Service Block, see CEA-708B Figure 5 and 6.
- int serviceNumber = (data[pos] & 0xe0) >> 5;
- int blockSize = data[pos] & 0x1f;
- ++pos;
- if (serviceNumber == DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
- serviceNumber = (data[pos] & 0x3f);
- ++pos;
-
- // Return if invalid service number
- if (serviceNumber < DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
- return;
- }
- }
- if (pos + blockSize > limit) {
- return;
- }
-
- // Send parsed service number in order to find unveiled closed caption tracks which
- // are not specified in any ATSC PSIP sections. Since some broadcasts send empty closed
- // caption tracks, it detects the proper closed caption tracks by counting the number of
- // bytes sent with the same service number during a discovery period.
- // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different
- // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported.
- if (blockSize > 0 && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START
- && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) {
- mDiscoveredNumBytes.put(
- serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0));
- }
- if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()
- || !mFirstServiceNumberDiscovered) {
- for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) {
- int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i);
- if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) {
- int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i);
- mListener.discoverServiceNumber(discoveredServiceNumber);
- mFirstServiceNumberDiscovered = true;
- }
- }
- mDiscoveredNumBytes.clear();
- mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
- }
-
- // Skip current service block if either there is no block data or the service number
- // is not same as listening service number.
- if (blockSize == 0 || serviceNumber != mListenServiceNumber) {
- pos += blockSize;
- continue;
- }
-
- // From this point, starts to read DTVCC coding layer.
- // First, identify code groups, which is defined in CEA-708B Section 7.1.
- int blockLimit = pos + blockSize;
- while (pos < blockLimit) {
- pos = parseServiceBlockData(data, pos);
- }
-
- // Emit the buffer after reading codes.
- emitCaptionBuffer();
- pos = blockLimit;
- }
- }
-
- // Step 4. Main code groups
- private int parseServiceBlockData(byte[] data, int pos) {
- // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
- mCommand = data[pos] & 0xff;
- ++pos;
- if (mCommand == Cea708Data.CODE_C0_EXT1) {
- pos = parseExt1(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C0_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_RANGE_END) {
- pos = parseC0(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C1_RANGE_START
- && mCommand <= Cea708Data.CODE_C1_RANGE_END) {
- pos = parseC1(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G0_RANGE_START
- && mCommand <= Cea708Data.CODE_G0_RANGE_END) {
- pos = parseG0(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G1_RANGE_START
- && mCommand <= Cea708Data.CODE_G1_RANGE_END) {
- pos = parseG1(data, pos);
- }
- return pos;
- }
-
- private int parseC0(byte[] data, int pos) {
- // For the details of C0 code group, see CEA-708B Section 7.4.1.
- // CL Group: C0 Subset of ASCII Control codes
- if (mCommand >= Cea708Data.CODE_C0_SKIP2_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_SKIP2_RANGE_END) {
- if (mCommand == Cea708Data.CODE_C0_P16) {
- // TODO : P16 escapes next two bytes for the large character maps.(no standard rule)
- // TODO : For korea broadcasting, express whole letters by using this.
- try {
- if (data[pos] == 0) {
- mBuffer.append((char) data[pos + 1]);
- } else {
- String value = new String(
- Arrays.copyOfRange(data, pos, pos + 2),
- "EUC-KR");
- mBuffer.append(value);
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "P16 Code - Could not find supported encoding", e);
- }
- }
- pos += 2;
- } else if (mCommand >= Cea708Data.CODE_C0_SKIP1_RANGE_START
- && mCommand <= Cea708Data.CODE_C0_SKIP1_RANGE_END) {
- ++pos;
- } else {
- // NUL, BS, FF, CR interpreted as they are in ASCII control codes.
- // HCR moves the pen location to th beginning of the current line and deletes contents.
- // FF clears the screen and moves the pen location to (0,0).
- // ETX is the NULL command which is used to flush text to the current window when no
- // other command is pending.
- switch (mCommand) {
- case Cea708Data.CODE_C0_NUL:
- break;
- case Cea708Data.CODE_C0_ETX:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_BS:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_FF:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- case Cea708Data.CODE_C0_CR:
- mBuffer.append('\n');
- break;
- case Cea708Data.CODE_C0_HCR:
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
- break;
- default:
- break;
- }
- }
- return pos;
- }
-
- private int parseC1(byte[] data, int pos) {
- // For the details of C1 code group, see CEA-708B Section 8.10.
- // CR Group: C1 Caption Control Codes
- switch (mCommand) {
- case Cea708Data.CODE_C1_CW0:
- case Cea708Data.CODE_C1_CW1:
- case Cea708Data.CODE_C1_CW2:
- case Cea708Data.CODE_C1_CW3:
- case Cea708Data.CODE_C1_CW4:
- case Cea708Data.CODE_C1_CW5:
- case Cea708Data.CODE_C1_CW6:
- case Cea708Data.CODE_C1_CW7: {
- // SetCurrentWindow0-7
- int windowId = mCommand - Cea708Data.CODE_C1_CW0;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_CLW: {
- // ClearWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DSW: {
- // DisplayWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_HDW: {
- // HideWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_TGW: {
- // ToggleWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DLW: {
- // DeleteWindows
- int windowBitmap = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DLY: {
- // Delay
- int tenthsOfSeconds = data[pos] & 0xff;
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds",
- tenthsOfSeconds));
- }
- break;
- }
- case Cea708Data.CODE_C1_DLC: {
- // DelayCancel
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null));
- if (DEBUG) {
- Log.d(TAG, "CaptionCommand DLC");
- }
- break;
- }
-
- case Cea708Data.CODE_C1_RST: {
- // Reset
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null));
- if (DEBUG) {
- Log.d(TAG, "CaptionCommand RST");
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPA: {
- // SetPenAttributes
- int textTag = (data[pos] & 0xf0) >> 4;
- int penSize = data[pos] & 0x03;
- int penOffset = (data[pos] & 0x0c) >> 2;
- boolean italic = (data[pos + 1] & 0x80) != 0;
- boolean underline = (data[pos + 1] & 0x40) != 0;
- int edgeType = (data[pos + 1] & 0x38) >> 3;
- int fontTag = data[pos + 1] & 0x7;
- pos += 2;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA,
- new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType,
- underline, italic)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, "
- + "fontTag: %d, edgeType: %d, underline: %s, italic: %s",
- penSize, penOffset, textTag, fontTag, edgeType, underline, italic));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPC: {
- // SetPenColor
- int opacity = (data[pos] & 0xc0) >> 6;
- int red = (data[pos] & 0x30) >> 4;
- int green = (data[pos] & 0x0c) >> 2;
- int blue = data[pos] & 0x03;
- CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue);
- ++pos;
- opacity = (data[pos] & 0xc0) >> 6;
- red = (data[pos] & 0x30) >> 4;
- green = (data[pos] & 0x0c) >> 2;
- blue = data[pos] & 0x03;
- CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue);
- ++pos;
- red = (data[pos] & 0x30) >> 4;
- green = (data[pos] & 0x0c) >> 2;
- blue = data[pos] & 0x03;
- CaptionColor edgeColor = new CaptionColor(
- CaptionColor.OPACITY_SOLID, red, green, blue);
- ++pos;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC,
- new CaptionPenColor(foregroundColor, backgroundColor, edgeColor)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s",
- foregroundColor, backgroundColor, edgeColor));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SPL: {
- // SetPenLocation
- // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats
- int row = data[pos] & 0x0f;
- int column = data[pos + 1] & 0x3f;
- pos += 2;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL,
- new CaptionPenLocation(row, column)));
- if (DEBUG) {
- Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d",
- row, column));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_SWA: {
- // SetWindowAttributes
- int opacity = (data[pos] & 0xc0) >> 6;
- int red = (data[pos] & 0x30) >> 4;
- int green = (data[pos] & 0x0c) >> 2;
- int blue = data[pos] & 0x03;
- CaptionColor fillColor = new CaptionColor(opacity, red, green, blue);
- int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5;
- red = (data[pos + 1] & 0x30) >> 4;
- green = (data[pos + 1] & 0x0c) >> 2;
- blue = data[pos + 1] & 0x03;
- CaptionColor borderColor = new CaptionColor(
- CaptionColor.OPACITY_SOLID, red, green, blue);
- boolean wordWrap = (data[pos + 2] & 0x40) != 0;
- int printDirection = (data[pos + 2] & 0x30) >> 4;
- int scrollDirection = (data[pos + 2] & 0x0c) >> 2;
- int justify = (data[pos + 2] & 0x03);
- int effectSpeed = (data[pos + 3] & 0xf0) >> 4;
- int effectDirection = (data[pos + 3] & 0x0c) >> 2;
- int displayEffect = data[pos + 3] & 0x3;
- pos += 4;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA,
- new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap,
- printDirection, scrollDirection, justify,
- effectDirection, effectSpeed, displayEffect)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d"
- + "wordWrap: %s, printDirection: %d, scrollDirection: %d, "
- + "justify: %s, effectDirection: %d, effectSpeed: %d, "
- + "displayEffect: %d",
- fillColor, borderColor, borderType, wordWrap, printDirection,
- scrollDirection, justify, effectDirection, effectSpeed, displayEffect));
- }
- break;
- }
-
- case Cea708Data.CODE_C1_DF0:
- case Cea708Data.CODE_C1_DF1:
- case Cea708Data.CODE_C1_DF2:
- case Cea708Data.CODE_C1_DF3:
- case Cea708Data.CODE_C1_DF4:
- case Cea708Data.CODE_C1_DF5:
- case Cea708Data.CODE_C1_DF6:
- case Cea708Data.CODE_C1_DF7: {
- // DefineWindow0-7
- int windowId = mCommand - Cea708Data.CODE_C1_DF0;
- boolean visible = (data[pos] & 0x20) != 0;
- boolean rowLock = (data[pos] & 0x10) != 0;
- boolean columnLock = (data[pos] & 0x08) != 0;
- int priority = data[pos] & 0x07;
- boolean relativePositioning = (data[pos + 1] & 0x80) != 0;
- int anchorVertical = data[pos + 1] & 0x7f;
- int anchorHorizontal = data[pos + 2] & 0xff;
- int anchorId = (data[pos + 3] & 0xf0) >> 4;
- int rowCount = data[pos + 3] & 0x0f;
- int columnCount = data[pos + 4] & 0x3f;
- int windowStyle = (data[pos + 5] & 0x38) >> 3;
- int penStyle = data[pos + 5] & 0x07;
- pos += 6;
- emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX,
- new CaptionWindow(windowId, visible, rowLock, columnLock, priority,
- relativePositioning, anchorVertical, anchorHorizontal, anchorId,
- rowCount, columnCount, penStyle, windowStyle)));
- if (DEBUG) {
- Log.d(TAG, String.format(
- "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, "
- + "rowLock: %s, visible: %s, anchorVertical: %d, "
- + "relativePositioning: %s, anchorHorizontal: %d, "
- + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, "
- + "windowStyle: %d",
- windowId, priority, columnLock, rowLock, visible, anchorVertical,
- relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount,
- penStyle, windowStyle));
- }
- break;
- }
-
- default:
- break;
- }
- return pos;
- }
-
- private int parseG0(byte[] data, int pos) {
- // For the details of G0 code group, see CEA-708B Section 7.4.3.
- // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII)
- if (mCommand == Cea708Data.CODE_G0_MUSICNOTE) {
- // Music note.
- mBuffer.append(MUSIC_NOTE_CHAR);
- } else {
- // Put ASCII code into buffer.
- mBuffer.append((char) mCommand);
- }
- return pos;
- }
-
- private int parseG1(byte[] data, int pos) {
- // For the details of G0 code group, see CEA-708B Section 7.4.4.
- // GR Group: G1 ISO 8859-1 Latin 1 Characters
- // Put ASCII Extended character set into buffer.
- mBuffer.append((char) mCommand);
- return pos;
- }
-
- // Step 4. Extended code groups
- private int parseExt1(byte[] data, int pos) {
- // For the details of EXT1 code group, see CEA-708B Section 7.2.
- mCommand = data[pos] & 0xff;
- ++pos;
- if (mCommand >= Cea708Data.CODE_C2_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_RANGE_END) {
- pos = parseC2(data, pos);
- } else if (mCommand >= Cea708Data.CODE_C3_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_RANGE_END) {
- pos = parseC3(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G2_RANGE_START
- && mCommand <= Cea708Data.CODE_G2_RANGE_END) {
- pos = parseG2(data, pos);
- } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START
- && mCommand <= Cea708Data.CODE_G3_RANGE_END) {
- pos = parseG3(data ,pos);
- }
- return pos;
- }
-
- private int parseC2(byte[] data, int pos) {
- // For the details of C2 code group, see CEA-708B Section 7.4.7.
- // Extended Miscellaneous Control Codes
- // C2 Table : No commands as of CEA-708B. A decoder must skip.
- if (mCommand >= Cea708Data.CODE_C2_SKIP0_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP0_RANGE_END) {
- // Do nothing.
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP1_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP1_RANGE_END) {
- ++pos;
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP2_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP2_RANGE_END) {
- pos += 2;
- } else if (mCommand >= Cea708Data.CODE_C2_SKIP3_RANGE_START
- && mCommand <= Cea708Data.CODE_C2_SKIP3_RANGE_END) {
- pos += 3;
- }
- return pos;
- }
-
- private int parseC3(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.8.
- // Extended Control Code Set 2
- // C3 Table : No commands as of CEA-708B. A decoder must skip.
- if (mCommand >= Cea708Data.CODE_C3_SKIP4_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_SKIP4_RANGE_END) {
- pos += 4;
- } else if (mCommand >= Cea708Data.CODE_C3_SKIP5_RANGE_START
- && mCommand <= Cea708Data.CODE_C3_SKIP5_RANGE_END) {
- pos += 5;
- }
- return pos;
- }
-
- private int parseG2(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.5.
- // Extended Control Code Set 1(G2 Table)
- switch (mCommand) {
- case Cea708Data.CODE_G2_TSP:
- // TODO : TSP is the Transparent space
- break;
- case Cea708Data.CODE_G2_NBTSP:
- // TODO : NBTSP is Non-Breaking Transparent Space.
- break;
- case Cea708Data.CODE_G2_BLK:
- // TODO : BLK indicates a solid block which fills the entire character block
- // TODO : with a solid foreground color.
- break;
- default:
- break;
- }
- return pos;
- }
-
- private int parseG3(byte[] data, int pos) {
- // For the details of C3 code group, see CEA-708B Section 7.4.6.
- // Future characters and icons(G3 Table)
- if (mCommand == Cea708Data.CODE_G3_CC) {
- // TODO : [CC] icon with square corners
- }
-
- // Do nothing
- return pos;
- }
-}
diff --git a/src/com/android/tv/tuner/data/Cea708Data.java b/src/com/android/tv/tuner/data/Cea708Data.java
deleted file mode 100644
index 6350d63c..00000000
--- a/src/com/android/tv/tuner/data/Cea708Data.java
+++ /dev/null
@@ -1,320 +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.data;
-
-import com.android.tv.tuner.cc.Cea708Parser;
-
-import android.graphics.Color;
-import android.support.annotation.NonNull;
-
-/**
- * Collection of CEA-708 structures.
- */
-public class Cea708Data {
-
- private Cea708Data() {
- }
-
- // According to CEA-708B, the range of valid service number is between 1 and 63.
- public static final int EMPTY_SERVICE_NUMBER = 0;
-
- // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
- public static final int CODE_C0_RANGE_START = 0x00;
- public static final int CODE_C0_RANGE_END = 0x1f;
- public static final int CODE_C1_RANGE_START = 0x80;
- public static final int CODE_C1_RANGE_END = 0x9f;
- public static final int CODE_G0_RANGE_START = 0x20;
- public static final int CODE_G0_RANGE_END = 0x7f;
- public static final int CODE_G1_RANGE_START = 0xa0;
- public static final int CODE_G1_RANGE_END = 0xff;
- public static final int CODE_C2_RANGE_START = 0x00;
- public static final int CODE_C2_RANGE_END = 0x1f;
- public static final int CODE_C3_RANGE_START = 0x80;
- public static final int CODE_C3_RANGE_END = 0x9f;
- public static final int CODE_G2_RANGE_START = 0x20;
- public static final int CODE_G2_RANGE_END = 0x7f;
- public static final int CODE_G3_RANGE_START = 0xa0;
- public static final int CODE_G3_RANGE_END = 0xff;
-
- // The following ranges are defined in CEA-708B Section 7.4.1.
- public static final int CODE_C0_SKIP2_RANGE_START = 0x18;
- public static final int CODE_C0_SKIP2_RANGE_END = 0x1f;
- public static final int CODE_C0_SKIP1_RANGE_START = 0x10;
- public static final int CODE_C0_SKIP1_RANGE_END = 0x17;
-
- // The following ranges are defined in CEA-708B Section 7.4.7.
- public static final int CODE_C2_SKIP0_RANGE_START = 0x00;
- public static final int CODE_C2_SKIP0_RANGE_END = 0x07;
- public static final int CODE_C2_SKIP1_RANGE_START = 0x08;
- public static final int CODE_C2_SKIP1_RANGE_END = 0x0f;
- public static final int CODE_C2_SKIP2_RANGE_START = 0x10;
- public static final int CODE_C2_SKIP2_RANGE_END = 0x17;
- public static final int CODE_C2_SKIP3_RANGE_START = 0x18;
- public static final int CODE_C2_SKIP3_RANGE_END = 0x1f;
-
- // The following ranges are defined in CEA-708B Section 7.4.8.
- public static final int CODE_C3_SKIP4_RANGE_START = 0x80;
- public static final int CODE_C3_SKIP4_RANGE_END = 0x87;
- public static final int CODE_C3_SKIP5_RANGE_START = 0x88;
- public static final int CODE_C3_SKIP5_RANGE_END = 0x8f;
-
- // The following values are the special characters of CEA-708 spec.
- public static final int CODE_C0_NUL = 0x00;
- public static final int CODE_C0_ETX = 0x03;
- public static final int CODE_C0_BS = 0x08;
- public static final int CODE_C0_FF = 0x0c;
- public static final int CODE_C0_CR = 0x0d;
- public static final int CODE_C0_HCR = 0x0e;
- public static final int CODE_C0_EXT1 = 0x10;
- public static final int CODE_C0_P16 = 0x18;
- public static final int CODE_G0_MUSICNOTE = 0x7f;
- public static final int CODE_G2_TSP = 0x20;
- public static final int CODE_G2_NBTSP = 0x21;
- public static final int CODE_G2_BLK = 0x30;
- public static final int CODE_G3_CC = 0xa0;
-
- // The following values are the command bits of CEA-708 spec.
- public static final int CODE_C1_CW0 = 0x80;
- public static final int CODE_C1_CW1 = 0x81;
- public static final int CODE_C1_CW2 = 0x82;
- public static final int CODE_C1_CW3 = 0x83;
- public static final int CODE_C1_CW4 = 0x84;
- public static final int CODE_C1_CW5 = 0x85;
- public static final int CODE_C1_CW6 = 0x86;
- public static final int CODE_C1_CW7 = 0x87;
- public static final int CODE_C1_CLW = 0x88;
- public static final int CODE_C1_DSW = 0x89;
- public static final int CODE_C1_HDW = 0x8a;
- public static final int CODE_C1_TGW = 0x8b;
- public static final int CODE_C1_DLW = 0x8c;
- public static final int CODE_C1_DLY = 0x8d;
- public static final int CODE_C1_DLC = 0x8e;
- public static final int CODE_C1_RST = 0x8f;
- public static final int CODE_C1_SPA = 0x90;
- public static final int CODE_C1_SPC = 0x91;
- public static final int CODE_C1_SPL = 0x92;
- public static final int CODE_C1_SWA = 0x97;
- public static final int CODE_C1_DF0 = 0x98;
- public static final int CODE_C1_DF1 = 0x99;
- public static final int CODE_C1_DF2 = 0x9a;
- public static final int CODE_C1_DF3 = 0x9b;
- public static final int CODE_C1_DF4 = 0x9c;
- public static final int CODE_C1_DF5 = 0x9d;
- public static final int CODE_C1_DF6 = 0x9e;
- public static final int CODE_C1_DF7 = 0x9f;
-
- public static class CcPacket implements Comparable<CcPacket> {
- public final byte[] bytes;
- public final int ccCount;
- public final long pts;
-
- public CcPacket(byte[] bytes, int ccCount, long pts) {
- this.bytes = bytes;
- this.ccCount = ccCount;
- this.pts = pts;
- }
-
- @Override
- public int compareTo(@NonNull CcPacket another) {
- return Long.compare(pts, another.pts);
- }
- }
-
- /**
- * CEA-708B-specific color.
- */
- public static class CaptionColor {
- public static final int OPACITY_SOLID = 0;
- public static final int OPACITY_FLASH = 1;
- public static final int OPACITY_TRANSLUCENT = 2;
- public static final int OPACITY_TRANSPARENT = 3;
-
- private static final int[] COLOR_MAP = new int[] { 0x00, 0x0f, 0xf0, 0xff };
- private static final int[] OPACITY_MAP = new int[] { 0xff, 0xfe, 0x80, 0x00 };
-
- public final int opacity;
- public final int red;
- public final int green;
- public final int blue;
-
- public CaptionColor(int opacity, int red, int green, int blue) {
- this.opacity = opacity;
- this.red = red;
- this.green = green;
- this.blue = blue;
- }
-
- public int getArgbValue() {
- return Color.argb(
- OPACITY_MAP[opacity], COLOR_MAP[red], COLOR_MAP[green], COLOR_MAP[blue]);
- }
- }
-
- /**
- * Caption event generated by {@link Cea708Parser}.
- */
- public static class CaptionEvent {
- @Cea708Parser.CaptionEmitType public final int type;
- public final Object obj;
-
- public CaptionEvent(int type, Object obj) {
- this.type = type;
- this.obj = obj;
- }
- }
-
- /**
- * Pen style information.
- */
- public static class CaptionPenAttr {
- // Pen sizes
- public static final int PEN_SIZE_SMALL = 0;
- public static final int PEN_SIZE_STANDARD = 1;
- public static final int PEN_SIZE_LARGE = 2;
-
- // Offsets
- public static final int OFFSET_SUBSCRIPT = 0;
- public static final int OFFSET_NORMAL = 1;
- public static final int OFFSET_SUPERSCRIPT = 2;
-
- public final int penSize;
- public final int penOffset;
- public final int textTag;
- public final int fontTag;
- public final int edgeType;
- public final boolean underline;
- public final boolean italic;
-
- public CaptionPenAttr(int penSize, int penOffset, int textTag, int fontTag, int edgeType,
- boolean underline, boolean italic) {
- this.penSize = penSize;
- this.penOffset = penOffset;
- this.textTag = textTag;
- this.fontTag = fontTag;
- this.edgeType = edgeType;
- this.underline = underline;
- this.italic = italic;
- }
- }
-
- /**
- * {@link CaptionColor} objects that indicate the foreground, background, and edge color of a
- * pen.
- */
- public static class CaptionPenColor {
- public final CaptionColor foregroundColor;
- public final CaptionColor backgroundColor;
- public final CaptionColor edgeColor;
-
- public CaptionPenColor(CaptionColor foregroundColor, CaptionColor backgroundColor,
- CaptionColor edgeColor) {
- this.foregroundColor = foregroundColor;
- this.backgroundColor = backgroundColor;
- this.edgeColor = edgeColor;
- }
- }
-
- /**
- * Location information of a pen.
- */
- public static class CaptionPenLocation {
- public final int row;
- public final int column;
-
- public CaptionPenLocation(int row, int column) {
- this.row = row;
- this.column = column;
- }
- }
-
- /**
- * Attributes of a caption window, which is defined in CEA-708B.
- */
- public static class CaptionWindowAttr {
- public static final int JUSTIFY_LEFT = 0;
- public static final int JUSTIFY_CENTER = 2;
- public static final int PRINT_LEFT_TO_RIGHT = 0;
- public static final int PRINT_RIGHT_TO_LEFT = 1;
- public static final int PRINT_TOP_TO_BOTTOM = 2;
- public static final int PRINT_BOTTOM_TO_TOP = 3;
-
- public final CaptionColor fillColor;
- public final CaptionColor borderColor;
- public final int borderType;
- public final boolean wordWrap;
- public final int printDirection;
- public final int scrollDirection;
- public final int justify;
- public final int effectDirection;
- public final int effectSpeed;
- public final int displayEffect;
-
- public CaptionWindowAttr(CaptionColor fillColor, CaptionColor borderColor, int borderType,
- boolean wordWrap, int printDirection, int scrollDirection, int justify,
- int effectDirection,
- int effectSpeed, int displayEffect) {
- this.fillColor = fillColor;
- this.borderColor = borderColor;
- this.borderType = borderType;
- this.wordWrap = wordWrap;
- this.printDirection = printDirection;
- this.scrollDirection = scrollDirection;
- this.justify = justify;
- this.effectDirection = effectDirection;
- this.effectSpeed = effectSpeed;
- this.displayEffect = displayEffect;
- }
- }
-
- /**
- * Construction information of the caption window of CEA-708B.
- */
- public static class CaptionWindow {
- public final int id;
- public final boolean visible;
- public final boolean rowLock;
- public final boolean columnLock;
- public final int priority;
- public final boolean relativePositioning;
- public final int anchorVertical;
- public final int anchorHorizontal;
- public final int anchorId;
- public final int rowCount;
- public final int columnCount;
- public final int penStyle;
- public final int windowStyle;
-
- public CaptionWindow(int id, boolean visible,
- boolean rowLock, boolean columnLock, int priority, boolean relativePositioning,
- int anchorVertical, int anchorHorizontal, int anchorId,
- int rowCount, int columnCount, int penStyle, int windowStyle) {
- this.id = id;
- this.visible = visible;
- this.rowLock = rowLock;
- this.columnLock = columnLock;
- this.priority = priority;
- this.relativePositioning = relativePositioning;
- this.anchorVertical = anchorVertical;
- this.anchorHorizontal = anchorHorizontal;
- this.anchorId = anchorId;
- this.rowCount = rowCount;
- this.columnCount = columnCount;
- this.penStyle = penStyle;
- this.windowStyle = windowStyle;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java
deleted file mode 100644
index 67700c6a..00000000
--- a/src/com/android/tv/tuner/data/PsiData.java
+++ /dev/null
@@ -1,94 +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.data;
-
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.List;
-
-/**
- * Collection of MPEG PSI table items.
- */
-public class PsiData {
-
- private PsiData() {
- }
-
- public static class PatItem {
- private final int mProgramNo;
- private final int mPmtPid;
-
- public PatItem(int programNo, int pmtPid) {
- mProgramNo = programNo;
- mPmtPid = pmtPid;
- }
-
- public int getProgramNo() {
- return mProgramNo;
- }
-
- public int getPmtPid() {
- return mPmtPid;
- }
-
- @Override
- public String toString() {
- return String.format("Program No: %x PMT Pid: %x", mProgramNo, mPmtPid);
- }
- }
-
- public static class PmtItem {
- public static final int ES_PID_PCR = 0x100;
-
- private final int mStreamType;
- private final int mEsPid;
- private final List<AtscAudioTrack> mAudioTracks;
- private final List<AtscCaptionTrack> mCaptionTracks;
-
- public PmtItem(int streamType, int esPid,
- List<AtscAudioTrack> audioTracks, List<AtscCaptionTrack> captionTracks) {
- mStreamType = streamType;
- mEsPid = esPid;
- mAudioTracks = audioTracks;
- mCaptionTracks = captionTracks;
- }
-
- public int getStreamType() {
- return mStreamType;
- }
-
- public int getEsPid() {
- return mEsPid;
- }
-
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
-
- @Override
- public String toString() {
- return String.format("Stream Type: %x ES Pid: %x AudioTracks: %s CaptionTracks: %s",
- mStreamType, mEsPid, mAudioTracks, mCaptionTracks);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java
deleted file mode 100644
index 8f98e67c..00000000
--- a/src/com/android/tv/tuner/data/PsipData.java
+++ /dev/null
@@ -1,820 +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.data;
-
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.ts.SectionParser;
-import com.android.tv.tuner.util.ConvertUtils;
-import com.android.tv.util.StringUtils;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Collection of ATSC PSIP table items.
- */
-public class PsipData {
-
- private PsipData() {
- }
-
- public static class PsipSection {
- private final int mTableId;
- private final int mTableIdExtension;
- private final int mSectionNumber;
- private final boolean mCurrentNextIndicator;
-
- public static PsipSection create(byte[] data) {
- if (data.length < 9) {
- return null;
- }
- int tableId = data[0] & 0xff;
- int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff);
- int sectionNumber = data[6] & 0xff;
- boolean currentNextIndicator = (data[5] & 0x01) != 0;
- return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator);
- }
-
- private PsipSection(int tableId, int tableIdExtension, int sectionNumber,
- boolean currentNextIndicator) {
- mTableId = tableId;
- mTableIdExtension = tableIdExtension;
- mSectionNumber = sectionNumber;
- mCurrentNextIndicator = currentNextIndicator;
- }
-
- public int getTableId() {
- return mTableId;
- }
-
- public int getTableIdExtension() {
- return mTableIdExtension;
- }
-
- public int getSectionNumber() {
- return mSectionNumber;
- }
-
- // This is for indicating that the section sent is applicable.
- // We only consider a situation where currentNextIndicator is expected to have a true value.
- // So, we are not going to compare this variable in hashCode() and equals() methods.
- public boolean getCurrentNextIndicator() {
- return mCurrentNextIndicator;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + mTableId;
- result = 31 * result + mTableIdExtension;
- result = 31 * result + mSectionNumber;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PsipSection) {
- PsipSection another = (PsipSection) obj;
- return mTableId == another.getTableId()
- && mTableIdExtension == another.getTableIdExtension()
- && mSectionNumber == another.getSectionNumber();
- }
- return false;
- }
- }
-
- /**
- * {@link TvTracksInterface} for serving the audio and caption tracks.
- */
- public interface TvTracksInterface {
- /**
- * Set the flag that tells the caption tracks have been found in this section container.
- */
- void setHasCaptionTrack();
-
- /**
- * Returns whether or not the caption tracks have been found in this section container.
- * If true, zero caption track will be interpreted as a clearance of the caption tracks.
- */
- boolean hasCaptionTrack();
-
- /**
- * Returns the audio tracks received.
- */
- List<AtscAudioTrack> getAudioTracks();
-
- /**
- * Returns the caption tracks received.
- */
- List<AtscCaptionTrack> getCaptionTracks();
- }
-
- public static class MgtItem {
- public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100;
- public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f;
- public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004;
- public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200;
- public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f;
-
- private final int mTableType;
- private final int mTableTypePid;
-
- public MgtItem(int tableType, int tableTypePid) {
- mTableType = tableType;
- mTableTypePid = tableTypePid;
- }
-
- public int getTableType() {
- return mTableType;
- }
-
- public int getTableTypePid() {
- return mTableTypePid;
- }
- }
-
- public static class VctItem {
- private final String mShortName;
- private final String mLongName;
- private final int mServiceType;
- private final int mChannelTsid;
- private final int mProgramNumber;
- private final int mMajorChannelNumber;
- private final int mMinorChannelNumber;
- private final int mSourceId;
- private String mDescription;
-
- public VctItem(String shortName, String longName, int serviceType, int channelTsid,
- int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId) {
- mShortName = shortName;
- mLongName = longName;
- mServiceType = serviceType;
- mChannelTsid = channelTsid;
- mProgramNumber = programNumber;
- mMajorChannelNumber = majorChannelNumber;
- mMinorChannelNumber = minorChannelNumber;
- mSourceId = sourceId;
- }
-
- public String getShortName() {
- return mShortName;
- }
-
- public String getLongName() {
- return mLongName;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public int getChannelTsid() {
- return mChannelTsid;
- }
-
- public int getProgramNumber() {
- return mProgramNumber;
- }
-
- public int getMajorChannelNumber() {
- return mMajorChannelNumber;
- }
-
- public int getMinorChannelNumber() {
- return mMinorChannelNumber;
- }
-
- public int getSourceId() {
- return mSourceId;
- }
-
- @Override
- public String toString() {
- return String
- .format(Locale.US, "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x "
- + "ProgramNumber:%d %d-%d SourceId: %x",
- mShortName, mLongName, mServiceType, mChannelTsid,
- mProgramNumber, mMajorChannelNumber, mMinorChannelNumber, mSourceId);
- }
-
- public void setDescription(String description) {
- mDescription = description;
- }
-
- public String getDescription() {
- return mDescription;
- }
- }
-
- public static class SdtItem {
- private final String mServiceName;
- private final String mServiceProviderName;
- private final int mServiceType;
- private final int mServiceId;
- private final int mOriginalNetWorkId;
-
- public SdtItem(String serviceName, String serviceProviderName, int serviceType,
- int serviceId, int originalNetWorkId) {
- mServiceName = serviceName;
- mServiceProviderName = serviceProviderName;
- mServiceType = serviceType;
- mServiceId = serviceId;
- mOriginalNetWorkId = originalNetWorkId;
- }
-
- public String getServiceName() {
- return mServiceName;
- }
-
- public String getServiceProviderName() {
- return mServiceProviderName;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public int getServiceId() {
- return mServiceId;
- }
-
- public int getOriginalNetworkId() {
- return mOriginalNetWorkId;
- }
-
- @Override
- public String toString() {
- return String.format("ServiceName: %s ServiceProviderName:%s ServiceType:%d "
- + "OriginalNetworkId:%d",
- mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId);
- }
- }
-
- /**
- * A base class for descriptors of Ts packets.
- */
- public abstract static class TsDescriptor {
- public abstract int getTag();
- }
-
- public static class ContentAdvisoryDescriptor extends TsDescriptor {
- private final List<RatingRegion> mRatingRegions;
-
- public ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions) {
- mRatingRegions = ratingRegions;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY;
- }
-
- public List<RatingRegion> getRatingRegions() {
- return mRatingRegions;
- }
- }
-
- public static class CaptionServiceDescriptor extends TsDescriptor {
- private final List<AtscCaptionTrack> mCaptionTracks;
-
- public CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks) {
- mCaptionTracks = captionTracks;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE;
- }
-
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
- }
-
- public static class ExtendedChannelNameDescriptor extends TsDescriptor {
- private final String mLongChannelName;
-
- public ExtendedChannelNameDescriptor(String longChannelName) {
- mLongChannelName = longChannelName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME;
- }
-
- public String getLongChannelName() {
- return mLongChannelName;
- }
- }
-
- public static class GenreDescriptor extends TsDescriptor {
- private final String[] mBroadcastGenres;
- private final String[] mCanonicalGenres;
-
- public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) {
- mBroadcastGenres = broadcastGenres;
- mCanonicalGenres = canonicalGenres;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_GENRE;
- }
-
- public String[] getBroadcastGenres() {
- return mBroadcastGenres;
- }
-
- public String[] getCanonicalGenres() {
- return mCanonicalGenres;
- }
- }
-
- public static class Ac3AudioDescriptor extends TsDescriptor {
- // See A/52 Annex A. Table A4.2
- private static final byte SAMPLE_RATE_CODE_48000HZ = 0;
- private static final byte SAMPLE_RATE_CODE_44100HZ = 1;
- private static final byte SAMPLE_RATE_CODE_32000HZ = 2;
-
- private final byte mSampleRateCode;
- private final byte mBsid;
- private final byte mBitRateCode;
- private final byte mSurroundMode;
- private final byte mBsmod;
- private final int mNumChannels;
- private final boolean mFullSvc;
- private final byte mLangCod;
- private final byte mLangCod2;
- private final byte mMainId;
- private final byte mPriority;
- private final byte mAsvcflags;
- private final String mText;
- private final String mLanguage;
- private final String mLanguage2;
-
- public Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode,
- byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod,
- byte langCod2, byte mainId, byte priority, byte asvcflags, String text,
- String language, String language2) {
- mSampleRateCode = sampleRateCode;
- mBsid = bsid;
- mBitRateCode = bitRateCode;
- mSurroundMode = surroundMode;
- mBsmod = bsmod;
- mNumChannels = numChannels;
- mFullSvc = fullSvc;
- mLangCod = langCod;
- mLangCod2 = langCod2;
- mMainId = mainId;
- mPriority = priority;
- mAsvcflags = asvcflags;
- mText = text;
- mLanguage = language;
- mLanguage2 = language2;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM;
- }
-
- public byte getSampleRateCode() {
- return mSampleRateCode;
- }
-
- public int getSampleRate() {
- switch (mSampleRateCode) {
- case SAMPLE_RATE_CODE_48000HZ:
- return 48000;
- case SAMPLE_RATE_CODE_44100HZ:
- return 44100;
- case SAMPLE_RATE_CODE_32000HZ:
- return 32000;
- default:
- return 0;
- }
- }
-
- public byte getBsid() {
- return mBsid;
- }
-
- public byte getBitRateCode() {
- return mBitRateCode;
- }
-
- public byte getSurroundMode() {
- return mSurroundMode;
- }
-
- public byte getBsmod() {
- return mBsmod;
- }
-
- public int getNumChannels() {
- return mNumChannels;
- }
-
- public boolean isFullSvc() {
- return mFullSvc;
- }
-
- public byte getLangCod() {
- return mLangCod;
- }
-
- public byte getLangCod2() {
- return mLangCod2;
- }
-
- public byte getMainId() {
- return mMainId;
- }
-
- public byte getPriority() {
- return mPriority;
- }
-
- public byte getAsvcflags() {
- return mAsvcflags;
- }
-
- public String getText() {
- return mText;
- }
-
- public String getLanguage() {
- return mLanguage;
- }
-
- public String getLanguage2() {
- return mLanguage2;
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US,
- "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, "
- + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, "
- + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s"
- + ", language2: %s", mSampleRateCode, mBsid, mBitRateCode, mSurroundMode,
- mBsmod, mNumChannels, mFullSvc, mLangCod, mLangCod2, mMainId, mPriority,
- mAsvcflags, mText, mLanguage, mLanguage2);
- }
- }
-
- public static class Iso639LanguageDescriptor extends TsDescriptor {
- private final List<AtscAudioTrack> mAudioTracks;
-
- public Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks) {
- mAudioTracks = audioTracks;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE;
- }
-
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- @Override
- public String toString() {
- return String.format("%s %s", getClass().getName(), mAudioTracks);
- }
- }
-
- public static class ServiceDescriptor extends TsDescriptor {
- private final int mServiceType;
- private final String mServiceProviderName;
- private final String mServiceName;
-
- public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) {
- mServiceType = serviceType;
- mServiceProviderName = serviceProviderName;
- mServiceName = serviceName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE;
- }
-
- public int getServiceType() {
- return mServiceType;
- }
-
- public String getServiceProviderName() {
- return mServiceProviderName;
- }
-
- public String getServiceName() {
- return mServiceName;
- }
-
- @Override
- public String toString() {
- return String.format(
- "Service descriptor, service type: %d, "
- + "service provider name: %s, "
- + "service name: %s", mServiceType, mServiceProviderName, mServiceName);
- }
- }
-
- public static class ShortEventDescriptor extends TsDescriptor {
- private final String mLanguage;
- private final String mEventName;
- private final String mText;
-
- public ShortEventDescriptor(String language, String eventName, String text) {
- mLanguage = language;
- mEventName = eventName;
- mText = text;
- }
-
- public String getEventName() {
- return mEventName;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT;
- }
-
- @Override
- public String toString() {
- return String.format("ShortEvent Descriptor, language:%s, event name: %s, "
- + "text:%s", mLanguage, mEventName, mText);
- }
- }
-
- public static class ParentalRatingDescriptor extends TsDescriptor {
- private final HashMap<String, Integer> mRatings;
-
- public ParentalRatingDescriptor(HashMap<String, Integer> ratings) {
- mRatings = ratings;
- }
-
- @Override
- public int getTag() {
- return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING;
- }
-
- public HashMap<String, Integer> getRatings() {
- return mRatings;
- }
-
- @Override
- public String toString() {
- return String.format("Parental rating descriptor, ratings:" + mRatings);
- }
- }
-
- public static class RatingRegion {
- private final int mName;
- private final String mDescription;
- private final List<RegionalRating> mRegionalRatings;
-
- public RatingRegion(int name, String description, List<RegionalRating> regionalRatings) {
- mName = name;
- mDescription = description;
- mRegionalRatings = regionalRatings;
- }
-
- public int getName() {
- return mName;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- public List<RegionalRating> getRegionalRatings() {
- return mRegionalRatings;
- }
- }
-
- public static class RegionalRating {
- private final int mDimension;
- private final int mRating;
-
- public RegionalRating(int dimension, int rating) {
- mDimension = dimension;
- mRating = rating;
- }
-
- public int getDimension() {
- return mDimension;
- }
-
- public int getRating() {
- return mRating;
- }
- }
-
- public static class EitItem implements Comparable<EitItem>, TvTracksInterface {
- public static final long INVALID_PROGRAM_ID = -1;
-
- // A program id is a primary key of TvContract.Programs table. So it must be positive.
- private final long mProgramId;
- private final int mEventId;
- private final String mTitleText;
- private String mDescription;
- private final long mStartTime;
- private final int mLengthInSecond;
- private final String mContentRating;
- private final List<AtscAudioTrack> mAudioTracks;
- private final List<AtscCaptionTrack> mCaptionTracks;
- private boolean mHasCaptionTrack;
- private final String mBroadcastGenre;
- private final String mCanonicalGenre;
-
- public EitItem(long programId, int eventId, String titleText, long startTime,
- int lengthInSecond, String contentRating, List<AtscAudioTrack> audioTracks,
- List<AtscCaptionTrack> captionTracks, String broadcastGenre, String canonicalGenre,
- String description) {
- mProgramId = programId;
- mEventId = eventId;
- mTitleText = titleText;
- mStartTime = startTime;
- mLengthInSecond = lengthInSecond;
- mContentRating = contentRating;
- mAudioTracks = audioTracks;
- mCaptionTracks = captionTracks;
- mBroadcastGenre = broadcastGenre;
- mCanonicalGenre = canonicalGenre;
- mDescription = description;
- }
-
- public long getProgramId() {
- return mProgramId;
- }
-
- public int getEventId() {
- return mEventId;
- }
-
- public String getTitleText() {
- return mTitleText;
- }
-
- public void setDescription(String description) {
- mDescription = description;
- }
-
- public String getDescription() {
- return mDescription;
- }
-
- public long getStartTime() {
- return mStartTime;
- }
-
- public int getLengthInSecond() {
- return mLengthInSecond;
- }
-
- public long getStartTimeUtcMillis() {
- return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS;
- }
-
- public long getEndTimeUtcMillis() {
- return ConvertUtils.convertGPSTimeToUnixEpoch(
- mStartTime + mLengthInSecond) * DateUtils.SECOND_IN_MILLIS;
- }
-
- public String getContentRating() {
- return mContentRating;
- }
-
- @Override
- public List<AtscAudioTrack> getAudioTracks() {
- return mAudioTracks;
- }
-
- @Override
- public List<AtscCaptionTrack> getCaptionTracks() {
- return mCaptionTracks;
- }
-
- public String getBroadcastGenre() {
- return mBroadcastGenre;
- }
-
- public String getCanonicalGenre() {
- return mCanonicalGenre;
- }
-
- @Override
- public void setHasCaptionTrack() {
- mHasCaptionTrack = true;
- }
-
- @Override
- public boolean hasCaptionTrack() {
- return mHasCaptionTrack;
- }
-
- @Override
- public int compareTo(@NonNull EitItem item) {
- // The list of caption tracks and the program ids are not compared in here because the
- // channels in TIF have the concept of the caption and audio tracks while the programs
- // do not and the programs in TIF only have a program id since they are the rows of
- // Content Provider.
- int ret = mEventId - item.getEventId();
- if (ret != 0) {
- return ret;
- }
- ret = StringUtils.compare(mTitleText, item.getTitleText());
- if (ret != 0) {
- return ret;
- }
- if (mStartTime > item.getStartTime()) {
- return 1;
- } else if (mStartTime < item.getStartTime()) {
- return -1;
- }
- if (mLengthInSecond > item.getLengthInSecond()) {
- return 1;
- } else if (mLengthInSecond < item.getLengthInSecond()) {
- return -1;
- }
-
- // Compares content ratings
- ret = StringUtils.compare(mContentRating, item.getContentRating());
- if (ret != 0) {
- return ret;
- }
-
- // Compares broadcast genres
- ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre());
- if (ret != 0) {
- return ret;
- }
- // Compares canonical genres
- ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre());
- if (ret != 0) {
- return ret;
- }
-
- // Compares descriptions
- return StringUtils.compare(mDescription, item.getDescription());
- }
-
- public String getAudioLanguage() {
- if (mAudioTracks == null) {
- return "";
- }
- ArrayList<String> languages = new ArrayList<>();
- for (AtscAudioTrack audioTrack : mAudioTracks) {
- languages.add(audioTrack.language);
- }
- return TextUtils.join(",", languages);
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US,
- "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, "
- + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, "
- + "genres (broadcast: %s, canonical: %s), description: %s",
- mProgramId, mEventId, mTitleText, mStartTime, mLengthInSecond, mContentRating,
- mAudioTracks != null ? mAudioTracks.size() : 0,
- mCaptionTracks != null ? mCaptionTracks.size() : 0,
- mBroadcastGenre, mCanonicalGenre, mDescription);
- }
- }
-
- public static class EttItem {
- public final int eventId;
- public final String text;
-
- public EttItem(int eventId, String text) {
- this.eventId = eventId;
- this.text = text;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java
deleted file mode 100644
index 1cf514c1..00000000
--- a/src/com/android/tv/tuner/data/TunerChannel.java
+++ /dev/null
@@ -1,511 +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.data;
-
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Channel.TunerChannelProto;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.Ints;
-import com.android.tv.util.StringUtils;
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A class that represents a single channel accessible through a tuner.
- */
-public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface {
- private static final String TAG = "TunerChannel";
-
- /**
- * Channel number separator between major number and minor number.
- */
- public static final char CHANNEL_NUMBER_SEPARATOR = '-';
-
- // See ATSC Code Points Registry.
- private static final String[] ATSC_SERVICE_TYPE_NAMES = new String[] {
- "ATSC Reserved",
- "Analog television channels",
- "ATSC_digital_television",
- "ATSC_audio",
- "ATSC_data_only_service",
- "Software Download",
- "Unassociated/Small Screen Service",
- "Parameterized Service",
- "ATSC NRT Service",
- "Extended Parameterized Service" };
- private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
- ATSC_SERVICE_TYPE_NAMES[Channel.SERVICE_TYPE_ATSC_RESERVED];
-
- public static final int INVALID_FREQUENCY = -1;
-
- // According to RFC4259, The number of available PIDs ranges from 0 to 8191.
- public static final int INVALID_PID = -1;
-
- // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
- public static final int INVALID_STREAMTYPE = -1;
-
- // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
- private final TunerChannelProto mProto;
-
- private TunerChannel(PsipData.VctItem channel, int programNumber,
- List<PsiData.PmtItem> pmtItems, int type) {
- mProto = new TunerChannelProto();
- if (channel == null) {
- mProto.shortName = "";
- mProto.tsid = 0;
- mProto.programNumber = programNumber;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- } else {
- mProto.shortName = channel.getShortName();
- if (channel.getLongName() != null) {
- mProto.longName = channel.getLongName();
- }
- mProto.tsid = channel.getChannelTsid();
- mProto.programNumber = channel.getProgramNumber();
- mProto.virtualMajor = channel.getMajorChannelNumber();
- mProto.virtualMinor = channel.getMinorChannelNumber();
- if (channel.getDescription() != null) {
- mProto.description = channel.getDescription();
- }
- mProto.serviceType = channel.getServiceType();
- }
- initProto(pmtItems, type);
- }
-
- private void initProto(List<PsiData.PmtItem> pmtItems, int type) {
- mProto.type = type;
- mProto.channelId = -1L;
- mProto.frequency = INVALID_FREQUENCY;
- mProto.videoPid = INVALID_PID;
- mProto.videoStreamType = INVALID_STREAMTYPE;
- List<Integer> audioPids = new ArrayList<>();
- List<Integer> audioStreamTypes = new ArrayList<>();
- for (PsiData.PmtItem pmt : pmtItems) {
- switch (pmt.getStreamType()) {
- // MPEG ES stream video types
- case Channel.MPEG1:
- case Channel.MPEG2:
- case Channel.H263:
- case Channel.H264:
- case Channel.H265:
- mProto.videoPid = pmt.getEsPid();
- mProto.videoStreamType = pmt.getStreamType();
- break;
-
- // MPEG ES stream audio types
- case Channel.MPEG1AUDIO:
- case Channel.MPEG2AUDIO:
- case Channel.MPEG2AACAUDIO:
- case Channel.MPEG4LATMAACAUDIO:
- case Channel.A52AC3AUDIO:
- case Channel.EAC3AUDIO:
- audioPids.add(pmt.getEsPid());
- audioStreamTypes.add(pmt.getStreamType());
- break;
-
- // Non MPEG ES stream types
- case 0x100: // PmtItem.ES_PID_PCR:
- mProto.pcrPid = pmt.getEsPid();
- break;
- }
- }
- mProto.audioPids = Ints.toArray(audioPids);
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
- mProto.audioTrackIndex = (audioPids.size() > 0) ? 0 : -1;
- }
-
- private TunerChannel(int programNumber, int type, PsipData.SdtItem channel,
- List<PsiData.PmtItem> pmtItems) {
- mProto = new TunerChannelProto();
- mProto.tsid = 0;
- mProto.virtualMajor = 0;
- mProto.virtualMinor = 0;
- if (channel == null) {
- mProto.shortName = "";
- mProto.programNumber = programNumber;
- } else {
- mProto.shortName = channel.getServiceName();
- mProto.programNumber = channel.getServiceId();
- mProto.serviceType = channel.getServiceType();
- }
- initProto(pmtItems, type);
- }
-
- /**
- * Initialize tuner channel with VCT items and PMT items.
- */
- public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- this(channel, 0, pmtItems, Channel.TYPE_TUNER);
- }
-
- /**
- * Initialize tuner channel with program number and PMT items.
- */
- public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) {
- this(null, programNumber, pmtItems, Channel.TYPE_TUNER);
- }
-
- /**
- * Initialize tuner channel with SDT items and PMT items.
- */
- public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- this(0, Channel.TYPE_TUNER, channel, pmtItems);
- }
-
- private TunerChannel(TunerChannelProto tunerChannelProto) {
- mProto = tunerChannelProto;
- }
-
- public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE);
- }
-
- public static TunerChannel forDvbFile(
- PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- return new TunerChannel(0, Channel.TYPE_FILE, channel, pmtItems);
- }
-
- /**
- * Create a TunerChannel object suitable for network tuners
- * @param major Channel number major
- * @param minor Channel number minor
- * @param programNumber Program number
- * @param shortName Short name
- * @param recordingProhibited Recording prohibition info
- * @param videoFormat Video format. Should be {@code null} or one of the followings:
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P},
- * {@link android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P}
- * @return a TunerChannel object
- */
- public static TunerChannel forNetwork(int major, int minor, int programNumber,
- String shortName, boolean recordingProhibited, String videoFormat) {
- TunerChannel tunerChannel = new TunerChannel(
- null, programNumber, Collections.EMPTY_LIST, Channel.TYPE_NETWORK);
- tunerChannel.setVirtualMajor(major);
- tunerChannel.setVirtualMinor(minor);
- tunerChannel.setShortName(shortName);
- // Set audio and video pids in order to work around the audio-only channel check.
- tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0)));
- tunerChannel.selectAudioTrack(0);
- tunerChannel.setVideoPid(0);
- tunerChannel.setRecordingProhibited(recordingProhibited);
- if (videoFormat != null) {
- tunerChannel.setVideoFormat(videoFormat);
- }
- return tunerChannel;
- }
-
- public String getName() {
- return (!mProto.shortName.isEmpty()) ? mProto.shortName : mProto.longName;
- }
-
- public String getShortName() {
- return mProto.shortName;
- }
-
- public int getProgramNumber() {
- return mProto.programNumber;
- }
-
- public int getServiceType() {
- return mProto.serviceType;
- }
-
- public String getServiceTypeName() {
- int serviceType = mProto.serviceType;
- if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
- return ATSC_SERVICE_TYPE_NAMES[serviceType];
- }
- return ATSC_SERVICE_TYPE_NAME_RESERVED;
- }
-
- public int getVirtualMajor() {
- return mProto.virtualMajor;
- }
-
- public int getVirtualMinor() {
- return mProto.virtualMinor;
- }
-
- public int getFrequency() {
- return mProto.frequency;
- }
-
- public String getModulation() {
- return mProto.modulation;
- }
-
- public int getTsid() {
- return mProto.tsid;
- }
-
- public int getVideoPid() {
- return mProto.videoPid;
- }
-
- synchronized public void setVideoPid(int videoPid) {
- mProto.videoPid = videoPid;
- }
-
- public int getVideoStreamType() {
- return mProto.videoStreamType;
- }
-
- public int getAudioPid() {
- if (mProto.audioTrackIndex == -1) {
- return INVALID_PID;
- }
- return mProto.audioPids[mProto.audioTrackIndex];
- }
-
- public int getAudioStreamType() {
- if (mProto.audioTrackIndex == -1) {
- return INVALID_STREAMTYPE;
- }
- return mProto.audioStreamTypes[mProto.audioTrackIndex];
- }
-
- public List<Integer> getAudioPids() {
- return Ints.asList(mProto.audioPids);
- }
-
- synchronized public void setAudioPids(List<Integer> audioPids) {
- mProto.audioPids = Ints.toArray(audioPids);
- }
-
- public List<Integer> getAudioStreamTypes() {
- return Ints.asList(mProto.audioStreamTypes);
- }
-
- synchronized public void setAudioStreamTypes(List<Integer> audioStreamTypes) {
- mProto.audioStreamTypes = Ints.toArray(audioStreamTypes);
- }
-
- public int getPcrPid() {
- return mProto.pcrPid;
- }
-
- public int getType() {
- return mProto.type;
- }
-
- synchronized public void setFilepath(String filepath) {
- mProto.filepath = filepath == null ? "" : filepath;
- }
-
- public String getFilepath() {
- return mProto.filepath;
- }
-
- synchronized public void setVirtualMajor(int virtualMajor) {
- mProto.virtualMajor = virtualMajor;
- }
-
- synchronized public void setVirtualMinor(int virtualMinor) {
- mProto.virtualMinor = virtualMinor;
- }
-
- synchronized public void setShortName(String shortName) {
- mProto.shortName = shortName == null ? "" : shortName;
- }
-
- synchronized public void setFrequency(int frequency) {
- mProto.frequency = frequency;
- }
-
- synchronized public void setModulation(String modulation) {
- mProto.modulation = modulation == null ? "" : modulation;
- }
-
- public boolean hasVideo() {
- return mProto.videoPid != INVALID_PID;
- }
-
- public boolean hasAudio() {
- return getAudioPid() != INVALID_PID;
- }
-
- public long getChannelId() {
- return mProto.channelId;
- }
-
- synchronized public void setChannelId(long channelId) {
- mProto.channelId = channelId;
- }
-
- public String getDisplayNumber() {
- return getDisplayNumber(true);
- }
-
- public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
- if (mProto.virtualMajor != 0 && (mProto.virtualMinor != 0 || !ignoreZeroMinorNumber)) {
- return String.format("%d%c%d", mProto.virtualMajor, CHANNEL_NUMBER_SEPARATOR,
- mProto.virtualMinor);
- } else if (mProto.virtualMajor != 0) {
- return Integer.toString(mProto.virtualMajor);
- } else {
- return Integer.toString(mProto.programNumber);
- }
- }
-
- public String getDescription() {
- return mProto.description;
- }
-
- @Override
- synchronized public void setHasCaptionTrack() {
- mProto.hasCaptionTrack = true;
- }
-
- @Override
- public boolean hasCaptionTrack() {
- return mProto.hasCaptionTrack;
- }
-
- @Override
- public List<AtscAudioTrack> getAudioTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks));
- }
-
- synchronized public void setAudioTracks(List<AtscAudioTrack> audioTracks) {
- mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]);
- }
-
- @Override
- public List<AtscCaptionTrack> getCaptionTracks() {
- return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks));
- }
-
- synchronized public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
- mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]);
- }
-
- synchronized public void selectAudioTrack(int index) {
- if (0 <= index && index < mProto.audioPids.length) {
- mProto.audioTrackIndex = index;
- } else {
- mProto.audioTrackIndex = -1;
- }
- }
-
- synchronized public void setRecordingProhibited(boolean recordingProhibited) {
- mProto.recordingProhibited = recordingProhibited;
- }
-
- public boolean isRecordingProhibited() {
- return mProto.recordingProhibited;
- }
-
- synchronized public void setVideoFormat(String videoFormat) {
- mProto.videoFormat = videoFormat == null ? "" : videoFormat;
- }
-
- public String getVideoFormat() {
- return mProto.videoFormat;
- }
-
- @Override
- public String toString() {
- switch (mProto.type) {
- case Channel.TYPE_FILE:
- return String.format("{%d-%d %s} Filepath: %s, ProgramNumber %d",
- mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
- mProto.filepath, mProto.programNumber);
- //case Channel.TYPE_TUNER:
- default:
- return String.format("{%d-%d %s} Frequency: %d, ProgramNumber %d",
- mProto.virtualMajor, mProto.virtualMinor, mProto.shortName,
- mProto.frequency, mProto.programNumber);
- }
- }
-
- @Override
- public int compareTo(@NonNull TunerChannel channel) {
- // In the same frequency, the program number acts as the sub-channel number.
- int ret = getFrequency() - channel.getFrequency();
- if (ret != 0) {
- return ret;
- }
- ret = getProgramNumber() - channel.getProgramNumber();
- if (ret != 0) {
- return ret;
- }
- ret = StringUtils.compare(getName(), channel.getName());
- if (ret != 0) {
- return ret;
- }
- // For FileTsStreamer, file paths should be compared.
- return StringUtils.compare(getFilepath(), channel.getFilepath());
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof TunerChannel)) {
- return false;
- }
- return compareTo((TunerChannel) o) == 0;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath());
- }
-
- // Serialization
- synchronized public byte[] toByteArray() {
- try {
- return MessageNano.toByteArray(mProto);
- } catch (Exception e) {
- // Retry toByteArray. b/34197766
- Log.w(TAG, "TunerChannel or its variables are modified in multiple thread without lock",
- e);
- return MessageNano.toByteArray(mProto);
- }
- }
-
- public static TunerChannel parseFrom(byte[] data) {
- if (data == null) {
- return null;
- }
- try {
- return new TunerChannel(TunerChannelProto.parseFrom(data));
- } catch (IOException e) {
- Log.e(TAG, "Could not parse from byte array", e);
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
deleted file mode 100644
index 5f536708..00000000
--- a/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
+++ /dev/null
@@ -1,302 +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.exoplayer;
-
-import android.util.Log;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaClock;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.util.Assertions;
-import com.android.tv.tuner.cc.Cea708Parser;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-
-import java.io.IOException;
-
-/**
- * A {@link TrackRenderer} for CEA-708 textual subtitles.
- */
-public class Cea708TextTrackRenderer extends TrackRenderer implements
- Cea708Parser.OnCea708ParserListener {
- private static final String TAG = "Cea708TextTrackRenderer";
- private static final boolean DEBUG = false;
-
- public static final int MSG_SERVICE_NUMBER = 1;
- public static final int MSG_ENABLE_CLOSED_CAPTION = 2;
-
- // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
- private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8;
-
- private final SampleSource.SampleSourceReader mSource;
- private final SampleHolder mSampleHolder;
- private final MediaFormatHolder mFormatHolder;
- private int mServiceNumber;
- private boolean mInputStreamEnded;
- private long mCurrentPositionUs;
- private long mPresentationTimeUs;
- private int mTrackIndex;
- private boolean mRenderingDisabled;
- private Cea708Parser mCea708Parser;
- private CcListener mCcListener;
-
- public interface CcListener {
- void emitEvent(CaptionEvent captionEvent);
- void clearCaption();
- void discoverServiceNumber(int serviceNumber);
- }
-
- public Cea708TextTrackRenderer(SampleSource source) {
- mSource = source.register();
- mTrackIndex = -1;
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mFormatHolder = new MediaFormatHolder();
- }
-
- @Override
- protected MediaClock getMediaClock() {
- return null;
- }
-
- private boolean handlesMimeType(String mimeType) {
- return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708);
- }
-
- @Override
- protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
- boolean sourcePrepared = mSource.prepare(positionUs);
- if (!sourcePrepared) {
- return false;
- }
- int trackCount = mSource.getTrackCount();
- for (int i = 0; i < trackCount; ++i) {
- MediaFormat trackFormat = mSource.getFormat(i);
- if (handlesMimeType(trackFormat.mimeType)) {
- mTrackIndex = i;
- clearDecodeState();
- return true;
- }
- }
- // TODO: Check this case. (Source do not have the proper mime type.)
- return true;
- }
-
- @Override
- protected void onEnabled(int track, long positionUs, boolean joining) {
- Assertions.checkArgument(mTrackIndex != -1 && track == 0);
- mSource.enable(mTrackIndex, positionUs);
- mInputStreamEnded = false;
- mPresentationTimeUs = positionUs;
- mCurrentPositionUs = Long.MIN_VALUE;
- }
-
- @Override
- protected void onDisabled() {
- mSource.disable(mTrackIndex);
- }
-
- @Override
- protected void onReleased() {
- mSource.release();
- mCea708Parser = null;
- }
-
- @Override
- protected boolean isEnded() {
- return mInputStreamEnded;
- }
-
- @Override
- protected boolean isReady() {
- // Since this track will be fed by {@link VideoTrackRenderer},
- // it is not required to control transition between ready state and buffering state.
- return true;
- }
-
- @Override
- protected int getTrackCount() {
- return mTrackIndex < 0 ? 0 : 1;
- }
-
- @Override
- protected MediaFormat getFormat(int track) {
- Assertions.checkArgument(mTrackIndex != -1 && track == 0);
- return mSource.getFormat(mTrackIndex);
- }
-
- @Override
- protected void maybeThrowError() throws ExoPlaybackException {
- try {
- mSource.maybeThrowError();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- @Override
- protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- try {
- mPresentationTimeUs = positionUs;
- if (!mInputStreamEnded) {
- processOutput();
- feedInputBuffer();
- }
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- private boolean processOutput() {
- return !mInputStreamEnded && mCea708Parser != null &&
- mCea708Parser.processClosedCaptions(mPresentationTimeUs);
- }
-
- private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
- if (mInputStreamEnded) {
- return false;
- }
- long discontinuity = mSource.readDiscontinuity(mTrackIndex);
- if (discontinuity != SampleSource.NO_DISCONTINUITY) {
- if (DEBUG) {
- Log.d(TAG, "Read discontinuity happened");
- }
-
- // TODO: handle input discontinuity for trickplay.
- clearDecodeState();
- mPresentationTimeUs = discontinuity;
- return false;
- }
- mSampleHolder.data.clear();
- mSampleHolder.size = 0;
- int result = mSource.readData(mTrackIndex, mPresentationTimeUs,
- mFormatHolder, mSampleHolder);
- switch (result) {
- case SampleSource.NOTHING_READ: {
- return false;
- }
- case SampleSource.FORMAT_READ: {
- if (DEBUG) {
- Log.i(TAG, "Format was read again");
- }
- return true;
- }
- case SampleSource.END_OF_STREAM: {
- if (DEBUG) {
- Log.i(TAG, "End of stream from SampleSource");
- }
- mInputStreamEnded = true;
- return false;
- }
- case SampleSource.SAMPLE_READ: {
- mSampleHolder.data.flip();
- if (mCea708Parser != null && !mRenderingDisabled) {
- mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs);
- }
- return true;
- }
- }
- return false;
- }
-
- private void clearDecodeState() {
- mCea708Parser = new Cea708Parser();
- mCea708Parser.setListener(this);
- mCea708Parser.setListenServiceNumber(mServiceNumber);
- }
-
- @Override
- protected long getDurationUs() {
- return mSource.getFormat(mTrackIndex).durationUs;
- }
-
- @Override
- protected long getBufferedPositionUs() {
- return mSource.getBufferedPositionUs();
- }
-
- @Override
- protected void seekTo(long currentPositionUs) throws ExoPlaybackException {
- mSource.seekToUs(currentPositionUs);
- mInputStreamEnded = false;
- mPresentationTimeUs = currentPositionUs;
- mCurrentPositionUs = Long.MIN_VALUE;
- }
-
- @Override
- protected void onStarted() {
- // do nothing.
- }
-
- @Override
- protected void onStopped() {
- // do nothing.
- }
-
- private void setServiceNumber(int serviceNumber) {
- mServiceNumber = serviceNumber;
- if (mCea708Parser != null) {
- mCea708Parser.setListenServiceNumber(serviceNumber);
- }
- }
-
- @Override
- public void emitEvent(CaptionEvent event) {
- if (mCcListener != null) {
- mCcListener.emitEvent(event);
- }
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- if (mCcListener != null) {
- mCcListener.discoverServiceNumber(serviceNumber);
- }
- }
-
- public void setCcListener(CcListener ccListener) {
- mCcListener = ccListener;
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SERVICE_NUMBER:
- setServiceNumber((int) message);
- break;
- case MSG_ENABLE_CLOSED_CAPTION:
- boolean renderingDisabled = (Boolean) message == false;
- if (mRenderingDisabled != renderingDisabled) {
- mRenderingDisabled = renderingDisabled;
- if (mRenderingDisabled) {
- if (mCea708Parser != null) {
- mCea708Parser.clear();
- }
- if (mCcListener != null) {
- mCcListener.clearCaption();
- }
- }
- }
- break;
- default:
- super.handleMessage(messageType, message);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java
deleted file mode 100644
index 0ab6d8c4..00000000
--- a/src/com/android/tv/tuner/exoplayer/ExoPlayerExtractorsFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer;
-
-import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
-import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.extractor.TimestampAdjuster;
-import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
-import com.google.android.exoplayer2.extractor.ts.TsExtractor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for
- * H.264 stream
- */
-public final class ExoPlayerExtractorsFactory implements ExtractorsFactory {
- @Override
- public Extractor[] createExtractors() {
- // Only create TsExtractor since we only target MPEG2TS stream.
- Extractor[] extractors = {
- new TsExtractor(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(
- DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES), false) };
- return extractors;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
deleted file mode 100644
index 0b648400..00000000
--- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java
+++ /dev/null
@@ -1,552 +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.exoplayer;
-
-import android.net.Uri;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.util.Pair;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.FormatHolder;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
-import com.google.android.exoplayer2.source.ExtractorMediaSource.EventListener;
-import com.google.android.exoplayer2.source.MediaPeriod;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.SampleStream;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
-import com.google.android.exoplayer2.trackselection.TrackSelection;
-import com.google.android.exoplayer2.upstream.DataSpec;
-import com.google.android.exoplayer2.upstream.DefaultAllocator;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
-import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A class that extracts samples from a live broadcast stream while storing the sample on the disk.
- * For demux, this class relies on {@link com.google.android.exoplayer.extractor.ts.TsExtractor}.
- */
-public class ExoPlayerSampleExtractor implements SampleExtractor {
- private static final String TAG = "ExoPlayerSampleExtracto";
-
- private static final int INVALID_TRACK_INDEX = -1;
- private final HandlerThread mSourceReaderThread;
- private final long mId;
-
- private final Handler.Callback mSourceReaderWorker;
-
- private BufferManager.SampleBuffer mSampleBuffer;
- private Handler mSourceReaderHandler;
- private volatile boolean mPrepared;
- private AtomicBoolean mOnCompletionCalled = new AtomicBoolean();
- private IOException mExceptionOnPrepare;
- private List<MediaFormat> mTrackFormats;
- private int mVideoTrackIndex = INVALID_TRACK_INDEX;
- private boolean mVideoTrackMet;
- private long mBaseSamplePts = Long.MIN_VALUE;
- private HashMap<Integer, Long> mLastExtractedPositionUsMap = new HashMap<>();
- private final List<Pair<Integer, SampleHolder>> mPendingSamples = new LinkedList<>();
- private OnCompletionListener mOnCompletionListener;
- private Handler mOnCompletionListenerHandler;
- private IOException mError;
-
- public ExoPlayerSampleExtractor(Uri uri, final DataSource source, BufferManager bufferManager,
- PlaybackBufferListener bufferListener, boolean isRecording) {
- // It'll be used as a timeshift file chunk name's prefix.
- mId = System.currentTimeMillis();
-
- EventListener eventListener = new EventListener() {
- @Override
- public void onLoadError(IOException error) {
- mError = error;
- }
- };
-
- mSourceReaderThread = new HandlerThread("SourceReaderThread");
- mSourceReaderWorker = new SourceReaderWorker(new ExtractorMediaSource(uri,
- new com.google.android.exoplayer2.upstream.DataSource.Factory() {
- @Override
- public com.google.android.exoplayer2.upstream.DataSource createDataSource() {
- // Returns an adapter implementation for ExoPlayer V2 DataSource interface.
- return new com.google.android.exoplayer2.upstream.DataSource() {
- @Override
- public long open(DataSpec dataSpec) throws IOException {
- return source.open(
- new com.google.android.exoplayer.upstream.DataSpec(
- dataSpec.uri, dataSpec.postBody,
- dataSpec.absoluteStreamPosition, dataSpec.position,
- dataSpec.length, dataSpec.key, dataSpec.flags));
- }
-
- @Override
- public int read(byte[] buffer, int offset, int readLength)
- throws IOException {
- return source.read(buffer, offset, readLength);
- }
-
- @Override
- public Uri getUri() {
- return null;
- }
-
- @Override
- public void close() throws IOException {
- source.close();
- }
- };
- }
- },
- new ExoPlayerExtractorsFactory(),
- // Do not create a handler if we not on a looper. e.g. test.
- Looper.myLooper() != null ? new Handler() : null, eventListener));
- if (isRecording) {
- mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false,
- RecordingSampleBuffer.BUFFER_REASON_RECORDING);
- } else {
- if (bufferManager == null) {
- mSampleBuffer = new SimpleSampleBuffer(bufferListener);
- } else {
- mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true,
- RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK);
- }
- }
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {
- mOnCompletionListener = listener;
- mOnCompletionListenerHandler = handler;
- }
-
- private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback {
- public static final int MSG_PREPARE = 1;
- public static final int MSG_FETCH_SAMPLES = 2;
- public static final int MSG_RELEASE = 3;
- private static final int RETRY_INTERVAL_MS = 50;
-
- private final MediaSource mSampleSource;
- private MediaPeriod mMediaPeriod;
- private SampleStream[] mStreams;
- private boolean[] mTrackMetEos;
- private boolean mMetEos = false;
- private long mCurrentPosition;
- private DecoderInputBuffer mDecoderInputBuffer;
- private SampleHolder mSampleHolder;
- private boolean mPrepareRequested;
-
- public SourceReaderWorker(MediaSource sampleSource) {
- mSampleSource = sampleSource;
- mSampleSource.prepareSource(null, false, new MediaSource.Listener() {
- @Override
- public void onSourceInfoRefreshed(Timeline timeline, Object manifest) {
- // Dynamic stream change is not supported yet. b/28169263
- // For now, this will cause EOS and playback reset.
- }
- });
- mDecoderInputBuffer = new DecoderInputBuffer(
- DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- }
-
- MediaFormat convertFormat(Format format) {
- if (format.sampleMimeType.startsWith("audio/")) {
- return MediaFormat.createAudioFormat(format.id, format.sampleMimeType,
- format.bitrate, format.maxInputSize,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.channelCount,
- format.sampleRate, format.initializationData, format.language,
- format.pcmEncoding);
- } else if (format.sampleMimeType.startsWith("video/")) {
- return MediaFormat.createVideoFormat(
- format.id, format.sampleMimeType, format.bitrate, format.maxInputSize,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.width, format.height,
- format.initializationData, format.rotationDegrees,
- format.pixelWidthHeightRatio, format.projectionData, format.stereoMode);
- } else if (format.sampleMimeType.endsWith("/cea-608")
- || format.sampleMimeType.startsWith("text/")) {
- return MediaFormat.createTextFormat(
- format.id, format.sampleMimeType, format.bitrate,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US, format.language);
- } else {
- return MediaFormat.createFormatForMimeType(
- format.id, format.sampleMimeType, format.bitrate,
- com.google.android.exoplayer.C.UNKNOWN_TIME_US);
- }
- }
-
- @Override
- public void onPrepared(MediaPeriod mediaPeriod) {
- if (mMediaPeriod == null) {
- // This instance is already released while the extractor is preparing.
- return;
- }
- TrackSelection.Factory selectionFactory = new FixedTrackSelection.Factory();
- TrackGroupArray trackGroupArray = mMediaPeriod.getTrackGroups();
- TrackSelection[] selections = new TrackSelection[trackGroupArray.length];
- for (int i = 0; i < selections.length; ++i) {
- selections[i] = selectionFactory.createTrackSelection(trackGroupArray.get(i), 0);
- }
- boolean retain[] = new boolean[trackGroupArray.length];
- boolean reset[] = new boolean[trackGroupArray.length];
- mStreams = new SampleStream[trackGroupArray.length];
- mMediaPeriod.selectTracks(selections, retain, mStreams, reset, 0);
- if (mTrackFormats == null) {
- int trackCount = trackGroupArray.length;
- mTrackMetEos = new boolean[trackCount];
- List<MediaFormat> trackFormats = new ArrayList<>();
- int videoTrackCount = 0;
- for (int i = 0; i < trackCount; i++) {
- Format format = trackGroupArray.get(i).getFormat(0);
- if (format.sampleMimeType.startsWith("video/")) {
- videoTrackCount++;
- mVideoTrackIndex = i;
- }
- trackFormats.add(convertFormat(format));
- }
- if (videoTrackCount > 1) {
- // Disable dropping samples when there are multiple video tracks.
- mVideoTrackIndex = INVALID_TRACK_INDEX;
- }
- mTrackFormats = trackFormats;
- List<String> ids = new ArrayList<>();
- for (int i = 0; i < mTrackFormats.size(); i++) {
- ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i));
- }
- try {
- mSampleBuffer.init(ids, mTrackFormats);
- } catch (IOException e) {
- // In this case, we will not schedule any further operation.
- // mExceptionOnPrepare will be notified to ExoPlayer, and ExoPlayer will
- // call release() eventually.
- mExceptionOnPrepare = e;
- return;
- }
- mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
- mPrepared = true;
- }
- }
-
- @Override
- public void onContinueLoadingRequested(MediaPeriod source) {
- source.continueLoading(mCurrentPosition);
- }
-
- @Override
- public boolean handleMessage(Message message) {
- switch (message.what) {
- case MSG_PREPARE:
- if (!mPrepareRequested) {
- mPrepareRequested = true;
- mMediaPeriod = mSampleSource.createPeriod(0,
- new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE), 0);
- mMediaPeriod.prepare(this);
- try {
- mMediaPeriod.maybeThrowPrepareError();
- } catch (IOException e) {
- mError = e;
- }
- }
- return true;
- case MSG_FETCH_SAMPLES:
- boolean didSomething = false;
- ConditionVariable conditionVariable = new ConditionVariable();
- int trackCount = mStreams.length;
- for (int i = 0; i < trackCount; ++i) {
- if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ
- != fetchSample(i, mSampleHolder, conditionVariable)) {
- if (mMetEos) {
- // If mMetEos was on during fetchSample() due to an error,
- // fetching from other tracks is not necessary.
- break;
- }
- didSomething = true;
- }
- }
- mMediaPeriod.continueLoading(mCurrentPosition);
- if (!mMetEos) {
- if (didSomething) {
- mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
- } else {
- mSourceReaderHandler.sendEmptyMessageDelayed(MSG_FETCH_SAMPLES,
- RETRY_INTERVAL_MS);
- }
- } else {
- notifyCompletionIfNeeded(false);
- }
- return true;
- case MSG_RELEASE:
- if (mMediaPeriod != null) {
- mSampleSource.releasePeriod(mMediaPeriod);
- mSampleSource.releaseSource();
- mMediaPeriod = null;
- }
- cleanUp();
- mSourceReaderHandler.removeCallbacksAndMessages(null);
- return true;
- }
- return false;
- }
-
- private int fetchSample(int track, SampleHolder sample,
- ConditionVariable conditionVariable) {
- FormatHolder dummyFormatHolder = new FormatHolder();
- mDecoderInputBuffer.clear();
- int ret = mStreams[track].readData(dummyFormatHolder, mDecoderInputBuffer);
- if (ret == C.RESULT_BUFFER_READ
- // Double-check if the extractor provided the data to prevent NPE. b/33758354
- && mDecoderInputBuffer.data != null) {
- if (mCurrentPosition < mDecoderInputBuffer.timeUs) {
- mCurrentPosition = mDecoderInputBuffer.timeUs;
- }
- try {
- Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track);
- if (lastExtractedPositionUs == null) {
- mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs);
- } else {
- mLastExtractedPositionUsMap.put(track,
- Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs));
- }
- queueSample(track, conditionVariable);
- } catch (IOException e) {
- mLastExtractedPositionUsMap.clear();
- mMetEos = true;
- mSampleBuffer.setEos();
- }
- } else if (ret == C.RESULT_END_OF_INPUT) {
- mTrackMetEos[track] = true;
- for (int i = 0; i < mTrackMetEos.length; ++i) {
- if (!mTrackMetEos[i]) {
- break;
- }
- if (i == mTrackMetEos.length - 1) {
- mMetEos = true;
- mSampleBuffer.setEos();
- }
- }
- }
- // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263
- return ret;
- }
-
- private void queueSample(int index, ConditionVariable conditionVariable)
- throws IOException {
- if (mVideoTrackIndex != INVALID_TRACK_INDEX) {
- if (!mVideoTrackMet) {
- if (index != mVideoTrackIndex) {
- SampleHolder sample =
- new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- mSampleHolder.flags =
- (mDecoderInputBuffer.isKeyFrame()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0)
- | (mDecoderInputBuffer.isDecodeOnly()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY
- : 0);
- sample.timeUs = mDecoderInputBuffer.timeUs;
- sample.size = mDecoderInputBuffer.data.position();
- sample.ensureSpaceForWrite(sample.size);
- mDecoderInputBuffer.flip();
- sample.data.position(0);
- sample.data.put(mDecoderInputBuffer.data);
- sample.data.flip();
- mPendingSamples.add(new Pair<>(index, sample));
- return;
- }
- mVideoTrackMet = true;
- mBaseSamplePts =
- mDecoderInputBuffer.timeUs
- - MpegTsDefaultAudioTrackRenderer
- .INITIAL_AUDIO_BUFFERING_TIME_US;
- for (Pair<Integer, SampleHolder> pair : mPendingSamples) {
- if (pair.second.timeUs >= mBaseSamplePts) {
- mSampleBuffer.writeSample(pair.first, pair.second, conditionVariable);
- }
- }
- mPendingSamples.clear();
- } else {
- if (mDecoderInputBuffer.timeUs < mBaseSamplePts
- && mVideoTrackIndex != index) {
- return;
- }
- }
- }
- // Copy the decoder input to the sample holder.
- mSampleHolder.clearData();
- mSampleHolder.flags =
- (mDecoderInputBuffer.isKeyFrame()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC : 0)
- | (mDecoderInputBuffer.isDecodeOnly()
- ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY : 0);
- mSampleHolder.timeUs = mDecoderInputBuffer.timeUs;
- mSampleHolder.size = mDecoderInputBuffer.data.position();
- mSampleHolder.ensureSpaceForWrite(mSampleHolder.size);
- mDecoderInputBuffer.flip();
- mSampleHolder.data.position(0);
- mSampleHolder.data.put(mDecoderInputBuffer.data);
- mSampleHolder.data.flip();
- long writeStartTimeNs = SystemClock.elapsedRealtimeNanos();
- mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable);
-
- // Checks whether the storage has enough bandwidth for recording samples.
- if (mSampleBuffer.isWriteSpeedSlow(mSampleHolder.size,
- SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
- mSampleBuffer.handleWriteSpeedSlow();
- }
- }
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mError != null) {
- IOException e = mError;
- mError = null;
- throw e;
- }
- }
-
- @Override
- public boolean prepare() throws IOException {
- if (!mSourceReaderThread.isAlive()) {
- mSourceReaderThread.start();
- mSourceReaderHandler = new Handler(mSourceReaderThread.getLooper(),
- mSourceReaderWorker);
- mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_PREPARE);
- }
- if (mExceptionOnPrepare != null) {
- throw mExceptionOnPrepare;
- }
- return mPrepared;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- outMediaFormatHolder.format = mTrackFormats.get(track);
- outMediaFormatHolder.drmInitData = null;
- }
-
- @Override
- public void selectTrack(int index) {
- mSampleBuffer.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- mSampleBuffer.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleBuffer.getBufferedPositionUs();
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleBuffer.continueBuffering(positionUs);
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleBuffer.seekTo(positionUs);
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- return mSampleBuffer.readSample(track, sampleHolder);
- }
-
- @Override
- public void release() {
- if (mSourceReaderThread.isAlive()) {
- mSourceReaderHandler.removeCallbacksAndMessages(null);
- mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_RELEASE);
- mSourceReaderThread.quitSafely();
- // Return early in this case so that session worker can start working on the next
- // request as early as it can. The clean up will be done in the reader thread while
- // handling MSG_RELEASE.
- } else {
- cleanUp();
- }
- }
-
- private void cleanUp() {
- boolean result = true;
- try {
- if (mSampleBuffer != null) {
- mSampleBuffer.release();
- mSampleBuffer = null;
- }
- } catch (IOException e) {
- result = false;
- }
- notifyCompletionIfNeeded(result);
- setOnCompletionListener(null, null);
- }
-
- private void notifyCompletionIfNeeded(final boolean result) {
- if (!mOnCompletionCalled.getAndSet(true)) {
- final OnCompletionListener listener = mOnCompletionListener;
- final long lastExtractedPositionUs = getLastExtractedPositionUs();
- if (mOnCompletionListenerHandler != null && mOnCompletionListener != null) {
- mOnCompletionListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- listener.onCompletion(result, lastExtractedPositionUs);
- }
- });
- }
- }
- }
-
- private long getLastExtractedPositionUs() {
- long lastExtractedPositionUs = Long.MIN_VALUE;
- for (Map.Entry<Integer, Long> entry : mLastExtractedPositionUsMap.entrySet()) {
- if (mVideoTrackIndex != entry.getKey()) {
- lastExtractedPositionUs = Math.max(lastExtractedPositionUs, entry.getValue());
- }
- }
- if (lastExtractedPositionUs == Long.MIN_VALUE) {
- lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US;
- }
- return lastExtractedPositionUs;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
deleted file mode 100644
index b7e42a7c..00000000
--- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java
+++ /dev/null
@@ -1,138 +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.exoplayer;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaFormatUtil;
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import android.os.Handler;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A class that plays a recorded stream without using {@link android.media.MediaExtractor},
- * since all samples are extracted and stored to the permanent storage already.
- */
-public class FileSampleExtractor implements SampleExtractor{
- private static final String TAG = "FileSampleExtractor";
- private static final boolean DEBUG = false;
-
- private int mTrackCount;
- private boolean mReleased;
-
- private final List<MediaFormat> mTrackFormats = new ArrayList<>();
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
- private BufferManager.SampleBuffer mSampleBuffer;
-
- public FileSampleExtractor(
- BufferManager bufferManager, PlaybackBufferListener bufferListener) {
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- mTrackCount = -1;
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- // Do nothing.
- }
-
- @Override
- public boolean prepare() throws IOException {
- List<BufferManager.TrackFormat> trackFormatList = mBufferManager.readTrackInfoFiles();
- if (trackFormatList == null || trackFormatList.isEmpty()) {
- throw new IOException("Cannot find meta files for the recording.");
- }
- mTrackCount = trackFormatList.size();
- List<String> ids = new ArrayList<>();
- mTrackFormats.clear();
- for (int i = 0; i < mTrackCount; ++i) {
- BufferManager.TrackFormat trackFormat = trackFormatList.get(i);
- ids.add(trackFormat.trackId);
- mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format));
- }
- mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true,
- RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK);
- mSampleBuffer.init(ids, mTrackFormats);
- return true;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- outMediaFormatHolder.format = mTrackFormats.get(track);
- outMediaFormatHolder.drmInitData = null;
- }
-
- @Override
- public void release() {
- if (!mReleased) {
- if (mSampleBuffer != null) {
- try {
- mSampleBuffer.release();
- } catch (IOException e) {
- // Do nothing. Playback ends now.
- }
- }
- }
- mReleased = true;
- }
-
- @Override
- public void selectTrack(int index) {
- mSampleBuffer.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- mSampleBuffer.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleBuffer.getBufferedPositionUs();
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleBuffer.seekTo(positionUs);
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- return mSampleBuffer.readSample(track, sampleHolder);
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleBuffer.continueBuffering(positionUs);
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
deleted file mode 100644
index 2694298a..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
+++ /dev/null
@@ -1,696 +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.exoplayer;
-
-import android.content.Context;
-import android.media.AudioFormat;
-import android.media.MediaCodec.CryptoException;
-import android.media.PlaybackParams;
-import android.os.Handler;
-import android.support.annotation.IntDef;
-import android.view.Surface;
-
-import com.google.android.exoplayer.DummyTrackRenderer;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.ExoPlayer;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.audio.MpegTsMediaCodecAudioTrackRenderer;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.TunerDebug;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** MPEG-2 TS stream player implementation using ExoPlayer. */
-public class MpegTsPlayer
- implements ExoPlayer.Listener,
- MediaCodecVideoTrackRenderer.EventListener,
- MpegTsDefaultAudioTrackRenderer.EventListener,
- MpegTsMediaCodecAudioTrackRenderer.Ac3EventListener {
- private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
-
- /**
- * Interface definition for building specific track renderers.
- */
- public interface RendererBuilder {
- void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource,
- boolean hasSoftwareAudioDecoder, RendererBuilderCallback callback);
- }
-
- /**
- * Interface definition for {@link RendererBuilder#buildRenderers} to notify the result.
- */
- public interface RendererBuilderCallback {
- void onRenderers(String[][] trackNames, TrackRenderer[] renderers);
- void onRenderersError(Exception e);
- }
-
- /**
- * Interface definition for a callback to be notified of changes in player state.
- */
- public interface Listener {
- void onStateChanged(boolean playWhenReady, int playbackState);
- void onError(Exception e);
- void onVideoSizeChanged(int width, int height,
- float pixelWidthHeightRatio);
- void onDrawnToSurface(MpegTsPlayer player, Surface surface);
- void onAudioUnplayable();
- void onSmoothTrickplayForceStopped();
- }
-
- /**
- * Interface definition for a callback to be notified of changes on video display.
- */
- public interface VideoEventListener {
- /**
- * Notifies the caption event.
- */
- void onEmitCaptionEvent(CaptionEvent event);
-
- /**
- * Notifies clearing up whole closed caption event.
- */
- void onClearCaptionEvent();
-
- /**
- * Notifies the discovered caption service number.
- */
- void onDiscoverCaptionServiceNumber(int serviceNumber);
- }
-
- public static final int RENDERER_COUNT = 3;
- public static final int MIN_BUFFER_MS = 0;
- public static final int MIN_REBUFFER_MS = 500;
-
- @IntDef({TRACK_TYPE_VIDEO, TRACK_TYPE_AUDIO, TRACK_TYPE_TEXT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface TrackType {}
- public static final int TRACK_TYPE_VIDEO = 0;
- public static final int TRACK_TYPE_AUDIO = 1;
- public static final int TRACK_TYPE_TEXT = 2;
-
- @IntDef({RENDERER_BUILDING_STATE_IDLE, RENDERER_BUILDING_STATE_BUILDING,
- RENDERER_BUILDING_STATE_BUILT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface RendererBuildingState {}
- private static final int RENDERER_BUILDING_STATE_IDLE = 1;
- private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
- private static final int RENDERER_BUILDING_STATE_BUILT = 3;
-
- private static final float MAX_SMOOTH_TRICKPLAY_SPEED = 9.0f;
- private static final float MIN_SMOOTH_TRICKPLAY_SPEED = 0.1f;
-
- private final RendererBuilder mRendererBuilder;
- private final ExoPlayer mPlayer;
- private final Handler mMainHandler;
- private final AudioCapabilities mAudioCapabilities;
- private final TsDataSourceManager mSourceManager;
-
- private Listener mListener;
- @RendererBuildingState private int mRendererBuildingState;
-
- private Surface mSurface;
- private TsDataSource mDataSource;
- private InternalRendererBuilderCallback mBuilderCallback;
- private TrackRenderer mVideoRenderer;
- private TrackRenderer mAudioRenderer;
- private Cea708TextTrackRenderer mTextRenderer;
- private final Cea708TextTrackRenderer.CcListener mCcListener;
- private VideoEventListener mVideoEventListener;
- private boolean mTrickplayRunning;
- private float mVolume;
-
- /**
- * Creates MPEG2-TS stream player.
- *
- * @param rendererBuilder the builder of track renderers
- * @param handler the handler for the playback events in track renderers
- * @param sourceManager the manager for {@link DataSource}
- * @param capabilities the {@link AudioCapabilities} of the current device
- * @param listener the listener for playback state changes
- */
- public MpegTsPlayer(RendererBuilder rendererBuilder, Handler handler,
- TsDataSourceManager sourceManager, AudioCapabilities capabilities,
- Listener listener) {
- mRendererBuilder = rendererBuilder;
- mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS);
- mPlayer.addListener(this);
- mMainHandler = handler;
- mAudioCapabilities = capabilities;
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- mCcListener = new MpegTsCcListener();
- mSourceManager = sourceManager;
- mListener = listener;
- }
-
- /**
- * Sets the video event listener.
- *
- * @param videoEventListener the listener for video events
- */
- public void setVideoEventListener(VideoEventListener videoEventListener) {
- mVideoEventListener = videoEventListener;
- }
-
- /**
- * Sets the closed caption service number.
- *
- * @param captionServiceNumber the service number of CEA-708 closed caption
- */
- public void setCaptionServiceNumber(int captionServiceNumber) {
- mCaptionServiceNumber = captionServiceNumber;
- if (mTextRenderer != null) {
- mPlayer.sendMessage(mTextRenderer,
- Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
- }
- }
-
- /**
- * Sets the surface for the player.
- *
- * @param surface the {@link Surface} to render video
- */
- public void setSurface(Surface surface) {
- mSurface = surface;
- pushSurface(false);
- }
-
- /**
- * Returns the current surface of the player.
- */
- public Surface getSurface() {
- return mSurface;
- }
-
- /**
- * Clears the surface and waits until the surface is being cleaned.
- */
- public void blockingClearSurface() {
- mSurface = null;
- pushSurface(true);
- }
-
- /**
- * Creates renderers and {@link DataSource} and initializes player.
- * @param context a {@link Context} instance
- * @param channel to play
- * @param hasSoftwareAudioDecoder {@code true} if there is connected software decoder
- * @param eventListener for program information which will be scanned from MPEG2-TS stream
- * @return true when everything is created and initialized well, false otherwise
- */
- public boolean prepare(Context context, TunerChannel channel, boolean hasSoftwareAudioDecoder,
- EventDetector.EventListener eventListener) {
- TsDataSource source = null;
- if (channel != null) {
- source = mSourceManager.createDataSource(context, channel, eventListener);
- if (source == null) {
- return false;
- }
- }
- mDataSource = source;
- if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
- mPlayer.stop();
- }
- if (mBuilderCallback != null) {
- mBuilderCallback.cancel();
- }
- mRendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
- mBuilderCallback = new InternalRendererBuilderCallback();
- mRendererBuilder.buildRenderers(this, source, hasSoftwareAudioDecoder, mBuilderCallback);
- return true;
- }
-
- /**
- * Returns {@link TsDataSource} which provides MPEG2-TS stream.
- */
- public TsDataSource getDataSource() {
- return mDataSource;
- }
-
- private void onRenderers(TrackRenderer[] renderers) {
- mBuilderCallback = null;
- for (int i = 0; i < RENDERER_COUNT; i++) {
- if (renderers[i] == null) {
- // Convert a null renderer to a dummy renderer.
- renderers[i] = new DummyTrackRenderer();
- }
- }
- mVideoRenderer = renderers[TRACK_TYPE_VIDEO];
- mAudioRenderer = renderers[TRACK_TYPE_AUDIO];
- mTextRenderer = (Cea708TextTrackRenderer) renderers[TRACK_TYPE_TEXT];
- mTextRenderer.setCcListener(mCcListener);
- mPlayer.sendMessage(
- mTextRenderer, Cea708TextTrackRenderer.MSG_SERVICE_NUMBER, mCaptionServiceNumber);
- mRendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
- pushSurface(false);
- mPlayer.prepare(renderers);
- pushTrackSelection(TRACK_TYPE_VIDEO, true);
- pushTrackSelection(TRACK_TYPE_AUDIO, true);
- pushTrackSelection(TRACK_TYPE_TEXT, true);
- }
-
- private void onRenderersError(Exception e) {
- mBuilderCallback = null;
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- if (mListener != null) {
- mListener.onError(e);
- }
- }
-
- /**
- * Sets the player state to pause or play.
- *
- * @param playWhenReady sets the player state to being ready to play when {@code true},
- * sets the player state to being paused when {@code false}
- *
- */
- public void setPlayWhenReady(boolean playWhenReady) {
- mPlayer.setPlayWhenReady(playWhenReady);
- stopSmoothTrickplay(false);
- }
-
- /**
- * Returns true, if trickplay is supported.
- */
- public boolean supportSmoothTrickPlay(float playbackSpeed) {
- return playbackSpeed > MIN_SMOOTH_TRICKPLAY_SPEED
- && playbackSpeed < MAX_SMOOTH_TRICKPLAY_SPEED;
- }
-
- /**
- * Starts trickplay. It'll be reset, if {@link #seekTo} or {@link #setPlayWhenReady} is called.
- */
- public void startSmoothTrickplay(PlaybackParams playbackParams) {
- SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed()));
- mPlayer.setPlayWhenReady(true);
- mTrickplayRunning = true;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer,
- MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED,
- playbackParams.getSpeed());
- } else {
- mPlayer.sendMessage(mAudioRenderer,
- MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS,
- playbackParams);
- }
- }
-
- private void stopSmoothTrickplay(boolean calledBySeek) {
- if (mTrickplayRunning) {
- mTrickplayRunning = false;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(
- mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_PLAYBACK_SPEED,
- 1.0f);
- } else {
- mPlayer.sendMessage(mAudioRenderer,
- MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS,
- new PlaybackParams().setSpeed(1.0f));
- }
- if (!calledBySeek) {
- mPlayer.seekTo(mPlayer.getCurrentPosition());
- }
- }
- }
-
- /**
- * Seeks to the specified position of the current playback.
- *
- * @param positionMs the specified position in milli seconds.
- */
- public void seekTo(long positionMs) {
- mPlayer.seekTo(positionMs);
- stopSmoothTrickplay(true);
- }
-
- /**
- * Releases the player.
- */
- public void release() {
- if (mDataSource != null) {
- mSourceManager.releaseDataSource(mDataSource);
- mDataSource = null;
- }
- if (mBuilderCallback != null) {
- mBuilderCallback.cancel();
- mBuilderCallback = null;
- }
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- mSurface = null;
- mListener = null;
- mPlayer.release();
- }
-
- /**
- * Returns the current status of the player.
- */
- public int getPlaybackState() {
- if (mRendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
- return ExoPlayer.STATE_PREPARING;
- }
- return mPlayer.getPlaybackState();
- }
-
- /**
- * Returns {@code true} when the player is prepared to play, {@code false} otherwise.
- */
- public boolean isPrepared() {
- int state = getPlaybackState();
- return state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING;
- }
-
- /**
- * Returns {@code true} when the player is being ready to play, {@code false} otherwise.
- */
- public boolean isPlaying() {
- int state = getPlaybackState();
- return (state == ExoPlayer.STATE_READY || state == ExoPlayer.STATE_BUFFERING)
- && mPlayer.getPlayWhenReady();
- }
-
- /**
- * Returns {@code true} when the player is buffering, {@code false} otherwise.
- */
- public boolean isBuffering() {
- return getPlaybackState() == ExoPlayer.STATE_BUFFERING;
- }
-
- /**
- * Returns the current position of the playback in milli seconds.
- */
- public long getCurrentPosition() {
- return mPlayer.getCurrentPosition();
- }
-
- /**
- * Returns the total duration of the playback.
- */
- public long getDuration() {
- return mPlayer.getDuration();
- }
-
- /**
- * Returns {@code true} when the player is being ready to play,
- * {@code false} when the player is paused.
- */
- public boolean getPlayWhenReady() {
- return mPlayer.getPlayWhenReady();
- }
-
- /**
- * Sets the volume of the audio.
- *
- * @param volume see also {@link AudioTrack#setVolume(float)}
- */
- public void setVolume(float volume) {
- mVolume = volume;
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_VOLUME,
- volume);
- } else {
- mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
- volume);
- }
- }
-
- /**
- * Enables or disables audio and closed caption.
- *
- * @param enable enables the audio and closed caption when {@code true}, disables otherwise.
- */
- public void setAudioTrackAndClosedCaption(boolean enable) {
- if (mAudioRenderer instanceof MpegTsDefaultAudioTrackRenderer) {
- mPlayer.sendMessage(mAudioRenderer, MpegTsDefaultAudioTrackRenderer.MSG_SET_AUDIO_TRACK,
- enable ? 1 : 0);
- } else {
- mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
- enable ? mVolume : 0.0f);
- }
- mPlayer.sendMessage(mTextRenderer, Cea708TextTrackRenderer.MSG_ENABLE_CLOSED_CAPTION,
- enable);
- }
-
- /**
- * Returns {@code true} when AC3 audio can be played, {@code false} otherwise.
- */
- public boolean isAc3Playable() {
- return mAudioCapabilities != null
- && mAudioCapabilities.supportsEncoding(AudioFormat.ENCODING_AC3);
- }
-
- /**
- * Notifies when the audio cannot be played by the current device.
- */
- public void onAudioUnplayable() {
- if (mListener != null) {
- mListener.onAudioUnplayable();
- }
- }
-
- /**
- * Returns {@code true} if the player has any video track, {@code false} otherwise.
- */
- public boolean hasVideo() {
- return mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0;
- }
-
- /**
- * Returns {@code true} if the player has any audio trock, {@code false} otherwise.
- */
- public boolean hasAudio() {
- return mPlayer.getTrackCount(TRACK_TYPE_AUDIO) > 0;
- }
-
- /**
- * Returns the number of tracks exposed by the specified renderer.
- */
- public int getTrackCount(int rendererIndex) {
- return mPlayer.getTrackCount(rendererIndex);
- }
-
- /**
- * Selects a track for the specified renderer.
- */
- public void setSelectedTrack(int rendererIndex, int trackIndex) {
- if (trackIndex >= getTrackCount(rendererIndex)) {
- return;
- }
- mPlayer.setSelectedTrack(rendererIndex, trackIndex);
- }
-
- /**
- * Returns the index of the currently selected track for the specified renderer.
- *
- * @param rendererIndex The index of the renderer.
- * @return The selected track. A negative value or a value greater than or equal to the renderer's
- * track count indicates that the renderer is disabled.
- */
- public int getSelectedTrack(int rendererIndex) {
- return mPlayer.getSelectedTrack(rendererIndex);
- }
-
- /**
- * Returns the format of a track.
- *
- * @param rendererIndex The index of the renderer.
- * @param trackIndex The index of the track.
- * @return The format of the track.
- */
- public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
- return mPlayer.getTrackFormat(rendererIndex, trackIndex);
- }
-
- /**
- * Gets the main handler of the player.
- */
- /* package */ Handler getMainHandler() {
- return mMainHandler;
- }
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, int state) {
- if (mListener == null) {
- return;
- }
- mListener.onStateChanged(playWhenReady, state);
- if (state == ExoPlayer.STATE_READY && mPlayer.getTrackCount(TRACK_TYPE_VIDEO) > 0
- && playWhenReady) {
- MediaFormat format = mPlayer.getTrackFormat(TRACK_TYPE_VIDEO, 0);
- mListener.onVideoSizeChanged(format.width,
- format.height, format.pixelWidthHeightRatio);
- }
- }
-
- @Override
- public void onPlayerError(ExoPlaybackException exception) {
- mRendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
- if (mListener != null) {
- mListener.onError(exception);
- }
- }
-
- @Override
- public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
- float pixelWidthHeightRatio) {
- if (mListener != null) {
- mListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio);
- }
- }
-
- @Override
- public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
- long initializationDurationMs) {
- // Do nothing.
- }
-
- @Override
- public void onDecoderInitializationError(DecoderInitializationException e) {
- // Do nothing.
- }
-
- @Override
- public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
- if (mListener != null) {
- mListener.onAudioUnplayable();
- }
- }
-
- @Override
- public void onAudioTrackWriteError(AudioTrack.WriteException e) {
- // Do nothing.
- }
-
- @Override
- public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs,
- long elapsedSinceLastFeedMs) {
- // Do nothing.
- }
-
- @Override
- public void onCryptoError(CryptoException e) {
- // Do nothing.
- }
-
- @Override
- public void onPlayWhenReadyCommitted() {
- // Do nothing.
- }
-
- @Override
- public void onDrawnToSurface(Surface surface) {
- if (mListener != null) {
- mListener.onDrawnToSurface(this, surface);
- }
- }
-
- @Override
- public void onDroppedFrames(int count, long elapsed) {
- TunerDebug.notifyVideoFrameDrop(count, elapsed);
- if (mTrickplayRunning && mListener != null) {
- mListener.onSmoothTrickplayForceStopped();
- }
- }
-
- @Override
- public void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e) {
- if (mTrickplayRunning && mListener != null) {
- mListener.onSmoothTrickplayForceStopped();
- }
- }
-
- private void pushSurface(boolean blockForSurfacePush) {
- if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
- return;
- }
-
- if (blockForSurfacePush) {
- mPlayer.blockingSendMessage(
- mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
- } else {
- mPlayer.sendMessage(
- mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, mSurface);
- }
- }
-
- private void pushTrackSelection(@TrackType int type, boolean allowRendererEnable) {
- if (mRendererBuildingState != RENDERER_BUILDING_STATE_BUILT) {
- return;
- }
- mPlayer.setSelectedTrack(type, allowRendererEnable ? 0 : -1);
- }
-
- private class MpegTsCcListener implements Cea708TextTrackRenderer.CcListener {
-
- @Override
- public void emitEvent(CaptionEvent captionEvent) {
- if (mVideoEventListener != null) {
- mVideoEventListener.onEmitCaptionEvent(captionEvent);
- }
- }
-
- @Override
- public void clearCaption() {
- if (mVideoEventListener != null) {
- mVideoEventListener.onClearCaptionEvent();
- }
- }
-
- @Override
- public void discoverServiceNumber(int serviceNumber) {
- if (mVideoEventListener != null) {
- mVideoEventListener.onDiscoverCaptionServiceNumber(serviceNumber);
- }
- }
- }
-
- private class InternalRendererBuilderCallback implements RendererBuilderCallback {
- private boolean canceled;
-
- public void cancel() {
- canceled = true;
- }
-
- @Override
- public void onRenderers(String[][] trackNames, TrackRenderer[] renderers) {
- if (!canceled) {
- MpegTsPlayer.this.onRenderers(renderers);
- }
- }
-
- @Override
- public void onRenderersError(Exception e) {
- if (!canceled) {
- MpegTsPlayer.this.onRenderersError(e);
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
deleted file mode 100644
index 006ccac2..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java
+++ /dev/null
@@ -1,75 +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.exoplayer;
-
-import android.content.Context;
-
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.android.tv.Features;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilder;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer.RendererBuilderCallback;
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-/**
- * Builder for renderer objects for {@link MpegTsPlayer}.
- */
-public class MpegTsRendererBuilder implements RendererBuilder {
- private final Context mContext;
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
-
- public MpegTsRendererBuilder(Context context, BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mContext = context;
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- }
-
- @Override
- public void buildRenderers(MpegTsPlayer mpegTsPlayer, DataSource dataSource,
- boolean mHasSoftwareAudioDecoder, RendererBuilderCallback callback) {
- // Build the video and audio renderers.
- SampleExtractor extractor = dataSource == null ?
- new MpegTsSampleExtractor(mBufferManager, mBufferListener) :
- new MpegTsSampleExtractor(dataSource, mBufferManager, mBufferListener);
- SampleSource sampleSource = new MpegTsSampleSource(extractor);
- MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext,
- sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer);
- // TODO: Only using MpegTsDefaultAudioTrackRenderer for A/V sync issue. We will use
- // {@link MpegTsMediaCodecAudioTrackRenderer} when we use ExoPlayer's extractor.
- TrackRenderer audioRenderer =
- new MpegTsDefaultAudioTrackRenderer(
- sampleSource,
- MediaCodecSelector.DEFAULT,
- mpegTsPlayer.getMainHandler(),
- mpegTsPlayer,
- mHasSoftwareAudioDecoder,
- !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext));
- Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource);
-
- TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT];
- renderers[MpegTsPlayer.TRACK_TYPE_VIDEO] = videoRenderer;
- renderers[MpegTsPlayer.TRACK_TYPE_AUDIO] = audioRenderer;
- renderers[MpegTsPlayer.TRACK_TYPE_TEXT] = textRenderer;
- callback.onRenderers(null, renderers);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
deleted file mode 100644
index 7bf116c8..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleExtractor.java
+++ /dev/null
@@ -1,335 +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.exoplayer;
-
-import android.net.Uri;
-import android.os.Handler;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.upstream.DataSource;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.SamplePool;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Extracts samples from {@link DataSource} for MPEG-TS streams.
- */
-public final class MpegTsSampleExtractor implements SampleExtractor {
- public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
-
- private static final int CC_BUFFER_SIZE_IN_BYTES = 9600 / 8;
-
- private final SampleExtractor mSampleExtractor;
- private final List<MediaFormat> mTrackFormats = new ArrayList<>();
- private final List<Boolean> mReachedEos = new ArrayList<>();
- private int mVideoTrackIndex;
- private final SamplePool mCcSamplePool = new SamplePool();
- private final List<SampleHolder> mPendingCcSamples = new LinkedList<>();
-
- private int mCea708TextTrackIndex;
- private boolean mCea708TextTrackSelected;
-
- private CcParser mCcParser;
-
- private void init() {
- mVideoTrackIndex = -1;
- mCea708TextTrackIndex = -1;
- mCea708TextTrackSelected = false;
- }
-
- /**
- * Creates MpegTsSampleExtractor for {@link DataSource}.
- *
- * @param source the {@link DataSource} to extract from
- * @param bufferManager the manager for reading & writing samples backed by physical storage
- * @param bufferListener the {@link PlaybackBufferListener}
- * to notify buffer storage status change
- */
- public MpegTsSampleExtractor(DataSource source, BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mSampleExtractor = new ExoPlayerSampleExtractor(Uri.EMPTY, source, bufferManager,
- bufferListener, false);
- init();
- }
-
- /**
- * Creates MpegTsSampleExtractor for a recorded program.
- *
- * @param bufferManager the samples provider which is stored in physical storage
- * @param bufferListener the {@link PlaybackBufferListener}
- * to notify buffer storage status change
- */
- public MpegTsSampleExtractor(BufferManager bufferManager,
- PlaybackBufferListener bufferListener) {
- mSampleExtractor = new FileSampleExtractor(bufferManager, bufferListener);
- init();
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mSampleExtractor != null) {
- mSampleExtractor.maybeThrowError();
- }
- }
-
- @Override
- public boolean prepare() throws IOException {
- if(!mSampleExtractor.prepare()) {
- return false;
- }
- List<MediaFormat> formats = mSampleExtractor.getTrackFormats();
- int trackCount = formats.size();
- mTrackFormats.clear();
- mReachedEos.clear();
-
- for (int i = 0; i < trackCount; ++i) {
- mTrackFormats.add(formats.get(i));
- mReachedEos.add(false);
- String mime = formats.get(i).mimeType;
- if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) {
- mVideoTrackIndex = i;
- if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) {
- mCcParser = new Mpeg2CcParser();
- } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
- mCcParser = new H264CcParser();
- }
- }
- }
-
- if (mVideoTrackIndex != -1) {
- mCea708TextTrackIndex = trackCount;
- }
- if (mCea708TextTrackIndex >= 0) {
- mTrackFormats.add(MediaFormat.createTextFormat(null, MIMETYPE_TEXT_CEA_708, 0,
- mTrackFormats.get(0).durationUs, ""));
- }
- return true;
- }
-
- @Override
- public List<MediaFormat> getTrackFormats() {
- return mTrackFormats;
- }
-
- @Override
- public void selectTrack(int index) {
- if (index == mCea708TextTrackIndex) {
- mCea708TextTrackSelected = true;
- return;
- }
- mSampleExtractor.selectTrack(index);
- }
-
- @Override
- public void deselectTrack(int index) {
- if (index == mCea708TextTrackIndex) {
- mCea708TextTrackSelected = false;
- return;
- }
- mSampleExtractor.deselectTrack(index);
- }
-
- @Override
- public long getBufferedPositionUs() {
- return mSampleExtractor.getBufferedPositionUs();
- }
-
- @Override
- public void seekTo(long positionUs) {
- mSampleExtractor.seekTo(positionUs);
- for (SampleHolder holder : mPendingCcSamples) {
- mCcSamplePool.releaseSample(holder);
- }
- mPendingCcSamples.clear();
- }
-
- @Override
- public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
- if (track != mCea708TextTrackIndex) {
- mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
- }
- }
-
- @Override
- public int readSample(int track, SampleHolder sampleHolder) {
- if (track == mCea708TextTrackIndex) {
- if (mCea708TextTrackSelected && !mPendingCcSamples.isEmpty()) {
- SampleHolder holder = mPendingCcSamples.remove(0);
- holder.data.flip();
- sampleHolder.timeUs = holder.timeUs;
- sampleHolder.data.put(holder.data);
- mCcSamplePool.releaseSample(holder);
- return SampleSource.SAMPLE_READ;
- } else {
- return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex)
- ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ;
- }
- }
-
- int result = mSampleExtractor.readSample(track, sampleHolder);
- switch (result) {
- case SampleSource.END_OF_STREAM: {
- mReachedEos.set(track, true);
- break;
- }
- case SampleSource.SAMPLE_READ: {
- if (mCea708TextTrackSelected && track == mVideoTrackIndex
- && sampleHolder.data != null) {
- mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs);
- }
- break;
- }
- }
- return result;
- }
-
- @Override
- public void release() {
- mSampleExtractor.release();
- mVideoTrackIndex = -1;
- mCea708TextTrackIndex = -1;
- mCea708TextTrackSelected = false;
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- return mSampleExtractor.continueBuffering(positionUs);
- }
-
- @Override
- public void setOnCompletionListener(OnCompletionListener listener, Handler handler) { }
-
- private abstract class CcParser {
- // Interim buffer for reduce direct access to ByteBuffer which is expensive. Using
- // relatively small buffer size in order to minimize memory footprint increase.
- protected final byte[] mBuffer = new byte[1024];
-
- abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs);
-
- protected int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) {
- // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9.
- int pos = offset;
- if (pos + 2 >= buffer.position()) {
- return offset;
- }
- boolean processCcDataFlag = (buffer.get(pos) & 64) != 0;
- int ccCount = buffer.get(pos) & 0x1f;
- pos += 2;
- if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) {
- return offset;
- }
- SampleHolder holder = mCcSamplePool.acquireSample(CC_BUFFER_SIZE_IN_BYTES);
- for (int i = 0; i < 3 * ccCount; i++) {
- holder.data.put(buffer.get(pos++));
- }
- holder.timeUs = presentationTimeUs;
- mPendingCcSamples.add(holder);
- return pos;
- }
- }
-
- private class Mpeg2CcParser extends CcParser {
- private static final int PATTERN_LENGTH = 9;
-
- @Override
- public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
- int totalSize = buffer.position();
- // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with
- // overlapping to handle the case that the pattern exists in the boundary.
- for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) {
- buffer.position(i);
- int size = Math.min(totalSize - i, mBuffer.length);
- buffer.get(mBuffer, 0, size);
- int j = 0;
- while (j < size - PATTERN_LENGTH) {
- // Find the start prefix code of private user data.
- if (mBuffer[j] == 0
- && mBuffer[j + 1] == 0
- && mBuffer[j + 2] == 1
- && (mBuffer[j + 3] & 0xff) == 0xb2) {
- // ATSC closed caption data embedded in MPEG2VIDEO stream has 'GA94' user
- // identifier and user data type code 3.
- if (mBuffer[j + 4] == 'G'
- && mBuffer[j + 5] == 'A'
- && mBuffer[j + 6] == '9'
- && mBuffer[j + 7] == '4'
- && mBuffer[j + 8] == 3) {
- j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH,
- presentationTimeUs) - i;
- } else {
- j += PATTERN_LENGTH;
- }
- } else {
- ++j;
- }
- }
- }
- buffer.position(totalSize);
- }
- }
-
- private class H264CcParser extends CcParser {
- private static final int PATTERN_LENGTH = 14;
-
- @Override
- public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
- int totalSize = buffer.position();
- // Reading the frame in bulk to reduce the overhead from ByteBuffer.get() with
- // overlapping to handle the case that the pattern exists in the boundary.
- for (int i = 0; i < totalSize; i += mBuffer.length - PATTERN_LENGTH) {
- buffer.position(i);
- int size = Math.min(totalSize - i, mBuffer.length);
- buffer.get(mBuffer, 0, size);
- int j = 0;
- while (j < size - PATTERN_LENGTH) {
- // Find the start prefix code of a NAL Unit.
- if (mBuffer[j] == 0
- && mBuffer[j + 1] == 0
- && mBuffer[j + 2] == 1) {
- int nalType = mBuffer[j + 3] & 0x1f;
- int payloadType = mBuffer[j + 4] & 0xff;
-
- // ATSC closed caption data embedded in H264 private user data has NAL type
- // 6, payload type 4, and 'GA94' user identifier for ATSC.
- if (nalType == 6 && payloadType == 4 && mBuffer[j + 9] == 'G'
- && mBuffer[j + 10] == 'A'
- && mBuffer[j + 11] == '9'
- && mBuffer[j + 12] == '4') {
- j = parseClosedCaption(buffer, i + j + PATTERN_LENGTH,
- presentationTimeUs) - i;
- } else {
- j += 7;
- }
- } else {
- ++j;
- }
- }
- }
- buffer.position(totalSize);
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java b/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java
deleted file mode 100644
index 6007b0be..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsSampleSource.java
+++ /dev/null
@@ -1,196 +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.exoplayer;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.SampleSource.SampleSourceReader;
-import com.google.android.exoplayer.util.Assertions;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-/** {@link SampleSource} that extracts sample data using a {@link SampleExtractor}. */
-public final class MpegTsSampleSource implements SampleSource, SampleSourceReader {
-
- private static final int TRACK_STATE_DISABLED = 0;
- private static final int TRACK_STATE_ENABLED = 1;
- private static final int TRACK_STATE_FORMAT_SENT = 2;
-
- private final SampleExtractor mSampleExtractor;
- private final List<Integer> mTrackStates = new ArrayList<>();
- private final List<Boolean> mPendingDiscontinuities = new ArrayList<>();
-
- private boolean mPrepared;
- private IOException mPreparationError;
- private int mRemainingReleaseCount;
-
- private long mLastSeekPositionUs;
- private long mPendingSeekPositionUs;
-
- /**
- * Creates a new sample source that extracts samples using {@code mSampleExtractor}.
- *
- * @param sampleExtractor a sample extractor for accessing media samples
- */
- public MpegTsSampleSource(SampleExtractor sampleExtractor) {
- mSampleExtractor = Assertions.checkNotNull(sampleExtractor);
- }
-
- @Override
- public SampleSourceReader register() {
- mRemainingReleaseCount++;
- return this;
- }
-
- @Override
- public boolean prepare(long positionUs) {
- if (!mPrepared) {
- if (mPreparationError != null) {
- return false;
- }
- try {
- if (mSampleExtractor.prepare()) {
- int trackCount = mSampleExtractor.getTrackFormats().size();
- mTrackStates.clear();
- mPendingDiscontinuities.clear();
- for (int i = 0; i < trackCount; ++i) {
- mTrackStates.add(i, TRACK_STATE_DISABLED);
- mPendingDiscontinuities.add(i, false);
- }
- mPrepared = true;
- } else {
- return false;
- }
- } catch (IOException e) {
- mPreparationError = e;
- return false;
- }
- }
- return true;
- }
-
- @Override
- public int getTrackCount() {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getTrackFormats().size();
- }
-
- @Override
- public MediaFormat getFormat(int track) {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getTrackFormats().get(track);
- }
-
- @Override
- public void enable(int track, long positionUs) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED);
- mTrackStates.set(track, TRACK_STATE_ENABLED);
- mSampleExtractor.selectTrack(track);
- seekToUsInternal(positionUs, positionUs != 0);
- }
-
- @Override
- public void disable(int track) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
- mSampleExtractor.deselectTrack(track);
- mPendingDiscontinuities.set(track, false);
- mTrackStates.set(track, TRACK_STATE_DISABLED);
- }
-
- @Override
- public boolean continueBuffering(int track, long positionUs) {
- return mSampleExtractor.continueBuffering(positionUs);
- }
-
- @Override
- public long readDiscontinuity(int track) {
- if (mPendingDiscontinuities.get(track)) {
- mPendingDiscontinuities.set(track, false);
- return mLastSeekPositionUs;
- }
- return NO_DISCONTINUITY;
- }
-
- @Override
- public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
- SampleHolder sampleHolder) {
- Assertions.checkState(mPrepared);
- Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
- if (mPendingDiscontinuities.get(track)) {
- return NOTHING_READ;
- }
- if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) {
- mSampleExtractor.getTrackMediaFormat(track, formatHolder);
- mTrackStates.set(track, TRACK_STATE_FORMAT_SENT);
- return FORMAT_READ;
- }
-
- mPendingSeekPositionUs = C.UNKNOWN_TIME_US;
- return mSampleExtractor.readSample(track, sampleHolder);
- }
-
- @Override
- public void maybeThrowError() throws IOException {
- if (mPreparationError != null) {
- throw mPreparationError;
- }
- if (mSampleExtractor != null) {
- mSampleExtractor.maybeThrowError();
- }
- }
-
- @Override
- public void seekToUs(long positionUs) {
- Assertions.checkState(mPrepared);
- seekToUsInternal(positionUs, false);
- }
-
- @Override
- public long getBufferedPositionUs() {
- Assertions.checkState(mPrepared);
- return mSampleExtractor.getBufferedPositionUs();
- }
-
- @Override
- public void release() {
- Assertions.checkState(mRemainingReleaseCount > 0);
- if (--mRemainingReleaseCount == 0) {
- mSampleExtractor.release();
- }
- }
-
- private void seekToUsInternal(long positionUs, boolean force) {
- // Unless forced, avoid duplicate calls to the underlying extractor's seek method
- // in the case that there have been no interleaving calls to readSample.
- if (force || mPendingSeekPositionUs != positionUs) {
- mLastSeekPositionUs = positionUs;
- mPendingSeekPositionUs = positionUs;
- mSampleExtractor.seekTo(positionUs);
- for (int i = 0; i < mTrackStates.size(); ++i) {
- if (mTrackStates.get(i) != TRACK_STATE_DISABLED) {
- mPendingDiscontinuities.set(i, true);
- }
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java
deleted file mode 100644
index 19360c69..00000000
--- a/src/com/android/tv/tuner/exoplayer/MpegTsVideoTrackRenderer.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.android.tv.tuner.exoplayer;
-
-import android.content.Context;
-import android.media.MediaCodec;
-import android.os.Handler;
-import android.util.Log;
-
-import com.google.android.exoplayer.DecoderInfo;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaSoftwareCodecUtil;
-import com.google.android.exoplayer.SampleSource;
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.lang.reflect.Field;
-
-/**
- * MPEG-2 TS video track renderer
- */
-public class MpegTsVideoTrackRenderer extends MediaCodecVideoTrackRenderer {
- private static final String TAG = "MpegTsVideoTrackRender";
-
- private static final int VIDEO_PLAYBACK_DEADLINE_IN_MS = 5000;
- // If DROPPED_FRAMES_NOTIFICATION_THRESHOLD frames are consecutively dropped, it'll be notified.
- private static final int DROPPED_FRAMES_NOTIFICATION_THRESHOLD = 10;
- private static final int MIN_HD_HEIGHT = 720;
- private static final String MIMETYPE_MPEG2 = "video/mpeg2";
- private static Field sRenderedFirstFrameField;
-
- private final boolean mIsSwCodecEnabled;
- private boolean mCodecIsSwPreferred;
- private boolean mSetRenderedFirstFrame;
-
- static {
- // Remove the reflection below once b/31223646 is resolved.
- try {
- sRenderedFirstFrameField = MediaCodecVideoTrackRenderer.class.getDeclaredField(
- "renderedFirstFrame");
- sRenderedFirstFrameField.setAccessible(true);
- } catch (NoSuchFieldException e) {
- // Null-checking for {@code sRenderedFirstFrameField} will do the error handling.
- }
- }
-
- public MpegTsVideoTrackRenderer(Context context, SampleSource source, Handler handler,
- MediaCodecVideoTrackRenderer.EventListener listener) {
- super(context, source, MediaCodecSelector.DEFAULT,
- MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_PLAYBACK_DEADLINE_IN_MS, handler,
- listener, DROPPED_FRAMES_NOTIFICATION_THRESHOLD);
- mIsSwCodecEnabled = CommonFeatures.USE_SW_CODEC_FOR_SD.isEnabled(context);
- }
-
- @Override
- protected DecoderInfo getDecoderInfo(MediaCodecSelector codecSelector, String mimeType,
- boolean requiresSecureDecoder) throws MediaCodecUtil.DecoderQueryException {
- try {
- if (mIsSwCodecEnabled && mCodecIsSwPreferred) {
- DecoderInfo swCodec = MediaSoftwareCodecUtil.getSoftwareDecoderInfo(
- mimeType, requiresSecureDecoder);
- if (swCodec != null) {
- return swCodec;
- }
- }
- } catch (MediaSoftwareCodecUtil.DecoderQueryException e) {
- }
- return super.getDecoderInfo(codecSelector, mimeType,requiresSecureDecoder);
- }
-
- @Override
- protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
- mCodecIsSwPreferred = MIMETYPE_MPEG2.equalsIgnoreCase(holder.format.mimeType)
- && holder.format.height < MIN_HD_HEIGHT;
- super.onInputFormatChanged(holder);
- }
-
- @Override
- protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
- super.onDiscontinuity(positionUs);
- // Disabling pre-rendering of the first frame in order to avoid a frozen picture when
- // starting the playback. We do this only once, when the renderer is enabled at first, since
- // we need to pre-render the frame in advance when we do trickplay backed by seeking.
- if (!mSetRenderedFirstFrame) {
- setRenderedFirstFrame(true);
- mSetRenderedFirstFrame = true;
- }
- }
-
- private void setRenderedFirstFrame(boolean renderedFirstFrame) {
- if (sRenderedFirstFrameField != null) {
- try {
- sRenderedFirstFrameField.setBoolean(this, renderedFirstFrame);
- } catch (IllegalAccessException e) {
- Log.w(TAG, "renderedFirstFrame is not accessible. Playback may start with a frozen"
- +" picture.");
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java b/src/com/android/tv/tuner/exoplayer/SampleExtractor.java
deleted file mode 100644
index 543588c7..00000000
--- a/src/com/android/tv/tuner/exoplayer/SampleExtractor.java
+++ /dev/null
@@ -1,136 +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.exoplayer;
-
-import android.os.Handler;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Extractor for reading track metadata and samples stored in tracks.
- *
- * <p>Call {@link #prepare} until it returns {@code true}, then access track metadata via
- * {@link #getTrackFormats} and {@link #getTrackMediaFormat}.
- *
- * <p>Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected
- * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample
- * data or seeking. Initially, all tracks are deselected.
- *
- * <p>Call {@link #release()} when the extractor is no longer needed to free resources.
- */
-public interface SampleExtractor {
-
- /**
- * If the extractor is currently having difficulty preparing or loading samples, then this
- * method throws the underlying error. Otherwise does nothing.
- *
- * @throws IOException The underlying error.
- */
- void maybeThrowError() throws IOException;
-
- /**
- * Prepares the extractor for reading track metadata and samples.
- *
- * @return whether the source is ready; if {@code false}, this method must be called again.
- * @throws IOException thrown if the source can't be read
- */
- boolean prepare() throws IOException;
-
- /** Returns track information about all tracks that can be selected. */
- List<MediaFormat> getTrackFormats();
-
- /** Selects the track at {@code index} for reading sample data. */
- void selectTrack(int index);
-
- /** Deselects the track at {@code index}, so no more samples will be read from that track. */
- void deselectTrack(int index);
-
- /**
- * Returns an estimate of the position up to which data is buffered.
- *
- * <p>This method should not be called until after the extractor has been successfully prepared.
- *
- * @return an estimate of the absolute position in microseconds up to which data is buffered,
- * or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or
- * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
- */
- long getBufferedPositionUs();
-
- /**
- * Seeks to the specified time in microseconds.
- *
- * <p>This method should not be called until after the extractor has been successfully prepared.
- *
- * @param positionUs the seek position in microseconds
- */
- void seekTo(long positionUs);
-
- /** Stores the {@link MediaFormat} of {@code track}. */
- void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder);
-
- /**
- * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, returning
- * {@link SampleSource#SAMPLE_READ} if it is available.
- *
- * <p>Advances to the next sample if a sample was read.
- *
- * @param track the index of the track from which to read a sample
- * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is
- * returned
- * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or
- * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
- * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
- * loaded.
- */
- int readSample(int track, SampleHolder sampleHolder);
-
- /** Releases resources associated with this extractor. */
- void release();
-
- /** Indicates to the source that it should still be buffering data. */
- boolean continueBuffering(long positionUs);
-
- /**
- * Sets OnCompletionListener for notifying the completion of SampleExtractor.
- *
- * @param listener the OnCompletionListener
- * @param handler the {@link Handler} for {@link Handler#post(Runnable)} of OnCompletionListener
- */
- void setOnCompletionListener(OnCompletionListener listener, Handler handler);
-
- /**
- * The listener for SampleExtractor being completed.
- */
- interface OnCompletionListener {
-
- /**
- * Called when sample extraction is completed.
- *
- * @param result {@code true} when the extractor is finished without an error,
- * {@code false} otherwise (storage error, weak signal, being reached at EoS
- * prematurely, etc.)
- * @param lastExtractedPositionUs the last extracted position when extractor is completed
- */
- void onCompletion(boolean result, long lastExtractedPositionUs);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java b/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java
deleted file mode 100644
index 5666c5b9..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioClock.java
+++ /dev/null
@@ -1,107 +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.exoplayer.audio;
-
-import com.android.tv.common.SoftPreconditions;
-
-import android.os.SystemClock;
-
-/**
- * Copy of {@link com.google.android.exoplayer.MediaClock}.
- * <p>
- * A simple clock for tracking the progression of media time. The clock can be started, stopped and
- * its time can be set and retrieved. When started, this clock is based on
- * {@link SystemClock#elapsedRealtime()}.
- */
-/* package */ class AudioClock {
- private boolean mStarted;
-
- /**
- * The media time when the clock was last set or stopped.
- */
- private long mPositionUs;
-
- /**
- * The difference between {@link SystemClock#elapsedRealtime()} and {@link #mPositionUs}
- * when the clock was last set or mStarted.
- */
- private long mDeltaUs;
-
- private float mPlaybackSpeed = 1.0f;
- private long mDeltaUpdatedTimeUs;
-
- /**
- * Starts the clock. Does nothing if the clock is already started.
- */
- public void start() {
- if (!mStarted) {
- mStarted = true;
- mDeltaUs = elapsedRealtimeMinus(mPositionUs);
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- }
- }
-
- /**
- * Stops the clock. Does nothing if the clock is already stopped.
- */
- public void stop() {
- if (mStarted) {
- mPositionUs = elapsedRealtimeMinus(mDeltaUs);
- mStarted = false;
- }
- }
-
- /**
- * @param timeUs The position to set in microseconds.
- */
- public void setPositionUs(long timeUs) {
- this.mPositionUs = timeUs;
- mDeltaUs = elapsedRealtimeMinus(timeUs);
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- }
-
- /**
- * @return The current position in microseconds.
- */
- public long getPositionUs() {
- if (!mStarted) {
- return mPositionUs;
- }
- if (mPlaybackSpeed != 1.0f) {
- long elapsedTimeFromPlaybackSpeedChanged = SystemClock.elapsedRealtime() * 1000
- - mDeltaUpdatedTimeUs;
- return elapsedRealtimeMinus(mDeltaUs)
- + (long) ((mPlaybackSpeed - 1.0f) * elapsedTimeFromPlaybackSpeedChanged);
- } else {
- return elapsedRealtimeMinus(mDeltaUs);
- }
- }
-
- /**
- * Sets playback speed. {@code speed} should be positive.
- */
- public void setPlaybackSpeed(float speed) {
- SoftPreconditions.checkState(speed > 0);
- mDeltaUs = elapsedRealtimeMinus(getPositionUs());
- mDeltaUpdatedTimeUs = SystemClock.elapsedRealtime() * 1000;
- mPlaybackSpeed = speed;
- }
-
- private long elapsedRealtimeMinus(long toSubtractUs) {
- return SystemClock.elapsedRealtime() * 1000 - toSubtractUs;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java
deleted file mode 100644
index e581092a..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioDecoder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer.audio;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-
-import java.nio.ByteBuffer;
-
-/** A base class for audio decoders. */
-public abstract class AudioDecoder {
-
- /**
- * Decodes an audio sample.
- *
- * @param sampleHolder a holder that contains the sample data and corresponding metadata
- */
- public abstract void decode(SampleHolder sampleHolder);
-
- /** Returns a decoded sample from decoder. */
- public abstract ByteBuffer getDecodedSample();
-
- /** Returns the presentation time for the decoded sample. */
- public abstract long getDecodedTimeUs();
-
- /**
- * Clear previous decode state if any. Prepares to decode samples of the specified encoding.
- * This method should be called before using decode.
- *
- * @param mime audio encoding
- */
- public abstract void resetDecoderState(String mimeType);
-
- /** Releases all the resource. */
- public abstract void release();
-
- /**
- * Init decoder if needed.
- *
- * @param format the format used to initialize decoder
- */
- public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException {
- // Do nothing.
- }
-
- /** Returns input buffer that will be used in decoder. */
- public ByteBuffer getInputBuffer() {
- return null;
- }
-
- /** Returns the output format. */
- public android.media.MediaFormat getOutputFormat() {
- return null;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java
deleted file mode 100644
index ec616b13..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackMonitor.java
+++ /dev/null
@@ -1,129 +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.exoplayer.audio;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.util.MimeTypes;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Monitors the rendering position of {@link AudioTrack}.
- */
-public class AudioTrackMonitor {
- private static final String TAG = "AudioTrackMonitor";
- private static final boolean DEBUG = false;
-
- // For fetched audio samples
- private final ArrayList<Pair<Long, Integer>> mPtsList = new ArrayList<>();
- private final Set<Integer> mSampleSize = new HashSet<>();
- private final Set<Integer> mCurSampleSize = new HashSet<>();
- private final Set<Integer> mHeader = new HashSet<>();
-
- private long mExpireMs;
- private long mDuration;
- private long mSampleCount;
- private long mTotalCount;
- private long mStartMs;
-
- private boolean mIsMp2;
-
- private void flush() {
- mExpireMs += mDuration;
- mSampleCount = 0;
- mCurSampleSize.clear();
- mPtsList.clear();
- }
-
- /**
- * Resets and initializes {@link AudioTrackMonitor}.
- *
- * @param duration the frequency of monitoring in milliseconds
- */
- public void reset(long duration) {
- mExpireMs = SystemClock.elapsedRealtime();
- mDuration = duration;
- mTotalCount = 0;
- mStartMs = 0;
- mSampleSize.clear();
- mHeader.clear();
- flush();
- }
-
- public void setEncoding(String mime) {
- mIsMp2 = MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mime);
- }
-
- /**
- * Adds an audio sample information for monitoring.
- *
- * @param pts the presentation timestamp of the sample
- * @param sampleSize the size in bytes of the sample
- * @param header the bitrate &amp; sampling information header of the sample
- */
- public void addPts(long pts, int sampleSize, int header) {
- mTotalCount++;
- mSampleCount++;
- mSampleSize.add(sampleSize);
- mHeader.add(header);
- mCurSampleSize.add(sampleSize);
- if (mTotalCount == 1) {
- mStartMs = SystemClock.elapsedRealtime();
- }
- if (mPtsList.isEmpty() || mPtsList.get(mPtsList.size() - 1).first != pts) {
- mPtsList.add(Pair.create(pts, 1));
- return;
- }
- Pair<Long, Integer> pair = mPtsList.get(mPtsList.size() - 1);
- mPtsList.set(mPtsList.size() - 1, Pair.create(pair.first, pair.second + 1));
- }
-
- /**
- * Logs if interested events are present.
- * <p>
- * Periodic logging is not enabled in release mode in order to avoid verbose logging.
- */
- public void maybeLog() {
- long now = SystemClock.elapsedRealtime();
- if (mExpireMs != 0 && now >= mExpireMs) {
- if (DEBUG) {
- long unitDuration = mIsMp2 ? MpegTsDefaultAudioTrackRenderer.MP2_SAMPLE_DURATION_US
- : MpegTsDefaultAudioTrackRenderer.AC3_SAMPLE_DURATION_US;
- long sampleDuration = (mTotalCount - 1) * unitDuration / 1000;
- long totalDuration = now - mStartMs;
- StringBuilder ptsBuilder = new StringBuilder();
- ptsBuilder.append("PTS received ").append(mSampleCount).append(", ")
- .append(totalDuration - sampleDuration).append(' ');
-
- for (Pair<Long, Integer> pair : mPtsList) {
- ptsBuilder.append('[').append(pair.first).append(':').append(pair.second)
- .append("], ");
- }
- Log.d(TAG, ptsBuilder.toString());
- }
- if (DEBUG || mCurSampleSize.size() > 1) {
- Log.d(TAG, "PTS received sample size: "
- + String.valueOf(mSampleSize) + mCurSampleSize + mHeader);
- }
- flush();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java
deleted file mode 100644
index 953c9fc4..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/AudioTrackWrapper.java
+++ /dev/null
@@ -1,176 +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.exoplayer.audio;
-
-import android.media.MediaFormat;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.audio.AudioTrack;
-
-import java.nio.ByteBuffer;
-
-/**
- * {@link AudioTrack} wrapper class for trickplay operations including FF/RW.
- * FF/RW trickplay operations do not need framework {@link AudioTrack}.
- * This wrapper class will do nothing in disabled status for those operations.
- */
-public class AudioTrackWrapper {
- private static final int PCM16_FRAME_BYTES = 2;
- private static final int AC3_FRAMES_IN_ONE_SAMPLE = 1536;
- private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK =
- MpegTsDefaultAudioTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK;
- private final AudioTrack mAudioTrack = new AudioTrack();
- private int mAudioSessionID;
- private boolean mIsEnabled;
-
- AudioTrackWrapper() {
- mIsEnabled = true;
- }
-
- public void resetSessionId() {
- mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET;
- }
-
- public boolean isInitialized() {
- return mIsEnabled && mAudioTrack.isInitialized();
- }
-
- public void restart() {
- if (mAudioTrack.isInitialized()) {
- mAudioTrack.release();
- }
- mIsEnabled = true;
- resetSessionId();
- }
-
- public void release() {
- if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
- mAudioTrack.release();
- }
- }
-
- public void initialize() throws AudioTrack.InitializationException {
- if (!mIsEnabled) {
- return;
- }
- if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
- mAudioTrack.initialize(mAudioSessionID);
- } else {
- mAudioSessionID = mAudioTrack.initialize();
- }
- }
-
- public void reset() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.reset();
- }
-
- public boolean isEnded() {
- return !mIsEnabled || !mAudioTrack.hasPendingData();
- }
-
- public boolean isReady() {
- // In the case of not playing actual audio data, Audio track is always ready.
- return !mIsEnabled || mAudioTrack.hasPendingData();
- }
-
- public void play() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.play();
- }
-
- public void pause() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.pause();
- }
-
- public void setVolume(float volume) {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.setVolume(volume);
- }
-
- public void reconfigure(MediaFormat format, int audioBufferSize) {
- if (!mIsEnabled || format == null) {
- return;
- }
- String mimeType = format.getString(MediaFormat.KEY_MIME);
- int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
- int pcmEncoding;
- try {
- pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING);
- } catch (Exception e) {
- pcmEncoding = C.ENCODING_PCM_16BIT;
- }
- // TODO: Handle non-AC3.
- if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) {
- // Workarounds b/25955476.
- // Since all devices and platforms does not support passthrough for non-stereo AC3,
- // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode.
- // In other words, the channel count should be always 2.
- channelCount = 2;
- }
- if (MediaFormat.MIMETYPE_AUDIO_RAW.equalsIgnoreCase(mimeType)) {
- audioBufferSize =
- channelCount
- * PCM16_FRAME_BYTES
- * AC3_FRAMES_IN_ONE_SAMPLE
- * BUFFERED_SAMPLES_IN_AUDIOTRACK;
- }
- mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, audioBufferSize);
- }
-
- public void handleDiscontinuity() {
- if (!mIsEnabled) {
- return;
- }
- mAudioTrack.handleDiscontinuity();
- }
-
- public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs)
- throws AudioTrack.WriteException {
- if (!mIsEnabled) {
- return AudioTrack.RESULT_BUFFER_CONSUMED;
- }
- return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs);
- }
-
- public void setStatus(boolean enable) {
- if (enable == mIsEnabled) {
- return;
- }
- mAudioTrack.reset();
- mIsEnabled = enable;
- }
-
- public boolean isEnabled() {
- return mIsEnabled;
- }
-
- // This should be used only in case of being enabled.
- public long getCurrentPositionUs(boolean isEnded) {
- return mAudioTrack.getCurrentPositionUs(isEnded);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java b/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java
deleted file mode 100644
index 72bc68b6..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MediaCodecAudioDecoder.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer.audio;
-
-import android.media.MediaCodec;
-import android.util.Log;
-
-import com.google.android.exoplayer.CodecCounters;
-import com.google.android.exoplayer.DecoderInfo;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaCodecUtil;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/** A decoder to use MediaCodec for decoding audio stream. */
-public class MediaCodecAudioDecoder extends AudioDecoder {
- private static final String TAG = "MediaCodecAudioDecoder";
-
- public static final int INDEX_INVALID = -1;
-
- private final CodecCounters mCodecCounters;
- private final MediaCodecSelector mSelector;
-
- private MediaCodec mCodec;
- private MediaCodec.BufferInfo mOutputBufferInfo;
- private ByteBuffer mMediaCodecOutputBuffer;
- private ArrayList<Long> mDecodeOnlyPresentationTimestamps;
- private boolean mWaitingForFirstSyncFrame;
- private boolean mIsNewIndex;
- private int mInputIndex;
- private int mOutputIndex;
-
- /** Creates a MediaCodec based audio decoder. */
- public MediaCodecAudioDecoder(MediaCodecSelector selector) {
- mSelector = selector;
- mOutputBufferInfo = new MediaCodec.BufferInfo();
- mCodecCounters = new CodecCounters();
- mDecodeOnlyPresentationTimestamps = new ArrayList<>();
- }
-
- /** Returns {@code true} if there is decoder for {@code mimeType}. */
- public static boolean supportMimeType(MediaCodecSelector selector, String mimeType) {
- if (selector == null) {
- return false;
- }
- return getDecoderInfo(selector, mimeType) != null;
- }
-
- private static DecoderInfo getDecoderInfo(MediaCodecSelector selector, String mimeType) {
- try {
- return selector.getDecoderInfo(mimeType, false);
- } catch (MediaCodecUtil.DecoderQueryException e) {
- Log.e(TAG, "Select decoder error:" + e);
- return null;
- }
- }
-
- private boolean shouldInitCodec(MediaFormat format) {
- return format != null && mCodec == null;
- }
-
- @Override
- public void maybeInitDecoder(MediaFormat format) throws ExoPlaybackException {
- if (!shouldInitCodec(format)) {
- return;
- }
-
- String mimeType = format.mimeType;
- DecoderInfo decoderInfo = getDecoderInfo(mSelector, mimeType);
- if (decoderInfo == null) {
- Log.i(TAG, "There is not decoder found for " + mimeType);
- return;
- }
-
- String codecName = decoderInfo.name;
- try {
- mCodec = MediaCodec.createByCodecName(codecName);
- mCodec.configure(format.getFrameworkMediaFormatV16(), null, null, 0);
- mCodec.start();
- } catch (Exception e) {
- Log.e(TAG, "Failed when configure or start codec:" + e);
- throw new ExoPlaybackException(e);
- }
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mWaitingForFirstSyncFrame = true;
- mCodecCounters.codecInitCount++;
- }
-
- @Override
- public void resetDecoderState(String mimeType) {
- if (mCodec == null) {
- return;
- }
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mDecodeOnlyPresentationTimestamps.clear();
- mCodec.flush();
- mWaitingForFirstSyncFrame = true;
- }
-
- @Override
- public void release() {
- if (mCodec != null) {
- mDecodeOnlyPresentationTimestamps.clear();
- mInputIndex = INDEX_INVALID;
- mOutputIndex = INDEX_INVALID;
- mCodecCounters.codecReleaseCount++;
- try {
- mCodec.stop();
- } finally {
- try {
- mCodec.release();
- } finally {
- mCodec = null;
- }
- }
- }
- }
-
- /** Returns the index of input buffer which is ready for using. */
- public int getInputIndex() {
- return mInputIndex;
- }
-
- @Override
- public ByteBuffer getInputBuffer() {
- if (mInputIndex < 0) {
- mInputIndex = mCodec.dequeueInputBuffer(0);
- if (mInputIndex < 0) {
- return null;
- }
- return mCodec.getInputBuffer(mInputIndex);
- }
- return mCodec.getInputBuffer(mInputIndex);
- }
-
- @Override
- public void decode(SampleHolder sampleHolder) {
- if (mWaitingForFirstSyncFrame) {
- if (!sampleHolder.isSyncFrame()) {
- sampleHolder.clearData();
- return;
- }
- mWaitingForFirstSyncFrame = false;
- }
- long presentationTimeUs = sampleHolder.timeUs;
- if (sampleHolder.isDecodeOnly()) {
- mDecodeOnlyPresentationTimestamps.add(presentationTimeUs);
- }
- mCodec.queueInputBuffer(mInputIndex, 0, sampleHolder.data.limit(), presentationTimeUs, 0);
- mInputIndex = INDEX_INVALID;
- mCodecCounters.inputBufferCount++;
- }
-
- private int getDecodeOnlyIndex(long presentationTimeUs) {
- final int size = mDecodeOnlyPresentationTimestamps.size();
- for (int i = 0; i < size; i++) {
- if (mDecodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) {
- return i;
- }
- }
- return INDEX_INVALID;
- }
-
- /** Returns the index of output buffer which is ready for using. */
- public int getOutputIndex() {
- if (mOutputIndex < 0) {
- mOutputIndex = mCodec.dequeueOutputBuffer(mOutputBufferInfo, 0);
- mIsNewIndex = true;
- } else {
- mIsNewIndex = false;
- }
- return mOutputIndex;
- }
-
- @Override
- public android.media.MediaFormat getOutputFormat() {
- return mCodec.getOutputFormat();
- }
-
- /** Returns {@code true} if the output is only for decoding but not for rendering. */
- public boolean maybeDecodeOnlyIndex() {
- int decodeOnlyIndex = getDecodeOnlyIndex(mOutputBufferInfo.presentationTimeUs);
- if (decodeOnlyIndex != INDEX_INVALID) {
- mCodec.releaseOutputBuffer(mOutputIndex, false);
- mCodecCounters.skippedOutputBufferCount++;
- mDecodeOnlyPresentationTimestamps.remove(decodeOnlyIndex);
- mOutputIndex = INDEX_INVALID;
- return true;
- }
- return false;
- }
-
- @Override
- public ByteBuffer getDecodedSample() {
- if (maybeDecodeOnlyIndex() || mOutputIndex < 0) {
- return null;
- }
- if (mIsNewIndex) {
- mMediaCodecOutputBuffer = mCodec.getOutputBuffer(mOutputIndex);
- }
- return mMediaCodecOutputBuffer;
- }
-
- @Override
- public long getDecodedTimeUs() {
- return mOutputBufferInfo.presentationTimeUs;
- }
-
- /** Releases the output buffer after rendering. */
- public void releaseOutputBuffer() {
- mCodecCounters.renderedOutputBufferCount++;
- mCodec.releaseOutputBuffer(mOutputIndex, false);
- mOutputIndex = INDEX_INVALID;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
deleted file mode 100644
index 77170419..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsDefaultAudioTrackRenderer.java
+++ /dev/null
@@ -1,735 +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.exoplayer.audio;
-
-import android.media.MediaCodec;
-import android.os.Build;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.google.android.exoplayer.CodecCounters;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaClock;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.util.Assertions;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient;
-import com.android.tv.tuner.tvinput.TunerDebug;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/**
- * Decodes and renders DTV audio. Supports MediaCodec based decoding, passthrough playback and
- * ffmpeg based software decoding (AC3, MP2).
- */
-public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements MediaClock {
- public static final int MSG_SET_VOLUME = 10000;
- public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1;
- public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2;
-
- // ATSC/53 allows sample rate to be only 48Khz.
- // One AC3 sample has 1536 frames, and its duration is 32ms.
- public static final long AC3_SAMPLE_DURATION_US = 32000;
-
- // TODO: Check whether DVB broadcasting uses sample rate other than 48Khz.
- // MPEG-1 audio Layer II and III has 1152 frames per sample.
- // 1152 frames duration is 24ms when sample rate is 48Khz.
- static final long MP2_SAMPLE_DURATION_US = 24000;
-
- // This is around 150ms, 150ms is big enough not to under-run AudioTrack,
- // and 150ms is also small enough to fill the buffer rapidly.
- static int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5;
- public static final long INITIAL_AUDIO_BUFFERING_TIME_US =
- BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US;
-
-
- private static final String TAG = "MpegTsDefaultAudioTrac";
- private static final boolean DEBUG = false;
-
- /**
- * Interface definition for a callback to be notified of
- * {@link com.google.android.exoplayer.audio.AudioTrack} error.
- */
- public interface EventListener {
- void onAudioTrackInitializationError(AudioTrack.InitializationException e);
- void onAudioTrackWriteError(AudioTrack.WriteException e);
- }
-
- private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2;
- private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024;
- private static final int MONITOR_DURATION_MS = 1000;
- private static final int AC3_HEADER_BITRATE_OFFSET = 4;
- private static final int MP2_HEADER_BITRATE_OFFSET = 2;
- private static final int MP2_HEADER_BITRATE_MASK = 0xfc;
-
- // Keep this as static in order to prevent new framework AudioTrack creation
- // while old AudioTrack is being released.
- private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper();
- private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000;
-
- // Ignore AudioTrack backward movement if duration of movement is below the threshold.
- private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000;
-
- // AudioTrack position cannot go ahead beyond this limit.
- private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000;
-
- // Since MediaCodec processing and AudioTrack playing add delay,
- // PTS interpolated time should be delayed reasonably when AudioTrack is not used.
- private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000;
-
- private final MediaCodecSelector mSelector;
-
- private final CodecCounters mCodecCounters;
- private final SampleSource.SampleSourceReader mSource;
- private final MediaFormatHolder mFormatHolder;
- private final EventListener mEventListener;
- private final Handler mEventHandler;
- private final AudioTrackMonitor mMonitor;
- private final AudioClock mAudioClock;
- private final boolean mAc3Passthrough;
- private final boolean mSoftwareDecoderAvailable;
-
- private MediaFormat mFormat;
- private SampleHolder mSampleHolder;
- private String mDecodingMime;
- private boolean mFormatConfigured;
- private int mSampleSize;
- private final ByteBuffer mOutputBuffer;
- private AudioDecoder mAudioDecoder;
- private boolean mOutputReady;
- private int mTrackIndex;
- private boolean mSourceStateReady;
- private boolean mInputStreamEnded;
- private boolean mOutputStreamEnded;
- private long mEndOfStreamMs;
- private long mCurrentPositionUs;
- private int mPresentationCount;
- private long mPresentationTimeUs;
- private long mInterpolatedTimeUs;
- private long mPreviousPositionUs;
- private boolean mIsStopped;
- private boolean mEnabled = true;
- private boolean mIsMuted;
- private ArrayList<Integer> mTracksIndex;
- private boolean mUseFrameworkDecoder;
-
- public MpegTsDefaultAudioTrackRenderer(
- SampleSource source,
- MediaCodecSelector selector,
- Handler eventHandler,
- EventListener listener,
- boolean hasSoftwareAudioDecoder,
- boolean usePassthrough) {
- mSource = source.register();
- mSelector = selector;
- mEventHandler = eventHandler;
- mEventListener = listener;
- mTrackIndex = -1;
- mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE);
- mFormatHolder = new MediaFormatHolder();
- AUDIO_TRACK.restart();
- mCodecCounters = new CodecCounters();
- mMonitor = new AudioTrackMonitor();
- mAudioClock = new AudioClock();
- mTracksIndex = new ArrayList<>();
- mAc3Passthrough = usePassthrough;
- mSoftwareDecoderAvailable = hasSoftwareAudioDecoder && FfmpegDecoderClient.isAvailable();
- }
-
- @Override
- protected MediaClock getMediaClock() {
- return this;
- }
-
- private boolean handlesMimeType(String mimeType) {
- return mimeType.equals(MimeTypes.AUDIO_AC3)
- || mimeType.equals(MimeTypes.AUDIO_E_AC3)
- || mimeType.equals(MimeTypes.AUDIO_MPEG_L2)
- || MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
- }
-
- @Override
- protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
- boolean sourcePrepared = mSource.prepare(positionUs);
- if (!sourcePrepared) {
- return false;
- }
- for (int i = 0; i < mSource.getTrackCount(); i++) {
- String mimeType = mSource.getFormat(i).mimeType;
- if (MimeTypes.isAudio(mimeType) && handlesMimeType(mimeType)) {
- if (mTrackIndex < 0) {
- mTrackIndex = i;
- }
- mTracksIndex.add(i);
- }
- }
-
- // TODO: Check this case. Source does not have the proper mime type.
- return true;
- }
-
- @Override
- protected int getTrackCount() {
- return mTracksIndex.size();
- }
-
- @Override
- protected MediaFormat getFormat(int track) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- return mSource.getFormat(mTracksIndex.get(track));
- }
-
- @Override
- protected void onEnabled(int track, long positionUs, boolean joining) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- mTrackIndex = mTracksIndex.get(track);
- mSource.enable(mTrackIndex, positionUs);
- seekToInternal(positionUs);
- }
-
- @Override
- protected void onDisabled() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- AUDIO_TRACK.resetSessionId();
- }
- clearDecodeState();
- mFormat = null;
- mSource.disable(mTrackIndex);
- }
-
- @Override
- protected void onReleased() {
- releaseDecoder();
- AUDIO_TRACK.release();
- mSource.release();
- }
-
- @Override
- protected boolean isEnded() {
- return mOutputStreamEnded && AUDIO_TRACK.isEnded();
- }
-
- @Override
- protected boolean isReady() {
- return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady));
- }
-
- private void seekToInternal(long positionUs) {
- mMonitor.reset(MONITOR_DURATION_MS);
- mSourceStateReady = false;
- mInputStreamEnded = false;
- mOutputStreamEnded = false;
- mPresentationTimeUs = positionUs;
- mPresentationCount = 0;
- mPreviousPositionUs = 0;
- mCurrentPositionUs = Long.MIN_VALUE;
- mInterpolatedTimeUs = Long.MIN_VALUE;
- mAudioClock.setPositionUs(positionUs);
- }
-
- @Override
- protected void seekTo(long positionUs) {
- mSource.seekToUs(positionUs);
- AUDIO_TRACK.reset();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- // resetSessionId() will create a new framework AudioTrack instead of reusing old one.
- AUDIO_TRACK.resetSessionId();
- }
- seekToInternal(positionUs);
- clearDecodeState();
- }
-
- @Override
- protected void onStarted() {
- AUDIO_TRACK.play();
- mAudioClock.start();
- mIsStopped = false;
- }
-
- @Override
- protected void onStopped() {
- AUDIO_TRACK.pause();
- mAudioClock.stop();
- mIsStopped = true;
- }
-
- @Override
- protected void maybeThrowError() throws ExoPlaybackException {
- try {
- mSource.maybeThrowError();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- @Override
- protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- mMonitor.maybeLog();
- try {
- if (mEndOfStreamMs != 0) {
- // Ensure playback stops, after EoS was notified.
- // Sometimes MediaCodecTrackRenderer does not fetch EoS timely
- // after EoS was notified here long before.
- long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
- if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) {
- throw new ExoPlaybackException("Much time has elapsed after EoS");
- }
- }
- boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs);
- if (mSourceStateReady != continueBuffering) {
- mSourceStateReady = continueBuffering;
- if (DEBUG) {
- Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady));
- }
- }
- long discontinuity = mSource.readDiscontinuity(mTrackIndex);
- if (discontinuity != SampleSource.NO_DISCONTINUITY) {
- AUDIO_TRACK.handleDiscontinuity();
- mPresentationTimeUs = discontinuity;
- mPresentationCount = 0;
- clearDecodeState();
- return;
- }
- if (mFormat == null) {
- readFormat();
- return;
- }
-
- if (mAudioDecoder != null) {
- mAudioDecoder.maybeInitDecoder(mFormat);
- }
- // Process only one sample at a time for doSomeWork() when using FFmpeg decoder.
- if (processOutput()) {
- if (!mOutputReady) {
- while (feedInputBuffer()) {
- if (mOutputReady) break;
- }
- }
- }
- mCodecCounters.ensureUpdated();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- private void ensureAudioTrackInitialized() {
- if (!AUDIO_TRACK.isInitialized()) {
- try {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack initialized");
- }
- AUDIO_TRACK.initialize();
- } catch (AudioTrack.InitializationException e) {
- Log.e(TAG, "Error on AudioTrack initialization", e);
- notifyAudioTrackInitializationError(e);
-
- // Do not throw exception here but just disabling audioTrack to keep playing
- // video without audio.
- AUDIO_TRACK.setStatus(false);
- }
- if (getState() == TrackRenderer.STATE_STARTED) {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack played");
- }
- AUDIO_TRACK.play();
- }
- }
- }
-
- private void clearDecodeState() {
- mOutputReady = false;
- if (mAudioDecoder != null) {
- mAudioDecoder.resetDecoderState(mDecodingMime);
- }
- AUDIO_TRACK.reset();
- }
-
- private void releaseDecoder() {
- if (mAudioDecoder != null) {
- mAudioDecoder.release();
- }
- }
-
- private void readFormat() throws IOException, ExoPlaybackException {
- int result = mSource.readData(mTrackIndex, mCurrentPositionUs,
- mFormatHolder, mSampleHolder);
- if (result == SampleSource.FORMAT_READ) {
- onInputFormatChanged(mFormatHolder);
- }
- }
-
- private MediaFormat convertMediaFormatToRaw(MediaFormat format) {
- return MediaFormat.createAudioFormat(
- format.trackId,
- MimeTypes.AUDIO_RAW,
- format.bitrate,
- format.maxInputSize,
- format.durationUs,
- format.channelCount,
- format.sampleRate,
- format.initializationData,
- format.language);
- }
-
- private void onInputFormatChanged(MediaFormatHolder formatHolder)
- throws ExoPlaybackException {
- String mimeType = formatHolder.format.mimeType;
- mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
- if (mUseFrameworkDecoder) {
- mAudioDecoder = new MediaCodecAudioDecoder(mSelector);
- mFormat = formatHolder.format;
- mAudioDecoder.maybeInitDecoder(mFormat);
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
- } else if (mSoftwareDecoderAvailable
- && (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mimeType)
- || MimeTypes.AUDIO_AC3.equalsIgnoreCase(mimeType) && !mAc3Passthrough)) {
- releaseDecoder();
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mAudioDecoder = FfmpegDecoderClient.getInstance();
- mDecodingMime = mimeType;
- mFormat = convertMediaFormatToRaw(formatHolder.format);
- } else {
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mFormat = formatHolder.format;
- releaseDecoder();
- }
- mFormatConfigured = true;
- mMonitor.setEncoding(mimeType);
- if (DEBUG && !mUseFrameworkDecoder) {
- Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
- }
- clearDecodeState();
- if (!mUseFrameworkDecoder) {
- AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), 0);
- }
- }
-
- private void onSampleSizeChanged(int sampleSize) {
- if (DEBUG) {
- Log.d(TAG, "Sample size was changed to : " + sampleSize);
- }
- clearDecodeState();
- int audioBufferSize = sampleSize * BUFFERED_SAMPLES_IN_AUDIOTRACK;
- mSampleSize = sampleSize;
- AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), audioBufferSize);
- }
-
- private void onOutputFormatChanged(android.media.MediaFormat format) {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack was configured to FORMAT: " + format.toString());
- }
- AUDIO_TRACK.reconfigure(format, 0);
- }
-
- private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
- if (mInputStreamEnded) {
- return false;
- }
-
- if (mUseFrameworkDecoder) {
- boolean indexChanged =
- ((MediaCodecAudioDecoder) mAudioDecoder).getInputIndex()
- == MediaCodecAudioDecoder.INDEX_INVALID;
- if (indexChanged) {
- mSampleHolder.data = mAudioDecoder.getInputBuffer();
- if (mSampleHolder.data != null) {
- mSampleHolder.clearData();
- } else {
- return false;
- }
- }
- } else {
- mSampleHolder.data.clear();
- mSampleHolder.size = 0;
- }
- int result =
- mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder);
- switch (result) {
- case SampleSource.NOTHING_READ: {
- return false;
- }
- case SampleSource.FORMAT_READ: {
- Log.i(TAG, "Format was read again");
- onInputFormatChanged(mFormatHolder);
- return true;
- }
- case SampleSource.END_OF_STREAM: {
- Log.i(TAG, "End of stream from SampleSource");
- mInputStreamEnded = true;
- return false;
- }
- default: {
- if (mSampleHolder.size != mSampleSize
- && mFormatConfigured
- && !mUseFrameworkDecoder) {
- onSampleSizeChanged(mSampleHolder.size);
- }
- mSampleHolder.data.flip();
- if (!mUseFrameworkDecoder) {
- if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
- mMonitor.addPts(
- mSampleHolder.timeUs,
- mOutputBuffer.position(),
- mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET)
- & MP2_HEADER_BITRATE_MASK);
- } else {
- mMonitor.addPts(
- mSampleHolder.timeUs,
- mOutputBuffer.position(),
- mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff);
- }
- }
- if (mAudioDecoder != null) {
- mAudioDecoder.decode(mSampleHolder);
- if (mUseFrameworkDecoder) {
- int outputIndex =
- ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex();
- if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- onOutputFormatChanged(mAudioDecoder.getOutputFormat());
- return true;
- } else if (outputIndex < 0) {
- return true;
- }
- if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) {
- AUDIO_TRACK.handleDiscontinuity();
- return true;
- }
- }
- ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample();
- long presentationTimeUs = mAudioDecoder.getDecodedTimeUs();
- decodeDone(outputBuffer, presentationTimeUs);
- } else {
- decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
- }
- return true;
- }
- }
- }
-
- private boolean processOutput() throws ExoPlaybackException {
- if (mOutputStreamEnded) {
- return false;
- }
- if (!mOutputReady) {
- if (mInputStreamEnded) {
- mOutputStreamEnded = true;
- mEndOfStreamMs = SystemClock.elapsedRealtime();
- return false;
- }
- return true;
- }
-
- ensureAudioTrackInitialized();
- int handleBufferResult;
- try {
- // To reduce discontinuity, interpolate presentation time.
- if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
- mInterpolatedTimeUs = mPresentationTimeUs
- + mPresentationCount * MP2_SAMPLE_DURATION_US;
- } else if (!mUseFrameworkDecoder) {
- mInterpolatedTimeUs = mPresentationTimeUs
- + mPresentationCount * AC3_SAMPLE_DURATION_US;
- } else {
- mInterpolatedTimeUs = mPresentationTimeUs;
- }
- handleBufferResult =
- AUDIO_TRACK.handleBuffer(
- mOutputBuffer, 0, mOutputBuffer.limit(), mInterpolatedTimeUs);
- } catch (AudioTrack.WriteException e) {
- notifyAudioTrackWriteError(e);
- throw new ExoPlaybackException(e);
- }
- if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
- Log.i(TAG, "Play discontinuity happened");
- mCurrentPositionUs = Long.MIN_VALUE;
- }
- if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
- mCodecCounters.renderedOutputBufferCount++;
- mOutputReady = false;
- if (mUseFrameworkDecoder) {
- ((MediaCodecAudioDecoder) mAudioDecoder).releaseOutputBuffer();
- }
- return true;
- }
- return false;
- }
-
- @Override
- protected long getDurationUs() {
- return mSource.getFormat(mTrackIndex).durationUs;
- }
-
- @Override
- protected long getBufferedPositionUs() {
- long pos = mSource.getBufferedPositionUs();
- return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US
- ? pos : Math.max(pos, getPositionUs());
- }
-
- @Override
- public long getPositionUs() {
- if (!AUDIO_TRACK.isInitialized()) {
- return mAudioClock.getPositionUs();
- } else if (!AUDIO_TRACK.isEnabled()) {
- if (mInterpolatedTimeUs > 0 && !mUseFrameworkDecoder) {
- return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US;
- }
- return mPresentationTimeUs;
- }
- long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded());
- if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) {
- mPreviousPositionUs = 0L;
- if (DEBUG) {
- long oldPositionUs = Math.max(mCurrentPositionUs, 0);
- long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- Log.d(TAG, "Audio position is not set, diff in us: "
- + String.valueOf(currentPositionUs - oldPositionUs));
- }
- mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- } else {
- if (mPreviousPositionUs
- > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
- Log.e(TAG, "audio_position BACK JUMP: "
- + (mPreviousPositionUs - audioTrackCurrentPositionUs));
- mCurrentPositionUs = audioTrackCurrentPositionUs;
- } else {
- mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs);
- }
- mPreviousPositionUs = audioTrackCurrentPositionUs;
- }
- long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US;
- if (mCurrentPositionUs > upperBound) {
- mCurrentPositionUs = upperBound;
- }
- return mCurrentPositionUs;
- }
-
- private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) {
- if (outputBuffer == null || mOutputBuffer == null) {
- return;
- }
- if (presentationTimeUs < 0) {
- Log.e(TAG, "decodeDone - invalid presentationTimeUs");
- return;
- }
-
- if (TunerDebug.ENABLED) {
- TunerDebug.setAudioPtsUs(presentationTimeUs);
- }
-
- mOutputBuffer.clear();
- Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit());
-
- mOutputBuffer.put(outputBuffer);
- if (presentationTimeUs == mPresentationTimeUs) {
- mPresentationCount++;
- } else {
- mPresentationCount = 0;
- mPresentationTimeUs = presentationTimeUs;
- }
- mOutputBuffer.flip();
- mOutputReady = true;
- }
-
- private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackInitializationError(e);
- }
- });
- }
-
- private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackWriteError(e);
- }
- });
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SET_VOLUME:
- float volume = (Float) message;
- // Workaround: we cannot mute the audio track by setting the volume to 0, we need to
- // disable the AUDIO_TRACK for this intent. However, enabling/disabling audio track
- // whenever volume is being set might cause side effects, therefore we only handle
- // "explicit mute operations", i.e., only after certain non-zero volume has been
- // set, the subsequent volume setting operations will be consider as mute/un-mute
- // operations and thus enable/disable the audio track.
- if (mIsMuted && volume > 0) {
- mIsMuted = false;
- if (mEnabled) {
- setStatus(true);
- }
- } else if (!mIsMuted && volume == 0) {
- mIsMuted = true;
- if (mEnabled) {
- setStatus(false);
- }
- }
- AUDIO_TRACK.setVolume(volume);
- break;
- case MSG_SET_AUDIO_TRACK:
- mEnabled = (Integer) message == 1;
- setStatus(mEnabled);
- break;
- case MSG_SET_PLAYBACK_SPEED:
- mAudioClock.setPlaybackSpeed((Float) message);
- break;
- default:
- super.handleMessage(messageType, message);
- }
- }
-
- private void setStatus(boolean enabled) {
- if (enabled == AUDIO_TRACK.isEnabled()) {
- return;
- }
- if (!enabled) {
- // mAudioClock can be different from getPositionUs. In order to sync them,
- // we set mAudioClock.
- mAudioClock.setPositionUs(getPositionUs());
- }
- AUDIO_TRACK.setStatus(enabled);
- if (enabled) {
- // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to
- // the current position. If not, AUDIO_TRACK has the obsolete data.
- seekTo(mAudioClock.getPositionUs());
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java
deleted file mode 100644
index 142aa9b2..00000000
--- a/src/com/android/tv/tuner/exoplayer/audio/MpegTsMediaCodecAudioTrackRenderer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2016 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.exoplayer.audio;
-
-import android.os.Handler;
-
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaCodecSelector;
-import com.google.android.exoplayer.SampleSource;
-
-/**
- * MPEG-2 TS audio track renderer.
- *
- * <p>Since the audio output from {@link android.media.MediaExtractor} contains extra samples at the
- * beginning, using original {@link MediaCodecAudioTrackRenderer} as audio renderer causes
- * asynchronous Audio/Video outputs. This class calculates the offset of audio data and adjust the
- * presentation times to avoid the asynchronous Audio/Video problem.
- */
-public class MpegTsMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRenderer {
- private final Ac3EventListener mListener;
-
- public interface Ac3EventListener extends EventListener {
- /**
- * Invoked when a {@link android.media.PlaybackParams} set to an
- * {@link android.media.AudioTrack} is not valid.
- *
- * @param e The corresponding exception.
- */
- void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e);
- }
-
- public MpegTsMediaCodecAudioTrackRenderer(
- SampleSource source,
- MediaCodecSelector mediaCodecSelector,
- Handler eventHandler,
- EventListener eventListener) {
- super(source, mediaCodecSelector, eventHandler, eventListener);
- mListener = (Ac3EventListener) eventListener;
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- if (messageType == MSG_SET_PLAYBACK_PARAMS) {
- try {
- super.handleMessage(messageType, message);
- } catch (IllegalArgumentException e) {
- if (isAudioTrackSetPlaybackParamsError(e)) {
- notifyAudioTrackSetPlaybackParamsError(e);
- }
- }
- return;
- }
- super.handleMessage(messageType, message);
- }
-
- private void notifyAudioTrackSetPlaybackParamsError(final IllegalArgumentException e) {
- if (eventHandler != null && mListener != null) {
- eventHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onAudioTrackSetPlaybackParamsError(e);
- }
- });
- }
- }
-
- static private boolean isAudioTrackSetPlaybackParamsError(IllegalArgumentException e) {
- if (e.getStackTrace() == null || e.getStackTrace().length < 1) {
- return false;
- }
- for (StackTraceElement element : e.getStackTrace()) {
- String elementString = element.toString();
- if (elementString.startsWith("android.media.AudioTrack.setPlaybackParams")) {
- return true;
- }
- }
- return false;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
deleted file mode 100644
index 112e9dc4..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java
+++ /dev/null
@@ -1,692 +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.exoplayer.buffer;
-
-import android.media.MediaFormat;
-import android.os.ConditionVariable;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Manages {@link SampleChunk} objects.
- * <p>
- * The buffer manager can be disabled, while running, if the write throughput to the associated
- * external storage is detected to be lower than a threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}".
- * This leads to restarting playback flow.
- */
-public class BufferManager {
- private static final String TAG = "BufferManager";
- private static final boolean DEBUG = false;
-
- // Constants for the disk write speed checking
- private static final long MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK =
- 10L * 1024 * 1024; // Checks for every 10M disk write
- private static final int MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK = 15 * 1024;
- private static final int MAXIMUM_SPEED_CHECK_COUNT = 5; // Checks only 5 times
- private static final int MINIMUM_DISK_WRITE_SPEED_MBPS = 3; // 3 Megabytes per second
-
- private final SampleChunk.SampleChunkCreator mSampleChunkCreator;
- // Maps from track name to a map which maps from starting position to {@link SampleChunk}.
- private final Map<String, SortedMap<Long, Pair<SampleChunk, Integer>>> mChunkMap =
- new ArrayMap<>();
- private final Map<String, Long> mStartPositionMap = new ArrayMap<>();
- private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>();
- private final StorageManager mStorageManager;
- private long mBufferSize = 0;
- private final EvictChunkQueueMap mPendingDelete = new EvictChunkQueueMap();
- private final SampleChunk.ChunkCallback mChunkCallback = new SampleChunk.ChunkCallback() {
- @Override
- public void onChunkWrite(SampleChunk chunk) {
- mBufferSize += chunk.getSize();
- }
-
- @Override
- public void onChunkDelete(SampleChunk chunk) {
- mBufferSize -= chunk.getSize();
- }
- };
-
- private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK;
- private long mTotalWriteSize;
- private long mTotalWriteTimeNs;
- private float mWriteBandwidth = 0.0f;
- private volatile int mSpeedCheckCount;
-
- public interface ChunkEvictedListener {
- void onChunkEvicted(String id, long createdTimeMs);
- }
- /**
- * Handles I/O
- * between BufferManager and {@link SampleExtractor}.
- */
- public interface SampleBuffer {
-
- /**
- * Initializes SampleBuffer.
- * @param Ids track identifiers for storage read/write.
- * @param mediaFormats meta-data for each track.
- * @throws IOException
- */
- void init(@NonNull List<String> Ids,
- @NonNull List<com.google.android.exoplayer.MediaFormat> mediaFormats)
- throws IOException;
-
- /**
- * Selects the track {@code index} for reading sample data.
- */
- void selectTrack(int index);
-
- /**
- * Deselects the track at {@code index},
- * so that no more samples will be read from the track.
- */
- void deselectTrack(int index);
-
- /**
- * Writes sample to storage.
- *
- * @param index track index
- * @param sample sample to write at storage
- * @param conditionVariable notifies the completion of writing sample.
- * @throws IOException
- */
- void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
- throws IOException;
-
- /**
- * Checks whether storage write speed is slow.
- */
- boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs);
-
- /**
- * Handles when write speed is slow.
- * @throws IOException
- */
- void handleWriteSpeedSlow() throws IOException;
-
- /**
- * Sets the flag when EoS was reached.
- */
- void setEos();
-
- /**
- * Reads the next sample in the track at index {@code track} into {@code sampleHolder},
- * returning {@link com.google.android.exoplayer.SampleSource#SAMPLE_READ}
- * if it is available.
- * If the next sample is not available,
- * returns {@link com.google.android.exoplayer.SampleSource#NOTHING_READ}.
- */
- int readSample(int index, SampleHolder outSample);
-
- /**
- * Seeks to the specified time in microseconds.
- */
- void seekTo(long positionUs);
-
- /**
- * Returns an estimate of the position up to which data is buffered.
- */
- long getBufferedPositionUs();
-
- /**
- * Returns whether there is buffered data.
- */
- boolean continueBuffering(long positionUs);
-
- /**
- * Cleans up and releases everything.
- * @throws IOException
- */
- void release() throws IOException;
- }
-
- /**
- * A Track format which will be loaded and saved from the permanent storage for recordings.
- */
- public static class TrackFormat {
-
- /**
- * The track id for the specified track. The track id will be used as a track identifier
- * for recordings.
- */
- public final String trackId;
-
- /**
- * The {@link MediaFormat} for the specified track.
- */
- public final MediaFormat format;
-
- /**
- * Creates TrackFormat.
- * @param trackId
- * @param format
- */
- public TrackFormat(String trackId, MediaFormat format) {
- this.trackId = trackId;
- this.format = format;
- }
- }
-
- /**
- * A Holder for a sample position which will be loaded from the index file for recordings.
- */
- public static class PositionHolder {
-
- /**
- * The current sample position in microseconds.
- * The position is identical to the PTS(presentation time stamp) of the sample.
- */
- public final long positionUs;
-
- /**
- * Base sample position for the current {@link SampleChunk}.
- */
- public final long basePositionUs;
-
- /**
- * The file offset for the current sample in the current {@link SampleChunk}.
- */
- public final int offset;
-
- /**
- * Creates a holder for a specific position in the recording.
- * @param positionUs
- * @param offset
- */
- public PositionHolder(long positionUs, long basePositionUs, int offset) {
- this.positionUs = positionUs;
- this.basePositionUs = basePositionUs;
- this.offset = offset;
- }
- }
-
- /**
- * Storage configuration and policy manager for {@link BufferManager}
- */
- public interface StorageManager {
-
- /**
- * Provides eligible storage directory for {@link BufferManager}.
- *
- * @return a directory to save buffer(chunks) and meta files
- */
- File getBufferDir();
-
- /**
- * Informs whether the storage is used for persistent use. (eg. dvr recording/play)
- *
- * @return {@code true} if stored files are persistent
- */
- boolean isPersistent();
-
- /**
- * Informs whether the storage usage exceeds pre-determined size.
- *
- * @param bufferSize the current total usage of Storage in bytes.
- * @param pendingDelete the current storage usage which will be deleted in near future by
- * bytes
- * @return {@code true} if it reached pre-determined max size
- */
- boolean reachedStorageMax(long bufferSize, long pendingDelete);
-
- /**
- * Informs whether the storage has enough remained space.
- *
- * @param pendingDelete the current storage usage which will be deleted in near future by
- * bytes
- * @return {@code true} if it has enough space
- */
- boolean hasEnoughBuffer(long pendingDelete);
-
- /**
- * Reads track name & {@link MediaFormat} from storage.
- *
- * @param isAudio {@code true} if it is for audio track
- * @return {@link List} of TrackFormat
- */
- List<TrackFormat> readTrackInfoFiles(boolean isAudio);
-
- /**
- * Reads key sample positions for each written sample from storage.
- *
- * @param trackId track name
- * @return indexes of the specified track
- * @throws IOException
- */
- ArrayList<PositionHolder> readIndexFile(String trackId) throws IOException;
-
- /**
- * Writes track information to storage.
- *
- * @param formatList {@list List} of TrackFormat
- * @param isAudio {@code true} if it is for audio track
- * @throws IOException
- */
- void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio)
- throws IOException;
-
- /**
- * Writes index file to storage.
- *
- * @param trackName track name
- * @param index {@link SampleChunk} container
- * @throws IOException
- */
- void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
- throws IOException;
- }
-
- private static class EvictChunkQueueMap {
- private final Map<String, LinkedList<SampleChunk>> mEvictMap = new ArrayMap<>();
- private long mSize;
-
- private void init(String key) {
- mEvictMap.put(key, new LinkedList<>());
- }
-
- private void add(String key, SampleChunk chunk) {
- LinkedList<SampleChunk> queue = mEvictMap.get(key);
- if (queue != null) {
- mSize += chunk.getSize();
- queue.add(chunk);
- }
- }
-
- private SampleChunk poll(String key, long startPositionUs) {
- LinkedList<SampleChunk> queue = mEvictMap.get(key);
- if (queue != null) {
- SampleChunk chunk = queue.peek();
- if (chunk != null && chunk.getStartPositionUs() < startPositionUs) {
- mSize -= chunk.getSize();
- return queue.poll();
- }
- }
- return null;
- }
-
- private long getSize() {
- return mSize;
- }
-
- private void release() {
- for (Map.Entry<String, LinkedList<SampleChunk>> entry : mEvictMap.entrySet()) {
- for (SampleChunk chunk : entry.getValue()) {
- SampleChunk.IoState.release(chunk, true);
- }
- }
- mEvictMap.clear();
- mSize = 0;
- }
- }
-
- public BufferManager(StorageManager storageManager) {
- this(storageManager, new SampleChunk.SampleChunkCreator());
- }
-
- public BufferManager(StorageManager storageManager,
- SampleChunk.SampleChunkCreator sampleChunkCreator) {
- mStorageManager = storageManager;
- mSampleChunkCreator = sampleChunkCreator;
- }
-
- public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) {
- mEvictListeners.put(id, listener);
- }
-
- public void unregisterChunkEvictedListener(String id) {
- mEvictListeners.remove(id);
- }
-
- private static String getFileName(String id, long positionUs) {
- return String.format(Locale.ENGLISH, "%s_%016x.chunk", id, positionUs);
- }
-
- /**
- * Creates a new {@link SampleChunk} for caching samples if it is needed.
- *
- * @param id the name of the track
- * @param positionUs current position to write a sample in micro seconds.
- * @param samplePool {@link SamplePool} for the fast creation of samples.
- * @param currentChunk the current {@link SampleChunk} to write, {@code null} when to create
- * a new {@link SampleChunk}.
- * @param currentOffset the current offset to write.
- * @return returns the created {@link SampleChunk}.
- * @throws IOException
- */
- public SampleChunk createNewWriteFileIfNeeded(String id, long positionUs, SamplePool samplePool,
- SampleChunk currentChunk, int currentOffset) throws IOException {
- if (!maybeEvictChunk()) {
- throw new IOException("Not enough storage space");
- }
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
- if (map == null) {
- map = new TreeMap<>();
- mChunkMap.put(id, map);
- mStartPositionMap.put(id, positionUs);
- mPendingDelete.init(id);
- }
- if (currentChunk == null) {
- File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs));
- SampleChunk sampleChunk = mSampleChunkCreator
- .createSampleChunk(samplePool, file, positionUs, mChunkCallback);
- map.put(positionUs, new Pair(sampleChunk, 0));
- return sampleChunk;
- } else {
- map.put(positionUs, new Pair(currentChunk, currentOffset));
- return null;
- }
- }
-
- /**
- * Loads a track using {@link BufferManager.StorageManager}.
- *
- * @param trackId the name of the track.
- * @param samplePool {@link SamplePool} for the fast creation of samples.
- * @throws IOException
- */
- public void loadTrackFromStorage(String trackId, SamplePool samplePool) throws IOException {
- ArrayList<PositionHolder> keyPositions = mStorageManager.readIndexFile(trackId);
- long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0).positionUs : 0;
-
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId);
- if (map == null) {
- map = new TreeMap<>();
- mChunkMap.put(trackId, map);
- mStartPositionMap.put(trackId, startPositionUs);
- mPendingDelete.init(trackId);
- }
- SampleChunk chunk = null;
- long basePositionUs = -1;
- for (PositionHolder position: keyPositions) {
- if (position.basePositionUs != basePositionUs) {
- chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool,
- mStorageManager.getBufferDir(), getFileName(trackId, position.positionUs),
- position.positionUs, mChunkCallback, chunk);
- basePositionUs = position.basePositionUs;
- }
- map.put(position.positionUs, new Pair(chunk, position.offset));
- }
- }
-
- /**
- * Finds a {@link SampleChunk} for the specified track name and the position.
- *
- * @param id the name of the track.
- * @param positionUs the position.
- * @return returns the found {@link SampleChunk}.
- */
- public Pair<SampleChunk, Integer> getReadFile(String id, long positionUs) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id);
- if (map == null) {
- return null;
- }
- Pair<SampleChunk, Integer> ret;
- SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1);
- if (!headMap.isEmpty()) {
- ret = headMap.get(headMap.lastKey());
- } else {
- ret = map.get(map.firstKey());
- }
- return ret;
- }
-
- /**
- * Evicts chunks which are ready to be evicted for the specified track
- *
- * @param id the specified track
- * @param earlierThanPositionUs the start position of the {@link SampleChunk}
- * should be earlier than
- */
- public void evictChunks(String id, long earlierThanPositionUs) {
- SampleChunk chunk = null;
- while ((chunk = mPendingDelete.poll(id, earlierThanPositionUs)) != null) {
- SampleChunk.IoState.release(chunk, !mStorageManager.isPersistent()) ;
- }
- }
-
- /**
- * Returns the start position of the specified track in micro seconds.
- *
- * @param id the specified track
- */
- public long getStartPositionUs(String id) {
- Long ret = mStartPositionMap.get(id);
- return ret == null ? 0 : ret;
- }
-
- private boolean maybeEvictChunk() {
- long pendingDelete = mPendingDelete.getSize();
- while (mStorageManager.reachedStorageMax(mBufferSize, pendingDelete)
- || !mStorageManager.hasEnoughBuffer(pendingDelete)) {
- if (mStorageManager.isPersistent()) {
- // Since chunks are persistent, we cannot evict chunks.
- return false;
- }
- SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null;
- SampleChunk earliestChunk = null;
- String earliestChunkId = null;
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
- if (map.isEmpty()) {
- continue;
- }
- SampleChunk chunk = map.get(map.firstKey()).first;
- if (earliestChunk == null
- || chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) {
- earliestChunkMap = map;
- earliestChunk = chunk;
- earliestChunkId = entry.getKey();
- }
- }
- if (earliestChunk == null) {
- break;
- }
- mPendingDelete.add(earliestChunkId, earliestChunk);
- earliestChunkMap.remove(earliestChunk.getStartPositionUs());
- if (DEBUG) {
- Log.d(TAG, String.format("bufferSize = %d; pendingDelete = %b; "
- + "earliestChunk size = %d; %s@%d (%s)",
- mBufferSize, pendingDelete, earliestChunk.getSize(), earliestChunkId,
- earliestChunk.getStartPositionUs(),
- Utils.toIsoDateTimeString(earliestChunk.getCreatedTimeMs())));
- }
- ChunkEvictedListener listener = mEvictListeners.get(earliestChunkId);
- if (listener != null) {
- listener.onChunkEvicted(earliestChunkId, earliestChunk.getCreatedTimeMs());
- }
- pendingDelete = mPendingDelete.getSize();
- }
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue();
- if (map.isEmpty()) {
- continue;
- }
- mStartPositionMap.put(entry.getKey(), map.firstKey());
- }
- return true;
- }
-
- /**
- * Reads track information which includes {@link MediaFormat}.
- *
- * @return returns all track information which is found by {@link BufferManager.StorageManager}.
- * @throws IOException
- */
- public List<TrackFormat> readTrackInfoFiles() throws IOException {
- List<TrackFormat> trackFormatList = new ArrayList<>();
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(false));
- trackFormatList.addAll(mStorageManager.readTrackInfoFiles(true));
- if (trackFormatList.isEmpty()) {
- throw new IOException("No track information to load");
- }
- return trackFormatList;
- }
-
- /**
- * Writes track information and index information for all tracks.
- *
- * @param audios list of audio track information
- * @param videos list of audio track information
- * @throws IOException
- */
- public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos)
- throws IOException {
- if (audios.isEmpty() && videos.isEmpty()) {
- throw new IOException("No track information to save");
- }
- if (!audios.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(audios, true);
- for (TrackFormat trackFormat : audios) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Audio track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
- }
- }
- if (!videos.isEmpty()) {
- mStorageManager.writeTrackInfoFiles(videos, false);
- for (TrackFormat trackFormat : videos) {
- SortedMap<Long, Pair<SampleChunk, Integer>> map =
- mChunkMap.get(trackFormat.trackId);
- if (map == null) {
- throw new IOException("Video track index missing");
- }
- mStorageManager.writeIndexFile(trackFormat.trackId, map);
- }
- }
- }
-
- /**
- * Releases all the resources.
- */
- public void release() {
- try {
- mPendingDelete.release();
- for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry :
- mChunkMap.entrySet()) {
- SampleChunk toRelease = null;
- for (Pair<SampleChunk, Integer> positions : entry.getValue().values()) {
- if (toRelease != positions.first) {
- toRelease = positions.first;
- SampleChunk.IoState.release(toRelease, !mStorageManager.isPersistent());
- }
- }
- }
- mChunkMap.clear();
- } catch (ConcurrentModificationException | NullPointerException e) {
- // TODO: remove this after it it confirmed that race condition issues are resolved.
- // b/32492258, b/32373376
- SoftPreconditions.checkState(false, "Exception on BufferManager#release: ",
- e.toString());
- }
- }
-
- private void resetWriteStat(float writeBandwidth) {
- mWriteBandwidth = writeBandwidth;
- mTotalWriteSize = 0;
- mTotalWriteTimeNs = 0;
- }
-
- /**
- * Adds a disk write sample size to calculate the average disk write bandwidth.
- */
- public void addWriteStat(long size, long timeNs) {
- if (size >= mMinSampleSizeForSpeedCheck) {
- mTotalWriteSize += size;
- mTotalWriteTimeNs += timeNs;
- }
- }
-
- /**
- * Returns if the average disk write bandwidth is slower than
- * threshold {@code MINIMUM_DISK_WRITE_SPEED_MBPS}.
- */
- public boolean isWriteSlow() {
- if (mTotalWriteSize < MINIMUM_WRITE_SIZE_FOR_SPEED_CHECK) {
- return false;
- }
-
- // Checks write speed for only MAXIMUM_SPEED_CHECK_COUNT times to ignore outliers
- // by temporary system overloading during the playback.
- if (mSpeedCheckCount > MAXIMUM_SPEED_CHECK_COUNT) {
- return false;
- }
- mSpeedCheckCount++;
- float megabytePerSecond = calculateWriteBandwidth();
- resetWriteStat(megabytePerSecond);
- if (DEBUG) {
- Log.d(TAG, "Measured disk write performance: " + megabytePerSecond + "MBps");
- }
- return megabytePerSecond < MINIMUM_DISK_WRITE_SPEED_MBPS;
- }
-
- /**
- * Returns recent write bandwidth in MBps. If recent bandwidth is not available,
- * returns {float -1.0f}.
- */
- public float getWriteBandwidth() {
- return mWriteBandwidth == 0.0f ? -1.0f : mWriteBandwidth;
- }
-
- private float calculateWriteBandwidth() {
- if (mTotalWriteTimeNs == 0) {
- return -1;
- }
- return ((float) mTotalWriteSize * 1000 / mTotalWriteTimeNs);
- }
-
- /**
- * Returns if {@link BufferManager} has checked the write speed,
- * which is suitable for Trickplay.
- */
- @VisibleForTesting
- public boolean hasSpeedCheckDone() {
- return mSpeedCheckCount > 0;
- }
-
- /**
- * Sets minimum sample size for write speed check.
- * @param sampleSize minimum sample size for write speed check.
- */
- @VisibleForTesting
- public void setMinimumSampleSizeForSpeedCheck(int sampleSize) {
- mMinSampleSizeForSpeedCheck = sampleSize;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
deleted file mode 100644
index 6a09016c..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java
+++ /dev/null
@@ -1,392 +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.exoplayer.buffer;
-
-import android.media.MediaFormat;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-
-/**
- * Manages DVR storage.
- */
-public class DvrStorageManager implements BufferManager.StorageManager {
- private static final String TAG = "DvrStorageManager";
-
- // TODO: make serializable classes and use protobuf after internal data structure is finalized.
- private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO =
- "com.google.android.videos.pixelWidthHeightRatio";
- private static final String META_FILE_TYPE_AUDIO = "audio";
- private static final String META_FILE_TYPE_VIDEO = "video";
- private static final String META_FILE_TYPE_CAPTION = "caption";
- private static final String META_FILE_SUFFIX = ".meta";
- private static final String IDX_FILE_SUFFIX = ".idx";
- private static final String IDX_FILE_SUFFIX_V2 = IDX_FILE_SUFFIX + "2";
-
- // Size of minimum reserved storage buffer which will be used to save meta files
- // and index files after actual recording finished.
- private static final long MIN_BUFFER_BYTES = 256L * 1024 * 1024;
- private static final int NO_VALUE = -1;
- private static final long NO_VALUE_LONG = -1L;
-
- private final File mBufferDir;
-
- // {@code true} when this is for recording, {@code false} when this is for replaying.
- private final boolean mIsRecording;
-
- public DvrStorageManager(File file, boolean isRecording) {
- mBufferDir = file;
- mBufferDir.mkdirs();
- mIsRecording = isRecording;
- }
-
- @Override
- public File getBufferDir() {
- return mBufferDir;
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean reachedStorageMax(long bufferSize, long pendingDelete) {
- return false;
- }
-
- @Override
- public boolean hasEnoughBuffer(long pendingDelete) {
- return !mIsRecording || mBufferDir.getUsableSpace() >= MIN_BUFFER_BYTES;
- }
-
- private void readFormatInt(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- int val = in.readInt();
- if (val != NO_VALUE) {
- format.setInteger(key, val);
- }
- }
-
- private void readFormatLong(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- long val = in.readLong();
- if (val != NO_VALUE_LONG) {
- format.setLong(key, val);
- }
- }
-
- private void readFormatFloat(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- float val = in.readFloat();
- if (val != NO_VALUE) {
- format.setFloat(key, val);
- }
- }
-
- private String readString(DataInputStream in) throws IOException {
- int len = in.readInt();
- if (len <= 0) {
- return null;
- }
- byte [] strBytes = new byte[len];
- in.readFully(strBytes);
- return new String(strBytes, StandardCharsets.UTF_8);
- }
-
- private void readFormatString(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- String str = readString(in);
- if (str != null) {
- format.setString(key, str);
- }
- }
-
- private void readFormatStringOptional(DataInputStream in, MediaFormat format, String key) {
- try {
- String str = readString(in);
- if (str != null) {
- format.setString(key, str);
- }
- } catch (IOException e) {
- // Since we are reading optional field, ignore the exception.
- }
- }
-
- private ByteBuffer readByteBuffer(DataInputStream in) throws IOException {
- int len = in.readInt();
- if (len <= 0) {
- return null;
- }
- byte [] bytes = new byte[len];
- in.readFully(bytes);
- ByteBuffer buffer = ByteBuffer.allocate(len);
- buffer.put(bytes);
- buffer.flip();
-
- return buffer;
- }
-
- private void readFormatByteBuffer(DataInputStream in, MediaFormat format, String key)
- throws IOException {
- ByteBuffer buffer = readByteBuffer(in);
- if (buffer != null) {
- format.setByteBuffer(key, buffer);
- }
- }
-
- @Override
- public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
- List<BufferManager.TrackFormat> trackFormatList = new ArrayList<>();
- int index = 0;
- boolean trackNotFound = false;
- do {
- String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO)
- + ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
- String name = readString(in);
- MediaFormat format = new MediaFormat();
- readFormatString(in, format, MediaFormat.KEY_MIME);
- readFormatInt(in, format, MediaFormat.KEY_MAX_INPUT_SIZE);
- readFormatInt(in, format, MediaFormat.KEY_WIDTH);
- readFormatInt(in, format, MediaFormat.KEY_HEIGHT);
- readFormatInt(in, format, MediaFormat.KEY_CHANNEL_COUNT);
- readFormatInt(in, format, MediaFormat.KEY_SAMPLE_RATE);
- readFormatFloat(in, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
- for (int i = 0; i < 3; ++i) {
- readFormatByteBuffer(in, format, "csd-" + i);
- }
- readFormatLong(in, format, MediaFormat.KEY_DURATION);
-
- // This is optional since language field is added later.
- readFormatStringOptional(in, format, MediaFormat.KEY_LANGUAGE);
- trackFormatList.add(new BufferManager.TrackFormat(name, format));
- } catch (IOException e) {
- trackNotFound = true;
- }
- index++;
- } while(!trackNotFound);
- return trackFormatList;
- }
-
- /**
- * Reads caption information from files.
- *
- * @return a list of {@link AtscCaptionTrack} objects which store caption information.
- */
- public List<AtscCaptionTrack> readCaptionInfoFiles() {
- List<AtscCaptionTrack> tracks = new ArrayList<>();
- int index = 0;
- boolean trackNotFound = false;
- do {
- String fileName = META_FILE_TYPE_CAPTION +
- ((index == 0) ? META_FILE_SUFFIX : (index + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataInputStream in = new DataInputStream(new FileInputStream(file))) {
- byte[] data = new byte[(int) file.length()];
- in.read(data);
- tracks.add(AtscCaptionTrack.parseFrom(data));
- } catch (IOException e) {
- trackNotFound = true;
- }
- index++;
- } while(!trackNotFound);
- return tracks;
- }
-
- private ArrayList<BufferManager.PositionHolder> readOldIndexFile(File indexFile)
- throws IOException {
- ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>();
- try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) {
- long count = in.readLong();
- for (long i = 0; i < count; ++i) {
- long positionUs = in.readLong();
- indices.add(new BufferManager.PositionHolder(positionUs, positionUs, 0));
- }
- return indices;
- }
- }
-
- private ArrayList<BufferManager.PositionHolder> readNewIndexFile(File indexFile)
- throws IOException {
- ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>();
- try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) {
- long count = in.readLong();
- for (long i = 0; i < count; ++i) {
- long positionUs = in.readLong();
- long basePositionUs = in.readLong();
- int offset = in.readInt();
- indices.add(new BufferManager.PositionHolder(positionUs, basePositionUs, offset));
- }
- return indices;
- }
- }
-
- @Override
- public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId)
- throws IOException {
- File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX_V2);
- if (file.exists()) {
- return readNewIndexFile(file);
- } else {
- return readOldIndexFile(new File(getBufferDir(),trackId + IDX_FILE_SUFFIX));
- }
- }
-
- private void writeFormatInt(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeInt(format.getInteger(key));
- } else {
- out.writeInt(NO_VALUE);
- }
- }
-
- private void writeFormatLong(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeLong(format.getLong(key));
- } else {
- out.writeLong(NO_VALUE_LONG);
- }
- }
-
- private void writeFormatFloat(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- out.writeFloat(format.getFloat(key));
- } else {
- out.writeFloat(NO_VALUE);
- }
- }
-
- private void writeString(DataOutputStream out, String str) throws IOException {
- byte [] data = str.getBytes(StandardCharsets.UTF_8);
- out.writeInt(data.length);
- if (data.length > 0) {
- out.write(data);
- }
- }
-
- private void writeFormatString(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- writeString(out, format.getString(key));
- } else {
- out.writeInt(0);
- }
- }
-
- private void writeByteBuffer(DataOutputStream out, ByteBuffer buffer) throws IOException {
- byte [] data = new byte[buffer.limit()];
- buffer.get(data);
- buffer.flip();
- out.writeInt(data.length);
- if (data.length > 0) {
- out.write(data);
- } else {
- out.writeInt(0);
- }
- }
-
- private void writeFormatByteBuffer(DataOutputStream out, MediaFormat format, String key)
- throws IOException {
- if (format.containsKey(key)) {
- writeByteBuffer(out, format.getByteBuffer(key));
- } else {
- out.writeInt(0);
- }
- }
-
- @Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio)
- throws IOException {
- for (int i = 0; i < formatList.size() ; ++i) {
- BufferManager.TrackFormat trackFormat = formatList.get(i);
- String fileName = (isAudio ? META_FILE_TYPE_AUDIO : META_FILE_TYPE_VIDEO)
- + ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
- writeString(out, trackFormat.trackId);
- writeFormatString(out, trackFormat.format, MediaFormat.KEY_MIME);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_MAX_INPUT_SIZE);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_WIDTH);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_HEIGHT);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_CHANNEL_COUNT);
- writeFormatInt(out, trackFormat.format, MediaFormat.KEY_SAMPLE_RATE);
- writeFormatFloat(out, trackFormat.format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
- for (int j = 0; j < 3; ++j) {
- writeFormatByteBuffer(out, trackFormat.format, "csd-" + j);
- }
- writeFormatLong(out, trackFormat.format, MediaFormat.KEY_DURATION);
- writeFormatString(out, trackFormat.format, MediaFormat.KEY_LANGUAGE);
- }
- }
- }
-
- /**
- * Writes caption information to files.
- *
- * @param tracks a list of {@link AtscCaptionTrack} objects which store caption information.
- */
- public void writeCaptionInfoFiles(List<AtscCaptionTrack> tracks) {
- if (tracks == null || tracks.isEmpty()) {
- return;
- }
- for (int i = 0; i < tracks.size(); i++) {
- AtscCaptionTrack track = tracks.get(i);
- String fileName = META_FILE_TYPE_CAPTION +
- ((i == 0) ? META_FILE_SUFFIX : (i + META_FILE_SUFFIX));
- File file = new File(getBufferDir(), fileName);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) {
- out.write(MessageNano.toByteArray(track));
- } catch (Exception e) {
- Log.e(TAG, "Fail to write caption info to files", e);
- }
- }
- }
-
- @Override
- public void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index)
- throws IOException {
- File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2);
- try (DataOutputStream out = new DataOutputStream(new FileOutputStream(indexFile))) {
- out.writeLong(index.size());
- for (Map.Entry<Long, Pair<SampleChunk, Integer>> entry : index.entrySet()) {
- out.writeLong(entry.getKey());
- out.writeLong(entry.getValue().first.getStartPositionUs());
- out.writeInt(entry.getValue().second);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
deleted file mode 100644
index af0c3f0d..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * Copyright (C) 2016 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.exoplayer.buffer;
-
-import android.os.ConditionVariable;
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.util.Assertions;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Handles I/O between {@link SampleExtractor} and
- * {@link BufferManager}.Reads & writes samples from/to {@link SampleChunk} which is backed
- * by physical storage.
- */
-public class RecordingSampleBuffer implements BufferManager.SampleBuffer,
- BufferManager.ChunkEvictedListener {
- private static final String TAG = "RecordingSampleBuffer";
-
- @IntDef({BUFFER_REASON_LIVE_PLAYBACK, BUFFER_REASON_RECORDED_PLAYBACK, BUFFER_REASON_RECORDING})
- @Retention(RetentionPolicy.SOURCE)
- public @interface BufferReason {}
-
- /**
- * A buffer reason for live-stream playback.
- */
- public static final int BUFFER_REASON_LIVE_PLAYBACK = 0;
-
- /**
- * A buffer reason for playback of a recorded program.
- */
- public static final int BUFFER_REASON_RECORDED_PLAYBACK = 1;
-
- /**
- * A buffer reason for recording a program.
- */
- public static final int BUFFER_REASON_RECORDING = 2;
-
- /**
- * The minimum duration to support seek in Trickplay.
- */
- static final long MIN_SEEK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500);
-
- /**
- * The duration of a {@link SampleChunk} for recordings.
- */
- static final long RECORDING_CHUNK_DURATION_US = MIN_SEEK_DURATION_US * 1200; // 10 minutes
- private static final long BUFFER_WRITE_TIMEOUT_MS = 10 * 1000; // 10 seconds
- private static final long BUFFER_NEEDED_US =
- 1000L * Math.max(MpegTsPlayer.MIN_BUFFER_MS, MpegTsPlayer.MIN_REBUFFER_MS);
-
- private final BufferManager mBufferManager;
- private final PlaybackBufferListener mBufferListener;
- private final @BufferReason int mBufferReason;
-
- private int mTrackCount;
- private boolean[] mTrackSelected;
- private List<SampleQueue> mReadSampleQueues;
- private final SamplePool mSamplePool = new SamplePool();
- private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US;
- private long mCurrentPlaybackPositionUs = 0;
-
- // An error in I/O thread of {@link SampleChunkIoHelper} will be notified.
- private volatile boolean mError;
-
- // Eos was reached in I/O thread of {@link SampleChunkIoHelper}.
- private volatile boolean mEos;
- private SampleChunkIoHelper mSampleChunkIoHelper;
- private final SampleChunkIoHelper.IoCallback mIoCallback =
- new SampleChunkIoHelper.IoCallback() {
- @Override
- public void onIoReachedEos() {
- mEos = true;
- }
-
- @Override
- public void onIoError() {
- mError = true;
- }
- };
-
- /**
- * Creates {@link BufferManager.SampleBuffer} with
- * cached I/O backed by physical storage (e.g. trickplay,recording,recorded-playback).
- *
- * @param bufferManager the manager of {@link SampleChunk}
- * @param bufferListener the listener for buffer I/O event
- * @param enableTrickplay {@code true} when trickplay should be enabled
- * @param bufferReason the reason for caching samples {@link RecordingSampleBuffer.BufferReason}
- */
- public RecordingSampleBuffer(BufferManager bufferManager, PlaybackBufferListener bufferListener,
- boolean enableTrickplay, @BufferReason int bufferReason) {
- mBufferManager = bufferManager;
- mBufferListener = bufferListener;
- if (bufferListener != null) {
- bufferListener.onBufferStateChanged(enableTrickplay);
- }
- mBufferReason = bufferReason;
- }
-
- @Override
- public void init(@NonNull List<String> ids, @NonNull List<MediaFormat> mediaFormats)
- throws IOException {
- mTrackCount = ids.size();
- if (mTrackCount <= 0) {
- throw new IOException("No tracks to initialize");
- }
- mTrackSelected = new boolean[mTrackCount];
- mReadSampleQueues = new ArrayList<>();
- mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason,
- mBufferManager, mSamplePool, mIoCallback);
- for (int i = 0; i < mTrackCount; ++i) {
- mReadSampleQueues.add(i, new SampleQueue(mSamplePool));
- }
- mSampleChunkIoHelper.init();
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this);
- }
- }
-
- @Override
- public void selectTrack(int index) {
- if (!mTrackSelected[index]) {
- mTrackSelected[index] = true;
- mReadSampleQueues.get(index).clear();
- mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs);
- }
- }
-
- @Override
- public void deselectTrack(int index) {
- if (mTrackSelected[index]) {
- mTrackSelected[index] = false;
- mReadSampleQueues.get(index).clear();
- mSampleChunkIoHelper.closeRead(index);
- }
- }
-
- @Override
- public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
- throws IOException {
- mSampleChunkIoHelper.writeSample(index, sample, conditionVariable);
-
- if (!conditionVariable.block(BUFFER_WRITE_TIMEOUT_MS)) {
- Log.e(TAG, "Error: Serious delay on writing buffer");
- conditionVariable.block();
- }
- }
-
- @Override
- public boolean isWriteSpeedSlow(int sampleSize, long writeDurationNs) {
- if (mBufferReason == BUFFER_REASON_RECORDED_PLAYBACK) {
- return false;
- }
- mBufferManager.addWriteStat(sampleSize, writeDurationNs);
- return mBufferManager.isWriteSlow();
- }
-
- @Override
- public void handleWriteSpeedSlow() throws IOException{
- if (mBufferReason == BUFFER_REASON_RECORDING) {
- // Recording does not need to stop because I/O speed is slow temporarily.
- // If fixed size buffer of TsStreamer overflows, TsDataSource will reach EoS.
- // Reaching EoS will stop recording eventually.
- Log.w(TAG, "Disk I/O speed is slow for recording temporarily: "
- + mBufferManager.getWriteBandwidth() + "MBps");
- return;
- }
- // Disables buffering samples afterwards, and notifies the disk speed is slow.
- Log.w(TAG, "Disk is too slow for trickplay");
- mBufferListener.onDiskTooSlow();
- }
-
- @Override
- public void setEos() {
- mSampleChunkIoHelper.closeWrite();
- }
-
- private boolean maybeReadSample(SampleQueue queue, int index) {
- if (queue.getLastQueuedPositionUs() != null
- && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US
- && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) {
- // The speed of queuing samples can be higher than the playback speed.
- // If the duration of the samples in the queue is not limited,
- // samples can be accumulated and there can be out-of-memory issues.
- // But, the throttling should provide enough samples for the player to
- // finish the buffering state.
- return false;
- }
- SampleHolder sample = mSampleChunkIoHelper.readSample(index);
- if (sample != null) {
- queue.queueSample(sample);
- return true;
- }
- return false;
- }
-
- @Override
- public int readSample(int track, SampleHolder outSample) {
- Assertions.checkState(mTrackSelected[track]);
- maybeReadSample(mReadSampleQueues.get(track), track);
- int result = mReadSampleQueues.get(track).dequeueSample(outSample);
- if ((result != SampleSource.SAMPLE_READ && mEos) || mError) {
- return SampleSource.END_OF_STREAM;
- }
- return result;
- }
-
- @Override
- public void seekTo(long positionUs) {
- for (int i = 0; i < mTrackCount; ++i) {
- if (mTrackSelected[i]) {
- mReadSampleQueues.get(i).clear();
- mSampleChunkIoHelper.openRead(i, positionUs);
- }
- }
- mLastBufferedPositionUs = positionUs;
- }
-
- @Override
- public long getBufferedPositionUs() {
- Long result = null;
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mTrackSelected[i]) {
- continue;
- }
- Long lastQueuedSamplePositionUs =
- mReadSampleQueues.get(i).getLastQueuedPositionUs();
- if (lastQueuedSamplePositionUs == null) {
- // No sample has been queued.
- result = mLastBufferedPositionUs;
- continue;
- }
- if (result == null || result > lastQueuedSamplePositionUs) {
- result = lastQueuedSamplePositionUs;
- }
- }
- if (result == null) {
- return mLastBufferedPositionUs;
- }
- return (mLastBufferedPositionUs = result);
- }
-
- @Override
- public boolean continueBuffering(long positionUs) {
- mCurrentPlaybackPositionUs = positionUs;
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mTrackSelected[i]) {
- continue;
- }
- SampleQueue queue = mReadSampleQueues.get(i);
- maybeReadSample(queue, i);
- if (queue.getLastQueuedPositionUs() == null
- || positionUs > queue.getLastQueuedPositionUs()) {
- // No more buffered data.
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void release() throws IOException {
- if (mTrackCount <= 0) {
- return;
- }
- if (mSampleChunkIoHelper != null) {
- mSampleChunkIoHelper.release();
- }
- }
-
- // onChunkEvictedListener
- @Override
- public void onChunkEvicted(String id, long createdTimeMs) {
- if (mBufferListener != null) {
- mBufferListener.onBufferStartTimeChanged(
- createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US));
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
deleted file mode 100644
index 04b5a071..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2016 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.exoplayer.buffer;
-
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.google.android.exoplayer.SampleHolder;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-
-/**
- * {@link SampleChunk} stores samples into file and makes them available for read.
- * Stored file = { Header, Sample } * N
- * Header = sample size : int, sample flag : int, sample PTS in micro second : long
- */
-public class SampleChunk {
- private static final String TAG = "SampleChunk";
- private static final boolean DEBUG = false;
-
- private final long mCreatedTimeMs;
- private final long mStartPositionUs;
- private SampleChunk mNextChunk;
-
- // Header = sample size : int, sample flag : int, sample PTS in micro second : long
- private static final int SAMPLE_HEADER_LENGTH = 16;
-
- private final File mFile;
- private final ChunkCallback mChunkCallback;
- private final SamplePool mSamplePool;
- private RandomAccessFile mAccessFile;
- private long mWriteOffset;
- private boolean mWriteFinished;
- private boolean mIsReading;
- private boolean mIsWriting;
-
- /**
- * A callback for chunks being committed to permanent storage.
- */
- public static abstract class ChunkCallback {
-
- /**
- * Notifies when writing a SampleChunk is completed.
- *
- * @param chunk SampleChunk which is written completely
- */
- public void onChunkWrite(SampleChunk chunk) {
-
- }
-
- /**
- * Notifies when a SampleChunk is deleted.
- *
- * @param chunk SampleChunk which is deleted from storage
- */
- public void onChunkDelete(SampleChunk chunk) {
- }
- }
-
- /**
- * A class for SampleChunk creation.
- */
- public static class SampleChunkCreator {
-
- /**
- * Returns a newly created SampleChunk to read & write samples.
- *
- * @param samplePool sample allocator
- * @param file filename which will be created newly
- * @param startPositionUs the start position of the earliest sample to be stored
- * @param chunkCallback for total storage usage change notification
- */
- SampleChunk createSampleChunk(SamplePool samplePool, File file,
- long startPositionUs, ChunkCallback chunkCallback) {
- return new SampleChunk(samplePool, file, startPositionUs, System.currentTimeMillis(),
- chunkCallback);
- }
-
- /**
- * Returns a newly created SampleChunk which is backed by an existing file.
- * Created SampleChunk is read-only.
- *
- * @param samplePool sample allocator
- * @param bufferDir the directory where the file to read is located
- * @param filename the filename which will be read afterwards
- * @param startPositionUs the start position of the earliest sample in the file
- * @param chunkCallback for total storage usage change notification
- * @param prev the previous SampleChunk just before the newly created SampleChunk
- * @throws IOException
- */
- SampleChunk loadSampleChunkFromFile(SamplePool samplePool, File bufferDir,
- String filename, long startPositionUs, ChunkCallback chunkCallback,
- SampleChunk prev) throws IOException {
- File file = new File(bufferDir, filename);
- SampleChunk chunk =
- new SampleChunk(samplePool, file, startPositionUs, chunkCallback);
- if (prev != null) {
- prev.mNextChunk = chunk;
- }
- return chunk;
- }
- }
-
- /**
- * Handles I/O for SampleChunk.
- * Maintains current SampleChunk and the current offset for next I/O operation.
- */
- static class IoState {
- private SampleChunk mChunk;
- private long mCurrentOffset;
-
- private boolean equals(SampleChunk chunk, long offset) {
- return chunk == mChunk && mCurrentOffset == offset;
- }
-
- /**
- * Returns whether read I/O operation is finished.
- */
- boolean isReadFinished() {
- return mChunk == null;
- }
-
- /**
- * Returns the start position of the current SampleChunk
- */
- long getStartPositionUs() {
- return mChunk == null ? 0 : mChunk.getStartPositionUs();
- }
-
- private void reset(@Nullable SampleChunk chunk) {
- mChunk = chunk;
- mCurrentOffset = 0;
- }
-
- private void reset(SampleChunk chunk, long offset) {
- mChunk = chunk;
- mCurrentOffset = offset;
- }
-
- /**
- * Prepares for read I/O operation from a new SampleChunk.
- *
- * @param chunk the new SampleChunk to read from
- * @throws IOException
- */
- void openRead(SampleChunk chunk, long offset) throws IOException {
- if (mChunk != null) {
- mChunk.closeRead();
- }
- chunk.openRead();
- reset(chunk, offset);
- }
-
- /**
- * Prepares for write I/O operation to a new SampleChunk.
- *
- * @param chunk the new SampleChunk to write samples afterwards
- * @throws IOException
- */
- void openWrite(SampleChunk chunk) throws IOException{
- if (mChunk != null) {
- mChunk.closeWrite(chunk);
- }
- chunk.openWrite();
- reset(chunk);
- }
-
- /**
- * Reads a sample if it is available.
- *
- * @return Returns a sample if it is available, null otherwise.
- * @throws IOException
- */
- SampleHolder read() throws IOException {
- if (mChunk != null && mChunk.isReadFinished(this)) {
- SampleChunk next = mChunk.mNextChunk;
- mChunk.closeRead();
- if (next != null) {
- next.openRead();
- }
- reset(next);
- }
- if (mChunk != null) {
- try {
- return mChunk.read(this);
- } catch (IllegalStateException e) {
- // Write is finished and there is no additional buffer to read.
- Log.w(TAG, "Tried to read sample over EOS.");
- return null;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Writes a sample.
- *
- * @param sample to write
- * @param nextChunk if this is {@code null} writes at the current SampleChunk,
- * otherwise close current SampleChunk and writes at this
- * @throws IOException
- */
- void write(SampleHolder sample, SampleChunk nextChunk)
- throws IOException {
- if (nextChunk != null) {
- if (mChunk == null || mChunk.mNextChunk != null) {
- throw new IllegalStateException("Requested write for wrong SampleChunk");
- }
- mChunk.closeWrite(nextChunk);
- mChunk.mChunkCallback.onChunkWrite(mChunk);
- nextChunk.openWrite();
- reset(nextChunk);
- }
- mChunk.write(sample, this);
- }
-
- /**
- * Finishes write I/O operation.
- *
- * @throws IOException
- */
- void closeWrite() throws IOException {
- if (mChunk != null) {
- mChunk.closeWrite(null);
- }
- }
-
- /**
- * Returns the current SampleChunk for subsequent I/O operation.
- */
- SampleChunk getChunk() {
- return mChunk;
- }
-
- /**
- * Returns the current offset of the current SampleChunk for subsequent I/O operation.
- */
- long getOffset() {
- return mCurrentOffset;
- }
-
- /**
- * Releases SampleChunk. the SampleChunk will not be used anymore.
- *
- * @param chunk to release
- * @param delete {@code true} when the backed file needs to be deleted,
- * {@code false} otherwise.
- */
- static void release(SampleChunk chunk, boolean delete) {
- chunk.release(delete);
- }
- }
-
- @VisibleForTesting
- protected SampleChunk(SamplePool samplePool, File file, long startPositionUs,
- long createdTimeMs, ChunkCallback chunkCallback) {
- mStartPositionUs = startPositionUs;
- mCreatedTimeMs = createdTimeMs;
- mSamplePool = samplePool;
- mFile = file;
- mChunkCallback = chunkCallback;
- }
-
- // Constructor of SampleChunk which is backed by the given existing file.
- private SampleChunk(SamplePool samplePool, File file, long startPositionUs,
- ChunkCallback chunkCallback) throws IOException {
- mStartPositionUs = startPositionUs;
- mCreatedTimeMs = mStartPositionUs / 1000;
- mSamplePool = samplePool;
- mFile = file;
- mChunkCallback = chunkCallback;
- mWriteFinished = true;
- }
-
- private void openRead() throws IOException {
- if (!mIsReading) {
- if (mAccessFile == null) {
- mAccessFile = new RandomAccessFile(mFile, "r");
- }
- if (mWriteFinished && mWriteOffset == 0) {
- // Lazy loading of write offset, in order not to load
- // all SampleChunk's write offset at start time of recorded playback.
- mWriteOffset = mAccessFile.length();
- }
- mIsReading = true;
- }
- }
-
- private void openWrite() throws IOException {
- if (mWriteFinished) {
- throw new IllegalStateException("Opened for write though write is already finished");
- }
- if (!mIsWriting) {
- if (mIsReading) {
- throw new IllegalStateException("Write is requested for "
- + "an already opened SampleChunk");
- }
- mAccessFile = new RandomAccessFile(mFile, "rw");
- mIsWriting = true;
- }
- }
-
- private void CloseAccessFileIfNeeded() throws IOException {
- if (!mIsReading && !mIsWriting) {
- try {
- if (mAccessFile != null) {
- mAccessFile.close();
- }
- } finally {
- mAccessFile = null;
- }
- }
- }
-
- private void closeRead() throws IOException{
- if (mIsReading) {
- mIsReading = false;
- CloseAccessFileIfNeeded();
- }
- }
-
- private void closeWrite(SampleChunk nextChunk)
- throws IOException {
- if (mIsWriting) {
- mNextChunk = nextChunk;
- mIsWriting = false;
- mWriteFinished = true;
- CloseAccessFileIfNeeded();
- }
- }
-
- private boolean isReadFinished(IoState state) {
- return mWriteFinished && state.equals(this, mWriteOffset);
- }
-
- private SampleHolder read(IoState state) throws IOException {
- if (mAccessFile == null || state.mChunk != this) {
- throw new IllegalStateException("Requested read for wrong SampleChunk");
- }
- long offset = state.mCurrentOffset;
- if (offset >= mWriteOffset) {
- if (mWriteFinished) {
- throw new IllegalStateException("Requested read for wrong range");
- } else {
- if (offset != mWriteOffset) {
- Log.e(TAG, "This should not happen!");
- }
- return null;
- }
- }
- mAccessFile.seek(offset);
- int size = mAccessFile.readInt();
- SampleHolder sample = mSamplePool.acquireSample(size);
- sample.size = size;
- sample.flags = mAccessFile.readInt();
- sample.timeUs = mAccessFile.readLong();
- sample.clearData();
- sample.data.put(mAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY,
- offset + SAMPLE_HEADER_LENGTH, sample.size));
- offset += sample.size + SAMPLE_HEADER_LENGTH;
- state.mCurrentOffset = offset;
- return sample;
- }
-
- @VisibleForTesting
- protected void write(SampleHolder sample, IoState state)
- throws IOException {
- if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) {
- throw new IllegalStateException("Requested write for wrong SampleChunk");
- }
-
- mAccessFile.seek(mWriteOffset);
- mAccessFile.writeInt(sample.size);
- mAccessFile.writeInt(sample.flags);
- mAccessFile.writeLong(sample.timeUs);
- sample.data.position(0).limit(sample.size);
- mAccessFile.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data);
- mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH;
- state.mCurrentOffset = mWriteOffset;
- }
-
- private void release(boolean delete) {
- mWriteFinished = true;
- mIsReading = mIsWriting = false;
- try {
- if (mAccessFile != null) {
- mAccessFile.close();
- }
- } catch (IOException e) {
- // Since the SampleChunk will not be reused, ignore exception.
- }
- if (delete) {
- mFile.delete();
- mChunkCallback.onChunkDelete(this);
- }
- }
-
- /**
- * Returns the start position.
- */
- public long getStartPositionUs() {
- return mStartPositionUs;
- }
-
- /**
- * Returns the creation time.
- */
- public long getCreatedTimeMs() {
- return mCreatedTimeMs;
- }
-
- /**
- * Returns the current size.
- */
- public long getSize() {
- return mWriteOffset;
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
deleted file mode 100644
index ca97a91a..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2016 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.exoplayer.buffer;
-
-import android.media.MediaCodec;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer.BufferReason;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Handles all {@link SampleChunk} I/O operations.
- * An I/O dedicated thread handles all I/O operations for synchronization.
- */
-public class SampleChunkIoHelper implements Handler.Callback {
- private static final String TAG = "SampleChunkIoHelper";
-
- private static final int MAX_READ_BUFFER_SAMPLES = 3;
- private static final int READ_RESCHEDULING_DELAY_MS = 10;
-
- private static final int MSG_OPEN_READ = 1;
- private static final int MSG_OPEN_WRITE = 2;
- private static final int MSG_CLOSE_READ = 3;
- private static final int MSG_CLOSE_WRITE = 4;
- private static final int MSG_READ = 5;
- private static final int MSG_WRITE = 6;
- private static final int MSG_RELEASE = 7;
-
- private final long mSampleChunkDurationUs;
- private final int mTrackCount;
- private final List<String> mIds;
- private final List<MediaFormat> mMediaFormats;
- private final @BufferReason int mBufferReason;
- private final BufferManager mBufferManager;
- private final SamplePool mSamplePool;
- private final IoCallback mIoCallback;
-
- private Handler mIoHandler;
- private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[];
- private final ConcurrentLinkedQueue<SampleHolder> mHandlerReadSampleBuffers[];
- private final long[] mWriteIndexEndPositionUs;
- private final long[] mWriteChunkEndPositionUs;
- private final SampleChunk.IoState[] mReadIoStates;
- private final SampleChunk.IoState[] mWriteIoStates;
- private final Set<Integer> mSelectedTracks = new ArraySet<>();
- private long mBufferDurationUs = 0;
- private boolean mWriteEnded;
- private boolean mErrorNotified;
- private boolean mFinished;
-
- /**
- * A Callback for I/O events.
- */
- public static abstract class IoCallback {
-
- /**
- * Called when there is no sample to read.
- */
- public void onIoReachedEos() {
- }
-
- /**
- * Called when there is an irrecoverable error during I/O.
- */
- public void onIoError() {
- }
- }
-
- private class IoParams {
- private final int index;
- private final long positionUs;
- private final SampleHolder sample;
- private final ConditionVariable conditionVariable;
- private final ConcurrentLinkedQueue<SampleHolder> readSampleBuffer;
-
- private IoParams(int index, long positionUs, SampleHolder sample,
- ConditionVariable conditionVariable,
- ConcurrentLinkedQueue<SampleHolder> readSampleBuffer) {
- this.index = index;
- this.positionUs = positionUs;
- this.sample = sample;
- this.conditionVariable = conditionVariable;
- this.readSampleBuffer = readSampleBuffer;
- }
- }
-
- /**
- * Creates {@link SampleChunk} I/O handler.
- *
- * @param ids track names
- * @param mediaFormats {@link android.media.MediaFormat} for each track
- * @param bufferReason reason to be buffered
- * @param bufferManager manager of {@link SampleChunk} collections
- * @param samplePool allocator for a sample
- * @param ioCallback listeners for I/O events
- */
- public SampleChunkIoHelper(List<String> ids, List<MediaFormat> mediaFormats,
- @BufferReason int bufferReason, BufferManager bufferManager, SamplePool samplePool,
- IoCallback ioCallback) {
- mTrackCount = ids.size();
- mIds = ids;
- mMediaFormats = mediaFormats;
- mBufferReason = bufferReason;
- mBufferManager = bufferManager;
- mSamplePool = samplePool;
- mIoCallback = ioCallback;
-
- mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
- mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount];
- mWriteIndexEndPositionUs = new long[mTrackCount];
- mWriteChunkEndPositionUs = new long[mTrackCount];
- mReadIoStates = new SampleChunk.IoState[mTrackCount];
- mWriteIoStates = new SampleChunk.IoState[mTrackCount];
-
- // Small chunk duration for live playback will give more fine grained storage usage
- // and eviction handling for trickplay.
- mSampleChunkDurationUs =
- bufferReason == RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK ?
- RecordingSampleBuffer.MIN_SEEK_DURATION_US :
- RecordingSampleBuffer.RECORDING_CHUNK_DURATION_US;
- for (int i = 0; i < mTrackCount; ++i) {
- mWriteIndexEndPositionUs[i] = RecordingSampleBuffer.MIN_SEEK_DURATION_US;
- mWriteChunkEndPositionUs[i] = mSampleChunkDurationUs;
- mReadIoStates[i] = new SampleChunk.IoState();
- mWriteIoStates[i] = new SampleChunk.IoState();
- }
- }
-
- /**
- * Prepares and initializes for I/O operations.
- *
- * @throws IOException
- */
- public void init() throws IOException {
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mIoHandler = new Handler(handlerThread.getLooper(), this);
- if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK) {
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.loadTrackFromStorage(mIds.get(i), mSamplePool);
- }
- mWriteEnded = true;
- } else {
- for (int i = 0; i < mTrackCount; ++i) {
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_WRITE, i));
- }
- }
- }
-
- /**
- * Reads a sample if it is available.
- *
- * @param index track index
- * @return {@code null} if a sample is not available, otherwise returns a sample
- */
- public SampleHolder readSample(int index) {
- SampleHolder sample = mReadSampleBuffers[index].poll();
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index));
- return sample;
- }
-
- /**
- * Writes a sample.
- *
- * @param index track index
- * @param sample to write
- * @param conditionVariable which will be wait until the write is finished
- * @throws IOException
- */
- public void writeSample(int index, SampleHolder sample,
- ConditionVariable conditionVariable) throws IOException {
- if (mErrorNotified) {
- throw new IOException("Storage I/O error happened");
- }
- conditionVariable.close();
- IoParams params = new IoParams(index, 0, sample, conditionVariable, null);
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_WRITE, params));
- }
-
- /**
- * Starts read from the specified position.
- *
- * @param index track index
- * @param positionUs the specified position
- */
- public void openRead(int index, long positionUs) {
- // Old mReadSampleBuffers may have a pending read.
- mReadSampleBuffers[index] = new ConcurrentLinkedQueue<>();
- IoParams params = new IoParams(index, positionUs, null, null, mReadSampleBuffers[index]);
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_OPEN_READ, params));
- }
-
- /**
- * Closes read from the specified track.
- *
- * @param index track index
- */
- public void closeRead(int index) {
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_CLOSE_READ, index));
- }
-
- /**
- * Notifies writes are finished.
- */
- public void closeWrite() {
- mIoHandler.sendEmptyMessage(MSG_CLOSE_WRITE);
- }
-
- /**
- * Finishes I/O operations and releases all the resources.
- * @throws IOException
- */
- public void release() throws IOException {
- if (mIoHandler == null) {
- return;
- }
- // Finishes all I/O operations.
- ConditionVariable conditionVariable = new ConditionVariable();
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_RELEASE, conditionVariable));
- conditionVariable.block();
-
- for (int i = 0; i < mTrackCount; ++i) {
- mBufferManager.unregisterChunkEvictedListener(mIds.get(i));
- }
- try {
- if (mBufferReason == RecordingSampleBuffer.BUFFER_REASON_RECORDING && mTrackCount > 0) {
- // Saves meta information for recording.
- List<BufferManager.TrackFormat> audios = new LinkedList<>();
- List<BufferManager.TrackFormat> videos = new LinkedList<>();
- for (int i = 0; i < mTrackCount; ++i) {
- android.media.MediaFormat format =
- mMediaFormats.get(i).getFrameworkMediaFormatV16();
- format.setLong(android.media.MediaFormat.KEY_DURATION, mBufferDurationUs);
- if (MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) {
- audios.add(new BufferManager.TrackFormat(mIds.get(i), format));
- } else if (MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) {
- videos.add(new BufferManager.TrackFormat(mIds.get(i), format));
- }
- }
- mBufferManager.writeMetaFiles(audios, videos);
- }
- } finally {
- mBufferManager.release();
- mIoHandler.getLooper().quitSafely();
- }
- }
-
- @Override
- public boolean handleMessage(Message message) {
- if (mFinished) {
- return true;
- }
- releaseEvictedChunks();
- try {
- switch (message.what) {
- case MSG_OPEN_READ:
- doOpenRead((IoParams) message.obj);
- return true;
- case MSG_OPEN_WRITE:
- doOpenWrite((int) message.obj);
- return true;
- case MSG_CLOSE_READ:
- doCloseRead((int) message.obj);
- return true;
- case MSG_CLOSE_WRITE:
- doCloseWrite();
- return true;
- case MSG_READ:
- doRead((int) message.obj);
- return true;
- case MSG_WRITE:
- doWrite((IoParams) message.obj);
- // Since only write will increase storage, eviction will be handled here.
- return true;
- case MSG_RELEASE:
- doRelease((ConditionVariable) message.obj);
- return true;
- }
- } catch (IOException e) {
- mIoCallback.onIoError();
- mErrorNotified = true;
- Log.e(TAG, "IoException happened", e);
- return true;
- }
- return false;
- }
-
- private void doOpenRead(IoParams params) throws IOException {
- int index = params.index;
- mIoHandler.removeMessages(MSG_READ, index);
- Pair<SampleChunk, Integer> readPosition =
- mBufferManager.getReadFile(mIds.get(index), params.positionUs);
- if (readPosition == null) {
- String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs
- + "is not found";
- SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage);
- throw new IOException(errorMessage);
- }
- mSelectedTracks.add(index);
- mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second);
- if (mHandlerReadSampleBuffers[index] != null) {
- SampleHolder sample;
- while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) {
- mSamplePool.releaseSample(sample);
- }
- }
- mHandlerReadSampleBuffers[index] = params.readSampleBuffer;
- mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index));
- }
-
- private void doOpenWrite(int index) throws IOException {
- SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0,
- mSamplePool, null, 0);
- mWriteIoStates[index].openWrite(chunk);
- }
-
- private void doCloseRead(int index) {
- mSelectedTracks.remove(index);
- if (mHandlerReadSampleBuffers[index] != null) {
- SampleHolder sample;
- while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) {
- mSamplePool.releaseSample(sample);
- }
- }
- mIoHandler.removeMessages(MSG_READ, index);
- }
-
- private void doRead(int index) throws IOException {
- mIoHandler.removeMessages(MSG_READ, index);
- if (mHandlerReadSampleBuffers[index].size() >= MAX_READ_BUFFER_SAMPLES) {
- // If enough samples are buffered, try again few moments later hoping that
- // buffered samples are consumed.
- mIoHandler.sendMessageDelayed(
- mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS);
- } else {
- if (mReadIoStates[index].isReadFinished()) {
- for (int i = 0; i < mTrackCount; ++i) {
- if (!mReadIoStates[i].isReadFinished()) {
- return;
- }
- }
- mIoCallback.onIoReachedEos();
- return;
- }
- SampleHolder sample = mReadIoStates[index].read();
- if (sample != null) {
- mHandlerReadSampleBuffers[index].offer(sample);
- } else {
- // Read reached write but write is not finished yet --- wait a few moments to
- // see if another sample is written.
- mIoHandler.sendMessageDelayed(
- mIoHandler.obtainMessage(MSG_READ, index),
- READ_RESCHEDULING_DELAY_MS);
- }
- }
- }
-
- private void doWrite(IoParams params) throws IOException {
- try {
- if (mWriteEnded) {
- SoftPreconditions.checkState(false);
- return;
- }
- int index = params.index;
- SampleHolder sample = params.sample;
- SampleChunk nextChunk = null;
- if ((sample.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
- if (sample.timeUs > mBufferDurationUs) {
- mBufferDurationUs = sample.timeUs;
- }
- if (sample.timeUs >= mWriteIndexEndPositionUs[index]) {
- SampleChunk currentChunk = sample.timeUs >= mWriteChunkEndPositionUs[index] ?
- null : mWriteIoStates[params.index].getChunk();
- int currentOffset = (int) mWriteIoStates[params.index].getOffset();
- nextChunk = mBufferManager.createNewWriteFileIfNeeded(
- mIds.get(index), mWriteIndexEndPositionUs[index], mSamplePool,
- currentChunk, currentOffset);
- mWriteIndexEndPositionUs[index] =
- ((sample.timeUs / RecordingSampleBuffer.MIN_SEEK_DURATION_US) + 1) *
- RecordingSampleBuffer.MIN_SEEK_DURATION_US;
- if (nextChunk != null) {
- mWriteChunkEndPositionUs[index] =
- ((sample.timeUs / mSampleChunkDurationUs) + 1)
- * mSampleChunkDurationUs;
- }
- }
- }
- mWriteIoStates[params.index].write(params.sample, nextChunk);
- } finally {
- params.conditionVariable.open();
- }
- }
-
- private void doCloseWrite() throws IOException {
- if (mWriteEnded) {
- return;
- }
- mWriteEnded = true;
- boolean readFinished = true;
- for (int i = 0; i < mTrackCount; ++i) {
- readFinished = readFinished && mReadIoStates[i].isReadFinished();
- mWriteIoStates[i].closeWrite();
- }
- if (readFinished) {
- mIoCallback.onIoReachedEos();
- }
- }
-
- private void doRelease(ConditionVariable conditionVariable) {
- mIoHandler.removeCallbacksAndMessages(null);
- mFinished = true;
- conditionVariable.open();
- mSelectedTracks.clear();
- }
-
- private void releaseEvictedChunks() {
- if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK
- || mSelectedTracks.isEmpty()) {
- return;
- }
- long currentStartPositionUs = Long.MAX_VALUE;
- for (int trackIndex : mSelectedTracks) {
- currentStartPositionUs = Math.min(currentStartPositionUs,
- mReadIoStates[trackIndex].getStartPositionUs());
- }
- for (int i = 0; i < mTrackCount; ++i) {
- long evictEndPositionUs = Math.min(mBufferManager.getStartPositionUs(mIds.get(i)),
- currentStartPositionUs);
- mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java b/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java
deleted file mode 100644
index bb048e85..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SamplePool.java
+++ /dev/null
@@ -1,71 +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.exoplayer.buffer;
-
-import com.google.android.exoplayer.SampleHolder;
-
-import java.util.LinkedList;
-
-/**
- * Pool of samples to recycle ByteBuffers as much as possible.
- */
-public class SamplePool {
- private final LinkedList<SampleHolder> mSamplePool = new LinkedList<>();
-
- /**
- * Acquires a sample with a buffer larger than size from the pool. Allocate new one or resize
- * an existing buffer if necessary.
- */
- public synchronized SampleHolder acquireSample(int size) {
- if (mSamplePool.isEmpty()) {
- SampleHolder sample = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
- sample.ensureSpaceForWrite(size);
- return sample;
- }
- SampleHolder smallestSufficientSample = null;
- SampleHolder maxSample = mSamplePool.getFirst();
- for (SampleHolder sample : mSamplePool) {
- // Grab the smallest sufficient sample.
- if (sample.data.capacity() >= size && (smallestSufficientSample == null
- || smallestSufficientSample.data.capacity() > sample.data.capacity())) {
- smallestSufficientSample = sample;
- }
-
- // Grab the max size sample.
- if (maxSample.data.capacity() < sample.data.capacity()) {
- maxSample = sample;
- }
- }
- SampleHolder sampleFromPool = smallestSufficientSample;
-
- // If there's no sufficient sample, grab the maximum sample and resize it to size.
- if (sampleFromPool == null) {
- sampleFromPool = maxSample;
- sampleFromPool.ensureSpaceForWrite(size);
- }
- mSamplePool.remove(sampleFromPool);
- return sampleFromPool;
- }
-
- /**
- * Releases the sample back to the pool.
- */
- public synchronized void releaseSample(SampleHolder sample) {
- sample.clearData();
- mSamplePool.offerLast(sample);
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
deleted file mode 100644
index 75eac5a2..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java
+++ /dev/null
@@ -1,75 +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.exoplayer.buffer;
-
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-
-import java.util.LinkedList;
-
-/**
- * A sample queue which reads from the buffer and passes to player pipeline.
- */
-public class SampleQueue {
- private final LinkedList<SampleHolder> mQueue = new LinkedList<>();
- private final SamplePool mSamplePool;
- private Long mLastQueuedPositionUs = null;
-
- public SampleQueue(SamplePool samplePool) {
- mSamplePool = samplePool;
- }
-
- public void queueSample(SampleHolder sample) {
- mQueue.offer(sample);
- mLastQueuedPositionUs = sample.timeUs;
- }
-
- public int dequeueSample(SampleHolder sample) {
- SampleHolder sampleFromQueue = mQueue.poll();
- if (sampleFromQueue == null) {
- return SampleSource.NOTHING_READ;
- }
- sample.ensureSpaceForWrite(sampleFromQueue.size);
- sample.size = sampleFromQueue.size;
- sample.flags = sampleFromQueue.flags;
- sample.timeUs = sampleFromQueue.timeUs;
- sample.clearData();
- sampleFromQueue.data.position(0).limit(sample.size);
- sample.data.put(sampleFromQueue.data);
- mSamplePool.releaseSample(sampleFromQueue);
- return SampleSource.SAMPLE_READ;
- }
-
- public void clear() {
- while (!mQueue.isEmpty()) {
- mSamplePool.releaseSample(mQueue.poll());
- }
- mLastQueuedPositionUs = null;
- }
-
- public Long getLastQueuedPositionUs() {
- return mLastQueuedPositionUs;
- }
-
- public boolean isDurationGreaterThan(long durationUs) {
- return !mQueue.isEmpty() && mQueue.getLast().timeUs - mQueue.getFirst().timeUs > durationUs;
- }
-
- public boolean isEmpty() {
- return mQueue.isEmpty();
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
deleted file mode 100644
index 159fde18..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java
+++ /dev/null
@@ -1,180 +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.exoplayer.buffer;
-
-import android.os.ConditionVariable;
-
-import android.support.annotation.NonNull;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.tvinput.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Handles I/O for {@link SampleExtractor} when
- * physical storage based buffer is not used. Trickplay is disabled.
- */
-public class SimpleSampleBuffer implements BufferManager.SampleBuffer {
- private final SamplePool mSamplePool = new SamplePool();
- private SampleQueue[] mPlayingSampleQueues;
- private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US;
-
- private volatile boolean mEos;
-
- public SimpleSampleBuffer(PlaybackBufferListener bufferListener) {
- if (bufferListener != null) {
- // Disables trickplay.
- bufferListener.onBufferStateChanged(false);
- }
- }
-
- @Override
- public synchronized void init(@NonNull List<String> ids,
- @NonNull List<MediaFormat> mediaFormats) {
- int trackCount = ids.size();
- mPlayingSampleQueues = new SampleQueue[trackCount];
- for (int i = 0; i < trackCount; i++) {
- mPlayingSampleQueues[i] = null;
- }
- }
-
- @Override
- public void setEos() {
- mEos = true;
- }
-
- private boolean reachedEos() {
- return mEos;
- }
-
- @Override
- public void selectTrack(int index) {
- synchronized (this) {
- if (mPlayingSampleQueues[index] == null) {
- mPlayingSampleQueues[index] = new SampleQueue(mSamplePool);
- } else {
- mPlayingSampleQueues[index].clear();
- }
- }
- }
-
- @Override
- public void deselectTrack(int index) {
- synchronized (this) {
- if (mPlayingSampleQueues[index] != null) {
- mPlayingSampleQueues[index].clear();
- mPlayingSampleQueues[index] = null;
- }
- }
- }
-
- @Override
- public synchronized long getBufferedPositionUs() {
- Long result = null;
- for (SampleQueue queue : mPlayingSampleQueues) {
- if (queue == null) {
- continue;
- }
- Long lastQueuedSamplePositionUs = queue.getLastQueuedPositionUs();
- if (lastQueuedSamplePositionUs == null) {
- // No sample has been queued.
- result = mLastBufferedPositionUs;
- continue;
- }
- if (result == null || result > lastQueuedSamplePositionUs) {
- result = lastQueuedSamplePositionUs;
- }
- }
- if (result == null) {
- return mLastBufferedPositionUs;
- }
- return (mLastBufferedPositionUs = result);
- }
-
- @Override
- public synchronized int readSample(int track, SampleHolder sampleHolder) {
- SampleQueue queue = mPlayingSampleQueues[track];
- SoftPreconditions.checkNotNull(queue);
- int result = queue == null ? SampleSource.NOTHING_READ : queue.dequeueSample(sampleHolder);
- if (result != SampleSource.SAMPLE_READ && reachedEos()) {
- return SampleSource.END_OF_STREAM;
- }
- return result;
- }
-
- @Override
- public void writeSample(int index, SampleHolder sample,
- ConditionVariable conditionVariable) throws IOException {
- sample.data.position(0).limit(sample.size);
- SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size);
- sampleToQueue.size = sample.size;
- sampleToQueue.clearData();
- sampleToQueue.data.put(sample.data);
- sampleToQueue.timeUs = sample.timeUs;
- sampleToQueue.flags = sample.flags;
-
- synchronized (this) {
- if (mPlayingSampleQueues[index] != null) {
- mPlayingSampleQueues[index].queueSample(sampleToQueue);
- }
- }
- }
-
- @Override
- public boolean isWriteSpeedSlow(int sampleSize, long durationNs) {
- // Since SimpleSampleBuffer write samples only to memory (not to physical storage),
- // write speed is always fine.
- return false;
- }
-
- @Override
- public void handleWriteSpeedSlow() {
- // no-op
- }
-
- @Override
- public synchronized boolean continueBuffering(long positionUs) {
- for (SampleQueue queue : mPlayingSampleQueues) {
- if (queue == null) {
- continue;
- }
- if (queue.getLastQueuedPositionUs() == null
- || positionUs > queue.getLastQueuedPositionUs()) {
- // No more buffered data.
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void seekTo(long positionUs) {
- // Not used.
- }
-
- @Override
- public void release() {
- // Not used.
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
deleted file mode 100644
index 9fe921b8..00000000
--- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java
+++ /dev/null
@@ -1,147 +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.exoplayer.buffer;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.util.Pair;
-
-import com.android.tv.common.SoftPreconditions;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedMap;
-
-/**
- * Manages Trickplay storage.
- */
-public class TrickplayStorageManager implements BufferManager.StorageManager {
- // TODO: Support multi-sessions.
- private static final String BUFFER_DIR = "timeshift";
-
- // Copied from android.provider.Settings.Global (hidden fields)
- private static final String
- SYS_STORAGE_THRESHOLD_PERCENTAGE = "sys_storage_threshold_percentage";
- private static final String
- SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes";
-
- // Copied from android.os.StorageManager
- private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
- private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500L * 1024 * 1024;
-
- private static AsyncTask<Void, Void, Void> sLastCacheCleanUpTask;
- private static File sBufferDir;
- private static long sStorageBufferBytes;
-
- private final long mMaxBufferSize;
-
- private static void initParamsIfNeeded(Context context, @NonNull File path) {
- // TODO: Support multi-sessions.
- SoftPreconditions.checkState(
- sBufferDir == null || sBufferDir.equals(path));
- if (path.equals(sBufferDir)) {
- return;
- }
- sBufferDir = path;
- long lowPercentage = Settings.Global.getInt(context.getContentResolver(),
- SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
- long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100;
- long maxLowBytes = Settings.Global.getLong(context.getContentResolver(),
- SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
- sStorageBufferBytes = Math.min(lowPercentageToBytes, maxLowBytes);
- }
-
- public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) {
- initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR));
- sBufferDir.mkdirs();
- mMaxBufferSize = maxBufferSize;
- clearStorage();
- }
-
- private void clearStorage() {
- long now = System.currentTimeMillis();
- if (sLastCacheCleanUpTask != null) {
- sLastCacheCleanUpTask.cancel(true);
- }
- sLastCacheCleanUpTask = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- if (isCancelled()) {
- return null;
- }
- File files[] = sBufferDir.listFiles();
- if (files == null || files.length == 0) {
- return null;
- }
- for (File file : files) {
- if (isCancelled()) {
- break;
- }
- long lastModified = file.lastModified();
- if (lastModified != 0 && lastModified < now) {
- file.delete();
- }
- }
- return null;
- }
- };
- sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- @Override
- public File getBufferDir() {
- return sBufferDir;
- }
-
- @Override
- public boolean isPersistent() {
- return false;
- }
-
- @Override
- public boolean reachedStorageMax(long bufferSize, long pendingDelete) {
- return bufferSize - pendingDelete > mMaxBufferSize;
- }
-
- @Override
- public boolean hasEnoughBuffer(long pendingDelete) {
- return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes;
- }
-
- @Override
- public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) {
- return null;
- }
-
- @Override
- public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId) {
- return null;
- }
-
- @Override
- public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) {
- }
-
- @Override
- public void writeIndexFile(String trackName,
- SortedMap<Long, Pair<SampleChunk, Integer>> index) {
- }
-
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java
deleted file mode 100644
index 356636cc..00000000
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderClient.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer.ffmpeg;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import android.support.annotation.VisibleForTesting;
-import com.google.android.exoplayer.SampleHolder;
-import com.android.tv.Features;
-import com.android.tv.tuner.exoplayer.audio.AudioDecoder;
-
-import java.nio.ByteBuffer;
-
-/**
- * The class connects {@link FfmpegDecoderService} to decode audio samples.
- * In order to sandbox ffmpeg based decoder, {@link FfmpegDecoderService} is an isolated process
- * without any permission and connected by binder.
- */
-public class FfmpegDecoderClient extends AudioDecoder {
- private static FfmpegDecoderClient sInstance;
-
- private IFfmpegDecoder mService;
- private Boolean mIsAvailable;
-
- private static final String FFMPEG_DECODER_SERVICE_FILTER =
- "com.android.tv.tuner.exoplayer.ffmpeg.IFfmpegDecoder";
- private static final long FFMPEG_SERVICE_CONNECT_TIMEOUT_MS = 500;
-
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = IFfmpegDecoder.Stub.asInterface(service);
- synchronized (FfmpegDecoderClient.this) {
- try {
- mIsAvailable = mService.isAvailable();
- } catch (RemoteException e) {
- }
- FfmpegDecoderClient.this.notify();
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- synchronized (FfmpegDecoderClient.this) {
- sInstance.releaseLocked();
- mIsAvailable = false;
- mService = null;
- }
- }
- };
-
- /**
- * Connects to the decoder service for future uses.
- * @param context
- * @return {@code true} when decoder service is connected.
- */
- @MainThread
- public synchronized static boolean connect(Context context) {
- if (Features.AC3_SOFTWARE_DECODE.isEnabled(context)) {
- if (sInstance == null) {
- sInstance = new FfmpegDecoderClient();
- Intent intent =
- new Intent(FFMPEG_DECODER_SERVICE_FILTER)
- .setComponent(
- new ComponentName(context, FfmpegDecoderService.class));
- if (context.bindService(intent, sInstance.mConnection, Context.BIND_AUTO_CREATE)) {
- return true;
- } else {
- sInstance = null;
- }
- }
- }
- return false;
- }
-
- /**
- * Disconnects from the decoder service and release resources.
- * @param context
- */
- @MainThread
- public synchronized static void disconnect(Context context) {
- if (sInstance != null) {
- synchronized (sInstance) {
- sInstance.releaseLocked();
- if (sInstance.mIsAvailable != null && sInstance.mIsAvailable) {
- context.unbindService(sInstance.mConnection);
- }
- sInstance.mIsAvailable = false;
- sInstance.mService = null;
- }
- sInstance = null;
- }
- }
-
- /**
- * Returns whether service is available or not.
- * Before using client, this should be used to check availability.
- */
- @WorkerThread
- public synchronized static boolean isAvailable() {
- if (sInstance != null) {
- return sInstance.available();
- }
- return false;
- }
-
- /**
- * Returns an client instance.
- */
- public synchronized static FfmpegDecoderClient getInstance() {
- if (sInstance != null) {
- sInstance.createDecoder();
- }
- return sInstance;
- }
-
- private FfmpegDecoderClient() {
- }
-
- private synchronized boolean available() {
- if (mIsAvailable == null) {
- try {
- this.wait(FFMPEG_SERVICE_CONNECT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- }
- }
- return mIsAvailable != null && mIsAvailable == true;
- }
-
- private synchronized void createDecoder() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.create();
- } catch (RemoteException e) {
- }
- }
-
- private void releaseLocked() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.release();
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized void release() {
- releaseLocked();
- }
-
- @Override
- public synchronized void decode(SampleHolder sampleHolder) {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- byte[] sampleBytes = new byte [sampleHolder.data.limit()];
- sampleHolder.data.get(sampleBytes, 0, sampleBytes.length);
- try {
- mService.decode(sampleHolder.timeUs, sampleBytes);
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized void resetDecoderState(String mimeType) {
- if (mIsAvailable == null || mIsAvailable == false) {
- return;
- }
- try {
- mService.resetDecoderState(mimeType);
- } catch (RemoteException e) {
- }
- }
-
- @Override
- public synchronized ByteBuffer getDecodedSample() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return null;
- }
- try {
- byte[] outputBytes = mService.getDecodedSample();
- if (outputBytes != null && outputBytes.length > 0) {
- return ByteBuffer.wrap(outputBytes);
- }
- } catch (RemoteException e) {
- }
- return null;
- }
-
- @Override
- public synchronized long getDecodedTimeUs() {
- if (mIsAvailable == null || mIsAvailable == false) {
- return 0;
- }
- try {
- return mService.getDecodedTimeUs();
- } catch (RemoteException e) {
- }
- return 0;
- }
-
- @VisibleForTesting
- public boolean testSandboxIsolatedProcess() {
- // When testing isolated process, we will check the permission in FfmpegDecoderService.
- // If the service have any permission, an exception will be thrown.
- try {
- mService.testSandboxIsolatedProcess();
- } catch (RemoteException e) {
- return false;
- }
- return true;
- }
-
- @VisibleForTesting
- public void testSandboxMinijail() {
- // When testing minijail, we will call a system call which is blocked by minijail. In that
- // case, the FfmpegDecoderService will be disconnected, we can check the connection status
- // to make sure if the minijail works or not.
- try {
- mService.testSandboxMinijail();
- } catch (RemoteException e) {
- }
- }
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java b/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java
deleted file mode 100644
index 3ebdd381..00000000
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/FfmpegDecoderService.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer.ffmpeg;
-
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.os.AsyncTask;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioDecoder;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * Ffmpeg based audio decoder service.
- * It should be isolatedProcess due to security reason.
- */
-public class FfmpegDecoderService extends Service {
- private static final String TAG = "FfmpegDecoderService";
- private static final boolean DEBUG = false;
-
- private static final String POLICY_FILE = "whitelist.policy";
-
- private static final long MINIJAIL_SETUP_WAIT_TIMEOUT_MS = 5000;
-
- private static boolean sLibraryLoaded = true;
-
- static {
- try {
- System.loadLibrary("minijail_jni");
- } catch (Exception | Error e) {
- Log.e(TAG, "Load minijail failed:", e);
- sLibraryLoaded = false;
- }
- }
-
- private FfmpegDecoder mBinder = new FfmpegDecoder();
- private volatile Object mMinijailSetupMonitor = new Object();
- //@GuardedBy("mMinijailSetupMonitor")
- private volatile Boolean mMinijailSetup;
-
- @Override
- public void onCreate() {
- if (sLibraryLoaded) {
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (mMinijailSetupMonitor) {
- int pipeFd = getPolicyPipeFd();
- if (pipeFd <= 0) {
- Log.e(TAG, "fail to open policy file");
- mMinijailSetup = false;
- } else {
- nativeSetupMinijail(pipeFd);
- mMinijailSetup = true;
- if (DEBUG) Log.d(TAG, "Minijail setup successfully");
- }
- mMinijailSetupMonitor.notify();
- }
- return null;
- }
- }.execute();
- } else {
- synchronized (mMinijailSetupMonitor) {
- mMinijailSetup = false;
- mMinijailSetupMonitor.notify();
- }
- }
- super.onCreate();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- private int getPolicyPipeFd() {
- try {
- ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
- new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]);
- final AssetFileDescriptor policyFile = getAssets().openFd("whitelist.policy");
- final byte[] buffer = new byte[2048];
- final FileInputStream policyStream = policyFile.createInputStream();
- while (true) {
- int bytesRead = policyStream.read(buffer);
- if (bytesRead == -1) break;
- outputStream.write(buffer, 0, bytesRead);
- }
- policyStream.close();
- outputStream.close();
- return pipe[0].detachFd();
- } catch (IOException e) {
- Log.e(TAG, "Policy file not found:" + e);
- }
- return -1;
- }
-
- private final class FfmpegDecoder extends IFfmpegDecoder.Stub {
- FfmpegAudioDecoder mDecoder;
- @Override
- public boolean isAvailable() {
- return isMinijailSetupDone() && FfmpegAudioDecoder.isAvailable();
- }
-
- @Override
- public void create() {
- mDecoder = new FfmpegAudioDecoder(FfmpegDecoderService.this);
- }
-
- @Override
- public void release() {
- if (mDecoder != null) {
- mDecoder.release();
- mDecoder = null;
- }
- }
-
- @Override
- public void decode(long timeUs, byte[] sample) {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we don't run decode for better security.
- return;
- }
- mDecoder.decode(timeUs, sample);
- }
-
- @Override
- public void resetDecoderState(String mimetype) {
- mDecoder.resetDecoderState(mimetype);
- }
-
- @Override
- public byte[] getDecodedSample() {
- ByteBuffer decodedBuffer = mDecoder.getDecodedSample();
- byte[] ret = new byte[decodedBuffer.limit()];
- decodedBuffer.get(ret, 0, ret.length);
- return ret;
- }
-
- @Override
- public long getDecodedTimeUs() {
- return mDecoder.getDecodedTimeUs();
- }
-
- private boolean isMinijailSetupDone() {
- synchronized (mMinijailSetupMonitor) {
- if (DEBUG) Log.d(TAG, "mMinijailSetup in isAvailable(): " + mMinijailSetup);
- if (mMinijailSetup == null) {
- try {
- if (DEBUG) Log.d(TAG, "Wait till Minijail setup is done");
- mMinijailSetupMonitor.wait(MINIJAIL_SETUP_WAIT_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- return mMinijailSetup != null && mMinijailSetup;
- }
- }
-
- @Override
- public void testSandboxIsolatedProcess() {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we return directly to make the test fail.
- return;
- }
- if (FfmpegDecoderService.this.checkSelfPermission("android.permission.INTERNET")
- == PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Shouldn't have the permission of internet");
- }
- }
-
- @Override
- public void testSandboxMinijail() {
- if (!isMinijailSetupDone()) {
- // If minijail is not setup, we return directly to make the test fail.
- return;
- }
- nativeTestMinijail();
- }
- }
-
- private native void nativeSetupMinijail(int policyFd);
- private native void nativeTestMinijail();
-}
diff --git a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl b/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl
deleted file mode 100644
index ed053790..00000000
--- a/src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 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.exoplayer.ffmpeg;
-
-interface IFfmpegDecoder {
- boolean isAvailable();
- void create();
- void release();
- void resetDecoderState(String mimetype);
- void decode(long timeUs, in byte[] sample);
- byte[] getDecodedSample();
- long getDecodedTimeUs();
- void testSandboxIsolatedProcess();
- void testSandboxMinijail();
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/layout/ScaledLayout.java b/src/com/android/tv/tuner/layout/ScaledLayout.java
deleted file mode 100644
index 379ea70e..00000000
--- a/src/com/android/tv/tuner/layout/ScaledLayout.java
+++ /dev/null
@@ -1,274 +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.layout;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.tuner.R;
-
-import java.util.Arrays;
-import java.util.Comparator;
-
-/**
- * A layout that scales its children using the given percentage value.
- */
-public class ScaledLayout extends ViewGroup {
- private static final String TAG = "ScaledLayout";
- private static final boolean DEBUG = false;
- private static final Comparator<Rect> mRectTopLeftSorter = new Comparator<Rect>() {
- @Override
- public int compare(Rect lhs, Rect rhs) {
- if (lhs.top != rhs.top) {
- return lhs.top - rhs.top;
- } else {
- return lhs.left - rhs.left;
- }
- }
- };
-
- private Rect[] mRectArray;
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- public ScaledLayout(Context context) {
- this(context, null);
- }
-
- public ScaledLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ScaledLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Point size = new Point();
- DisplayManager displayManager = (DisplayManager) getContext()
- .getSystemService(Context.DISPLAY_SERVICE);
- Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- display.getRealSize(size);
- mMaxWidth = size.x;
- mMaxHeight = size.y;
- }
-
- /**
- * ScaledLayoutParams stores the four scale factors.
- * <br>
- * Vertical coordinate system: ({@code scaleStartRow} * 100) % ~ ({@code scaleEndRow} * 100) %
- * Horizontal coordinate system: ({@code scaleStartCol} * 100) % ~ ({@code scaleEndCol} * 100) %
- * <br>
- * In XML, for example,
- * <pre>
- * {@code
- * <View
- * app:layout_scaleStartRow="0.1"
- * app:layout_scaleEndRow="0.5"
- * app:layout_scaleStartCol="0.4"
- * app:layout_scaleEndCol="1" />
- * }
- * </pre>
- */
- public static class ScaledLayoutParams extends ViewGroup.LayoutParams {
- public static final float SCALE_UNSPECIFIED = -1;
- public final float scaleStartRow;
- public final float scaleEndRow;
- public final float scaleStartCol;
- public final float scaleEndCol;
-
- public ScaledLayoutParams(float scaleStartRow, float scaleEndRow,
- float scaleStartCol, float scaleEndCol) {
- super(MATCH_PARENT, MATCH_PARENT);
- this.scaleStartRow = scaleStartRow;
- this.scaleEndRow = scaleEndRow;
- this.scaleStartCol = scaleStartCol;
- this.scaleEndCol = scaleEndCol;
- }
-
- public ScaledLayoutParams(Context context, AttributeSet attrs) {
- super(MATCH_PARENT, MATCH_PARENT);
- TypedArray array =
- context.obtainStyledAttributes(attrs, R.styleable.utScaledLayout);
- scaleStartRow =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleStartRow, SCALE_UNSPECIFIED);
- scaleEndRow =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleEndRow, SCALE_UNSPECIFIED);
- scaleStartCol =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleStartCol, SCALE_UNSPECIFIED);
- scaleEndCol =
- array.getFloat(R.styleable.utScaledLayout_layout_scaleEndCol, SCALE_UNSPECIFIED);
- array.recycle();
- }
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new ScaledLayoutParams(getContext(), attrs);
- }
-
- @Override
- protected boolean checkLayoutParams(LayoutParams p) {
- return (p instanceof ScaledLayoutParams);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- int width = widthSpecSize - getPaddingLeft() - getPaddingRight();
- int height = heightSpecSize - getPaddingTop() - getPaddingBottom();
- if (DEBUG) {
- Log.d(TAG, String.format("onMeasure width: %d, height: %d", width, height));
- }
- int count = getChildCount();
- mRectArray = new Rect[count];
- for (int i = 0; i < count; ++i) {
- View child = getChildAt(i);
- ViewGroup.LayoutParams params = child.getLayoutParams();
- float scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol;
- if (!(params instanceof ScaledLayoutParams)) {
- throw new RuntimeException(
- "A child of ScaledLayout cannot have the UNSPECIFIED scale factors");
- }
- scaleStartRow = ((ScaledLayoutParams) params).scaleStartRow;
- scaleEndRow = ((ScaledLayoutParams) params).scaleEndRow;
- scaleStartCol = ((ScaledLayoutParams) params).scaleStartCol;
- scaleEndCol = ((ScaledLayoutParams) params).scaleEndCol;
- if (scaleStartRow < 0 || scaleStartRow > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleStartRow between 0 and 1");
- }
- if (scaleEndRow < scaleStartRow || scaleStartRow > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleEndRow between scaleStartRow and 1");
- }
- if (scaleEndCol < 0 || scaleEndCol > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleStartCol between 0 and 1");
- }
- if (scaleEndCol < scaleStartCol || scaleEndCol > 1) {
- throw new RuntimeException("A child of ScaledLayout should have a range of "
- + "scaleEndCol between scaleStartCol and 1");
- }
- if (DEBUG) {
- Log.d(TAG, String.format("onMeasure child scaleStartRow: %f scaleEndRow: %f "
- + "scaleStartCol: %f scaleEndCol: %f",
- scaleStartRow, scaleEndRow, scaleStartCol, scaleEndCol));
- }
- mRectArray[i] = new Rect((int) (scaleStartCol * width), (int) (scaleStartRow * height),
- (int) (scaleEndCol * width), (int) (scaleEndRow * height));
- int scaleWidth = (int) (width * (scaleEndCol - scaleStartCol));
- int childWidthSpec = MeasureSpec.makeMeasureSpec(
- scaleWidth > mMaxWidth ? mMaxWidth : scaleWidth, MeasureSpec.EXACTLY);
- int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- child.measure(childWidthSpec, childHeightSpec);
-
- // If the height of the measured child view is bigger than the height of the calculated
- // region by the given ScaleLayoutParams, the height of the region should be increased
- // to fit the size of the child view.
- if (child.getMeasuredHeight() > mRectArray[i].height()) {
- int overflowedHeight = child.getMeasuredHeight() - mRectArray[i].height();
- overflowedHeight = (overflowedHeight + 1) / 2;
- mRectArray[i].bottom += overflowedHeight;
- mRectArray[i].top -= overflowedHeight;
- if (mRectArray[i].top < 0) {
- mRectArray[i].bottom -= mRectArray[i].top;
- mRectArray[i].top = 0;
- }
- if (mRectArray[i].bottom > height) {
- mRectArray[i].top -= mRectArray[i].bottom - height;
- mRectArray[i].bottom = height;
- }
- }
- int scaleHeight = (int) (height * (scaleEndRow - scaleStartRow));
- childHeightSpec = MeasureSpec.makeMeasureSpec(
- scaleHeight > mMaxHeight ? mMaxHeight : scaleHeight, MeasureSpec.EXACTLY);
- child.measure(childWidthSpec, childHeightSpec);
- }
-
- // Avoid overlapping rectangles.
- // Step 1. Sort rectangles by position (top-left).
- int visibleRectCount = 0;
- int[] visibleRectGroup = new int[count];
- Rect[] visibleRectArray = new Rect[count];
- for (int i = 0; i < count; ++i) {
- if (getChildAt(i).getVisibility() == View.VISIBLE) {
- visibleRectGroup[visibleRectCount] = visibleRectCount;
- visibleRectArray[visibleRectCount] = mRectArray[i];
- ++visibleRectCount;
- }
- }
- Arrays.sort(visibleRectArray, 0, visibleRectCount, mRectTopLeftSorter);
-
- // Step 2. Move down if there are overlapping rectangles.
- for (int i = 0; i < visibleRectCount - 1; ++i) {
- for (int j = i + 1; j < visibleRectCount; ++j) {
- if (Rect.intersects(visibleRectArray[i], visibleRectArray[j])) {
- visibleRectGroup[j] = visibleRectGroup[i];
- visibleRectArray[j].set(visibleRectArray[j].left,
- visibleRectArray[i].bottom,
- visibleRectArray[j].right,
- visibleRectArray[i].bottom + visibleRectArray[j].height());
- }
- }
- }
-
- // Step 3. Move up if there is any overflowed rectangle.
- for (int i = visibleRectCount - 1; i >= 0; --i) {
- if (visibleRectArray[i].bottom > height) {
- int overflowedHeight = visibleRectArray[i].bottom - height;
- for (int j = 0; j <= i; ++j) {
- if (visibleRectGroup[i] == visibleRectGroup[j]) {
- visibleRectArray[j].set(visibleRectArray[j].left,
- visibleRectArray[j].top - overflowedHeight,
- visibleRectArray[j].right,
- visibleRectArray[j].bottom - overflowedHeight);
- }
- }
- }
- }
- setMeasuredDimension(widthSpecSize, heightSpecSize);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int paddingLeft = getPaddingLeft();
- int paddingTop = getPaddingTop();
- int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- int childLeft = paddingLeft + mRectArray[i].left;
- int childTop = paddingTop + mRectArray[i].top;
- int childBottom = paddingLeft + mRectArray[i].bottom;
- int childRight = paddingTop + mRectArray[i].right;
- if (DEBUG) {
- Log.d(TAG, String.format("layoutChild bottom: %d left: %d right: %d top: %d",
- childBottom, childLeft,
- childRight, childTop));
- }
- child.layout(childLeft, childTop, childRight, childBottom);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
deleted file mode 100644
index e0e21a20..00000000
--- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java
+++ /dev/null
@@ -1,101 +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.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import com.android.tv.common.BuildConfig;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-
-import java.util.List;
-import java.util.TimeZone;
-
-/**
- * A fragment for connection type selection.
- */
-public class ConnectionTypeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.ConnectionTypeFragment";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- ((TunerSetupActivity) getActivity()).generateTunerHal();
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public void onResume() {
- ((TunerSetupActivity) getActivity()).generateTunerHal();
- super.onResume();
- }
-
- @Override
- public void onDestroy() {
- ((TunerSetupActivity) getActivity()).clearTunerHal();
- super.onDestroy();
- }
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- return new ContentFragment();
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- return new Guidance(getString(R.string.ut_connection_title),
- getString(R.string.ut_connection_description),
- getString(R.string.ut_setup_breadcrumb), null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices = getResources().getStringArray(R.array.ut_connection_choices);
- int length = choices.length - 1;
- int startOffset = 0;
- for (int i = 0; i < length; ++i) {
- actions.add(new GuidedAction.Builder(getActivity())
- .id(startOffset + i)
- .title(choices[i])
- .build());
- }
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/PostalCodeFragment.java b/src/com/android/tv/tuner/setup/PostalCodeFragment.java
deleted file mode 100644
index 025b9193..00000000
--- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2017 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.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.text.InputFilter;
-import android.text.InputFilter.AllCaps;
-import android.view.View;
-import android.widget.TextView;
-import com.android.tv.R;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.util.PostalCodeUtils;
-import com.android.tv.util.LocationUtils;
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class PostalCodeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.PostalCodeFragment";
- private static final int VIEW_TYPE_EDITABLE = 1;
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- ContentFragment fragment = new ContentFragment();
- Bundle arguments = new Bundle();
- arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true);
- fragment.setArguments(arguments);
- return fragment;
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return true;
- }
-
- @Override
- protected boolean needsSkipButton() {
- return true;
- }
-
- @Override
- protected void setOnClickAction(View view, final String category, final int actionId) {
- if (actionId == ACTION_DONE) {
- view.setOnClickListener(
- new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- CharSequence postalCode =
- ((ContentFragment) getContentFragment()).mEditAction.getTitle();
- String region = LocationUtils.getCurrentCountry(getContext());
- if (postalCode != null && PostalCodeUtils.matches(postalCode, region)) {
- PostalCodeUtils.setLastPostalCode(
- getContext(), postalCode.toString());
- onActionClick(category, actionId);
- } else {
- ContentFragment contentFragment =
- (ContentFragment) getContentFragment();
- contentFragment.mEditAction.setDescription(
- getString(R.string.postal_code_invalid_warning));
- contentFragment.notifyActionChanged(0);
- contentFragment.mEditedActionView.performClick();
- }
- }
- });
- } else if (actionId == ACTION_SKIP) {
- super.setOnClickAction(view, category, ACTION_SKIP);
- }
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private GuidedAction mEditAction;
- private View mEditedActionView;
- private View mDoneActionView;
- private boolean mProceed;
-
- @Override
- public void onGuidedActionFocused(GuidedAction action) {
- if (action.equals(mEditAction)) {
- if (mProceed) {
- // "NEXT" in IME was just clicked, moves focus to Done button.
- if (mDoneActionView == null) {
- mDoneActionView = getActivity().findViewById(R.id.button_done);
- }
- mDoneActionView.requestFocus();
- mProceed = false;
- } else {
- // Directly opens IME to input postal/zip code.
- if (mEditedActionView == null) {
- int maxLength = PostalCodeUtils.getRegionMaxLength(getContext());
- mEditedActionView = getView().findViewById(R.id.guidedactions_editable);
- ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title))
- .setFilters(
- new InputFilter[] {
- new InputFilter.LengthFilter(maxLength), new AllCaps()
- });
- }
- mEditedActionView.performClick();
- }
- }
- }
-
- @Override
- public long onGuidedActionEditedAndProceed(GuidedAction action) {
- mProceed = true;
- return 0;
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title = getString(R.string.postal_code_guidance_title);
- String description = getString(R.string.postal_code_guidance_description);
- String breadcrumb = getString(R.string.ut_setup_breadcrumb);
- return new Guidance(title, description, breadcrumb, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String description = getString(R.string.postal_code_action_description);
- mEditAction = new GuidedAction.Builder(getActivity()).id(0).editable(true)
- .description(description).build();
- actions.add(mEditAction);
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- public GuidedActionsStylist onCreateActionsStylist() {
- return new GuidedActionsStylist() {
- @Override
- public int getItemViewType(GuidedAction action) {
- if (action.isEditable()) {
- return VIEW_TYPE_EDITABLE;
- }
- return super.getItemViewType(action);
- }
-
- @Override
- public int onProvideItemLayoutId(int viewType) {
- if (viewType == VIEW_TYPE_EDITABLE) {
- return R.layout.guided_action_editable;
- }
- return super.onProvideItemLayoutId(viewType);
- }
- };
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/setup/ScanFragment.java b/src/com/android/tv/tuner/setup/ScanFragment.java
deleted file mode 100644
index b6936e38..00000000
--- a/src/com/android/tv/tuner/setup/ScanFragment.java
+++ /dev/null
@@ -1,523 +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.setup;
-
-import android.animation.LayoutTransition;
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.Button;
-import android.widget.ListView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.ui.setup.SetupFragment;
-import com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.source.FileTsStreamer;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsStreamer;
-import com.android.tv.tuner.source.TunerTsStreamer;
-import com.android.tv.tuner.tvinput.ChannelDataManager;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A fragment for scanning channels.
- */
-public class ScanFragment extends SetupFragment {
- private static final String TAG = "ScanFragment";
- private static final boolean DEBUG = false;
-
- // In the fake mode, the connection to antenna or cable is not necessary.
- // Instead dummy channels are added.
- private static final boolean FAKE_MODE = false;
-
- private static final String VCTLESS_CHANNEL_NAME_FORMAT = "RF%d-%d";
-
- public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.ScanFragment";
- public static final int ACTION_CANCEL = 1;
- public static final int ACTION_FINISH = 2;
-
- public static final String EXTRA_FOR_CHANNEL_SCAN_FILE = "scan_file_choice";
-
- private static final long CHANNEL_SCAN_SHOW_DELAY_MS = 10000;
- private static final long CHANNEL_SCAN_PERIOD_MS = 4000;
- private static final long SHOW_PROGRESS_DIALOG_DELAY_MS = 300;
-
- // Build channels out of the locally stored TS streams.
- private static final boolean SCAN_LOCAL_STREAMS = true;
-
- private ChannelDataManager mChannelDataManager;
- private ChannelScanTask mChannelScanTask;
- private ProgressBar mProgressBar;
- private TextView mScanningMessage;
- private View mChannelHolder;
- private ChannelAdapter mAdapter;
- private volatile boolean mChannelListVisible;
- private Button mCancelButton;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreateView");
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mChannelDataManager = new ChannelDataManager(getActivity());
- mChannelDataManager.checkDataVersion(getActivity());
- mAdapter = new ChannelAdapter();
- mProgressBar = (ProgressBar) view.findViewById(R.id.tune_progress);
- mScanningMessage = (TextView) view.findViewById(R.id.tune_description);
- ListView channelList = (ListView) view.findViewById(R.id.channel_list);
- channelList.setAdapter(mAdapter);
- channelList.setOnItemClickListener(null);
- ViewGroup progressHolder = (ViewGroup) view.findViewById(R.id.progress_holder);
- LayoutTransition transition = new LayoutTransition();
- transition.enableTransitionType(LayoutTransition.CHANGING);
- progressHolder.setLayoutTransition(transition);
- mChannelHolder = view.findViewById(R.id.channel_holder);
- mCancelButton = (Button) view.findViewById(R.id.tune_cancel);
- mCancelButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- finishScan(false);
- }
- });
- Bundle args = getArguments();
- int tunerType = (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0));
- // TODO: Handle the case when the fragment is restored.
- startScan(args == null ? 0 : args.getInt(EXTRA_FOR_CHANNEL_SCAN_FILE, 0));
- TextView scanTitleView = (TextView) view.findViewById(R.id.tune_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- scanTitleView.setText(R.string.ut_channel_scan);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- scanTitleView.setText(R.string.nt_channel_scan);
- break;
- default:
- scanTitleView.setText(R.string.bt_channel_scan);
- }
- return view;
- }
-
- @Override
- protected int getLayoutResourceId() {
- return R.layout.ut_channel_scan;
- }
-
- @Override
- protected int[] getParentIdsForDelay() {
- return new int[] {R.id.progress_holder};
- }
-
- private void startScan(int channelMapId) {
- mChannelScanTask = new ChannelScanTask(channelMapId);
- mChannelScanTask.execute();
- }
-
- @Override
- public void onPause() {
- Log.d(TAG, "onPause");
- if (mChannelScanTask != null) {
- // Ensure scan task will stop.
- Log.w(TAG, "The activity went to the background. Stopping channel scan.");
- mChannelScanTask.stopScan();
- }
- super.onPause();
- }
-
- /**
- * Finishes the current scan thread. This fragment will be popped after the scan thread ends.
- *
- * @param cancel a flag which indicates the scan is canceled or not.
- */
- public void finishScan(boolean cancel) {
- if (mChannelScanTask != null) {
- mChannelScanTask.cancelScan(cancel);
-
- // Notifies a user of waiting to finish the scanning process.
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mChannelScanTask != null) {
- mChannelScanTask.showFinishingProgressDialog();
- }
- }
- }, SHOW_PROGRESS_DIALOG_DELAY_MS);
-
- // Hides the cancel button.
- mCancelButton.setEnabled(false);
- }
- }
-
- private class ChannelAdapter extends BaseAdapter {
- private final ArrayList<TunerChannel> mChannels;
-
- public ChannelAdapter() {
- mChannels = new ArrayList<>();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public boolean isEnabled(int pos) {
- return false;
- }
-
- @Override
- public int getCount() {
- return mChannels.size();
- }
-
- @Override
- public Object getItem(int pos) {
- return pos;
- }
-
- @Override
- public long getItemId(int pos) {
- return pos;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
-
- if (convertView == null) {
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- convertView = inflater.inflate(R.layout.ut_channel_list, parent, false);
- }
-
- TextView channelNum = (TextView) convertView.findViewById(R.id.channel_num);
- channelNum.setText(mChannels.get(position).getDisplayNumber());
-
- TextView channelName = (TextView) convertView.findViewById(R.id.channel_name);
- channelName.setText(mChannels.get(position).getName());
- return convertView;
- }
-
- public void add(TunerChannel channel) {
- mChannels.add(channel);
- notifyDataSetChanged();
- }
- }
-
- private class ChannelScanTask extends AsyncTask<Void, Integer, Void>
- implements EventDetector.EventListener, ChannelDataManager.ChannelScanListener {
- private static final int MAX_PROGRESS = 100;
-
- private final Activity mActivity;
- private final int mChannelMapId;
- private final TsStreamer mScanTsStreamer;
- private final TsStreamer mFileTsStreamer;
- private final ConditionVariable mConditionStopped;
-
- private final List<ChannelScanFileParser.ScanChannel> mScanChannelList = new ArrayList<>();
- private boolean mIsCanceled;
- private boolean mIsFinished;
- private ProgressDialog mFinishingProgressDialog;
- private CountDownLatch mLatch;
-
- public ChannelScanTask(int channelMapId) {
- mActivity = getActivity();
- mChannelMapId = channelMapId;
- if (FAKE_MODE) {
- mScanTsStreamer = new FakeTsStreamer(this);
- } else {
- TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal();
- if (hal == null) {
- throw new RuntimeException("Failed to open a DVB device");
- }
- mScanTsStreamer = new TunerTsStreamer(hal, this);
- }
- mFileTsStreamer = SCAN_LOCAL_STREAMS ? new FileTsStreamer(this, mActivity) : null;
- mConditionStopped = new ConditionVariable();
- mChannelDataManager.setChannelScanListener(this, new Handler());
- }
-
- private void maybeSetChannelListVisible() {
- mActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- int channelsFound = mAdapter.getCount();
- if (!mChannelListVisible && channelsFound > 0) {
- String format = getResources().getQuantityString(
- R.plurals.ut_channel_scan_message, channelsFound, channelsFound);
- mScanningMessage.setText(String.format(format, channelsFound));
- mChannelHolder.setVisibility(View.VISIBLE);
- mChannelListVisible = true;
- }
- }
- });
- }
-
- private void addChannel(final TunerChannel channel) {
- mActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mAdapter.add(channel);
- if (mChannelListVisible) {
- int channelsFound = mAdapter.getCount();
- String format = getResources().getQuantityString(
- R.plurals.ut_channel_scan_message, channelsFound, channelsFound);
- mScanningMessage.setText(String.format(format, channelsFound));
- }
- }
- });
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- mScanChannelList.clear();
- if (SCAN_LOCAL_STREAMS) {
- FileTsStreamer.addLocalStreamFiles(mScanChannelList);
- }
- mScanChannelList.addAll(ChannelScanFileParser.parseScanFile(
- getResources().openRawResource(mChannelMapId)));
- scanChannels();
- return null;
- }
-
- @Override
- protected void onCancelled() {
- SoftPreconditions.checkState(false, TAG, "call cancelScan instead of cancel");
- }
-
- @Override
- protected void onProgressUpdate(Integer... values) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- mProgressBar.setProgress(values[0], true);
- } else {
- mProgressBar.setProgress(values[0]);
- }
- }
-
- private void stopScan() {
- if (mLatch != null) {
- mLatch.countDown();
- }
- mConditionStopped.open();
- }
-
- private void cancelScan(boolean cancel) {
- mIsCanceled = cancel;
- stopScan();
- }
-
- private void scanChannels() {
- if (DEBUG) Log.i(TAG, "Channel scan starting");
- mChannelDataManager.notifyScanStarted();
-
- long startMs = System.currentTimeMillis();
- int i = 1;
- for (ChannelScanFileParser.ScanChannel scanChannel : mScanChannelList) {
- int frequency = scanChannel.frequency;
- String modulation = scanChannel.modulation;
- Log.i(TAG, "Tuning to " + frequency + " " + modulation);
-
- TsStreamer streamer = getStreamer(scanChannel.type);
- SoftPreconditions.checkNotNull(streamer);
- if (streamer != null && streamer.startStream(scanChannel)) {
- mLatch = new CountDownLatch(1);
- try {
- mLatch.await(CHANNEL_SCAN_PERIOD_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "The current thread is interrupted during scanChannels(). " +
- "The TS stream is stopped earlier than expected.", e);
- }
- streamer.stopStream();
-
- addChannelsWithoutVct(scanChannel);
- if (System.currentTimeMillis() > startMs + CHANNEL_SCAN_SHOW_DELAY_MS
- && !mChannelListVisible) {
- maybeSetChannelListVisible();
- }
- }
- if (mConditionStopped.block(-1)) {
- break;
- }
- publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size());
- }
- mChannelDataManager.notifyScanCompleted();
- if (!mConditionStopped.block(-1)) {
- publishProgress(MAX_PROGRESS);
- }
- if (DEBUG) Log.i(TAG, "Channel scan ended");
- }
-
-
- private void addChannelsWithoutVct(ChannelScanFileParser.ScanChannel scanChannel) {
- if (scanChannel.radioFrequencyNumber == null
- || !(mScanTsStreamer instanceof TunerTsStreamer)) {
- return;
- }
- for (TunerChannel tunerChannel
- : ((TunerTsStreamer) mScanTsStreamer).getMalFormedChannels()) {
- if ((tunerChannel.getVideoPid() != TunerChannel.INVALID_PID)
- && (tunerChannel.getAudioPid() != TunerChannel.INVALID_PID)) {
- tunerChannel.setFrequency(scanChannel.frequency);
- tunerChannel.setModulation(scanChannel.modulation);
- tunerChannel.setShortName(String.format(Locale.US, VCTLESS_CHANNEL_NAME_FORMAT,
- scanChannel.radioFrequencyNumber,
- tunerChannel.getProgramNumber()));
- tunerChannel.setVirtualMajor(scanChannel.radioFrequencyNumber);
- tunerChannel.setVirtualMinor(tunerChannel.getProgramNumber());
- onChannelDetected(tunerChannel, true);
- }
- }
- }
-
- private TsStreamer getStreamer(int type) {
- switch (type) {
- case Channel.TYPE_TUNER:
- return mScanTsStreamer;
- case Channel.TYPE_FILE:
- return mFileTsStreamer;
- default:
- return null;
- }
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- if (mLatch != null) {
- mLatch.countDown();
- }
- }
-
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (channelArrivedAtFirstTime) {
- Log.i(TAG, "Found channel " + channel);
- }
- if (channelArrivedAtFirstTime && channel.hasAudio()) {
- // Playbacks with video-only stream have not been tested yet.
- // No video-only channel has been found.
- addChannel(channel);
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
- }
-
- public void showFinishingProgressDialog() {
- // Show a progress dialog to wait for the scanning process if it's not done yet.
- if (!mIsFinished && mFinishingProgressDialog == null) {
- mFinishingProgressDialog = ProgressDialog.show(mActivity, "",
- getString(R.string.ut_setup_cancel), true, false);
- }
- }
-
- @Override
- public void onChannelHandlingDone() {
- mChannelDataManager.setCurrentVersion(mActivity);
- mChannelDataManager.releaseSafely();
- mIsFinished = true;
- TunerPreferences.setScannedChannelCount(mActivity.getApplicationContext(),
- mChannelDataManager.getScannedChannelCount());
- // Cancel a previously shown notification.
- TunerSetupActivity.cancelNotification(mActivity.getApplicationContext());
- // Mark scan as done
- TunerPreferences.setScanDone(mActivity.getApplicationContext());
- // finishing will be done manually.
- if (mFinishingProgressDialog != null) {
- mFinishingProgressDialog.dismiss();
- }
- // If the fragment is not resumed, the next fragment (scan result page) can't be
- // displayed. In that case, just close the activity.
- if (isResumed()) {
- onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH);
- } else if (getActivity() != null) {
- getActivity().finish();
- }
- mChannelScanTask = null;
- }
- }
-
- private static class FakeTsStreamer implements TsStreamer {
- private final EventDetector.EventListener mEventListener;
- private int mProgramNumber = 0;
-
- FakeTsStreamer(EventDetector.EventListener eventListener) {
- mEventListener = eventListener;
- }
-
- @Override
- public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
- if (++mProgramNumber % 2 == 1) {
- return true;
- }
- final String displayNumber = Integer.toString(mProgramNumber);
- final String name = "Channel-" + mProgramNumber;
- mEventListener.onChannelDetected(new TunerChannel(mProgramNumber, new ArrayList<>()) {
- @Override
- public String getDisplayNumber() {
- return displayNumber;
- }
-
- @Override
- public String getName() {
- return name;
- }
- }, true);
- return true;
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- return false;
- }
-
- @Override
- public void stopStream() {
- }
-
- @Override
- public TsDataSource createDataSource() {
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java
deleted file mode 100644
index 3b8cd823..00000000
--- a/src/com/android/tv/tuner/setup/ScanResultFragment.java
+++ /dev/null
@@ -1,127 +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.setup;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
-
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class ScanResultFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.ScanResultFragment";
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- return new ContentFragment();
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private int mChannelCountOnPreference;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mChannelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title;
- String description;
- String breadcrumb;
- if (mChannelCountOnPreference > 0) {
- Resources res = getResources();
- title = res.getQuantityString(R.plurals.ut_result_found_title,
- mChannelCountOnPreference, mChannelCountOnPreference);
- description = res.getQuantityString(R.plurals.ut_result_found_description,
- mChannelCountOnPreference, mChannelCountOnPreference);
- breadcrumb = null;
- } else {
- Bundle args = getArguments();
- int tunerType =
- (args == null ? 0 : args.getInt(TunerSetupActivity.KEY_TUNER_TYPE, 0));
- title = getString(R.string.ut_result_not_found_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- description = getString(R.string.ut_result_not_found_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- description = getString(R.string.nt_result_not_found_description);
- break;
- default:
- description = getString(R.string.bt_result_not_found_description);
- }
- breadcrumb = getString(R.string.ut_setup_breadcrumb);
- }
- return new Guidance(title, description, breadcrumb, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices;
- int doneActionIndex;
- if (mChannelCountOnPreference > 0) {
- choices = getResources().getStringArray(R.array.ut_result_found_choices);
- doneActionIndex = 0;
- } else {
- choices = getResources().getStringArray(R.array.ut_result_not_found_choices);
- doneActionIndex = 1;
- }
- for (int i = 0; i < choices.length; ++i) {
- if (i == doneActionIndex) {
- actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE)
- .title(choices[i]).build());
- } else {
- actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i])
- .build());
- }
- }
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
deleted file mode 100644
index e9f3baa7..00000000
--- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java
+++ /dev/null
@@ -1,543 +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.setup;
-
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.tv.TvContract;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.support.v4.app.NotificationCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.widget.Toast;
-
-import com.android.tv.Features;
-import com.android.tv.TvApplication;
-import com.android.tv.common.AutoCloseableUtils;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonConstants;
-import com.android.tv.common.TvCommonUtils;
-import com.android.tv.common.ui.setup.SetupActivity;
-import com.android.tv.common.ui.setup.SetupFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.experiments.Experiments;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-import com.android.tv.tuner.util.PostalCodeUtils;
-
-import java.util.concurrent.Executor;
-
-/**
- * An activity that serves tuner setup process.
- */
-public class TunerSetupActivity extends SetupActivity {
- private static final String TAG = "TunerSetupActivity";
- private static final boolean DEBUG = false;
-
- /**
- * Key for passing tuner type to sub-fragments.
- */
- public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType";
-
- // For the notification.
- private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity";
- private static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel";
- private static final String NOTIFY_TAG = "TunerSetup";
- private static final int NOTIFY_ID = 1000;
- private static final String TAG_DRAWABLE = "drawable";
- private static final String TAG_ICON = "ic_launcher_s";
- private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
-
- private static final int CHANNEL_MAP_SCAN_FILE[] = {
- R.raw.ut_us_atsc_center_frequencies_8vsb,
- R.raw.ut_us_cable_standard_center_frequencies_qam256,
- R.raw.ut_us_all,
- R.raw.ut_kr_atsc_center_frequencies_8vsb,
- R.raw.ut_kr_cable_standard_center_frequencies_qam256,
- R.raw.ut_kr_all,
- R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256,
- R.raw.ut_euro_dvbt_all,
- R.raw.ut_euro_dvbt_all,
- R.raw.ut_euro_dvbt_all
- };
-
- private ScanFragment mLastScanFragment;
- private Integer mTunerType;
- private TunerHalFactory mTunerHalFactory;
- private boolean mNeedToShowPostalCodeFragment;
- private String mPreviousPostalCode;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DEBUG) Log.d(TAG, "onCreate");
- new AsyncTask<Void, Void, Integer>() {
- @Override
- protected Integer doInBackground(Void... arg0) {
- return TunerHal.getTunerTypeAndCount(TunerSetupActivity.this).first;
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (!TunerSetupActivity.this.isDestroyed()) {
- mTunerType = result;
- if (result == null) {
- finish();
- } else {
- showInitialFragment();
- }
- }
- }
- }.execute();
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate(savedInstanceState);
- // TODO: check {@link shouldShowRequestPermissionRationale}.
- if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
- // No need to check the request result.
- requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
- }
- mTunerHalFactory = new TunerHalFactory(getApplicationContext());
- try {
- // Updating postal code takes time, therefore we called it here for "warm-up".
- mPreviousPostalCode = PostalCodeUtils.getLastPostalCode(this);
- PostalCodeUtils.setLastPostalCode(this, null);
- PostalCodeUtils.updatePostalCode(this);
- } catch (Exception e) {
- // Do nothing. If the last known postal code is null, we'll show guided fragment to
- // prompt users to input postal code before ConnectionTypeFragment is shown.
- Log.i(TAG, "Can't get postal code:" + e);
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults) {
- if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
- && Experiments.CLOUD_EPG.get()) {
- try {
- // Updating postal code takes time, therefore we should update postal code
- // right after the permission is granted, so that the subsequent operations,
- // especially EPG fetcher, could get the newly updated postal code.
- PostalCodeUtils.updatePostalCode(this);
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
- }
-
- @Override
- protected Fragment onCreateInitialFragment() {
- if (mTunerType != null) {
- SetupFragment fragment = new WelcomeFragment();
- Bundle args = new Bundle();
- args.putInt(KEY_TUNER_TYPE, mTunerType);
- fragment.setArguments(args);
- fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
- return fragment;
- } else {
- return null;
- }
- }
-
- @Override
- protected boolean executeAction(String category, int actionId, Bundle params) {
- switch (category) {
- case WelcomeFragment.ACTION_CATEGORY:
- switch (actionId) {
- case SetupMultiPaneFragment.ACTION_DONE:
- // If the scan was performed, then the result should be OK.
- setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK);
- finish();
- break;
- default:
- if (mNeedToShowPostalCodeFragment
- || Features.ENABLE_CLOUD_EPG_REGION.isEnabled(
- getApplicationContext())
- && TextUtils.isEmpty(
- PostalCodeUtils.getLastPostalCode(this))) {
- // We cannot get postal code automatically. Postal code input fragment
- // should always be shown even if users have input some valid postal
- // code in this activity before.
- mNeedToShowPostalCodeFragment = true;
- showPostalCodeFragment();
- } else {
- showConnectionTypeFragment();
- }
- break;
- }
- return true;
- case PostalCodeFragment.ACTION_CATEGORY:
- if (actionId == SetupMultiPaneFragment.ACTION_DONE
- || actionId == SetupMultiPaneFragment.ACTION_SKIP) {
- showConnectionTypeFragment();
- }
- return true;
- case ConnectionTypeFragment.ACTION_CATEGORY:
- if (mTunerHalFactory.getOrCreate() == null) {
- finish();
- Toast.makeText(getApplicationContext(),
- R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show();
- return true;
- }
- mLastScanFragment = new ScanFragment();
- Bundle args1 = new Bundle();
- args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
- CHANNEL_MAP_SCAN_FILE[actionId]);
- args1.putInt(KEY_TUNER_TYPE, mTunerType);
- mLastScanFragment.setArguments(args1);
- showFragment(mLastScanFragment, true);
- return true;
- case ScanFragment.ACTION_CATEGORY:
- switch (actionId) {
- case ScanFragment.ACTION_CANCEL:
- getFragmentManager().popBackStack();
- return true;
- case ScanFragment.ACTION_FINISH:
- mTunerHalFactory.clear();
- SetupFragment fragment = new ScanResultFragment();
- Bundle args2 = new Bundle();
- args2.putInt(KEY_TUNER_TYPE, mTunerType);
- fragment.setArguments(args2);
- fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
- showFragment(fragment, true);
- return true;
- }
- break;
- case ScanResultFragment.ACTION_CATEGORY:
- switch (actionId) {
- case SetupMultiPaneFragment.ACTION_DONE:
- setResult(RESULT_OK);
- finish();
- break;
- default:
- SetupFragment fragment = new ConnectionTypeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- break;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- FragmentManager manager = getFragmentManager();
- int count = manager.getBackStackEntryCount();
- if (count > 0) {
- String lastTag = manager.getBackStackEntryAt(count - 1).getName();
- if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
- // Pops fragment including ScanFragment.
- manager.popBackStack(manager.getBackStackEntryAt(count - 2).getName(),
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
- return true;
- } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
- mLastScanFragment.finishScan(true);
- return true;
- }
- }
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onDestroy() {
- if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) {
- PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode);
- }
- super.onDestroy();
- }
-
- /**
- * A callback to be invoked when the TvInputService is enabled or disabled.
- *
- * @param context a {@link Context} instance
- * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled;
- * otherwise {@code false}
- */
- public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) {
- // Send a notification for tuner setup if there's no channels and the tuner TV input
- // setup has been not done.
- boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context);
- int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
- if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) {
- TunerPreferences.setShouldShowSetupActivity(context, true);
- sendNotification(context, tunerType);
- } else {
- TunerPreferences.setShouldShowSetupActivity(context, false);
- cancelNotification(context);
- }
- }
-
- /**
- * Returns a {@link Intent} to launch the tuner TV input service.
- *
- * @param context a {@link Context} instance
- */
- public static Intent createSetupActivity(Context context) {
- String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(),
- TunerTvInputService.class.getName()));
-
- // Make an intent to launch the setup activity of TV tuner input.
- Intent intent = TvCommonUtils.createSetupIntent(
- new Intent(context, TunerSetupActivity.class), inputId);
- intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
- Intent tvActivityIntent = new Intent();
- tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME));
- intent.putExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent);
- return intent;
- }
-
- /**
- * Gets the currently used tuner HAL.
- */
- TunerHal getTunerHal() {
- return mTunerHalFactory.getOrCreate();
- }
-
- /**
- * Generates tuner HAL.
- */
- void generateTunerHal() {
- mTunerHalFactory.generate();
- }
-
- /**
- * Clears the currently used tuner HAL.
- */
- void clearTunerHal() {
- mTunerHalFactory.clear();
- }
-
- /**
- * Returns a {@link PendingIntent} to launch the tuner TV input service.
- *
- * @param context a {@link Context} instance
- */
- private static PendingIntent createPendingIntentForSetupActivity(Context context) {
- return PendingIntent.getActivity(context, 0, createSetupActivity(context),
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- private static void sendNotification(Context context, Integer tunerType) {
- SoftPreconditions.checkState(tunerType != null, TAG,
- "tunerType is null when send notification");
- if (tunerType == null) {
- return;
- }
- Resources resources = context.getResources();
- String contentTitle = resources.getString(R.string.ut_setup_notification_content_title);
- int contentTextId = 0;
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_BUILT_IN:
- contentTextId = R.string.bt_setup_notification_content_text;
- break;
- case TunerHal.TUNER_TYPE_USB:
- contentTextId = R.string.ut_setup_notification_content_text;
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- contentTextId = R.string.nt_setup_notification_content_text;
- break;
- }
- String contentText = resources.getString(contentTextId);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- sendNotificationInternal(context, contentTitle, contentText);
- } else {
- Bitmap largeIcon = BitmapFactory.decodeResource(resources,
- R.drawable.recommendation_antenna);
- sendRecommendationCard(context, contentTitle, contentText, largeIcon);
- }
- }
-
- /**
- * Sends the recommendation card to start the tuner TV input setup activity.
- *
- * @param context a {@link Context} instance
- */
- private static void sendRecommendationCard(Context context, String contentTitle,
- String contentText, Bitmap largeIcon) {
- // Build and send the notification.
- Notification notification = new NotificationCompat.BigPictureStyle(
- new NotificationCompat.Builder(context)
- .setAutoCancel(false)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setContentInfo(contentText)
- .setCategory(Notification.CATEGORY_RECOMMENDATION)
- .setLargeIcon(largeIcon)
- .setSmallIcon(context.getResources().getIdentifier(
- TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
- .setContentIntent(createPendingIntentForSetupActivity(context)))
- .build();
- NotificationManager notificationManager = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
- }
-
- private static void sendNotificationInternal(Context context, String contentTitle,
- String contentText) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- notificationManager.createNotificationChannel(new NotificationChannel(
- TUNER_SET_UP_NOTIFICATION_CHANNEL_ID,
- context.getResources().getString(R.string.ut_setup_notification_channel_name),
- NotificationManager.IMPORTANCE_HIGH));
- Notification notification = new Notification.Builder(
- context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setSmallIcon(context.getResources().getIdentifier(
- TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
- .setContentIntent(createPendingIntentForSetupActivity(context))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .extend(new Notification.TvExtender())
- .build();
- notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
- }
-
- private void showPostalCodeFragment() {
- SetupFragment fragment = new PostalCodeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- }
-
- private void showConnectionTypeFragment() {
- SetupFragment fragment = new ConnectionTypeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
- }
-
- /**
- * Cancels the previously shown notification.
- *
- * @param context a {@link Context} instance
- */
- public static void cancelNotification(Context context) {
- NotificationManager notificationManager = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID);
- }
-
- @VisibleForTesting
- static class TunerHalFactory {
- private Context mContext;
- @VisibleForTesting
- TunerHal mTunerHal;
- private GenerateTunerHalTask mGenerateTunerHalTask;
- private final Executor mExecutor;
-
- TunerHalFactory(Context context) {
- this(context, AsyncTask.SERIAL_EXECUTOR);
- }
-
- TunerHalFactory(Context context, Executor executor) {
- mContext = context;
- mExecutor = executor;
- }
-
- /**
- * Returns tuner HAL currently used. If it's {@code null} and tuner HAL is not generated
- * before, tries to generate it synchronously.
- */
- @WorkerThread
- TunerHal getOrCreate() {
- if (mGenerateTunerHalTask != null
- && mGenerateTunerHalTask.getStatus() != AsyncTask.Status.FINISHED) {
- try {
- return mGenerateTunerHalTask.get();
- } catch (Exception e) {
- Log.e(TAG, "Cannot get Tuner HAL: " + e);
- }
- } else if (mGenerateTunerHalTask == null && mTunerHal == null) {
- mTunerHal = createInstance();
- }
- return mTunerHal;
- }
-
- /**
- * Generates tuner hal for scanning with asynchronous tasks.
- */
- @MainThread
- void generate() {
- if (mGenerateTunerHalTask == null && mTunerHal == null) {
- mGenerateTunerHalTask = new GenerateTunerHalTask();
- mGenerateTunerHalTask.executeOnExecutor(mExecutor);
- }
- }
-
- /**
- * Clears the currently used tuner hal.
- */
- @MainThread
- void clear() {
- if (mGenerateTunerHalTask != null) {
- mGenerateTunerHalTask.cancel(true);
- mGenerateTunerHalTask = null;
- }
- if (mTunerHal != null) {
- AutoCloseableUtils.closeQuietly(mTunerHal);
- mTunerHal = null;
- }
- }
-
- @WorkerThread
- protected TunerHal createInstance() {
- return TunerHal.createInstance(mContext);
- }
-
- class GenerateTunerHalTask extends AsyncTask<Void, Void, TunerHal> {
- @Override
- protected TunerHal doInBackground(Void... args) {
- return createInstance();
- }
-
- @Override
- protected void onPostExecute(TunerHal tunerHal) {
- mTunerHal = tunerHal;
- }
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/setup/WelcomeFragment.java b/src/com/android/tv/tuner/setup/WelcomeFragment.java
deleted file mode 100644
index feae1ec9..00000000
--- a/src/com/android/tv/tuner/setup/WelcomeFragment.java
+++ /dev/null
@@ -1,120 +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.setup;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
-import android.support.v17.leanback.widget.GuidedAction;
-import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
-import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import java.util.List;
-
-/**
- * A fragment for initial screen.
- */
-public class WelcomeFragment extends SetupMultiPaneFragment {
- public static final String ACTION_CATEGORY =
- "com.android.tv.tuner.setup.WelcomeFragment";
-
- @Override
- protected SetupGuidedStepFragment onCreateContentFragment() {
- ContentFragment fragment = new ContentFragment();
- fragment.setArguments(getArguments());
- return fragment;
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
-
- @Override
- protected boolean needsDoneButton() {
- return false;
- }
-
- public static class ContentFragment extends SetupGuidedStepFragment {
- private int mChannelCountOnPreference;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- mChannelCountOnPreference =
- TunerPreferences.getScannedChannelCount(getActivity().getApplicationContext());
- super.onCreate(savedInstanceState);
- }
-
- @NonNull
- @Override
- public Guidance onCreateGuidance(Bundle savedInstanceState) {
- String title;
- String description;
- int tunerType = getArguments().getInt(TunerSetupActivity.KEY_TUNER_TYPE,
- TunerHal.TUNER_TYPE_BUILT_IN);
- if (mChannelCountOnPreference == 0) {
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- title = getString(R.string.ut_setup_new_title);
- description = getString(R.string.ut_setup_new_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- title = getString(R.string.nt_setup_new_title);
- description = getString(R.string.nt_setup_new_description);
- break;
- default:
- title = getString(R.string.bt_setup_new_title);
- description = getString(R.string.bt_setup_new_description);
- }
- } else {
- title = getString(R.string.bt_setup_again_title);
- switch (tunerType) {
- case TunerHal.TUNER_TYPE_USB:
- description = getString(R.string.ut_setup_again_description);
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- description = getString(R.string.nt_setup_again_description);
- break;
- default:
- description = getString(R.string.bt_setup_again_description);
- }
- }
- return new Guidance(title, description, null, null);
- }
-
- @Override
- public void onCreateActions(@NonNull List<GuidedAction> actions,
- Bundle savedInstanceState) {
- String[] choices = getResources().getStringArray(mChannelCountOnPreference == 0
- ? R.array.ut_setup_new_choices : R.array.ut_setup_again_choices);
- for (int i = 0; i < choices.length - 1; ++i) {
- actions.add(new GuidedAction.Builder(getActivity()).id(i).title(choices[i])
- .build());
- }
- actions.add(new GuidedAction.Builder(getActivity()).id(ACTION_DONE)
- .title(choices[choices.length - 1]).build());
- }
-
- @Override
- protected String getActionCategory() {
- return ACTION_CATEGORY;
- }
- }
-}
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 f17dd46b..00000000
--- a/src/com/android/tv/tuner/source/FileTsStreamer.java
+++ /dev/null
@@ -1,484 +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.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
-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 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();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsDataSource.java b/src/com/android/tv/tuner/source/TsDataSource.java
deleted file mode 100644
index 2ce3e670..00000000
--- a/src/com/android/tv/tuner/source/TsDataSource.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2016 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 com.google.android.exoplayer.upstream.DataSource;
-
-/**
- * {@link DataSource} for MPEG-TS stream, which will be used by {@link TsExtractor}.
- */
-public abstract class TsDataSource implements DataSource {
-
- /**
- * Returns the number of bytes being buffered by {@link TsStreamer} so far.
- *
- * @return the buffered position
- */
- public long getBufferedPosition() {
- return 0;
- }
-
- /**
- * Returns the offset position where the last {@link DataSource#read} read.
- *
- * @return the last read position
- */
- public long getLastReadPosition() {
- return 0;
- }
-
- /**
- * Shifts start position by the specified offset.
- * Do not call this method when the class already provided MPEG-TS stream to the extractor.
- * @param offset 0 <= offset <= buffered position
- */
- public void shiftStartPosition(long offset) { }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java
deleted file mode 100644
index 16be7582..00000000
--- a/src/com/android/tv/tuner/source/TsDataSourceManager.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 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.support.annotation.VisibleForTesting;
-
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Manages {@link DataSource} for playback and recording.
- * The class hides handling of {@link TunerHal} and {@link TsStreamer} from other classes.
- * One TsDataSourceManager should be created for per session.
- */
-public class TsDataSourceManager {
- private static final Object sLock = new Object();
- private static final Map<TsDataSource, TsStreamer> sTsStreamers =
- new ConcurrentHashMap<>();
-
- private static int sSequenceId;
-
- private final int mId;
- private final boolean mIsRecording;
- private final TunerTsStreamerManager mTunerStreamerManager =
- TunerTsStreamerManager.getInstance();
-
- private boolean mKeepTuneStatus;
-
- /**
- * Creates TsDataSourceManager to create and release {@link DataSource} which will be
- * used for playing and recording.
- * @param isRecording {@code true} when for recording, {@code false} otherwise
- * @return {@link TsDataSourceManager}
- */
- public static TsDataSourceManager createSourceManager(boolean isRecording) {
- int id;
- synchronized (sLock) {
- id = ++sSequenceId;
- }
- return new TsDataSourceManager(id, isRecording);
- }
-
- private TsDataSourceManager(int id, boolean isRecording) {
- mId = id;
- mIsRecording = isRecording;
- mKeepTuneStatus = true;
- }
-
- /**
- * Creates or retrieves {@link TsDataSource} for playing or recording
- * @param context a {@link Context} instance
- * @param channel to play or record
- * @param eventListener for program information which will be scanned from MPEG2-TS stream
- * @return {@link TsDataSource} which will provide the specified channel stream
- */
- public TsDataSource createDataSource(Context context, TunerChannel channel,
- EventDetector.EventListener eventListener) {
- if (channel.getType() == Channel.TYPE_FILE) {
- // MPEG2 TS captured stream file recording is not supported.
- if (mIsRecording) {
- return null;
- }
- FileTsStreamer streamer = new FileTsStreamer(eventListener, context);
- if (streamer.startStream(channel)) {
- TsDataSource source = streamer.createDataSource();
- sTsStreamers.put(source, streamer);
- return source;
- }
- return null;
- }
- return mTunerStreamerManager.createDataSource(context, channel, eventListener,
- mId, !mIsRecording && mKeepTuneStatus);
- }
-
- /**
- * Releases the specified {@link TsDataSource} and underlying {@link TunerHal}.
- * @param source to release
- */
- public void releaseDataSource(TsDataSource source) {
- if (source instanceof TunerTsStreamer.TunerDataSource) {
- mTunerStreamerManager.releaseDataSource(
- source, mId, !mIsRecording && mKeepTuneStatus);
- } else if (source instanceof FileTsStreamer.FileDataSource) {
- FileTsStreamer streamer = (FileTsStreamer) sTsStreamers.get(source);
- if (streamer != null) {
- sTsStreamers.remove(source);
- streamer.stopStream();
- }
- }
- }
-
- /**
- * Indicates that the current session has pending tunes.
- */
- public void setHasPendingTune() {
- mTunerStreamerManager.setHasPendingTune(mId);
- }
-
- /**
- * Indicates whether the underlying {@link TunerHal} should be kept or not when data source
- * is being released.
- * TODO: If b/30750953 is fixed, we can remove this function.
- * @param keepTuneStatus underlying {@link TunerHal} will be reused when data source releasing.
- */
- public void setKeepTuneStatus(boolean keepTuneStatus) {
- mKeepTuneStatus = keepTuneStatus;
- }
-
- /**
- * Add tuner hal into TunerTsStreamerManager for test.
- */
- @VisibleForTesting
- public void addTunerHalForTest(TunerHal tunerHal) {
- mTunerStreamerManager.addTunerHal(tunerHal, mId);
- }
-
- /**
- * Releases persistent resources.
- */
- public void release() {
- mTunerStreamerManager.release(mId);
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsStreamWriter.java b/src/com/android/tv/tuner/source/TsStreamWriter.java
deleted file mode 100644
index 30650555..00000000
--- a/src/com/android/tv/tuner/source/TsStreamWriter.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2016 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.util.Log;
-import com.android.tv.tuner.data.TunerChannel;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stores TS files to the disk for debugging.
- */
-public class TsStreamWriter {
- private static final String TAG = "TsStreamWriter";
- private static final boolean DEBUG = false;
-
- private static final long TIME_LIMIT_MS = 10000; // 10s
- private static final int NO_INSTANCE_ID = 0;
- private static final int MAX_GET_ID_RETRY_COUNT = 5;
- private static final int MAX_INSTANCE_ID = 10000;
- private static final String SEPARATOR = "_";
-
- private FileOutputStream mFileOutputStream;
- private long mFileStartTimeMs;
- private String mFileName = null;
- private final String mDirectoryPath;
- private final File mDirectory;
- private final int mInstanceId;
- private TunerChannel mChannel;
-
- public TsStreamWriter(Context context) {
- File externalFilesDir = context.getExternalFilesDir(null);
- if (externalFilesDir == null || !externalFilesDir.isDirectory()) {
- mDirectoryPath = null;
- mDirectory = null;
- mInstanceId = NO_INSTANCE_ID;
- if (DEBUG) {
- Log.w(TAG, "Fail to get external files dir!");
- }
- } else {
- mDirectoryPath = externalFilesDir.getPath() + "/EngTsStream";
- mDirectory = new File(mDirectoryPath);
- if (!mDirectory.exists()) {
- boolean madeDir = mDirectory.mkdir();
- if (!madeDir) {
- Log.w(TAG, "Error. Fail to create folder!");
- }
- }
- mInstanceId = generateInstanceId();
- }
- }
-
- /**
- * Sets the current channel.
- *
- * @param channel curren channel of the stream
- */
- public void setChannel(TunerChannel channel) {
- mChannel = channel;
- }
-
- /**
- * Opens a file to store TS data.
- */
- public void openFile() {
- if (mChannel == null || mDirectoryPath == null) {
- return;
- }
- mFileStartTimeMs = System.currentTimeMillis();
- mFileName = mChannel.getDisplayNumber() + SEPARATOR + mFileStartTimeMs + SEPARATOR
- + mInstanceId + ".ts";
- String filePath = mDirectoryPath + "/" + mFileName;
- try {
- mFileOutputStream = new FileOutputStream(filePath, false);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Cannot open file: " + filePath, e);
- }
- }
-
- /**
- * Closes the file and stops storing TS data.
- *
- * @param calledWhenStopStream {@code true} if this method is called when the stream is stopped
- * {@code false} otherwise
- */
- public void closeFile(boolean calledWhenStopStream) {
- if (mFileOutputStream == null) {
- return;
- }
- try {
- mFileOutputStream.close();
- deleteOutdatedFiles(calledWhenStopStream);
- mFileName = null;
- mFileOutputStream = null;
- } catch (IOException e) {
- Log.w(TAG, "Error on closing file.", e);
- }
- }
-
- /**
- * Writes the data to the file.
- *
- * @param buffer the data to be written
- * @param bytesWritten number of bytes written
- */
- public void writeToFile(byte[] buffer, int bytesWritten) {
- if (mFileOutputStream == null) {
- return;
- }
- if (System.currentTimeMillis() - mFileStartTimeMs > TIME_LIMIT_MS) {
- closeFile(false);
- openFile();
- }
- try {
- mFileOutputStream.write(buffer, 0, bytesWritten);
- } catch (IOException e) {
- Log.w(TAG, "Error on writing TS stream.", e);
- }
- }
-
- /**
- * Deletes outdated files to save storage.
- *
- * @param deleteAll {@code true} if all the files with the relative ID should be deleted
- * {@code false} if the most recent file should not be deleted
- */
- private void deleteOutdatedFiles(boolean deleteAll) {
- if (mFileName == null) {
- return;
- }
- if (mDirectory == null || !mDirectory.isDirectory()) {
- Log.e(TAG, "Error. The folder doesn't exist!");
- return;
- }
- if (mFileName == null) {
- Log.e(TAG, "Error. The current file name is null!");
- return;
- }
- for (File file : mDirectory.listFiles()) {
- if (file.isFile() && getFileId(file) == mInstanceId
- && (deleteAll || !mFileName.equals(file.getName()))) {
- boolean deleted = file.delete();
- if (DEBUG && !deleted) {
- Log.w(TAG, "Failed to delete " + file.getName());
- }
- }
- }
- }
-
- /**
- * Generates a unique instance ID.
- *
- * @return a unique instance ID
- */
- private int generateInstanceId() {
- if (mDirectory == null) {
- return NO_INSTANCE_ID;
- }
- Set<Integer> idSet = getExistingIds();
- if (idSet == null) {
- return NO_INSTANCE_ID;
- }
- for (int i = 0; i < MAX_GET_ID_RETRY_COUNT; i++) {
- // Range [1, MAX_INSTANCE_ID]
- int id = (int)Math.floor(Math.random() * MAX_INSTANCE_ID) + 1;
- if (!idSet.contains(id)) {
- return id;
- }
- }
- return NO_INSTANCE_ID;
- }
-
- /**
- * Gets all existing instance IDs.
- *
- * @return a set of all existing instance IDs
- */
- private Set<Integer> getExistingIds() {
- if (mDirectory == null || !mDirectory.isDirectory()) {
- return null;
- }
-
- Set<Integer> idSet = new HashSet<>();
- for (File file : mDirectory.listFiles()) {
- int id = getFileId(file);
- if(id != NO_INSTANCE_ID) {
- idSet.add(id);
- }
- }
- return idSet;
- }
-
- /**
- * Gets the instance ID of a given file.
- *
- * @param file the file whose TsStreamWriter ID is returned
- * @return the TsStreamWriter ID of the file or NO_INSTANCE_ID if not available
- */
- private static int getFileId(File file) {
- if (file == null || !file.isFile()) {
- return NO_INSTANCE_ID;
- }
- String fileName = file.getName();
- int lastSeparator = fileName.lastIndexOf(SEPARATOR);
- if (!fileName.endsWith(".ts") || lastSeparator == -1) {
- return NO_INSTANCE_ID;
- }
- try {
- return Integer.parseInt(fileName.substring(lastSeparator + 1, fileName.length() - 3));
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Log.e(TAG, fileName + " is not a valid file name.");
- }
- }
- return NO_INSTANCE_ID;
- }
-}
diff --git a/src/com/android/tv/tuner/source/TsStreamer.java b/src/com/android/tv/tuner/source/TsStreamer.java
deleted file mode 100644
index 1ac950bb..00000000
--- a/src/com/android/tv/tuner/source/TsStreamer.java
+++ /dev/null
@@ -1,56 +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 com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.data.TunerChannel;
-
-/**
- * Interface definition for a stream generator. The interface will provide streams
- * for scanning channels and/or playback.
- */
-public interface TsStreamer {
- /**
- * Starts streaming the data for channel scanning process.
- *
- * @param channel {@link ChannelScanFileParser.ScanChannel} to be scanned
- * @return {@code true} if ready to stream, otherwise {@code false}
- */
- boolean startStream(ChannelScanFileParser.ScanChannel channel);
-
- /**
- * Starts streaming the data for channel playing or recording.
- *
- * @param channel {@link TunerChannel} to tune
- * @return {@code true} if ready to stream, otherwise {@code false}
- */
- boolean startStream(TunerChannel channel);
-
- /**
- * Stops streaming the data.
- */
- void stopStream();
-
- /**
- * Creates {@link TsDataSource} which will provide MPEG-2 TS stream for
- * {@link android.media.MediaExtractor}. The source will start from the position
- * where it is created.
- *
- * @return {@link TsDataSource}
- */
- TsDataSource createDataSource();
-}
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java
deleted file mode 100644
index 843cbdb7..00000000
--- a/src/com/android/tv/tuner/source/TunerTsStreamer.java
+++ /dev/null
@@ -1,408 +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.util.Log;
-import android.util.Pair;
-
-import com.google.android.exoplayer.C;
-import com.google.android.exoplayer.upstream.DataSpec;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.ChannelScanFileParser;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.tvinput.EventDetector;
-import com.android.tv.tuner.tvinput.EventDetector.EventListener;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device.
- */
-public class TunerTsStreamer implements TsStreamer {
- private static final String TAG = "TunerTsStreamer";
-
- private static final int MIN_READ_UNIT = 1500;
- private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB
- private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000; // ~ 30MB
- private static final int TS_PACKET_SIZE = 188;
-
- private static final int READ_TIMEOUT_MS = 5000; // 5 secs.
- private static final int BUFFER_UNDERRUN_SLEEP_MS = 10;
- private static final int READ_ERROR_STREAMING_ENDED = -1;
- private static final int READ_ERROR_BUFFER_OVERWRITTEN = -2;
-
- private final Object mCircularBufferMonitor = new Object();
- private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE];
- private long mBytesFetched;
- private final AtomicLong mLastReadPosition = new AtomicLong();
- private boolean mStreaming;
-
- private final TunerHal mTunerHal;
- private TunerChannel mChannel;
- private Thread mStreamingThread;
- private final EventDetector mEventDetector;
- private final List<Pair<EventListener, Boolean>> mEventListenerActions = new ArrayList<>();
-
- private final TsStreamWriter mTsStreamWriter;
- private String mChannelNumber;
-
- public static class TunerDataSource extends TsDataSource {
- private final TunerTsStreamer mTsStreamer;
- private final AtomicLong mLastReadPosition = new AtomicLong(0);
- private long mStartBufferedPosition;
-
- private TunerDataSource(TunerTsStreamer 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);
- } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) {
- long currentPosition = mStartBufferedPosition + mLastReadPosition.get();
- long endPosition = mTsStreamer.getBufferedPosition();
- long diff = ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE)
- * TS_PACKET_SIZE;
- Log.w(TAG, "Demux position jump by overwritten buffer: " + diff);
- mStartBufferedPosition = currentPosition + diff;
- mLastReadPosition.set(0);
- return 0;
- }
- return ret;
- }
- }
- /**
- * Creates {@link TsStreamer} for playing or recording the specified channel.
- * @param tunerHal the HAL for tuner device
- * @param eventListener the listener for channel & program information
- */
- public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) {
- mTunerHal = tunerHal;
- mEventDetector = new EventDetector(mTunerHal);
- if (eventListener != null) {
- mEventDetector.registerListener(eventListener);
- }
- mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ?
- new TsStreamWriter(context) : null;
- }
-
- public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener) {
- this(tunerHal, eventListener, null);
- }
-
- @Override
- public boolean startStream(TunerChannel channel) {
- if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(),
- channel.getDisplayNumber(false))) {
- if (channel.hasVideo()) {
- mTunerHal.addPidFilter(channel.getVideoPid(),
- TunerHal.FILTER_TYPE_VIDEO);
- }
- boolean audioFilterSet = false;
- for (Integer audioPid : channel.getAudioPids()) {
- if (!audioFilterSet) {
- mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_AUDIO);
- audioFilterSet = true;
- } else {
- // FILTER_TYPE_AUDIO overrides the previous filter for audio. We use
- // FILTER_TYPE_OTHER from the secondary one to get the all audio tracks.
- mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_OTHER);
- }
- }
- mTunerHal.addPidFilter(channel.getPcrPid(),
- TunerHal.FILTER_TYPE_PCR);
- if (mEventDetector != null) {
- mEventDetector.startDetecting(channel.getFrequency(), channel.getModulation(),
- channel.getProgramNumber());
- }
- mChannel = channel;
- mChannelNumber = channel.getDisplayNumber();
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- Log.w(TAG, "Streaming should be stopped before start streaming");
- return true;
- }
- mStreaming = true;
- mBytesFetched = 0;
- mLastReadPosition.set(0L);
- }
- if (mTsStreamWriter != null) {
- mTsStreamWriter.setChannel(mChannel);
- mTsStreamWriter.openFile();
- }
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
- return false;
- }
-
- @Override
- public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
- if (mTunerHal.tune(channel.frequency, channel.modulation, null)) {
- mEventDetector.startDetecting(
- channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS);
- synchronized (mCircularBufferMonitor) {
- if (mStreaming) {
- Log.w(TAG, "Streaming should be stopped before start streaming");
- return true;
- }
- mStreaming = true;
- mBytesFetched = 0;
- mLastReadPosition.set(0L);
- }
- mStreamingThread = new StreamingThread();
- mStreamingThread.start();
- Log.i(TAG, "Streaming started");
- return true;
- }
- return false;
- }
-
- /**
- * 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() {
- mChannel = null;
- synchronized (mCircularBufferMonitor) {
- mStreaming = false;
- mCircularBufferMonitor.notifyAll();
- }
-
- try {
- if (mStreamingThread != null) {
- mStreamingThread.join();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- if (mTsStreamWriter != null) {
- mTsStreamWriter.closeFile(true);
- mTsStreamWriter.setChannel(null);
- }
- }
-
- @Override
- public TsDataSource createDataSource() {
- return new TunerDataSource(this);
- }
-
- /**
- * Returns incomplete channel lists which was scanned so far. Incomplete channel means
- * the channel whose channel information is not complete or is not well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- return mEventDetector.getMalFormedChannels();
- }
-
- /**
- * Returns the current {@link TunerHal} which provides MPEG-TS stream for TunerTsStreamer.
- * @return {@link TunerHal}
- */
- public TunerHal getTunerHal() {
- return mTunerHal;
- }
-
- /**
- * Returns the current tuned channel for TunerTsStreamer.
- * @return {@link TunerChannel}
- */
- public TunerChannel getChannel() {
- return mChannel;
- }
-
- /**
- * Returns the current buffered position from tuner.
- * @return the current buffered position
- */
- public long getBufferedPosition() {
- synchronized (mCircularBufferMonitor) {
- return mBytesFetched;
- }
- }
-
- public String getStreamerInfo() {
- return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming;
- }
-
- public void registerListener(EventListener listener) {
- if (mEventDetector != null && listener != null) {
- synchronized (mEventListenerActions) {
- mEventListenerActions.add(new Pair<>(listener, true));
- }
- }
- }
-
- public void unregisterListener(EventListener listener) {
- if (mEventDetector != null) {
- synchronized (mEventListenerActions) {
- mEventListenerActions.add(new Pair(listener, false));
- }
- }
- }
-
- private class StreamingThread extends Thread {
- @Override
- public void run() {
- // Buffers for streaming data from the tuner and the internal buffer.
- byte[] dataBuffer = new byte[READ_BUFFER_SIZE];
-
- while (true) {
- synchronized (mCircularBufferMonitor) {
- if (!mStreaming) {
- break;
- }
- }
-
- if (mEventDetector != null) {
- synchronized (mEventListenerActions) {
- for (Pair listenerAction : mEventListenerActions) {
- EventListener listener = (EventListener) listenerAction.first;
- if ((boolean) listenerAction.second) {
- mEventDetector.registerListener(listener);
- } else {
- mEventDetector.unregisterListener(listener);
- }
- }
- mEventListenerActions.clear();
- }
- }
-
- int bytesWritten = mTunerHal.readTsStream(dataBuffer, dataBuffer.length);
- 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;
- }
-
- if (mTsStreamWriter != null) {
- mTsStreamWriter.writeToFile(dataBuffer, bytesWritten);
- }
-
- if (mEventDetector != null) {
- 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.notifyAll();
- }
- }
-
- Log.i(TAG, "Streaming stopped");
- }
- }
-
- /**
- * 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 {
- while (true) {
- synchronized (mCircularBufferMonitor) {
- if (!mStreaming) {
- return READ_ERROR_STREAMING_ENDED;
- }
- if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
- Log.w(TAG, "Demux is requesting the data which is already overwritten.");
- return READ_ERROR_BUFFER_OVERWRITTEN;
- }
- if (mBytesFetched < pos + amount) {
- try {
- mCircularBufferMonitor.wait(READ_TIMEOUT_MS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- // Try again to prevent starvation.
- // Give chances to read from other threads.
- continue;
- }
- int startPos = (int) (pos % CIRCULAR_BUFFER_SIZE);
- int endPos = (int) ((pos + amount) % CIRCULAR_BUFFER_SIZE);
- int firstLength = (startPos > endPos ? CIRCULAR_BUFFER_SIZE : endPos) - startPos;
- System.arraycopy(mCircularBuffer, startPos, buffer, offset, firstLength);
- if (firstLength < amount) {
- System.arraycopy(mCircularBuffer, 0, buffer, offset + firstLength,
- amount - firstLength);
- }
- mCircularBufferMonitor.notifyAll();
- return amount;
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
deleted file mode 100644
index 258a4d86..00000000
--- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2016 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 com.android.tv.common.AutoCloseableUtils;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.tvinput.EventDetector;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Manages {@link TunerTsStreamer} for playback and recording.
- * The class hides handling of {@link TunerHal} from other classes.
- * This class is used by {@link TsDataSourceManager}. Don't use this class directly.
- */
-class TunerTsStreamerManager {
- // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator
- // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from
- // the same session.
- private final Object mCancelLock = new Object();
- private final StreamerFinder mStreamerFinder = new StreamerFinder();
- private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>();
- private final Map<Integer, EventDetector.EventListener> mListeners = new HashMap<>();
- private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>();
- private final TunerHalManager mTunerHalManager = new TunerHalManager();
- private static TunerTsStreamerManager sInstance;
-
- /**
- * Returns the singleton instance for the class
- * @return TunerTsStreamerManager
- */
- static synchronized TunerTsStreamerManager getInstance() {
- if (sInstance == null) {
- sInstance = new TunerTsStreamerManager();
- }
- return sInstance;
- }
-
- private TunerTsStreamerManager() { }
-
- synchronized TsDataSource createDataSource(
- Context context, TunerChannel channel, EventDetector.EventListener listener,
- int sessionId, boolean reuse) {
- TsStreamerCreator creator;
- synchronized (mCancelLock) {
- if (mStreamerFinder.containsLocked(channel)) {
- mStreamerFinder.appendSessionLocked(channel, sessionId);
- TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel);
- TsDataSource source = streamer.createDataSource();
- mListeners.put(sessionId, listener);
- streamer.registerListener(listener);
- mSourceToStreamerMap.put(source, streamer);
- return source;
- }
- creator = new TsStreamerCreator(context, channel, listener);
- mCreators.put(sessionId, creator);
- }
- TunerTsStreamer streamer = creator.create(sessionId, reuse);
- synchronized (mCancelLock) {
- mCreators.remove(sessionId);
- if (streamer == null) {
- return null;
- }
- if (!creator.isCancelledLocked()) {
- mStreamerFinder.putLocked(channel, sessionId, streamer);
- TsDataSource source = streamer.createDataSource();
- mListeners.put(sessionId, listener);
- mSourceToStreamerMap.put(source, streamer);
- return source;
- }
- }
- // Created streamer was cancelled by a new tune request.
- streamer.stopStream();
- TunerHal hal = streamer.getTunerHal();
- hal.setHasPendingTune(false);
- mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
- return null;
- }
-
- synchronized void releaseDataSource(TsDataSource source, int sessionId,
- boolean reuse) {
- TunerTsStreamer streamer;
- synchronized (mCancelLock) {
- streamer = mSourceToStreamerMap.get(source);
- mSourceToStreamerMap.remove(source);
- if (streamer == null) {
- return;
- }
- EventDetector.EventListener listener = mListeners.remove(sessionId);
- streamer.unregisterListener(listener);
- TunerChannel channel = streamer.getChannel();
- SoftPreconditions.checkState(channel != null);
- mStreamerFinder.removeSessionLocked(channel, sessionId);
- if (mStreamerFinder.containsLocked(channel)) {
- return;
- }
- }
- streamer.stopStream();
- TunerHal hal = streamer.getTunerHal();
- hal.setHasPendingTune(false);
- mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
- }
-
- void setHasPendingTune(int sessionId) {
- synchronized (mCancelLock) {
- if (mCreators.containsKey(sessionId)) {
- mCreators.get(sessionId).cancelLocked();
- }
- }
- }
-
- /**
- * Add tuner hal into TunerHalManager for test.
- */
- void addTunerHal(TunerHal tunerHal, int sessionId) {
- mTunerHalManager.addTunerHal(tunerHal, sessionId);
- }
-
- synchronized void release(int sessionId) {
- mTunerHalManager.releaseCachedHal(sessionId);
- }
-
- private class StreamerFinder {
- private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>();
- private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>();
-
- // @GuardedBy("mCancelLock")
- private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) {
- Set<Integer> sessions = new HashSet<>();
- sessions.add(sessionId);
- mSessions.put(channel, sessions);
- mStreamers.put(channel, streamer);
- }
-
- // @GuardedBy("mCancelLock")
- private void appendSessionLocked(TunerChannel channel, int sessionId) {
- if (mSessions.containsKey(channel)) {
- mSessions.get(channel).add(sessionId);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private void removeSessionLocked(TunerChannel channel, int sessionId) {
- Set<Integer> sessions = mSessions.get(channel);
- sessions.remove(sessionId);
- if (sessions.size() == 0) {
- mSessions.remove(channel);
- mStreamers.remove(channel);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private boolean containsLocked(TunerChannel channel) {
- return mSessions.containsKey(channel);
- }
-
- // @GuardedBy("mCancelLock")
- private TunerTsStreamer getStreamerLocked(TunerChannel channel) {
- return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null;
- }
- }
-
- /**
- * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same
- * session. The class supports the cancellation in creating new {@link TunerTsStreamer}.
- */
- private class TsStreamerCreator {
- private final Context mContext;
- private final TunerChannel mChannel;
- private final EventDetector.EventListener mEventListener;
- // mCancelled will be {@code true} if a new tune request for the same session
- // cancels create().
- private boolean mCancelled;
- private TunerHal mTunerHal;
-
- private TsStreamerCreator(Context context, TunerChannel channel,
- EventDetector.EventListener listener) {
- mContext = context;
- mChannel = channel;
- mEventListener = listener;
- }
-
- private TunerTsStreamer create(int sessionId, boolean reuse) {
- TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId);
- if (hal == null) {
- return null;
- }
- boolean canceled = false;
- synchronized (mCancelLock) {
- if (!mCancelled) {
- mTunerHal = hal;
- } else {
- canceled = true;
- }
- }
- if (!canceled) {
- TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext);
- if (tsStreamer.startStream(mChannel)) {
- return tsStreamer;
- }
- synchronized (mCancelLock) {
- mTunerHal = null;
- }
- }
- hal.setHasPendingTune(false);
- // Since TunerTsStreamer is not properly created, closes TunerHal.
- // And do not re-use TunerHal when it is not cancelled.
- mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse);
- return null;
- }
-
- // @GuardedBy("mCancelLock")
- private void cancelLocked() {
- if (mCancelled) {
- return;
- }
- mCancelled = true;
- if (mTunerHal != null) {
- mTunerHal.setHasPendingTune(true);
- }
- }
-
- // @GuardedBy("mCancelLock")
- private boolean isCancelledLocked() {
- return mCancelled;
- }
- }
-
- /**
- * Supports sharing {@link TunerHal} among multiple sessions.
- * The class also supports session affinity for {@link TunerHal} allocation.
- */
- private class TunerHalManager {
- private final Map<Integer, TunerHal> mTunerHals = new HashMap<>();
-
- private TunerHal getOrCreateTunerHal(Context context, int sessionId) {
- // Handles session affinity.
- TunerHal hal = mTunerHals.get(sessionId);
- if (hal != null) {
- mTunerHals.remove(sessionId);
- return hal;
- }
- // Finds a TunerHal which is cached for other sessions.
- Iterator it = mTunerHals.keySet().iterator();
- if (it.hasNext()) {
- Integer key = (Integer) it.next();
- hal = mTunerHals.get(key);
- mTunerHals.remove(key);
- return hal;
- }
- return TunerHal.createInstance(context);
- }
-
- private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
- if (!reuse || !hal.isReusable()) {
- AutoCloseableUtils.closeQuietly(hal);
- return;
- }
- TunerHal cachedHal = mTunerHals.get(sessionId);
- if (cachedHal != hal) {
- mTunerHals.put(sessionId, hal);
- if (cachedHal != null) {
- AutoCloseableUtils.closeQuietly(cachedHal);
- }
- }
- }
-
- private void releaseCachedHal(int sessionId) {
- TunerHal hal = mTunerHals.get(sessionId);
- if (hal != null) {
- mTunerHals.remove(sessionId);
- }
- if (hal != null) {
- AutoCloseableUtils.closeQuietly(hal);
- }
- }
-
- private void addTunerHal(TunerHal tunerHal, int sessionId) {
- mTunerHals.put(sessionId, tunerHal);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/ts/SectionParser.java b/src/com/android/tv/tuner/ts/SectionParser.java
deleted file mode 100644
index e1f890f3..00000000
--- a/src/com/android/tv/tuner/ts/SectionParser.java
+++ /dev/null
@@ -1,1759 +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.ts;
-
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract.Programs.Genres;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor;
-import com.android.tv.tuner.data.PsipData.CaptionServiceDescriptor;
-import com.android.tv.tuner.data.PsipData.ContentAdvisoryDescriptor;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.EttItem;
-import com.android.tv.tuner.data.PsipData.ExtendedChannelNameDescriptor;
-import com.android.tv.tuner.data.PsipData.GenreDescriptor;
-import com.android.tv.tuner.data.PsipData.Iso639LanguageDescriptor;
-import com.android.tv.tuner.data.PsipData.MgtItem;
-import com.android.tv.tuner.data.PsipData.ParentalRatingDescriptor;
-import com.android.tv.tuner.data.PsipData.PsipSection;
-import com.android.tv.tuner.data.PsipData.RatingRegion;
-import com.android.tv.tuner.data.PsipData.RegionalRating;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.ServiceDescriptor;
-import com.android.tv.tuner.data.PsipData.ShortEventDescriptor;
-import com.android.tv.tuner.data.PsipData.TsDescriptor;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import com.android.tv.tuner.util.ConvertUtils;
-import com.ibm.icu.text.UnicodeDecompressor;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.Calendar;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Parses ATSC PSIP sections.
- */
-public class SectionParser {
- private static final String TAG = "SectionParser";
- private static final boolean DEBUG = false;
-
- private static final byte TABLE_ID_PAT = (byte) 0x00;
- private static final byte TABLE_ID_PMT = (byte) 0x02;
- private static final byte TABLE_ID_MGT = (byte) 0xc7;
- private static final byte TABLE_ID_TVCT = (byte) 0xc8;
- private static final byte TABLE_ID_CVCT = (byte) 0xc9;
- private static final byte TABLE_ID_EIT = (byte) 0xcb;
- private static final byte TABLE_ID_ETT = (byte) 0xcc;
-
- // Table id for DVB
- private static final byte TABLE_ID_SDT = (byte) 0x42;
- private static final byte TABLE_ID_DVB_ACTUAL_P_F_EIT = (byte) 0x4e;
- private static final byte TABLE_ID_DVB_OTHER_P_F_EIT = (byte) 0x4f;
- private static final byte TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT = (byte) 0x50;
- private static final byte TABLE_ID_DVB_OTHER_SCHEDULE_EIT = (byte) 0x60;
-
- // For details of the structure for the tags of descriptors, see ATSC A/65 Table 6.25.
- public static final int DESCRIPTOR_TAG_ISO639LANGUAGE = 0x0a;
- public static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
- public static final int DESCRIPTOR_TAG_CONTENT_ADVISORY = 0x87;
- public static final int DESCRIPTOR_TAG_AC3_AUDIO_STREAM = 0x81;
- public static final int DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME = 0xa0;
- public static final int DESCRIPTOR_TAG_GENRE = 0xab;
-
- // For details of the structure for the tags of DVB descriptors, see DVB Document A038 Table 12.
- public static final int DVB_DESCRIPTOR_TAG_SERVICE = 0x48;
- public static final int DVB_DESCRIPTOR_TAG_SHORT_EVENT = 0X4d;
- public static final int DVB_DESCRIPTOR_TAG_CONTENT = 0x54;
- public static final int DVB_DESCRIPTOR_TAG_PARENTAL_RATING = 0x55;
-
- private static final byte COMPRESSION_TYPE_NO_COMPRESSION = (byte) 0x00;
- private static final byte MODE_SELECTED_UNICODE_RANGE_1 = (byte) 0x00; // 0x0000 - 0x00ff
- private static final byte MODE_UTF16 = (byte) 0x3f;
- private static final byte MODE_SCSU = (byte) 0x3e;
- private static final int MAX_SHORT_NAME_BYTES = 14;
-
- // See ANSI/CEA-766-C.
- private static final int RATING_REGION_US_TV = 1;
- private static final int RATING_REGION_KR_TV = 4;
-
- // The following values are defined in the live channels app.
- // See https://developer.android.com/reference/android/media/tv/TvContentRating.html.
- private static final String RATING_DOMAIN = "com.android.tv";
- private static final String RATING_REGION_RATING_SYSTEM_US_TV = "US_TV";
- private static final String RATING_REGION_RATING_SYSTEM_US_MV = "US_MV";
- private static final String RATING_REGION_RATING_SYSTEM_KR_TV = "KR_TV";
-
- private static final String[] RATING_REGION_TABLE_US_TV = {
- "US_TV_Y", "US_TV_Y7", "US_TV_G", "US_TV_PG", "US_TV_14", "US_TV_MA"
- };
-
- private static final String[] RATING_REGION_TABLE_US_MV = {
- "US_MV_G", "US_MV_PG", "US_MV_PG13", "US_MV_R", "US_MV_NC17"
- };
-
- private static final String[] RATING_REGION_TABLE_KR_TV = {
- "KR_TV_ALL", "KR_TV_7", "KR_TV_12", "KR_TV_15", "KR_TV_19"
- };
-
- private static final String[] RATING_REGION_TABLE_US_TV_SUBRATING = {
- "US_TV_D", "US_TV_L", "US_TV_S", "US_TV_V", "US_TV_FV"
- };
-
- // According to ANSI-CEA-766-D
- private static final int VALUE_US_TV_Y = 1;
- private static final int VALUE_US_TV_Y7 = 2;
- private static final int VALUE_US_TV_NONE = 1;
- private static final int VALUE_US_TV_G = 2;
- private static final int VALUE_US_TV_PG = 3;
- private static final int VALUE_US_TV_14 = 4;
- private static final int VALUE_US_TV_MA = 5;
-
- private static final int DIMENSION_US_TV_RATING = 0;
- private static final int DIMENSION_US_TV_D = 1;
- private static final int DIMENSION_US_TV_L = 2;
- private static final int DIMENSION_US_TV_S = 3;
- private static final int DIMENSION_US_TV_V = 4;
- private static final int DIMENSION_US_TV_Y = 5;
- private static final int DIMENSION_US_TV_FV = 6;
- private static final int DIMENSION_US_MV_RATING = 7;
-
- private static final int VALUE_US_MV_G = 2;
- private static final int VALUE_US_MV_PG = 3;
- private static final int VALUE_US_MV_PG13 = 4;
- private static final int VALUE_US_MV_R = 5;
- private static final int VALUE_US_MV_NC17 = 6;
- private static final int VALUE_US_MV_X = 7;
-
- private static final String STRING_US_TV_Y = "US_TV_Y";
- private static final String STRING_US_TV_Y7 = "US_TV_Y7";
- private static final String STRING_US_TV_FV = "US_TV_FV";
-
-
- /*
- * The following CRC table is from the code generated by the following command.
- * $ python pycrc.py --model crc-32-mpeg --algorithm table-driven --generate c
- * To see the details of pycrc, visit http://www.tty1.net/pycrc/index_en.html
- */
- public static final int[] CRC_TABLE = {
- 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
- 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
- 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
- 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
- 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
- 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
- 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
- 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
- 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
- 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
- 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
- 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
- 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
- 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
- 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
- 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
- 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
- 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
- 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
- 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
- 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
- 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
- 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
- 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
- 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
- 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
- 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
- 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
- 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
- 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
- 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
- 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
- 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
- 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
- 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
- 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
- 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
- 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
- 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
- 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
- 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
- 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
- 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
- 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
- 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
- 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
- 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
- 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
- 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
- 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
- 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
- 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
- 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
- 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
- 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
- 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
- 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
- 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
- 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
- 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
- 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
- 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
- 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
- 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
- };
-
- // A table which maps ATSC genres to TIF genres.
- // See ATSC/65 Table 6.20.
- private static final String[] CANONICAL_GENRES_TABLE = {
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- Genres.EDUCATION, Genres.ENTERTAINMENT, Genres.MOVIES, Genres.NEWS,
- Genres.LIFE_STYLE, Genres.SPORTS, null, Genres.MOVIES,
- null,
- Genres.FAMILY_KIDS, Genres.DRAMA, null, Genres.ENTERTAINMENT, Genres.SPORTS,
- Genres.SPORTS,
- null, null,
- Genres.MUSIC, Genres.EDUCATION,
- null,
- Genres.COMEDY,
- null,
- Genres.MUSIC,
- null, null,
- Genres.MOVIES, Genres.ENTERTAINMENT, Genres.NEWS, Genres.DRAMA,
- Genres.EDUCATION, Genres.MOVIES, Genres.SPORTS, Genres.MOVIES,
- null,
- Genres.LIFE_STYLE, Genres.ARTS, Genres.LIFE_STYLE, Genres.SPORTS,
- null, null,
- Genres.GAMING, Genres.LIFE_STYLE, Genres.SPORTS,
- null,
- Genres.LIFE_STYLE, Genres.EDUCATION, Genres.EDUCATION, Genres.LIFE_STYLE,
- Genres.SPORTS, Genres.LIFE_STYLE, Genres.MOVIES, Genres.NEWS,
- null, null, null,
- Genres.EDUCATION,
- null, null, null,
- Genres.EDUCATION,
- null, null, null,
- Genres.DRAMA, Genres.MUSIC, Genres.MOVIES,
- null,
- Genres.ANIMAL_WILDLIFE,
- null, null,
- Genres.PREMIER,
- null, null, null, null,
- Genres.SPORTS, Genres.ARTS,
- null, null, null,
- Genres.MOVIES, Genres.TECH_SCIENCE, Genres.DRAMA,
- null,
- Genres.SHOPPING, Genres.DRAMA,
- null,
- Genres.MOVIES, Genres.ENTERTAINMENT, Genres.TECH_SCIENCE, Genres.SPORTS,
- Genres.TRAVEL, Genres.ENTERTAINMENT, Genres.ARTS, Genres.NEWS,
- null,
- Genres.ARTS, Genres.SPORTS, Genres.SPORTS, Genres.NEWS,
- Genres.SPORTS, Genres.SPORTS, Genres.SPORTS, Genres.FAMILY_KIDS,
- Genres.FAMILY_KIDS, Genres.MOVIES,
- null,
- Genres.TECH_SCIENCE, Genres.MUSIC,
- null,
- Genres.SPORTS, Genres.FAMILY_KIDS, Genres.NEWS, Genres.SPORTS,
- Genres.NEWS, Genres.SPORTS, Genres.ANIMAL_WILDLIFE,
- null,
- Genres.MUSIC, Genres.NEWS, Genres.SPORTS,
- null,
- Genres.NEWS, Genres.NEWS, Genres.NEWS, Genres.NEWS,
- Genres.SPORTS, Genres.MOVIES, Genres.ARTS, Genres.ANIMAL_WILDLIFE,
- Genres.MUSIC, Genres.MUSIC, Genres.MOVIES, Genres.EDUCATION,
- Genres.DRAMA, Genres.SPORTS, Genres.SPORTS, Genres.SPORTS,
- Genres.SPORTS,
- null,
- Genres.SPORTS, Genres.SPORTS,
- };
-
- // A table which contains ATSC categorical genre code assignments.
- // See ATSC/65 Table 6.20.
- private static final String[] BROADCAST_GENRES_TABLE = new String[] {
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- null, null, null, null,
- "Education", "Entertainment", "Movie", "News",
- "Religious", "Sports", "Other", "Action",
- "Advertisement", "Animated", "Anthology", "Automobile",
- "Awards", "Baseball", "Basketball", "Bulletin",
- "Business", "Classical", "College", "Combat",
- "Comedy", "Commentary", "Concert", "Consumer",
- "Contemporary", "Crime", "Dance", "Documentary",
- "Drama", "Elementary", "Erotica", "Exercise",
- "Fantasy", "Farm", "Fashion", "Fiction",
- "Food", "Football", "Foreign", "Fund Raiser",
- "Game/Quiz", "Garden", "Golf", "Government",
- "Health", "High School", "History", "Hobby",
- "Hockey", "Home", "Horror", "Information",
- "Instruction", "International", "Interview", "Language",
- "Legal", "Live", "Local", "Math",
- "Medical", "Meeting", "Military", "Miniseries",
- "Music", "Mystery", "National", "Nature",
- "Police", "Politics", "Premier", "Prerecorded",
- "Product", "Professional", "Public", "Racing",
- "Reading", "Repair", "Repeat", "Review",
- "Romance", "Science", "Series", "Service",
- "Shopping", "Soap Opera", "Special", "Suspense",
- "Talk", "Technical", "Tennis", "Travel",
- "Variety", "Video", "Weather", "Western",
- "Art", "Auto Racing", "Aviation", "Biography",
- "Boating", "Bowling", "Boxing", "Cartoon",
- "Children", "Classic Film", "Community", "Computers",
- "Country Music", "Court", "Extreme Sports", "Family",
- "Financial", "Gymnastics", "Headlines", "Horse Racing",
- "Hunting/Fishing/Outdoors", "Independent", "Jazz", "Magazine",
- "Motorcycle Racing", "Music/Film/Books", "News-International", "News-Local",
- "News-National", "News-Regional", "Olympics", "Original",
- "Performing Arts", "Pets/Animals", "Pop", "Rock & Roll",
- "Sci-Fi", "Self Improvement", "Sitcom", "Skating",
- "Skiing", "Soccer", "Track/Field", "True",
- "Volleyball", "Wrestling",
- };
-
- // Audio language code map from ISO 639-2/B to 639-2/T, in order to show correct audio language.
- private static final HashMap<String, String> ISO_LANGUAGE_CODE_MAP;
- static {
- ISO_LANGUAGE_CODE_MAP = new HashMap<>();
- ISO_LANGUAGE_CODE_MAP.put("alb", "sqi");
- ISO_LANGUAGE_CODE_MAP.put("arm", "hye");
- ISO_LANGUAGE_CODE_MAP.put("baq", "eus");
- ISO_LANGUAGE_CODE_MAP.put("bur", "mya");
- ISO_LANGUAGE_CODE_MAP.put("chi", "zho");
- ISO_LANGUAGE_CODE_MAP.put("cze", "ces");
- ISO_LANGUAGE_CODE_MAP.put("dut", "nld");
- ISO_LANGUAGE_CODE_MAP.put("fre", "fra");
- ISO_LANGUAGE_CODE_MAP.put("geo", "kat");
- ISO_LANGUAGE_CODE_MAP.put("ger", "deu");
- ISO_LANGUAGE_CODE_MAP.put("gre", "ell");
- ISO_LANGUAGE_CODE_MAP.put("ice", "isl");
- ISO_LANGUAGE_CODE_MAP.put("mac", "mkd");
- ISO_LANGUAGE_CODE_MAP.put("mao", "mri");
- ISO_LANGUAGE_CODE_MAP.put("may", "msa");
- ISO_LANGUAGE_CODE_MAP.put("per", "fas");
- ISO_LANGUAGE_CODE_MAP.put("rum", "ron");
- ISO_LANGUAGE_CODE_MAP.put("slo", "slk");
- ISO_LANGUAGE_CODE_MAP.put("tib", "bod");
- ISO_LANGUAGE_CODE_MAP.put("wel", "cym");
- ISO_LANGUAGE_CODE_MAP.put("esl", "spa"); // Special entry for channel 9-1 KQED in bay area.
- }
-
- // Containers to store the last version numbers of the PSIP sections.
- private final HashMap<PsipSection, Integer> mSectionVersionMap = new HashMap<>();
- private final SparseArray<List<EttItem>> mParsedEttItems = new SparseArray<>();
-
- public interface OutputListener {
- void onPatParsed(List<PatItem> items);
- void onPmtParsed(int programNumber, List<PmtItem> items);
- void onMgtParsed(List<MgtItem> items);
- void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber);
- void onEitParsed(int sourceId, List<EitItem> items);
- void onEttParsed(int sourceId, List<EttItem> descriptions);
- void onSdtParsed(List<SdtItem> items);
- }
-
- private final OutputListener mListener;
-
- public SectionParser(OutputListener listener) {
- mListener = listener;
- }
-
- public void parseSections(ByteArrayBuffer data) {
- int pos = 0;
- while (pos + 3 <= data.length()) {
- if ((data.byteAt(pos) & 0xff) == 0xff) {
- // Clear stuffing bytes according to H222.0 section 2.4.4.
- data.setLength(0);
- break;
- }
- int sectionLength =
- (((data.byteAt(pos + 1) & 0x0f) << 8) | (data.byteAt(pos + 2) & 0xff)) + 3;
- if (pos + sectionLength > data.length()) {
- break;
- }
- if (DEBUG) {
- Log.d(TAG, "parseSections 0x" + Integer.toHexString(data.byteAt(pos) & 0xff));
- }
- parseSection(Arrays.copyOfRange(data.buffer(), pos, pos + sectionLength));
- pos += sectionLength;
- }
- if (mListener != null) {
- for (int i = 0; i < mParsedEttItems.size(); ++i) {
- int sourceId = mParsedEttItems.keyAt(i);
- List<EttItem> descriptions = mParsedEttItems.valueAt(i);
- mListener.onEttParsed(sourceId, descriptions);
- }
- }
- mParsedEttItems.clear();
- }
-
- public void resetVersionNumbers() {
- mSectionVersionMap.clear();
- }
-
- private void parseSection(byte[] data) {
- if (!checkSanity(data)) {
- Log.d(TAG, "Bad CRC!");
- return;
- }
- PsipSection section = PsipSection.create(data);
- if (section == null) {
- return;
- }
-
- // The currentNextIndicator indicates that the section sent is currently applicable.
- if (!section.getCurrentNextIndicator()) {
- return;
- }
- int versionNumber = (data[5] & 0x3e) >> 1;
- Integer oldVersionNumber = mSectionVersionMap.get(section);
-
- // The versionNumber shall be incremented when a change in the information carried within
- // the section occurs.
- if (oldVersionNumber != null && versionNumber == oldVersionNumber) {
- return;
- }
- boolean result = false;
- switch (data[0]) {
- case TABLE_ID_PAT:
- result = parsePAT(data);
- break;
- case TABLE_ID_PMT:
- result = parsePMT(data);
- break;
- case TABLE_ID_MGT:
- result = parseMGT(data);
- break;
- case TABLE_ID_TVCT:
- case TABLE_ID_CVCT:
- result = parseVCT(data);
- break;
- case TABLE_ID_EIT:
- result = parseEIT(data);
- break;
- case TABLE_ID_ETT:
- result = parseETT(data);
- break;
- case TABLE_ID_SDT:
- result = parseSDT(data);
- break;
- case TABLE_ID_DVB_ACTUAL_P_F_EIT:
- case TABLE_ID_DVB_ACTUAL_SCHEDULE_EIT:
- result = parseDVBEIT(data);
- break;
- default:
- break;
- }
- if (result) {
- mSectionVersionMap.put(section, versionNumber);
- }
- }
-
- private boolean parsePAT(byte[] data) {
- if (DEBUG) {
- Log.d(TAG, "PAT is discovered.");
- }
- int pos = 8;
-
- List<PatItem> results = new ArrayList<>();
- for (; pos < data.length - 4; pos = pos + 4) {
- if (pos > data.length - 4 - 4) {
- Log.e(TAG, "Broken PAT.");
- return false;
- }
- int programNo = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int pmtPid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
- results.add(new PatItem(programNo, pmtPid));
- }
- if (mListener != null) {
- mListener.onPatParsed(results);
- }
- return true;
- }
-
- private boolean parsePMT(byte[] data) {
- int table_id_ext = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- if (DEBUG) {
- Log.d(TAG, "PMT is discovered. programNo = " + table_id_ext);
- }
- if (data.length <= 11) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- int pcrPid = (data[8] & 0x1f) << 8 | data[9];
- int programInfoLen = (data[10] & 0x0f) << 8 | data[11];
- int pos = 12;
- List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + programInfoLen);
- pos += programInfoLen;
- if (DEBUG) {
- Log.d(TAG, "PMT descriptors size: " + descriptors.size());
- }
- List<PmtItem> results = new ArrayList<>();
- for (; pos < data.length - 4;) {
- if (pos < 0) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- int streamType = data[pos] & 0xff;
- int esPid = (data[pos + 1] & 0x1f) << 8 | (data[pos + 2] & 0xff);
- int esInfoLen = (data[pos + 3] & 0xf) << 8 | (data[pos + 4] & 0xff);
- if (data.length < pos + esInfoLen + 5) {
- Log.e(TAG, "Broken PMT.");
- return false;
- }
- descriptors = parseDescriptors(data, pos + 5, pos + 5 + esInfoLen);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- PmtItem pmtItem = new PmtItem(streamType, esPid, audioTracks, captionTracks);
- if (DEBUG) {
- Log.d(TAG, "PMT " + pmtItem + " descriptors size: " + descriptors.size());
- }
- results.add(pmtItem);
- pos = pos + esInfoLen + 5;
- }
- results.add(new PmtItem(PmtItem.ES_PID_PCR, pcrPid, null, null));
- if (mListener != null) {
- mListener.onPmtParsed(table_id_ext, results);
- }
- return true;
- }
-
- private boolean parseMGT(byte[] data) {
- // For details of the structure for MGT, see ATSC A/65 Table 6.2.
- if (DEBUG) {
- Log.d(TAG, "MGT is discovered.");
- }
- if (data.length <= 10) {
- Log.e(TAG, "Broken MGT.");
- return false;
- }
- int tablesDefined = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
- int pos = 11;
- List<MgtItem> results = new ArrayList<>();
- for (int i = 0; i < tablesDefined; ++i) {
- if (data.length <= pos + 10) {
- Log.e(TAG, "Broken MGT.");
- return false;
- }
- int tableType = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int tableTypePid = ((data[pos + 2] & 0x1f) << 8) | (data[pos + 3] & 0xff);
- int descriptorsLength = ((data[pos + 9] & 0x0f) << 8) | (data[pos + 10] & 0xff);
- pos += 11 + descriptorsLength;
- results.add(new MgtItem(tableType, tableTypePid));
- }
- // Skip the remaining descriptor part which we don't use.
-
- if (mListener != null) {
- mListener.onMgtParsed(results);
- }
- return true;
- }
-
- private boolean parseVCT(byte[] data) {
- // For details of the structure for VCT, see ATSC A/65 Table 6.4 and 6.8.
- if (DEBUG) {
- Log.d(TAG, "VCT is discovered.");
- }
- if (data.length <= 9) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- int numChannelsInSection = (data[9] & 0xff);
- int sectionNumber = (data[6] & 0xff);
- int lastSectionNumber = (data[7] & 0xff);
- if (sectionNumber > lastSectionNumber) {
- // According to section 6.3.1 of the spec ATSC A/65,
- // last section number is the largest section number.
- Log.w(TAG, "Invalid VCT. Section Number " + sectionNumber + " > Last Section Number "
- + lastSectionNumber);
- return false;
- }
- int pos = 10;
- List<VctItem> results = new ArrayList<>();
- for (int i = 0; i < numChannelsInSection; ++i) {
- if (data.length <= pos + 31) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- String shortName = "";
- int shortNameSize = getShortNameSize(data, pos);
- try {
- shortName = new String(
- Arrays.copyOfRange(data, pos, pos + shortNameSize), "UTF-16");
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Broken VCT.", e);
- return false;
- }
- if ((data[pos + 14] & 0xf0) != 0xf0) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- int majorNumber = ((data[pos + 14] & 0x0f) << 6) | ((data[pos + 15] & 0xff) >> 2);
- int minorNumber = ((data[pos + 15] & 0x03) << 8) | (data[pos + 16] & 0xff);
- if ((majorNumber & 0x3f0) == 0x3f0) {
- // If the six MSBs are 111111, these indicate that there is only one-part channel
- // number. To see details, refer A/65 Section 6.3.2.
- majorNumber = ((majorNumber & 0xf) << 10) + minorNumber;
- minorNumber = 0;
- }
- int channelTsid = ((data[pos + 22] & 0xff) << 8) | (data[pos + 23] & 0xff);
- int programNumber = ((data[pos + 24] & 0xff) << 8) | (data[pos + 25] & 0xff);
- boolean accessControlled = (data[pos + 26] & 0x20) != 0;
- boolean hidden = (data[pos + 26] & 0x10) != 0;
- int serviceType = (data[pos + 27] & 0x3f);
- int sourceId = ((data[pos + 28] & 0xff) << 8) | (data[pos + 29] & 0xff);
- int descriptorsPos = pos + 32;
- int descriptorsLength = ((data[pos + 30] & 0x03) << 8) | (data[pos + 31] & 0xff);
- pos += 32 + descriptorsLength;
- if (data.length < pos) {
- Log.e(TAG, "Broken VCT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- String longName = null;
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ExtendedChannelNameDescriptor) {
- ExtendedChannelNameDescriptor extendedChannelNameDescriptor =
- (ExtendedChannelNameDescriptor) descriptor;
- longName = extendedChannelNameDescriptor.getLongChannelName();
- break;
- }
- }
- if (DEBUG) {
- Log.d(TAG, String.format(
- "Found channel [%s] %s - serviceType: %d tsid: 0x%x program: %d "
- + "channel: %d-%d encrypted: %b hidden: %b, descriptors: %d",
- shortName, longName, serviceType, channelTsid, programNumber, majorNumber,
- minorNumber, accessControlled, hidden, descriptors.size()));
- }
- if (!accessControlled && !hidden && (serviceType == Channel.SERVICE_TYPE_ATSC_AUDIO ||
- serviceType == Channel.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION ||
- serviceType == Channel.SERVICE_TYPE_UNASSOCIATED_SMALL_SCREEN_SERVICE)) {
- // Hide hidden, encrypted, or unsupported ATSC service type channels
- results.add(new VctItem(shortName, longName, serviceType, channelTsid,
- programNumber, majorNumber, minorNumber, sourceId));
- }
- }
- // Skip the remaining descriptor part which we don't use.
-
- if (mListener != null) {
- mListener.onVctParsed(results, sectionNumber, lastSectionNumber);
- }
- return true;
- }
-
- private boolean parseEIT(byte[] data) {
- // For details of the structure for EIT, see ATSC A/65 Table 6.11.
- if (DEBUG) {
- Log.d(TAG, "EIT is discovered.");
- }
- if (data.length <= 9) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int numEventsInSection = (data[9] & 0xff);
-
- int pos = 10;
- List<EitItem> results = new ArrayList<>();
- for (int i = 0; i < numEventsInSection; ++i) {
- if (data.length <= pos + 9) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- if ((data[pos] & 0xc0) != 0xc0) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int eventId = ((data[pos] & 0x3f) << 8) + (data[pos + 1] & 0xff);
- long startTime = ((data[pos + 2] & (long) 0xff) << 24) | ((data[pos + 3] & 0xff) << 16)
- | ((data[pos + 4] & 0xff) << 8) | (data[pos + 5] & 0xff);
- int lengthInSecond = ((data[pos + 6] & 0x0f) << 16)
- | ((data[pos + 7] & 0xff) << 8) | (data[pos + 8] & 0xff);
- int titleLength = (data[pos + 9] & 0xff);
- if (data.length <= pos + 10 + titleLength + 1) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- String titleText = "";
- if (titleLength > 0) {
- titleText = extractText(data, pos + 10);
- }
- if ((data[pos + 10 + titleLength] & 0xf0) != 0xf0) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- int descriptorsLength = ((data[pos + 10 + titleLength] & 0x0f) << 8)
- | (data[pos + 10 + titleLength + 1] & 0xff);
- int descriptorsPos = pos + 10 + titleLength + 2;
- if (data.length < descriptorsPos + descriptorsLength) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- if (DEBUG) {
- Log.d(TAG, String.format("EIT descriptors size: %d", descriptors.size()));
- }
- String contentRating = generateContentRating(descriptors);
- String broadcastGenre = generateBroadcastGenre(descriptors);
- String canonicalGenre = generateCanonicalGenre(descriptors);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- pos += 10 + titleLength + 2 + descriptorsLength;
- results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
- startTime, lengthInSecond, contentRating, audioTracks, captionTracks,
- broadcastGenre, canonicalGenre, null));
- }
- if (mListener != null) {
- mListener.onEitParsed(sourceId, results);
- }
- return true;
- }
-
- private boolean parseETT(byte[] data) {
- // For details of the structure for ETT, see ATSC A/65 Table 6.13.
- if (DEBUG) {
- Log.d(TAG, "ETT is discovered.");
- }
- if (data.length <= 12) {
- Log.e(TAG, "Broken ETT.");
- return false;
- }
- int sourceId = ((data[9] & 0xff) << 8) | (data[10] & 0xff);
- int eventId = (((data[11] & 0xff) << 8) | (data[12] & 0xff)) >> 2;
- String text = extractText(data, 13);
- List<EttItem> ettItems = mParsedEttItems.get(sourceId);
- if (ettItems == null) {
- ettItems = new ArrayList<>();
- mParsedEttItems.put(sourceId, ettItems);
- }
- ettItems.add(new EttItem(eventId, text));
- return true;
- }
-
- private boolean parseSDT(byte[] data) {
- // For details of the structure for SDT, see DVB Document A038 Table 5.
- if (DEBUG) {
- Log.d(TAG, "SDT id discovered");
- }
- if (data.length <= 11) {
- Log.e(TAG, "Broken SDT.");
- return false;
- }
- if ((data[1] & 0x80) >> 7 != 1) {
- Log.e(TAG, "Broken SDT, section syntax indicator error.");
- return false;
- }
- int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
- int transportStreamId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int originalNetworkId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
- int pos = 11;
- if (sectionLength + 3 > data.length) {
- Log.e(TAG, "Broken SDT.");
- }
- List<SdtItem> sdtItems = new ArrayList<>();
- while (pos + 9 < data.length) {
- int serviceId = ((data[pos] & 0xff) << 8) | (data[pos + 1] & 0xff);
- int descriptorsLength = ((data[pos + 3] & 0x0f) << 8) | (data[pos + 4] & 0xff);
- pos += 5;
- List<TsDescriptor> descriptors = parseDescriptors(data, pos, pos + descriptorsLength);
- List<ServiceDescriptor> serviceDescriptors = generateServiceDescriptors(descriptors);
- String serviceName = "";
- String serviceProviderName = "";
- int serviceType = 0;
- for (ServiceDescriptor serviceDescriptor : serviceDescriptors) {
- serviceName = serviceDescriptor.getServiceName();
- serviceProviderName = serviceDescriptor.getServiceProviderName();
- serviceType = serviceDescriptor.getServiceType();
- }
- if (serviceDescriptors.size() > 0) {
- sdtItems.add(new SdtItem(serviceName, serviceProviderName, serviceType, serviceId,
- originalNetworkId));
- }
- pos += descriptorsLength;
- }
- if (mListener != null) {
- mListener.onSdtParsed(sdtItems);
- }
- return true;
- }
-
- private boolean parseDVBEIT(byte[] data) {
- // For details of the structure for DVB ETT, see DVB Document A038 Table 7.
- if (DEBUG) {
- Log.d(TAG, "DVB EIT is discovered.");
- }
- if (data.length < 18) {
- Log.e(TAG, "Broken DVB EIT.");
- return false;
- }
- int sectionLength = ((data[1] & 0x0f) << 8) | (data[2] & 0xff);
- int sourceId = ((data[3] & 0xff) << 8) | (data[4] & 0xff);
- int transportStreamId = ((data[8] & 0xff) << 8) | (data[9] & 0xff);
- int originalNetworkId = ((data[10] & 0xff) << 8) | (data[11] & 0xff);
-
- int pos = 14;
- List<EitItem> results = new ArrayList<>();
- while (pos + 12 < data.length) {
- int eventId = ((data[pos] & 0xff) << 8) + (data[pos + 1] & 0xff);
- float modifiedJulianDate = ((data[pos + 2] & 0xff) << 8) | (data[pos + 3] & 0xff);
- int startYear = (int) ((modifiedJulianDate - 15078.2f) / 365.25f);
- int mjdMonth = (int) ((modifiedJulianDate - 14956.1f
- - (int) (startYear * 365.25f)) / 30.6001f);
- int startDay = (int) modifiedJulianDate - 14956 - (int) (startYear * 365.25f)
- - (int) (mjdMonth * 30.6001f);
- int startMonth = mjdMonth - 1;
- if (mjdMonth == 14 || mjdMonth == 15) {
- startYear += 1;
- startMonth -= 12;
- }
- int startHour = ((data[pos + 4] & 0xf0) >> 4) * 10 + (data[pos + 4] & 0x0f);
- int startMinute = ((data[pos + 5] & 0xf0) >> 4) * 10 + (data[pos + 5] & 0x0f);
- int startSecond = ((data[pos + 6] & 0xf0) >> 4) * 10 + (data[pos + 6] & 0x0f);
- Calendar calendar = Calendar.getInstance();
- startYear += 1900;
- calendar.set(startYear, startMonth, startDay, startHour, startMinute, startSecond);
- long startTime = ConvertUtils.convertUnixEpochToGPSTime(
- calendar.getTimeInMillis() / 1000);
- int durationInSecond = (((data[pos + 7] & 0xf0) >> 4) * 10
- + (data[pos + 7] & 0x0f)) * 3600
- + (((data[pos + 8] & 0xf0) >> 4) * 10 + (data[pos + 8] & 0x0f)) * 60
- + (((data[pos + 9] & 0xf0) >> 4) * 10 + (data[pos + 9] & 0x0f));
- int descriptorsLength = ((data[pos + 10] & 0x0f) << 8)
- | (data[pos + 10 + 1] & 0xff);
- int descriptorsPos = pos + 10 + 2;
- if (data.length < descriptorsPos + descriptorsLength) {
- Log.e(TAG, "Broken EIT.");
- return false;
- }
- List<TsDescriptor> descriptors = parseDescriptors(
- data, descriptorsPos, descriptorsPos + descriptorsLength);
- if (DEBUG) {
- Log.d(TAG, String.format("DVB EIT descriptors size: %d", descriptors.size()));
- }
- // TODO: Add logic to generating content rating for dvb. See DVB document 6.2.28 for
- // details. Content rating here will be null
- String contentRating = generateContentRating(descriptors);
- // TODO: Add logic for generating genre for dvb. See DVB document 6.2.9 for details.
- // Genre here will be null here.
- String broadcastGenre = generateBroadcastGenre(descriptors);
- String canonicalGenre = generateCanonicalGenre(descriptors);
- String titleText = generateShortEventName(descriptors);
- List<AtscAudioTrack> audioTracks = generateAudioTracks(descriptors);
- List<AtscCaptionTrack> captionTracks = generateCaptionTracks(descriptors);
- pos += 12 + descriptorsLength;
- results.add(new EitItem(EitItem.INVALID_PROGRAM_ID, eventId, titleText,
- startTime, durationInSecond, contentRating, audioTracks, captionTracks,
- broadcastGenre, canonicalGenre, null));
- }
- if (mListener != null) {
- mListener.onEitParsed(sourceId, results);
- }
- return true;
- }
-
- private static List<AtscAudioTrack> generateAudioTracks(List<TsDescriptor> descriptors) {
- // The list of audio tracks sent is located at both AC3 Audio descriptor and ISO 639
- // Language descriptor.
- List<AtscAudioTrack> ac3Tracks = new ArrayList<>();
- List<AtscAudioTrack> iso639LanguageTracks = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof Ac3AudioDescriptor) {
- Ac3AudioDescriptor audioDescriptor =
- (Ac3AudioDescriptor) descriptor;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
- if (audioDescriptor.getLanguage() != null) {
- audioTrack.language = audioDescriptor.getLanguage();
- }
- if (audioTrack.language == null) {
- audioTrack.language = "";
- }
- audioTrack.audioType = AtscAudioTrack.AUDIOTYPE_UNDEFINED;
- audioTrack.channelCount = audioDescriptor.getNumChannels();
- audioTrack.sampleRate = audioDescriptor.getSampleRate();
- ac3Tracks.add(audioTrack);
- }
- }
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof Iso639LanguageDescriptor) {
- Iso639LanguageDescriptor iso639LanguageDescriptor =
- (Iso639LanguageDescriptor) descriptor;
- iso639LanguageTracks.addAll(iso639LanguageDescriptor.getAudioTracks());
- }
- }
-
- // An AC3 audio stream descriptor only has a audio channel count and a audio sample rate
- // while a ISO 639 Language descriptor only has a audio type, which describes a main use
- // case of its audio track.
- // Some channels contain only AC3 audio stream descriptors with valid language values.
- // Other channels contain both an AC3 audio stream descriptor and a ISO 639 Language
- // descriptor per audio track, and those AC3 audio stream descriptors often have a null
- // value of language field.
- // Combines two descriptors into one in order to gather more audio track specific
- // information as much as possible.
- List<AtscAudioTrack> tracks = new ArrayList<>();
- if (!ac3Tracks.isEmpty() && !iso639LanguageTracks.isEmpty()
- && ac3Tracks.size() != iso639LanguageTracks.size()) {
- // This shouldn't be happen. In here, it handles two cases. The first case is that the
- // only one type of descriptors arrives. The second case is that the two types of
- // descriptors have the same number of tracks.
- Log.e(TAG, "AC3 audio stream descriptors size != ISO 639 Language descriptors size");
- return tracks;
- }
- int size = Math.max(ac3Tracks.size(), iso639LanguageTracks.size());
- for (int i = 0; i < size; ++i) {
- AtscAudioTrack audioTrack = null;
- if (i < ac3Tracks.size()) {
- audioTrack = ac3Tracks.get(i);
- }
- if (i < iso639LanguageTracks.size()) {
- if (audioTrack == null) {
- audioTrack = iso639LanguageTracks.get(i);
- } else {
- AtscAudioTrack iso639LanguageTrack = iso639LanguageTracks.get(i);
- if (audioTrack.language == null || TextUtils.equals(audioTrack.language, "")) {
- audioTrack.language = iso639LanguageTrack.language;
- }
- audioTrack.audioType = iso639LanguageTrack.audioType;
- }
- }
- String language = ISO_LANGUAGE_CODE_MAP.get(audioTrack.language);
- if (language != null) {
- audioTrack.language = language;
- }
- tracks.add(audioTrack);
- }
- return tracks;
- }
-
- private static List<AtscCaptionTrack> generateCaptionTracks(List<TsDescriptor> descriptors) {
- List<AtscCaptionTrack> services = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof CaptionServiceDescriptor) {
- CaptionServiceDescriptor captionServiceDescriptor =
- (CaptionServiceDescriptor) descriptor;
- services.addAll(captionServiceDescriptor.getCaptionTracks());
- }
- }
- return services;
- }
-
- @VisibleForTesting
- static String generateContentRating(List<TsDescriptor> descriptors) {
- Set<String> contentRatings = new ArraySet<>();
- List<RatingRegion> usRatingRegions = getRatingRegions(descriptors, RATING_REGION_US_TV);
- List<RatingRegion> krRatingRegions = getRatingRegions(descriptors, RATING_REGION_KR_TV);
- for (RatingRegion region : usRatingRegions) {
- String contentRating = getUsRating(region);
- if (contentRating != null) {
- contentRatings.add(contentRating);
- }
- }
- for (RatingRegion region : krRatingRegions) {
- String contentRating = getKrRating(region);
- if (contentRating != null) {
- contentRatings.add(contentRating);
- }
- }
- return TextUtils.join(",", contentRatings);
- }
-
- /**
- * Gets a list of {@link RatingRegion} in the specific region.
- *
- * @param descriptors {@link TsDescriptor} list which may contains rating information
- * @param region the specific region
- * @return a list of {@link RatingRegion} in the specific region
- */
- private static List<RatingRegion> getRatingRegions(List<TsDescriptor> descriptors, int region) {
- List<RatingRegion> ratingRegions = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (!(descriptor instanceof ContentAdvisoryDescriptor)) {
- continue;
- }
- ContentAdvisoryDescriptor contentAdvisoryDescriptor =
- (ContentAdvisoryDescriptor) descriptor;
- for (RatingRegion ratingRegion : contentAdvisoryDescriptor.getRatingRegions()) {
- if (ratingRegion.getName() == region) {
- ratingRegions.add(ratingRegion);
- }
- }
- }
- return ratingRegions;
- }
-
- /**
- * Gets US content rating and subratings (if any).
- *
- * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
- * @return A string representing the US content rating and subratings. The format of the string
- * is defined in {@link TvContentRating}. null, if no such a string exists.
- */
- private static String getUsRating(RatingRegion ratingRegion) {
- if (ratingRegion.getName() != RATING_REGION_US_TV) {
- return null;
- }
- List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
- String rating = null;
- int ratingIndex = VALUE_US_TV_NONE;
- List<String> subratings = new ArrayList<>();
- for (RegionalRating index : regionalRatings) {
- // See Table 3 of ANSI-CEA-766-D
- int dimension = index.getDimension();
- int value = index.getRating();
- switch (dimension) {
- // According to Table 6.27 of ATSC A65,
- // the dimensions shall be in increasing order.
- // Therefore, rating and ratingIndex are assigned before any corresponding
- // subrating.
- case DIMENSION_US_TV_RATING:
- if (value >= VALUE_US_TV_G && value < RATING_REGION_TABLE_US_TV.length) {
- rating = RATING_REGION_TABLE_US_TV[value];
- ratingIndex = value;
- }
- break;
- case DIMENSION_US_TV_D:
- if (value == 1
- && (ratingIndex == VALUE_US_TV_PG || ratingIndex == VALUE_US_TV_14)) {
- // US_TV_D is applicable to US_TV_PG and US_TV_14
- subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
- }
- break;
- case DIMENSION_US_TV_L:
- case DIMENSION_US_TV_S:
- case DIMENSION_US_TV_V:
- if (value == 1
- && ratingIndex >= VALUE_US_TV_PG
- && ratingIndex <= VALUE_US_TV_MA) {
- // US_TV_L, US_TV_S, and US_TV_V are applicable to
- // US_TV_PG, US_TV_14 and US_TV_MA
- subratings.add(RATING_REGION_TABLE_US_TV_SUBRATING[dimension - 1]);
- }
- break;
- case DIMENSION_US_TV_Y:
- if (rating == null) {
- if (value == VALUE_US_TV_Y) {
- rating = STRING_US_TV_Y;
- } else if (value == VALUE_US_TV_Y7) {
- rating = STRING_US_TV_Y7;
- }
- }
- break;
- case DIMENSION_US_TV_FV:
- if (STRING_US_TV_Y7.equals(rating) && value == 1) {
- // US_TV_FV is applicable to US_TV_Y7
- subratings.add(STRING_US_TV_FV);
- }
- break;
- case DIMENSION_US_MV_RATING:
- if (value >= VALUE_US_MV_G && value <= VALUE_US_MV_X) {
- if (value == VALUE_US_MV_X) {
- // US_MV_X was replaced by US_MV_NC17 in 1990,
- // and it's not supported by TvContentRating
- value = VALUE_US_MV_NC17;
- }
- if (rating != null) {
- // According to Table 3 of ANSI-CEA-766-D,
- // DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING shall not be
- // present in the same descriptor.
- Log.w(
- TAG,
- "DIMENSION_US_TV_RATING and DIMENSION_US_MV_RATING are "
- + "present in the same descriptor");
- } else {
- return TvContentRating.createRating(
- RATING_DOMAIN,
- RATING_REGION_RATING_SYSTEM_US_MV,
- RATING_REGION_TABLE_US_MV[value - 2])
- .flattenToString();
- }
- }
- break;
-
- default:
- break;
- }
- }
- if (rating == null) {
- return null;
- }
-
- String[] subratingArray = subratings.toArray(new String[subratings.size()]);
- return TvContentRating.createRating(
- RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_US_TV, rating, subratingArray)
- .flattenToString();
- }
-
- /**
- * Gets KR(South Korea) content rating.
- *
- * @param ratingRegion a {@link RatingRegion} instance which may contain rating information.
- * @return A string representing the KR content rating. The format of the string is defined in
- * {@link TvContentRating}. null, if no such a string exists.
- */
- private static String getKrRating(RatingRegion ratingRegion) {
- if (ratingRegion.getName() != RATING_REGION_KR_TV) {
- return null;
- }
- List<RegionalRating> regionalRatings = ratingRegion.getRegionalRatings();
- String rating = null;
- for (RegionalRating index : regionalRatings) {
- if (index.getDimension() == 0
- && index.getRating() >= 0
- && index.getRating() < RATING_REGION_TABLE_KR_TV.length) {
- rating = RATING_REGION_TABLE_KR_TV[index.getRating()];
- break;
- }
- }
- if (rating == null) {
- return null;
- }
- return TvContentRating.createRating(
- RATING_DOMAIN, RATING_REGION_RATING_SYSTEM_KR_TV, rating)
- .flattenToString();
- }
-
- private static String generateBroadcastGenre(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof GenreDescriptor) {
- GenreDescriptor genreDescriptor =
- (GenreDescriptor) descriptor;
- return TextUtils.join(",", genreDescriptor.getBroadcastGenres());
- }
- }
- return null;
- }
-
- private static String generateCanonicalGenre(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof GenreDescriptor) {
- GenreDescriptor genreDescriptor =
- (GenreDescriptor) descriptor;
- return Genres.encode(genreDescriptor.getCanonicalGenres());
- }
- }
- return null;
- }
-
- private static List<ServiceDescriptor> generateServiceDescriptors(
- List<TsDescriptor> descriptors) {
- List<ServiceDescriptor> serviceDescriptors = new ArrayList<>();
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ServiceDescriptor) {
- ServiceDescriptor serviceDescriptor = (ServiceDescriptor) descriptor;
- serviceDescriptors.add(serviceDescriptor);
- }
- }
- return serviceDescriptors;
- }
-
- private static String generateShortEventName(List<TsDescriptor> descriptors) {
- for (TsDescriptor descriptor : descriptors) {
- if (descriptor instanceof ShortEventDescriptor) {
- ShortEventDescriptor shortEventDescriptor = (ShortEventDescriptor) descriptor;
- return shortEventDescriptor.getEventName();
- }
- }
- return "";
- }
-
- private static List<TsDescriptor> parseDescriptors(byte[] data, int offset, int limit) {
- // For details of the structure for descriptors, see ATSC A/65 Section 6.9.
- List<TsDescriptor> descriptors = new ArrayList<>();
- if (data.length < limit) {
- return descriptors;
- }
- int pos = offset;
- while (pos + 1 < limit) {
- int tag = data[pos] & 0xff;
- int length = data[pos + 1] & 0xff;
- if (length <= 0) {
- break;
- }
- if (limit < pos + length + 2) {
- break;
- }
- if (DEBUG) {
- Log.d(TAG, String.format("Descriptor tag: %02x", tag));
- }
- TsDescriptor descriptor = null;
- switch (tag) {
- case DESCRIPTOR_TAG_CONTENT_ADVISORY:
- descriptor = parseContentAdvisory(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_CAPTION_SERVICE:
- descriptor = parseCaptionService(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME:
- descriptor = parseLongChannelName(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_GENRE:
- descriptor = parseGenre(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_AC3_AUDIO_STREAM:
- descriptor = parseAc3AudioStream(data, pos, pos + length + 2);
- break;
-
- case DESCRIPTOR_TAG_ISO639LANGUAGE:
- descriptor = parseIso639Language(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_SERVICE:
- descriptor = parseDvbService(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_SHORT_EVENT:
- descriptor = parseDvbShortEvent(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_CONTENT:
- descriptor = parseDvbContent(data, pos, pos + length + 2);
- break;
-
- case DVB_DESCRIPTOR_TAG_PARENTAL_RATING:
- descriptor = parseDvbParentalRating(data, pos, pos + length + 2);
- break;
-
- default:
- }
- if (descriptor != null) {
- if (DEBUG) {
- Log.d(TAG, "Descriptor parsed: " + descriptor);
- }
- descriptors.add(descriptor);
- }
- pos += length + 2;
- }
- return descriptors;
- }
-
- private static Iso639LanguageDescriptor parseIso639Language(byte[] data, int pos, int limit) {
- // For the details of the structure of ISO 639 language descriptor,
- // see ISO13818-1 second edition Section 2.6.18.
- pos += 2;
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- while (pos + 4 <= limit) {
- if (limit <= pos + 3) {
- Log.e(TAG, "Broken Iso639Language.");
- return null;
- }
- String language = new String(data, pos, 3);
- int audioType = data[pos + 3] & 0xff;
- AtscAudioTrack audioTrack = new AtscAudioTrack();
- audioTrack.language = language;
- audioTrack.audioType = audioType;
- audioTracks.add(audioTrack);
- pos += 4;
- }
- return new Iso639LanguageDescriptor(audioTracks);
- }
-
- private static CaptionServiceDescriptor parseCaptionService(byte[] data, int pos, int limit) {
- // For the details of the structure of caption service descriptor,
- // see ATSC A/65 Section 6.9.2.
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken CaptionServiceDescriptor.");
- return null;
- }
- List<AtscCaptionTrack> services = new ArrayList<>();
- pos += 2;
- int numberServices = data[pos] & 0x1f;
- ++pos;
- if (limit < pos + numberServices * 6) {
- Log.e(TAG, "Broken CaptionServiceDescriptor.");
- return null;
- }
- for (int i = 0; i < numberServices; ++i) {
- String language = new String(Arrays.copyOfRange(data, pos, pos + 3));
- pos += 3;
- boolean ccType = (data[pos] & 0x80) != 0;
- if (!ccType) {
- pos +=3;
- continue;
- }
- int captionServiceNumber = data[pos] & 0x3f;
- ++pos;
- boolean easyReader = (data[pos] & 0x80) != 0;
- boolean wideAspectRatio = (data[pos] & 0x40) != 0;
- byte[] reserved = new byte[2];
- reserved[0] = (byte) (data[pos] << 2);
- reserved[0] |= (byte) ((data[pos + 1] & 0xc0) >>> 6);
- reserved[1] = (byte) ((data[pos + 1] & 0x3f) << 2);
- pos += 2;
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.language = language;
- captionTrack.serviceNumber = captionServiceNumber;
- captionTrack.easyReader = easyReader;
- captionTrack.wideAspectRatio = wideAspectRatio;
- services.add(captionTrack);
- }
- return new CaptionServiceDescriptor(services);
- }
-
- private static ContentAdvisoryDescriptor parseContentAdvisory(byte[] data, int pos, int limit) {
- // For details of the structure for content advisory descriptor, see A/65 Table 6.27.
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int count = data[pos + 2] & 0x3f;
- pos += 3;
- List<RatingRegion> ratingRegions = new ArrayList<>();
- for (int i = 0; i < count; ++i) {
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- List<RegionalRating> indices = new ArrayList<>();
- int ratingRegion = data[pos] & 0xff;
- int dimensionCount = data[pos + 1] & 0xff;
- pos += 2;
- int previousDimension = -1;
- for (int j = 0; j < dimensionCount; ++j) {
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int dimensionIndex = data[pos] & 0xff;
- int ratingValue = data[pos + 1] & 0x0f;
- if (dimensionIndex <= previousDimension) {
- // According to Table 6.27 of ATSC A65,
- // the indices shall be in increasing order.
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- previousDimension = dimensionIndex;
- pos += 2;
- indices.add(new RegionalRating(dimensionIndex, ratingValue));
- }
- if (limit <= pos) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- int ratingDescriptionLength = data[pos] & 0xff;
- ++pos;
- if (limit < pos + ratingDescriptionLength) {
- Log.e(TAG, "Broken ContentAdvisory");
- return null;
- }
- String ratingDescription = extractText(data, pos);
- pos += ratingDescriptionLength;
- ratingRegions.add(new RatingRegion(ratingRegion, ratingDescription, indices));
- }
- return new ContentAdvisoryDescriptor(ratingRegions);
- }
-
- private static ExtendedChannelNameDescriptor parseLongChannelName(byte[] data, int pos,
- int limit) {
- if (limit <= pos + 2) {
- Log.e(TAG, "Broken ExtendedChannelName.");
- return null;
- }
- pos += 2;
- String text = extractText(data, pos);
- if (text == null) {
- Log.e(TAG, "Broken ExtendedChannelName.");
- return null;
- }
- return new ExtendedChannelNameDescriptor(text);
- }
-
- private static GenreDescriptor parseGenre(byte[] data, int pos, int limit) {
- pos += 2;
- int attributeCount = data[pos] & 0x1f;
- if (limit <= pos + attributeCount) {
- Log.e(TAG, "Broken Genre.");
- return null;
- }
- HashSet<String> broadcastGenreSet = new HashSet<>();
- HashSet<String> canonicalGenreSet = new HashSet<>();
- for (int i = 0; i < attributeCount; ++i) {
- ++pos;
- int genreCode = data[pos] & 0xff;
- if (genreCode < BROADCAST_GENRES_TABLE.length) {
- String broadcastGenre = BROADCAST_GENRES_TABLE[genreCode];
- if (broadcastGenre != null && !broadcastGenreSet.contains(broadcastGenre)) {
- broadcastGenreSet.add(broadcastGenre);
- }
- }
- if (genreCode < CANONICAL_GENRES_TABLE.length) {
- String canonicalGenre = CANONICAL_GENRES_TABLE[genreCode];
- if (canonicalGenre != null && !canonicalGenreSet.contains(canonicalGenre)) {
- canonicalGenreSet.add(canonicalGenre);
- }
- }
- }
- return new GenreDescriptor(broadcastGenreSet.toArray(new String[broadcastGenreSet.size()]),
- canonicalGenreSet.toArray(new String[canonicalGenreSet.size()]));
- }
-
- private static TsDescriptor parseAc3AudioStream(byte[] data, int pos, int limit) {
- // For details of the AC3 audio stream descriptor, see A/52 Table A4.1.
- if (limit <= pos + 5) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- pos += 2;
- byte sampleRateCode = (byte) ((data[pos] & 0xe0) >> 5);
- byte bsid = (byte) (data[pos] & 0x1f);
- ++pos;
- byte bitRateCode = (byte) ((data[pos] & 0xfc) >> 2);
- byte surroundMode = (byte) (data[pos] & 0x03);
- ++pos;
- byte bsmod = (byte) ((data[pos] & 0xe0) >> 5);
- int numChannels = (data[pos] & 0x1e) >> 1;
- boolean fullSvc = (data[pos] & 0x01) != 0;
- ++pos;
- byte langCod = data[pos];
- byte langCod2 = 0;
- if (numChannels == 0) {
- if (limit <= pos) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- ++pos;
- langCod2 = data[pos];
- }
- if (limit <= pos + 1) {
- Log.e(TAG, "Broken AC3 audio stream descriptor.");
- return null;
- }
- byte mainId = 0;
- byte priority = 0;
- byte asvcflags = 0;
- ++pos;
- if (bsmod < 2) {
- mainId = (byte) ((data[pos] & 0xe0) >> 5);
- priority = (byte) ((data[pos] & 0x18) >> 3);
- if ((data[pos] & 0x07) != 0x07) {
- Log.e(TAG, "Broken AC3 audio stream descriptor reserved failed");
- return null;
- }
- } else {
- asvcflags = data[pos];
- }
-
- // See A/52B Table A3.6 num_channels.
- int numEncodedChannels;
- switch (numChannels) {
- case 1:
- case 8:
- numEncodedChannels = 1;
- break;
- case 2:
- case 9:
- numEncodedChannels = 2;
- break;
- case 3:
- case 4:
- case 10:
- numEncodedChannels = 3;
- break;
- case 5:
- case 6:
- case 11:
- numEncodedChannels = 4;
- break;
- case 7:
- case 12:
- numEncodedChannels = 5;
- break;
- case 13:
- numEncodedChannels = 6;
- break;
- default:
- numEncodedChannels = 0;
- break;
- }
-
- if (limit <= pos + 1) {
- Log.w(TAG, "Missing text and language fields on AC3 audio stream descriptor.");
- return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
- numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags,
- null, null, null);
- }
- ++pos;
- int textLen = (data[pos] & 0xfe) >> 1;
- boolean textCode = (data[pos] & 0x01) != 0;
- ++pos;
- String text = "";
- if (textLen > 0) {
- if (limit < pos + textLen) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- if (textCode) {
- text = new String(data, pos, textLen);
- } else {
- text = new String(data, pos, textLen, Charset.forName("UTF-16"));
- }
- pos += textLen;
- }
- String language = null;
- String language2 = null;
- if (pos < limit) {
- // Many AC3 audio stream descriptors skip the language fields.
- boolean languageFlag1 = (data[pos] & 0x80) != 0;
- boolean languageFlag2 = (data[pos] & 0x40) != 0;
- if ((data[pos] & 0x3f) != 0x3f) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- if (pos + (languageFlag1 ? 3 : 0) + (languageFlag2 ? 3 : 0) > limit) {
- Log.e(TAG, "Broken AC3 audio stream descriptor");
- return null;
- }
- ++pos;
- if (languageFlag1) {
- language = new String(data, pos, 3);
- pos += 3;
- }
- if (languageFlag2) {
- language2 = new String(data, pos, 3);
- }
- }
-
- return new Ac3AudioDescriptor(sampleRateCode, bsid, bitRateCode, surroundMode, bsmod,
- numEncodedChannels, fullSvc, langCod, langCod2, mainId, priority, asvcflags, text,
- language, language2);
- }
-
- private static TsDescriptor parseDvbService(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 86.
- if (limit < pos + 5) {
- Log.e(TAG, "Broken service descriptor.");
- return null;
- }
- pos += 2;
- int serviceType = data[pos] & 0xff;
- pos++;
- int serviceProviderNameLength = data[pos] & 0xff;
- pos++;
- String serviceProviderName = extractTextFromDvb(data, pos, serviceProviderNameLength);
- pos += serviceProviderNameLength;
- int serviceNameLength = data[pos] & 0xff;
- pos++;
- String serviceName = extractTextFromDvb(data, pos, serviceNameLength);
- return new ServiceDescriptor(serviceType, serviceProviderName, serviceName);
- }
-
- private static TsDescriptor parseDvbShortEvent(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 91.
- if (limit < pos + 7) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- pos += 2;
- String language = new String(data, pos, 3);
- int eventNameLength = data[pos + 3] & 0xff;
- pos += 4;
- if (pos + eventNameLength > limit) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- String eventName = new String(data, pos, eventNameLength);
- pos += eventNameLength;
- int textLength = data[pos] & 0xff;
- if (pos + textLength > limit) {
- Log.e(TAG, "Broken short event descriptor.");
- return null;
- }
- pos++;
- String text = new String(data, pos, textLength);
- return new ShortEventDescriptor(language, eventName, text);
- }
-
- private static TsDescriptor parseDvbContent(byte[] data, int pos, int limit) {
- // TODO: According to DVB Document A038 Table 27 to add a parser for content descriptor to
- // get content genre.
- return null;
- }
-
- private static TsDescriptor parseDvbParentalRating(byte[] data, int pos, int limit) {
- // For details of DVB service descriptors, see DVB Document A038 Table 81.
- HashMap<String, Integer> ratings = new HashMap<>();
- pos += 2;
- while (pos + 4 <= limit) {
- String countryCode = new String(data, pos, 3);
- int rating = data[pos + 3] & 0xff;
- pos += 4;
- if (rating > 15) {
- // Rating > 15 means that the ratings is defined by broadcaster.
- continue;
- }
- ratings.put(countryCode, rating + 3);
- }
- return new ParentalRatingDescriptor(ratings);
- }
-
- private static int getShortNameSize(byte[] data, int offset) {
- for (int i = 0; i < MAX_SHORT_NAME_BYTES; i += 2) {
- if (data[offset + i] == 0 && data[offset + i + 1] == 0) {
- return i;
- }
- }
- return MAX_SHORT_NAME_BYTES;
- }
-
- private static String extractText(byte[] data, int pos) {
- if (data.length < pos) {
- return null;
- }
- int numStrings = data[pos] & 0xff;
- pos++;
- for (int i = 0; i < numStrings; ++i) {
- if (data.length <= pos + 3) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- int numSegments = data[pos + 3] & 0xff;
- pos += 4;
- for (int j = 0; j < numSegments; ++j) {
- if (data.length <= pos + 2) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- int compressionType = data[pos] & 0xff;
- int mode = data[pos + 1] & 0xff;
- int numBytes = data[pos + 2] & 0xff;
- if (data.length < pos + 3 + numBytes) {
- Log.e(TAG, "Broken text.");
- return null;
- }
- byte[] bytes = Arrays.copyOfRange(data, pos + 3, pos + 3 + numBytes);
- if (compressionType == COMPRESSION_TYPE_NO_COMPRESSION) {
- try {
- switch (mode) {
- case MODE_SELECTED_UNICODE_RANGE_1:
- return new String(bytes, "ISO-8859-1");
- case MODE_SCSU:
- return UnicodeDecompressor.decompress(bytes);
- case MODE_UTF16:
- return new String(bytes, "UTF-16");
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Unsupported text format.", e);
- }
- }
- pos += 3 + numBytes;
- }
- }
- return null;
- }
-
- private static String extractTextFromDvb(byte[] data, int pos, int length) {
- // For details of DVB character set selection, see DVB Document A038 Annex A.
- if (data.length < pos + length) {
- return null;
- }
- try {
- String charsetPrefix = "ISO-8859-";
- switch (data[0]) {
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- case 0x09:
- case 0x0A:
- case 0x0B:
- String charset = charsetPrefix + String.valueOf(data[0] & 0xff + 4);
- return new String(data, pos, length, charset);
- case 0x10:
- if (length < 3) {
- Log.e(TAG, "Broken DVB text");
- return null;
- }
- int codeTable = data[pos + 2] & 0xff;
- if (data[pos + 1] == 0 && codeTable > 0 && codeTable < 15) {
- return new String(
- data, pos, length, charsetPrefix + String.valueOf(codeTable));
- } else {
- return new String(data, pos, length, "ISO-8859-1");
- }
- case 0x11:
- case 0x14:
- case 0x15:
- return new String(data, pos, length, "UTF-16BE");
- case 0x12:
- return new String(data, pos, length, "EUC-KR");
- case 0x13:
- return new String(data, pos, length, "GB2312");
- default:
- return new String(data, pos, length, "ISO-8859-1");
- }
- } catch (UnsupportedEncodingException e) {
- Log.e(TAG, "Unsupported text format.", e);
- }
- return new String(data, pos, length);
- }
-
- private static boolean checkSanity(byte[] data) {
- if (data.length <= 1) {
- return false;
- }
- boolean hasCRC = (data[1] & 0x80) != 0; // section_syntax_indicator
- if (hasCRC) {
- int crc = 0xffffffff;
- for(byte b : data) {
- int index = ((crc >> 24) ^ (b & 0xff)) & 0xff;
- crc = CRC_TABLE[index] ^ (crc << 8);
- }
- if(crc != 0){
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java
deleted file mode 100644
index 7cdb534e..00000000
--- a/src/com/android/tv/tuner/ts/TsParser.java
+++ /dev/null
@@ -1,520 +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.ts;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.EttItem;
-import com.android.tv.tuner.data.PsipData.MgtItem;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.ts.SectionParser.OutputListener;
-import com.android.tv.tuner.util.ByteArrayBuffer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeSet;
-
-/**
- * Parses MPEG-2 TS packets.
- */
-public class TsParser {
- private static final String TAG = "TsParser";
- private static final boolean DEBUG = false;
-
- public static final int ATSC_SI_BASE_PID = 0x1ffb;
- public static final int PAT_PID = 0x0000;
- public static final int DVB_SDT_PID = 0x0011;
- public static final int DVB_EIT_PID = 0x0012;
- private static final int TS_PACKET_START_CODE = 0x47;
- private static final int TS_PACKET_TEI_MASK = 0x80;
- private static final int TS_PACKET_SIZE = 188;
-
- /*
- * Using a SparseArray removes the need to auto box the int key for mStreamMap
- * in feedTdPacket which is called 100 times a second. This greatly reduces the
- * number of objects created and the frequency of garbage collection.
- * Other maps might be suitable for a SparseArray, but the performance
- * trade offs must be considered carefully.
- * mStreamMap is the only one called at such a high rate.
- */
- private final SparseArray<Stream> mStreamMap = new SparseArray<>();
- private final Map<Integer, VctItem> mSourceIdToVctItemMap = new HashMap<>();
- private final Map<Integer, String> mSourceIdToVctItemDescriptionMap = new HashMap<>();
- private final Map<Integer, VctItem> mProgramNumberToVctItemMap = new HashMap<>();
- private final Map<Integer, List<PmtItem>> mProgramNumberToPMTMap = new HashMap<>();
- private final Map<Integer, List<EitItem>> mSourceIdToEitMap = new HashMap<>();
- private final Map<Integer, SdtItem> mProgramNumberToSdtItemMap = new HashMap<>();
- private final Map<EventSourceEntry, List<EitItem>> mEitMap = new HashMap<>();
- private final Map<EventSourceEntry, List<EttItem>> mETTMap = new HashMap<>();
- private final TreeSet<Integer> mEITPids = new TreeSet<>();
- private final TreeSet<Integer> mETTPids = new TreeSet<>();
- private final SparseBooleanArray mProgramNumberHandledStatus = new SparseBooleanArray();
- private final SparseBooleanArray mVctItemHandledStatus = new SparseBooleanArray();
- private final TsOutputListener mListener;
- private final boolean mIsDvbSignal;
-
- private int mVctItemCount;
- private int mHandledVctItemCount;
- private int mVctSectionParsedCount;
- private boolean[] mVctSectionParsed;
-
- public interface TsOutputListener {
- void onPatDetected(List<PatItem> items);
- void onEitPidDetected(int pid);
- void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems);
- void onEitItemParsed(VctItem channel, List<EitItem> items);
- void onEttPidDetected(int pid);
- void onAllVctItemsParsed();
- void onSdtItemParsed(SdtItem channel, List<PmtItem> pmtItems);
- }
-
- private abstract class Stream {
- private static final int INVALID_CONTINUITY_COUNTER = -1;
- private static final int NUM_CONTINUITY_COUNTER = 16;
-
- protected int mContinuityCounter = INVALID_CONTINUITY_COUNTER;
- protected final ByteArrayBuffer mPacket = new ByteArrayBuffer(TS_PACKET_SIZE);
-
- public void feedData(byte[] data, int continuityCounter, boolean startIndicator) {
- if ((mContinuityCounter + 1) % NUM_CONTINUITY_COUNTER != continuityCounter) {
- mPacket.setLength(0);
- }
- mContinuityCounter = continuityCounter;
- handleData(data, startIndicator);
- }
-
- protected abstract void handleData(byte[] data, boolean startIndicator);
- protected abstract void resetDataVersions();
- }
-
- private class SectionStream extends Stream {
- private final SectionParser mSectionParser;
- private final int mPid;
-
- public SectionStream(int pid) {
- mPid = pid;
- mSectionParser = new SectionParser(mSectionListener);
- }
-
- @Override
- protected void handleData(byte[] data, boolean startIndicator) {
- int startPos = 0;
- if (mPacket.length() == 0) {
- if (startIndicator) {
- startPos = (data[0] & 0xff) + 1;
- } else {
- // Don't know where the section starts yet. Wait until start indicator is on.
- return;
- }
- } else {
- if (startIndicator) {
- startPos = 1;
- }
- }
-
- // When a broken packet is encountered, parsing will stop and return right away.
- if (startPos >= data.length) {
- mPacket.setLength(0);
- return;
- }
- mPacket.append(data, startPos, data.length - startPos);
- mSectionParser.parseSections(mPacket);
- }
-
- @Override
- protected void resetDataVersions() {
- mSectionParser.resetVersionNumbers();
- }
-
- private final OutputListener mSectionListener = new OutputListener() {
- @Override
- public void onPatParsed(List<PatItem> items) {
- for (PatItem i : items) {
- startListening(i.getPmtPid());
- }
- if (mListener != null) {
- mListener.onPatDetected(items);
- }
- }
-
- @Override
- public void onPmtParsed(int programNumber, List<PmtItem> items) {
- mProgramNumberToPMTMap.put(programNumber, items);
- if (DEBUG) {
- Log.d(TAG, "onPMTParsed, programNo " + programNumber + " handledStatus is "
- + mProgramNumberHandledStatus.get(programNumber, false));
- }
- int statusIndex = mProgramNumberHandledStatus.indexOfKey(programNumber);
- if (statusIndex < 0) {
- mProgramNumberHandledStatus.put(programNumber, false);
- }
- if (!mProgramNumberHandledStatus.get(programNumber)) {
- VctItem vctItem = mProgramNumberToVctItemMap.get(programNumber);
- if (vctItem != null) {
- // When PMT is parsed later than VCT.
- mProgramNumberHandledStatus.put(programNumber, true);
- handleVctItem(vctItem, items);
- mHandledVctItemCount++;
- if (mHandledVctItemCount >= mVctItemCount
- && mVctSectionParsedCount >= mVctSectionParsed.length
- && mListener != null) {
- mListener.onAllVctItemsParsed();
- }
- }
- SdtItem sdtItem = mProgramNumberToSdtItemMap.get(programNumber);
- if (sdtItem != null) {
- // When PMT is parsed later than SDT.
- mProgramNumberHandledStatus.put(programNumber, true);
- handleSdtItem(sdtItem, items);
- }
- }
- }
-
- @Override
- public void onMgtParsed(List<MgtItem> items) {
- for (MgtItem i : items) {
- if (mStreamMap.get(i.getTableTypePid()) != null) {
- continue;
- }
- if (i.getTableType() >= MgtItem.TABLE_TYPE_EIT_RANGE_START
- && i.getTableType() <= MgtItem.TABLE_TYPE_EIT_RANGE_END) {
- startListening(i.getTableTypePid());
- mEITPids.add(i.getTableTypePid());
- if (mListener != null) {
- mListener.onEitPidDetected(i.getTableTypePid());
- }
- } else if (i.getTableType() == MgtItem.TABLE_TYPE_CHANNEL_ETT ||
- (i.getTableType() >= MgtItem.TABLE_TYPE_ETT_RANGE_START
- && i.getTableType() <= MgtItem.TABLE_TYPE_ETT_RANGE_END)) {
- startListening(i.getTableTypePid());
- mETTPids.add(i.getTableTypePid());
- if (mListener != null) {
- mListener.onEttPidDetected(i.getTableTypePid());
- }
- }
- }
- }
-
- @Override
- public void onVctParsed(List<VctItem> items, int sectionNumber, int lastSectionNumber) {
- if (mVctSectionParsed == null) {
- mVctSectionParsed = new boolean[lastSectionNumber + 1];
- } else if (mVctSectionParsed[sectionNumber]) {
- // The current section was handled before.
- if (DEBUG) {
- Log.d(TAG, "Duplicate VCT section found.");
- }
- return;
- }
- mVctSectionParsed[sectionNumber] = true;
- mVctSectionParsedCount++;
- mVctItemCount += items.size();
- for (VctItem i : items) {
- if (DEBUG) Log.d(TAG, "onVCTParsed " + i);
- if (i.getSourceId() != 0) {
- mSourceIdToVctItemMap.put(i.getSourceId(), i);
- i.setDescription(mSourceIdToVctItemDescriptionMap.get(i.getSourceId()));
- }
- int programNumber = i.getProgramNumber();
- mProgramNumberToVctItemMap.put(programNumber, i);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- mProgramNumberHandledStatus.put(programNumber, true);
- handleVctItem(i, pmtList);
- mHandledVctItemCount++;
- if (mHandledVctItemCount >= mVctItemCount
- && mVctSectionParsedCount >= mVctSectionParsed.length
- && mListener != null) {
- mListener.onAllVctItemsParsed();
- }
- } else {
- mProgramNumberHandledStatus.put(programNumber, false);
- Log.i(TAG, "onVCTParsed, but PMT for programNo " + programNumber
- + " is not found yet.");
- }
- }
- }
-
- @Override
- public void onEitParsed(int sourceId, List<EitItem> items) {
- if (DEBUG) Log.d(TAG, "onEITParsed " + sourceId);
- EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
- mEitMap.put(entry, items);
- handleEvents(sourceId);
- }
-
- @Override
- public void onEttParsed(int sourceId, List<EttItem> descriptions) {
- if (DEBUG) {
- Log.d(TAG, String.format("onETTParsed sourceId: %d, descriptions.size(): %d",
- sourceId, descriptions.size()));
- }
- for (EttItem item : descriptions) {
- if (item.eventId == 0) {
- // Channel description
- mSourceIdToVctItemDescriptionMap.put(sourceId, item.text);
- VctItem vctItem = mSourceIdToVctItemMap.get(sourceId);
- if (vctItem != null) {
- vctItem.setDescription(item.text);
- List<PmtItem> pmtItems =
- mProgramNumberToPMTMap.get(vctItem.getProgramNumber());
- if (pmtItems != null) {
- handleVctItem(vctItem, pmtItems);
- }
- }
- }
- }
-
- // Event Information description
- EventSourceEntry entry = new EventSourceEntry(mPid, sourceId);
- mETTMap.put(entry, descriptions);
- handleEvents(sourceId);
- }
-
- @Override
- public void onSdtParsed(List<SdtItem> sdtItems) {
- for (SdtItem sdtItem : sdtItems) {
- if (DEBUG) Log.d(TAG, "onSdtParsed " + sdtItem);
- int programNumber = sdtItem.getServiceId();
- mProgramNumberToSdtItemMap.put(programNumber, sdtItem);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- mProgramNumberHandledStatus.put(programNumber, true);
- handleSdtItem(sdtItem, pmtList);
- } else {
- mProgramNumberHandledStatus.put(programNumber, false);
- Log.i(TAG, "onSdtParsed, but PMT for programNo " + programNumber
- + " is not found yet.");
- }
- }
- }
- };
- }
-
- private static class EventSourceEntry {
- public final int pid;
- public final int sourceId;
-
- public EventSourceEntry(int pid, int sourceId) {
- this.pid = pid;
- this.sourceId = sourceId;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + pid;
- result = 31 * result + sourceId;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof EventSourceEntry) {
- EventSourceEntry another = (EventSourceEntry) obj;
- return pid == another.pid && sourceId == another.sourceId;
- }
- return false;
- }
- }
-
- private void handleVctItem(VctItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "handleVctItem " + channel);
- }
- if (mListener != null) {
- mListener.onVctItemParsed(channel, pmtItems);
- }
- int sourceId = channel.getSourceId();
- int statusIndex = mVctItemHandledStatus.indexOfKey(sourceId);
- if (statusIndex < 0) {
- mVctItemHandledStatus.put(sourceId, false);
- return;
- }
- if (!mVctItemHandledStatus.valueAt(statusIndex)) {
- List<EitItem> eitItems = mSourceIdToEitMap.get(sourceId);
- if (eitItems != null) {
- // When VCT is parsed later than EIT.
- mVctItemHandledStatus.put(sourceId, true);
- handleEitItems(channel, eitItems);
- }
- }
- }
-
- private void handleEitItems(VctItem channel, List<EitItem> items) {
- if (mListener != null) {
- mListener.onEitItemParsed(channel, items);
- }
- }
-
- private void handleSdtItem(SdtItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "handleSdtItem " + channel);
- }
- if (mListener != null) {
- mListener.onSdtItemParsed(channel, pmtItems);
- }
- }
-
- private void handleEvents(int sourceId) {
- Map<Integer, EitItem> itemSet = new HashMap<>();
- for (int pid : mEITPids) {
- List<EitItem> eitItems = mEitMap.get(new EventSourceEntry(pid, sourceId));
- if (eitItems != null) {
- for (EitItem item : eitItems) {
- item.setDescription(null);
- itemSet.put(item.getEventId(), item);
- }
- }
- }
- for (int pid : mETTPids) {
- List<EttItem> ettItems = mETTMap.get(new EventSourceEntry(pid, sourceId));
- if (ettItems != null) {
- for (EttItem ettItem : ettItems) {
- if (ettItem.eventId != 0) {
- EitItem item = itemSet.get(ettItem.eventId);
- if (item != null) {
- item.setDescription(ettItem.text);
- }
- }
- }
- }
- }
- List<EitItem> items = new ArrayList<>(itemSet.values());
- mSourceIdToEitMap.put(sourceId, items);
- VctItem channel = mSourceIdToVctItemMap.get(sourceId);
- if (channel != null && mProgramNumberHandledStatus.get(channel.getProgramNumber())) {
- mVctItemHandledStatus.put(sourceId, true);
- handleEitItems(channel, items);
- } else {
- mVctItemHandledStatus.put(sourceId, false);
- if (!mIsDvbSignal) {
- // Log only when zapping to non-DVB channels, since there is not VCT in DVB signal.
- Log.i(TAG, "onEITParsed, but VCT for sourceId " + sourceId + " is not found yet.");
- }
- }
- }
-
- /**
- * Creates MPEG-2 TS parser.
- *
- * @param listener TsOutputListener
- */
- public TsParser(TsOutputListener listener, boolean isDvbSignal) {
- startListening(PAT_PID);
- startListening(ATSC_SI_BASE_PID);
- mIsDvbSignal = isDvbSignal;
- if (isDvbSignal) {
- startListening(DVB_EIT_PID);
- startListening(DVB_SDT_PID);
- }
- mListener = listener;
- }
-
- private void startListening(int pid) {
- mStreamMap.put(pid, new SectionStream(pid));
- }
-
- private boolean feedTSPacket(byte[] tsData, int pos) {
- if (tsData.length < pos + TS_PACKET_SIZE) {
- if (DEBUG) Log.d(TAG, "Data should include a single TS packet.");
- return false;
- }
- if (tsData[pos] != TS_PACKET_START_CODE) {
- if (DEBUG) Log.d(TAG, "Invalid ts packet.");
- return false;
- }
- if ((tsData[pos + 1] & TS_PACKET_TEI_MASK) != 0) {
- if (DEBUG) Log.d(TAG, "Erroneous ts packet.");
- return false;
- }
-
- // For details for the structure of TS packet, see H.222.0 Table 2-2.
- int pid = ((tsData[pos + 1] & 0x1f) << 8) | (tsData[pos + 2] & 0xff);
- boolean hasAdaptation = (tsData[pos + 3] & 0x20) != 0;
- boolean hasPayload = (tsData[pos + 3] & 0x10) != 0;
- boolean payloadStartIndicator = (tsData[pos + 1] & 0x40) != 0;
- int continuityCounter = tsData[pos + 3] & 0x0f;
- Stream stream = mStreamMap.get(pid);
- int payloadPos = pos;
- payloadPos += hasAdaptation ? 5 + (tsData[pos + 4] & 0xff) : 4;
- if (!hasPayload || stream == null) {
- // We are not interested in this packet.
- return false;
- }
- if (payloadPos >= pos + TS_PACKET_SIZE) {
- if (DEBUG) Log.d(TAG, "Payload should be included in a single TS packet.");
- return false;
- }
- stream.feedData(Arrays.copyOfRange(tsData, payloadPos, pos + TS_PACKET_SIZE),
- continuityCounter, payloadStartIndicator);
- return true;
- }
-
- /**
- * Feeds MPEG-2 TS data to parse.
- * @param tsData buffer for ATSC TS stream
- * @param pos the offset where buffer starts
- * @param length The length of available data
- */
- public void feedTSData(byte[] tsData, int pos, int length) {
- for (; pos <= length - TS_PACKET_SIZE; pos += TS_PACKET_SIZE) {
- feedTSPacket(tsData, pos);
- }
- }
-
- /**
- * Retrieves the channel information regardless of being well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- List<TunerChannel> incompleteChannels = new ArrayList<>();
- for (int i = 0; i < mProgramNumberHandledStatus.size(); i++) {
- if (!mProgramNumberHandledStatus.valueAt(i)) {
- int programNumber = mProgramNumberHandledStatus.keyAt(i);
- List<PmtItem> pmtList = mProgramNumberToPMTMap.get(programNumber);
- if (pmtList != null) {
- TunerChannel tunerChannel = new TunerChannel(programNumber, pmtList);
- incompleteChannels.add(tunerChannel);
- }
- }
- }
- return incompleteChannels;
- }
-
- /**
- * Reset the versions so that data with old version number can be handled.
- */
- public void resetDataVersions() {
- for (int eitPid : mEITPids) {
- Stream stream = mStreamMap.get(eitPid);
- if (stream != null) {
- stream.resetDataVersions();
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
deleted file mode 100644
index d2b4998a..00000000
--- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java
+++ /dev/null
@@ -1,734 +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.tvinput;
-
-import android.content.ComponentName;
-import android.content.ContentProviderOperation;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.RemoteException;
-import android.support.annotation.Nullable;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.util.ConvertUtils;
-import com.android.tv.util.PermissionUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Manages the channel info and EPG data through {@link TvInputManager}.
- */
-public class ChannelDataManager implements Handler.Callback {
- private static final String TAG = "ChannelDataManager";
-
- private static final String[] ALL_PROGRAMS_SELECTION_ARGS = new String[] {
- TvContract.Programs._ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_BROADCAST_GENRE,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_VERSION_NUMBER };
- private static final String[] CHANNEL_DATA_SELECTION_ARGS = new String[] {
- TvContract.Channels._ID,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA,
- TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1};
-
- private static final int MSG_HANDLE_EVENTS = 1;
- private static final int MSG_HANDLE_CHANNEL = 2;
- private static final int MSG_BUILD_CHANNEL_MAP = 3;
- private static final int MSG_REQUEST_PROGRAMS = 4;
- private static final int MSG_CLEAR_CHANNELS = 6;
- private static final int MSG_CHECK_VERSION = 7;
-
- // Throttle the batch operations to avoid TransactionTooLargeException.
- private static final int BATCH_OPERATION_COUNT = 100;
- // At most 16 days of program information is delivered through an EIT,
- // according to the Chapter 6.4 of ATSC Recommended Practice A/69.
- private static final long PROGRAM_QUERY_DURATION = TimeUnit.DAYS.toMillis(16);
-
- /**
- * A version number to enforce consistency of the channel data.
- *
- * WARNING: If a change in the database serialization lead to breaking the backward
- * compatibility, you must increment this value so that the old data are purged,
- * and the user is requested to perform the auto-scan again to generate the new data set.
- */
- private static final int VERSION = 6;
-
- private final Context mContext;
- private final String mInputId;
- private ProgramInfoListener mListener;
- private ChannelScanListener mChannelScanListener;
- private Handler mChannelScanHandler;
- private final HandlerThread mHandlerThread;
- private final Handler mHandler;
- private final ConcurrentHashMap<Long, TunerChannel> mTunerChannelMap;
- private final ConcurrentSkipListMap<TunerChannel, Long> mTunerChannelIdMap;
- private final Uri mChannelsUri;
-
- // Used for scanning
- private final ConcurrentSkipListSet<TunerChannel> mScannedChannels;
- private final ConcurrentSkipListSet<TunerChannel> mPreviousScannedChannels;
- private final AtomicBoolean mIsScanning;
- private final AtomicBoolean scanCompleted = new AtomicBoolean();
-
- public interface ProgramInfoListener {
-
- /**
- * Invoked when a request for getting programs of a channel has been processed and passes
- * the requested channel and the programs retrieved from database to the listener.
- */
- void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs);
-
- /**
- * Invoked when programs of a channel have been arrived and passes the arrived channel and
- * programs to the listener.
- */
- void onProgramsArrived(TunerChannel channel, List<EitItem> programs);
-
- /**
- * Invoked when a channel has been arrived and passes the arrived channel to the listener.
- */
- void onChannelArrived(TunerChannel channel);
-
- /**
- * Invoked when the database schema has been changed and the old-format channels have been
- * deleted. A receiver should notify to a user that re-scanning channels is necessary.
- */
- void onRescanNeeded();
- }
-
- public interface ChannelScanListener {
- /**
- * Invoked when all pending channels have been handled.
- */
- void onChannelHandlingDone();
- }
-
- public ChannelDataManager(Context context) {
- mContext = context;
- mInputId = TvContract.buildInputId(new ComponentName(mContext.getPackageName(),
- TunerTvInputService.class.getName()));
- mChannelsUri = TvContract.buildChannelsUriForInput(mInputId);
- mTunerChannelMap = new ConcurrentHashMap<>();
- mTunerChannelIdMap = new ConcurrentSkipListMap<>();
- mHandlerThread = new HandlerThread("TvInputServiceBackgroundThread");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper(), this);
- mIsScanning = new AtomicBoolean();
- mScannedChannels = new ConcurrentSkipListSet<>();
- mPreviousScannedChannels = new ConcurrentSkipListSet<>();
- }
-
- // Public methods
- public void checkDataVersion(Context context) {
- int version = TunerPreferences.getChannelDataVersion(context);
- Log.d(TAG, "ChannelDataManager.VERSION=" + VERSION + " (current=" + version + ")");
- if (version == VERSION) {
- // Everything is awesome. Return and continue.
- return;
- }
- setCurrentVersion(context);
-
- if (version == TunerPreferences.CHANNEL_DATA_VERSION_NOT_SET) {
- mHandler.sendEmptyMessage(MSG_CHECK_VERSION);
- } else {
- // The stored channel data seem outdated. Delete them all.
- mHandler.sendEmptyMessage(MSG_CLEAR_CHANNELS);
- }
- }
-
- public void setCurrentVersion(Context context) {
- TunerPreferences.setChannelDataVersion(context, VERSION);
- }
-
- public void setListener(ProgramInfoListener listener) {
- mListener = listener;
- }
-
- public void setChannelScanListener(ChannelScanListener listener, Handler handler) {
- mChannelScanListener = listener;
- mChannelScanHandler = handler;
- }
-
- public void release() {
- mHandler.removeCallbacksAndMessages(null);
- releaseSafely();
- }
-
- public void releaseSafely() {
- mHandlerThread.quitSafely();
- mListener = null;
- mChannelScanListener = null;
- mChannelScanHandler = null;
- }
-
- public TunerChannel getChannel(long channelId) {
- TunerChannel channel = mTunerChannelMap.get(channelId);
- if (channel != null) {
- return channel;
- }
- mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP);
- byte[] data = null;
- try (Cursor cursor = mContext.getContentResolver().query(TvContract.buildChannelUri(
- channelId), CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- data = cursor.getBlob(1);
- }
- }
- if (data == null) {
- return null;
- }
- channel = TunerChannel.parseFrom(data);
- if (channel == null) {
- return null;
- }
- channel.setChannelId(channelId);
- return channel;
- }
-
- public void requestProgramsData(TunerChannel channel) {
- mHandler.removeMessages(MSG_REQUEST_PROGRAMS);
- mHandler.obtainMessage(MSG_REQUEST_PROGRAMS, channel).sendToTarget();
- }
-
- public void notifyEventDetected(TunerChannel channel, List<EitItem> items) {
- mHandler.obtainMessage(MSG_HANDLE_EVENTS, new ChannelEvent(channel, items)).sendToTarget();
- }
-
- public void notifyChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (mIsScanning.get()) {
- // During scanning, channels should be handle first to improve scan time.
- // EIT items can be handled in background after channel scan.
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel));
- } else {
- mHandler.obtainMessage(MSG_HANDLE_CHANNEL, channel).sendToTarget();
- }
- }
-
- // For scanning process
- /**
- * Invoked when starting a scanning mode. This method gets the previous channels to detect the
- * obsolete channels after scanning and initializes the variables used for scanning.
- */
- public void notifyScanStarted() {
- mScannedChannels.clear();
- mPreviousScannedChannels.clear();
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long channelId = cursor.getLong(0);
- byte[] data = cursor.getBlob(1);
- TunerChannel channel = TunerChannel.parseFrom(data);
- if (channel != null) {
- channel.setChannelId(channelId);
- mPreviousScannedChannels.add(channel);
- }
- } while (cursor.moveToNext());
- }
- }
- mIsScanning.set(true);
- }
-
- /**
- * Invoked when completing the scanning mode. Passes {@code MSG_SCAN_COMPLETED} to the handler
- * in order to wait for finishing the remaining messages in the handler queue. Then removes the
- * obsolete channels, which are previously scanned but are not in the current scanned result.
- */
- public void notifyScanCompleted() {
- // Send a dummy message to check whether there is any MSG_HANDLE_CHANNEL in queue
- // and avoid race conditions.
- scanCompleted.set(true);
- mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, null));
- }
-
- public void scannedChannelHandlingCompleted() {
- mIsScanning.set(false);
- if (!mPreviousScannedChannels.isEmpty()) {
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- for (TunerChannel channel : mPreviousScannedChannels) {
- ops.add(ContentProviderOperation.newDelete(
- TvContract.buildChannelUri(channel.getChannelId())).build());
- }
- try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
- } catch (RemoteException | OperationApplicationException e) {
- Log.e(TAG, "Error deleting obsolete channels", e);
- }
- }
- if (mChannelScanListener != null && mChannelScanHandler != null) {
- mChannelScanHandler.post(new Runnable() {
- @Override
- public void run() {
- mChannelScanListener.onChannelHandlingDone();
- }
- });
- } else {
- Log.e(TAG, "Error. mChannelScanListener is null.");
- }
- }
-
- /**
- * Returns the number of scanned channels in the scanning mode.
- */
- public int getScannedChannelCount() {
- return mScannedChannels.size();
- }
-
- /**
- * Removes all callbacks and messages in handler to avoid previous messages from last channel.
- */
- public void removeAllCallbacksAndMessages() {
- mHandler.removeCallbacksAndMessages(null);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_HANDLE_EVENTS: {
- ChannelEvent event = (ChannelEvent) msg.obj;
- handleEvents(event.channel, event.eitItems);
- return true;
- }
- case MSG_HANDLE_CHANNEL: {
- TunerChannel channel = (TunerChannel) msg.obj;
- if (channel != null) {
- handleChannel(channel);
- }
- if (scanCompleted.get() && mIsScanning.get()
- && !mHandler.hasMessages(MSG_HANDLE_CHANNEL)) {
- // Complete the scan when all found channels have already been handled.
- scannedChannelHandlingCompleted();
- }
- return true;
- }
- case MSG_BUILD_CHANNEL_MAP: {
- mHandler.removeMessages(MSG_BUILD_CHANNEL_MAP);
- buildChannelMap();
- return true;
- }
- case MSG_REQUEST_PROGRAMS: {
- if (mHandler.hasMessages(MSG_REQUEST_PROGRAMS)) {
- return true;
- }
- TunerChannel channel = (TunerChannel) msg.obj;
- if (mListener != null) {
- mListener.onRequestProgramsResponse(channel, getAllProgramsForChannel(channel));
- }
- return true;
- }
- case MSG_CLEAR_CHANNELS: {
- clearChannels();
- return true;
- }
- case MSG_CHECK_VERSION: {
- checkVersion();
- return true;
- }
- }
- return false;
- }
-
- // Private methods
- private void handleEvents(TunerChannel channel, List<EitItem> items) {
- long channelId = getChannelId(channel);
- if (channelId <= 0) {
- return;
- }
- channel.setChannelId(channelId);
-
- // Schedule the audio and caption tracks of the current program and the programs being
- // listed after the current one into TIS.
- if (mListener != null) {
- mListener.onProgramsArrived(channel, items);
- }
-
- long currentTime = System.currentTimeMillis();
- List<EitItem> oldItems = getAllProgramsForChannel(channel, currentTime,
- currentTime + PROGRAM_QUERY_DURATION);
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- // TODO: Find a right way to check if the programs are added outside.
- boolean addedOutside = false;
- for (EitItem item : oldItems) {
- if (item.getEventId() == 0) {
- // The event has been added outside TV tuner.
- addedOutside = true;
- break;
- }
- }
-
- // Inserting programs only when there is no overlapping with existing data assuming that:
- // 1. external EPG is more accurate and rich and
- // 2. the data we add here will be updated when we apply external EPG.
- if (addedOutside) {
- // oldItemCount cannot be 0 if addedOutside is true.
- int oldItemCount = oldItems.size();
- for (EitItem newItem : items) {
- if (newItem.getEndTimeUtcMillis() < currentTime) {
- continue;
- }
- long newItemStartTime = newItem.getStartTimeUtcMillis();
- long newItemEndTime = newItem.getEndTimeUtcMillis();
- if (newItemStartTime < oldItems.get(0).getStartTimeUtcMillis()) {
- // Start time smaller than that of any old items. Insert if no overlap.
- if (newItemEndTime > oldItems.get(0).getStartTimeUtcMillis()) continue;
- } else if (newItemStartTime
- > oldItems.get(oldItemCount - 1).getStartTimeUtcMillis()) {
- // Start time larger than that of any old item. Insert if no overlap.
- if (newItemStartTime
- < oldItems.get(oldItemCount - 1).getEndTimeUtcMillis()) continue;
- } else {
- int pos = Collections.binarySearch(oldItems, newItem,
- new Comparator<EitItem>() {
- @Override
- public int compare(EitItem lhs, EitItem rhs) {
- return Long.compare(lhs.getStartTimeUtcMillis(),
- rhs.getStartTimeUtcMillis());
- }
- });
- if (pos >= 0) {
- // Same start Time found. Overlapped.
- continue;
- }
- int insertPoint = -1 - pos;
- // Check the two adjacent items.
- if (newItemStartTime < oldItems.get(insertPoint - 1).getEndTimeUtcMillis()
- || newItemEndTime > oldItems.get(insertPoint).getStartTimeUtcMillis()) {
- continue;
- }
- }
- ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), newItem, channel));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
- applyBatch(channel.getName(), ops);
- return;
- }
-
- List<EitItem> outdatedOldItems = new ArrayList<>();
- Map<Integer, EitItem> newEitItemMap = new HashMap<>();
- for (EitItem item : items) {
- newEitItemMap.put(item.getEventId(), item);
- }
- for (EitItem oldItem : oldItems) {
- EitItem item = newEitItemMap.get(oldItem.getEventId());
- if (item == null) {
- outdatedOldItems.add(oldItem);
- continue;
- }
-
- // Since program descriptions arrive at different time, the older one may have the
- // correct program description while the newer one has no clue what value is.
- if (oldItem.getDescription() != null && item.getDescription() == null
- && oldItem.getEventId() == item.getEventId()
- && oldItem.getStartTime() == item.getStartTime()
- && oldItem.getLengthInSecond() == item.getLengthInSecond()
- && Objects.equals(oldItem.getContentRating(), item.getContentRating())
- && Objects.equals(oldItem.getBroadcastGenre(), item.getBroadcastGenre())
- && Objects.equals(oldItem.getCanonicalGenre(), item.getCanonicalGenre())) {
- item.setDescription(oldItem.getDescription());
- }
- if (item.compareTo(oldItem) != 0) {
- ops.add(buildContentProviderOperation(ContentProviderOperation.newUpdate(
- TvContract.buildProgramUri(oldItem.getProgramId())), item, null));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
- newEitItemMap.remove(item.getEventId());
- }
- for (EitItem unverifiedOldItems : outdatedOldItems) {
- if (unverifiedOldItems.getStartTimeUtcMillis() > currentTime) {
- // The given new EIT item list covers partial time span of EPG. Here, we delete old
- // item only when it has an overlapping with the new EIT item list.
- long startTime = unverifiedOldItems.getStartTimeUtcMillis();
- long endTime = unverifiedOldItems.getEndTimeUtcMillis();
- for (EitItem item : newEitItemMap.values()) {
- long newItemStartTime = item.getStartTimeUtcMillis();
- long newItemEndTime = item.getEndTimeUtcMillis();
- if ((startTime >= newItemStartTime && startTime < newItemEndTime)
- || (endTime > newItemStartTime && endTime <= newItemEndTime)) {
- ops.add(ContentProviderOperation.newDelete(TvContract.buildProgramUri(
- unverifiedOldItems.getProgramId())).build());
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- break;
- }
- }
- }
- }
- for (EitItem item : newEitItemMap.values()) {
- if (item.getEndTimeUtcMillis() < currentTime) {
- continue;
- }
- ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert(
- TvContract.Programs.CONTENT_URI), item, channel));
- if (ops.size() >= BATCH_OPERATION_COUNT) {
- applyBatch(channel.getName(), ops);
- ops.clear();
- }
- }
-
- applyBatch(channel.getName(), ops);
- }
-
- private ContentProviderOperation buildContentProviderOperation(
- ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) {
- if (channel != null) {
- builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED,
- channel.isRecordingProhibited() ? 1 : 0);
- }
- }
- if (item != null) {
- builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText())
- .withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- item.getStartTimeUtcMillis())
- .withValue(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- item.getEndTimeUtcMillis())
- .withValue(TvContract.Programs.COLUMN_CONTENT_RATING,
- item.getContentRating())
- .withValue(TvContract.Programs.COLUMN_AUDIO_LANGUAGE,
- item.getAudioLanguage())
- .withValue(TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- item.getDescription())
- .withValue(TvContract.Programs.COLUMN_VERSION_NUMBER,
- item.getEventId());
- }
- return builder.build();
- }
-
- private void applyBatch(String channelName, ArrayList<ContentProviderOperation> operations) {
- try {
- mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, operations);
- } catch (RemoteException | OperationApplicationException e) {
- Log.e(TAG, "Error updating EPG " + channelName, e);
- }
- }
-
- private void handleChannel(TunerChannel channel) {
- long channelId = getChannelId(channel);
- ContentValues values = new ContentValues();
- values.put(TvContract.Channels.COLUMN_NETWORK_AFFILIATION, channel.getShortName());
- values.put(TvContract.Channels.COLUMN_SERVICE_TYPE, channel.getServiceTypeName());
- values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, channel.getTsid());
- values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, channel.getDisplayNumber());
- values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, channel.getName());
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA, channel.toByteArray());
- values.put(TvContract.Channels.COLUMN_DESCRIPTION, channel.getDescription());
- values.put(TvContract.Channels.COLUMN_VIDEO_FORMAT, channel.getVideoFormat());
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1, VERSION);
- values.put(TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2,
- channel.isRecordingProhibited() ? 1 : 0);
-
- if (channelId <= 0) {
- values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
- values.put(TvContract.Channels.COLUMN_TYPE, "QAM256".equals(channel.getModulation())
- ? TvContract.Channels.TYPE_ATSC_C : TvContract.Channels.TYPE_ATSC_T);
- values.put(TvContract.Channels.COLUMN_SERVICE_ID, channel.getProgramNumber());
-
- // ATSC doesn't have original_network_id
- values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.getFrequency());
-
- Uri channelUri = mContext.getContentResolver().insert(TvContract.Channels.CONTENT_URI,
- values);
- channelId = ContentUris.parseId(channelUri);
- } else {
- mContext.getContentResolver().update(
- TvContract.buildChannelUri(channelId), values, null, null);
- }
- channel.setChannelId(channelId);
- mTunerChannelMap.put(channelId, channel);
- mTunerChannelIdMap.put(channel, channelId);
- if (mIsScanning.get()) {
- mScannedChannels.add(channel);
- mPreviousScannedChannels.remove(channel);
- }
- if (mListener != null) {
- mListener.onChannelArrived(channel);
- }
- }
-
- private void clearChannels() {
- int count = mContext.getContentResolver().delete(mChannelsUri, null, null);
- if (count > 0) {
- // We have just deleted obsolete data. Now tell the user that he or she needs
- // to perform the auto-scan again.
- if (mListener != null) {
- mListener.onRescanNeeded();
- }
- }
- }
-
- private void checkVersion() {
- if (PermissionUtils.hasAccessAllEpg(mContext)) {
- String selection = TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 + "<>?";
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, selection,
- new String[] {Integer.toString(VERSION)}, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- // The stored channel data seem outdated. Delete them all.
- clearChannels();
- }
- }
- } else {
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- new String[] { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG1 },
- null, null, null)) {
- if (cursor != null) {
- while (cursor.moveToNext()) {
- int version = cursor.getInt(0);
- if (version != VERSION) {
- clearChannels();
- break;
- }
- }
- }
- }
- }
- }
-
- private long getChannelId(TunerChannel channel) {
- Long channelId = mTunerChannelIdMap.get(channel);
- if (channelId != null) {
- return channelId;
- }
- mHandler.sendEmptyMessage(MSG_BUILD_CHANNEL_MAP);
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- channelId = cursor.getLong(0);
- byte[] providerData = cursor.getBlob(1);
- TunerChannel tunerChannel = TunerChannel.parseFrom(providerData);
- if (tunerChannel != null && tunerChannel.compareTo(channel) == 0) {
- channel.setChannelId(channelId);
- mTunerChannelIdMap.put(channel, channelId);
- mTunerChannelMap.put(channelId, channel);
- return channelId;
- }
- } while (cursor.moveToNext());
- }
- }
- return -1;
- }
-
- private List<EitItem> getAllProgramsForChannel(TunerChannel channel) {
- return getAllProgramsForChannel(channel, null, null);
- }
-
- private List<EitItem> getAllProgramsForChannel(TunerChannel channel, @Nullable Long startTimeMs,
- @Nullable Long endTimeMs) {
- List<EitItem> items = new ArrayList<>();
- long channelId = channel.getChannelId();
- Uri programsUri = (startTimeMs == null || endTimeMs == null) ?
- TvContract.buildProgramsUriForChannel(channelId) :
- TvContract.buildProgramsUriForChannel(channelId, startTimeMs, endTimeMs);
- try (Cursor cursor = mContext.getContentResolver().query(programsUri,
- ALL_PROGRAMS_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long id = cursor.getLong(0);
- String titleText = cursor.getString(1);
- long startTime = ConvertUtils.convertUnixEpochToGPSTime(
- cursor.getLong(2) / DateUtils.SECOND_IN_MILLIS);
- long endTime = ConvertUtils.convertUnixEpochToGPSTime(
- cursor.getLong(3) / DateUtils.SECOND_IN_MILLIS);
- int lengthInSecond = (int) (endTime - startTime);
- String contentRating = cursor.getString(4);
- String broadcastGenre = cursor.getString(5);
- String canonicalGenre = cursor.getString(6);
- String description = cursor.getString(7);
- int eventId = cursor.getInt(8);
- EitItem eitItem = new EitItem(id, eventId, titleText, startTime, lengthInSecond,
- contentRating, null, null, broadcastGenre, canonicalGenre, description);
- items.add(eitItem);
- } while (cursor.moveToNext());
- }
- }
- return items;
- }
-
- private void buildChannelMap() {
- ArrayList<TunerChannel> channels = new ArrayList<>();
- try (Cursor cursor = mContext.getContentResolver().query(mChannelsUri,
- CHANNEL_DATA_SELECTION_ARGS, null, null, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- do {
- long channelId = cursor.getLong(0);
- byte[] data = cursor.getBlob(1);
- TunerChannel channel = TunerChannel.parseFrom(data);
- if (channel != null) {
- channel.setChannelId(channelId);
- channels.add(channel);
- }
- } while (cursor.moveToNext());
- }
- }
- mTunerChannelMap.clear();
- mTunerChannelIdMap.clear();
- for (TunerChannel channel : channels) {
- mTunerChannelMap.put(channel.getChannelId(), channel);
- mTunerChannelIdMap.put(channel, channel.getChannelId());
- }
- }
-
- private static class ChannelEvent {
- public final TunerChannel channel;
- public final List<EitItem> eitItems;
-
- public ChannelEvent(TunerChannel channel, List<EitItem> eitItems) {
- this.channel = channel;
- this.eitItems = eitItems;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java
deleted file mode 100644
index dc99118a..00000000
--- a/src/com/android/tv/tuner/tvinput/EventDetector.java
+++ /dev/null
@@ -1,334 +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.tvinput;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.data.PsiData;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Detects channels and programs that are emerged or changed while parsing ATSC PSIP information.
- */
-public class EventDetector {
- private static final String TAG = "EventDetector";
- private static final boolean DEBUG = false;
- public static final int ALL_PROGRAM_NUMBERS = -1;
-
- private final TunerHal mTunerHal;
-
- private TsParser mTsParser;
- private final Set<Integer> mPidSet = new HashSet<>();
-
- // To prevent channel duplication
- private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
- private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
- private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
- private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
- private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
- private final List<EventListener> mEventListeners = new ArrayList<>();
- private int mFrequency;
- private String mModulation;
- private int mProgramNumber = ALL_PROGRAM_NUMBERS;
-
- private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() {
- @Override
- public void onPatDetected(List<PsiData.PatItem> items) {
- for (PsiData.PatItem i : items) {
- if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) {
- mTunerHal.addPidFilter(i.getPmtPid(), TunerHal.FILTER_TYPE_OTHER);
- }
- }
- }
-
- @Override
- public void onEitPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onEitItemParsed(PsipData.VctItem channel, List<PsipData.EitItem> items) {
- TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
- if (DEBUG) {
- Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
- + channel.getProgramNumber());
- }
- int channelSourceId = channel.getSourceId();
-
- // Source id 0 is useful for cases where a cable operator wishes to define a channel for
- // which no EPG data is currently available.
- // We don't handle such a case.
- if (channelSourceId == 0) {
- return;
- }
-
- // If at least a one caption track have been found in EIT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
- for (PsipData.EitItem item : items) {
- if (captionTracksFound) {
- break;
- }
- List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
- if (captionTracks != null && !captionTracks.isEmpty()) {
- captionTracksFound = true;
- }
- }
- mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
- if (captionTracksFound) {
- for (PsipData.EitItem item : items) {
- item.setHasCaptionTrack();
- }
- }
- if (tunerChannel != null && !mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onEventDetected(tunerChannel, items);
- }
- }
- }
-
- @Override
- public void onEttPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onAllVctItemsParsed() {
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelScanDone();
- }
- }
- }
-
- @Override
- public void onVctItemParsed(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onVctItemParsed VCT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PsiData.PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getProgramNumber();
-
- // If at least a one caption track have been found in VCT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
- || !captionTracks.isEmpty();
- mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
- if (captionTracksFound) {
- tunerChannel.setHasCaptionTrack();
- }
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- tunerChannel.setFrequency(mFrequency);
- tunerChannel.setModulation(mModulation);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mVctProgramNumberSet.add(channelProgramNumber);
- }
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- }
-
- @Override
- public void onSdtItemParsed(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onSdtItemParsed SDT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = new TunerChannel(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PsiData.PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getServiceId();
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- tunerChannel.setFrequency(mFrequency);
- tunerChannel.setModulation(mModulation);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mSdtProgramNumberSet.add(channelProgramNumber);
- }
- if (!mEventListeners.isEmpty()) {
- for (EventListener eventListener : mEventListeners) {
- eventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- }
- };
-
- /**
- * Listener for detecting ATSC TV channels and receiving EPG data.
- */
- public interface EventListener {
-
- /**
- * Fired when new information of an ATSC TV channel arrived.
- *
- * @param channel an ATSC TV channel
- * @param channelArrivedAtFirstTime tells whether this channel arrived at first time
- */
- void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime);
-
- /**
- * Fired when new program events of an ATSC TV channel arrived.
- *
- * @param channel an ATSC TV channel
- * @param items a list of EIT items that were received
- */
- void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items);
-
- /**
- * Fired when information of all detectable ATSC TV channels in current frequency arrived.
- */
- void onChannelScanDone();
- }
-
- /**
- * Creates a detector for ATSC TV channles and program information.
- *
- * @param usbTunerInteface {@link TunerHal}
- */
- public EventDetector(TunerHal usbTunerInteface) {
- mTunerHal = usbTunerInteface;
- }
-
- private void reset() {
- // TODO: Use TsParser.reset()
- int deliverySystemType = mTunerHal.getDeliverySystemType();
- mTsParser =
- new TsParser(
- mTsOutputListener,
- TunerHal.isDvbDeliverySystem(mTunerHal.getDeliverySystemType()));
- mPidSet.clear();
- mVctProgramNumberSet.clear();
- mSdtProgramNumberSet.clear();
- mVctCaptionTracksFound.clear();
- mEitCaptionTracksFound.clear();
- mChannelMap.clear();
- }
-
- /**
- * Starts detecting channel and program information.
- *
- * @param frequency The frequency to listen to.
- * @param modulation The modulation type.
- * @param programNumber The program number if this is for handling tune request. For scanning
- * purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
- */
- public void startDetecting(int frequency, String modulation, int programNumber) {
- reset();
- mFrequency = frequency;
- mModulation = modulation;
- mProgramNumber = programNumber;
- }
-
- private void startListening(int pid) {
- if (mPidSet.contains(pid)) {
- return;
- }
- mPidSet.add(pid);
- mTunerHal.addPidFilter(pid, TunerHal.FILTER_TYPE_OTHER);
- }
-
- /**
- * Feeds ATSC TS stream to detect channel and program information.
- * @param data buffer for ATSC TS stream
- * @param startOffset the offset where buffer starts
- * @param length The length of available data
- */
- public void feedTSStream(byte[] data, int startOffset, int length) {
- if (mPidSet.isEmpty()) {
- startListening(TsParser.ATSC_SI_BASE_PID);
- }
- if (mTsParser != null) {
- mTsParser.feedTSData(data, startOffset, length);
- }
- }
-
- /**
- * Retrieves the channel information regardless of being well-formed.
- * @return {@link List} of {@link TunerChannel}
- */
- public List<TunerChannel> getMalFormedChannels() {
- return mTsParser.getMalFormedChannels();
- }
-
- /**
- * Registers an EventListener.
- * @param eventListener the listener to be registered
- */
- public void registerListener(EventListener eventListener) {
- if (mTsParser != null) {
- // Resets the version numbers so that the new listener can receive the EIT items.
- // Otherwise, each EIT session is handled only once unless there is a new version.
- mTsParser.resetDataVersions();
- }
- mEventListeners.add(eventListener);
- }
-
- /**
- * Unregisters an EventListener.
- * @param eventListener the listener to be unregistered
- */
- public void unregisterListener(EventListener eventListener) {
- boolean removed = mEventListeners.remove(eventListener);
- if (!removed && DEBUG) {
- Log.d(TAG, "Cannot unregister a non-registered listener!");
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
deleted file mode 100644
index 99222bf8..00000000
--- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java
+++ /dev/null
@@ -1,249 +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.tvinput;
-
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.tv.tuner.data.PsiData.PatItem;
-import com.android.tv.tuner.data.PsiData.PmtItem;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.SdtItem;
-import com.android.tv.tuner.data.PsipData.VctItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.source.FileTsStreamer;
-import com.android.tv.tuner.ts.TsParser;
-import com.android.tv.tuner.tvinput.EventDetector.EventListener;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * PSIP event detector for a file source.
- *
- * <p>Uses {@link TsParser} to analyze input MPEG-2 transport stream, detects and reports
- * various PSIP-related events via {@link TsParser.TsOutputListener}.
- */
-public class FileSourceEventDetector {
- private static final String TAG = "FileSourceEventDetector";
- private static final boolean DEBUG = true;
- public static final int ALL_PROGRAM_NUMBERS = 0;
-
- private TsParser mTsParser;
- private final Set<Integer> mVctProgramNumberSet = new HashSet<>();
- private final Set<Integer> mSdtProgramNumberSet = new HashSet<>();
- private final SparseArray<TunerChannel> mChannelMap = new SparseArray<>();
- private final SparseBooleanArray mVctCaptionTracksFound = new SparseBooleanArray();
- private final SparseBooleanArray mEitCaptionTracksFound = new SparseBooleanArray();
- private final EventListener mEventListener;
- private final boolean mEnableDvbSignal;
- private FileTsStreamer.StreamProvider mStreamProvider;
- private int mProgramNumber = ALL_PROGRAM_NUMBERS;
-
- public FileSourceEventDetector(EventDetector.EventListener listener, boolean enableDvbSignal) {
- mEventListener = listener;
- mEnableDvbSignal = enableDvbSignal;
- }
-
- /**
- * Starts detecting channel and program information.
- *
- * @param provider MPEG-2 transport stream source.
- * @param programNumber The program number if this is for handling tune request. For scanning
- * purpose, supply {@link #ALL_PROGRAM_NUMBERS}.
- */
- public void start(FileTsStreamer.StreamProvider provider, int programNumber) {
- mStreamProvider = provider;
- mProgramNumber = programNumber;
- reset();
- }
-
- private void reset() {
- mTsParser = new TsParser(mTsOutputListener, mEnableDvbSignal); // TODO: Use TsParser.reset()
- mStreamProvider.clearPidFilter();
- mVctProgramNumberSet.clear();
- mSdtProgramNumberSet.clear();
- mVctCaptionTracksFound.clear();
- mEitCaptionTracksFound.clear();
- mChannelMap.clear();
- }
-
- public void feedTSStream(byte[] data, int startOffset, int length) {
- if (mStreamProvider.isFilterEmpty()) {
- startListening(TsParser.ATSC_SI_BASE_PID);
- startListening(TsParser.PAT_PID);
- }
- if (mTsParser != null) {
- mTsParser.feedTSData(data, startOffset, length);
- }
- }
-
- private void startListening(int pid) {
- if (mStreamProvider.isInFilter(pid)) {
- return;
- }
- mStreamProvider.addPidFilter(pid);
- }
-
- private final TsParser.TsOutputListener mTsOutputListener = new TsParser.TsOutputListener() {
- @Override
- public void onPatDetected(List<PatItem> items) {
- for (PatItem i : items) {
- if (mProgramNumber == ALL_PROGRAM_NUMBERS || mProgramNumber == i.getProgramNo()) {
- mStreamProvider.addPidFilter(i.getPmtPid());
- }
- }
- }
-
- @Override
- public void onEitPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onEitItemParsed(VctItem channel, List<EitItem> items) {
- TunerChannel tunerChannel = mChannelMap.get(channel.getProgramNumber());
- if (DEBUG) {
- Log.d(TAG, "onEitItemParsed tunerChannel:" + tunerChannel + " "
- + channel.getProgramNumber());
- }
- int channelSourceId = channel.getSourceId();
-
- // Source id 0 is useful for cases where a cable operator wishes to define a channel for
- // which no EPG data is currently available.
- // We don't handle such a case.
- if (channelSourceId == 0) {
- return;
- }
-
- // If at least a one caption track have been found in EIT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mEitCaptionTracksFound.get(channelSourceId);
- for (EitItem item : items) {
- if (captionTracksFound) {
- break;
- }
- List<AtscCaptionTrack> captionTracks = item.getCaptionTracks();
- if (captionTracks != null && !captionTracks.isEmpty()) {
- captionTracksFound = true;
- }
- }
- mEitCaptionTracksFound.put(channelSourceId, captionTracksFound);
- if (captionTracksFound) {
- for (EitItem item : items) {
- item.setHasCaptionTrack();
- }
- }
- if (tunerChannel != null && mEventListener != null) {
- mEventListener.onEventDetected(tunerChannel, items);
- }
- }
-
- @Override
- public void onEttPidDetected(int pid) {
- startListening(pid);
- }
-
- @Override
- public void onAllVctItemsParsed() {
- // do nothing.
- }
-
- @Override
- public void onVctItemParsed(VctItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onVctItemParsed VCT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = TunerChannel.forFile(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getProgramNumber();
-
- // If at least a one caption track have been found in VCT items for the given channel,
- // we starts to interpret the zero tracks as a clearance of the caption tracks.
- boolean captionTracksFound = mVctCaptionTracksFound.get(channelProgramNumber)
- || !captionTracks.isEmpty();
- mVctCaptionTracksFound.put(channelProgramNumber, captionTracksFound);
- if (captionTracksFound) {
- tunerChannel.setHasCaptionTrack();
- }
- tunerChannel.setFilepath(mStreamProvider.getFilepath());
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
-
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mVctProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mVctProgramNumberSet.add(channelProgramNumber);
- }
- if (mEventListener != null) {
- mEventListener.onChannelDetected(tunerChannel, !found);
- }
- }
-
- @Override
- public void onSdtItemParsed(SdtItem channel, List<PmtItem> pmtItems) {
- if (DEBUG) {
- Log.d(TAG, "onSdtItemParsed SDT " + channel);
- Log.d(TAG, " PMT " + pmtItems);
- }
-
- // Merges the audio and caption tracks located in PMT items into the tracks of the given
- // tuner channel.
- TunerChannel tunerChannel = TunerChannel.forDvbFile(channel, pmtItems);
- List<AtscAudioTrack> audioTracks = new ArrayList<>();
- List<AtscCaptionTrack> captionTracks = new ArrayList<>();
- for (PmtItem pmtItem : pmtItems) {
- if (pmtItem.getAudioTracks() != null) {
- audioTracks.addAll(pmtItem.getAudioTracks());
- }
- if (pmtItem.getCaptionTracks() != null) {
- captionTracks.addAll(pmtItem.getCaptionTracks());
- }
- }
- int channelProgramNumber = channel.getServiceId();
- tunerChannel.setFilepath(mStreamProvider.getFilepath());
- tunerChannel.setAudioTracks(audioTracks);
- tunerChannel.setCaptionTracks(captionTracks);
- mChannelMap.put(tunerChannel.getProgramNumber(), tunerChannel);
- boolean found = mSdtProgramNumberSet.contains(channelProgramNumber);
- if (!found) {
- mSdtProgramNumberSet.add(channelProgramNumber);
- }
- if (mEventListener != null) {
- mEventListener.onChannelDetected(tunerChannel, !found);
- }
- }
- };
-}
diff --git a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java b/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java
deleted file mode 100644
index 3908fe6c..00000000
--- a/src/com/android/tv/tuner/tvinput/PlaybackBufferListener.java
+++ /dev/null
@@ -1,42 +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.tvinput;
-
-/**
- * The listener for buffer events occurred during playback.
- */
-public interface PlaybackBufferListener {
-
- /**
- * Invoked when the start position of the buffer has been changed.
- *
- * @param startTimeMs the new start time of the buffer in millisecond
- */
- void onBufferStartTimeChanged(long startTimeMs);
-
- /**
- * Invoked when the state of the buffer has been changed.
- *
- * @param available whether the buffer is available or not
- */
- void onBufferStateChanged(boolean available);
-
- /**
- * Invoked when the disk speed is too slow to write the buffers.
- */
- void onDiskTooSlow();
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerDebug.java b/src/com/android/tv/tuner/tvinput/TunerDebug.java
deleted file mode 100644
index 2ddc946a..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerDebug.java
+++ /dev/null
@@ -1,150 +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.tvinput;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * A class to maintain various debugging information.
- */
-public class TunerDebug {
- private static final String TAG = "TunerDebug";
- public static final boolean ENABLED = false;
-
- private int mVideoFrameDrop;
- private int mBytesInQueue;
-
- private long mAudioPositionUs;
- private long mAudioPtsUs;
- private long mVideoPtsUs;
-
- private long mLastAudioPositionUs;
- private long mLastAudioPtsUs;
- private long mLastVideoPtsUs;
- private long mLastCheckTimestampMs;
-
- private long mAudioPositionUsRate;
- private long mAudioPtsUsRate;
- private long mVideoPtsUsRate;
-
- private TunerDebug() {
- mVideoFrameDrop = 0;
- mLastCheckTimestampMs = SystemClock.elapsedRealtime();
- }
-
- private static class LazyHolder {
- private static final TunerDebug INSTANCE = new TunerDebug();
- }
-
- public static TunerDebug getInstance() {
- return LazyHolder.INSTANCE;
- }
-
- public static void notifyVideoFrameDrop(int count, long delta) {
- // TODO: provide timestamp mismatch information using delta
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mVideoFrameDrop += count;
- }
-
- public static int getVideoFrameDrop() {
- TunerDebug sTunerDebug = getInstance();
- int videoFrameDrop = sTunerDebug.mVideoFrameDrop;
- if (videoFrameDrop > 0) {
- Log.d(TAG, "Dropped video frame: " + videoFrameDrop);
- }
- sTunerDebug.mVideoFrameDrop = 0;
- return videoFrameDrop;
- }
-
- public static void setBytesInQueue(int bytesInQueue) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mBytesInQueue = bytesInQueue;
- }
-
- public static int getBytesInQueue() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mBytesInQueue;
- }
-
- public static void setAudioPositionUs(long audioPositionUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mAudioPositionUs = audioPositionUs;
- }
-
- public static long getAudioPositionUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPositionUs;
- }
-
- public static void setAudioPtsUs(long audioPtsUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mAudioPtsUs = audioPtsUs;
- }
-
- public static long getAudioPtsUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPtsUs;
- }
-
- public static void setVideoPtsUs(long videoPtsUs) {
- TunerDebug sTunerDebug = getInstance();
- sTunerDebug.mVideoPtsUs = videoPtsUs;
- }
-
- public static long getVideoPtsUs() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mVideoPtsUs;
- }
-
- public static void calculateDiff() {
- TunerDebug sTunerDebug = getInstance();
- long currentTime = SystemClock.elapsedRealtime();
- long duration = currentTime - sTunerDebug.mLastCheckTimestampMs;
- if (duration != 0) {
- sTunerDebug.mAudioPositionUsRate =
- (sTunerDebug.mAudioPositionUs - sTunerDebug.mLastAudioPositionUs) * 1000
- / duration;
- sTunerDebug.mAudioPtsUsRate =
- (sTunerDebug.mAudioPtsUs - sTunerDebug.mLastAudioPtsUs) * 1000
- / duration;
- sTunerDebug.mVideoPtsUsRate =
- (sTunerDebug.mVideoPtsUs - sTunerDebug.mLastVideoPtsUs) * 1000
- / duration;
- }
-
- sTunerDebug.mLastAudioPositionUs = sTunerDebug.mAudioPositionUs;
- sTunerDebug.mLastAudioPtsUs = sTunerDebug.mAudioPtsUs;
- sTunerDebug.mLastVideoPtsUs = sTunerDebug.mVideoPtsUs;
- sTunerDebug.mLastCheckTimestampMs = currentTime;
- }
-
- public static long getAudioPositionUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPositionUsRate;
- }
-
- public static long getAudioPtsUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mAudioPtsUsRate;
- }
-
- public static long getVideoPtsUsRate() {
- TunerDebug sTunerDebug = getInstance();
- return sTunerDebug.mVideoPtsUsRate;
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
deleted file mode 100644
index acdd149f..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java
+++ /dev/null
@@ -1,104 +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.tvinput;
-
-import android.content.Context;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-
-/**
- * Processes DVR recordings, and deletes the previously recorded contents.
- */
-public class TunerRecordingSession extends TvInputService.RecordingSession {
- private static final String TAG = "TunerRecordingSession";
- private static final boolean DEBUG = false;
-
- private final TunerRecordingSessionWorker mSessionWorker;
-
- public TunerRecordingSession(Context context, String inputId,
- ChannelDataManager channelDataManager) {
- super(context);
- mSessionWorker = new TunerRecordingSessionWorker(context, inputId, channelDataManager,
- this);
- }
-
- // RecordingSession
- @MainThread
- @Override
- public void onTune(Uri channelUri) {
- // TODO(dvr): support calling more than once, http://b/27171225
- if (DEBUG) {
- Log.d(TAG, "Requesting recording session tune: " + channelUri);
- }
- mSessionWorker.tune(channelUri);
- }
-
- @MainThread
- @Override
- public void onRelease() {
- if (DEBUG) {
- Log.d(TAG, "Requesting recording session release.");
- }
- mSessionWorker.release();
- }
-
- @MainThread
- @Override
- public void onStartRecording(@Nullable Uri programUri) {
- if (DEBUG) {
- Log.d(TAG, "Requesting start recording.");
- }
- mSessionWorker.startRecording(programUri);
- }
-
- @MainThread
- @Override
- public void onStopRecording() {
- if (DEBUG) {
- Log.d(TAG, "Requesting stop recording.");
- }
- mSessionWorker.stopRecording();
- }
-
- // Called from TunerRecordingSessionImpl in a worker thread.
- @WorkerThread
- public void onTuned(Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "Notifying recording session tuned.");
- }
- notifyTuned(channelUri);
- }
-
- @WorkerThread
- public void onRecordFinished(final Uri recordedProgramUri) {
- if (DEBUG) {
- Log.d(TAG, "Notifying record successfully finished.");
- }
- notifyRecordingStopped(recordedProgramUri);
- }
-
- @WorkerThread
- public void onError(int reason) {
- Log.w(TAG, "Notifying recording error: " + reason);
- notifyError(reason);
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
deleted file mode 100644
index 34013bf1..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java
+++ /dev/null
@@ -1,662 +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.tvinput;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.support.annotation.IntDef;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import android.util.Pair;
-import com.google.android.exoplayer.C;
-import com.android.tv.TvApplication;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.recording.RecordingCapability;
-import com.android.tv.dvr.DvrStorageStatusManager;
-import com.android.tv.dvr.data.RecordedProgram;
-import com.android.tv.tuner.DvbDeviceAccessor;
-import com.android.tv.tuner.data.PsipData;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Implements a DVR feature.
- */
-public class TunerRecordingSessionWorker implements PlaybackBufferListener,
- EventDetector.EventListener, SampleExtractor.OnCompletionListener,
- Handler.Callback {
- private static final String TAG = "TunerRecordingSessionW";
- private static final boolean DEBUG = false;
-
- private static final String SORT_BY_TIME = TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS
- + ", " + TvContract.Programs.COLUMN_CHANNEL_ID + ", "
- + TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS;
- private static final long TUNING_RETRY_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
- private static final long STORAGE_MONITOR_INTERVAL_MS = TimeUnit.SECONDS.toMillis(4);
- private static final long MIN_PARTIAL_RECORDING_DURATION_MS = TimeUnit.SECONDS.toMillis(10);
- private static final long PREPARE_RECORDER_POLL_MS = 50;
- private static final int MSG_TUNE = 1;
- private static final int MSG_START_RECORDING = 2;
- private static final int MSG_PREPARE_RECODER = 3;
- private static final int MSG_STOP_RECORDING = 4;
- private static final int MSG_MONITOR_STORAGE_STATUS = 5;
- private static final int MSG_RELEASE = 6;
- private static final int MSG_UPDATE_CC_INFO = 7;
- private final RecordingCapability mCapabilities;
-
- public RecordingCapability getCapabilities() {
- return mCapabilities;
- }
-
- @IntDef({STATE_IDLE, STATE_TUNING, STATE_TUNED, STATE_RECORDING})
- @Retention(RetentionPolicy.SOURCE)
- public @interface DvrSessionState {}
- private static final int STATE_IDLE = 1;
- private static final int STATE_TUNING = 2;
- private static final int STATE_TUNED = 3;
- private static final int STATE_RECORDING = 4;
-
- private static final long CHANNEL_ID_NONE = -1;
- private static final int MAX_TUNING_RETRY = 6;
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final DvrStorageStatusManager mDvrStorageStatusManager;
- private final Handler mHandler;
- private final TsDataSourceManager mSourceManager;
- private final Random mRandom = new Random();
-
- private TsDataSource mTunerSource;
- private TunerChannel mChannel;
- private File mStorageDir;
- private long mRecordStartTime;
- private long mRecordEndTime;
- private boolean mRecorderRunning;
- private SampleExtractor mRecorder;
- private final TunerRecordingSession mSession;
- @DvrSessionState private int mSessionState = STATE_IDLE;
- private final String mInputId;
- private Uri mProgramUri;
-
- private PsipData.EitItem mCurrenProgram;
- private List<AtscCaptionTrack> mCaptionTracks;
- private DvrStorageManager mDvrStorageManager;
-
- public TunerRecordingSessionWorker(Context context, String inputId,
- ChannelDataManager dataManager, TunerRecordingSession session) {
- mRandom.setSeed(System.nanoTime());
- mContext = context;
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper(), this);
- mDvrStorageStatusManager =
- TvApplication.getSingletons(context).getDvrStorageStatusManager();
- mChannelDataManager = dataManager;
- mChannelDataManager.checkDataVersion(context);
- mSourceManager = TsDataSourceManager.createSourceManager(true);
- mCapabilities = new DvbDeviceAccessor(context).getRecordingCapability(inputId);
- mInputId = inputId;
- if (DEBUG) Log.d(TAG, mCapabilities.toString());
- mSession = session;
- }
-
- // PlaybackBufferListener
- @Override
- public void onBufferStartTimeChanged(long startTimeMs) { }
-
- @Override
- public void onBufferStateChanged(boolean available) { }
-
- @Override
- public void onDiskTooSlow() { }
-
- // EventDetector.EventListener
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- if (mChannel == null || mChannel.compareTo(channel) != 0) {
- return;
- }
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<PsipData.EitItem> items) {
- if (mChannel == null || mChannel.compareTo(channel) != 0) {
- return;
- }
- mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget();
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- // do nothing.
- }
-
- // SampleExtractor.OnCompletionListener
- @Override
- public void onCompletion(boolean success, long lastExtractedPositionUs) {
- onRecordingResult(success, lastExtractedPositionUs);
- reset();
- }
-
- /**
- * Tunes to {@code channelUri}.
- */
- @MainThread
- public void tune(Uri channelUri) {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget();
- }
-
- /**
- * Starts recording.
- */
- @MainThread
- public void startRecording(@Nullable Uri programUri) {
- mHandler.obtainMessage(MSG_START_RECORDING, programUri).sendToTarget();
- }
-
- /**
- * Stops recording.
- */
- @MainThread
- public void stopRecording() {
- mHandler.sendEmptyMessage(MSG_STOP_RECORDING);
- }
-
- /**
- * Releases all resources.
- */
- @MainThread
- public void release() {
- mHandler.removeCallbacksAndMessages(null);
- mHandler.sendEmptyMessage(MSG_RELEASE);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_TUNE: {
- Uri channelUri = (Uri) msg.obj;
- int retryCount = msg.arg1;
- if (DEBUG) Log.d(TAG, "Tune to " + channelUri);
- if (doTune(channelUri)) {
- if (mSessionState == STATE_TUNED) {
- mSession.onTuned(channelUri);
- } else {
- Log.w(TAG, "Tuner stream cannot be created due to resource shortage.");
- if (retryCount < MAX_TUNING_RETRY) {
- Message tuneMsg =
- mHandler.obtainMessage(MSG_TUNE, retryCount + 1, 0, channelUri);
- mHandler.sendMessageDelayed(tuneMsg, TUNING_RETRY_INTERVAL_MS);
- } else {
- mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY);
- reset();
- }
- }
- }
- return true;
- }
- case MSG_START_RECORDING: {
- if (DEBUG) Log.d(TAG, "Start recording");
- if (!doStartRecording((Uri) msg.obj)) {
- reset();
- }
- return true;
- }
- case MSG_PREPARE_RECODER: {
- if (DEBUG) Log.d(TAG, "Preparing recorder");
- if (!mRecorderRunning) {
- return true;
- }
- try {
- if (!mRecorder.prepare()) {
- mHandler.sendEmptyMessageDelayed(MSG_PREPARE_RECODER,
- PREPARE_RECORDER_POLL_MS);
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor");
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- reset();
- }
- return true;
- }
- case MSG_STOP_RECORDING: {
- if (DEBUG) Log.d(TAG, "Stop recording");
- if (mSessionState != STATE_RECORDING) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- reset();
- return true;
- }
- if (mRecorderRunning) {
- stopRecorder();
- }
- return true;
- }
- case MSG_MONITOR_STORAGE_STATUS: {
- if (mSessionState != STATE_RECORDING) {
- return true;
- }
- if (!mDvrStorageStatusManager.isStorageSufficient()) {
- if (mRecorderRunning) {
- stopRecorder();
- }
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- reset();
- } else {
- mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS,
- STORAGE_MONITOR_INTERVAL_MS);
- }
- return true;
- }
- case MSG_RELEASE: {
- // Since release was requested, current recording will be cancelled
- // without notification.
- reset();
- mSourceManager.release();
- mHandler.removeCallbacksAndMessages(null);
- mHandler.getLooper().quitSafely();
- return true;
- }
- case MSG_UPDATE_CC_INFO: {
- Pair<TunerChannel, List<EitItem>> pair =
- (Pair<TunerChannel, List<EitItem>>) msg.obj;
- updateCaptionTracks(pair.first, pair.second);
- return true;
- }
- }
- return false;
- }
-
- @Nullable
- private TunerChannel getChannel(Uri channelUri) {
- if (channelUri == null) {
- return null;
- }
- long channelId;
- try {
- channelId = ContentUris.parseId(channelUri);
- } catch (UnsupportedOperationException | NumberFormatException e) {
- channelId = CHANNEL_ID_NONE;
- }
- return (channelId == CHANNEL_ID_NONE) ? null : mChannelDataManager.getChannel(channelId);
- }
-
- private String getStorageKey() {
- long prefix = System.currentTimeMillis();
- int suffix = mRandom.nextInt();
- return String.format(Locale.ENGLISH, "%016x_%016x", prefix, suffix);
- }
-
- private void reset() {
- if (mRecorder != null) {
- mRecorder.release();
- mRecorder = null;
- }
- if (mTunerSource != null) {
- mSourceManager.releaseDataSource(mTunerSource);
- mTunerSource = null;
- }
- mDvrStorageManager = null;
- mSessionState = STATE_IDLE;
- mRecorderRunning = false;
- }
-
- private boolean doTune(Uri channelUri) {
- if (mSessionState != STATE_IDLE && mSessionState != STATE_TUNING) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Tuning was requested from wrong status.");
- return false;
- }
- mChannel = getChannel(channelUri);
- if (mChannel == null) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Failed to start recording. Couldn't find the channel for " + mChannel);
- return false;
- } else if (mChannel.isRecordingProhibited()) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Failed to start recording. Not a recordable channel: " + mChannel);
- return false;
- }
- if (!mDvrStorageStatusManager.isStorageSufficient()) {
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Log.w(TAG, "Tuning failed due to insufficient storage.");
- return false;
- }
- mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this);
- if (mTunerSource == null) {
- // Retry tuning in this case.
- mSessionState = STATE_TUNING;
- return true;
- }
- mSessionState = STATE_TUNED;
- return true;
- }
-
- private boolean doStartRecording(@Nullable Uri programUri) {
- if (mSessionState != STATE_TUNED) {
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Recording session status abnormal");
- return false;
- }
- mStorageDir = mDvrStorageStatusManager.isStorageSufficient() ?
- new File(mDvrStorageStatusManager.getRecordingRootDataDirectory(),
- getStorageKey()) : null;
- if (mStorageDir == null) {
- mSession.onError(TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE);
- Log.w(TAG, "Failed to start recording due to insufficient storage.");
- return false;
- }
- // Since tuning might be happened a while ago, shifts the start position of tuned source.
- mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition());
- mRecordStartTime = System.currentTimeMillis();
- mDvrStorageManager = new DvrStorageManager(mStorageDir, true);
- mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource,
- new BufferManager(mDvrStorageManager), this, true);
- mRecorder.setOnCompletionListener(this, mHandler);
- mProgramUri = programUri;
- mSessionState = STATE_RECORDING;
- mRecorderRunning = true;
- mHandler.sendEmptyMessage(MSG_PREPARE_RECODER);
- mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
- mHandler.sendEmptyMessageDelayed(MSG_MONITOR_STORAGE_STATUS,
- STORAGE_MONITOR_INTERVAL_MS);
- return true;
- }
-
- private void stopRecorder() {
- // Do not change session status.
- if (mRecorder != null) {
- mRecorder.release();
- mRecordEndTime = System.currentTimeMillis();
- mRecorder = null;
- }
- mRecorderRunning = false;
- mHandler.removeMessages(MSG_MONITOR_STORAGE_STATUS);
- Log.i(TAG, "Recording stopped");
- }
-
- private void updateCaptionTracks(TunerChannel channel, List<PsipData.EitItem> items) {
- if (mChannel == null || channel == null || mChannel.compareTo(channel) != 0
- || items == null || items.isEmpty()) {
- return;
- }
- PsipData.EitItem currentProgram = getCurrentProgram(items);
- if (currentProgram == null || !currentProgram.hasCaptionTrack()
- || mCurrenProgram != null && mCurrenProgram.compareTo(currentProgram) == 0) {
- return;
- }
- mCurrenProgram = currentProgram;
- mCaptionTracks = new ArrayList<>(currentProgram.getCaptionTracks());
- if (DEBUG) {
- Log.d(TAG, "updated " + mCaptionTracks.size() + " caption tracks for "
- + currentProgram);
- }
- }
-
- private PsipData.EitItem getCurrentProgram(List<PsipData.EitItem> items) {
- for (PsipData.EitItem item : items) {
- if (mRecordStartTime >= item.getStartTimeUtcMillis()
- && mRecordStartTime < item.getEndTimeUtcMillis()) {
- return item;
- }
- }
- return null;
- }
-
- private static class Program {
- private final long mChannelId;
- private final String mTitle;
- private String mSeriesId;
- private final String mSeasonTitle;
- private final String mEpisodeTitle;
- private final String mSeasonNumber;
- private final String mEpisodeNumber;
- private final String mDescription;
- private final String mPosterArtUri;
- private final String mThumbnailUri;
- private final String mCanonicalGenres;
- private final String mContentRatings;
- private final long mStartTimeUtcMillis;
- private final long mEndTimeUtcMillis;
- private final int mVideoWidth;
- private final int mVideoHeight;
- private final byte[] mInternalProviderData;
-
- private static final String[] PROJECTION = {
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.Programs.COLUMN_TITLE,
- TvContract.Programs.COLUMN_SEASON_TITLE,
- TvContract.Programs.COLUMN_EPISODE_TITLE,
- TvContract.Programs.COLUMN_SEASON_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_EPISODE_DISPLAY_NUMBER,
- TvContract.Programs.COLUMN_SHORT_DESCRIPTION,
- TvContract.Programs.COLUMN_POSTER_ART_URI,
- TvContract.Programs.COLUMN_THUMBNAIL_URI,
- TvContract.Programs.COLUMN_CANONICAL_GENRE,
- TvContract.Programs.COLUMN_CONTENT_RATING,
- TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
- TvContract.Programs.COLUMN_VIDEO_WIDTH,
- TvContract.Programs.COLUMN_VIDEO_HEIGHT,
- TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA
- };
-
- public Program(Cursor cursor) {
- int index = 0;
- mChannelId = cursor.getLong(index++);
- mTitle = cursor.getString(index++);
- mSeasonTitle = cursor.getString(index++);
- mEpisodeTitle = cursor.getString(index++);
- mSeasonNumber = cursor.getString(index++);
- mEpisodeNumber = cursor.getString(index++);
- mDescription = cursor.getString(index++);
- mPosterArtUri = cursor.getString(index++);
- mThumbnailUri = cursor.getString(index++);
- mCanonicalGenres = cursor.getString(index++);
- mContentRatings = cursor.getString(index++);
- mStartTimeUtcMillis = cursor.getLong(index++);
- mEndTimeUtcMillis = cursor.getLong(index++);
- mVideoWidth = cursor.getInt(index++);
- mVideoHeight = cursor.getInt(index++);
- mInternalProviderData = cursor.getBlob(index++);
- SoftPreconditions.checkArgument(index == PROJECTION.length);
- }
-
- public Program(long channelId) {
- mChannelId = channelId;
- mTitle = "Unknown";
- mSeasonTitle = "";
- mEpisodeTitle = "";
- mSeasonNumber = "";
- mEpisodeNumber = "";
- mDescription = "Unknown";
- mPosterArtUri = null;
- mThumbnailUri = null;
- mCanonicalGenres = null;
- mContentRatings = null;
- mStartTimeUtcMillis = 0;
- mEndTimeUtcMillis = 0;
- mVideoWidth = 0;
- mVideoHeight = 0;
- mInternalProviderData = null;
- }
-
- public static Program onQuery(Cursor c) {
- Program program = null;
- if (c != null && c.moveToNext()) {
- program = new Program(c);
- }
- return program;
- }
-
- public ContentValues buildValues() {
- ContentValues values = new ContentValues();
- int index = 0;
- values.put(PROJECTION[index++], mChannelId);
- values.put(PROJECTION[index++], mTitle);
- values.put(PROJECTION[index++], mSeasonTitle);
- values.put(PROJECTION[index++], mEpisodeTitle);
- values.put(PROJECTION[index++], mSeasonNumber);
- values.put(PROJECTION[index++], mEpisodeNumber);
- values.put(PROJECTION[index++], mDescription);
- values.put(PROJECTION[index++], mPosterArtUri);
- values.put(PROJECTION[index++], mThumbnailUri);
- values.put(PROJECTION[index++], mCanonicalGenres);
- values.put(PROJECTION[index++], mContentRatings);
- values.put(PROJECTION[index++], mStartTimeUtcMillis);
- values.put(PROJECTION[index++], mEndTimeUtcMillis);
- values.put(PROJECTION[index++], mVideoWidth);
- values.put(PROJECTION[index++], mVideoHeight);
- values.put(PROJECTION[index++], mInternalProviderData);
- SoftPreconditions.checkArgument(index == PROJECTION.length);
- return values;
- }
- }
-
- private Program getRecordedProgram() {
- ContentResolver resolver = mContext.getContentResolver();
- Uri programUri = mProgramUri;
- if (mProgramUri == null) {
- long avg = mRecordStartTime / 2 + mRecordEndTime / 2;
- programUri = TvContract.buildProgramsUriForChannel(mChannel.getChannelId(), avg, avg);
- }
- try (Cursor c = resolver.query(programUri, Program.PROJECTION, null, null, SORT_BY_TIME)) {
- if (c != null) {
- Program result = Program.onQuery(c);
- if (DEBUG) {
- Log.v(TAG, "Finished query for " + this);
- }
- return result;
- } else {
- if (c == null) {
- Log.e(TAG, "Unknown query error for " + this);
- } else {
- if (DEBUG) Log.d(TAG, "Canceled query for " + this);
- }
- return null;
- }
- }
- }
-
- private Uri insertRecordedProgram(Program program, long channelId, String storageUri,
- long totalBytes, long startTime, long endTime) {
- // TODO: Set title even though program is null.
- RecordedProgram recordedProgram = RecordedProgram.builder()
- .setInputId(mInputId)
- .setChannelId(channelId)
- .setDataUri(storageUri)
- .setDurationMillis(endTime - startTime)
- .setDataBytes(totalBytes)
- // startTime and endTime could be overridden by program's start and end value.
- .setStartTimeUtcMillis(startTime)
- .setEndTimeUtcMillis(endTime)
- .build();
- ContentValues values = RecordedProgram.toValues(recordedProgram);
- if (program != null) {
- values.putAll(program.buildValues());
- }
- return mContext.getContentResolver().insert(TvContract.RecordedPrograms.CONTENT_URI,
- values);
- }
-
- private void onRecordingResult(boolean success, long lastExtractedPositionUs) {
- if (mSessionState != STATE_RECORDING) {
- // Error notification is not needed.
- Log.e(TAG, "Recording session status abnormal");
- return;
- }
- if (mRecorderRunning) {
- // In case of recorder not being stopped, because of premature termination of recording.
- stopRecorder();
- }
- if (!success && lastExtractedPositionUs <
- TimeUnit.MILLISECONDS.toMicros(MIN_PARTIAL_RECORDING_DURATION_MS)) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.w(TAG, "Recording failed during recording");
- return;
- }
- Log.i(TAG, "recording finished " + (success ? "completely" : "partially"));
- long recordEndTime =
- (lastExtractedPositionUs == C.UNKNOWN_TIME_US)
- ? System.currentTimeMillis()
- : mRecordStartTime + lastExtractedPositionUs / 1000;
- Uri uri =
- insertRecordedProgram(
- getRecordedProgram(),
- mChannel.getChannelId(),
- Uri.fromFile(mStorageDir).toString(),
- 1024 * 1024,
- mRecordStartTime,
- recordEndTime);
- if (uri == null) {
- new DeleteRecordingTask().execute(mStorageDir);
- mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
- Log.e(TAG, "Inserting a recording to DB failed");
- return;
- }
- mDvrStorageManager.writeCaptionInfoFiles(mCaptionTracks);
- mSession.onRecordFinished(uri);
- }
-
- private static class DeleteRecordingTask extends AsyncTask<File, Void, Void> {
-
- @Override
- public Void doInBackground(File... files) {
- if (files == null || files.length == 0) {
- return null;
- }
- for(File file : files) {
- Utils.deleteDirOrFile(file);
- }
- return null;
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java
deleted file mode 100644
index 44bae908..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerSession.java
+++ /dev/null
@@ -1,324 +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.tvinput;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.media.PlaybackParams;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvInputService;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.Html;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.TunerPreferences.TunerPreferencesChangedListener;
-import com.android.tv.tuner.cc.CaptionLayout;
-import com.android.tv.tuner.cc.CaptionTrackRenderer;
-import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.util.GlobalSettingsUtils;
-import com.android.tv.tuner.util.StatusTextUtils;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-
-/**
- * Provides a tuner TV input session. It handles Overlay UI works. Main tuner input functions
- * are implemented in {@link TunerSessionWorker}.
- */
-public class TunerSession extends TvInputService.Session implements
- Handler.Callback, TunerPreferencesChangedListener {
- private static final String TAG = "TunerSession";
- private static final boolean DEBUG = false;
- private static final String USBTUNER_SHOW_DEBUG = "persist.tv.tuner.show_debug";
-
- public static final int MSG_UI_SHOW_MESSAGE = 1;
- public static final int MSG_UI_HIDE_MESSAGE = 2;
- public static final int MSG_UI_SHOW_AUDIO_UNPLAYABLE = 3;
- public static final int MSG_UI_HIDE_AUDIO_UNPLAYABLE = 4;
- public static final int MSG_UI_PROCESS_CAPTION_TRACK = 5;
- public static final int MSG_UI_START_CAPTION_TRACK = 6;
- public static final int MSG_UI_STOP_CAPTION_TRACK = 7;
- public static final int MSG_UI_RESET_CAPTION_TRACK = 8;
- public static final int MSG_UI_CLEAR_CAPTION_RENDERER = 9;
- public static final int MSG_UI_SET_STATUS_TEXT = 10;
- public static final int MSG_UI_TOAST_RESCAN_NEEDED = 11;
-
- private final Context mContext;
- private final Handler mUiHandler;
- private final View mOverlayView;
- private final TextView mMessageView;
- private final TextView mStatusView;
- private final TextView mAudioStatusView;
- private final ViewGroup mMessageLayout;
- private final CaptionTrackRenderer mCaptionTrackRenderer;
- private final TunerSessionWorker mSessionWorker;
- private boolean mReleased = false;
- private boolean mPlayPaused;
- private long mTuneStartTimestamp;
-
- public TunerSession(Context context, ChannelDataManager channelDataManager) {
- super(context);
- mContext = context;
- mUiHandler = new Handler(this);
- LayoutInflater inflater = (LayoutInflater)
- context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mOverlayView = inflater.inflate(R.layout.ut_overlay_view, null);
- mMessageLayout = (ViewGroup) mOverlayView.findViewById(R.id.message_layout);
- mMessageLayout.setVisibility(View.INVISIBLE);
- mMessageView = (TextView) mOverlayView.findViewById(R.id.message);
- mStatusView = (TextView) mOverlayView.findViewById(R.id.tuner_status);
- boolean showDebug = SystemPropertiesProxy.getBoolean(USBTUNER_SHOW_DEBUG, false);
- mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE);
- mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status);
- mAudioStatusView.setVisibility(View.INVISIBLE);
- CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption);
- mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout);
- mSessionWorker = new TunerSessionWorker(context, channelDataManager, this);
- TunerPreferences.setTunerPreferencesChangedListener(this);
- }
-
- public boolean isReleased() {
- return mReleased;
- }
-
- @Override
- public View onCreateOverlayView() {
- return mOverlayView;
- }
-
- @Override
- public boolean onSelectTrack(int type, String trackId) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_SELECT_TRACK, type, 0, trackId);
- return false;
- }
-
- @Override
- public void onSetCaptionEnabled(boolean enabled) {
- mSessionWorker.setCaptionEnabled(enabled);
- }
-
- @Override
- public void onSetStreamVolume(float volume) {
- mSessionWorker.setStreamVolume(volume);
- }
-
- @Override
- public boolean onSetSurface(Surface surface) {
- mSessionWorker.setSurface(surface);
- return true;
- }
-
- @Override
- public void onTimeShiftPause() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_PAUSE);
- mPlayPaused = true;
- }
-
- @Override
- public void onTimeShiftResume() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_RESUME);
- mPlayPaused = false;
- }
-
- @Override
- public void onTimeShiftSeekTo(long timeMs) {
- if (DEBUG) Log.d(TAG, "Timeshift seekTo requested position: " + timeMs / 1000);
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TIMESHIFT_SEEK_TO,
- mPlayPaused ? 1 : 0, 0, timeMs);
- }
-
- @Override
- public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
- mSessionWorker.sendMessage(
- TunerSessionWorker.MSG_TIMESHIFT_SET_PLAYBACKPARAMS, params);
- }
-
- @Override
- public long onTimeShiftGetStartPosition() {
- return mSessionWorker.getStartPosition();
- }
-
- @Override
- public long onTimeShiftGetCurrentPosition() {
- return mSessionWorker.getCurrentPosition();
- }
-
- @Override
- public boolean onTune(Uri channelUri) {
- if (DEBUG) {
- Log.d(TAG, "onTune to " + channelUri != null ? channelUri.toString() : "");
- }
- if (channelUri == null) {
- Log.w(TAG, "onTune() is failed due to null channelUri.");
- mSessionWorker.stopTune();
- return false;
- }
- mTuneStartTimestamp = SystemClock.elapsedRealtime();
- mSessionWorker.tune(channelUri);
- mPlayPaused = false;
- return true;
- }
-
- @TargetApi(Build.VERSION_CODES.N)
- @Override
- public void onTimeShiftPlay(Uri recordUri) {
- if (recordUri == null) {
- Log.w(TAG, "onTimeShiftPlay() is failed due to null channelUri.");
- mSessionWorker.stopTune();
- return;
- }
- mTuneStartTimestamp = SystemClock.elapsedRealtime();
- mSessionWorker.tune(recordUri);
- mPlayPaused = false;
- }
-
- @Override
- public void onUnblockContent(TvContentRating unblockedRating) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_UNBLOCKED_RATING,
- unblockedRating);
- }
-
- @Override
- public void onRelease() {
- if (DEBUG) {
- Log.d(TAG, "onRelease");
- }
- mReleased = true;
- mSessionWorker.release();
- mUiHandler.removeCallbacksAndMessages(null);
- TunerPreferences.setTunerPreferencesChangedListener(null);
- }
-
- /**
- * Sets {@link AudioCapabilities}.
- */
- public void setAudioCapabilities(AudioCapabilities audioCapabilities) {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_AUDIO_CAPABILITIES_CHANGED,
- audioCapabilities);
- }
-
- @Override
- public void notifyVideoAvailable() {
- super.notifyVideoAvailable();
- if (mTuneStartTimestamp != 0) {
- Log.i(TAG, "[Profiler] Video available in "
- + (SystemClock.elapsedRealtime() - mTuneStartTimestamp) + " ms");
- mTuneStartTimestamp = 0;
- }
- }
-
- @Override
- public void notifyVideoUnavailable(int reason) {
- super.notifyVideoUnavailable(reason);
- if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
- && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL) {
- notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- }
- }
-
- public void sendUiMessage(int message) {
- mUiHandler.sendEmptyMessage(message);
- }
-
- public void sendUiMessage(int message, Object object) {
- mUiHandler.obtainMessage(message, object).sendToTarget();
- }
-
- public void sendUiMessage(int message, int arg1, int arg2, Object object) {
- mUiHandler.obtainMessage(message, arg1, arg2, object).sendToTarget();
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UI_SHOW_MESSAGE: {
- mMessageView.setText((String) msg.obj);
- mMessageLayout.setVisibility(View.VISIBLE);
- return true;
- }
- case MSG_UI_HIDE_MESSAGE: {
- mMessageLayout.setVisibility(View.INVISIBLE);
- return true;
- }
- case MSG_UI_SHOW_AUDIO_UNPLAYABLE: {
- // Showing message of enabling surround sound only when global surround sound
- // setting is "never".
- final int value = GlobalSettingsUtils.getEncodedSurroundOutputSettings(mContext);
- if (value == GlobalSettingsUtils.ENCODED_SURROUND_OUTPUT_NEVER) {
- mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
- mContext.getString(R.string.ut_surround_sound_disabled))));
- } else {
- mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML(
- mContext.getString(R.string.audio_passthrough_not_supported))));
- }
- mAudioStatusView.setVisibility(View.VISIBLE);
- return true;
- }
- case MSG_UI_HIDE_AUDIO_UNPLAYABLE: {
- mAudioStatusView.setVisibility(View.INVISIBLE);
- return true;
- }
- case MSG_UI_PROCESS_CAPTION_TRACK: {
- mCaptionTrackRenderer.processCaptionEvent((CaptionEvent) msg.obj);
- return true;
- }
- case MSG_UI_START_CAPTION_TRACK: {
- mCaptionTrackRenderer.start((AtscCaptionTrack) msg.obj);
- return true;
- }
- case MSG_UI_STOP_CAPTION_TRACK: {
- mCaptionTrackRenderer.stop();
- return true;
- }
- case MSG_UI_RESET_CAPTION_TRACK: {
- mCaptionTrackRenderer.reset();
- return true;
- }
- case MSG_UI_CLEAR_CAPTION_RENDERER: {
- mCaptionTrackRenderer.clear();
- return true;
- }
- case MSG_UI_SET_STATUS_TEXT: {
- mStatusView.setText((CharSequence) msg.obj);
- return true;
- }
- case MSG_UI_TOAST_RESCAN_NEEDED: {
- Toast.makeText(mContext, R.string.ut_rescan_needed, Toast.LENGTH_LONG).show();
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void onTunerPreferencesChanged() {
- mSessionWorker.sendMessage(TunerSessionWorker.MSG_TUNER_PREFERENCES_CHANGED);
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
deleted file mode 100644
index e7eb017e..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java
+++ /dev/null
@@ -1,1754 +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.tvinput;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.MediaFormat;
-import android.media.PlaybackParams;
-import android.media.tv.TvContentRating;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputManager;
-import android.media.tv.TvTrackInfo;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.SystemClock;
-import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.Surface;
-import android.view.accessibility.CaptioningManager;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.ExoPlayer;
-import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvContentRatingCache;
-import com.android.tv.customization.TvCustomizationManager;
-import com.android.tv.customization.TvCustomizationManager.TRICKPLAY_MODE;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.TunerPreferences.TrickplaySetting;
-import com.android.tv.tuner.data.Cea708Data;
-import com.android.tv.tuner.data.PsipData.EitItem;
-import com.android.tv.tuner.data.PsipData.TvTracksInterface;
-import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.data.nano.Channel;
-import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
-import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
-import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager.StorageManager;
-import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer;
-import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
-import com.android.tv.tuner.exoplayer.ffmpeg.FfmpegDecoderClient;
-import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.util.StatusTextUtils;
-import com.android.tv.tuner.util.SystemPropertiesProxy;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs
- * such as handling {@link ExoPlayer}, managing a tuner device, trickplay, and so on.
- */
-@WorkerThread
-public class TunerSessionWorker implements PlaybackBufferListener,
- MpegTsPlayer.VideoEventListener, MpegTsPlayer.Listener, EventDetector.EventListener,
- ChannelDataManager.ProgramInfoListener, Handler.Callback {
- private static final String TAG = "TunerSessionWorker";
- private static final boolean DEBUG = false;
- private static final boolean ENABLE_PROFILER = true;
- private static final String PLAY_FROM_CHANNEL = "channel";
- private static final String MAX_BUFFER_SIZE_KEY = "tv.tuner.buffersize_mbytes";
- private static final int MAX_BUFFER_SIZE_DEF = 2 * 1024; // 2GB
- private static final int MIN_BUFFER_SIZE_DEF = 256; // 256MB
-
- // Public messages
- public static final int MSG_SELECT_TRACK = 1;
- public static final int MSG_UPDATE_CAPTION_TRACK = 2;
- public static final int MSG_SET_STREAM_VOLUME = 3;
- public static final int MSG_TIMESHIFT_PAUSE = 4;
- public static final int MSG_TIMESHIFT_RESUME = 5;
- public static final int MSG_TIMESHIFT_SEEK_TO = 6;
- public static final int MSG_TIMESHIFT_SET_PLAYBACKPARAMS = 7;
- public static final int MSG_AUDIO_CAPABILITIES_CHANGED = 8;
- public static final int MSG_UNBLOCKED_RATING = 9;
- public static final int MSG_TUNER_PREFERENCES_CHANGED = 10;
-
- // Private messages
- private static final int MSG_TUNE = 1000;
- private static final int MSG_RELEASE = 1001;
- private static final int MSG_RETRY_PLAYBACK = 1002;
- private static final int MSG_START_PLAYBACK = 1003;
- private static final int MSG_UPDATE_PROGRAM = 1008;
- private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009;
- private static final int MSG_UPDATE_CHANNEL_INFO = 1010;
- private static final int MSG_TRICKPLAY_BY_SEEK = 1011;
- private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012;
- private static final int MSG_PARENTAL_CONTROLS = 1015;
- private static final int MSG_RESCHEDULE_PROGRAMS = 1016;
- private static final int MSG_BUFFER_START_TIME_CHANGED = 1017;
- private static final int MSG_CHECK_SIGNAL = 1018;
- private static final int MSG_DISCOVER_CAPTION_SERVICE_NUMBER = 1019;
- private static final int MSG_RESET_PLAYBACK = 1020;
- private static final int MSG_BUFFER_STATE_CHANGED = 1021;
- private static final int MSG_PROGRAM_DATA_RESULT = 1022;
- private static final int MSG_STOP_TUNE = 1023;
- private static final int MSG_SET_SURFACE = 1024;
- private static final int MSG_NOTIFY_AUDIO_TRACK_UPDATED = 1025;
-
- private static final int TS_PACKET_SIZE = 188;
- private static final int CHECK_NO_SIGNAL_INITIAL_DELAY_MS = 4000;
- private static final int CHECK_NO_SIGNAL_PERIOD_MS = 500;
- private static final int RECOVER_STOPPED_PLAYBACK_PERIOD_MS = 2500;
- private static final int PARENTAL_CONTROLS_INTERVAL_MS = 5000;
- private static final int RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS = 4000;
- private static final int RESCHEDULE_PROGRAMS_INTERVAL_MS = 10000;
- private static final int RESCHEDULE_PROGRAMS_TOLERANCE_MS = 2000;
- // The following 3s is defined empirically. This should be larger than 2s considering video
- // key frame interval in the TS stream.
- private static final int PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS = 3000;
- private static final int PLAYBACK_RETRY_DELAY_MS = 5000;
- private static final int MAX_IMMEDIATE_RETRY_COUNT = 5;
- private static final long INVALID_TIME = -1;
-
- // Some examples of the track ids of the audio tracks, "a0", "a1", "a2".
- // The number after prefix is being used for indicating a index of the given audio track.
- private static final String AUDIO_TRACK_PREFIX = "a";
-
- // Some examples of the tracks id of the caption tracks, "s1", "s2", "s3".
- // The number after prefix is being used for indicating a index of a caption service number
- // of the given caption track.
- private static final String SUBTITLE_TRACK_PREFIX = "s";
- private static final int TRACK_PREFIX_SIZE = 1;
- private static final String VIDEO_TRACK_ID = "v";
- private static final long BUFFER_UNDERFLOW_BUFFER_MS = 5000;
-
- // Actual interval would be divided by the speed.
- private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500;
- private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20;
- private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250;
- private static final int RELEASE_WAIT_INTERVAL_MS = 50;
- private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14);
-
- // Since release() is done asynchronously, synchronization between multiple TunerSessionWorker
- // creation/release is required.
- // This is used to guarantee that at most one active TunerSessionWorker exists at any give time.
- private static Semaphore sActiveSessionSemaphore = new Semaphore(1);
-
- private final Context mContext;
- private final ChannelDataManager mChannelDataManager;
- private final TsDataSourceManager mSourceManager;
- private final int mMaxTrickplayBufferSizeMb;
- private final File mTrickplayBufferDir;
- private final @TRICKPLAY_MODE int mTrickplayModeCustomization;
- private volatile Surface mSurface;
- private volatile float mVolume = 1.0f;
- private volatile boolean mCaptionEnabled;
- private volatile MpegTsPlayer mPlayer;
- private volatile TunerChannel mChannel;
- private volatile Long mRecordingDuration;
- private volatile long mRecordStartTimeMs;
- private volatile long mBufferStartTimeMs;
- private volatile boolean mTrickplayDisabledByStorageIssue;
- private @TrickplaySetting int mTrickplaySetting;
- private long mTrickplayExpiredMs;
- private String mRecordingId;
- private final Handler mHandler;
- private int mRetryCount;
- private final ArrayList<TvTrackInfo> mTvTracks;
- private final SparseArray<AtscAudioTrack> mAudioTrackMap;
- private final SparseArray<AtscCaptionTrack> mCaptionTrackMap;
- private AtscCaptionTrack mCaptionTrack;
- private PlaybackParams mPlaybackParams = new PlaybackParams();
- private boolean mPlayerStarted = false;
- private boolean mReportedDrawnToSurface = false;
- private boolean mReportedWeakSignal = false;
- private EitItem mProgram;
- private List<EitItem> mPrograms;
- private final TvInputManager mTvInputManager;
- private boolean mChannelBlocked;
- private TvContentRating mUnblockedContentRating;
- private long mLastPositionMs;
- private AudioCapabilities mAudioCapabilities;
- private long mLastLimitInBytes;
- private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance();
- private final TunerSession mSession;
- private final boolean mHasSoftwareAudioDecoder;
- private int mPlayerState = ExoPlayer.STATE_IDLE;
- private long mPreparingStartTimeMs;
- private long mBufferingStartTimeMs;
- private long mReadyStartTimeMs;
- private boolean mIsActiveSession;
- private boolean mReleaseRequested; // Guarded by mReleaseLock
- private final Object mReleaseLock = new Object();
-
- public TunerSessionWorker(Context context, ChannelDataManager channelDataManager,
- TunerSession tunerSession) {
- if (DEBUG) Log.d(TAG, "TunerSessionWorker created");
- mContext = context;
-
- // HandlerThread should be set up before it is registered as a listener in the all other
- // components.
- HandlerThread handlerThread = new HandlerThread(TAG);
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper(), this);
- mSession = tunerSession;
- mChannelDataManager = channelDataManager;
- mChannelDataManager.setListener(this);
- mChannelDataManager.checkDataVersion(mContext);
- mSourceManager = TsDataSourceManager.createSourceManager(false);
- mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
- mTvTracks = new ArrayList<>();
- mAudioTrackMap = new SparseArray<>();
- mCaptionTrackMap = new SparseArray<>();
- CaptioningManager captioningManager =
- (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
- mCaptionEnabled = captioningManager.isEnabled();
- mPlaybackParams.setSpeed(1.0f);
- mMaxTrickplayBufferSizeMb =
- SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF);
- mTrickplayModeCustomization = TvCustomizationManager.getTrickplayMode(context);
- if (mTrickplayModeCustomization ==
- TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
- boolean useExternalStorage =
- Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
- Environment.isExternalStorageRemovable();
- mTrickplayBufferDir = useExternalStorage ? context.getExternalCacheDir() : null;
- } else if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
- mTrickplayBufferDir = context.getCacheDir();
- } else {
- mTrickplayBufferDir = null;
- }
- mTrickplayDisabledByStorageIssue = mTrickplayBufferDir == null;
- mTrickplaySetting = TunerPreferences.getTrickplaySetting(context);
- if (mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_NOT_SET
- && mTrickplayModeCustomization
- == TvCustomizationManager.TRICKPLAY_MODE_USE_EXTERNAL_STORAGE) {
- // Consider the case of Customization package updates the value of trickplay mode
- // to TRICKPLAY_MODE_USE_EXTERNAL_STORAGE after install.
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_NOT_SET;
- TunerPreferences.setTrickplaySetting(context, mTrickplaySetting);
- TunerPreferences.setTrickplayExpiredMs(context, 0);
- }
- mTrickplayExpiredMs = TunerPreferences.getTrickplayExpiredMs(context);
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- mReadyStartTimeMs = INVALID_TIME;
- // NOTE: We assume that TunerSessionWorker instance will be at most one.
- // Only one TunerSessionWorker can be connected to FfmpegDecoderClient at any given time.
- // connect() will return false, if there is a connected TunerSessionWorker already.
- mHasSoftwareAudioDecoder = FfmpegDecoderClient.connect(context);
- }
-
- // Public methods
- @MainThread
- public void tune(Uri channelUri) {
- mHandler.removeCallbacksAndMessages(null);
- mSourceManager.setHasPendingTune();
- sendMessage(MSG_TUNE, channelUri);
- }
-
- @MainThread
- public void stopTune() {
- mHandler.removeCallbacksAndMessages(null);
- sendMessage(MSG_STOP_TUNE);
- }
-
- /**
- * Sets {@link Surface}.
- */
- @MainThread
- public void setSurface(Surface surface) {
- if (surface != null && !surface.isValid()) {
- Log.w(TAG, "Ignoring invalid surface.");
- return;
- }
- // mSurface is kept even when tune is called right after. But, messages can be deleted by
- // tune or updateChannelBlockStatus. So mSurface should be stored here, not through message.
- mSurface = surface;
- mHandler.sendEmptyMessage(MSG_SET_SURFACE);
- }
-
- /**
- * Sets volume.
- */
- @MainThread
- public void setStreamVolume(float volume) {
- // mVolume is kept even when tune is called right after. But, messages can be deleted by
- // tune or updateChannelBlockStatus. So mVolume is stored here and mPlayer.setVolume will be
- // called in MSG_SET_STREAM_VOLUME.
- mVolume = volume;
- mHandler.sendEmptyMessage(MSG_SET_STREAM_VOLUME);
- }
-
- /**
- * Sets if caption is enabled or disabled.
- */
- @MainThread
- public void setCaptionEnabled(boolean captionEnabled) {
- // mCaptionEnabled is kept even when tune is called right after. But, messages can be
- // deleted by tune or updateChannelBlockStatus. So mCaptionEnabled is stored here and
- // start/stopCaptionTrack will be called in MSG_UPDATE_CAPTION_STATUS.
- mCaptionEnabled = captionEnabled;
- mHandler.sendEmptyMessage(MSG_UPDATE_CAPTION_TRACK);
- }
-
- public TunerChannel getCurrentChannel() {
- return mChannel;
- }
-
- @MainThread
- public long getStartPosition() {
- return mBufferStartTimeMs;
- }
-
-
- private String getRecordingPath() {
- return Uri.parse(mRecordingId).getPath();
- }
-
- private Long getDurationForRecording(String recordingId) {
- DvrStorageManager storageManager =
- new DvrStorageManager(new File(getRecordingPath()), false);
- List<BufferManager.TrackFormat> trackFormatList =
- storageManager.readTrackInfoFiles(false);
- if (trackFormatList.isEmpty()) {
- trackFormatList = storageManager.readTrackInfoFiles(true);
- }
- if (!trackFormatList.isEmpty()) {
- BufferManager.TrackFormat trackFormat = trackFormatList.get(0);
- Long durationUs = trackFormat.format.getLong(MediaFormat.KEY_DURATION);
- // we need duration by milli for trickplay notification.
- return durationUs != null ? durationUs / 1000 : null;
- }
- Log.e(TAG, "meta file for recording was not found: " + recordingId);
- return null;
- }
-
- @MainThread
- public long getCurrentPosition() {
- // TODO: More precise time may be necessary.
- MpegTsPlayer mpegTsPlayer = mPlayer;
- long currentTime = mpegTsPlayer != null
- ? mRecordStartTimeMs + mpegTsPlayer.getCurrentPosition() : mRecordStartTimeMs;
- if (mChannel == null && mPlayerState == ExoPlayer.STATE_ENDED) {
- currentTime = mRecordingDuration + mRecordStartTimeMs;
- }
- if (DEBUG) {
- long systemCurrentTime = System.currentTimeMillis();
- Log.d(TAG, "currentTime = " + currentTime
- + " ; System.currentTimeMillis() = " + systemCurrentTime
- + " ; diff = " + (currentTime - systemCurrentTime));
- }
- return currentTime;
- }
-
- @AnyThread
- public void sendMessage(int messageType) {
- mHandler.sendEmptyMessage(messageType);
- }
-
- @AnyThread
- public void sendMessage(int messageType, Object object) {
- mHandler.obtainMessage(messageType, object).sendToTarget();
- }
-
- @AnyThread
- public void sendMessage(int messageType, int arg1, int arg2, Object object) {
- mHandler.obtainMessage(messageType, arg1, arg2, object).sendToTarget();
- }
-
- @MainThread
- public void release() {
- if (DEBUG) Log.d(TAG, "release()");
- synchronized (mReleaseLock) {
- mReleaseRequested = true;
- }
- if (mHasSoftwareAudioDecoder) {
- FfmpegDecoderClient.disconnect(mContext);
- }
- mChannelDataManager.setListener(null);
- mHandler.removeCallbacksAndMessages(null);
- mHandler.sendEmptyMessage(MSG_RELEASE);
- }
-
- // MpegTsPlayer.Listener
- // Called in the same thread as mHandler.
- @Override
- public void onStateChanged(boolean playWhenReady, int playbackState) {
- if (DEBUG) Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady);
- if (playbackState == mPlayerState) {
- return;
- }
- mReadyStartTimeMs = INVALID_TIME;
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- if (playbackState == ExoPlayer.STATE_READY) {
- if (DEBUG) Log.d(TAG, "ExoPlayer ready");
- if (!mPlayerStarted) {
- sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer));
- }
- mReadyStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_PREPARING) {
- mPreparingStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_BUFFERING) {
- mBufferingStartTimeMs = SystemClock.elapsedRealtime();
- } else if (playbackState == ExoPlayer.STATE_ENDED) {
- // Final status
- // notification of STATE_ENDED from MpegTsPlayer will be ignored afterwards.
- Log.i(TAG, "Player ended: end of stream");
- if (mChannel != null) {
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- }
- }
- mPlayerState = playbackState;
- }
-
- @Override
- public void onError(Exception e) {
- if (TunerPreferences.getStoreTsStream(mContext)) {
- // Crash intentionally to capture the error causing TS file.
- Log.e(TAG, "Crash intentionally to capture the error causing TS file. "
- + e.getMessage());
- SoftPreconditions.checkState(false);
- }
- // There maybe some errors that finally raise ExoPlaybackException and will be handled here.
- // If we are playing live stream, retrying playback maybe helpful. But for recorded stream,
- // retrying playback is not helpful.
- if (mChannel != null) {
- mHandler.obtainMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer))
- .sendToTarget();
- }
- }
-
- @Override
- public void onVideoSizeChanged(int width, int height, float pixelWidthHeight) {
- if (mChannel != null && mChannel.hasVideo()) {
- updateVideoTrack(width, height);
- }
- if (mRecordingId != null) {
- updateVideoTrack(width, height);
- }
- }
-
- @Override
- public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
- if (mSurface != null && mPlayerStarted) {
- if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
- if (mRecordingId != null) {
- // Workaround of b/33298048: set it to 1 instead of 0.
- mBufferStartTimeMs = mRecordStartTimeMs = 1;
- } else {
- mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
- }
- notifyVideoAvailable();
- mReportedDrawnToSurface = true;
-
- // If surface is drawn successfully, it means that the playback was brought back
- // to normal and therefore, the playback recovery status will be reset through
- // setting a zero value to the retry count.
- // TODO: Consider audio only channels for detecting playback status changes to
- // be normal.
- mRetryCount = 0;
- if (mCaptionEnabled && mCaptionTrack != null) {
- startCaptionTrack();
- } else {
- stopCaptionTrack();
- }
- mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
- }
- }
-
- @Override
- public void onSmoothTrickplayForceStopped() {
- if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) {
- return;
- }
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- doTrickplayBySeek((int) mPlayer.getCurrentPosition());
- }
-
- @Override
- public void onAudioUnplayable() {
- if (mPlayer == null) {
- return;
- }
- Log.i(TAG, "AC3 audio cannot be played due to device limitation");
- mSession.sendUiMessage(
- TunerSession.MSG_UI_SHOW_AUDIO_UNPLAYABLE);
- }
-
- // MpegTsPlayer.VideoEventListener
- @Override
- public void onEmitCaptionEvent(Cea708Data.CaptionEvent event) {
- mSession.sendUiMessage(TunerSession.MSG_UI_PROCESS_CAPTION_TRACK, event);
- }
-
- @Override
- public void onClearCaptionEvent() {
- mSession.sendUiMessage(TunerSession.MSG_UI_CLEAR_CAPTION_RENDERER);
- }
-
- @Override
- public void onDiscoverCaptionServiceNumber(int serviceNumber) {
- sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber);
- }
-
- // ChannelDataManager.ProgramInfoListener
- @Override
- public void onProgramsArrived(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(channel, programs));
- }
-
- @Override
- public void onChannelArrived(TunerChannel channel) {
- sendMessage(MSG_UPDATE_CHANNEL_INFO, channel);
- }
-
- @Override
- public void onRescanNeeded() {
- mSession.sendUiMessage(TunerSession.MSG_UI_TOAST_RESCAN_NEEDED);
- }
-
- @Override
- public void onRequestProgramsResponse(TunerChannel channel, List<EitItem> programs) {
- sendMessage(MSG_PROGRAM_DATA_RESULT, new Pair<>(channel, programs));
- }
-
- // PlaybackBufferListener
- @Override
- public void onBufferStartTimeChanged(long startTimeMs) {
- sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs);
- }
-
- @Override
- public void onBufferStateChanged(boolean available) {
- sendMessage(MSG_BUFFER_STATE_CHANGED, available);
- }
-
- @Override
- public void onDiskTooSlow() {
- mTrickplayDisabledByStorageIssue = true;
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- }
-
- // EventDetector.EventListener
- @Override
- public void onChannelDetected(TunerChannel channel, boolean channelArrivedAtFirstTime) {
- mChannelDataManager.notifyChannelDetected(channel, channelArrivedAtFirstTime);
- }
-
- @Override
- public void onEventDetected(TunerChannel channel, List<EitItem> items) {
- mChannelDataManager.notifyEventDetected(channel, items);
- }
-
- @Override
- public void onChannelScanDone() {
- // do nothing.
- }
-
- private long parseChannel(Uri uri) {
- try {
- List<String> paths = uri.getPathSegments();
- if (paths.size() > 1 && paths.get(0).equals(PLAY_FROM_CHANNEL)) {
- return ContentUris.parseId(uri);
- }
- } catch (UnsupportedOperationException | NumberFormatException e) {
- }
- return -1;
- }
-
- private static class RecordedProgram {
- private final long mChannelId;
- private final String mDataUri;
-
- private static final String[] PROJECTION = {
- TvContract.Programs.COLUMN_CHANNEL_ID,
- TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI,
- };
-
- public RecordedProgram(Cursor cursor) {
- int index = 0;
- mChannelId = cursor.getLong(index++);
- mDataUri = cursor.getString(index++);
- }
-
- public RecordedProgram(long channelId, String dataUri) {
- mChannelId = channelId;
- mDataUri = dataUri;
- }
-
- public static RecordedProgram onQuery(Cursor c) {
- RecordedProgram recording = null;
- if (c != null && c.moveToNext()) {
- recording = new RecordedProgram(c);
- }
- return recording;
- }
-
- public String getDataUri() {
- return mDataUri;
- }
- }
-
- private RecordedProgram getRecordedProgram(Uri recordedUri) {
- ContentResolver resolver = mContext.getContentResolver();
- try(Cursor c = resolver.query(recordedUri, RecordedProgram.PROJECTION, null, null, null)) {
- if (c != null) {
- RecordedProgram result = RecordedProgram.onQuery(c);
- if (DEBUG) {
- Log.d(TAG, "Finished query for " + this);
- }
- return result;
- } else {
- if (c == null) {
- Log.e(TAG, "Unknown query error for " + this);
- } else {
- if (DEBUG) Log.d(TAG, "Canceled query for " + this);
- }
- return null;
- }
- }
- }
-
- private String parseRecording(Uri uri) {
- RecordedProgram recording = getRecordedProgram(uri);
- if (recording != null) {
- return recording.getDataUri();
- }
- return null;
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_TUNE: {
- if (DEBUG) Log.d(TAG, "MSG_TUNE");
-
- // When sequential tuning messages arrived, it skips middle tuning messages in order
- // to change to the last requested channel quickly.
- if (mHandler.hasMessages(MSG_TUNE)) {
- return true;
- }
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
- if (!mIsActiveSession) {
- // Wait until release is finished if there is a pending release.
- try {
- while (!sActiveSessionSemaphore.tryAcquire(
- RELEASE_WAIT_INTERVAL_MS, TimeUnit.MILLISECONDS)) {
- synchronized (mReleaseLock) {
- if (mReleaseRequested) {
- return true;
- }
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- synchronized (mReleaseLock) {
- if (mReleaseRequested) {
- sActiveSessionSemaphore.release();
- return true;
- }
- }
- mIsActiveSession = true;
- }
- Uri channelUri = (Uri) msg.obj;
- String recording = null;
- long channelId = parseChannel(channelUri);
- TunerChannel channel = (channelId == -1) ? null
- : mChannelDataManager.getChannel(channelId);
- if (channelId == -1) {
- recording = parseRecording(channelUri);
- }
- if (channel == null && recording == null) {
- Log.w(TAG, "onTune() is failed. Can't find channel for " + channelUri);
- stopTune();
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return true;
- }
- clearCallbacksAndMessagesSafely();
- mChannelDataManager.removeAllCallbacksAndMessages();
- if (channel != null) {
- mChannelDataManager.requestProgramsData(channel);
- }
- prepareTune(channel, recording);
- // TODO: Need to refactor. notifyContentAllowed() should not be called if parental
- // control is turned on.
- mSession.notifyContentAllowed();
- resetTvTracks();
- resetPlayback();
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
- return true;
- }
- case MSG_STOP_TUNE: {
- if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE");
- mChannel = null;
- stopPlayback(true);
- stopCaptionTrack();
- resetTvTracks();
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return true;
- }
- case MSG_RELEASE: {
- if (DEBUG) Log.d(TAG, "MSG_RELEASE");
- mHandler.removeCallbacksAndMessages(null);
- stopPlayback(true);
- stopCaptionTrack();
- mSourceManager.release();
- mHandler.getLooper().quitSafely();
- if (mIsActiveSession) {
- sActiveSessionSemaphore.release();
- }
- return true;
- }
- case MSG_RETRY_PLAYBACK: {
- if (System.identityHashCode(mPlayer) == (int) msg.obj) {
- Log.i(TAG, "Retrying the playback for channel: " + mChannel);
- mHandler.removeMessages(MSG_RETRY_PLAYBACK);
- // When there is a request of retrying playback, don't reuse TunerHal.
- mSourceManager.setKeepTuneStatus(false);
- mRetryCount++;
- if (DEBUG) {
- Log.d(TAG, "MSG_RETRY_PLAYBACK " + mRetryCount);
- }
- mChannelDataManager.removeAllCallbacksAndMessages();
- if (mRetryCount <= MAX_IMMEDIATE_RETRY_COUNT) {
- resetPlayback();
- } else {
- // When it reaches this point, it may be due to an error that occurred in
- // the tuner device. Calling stopPlayback() resets the tuner device
- // to recover from the error.
- stopPlayback(false);
- stopCaptionTrack();
-
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal since fail to retry playback");
-
- // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically chosen
- // value before recovering the playback.
- mHandler.sendEmptyMessageDelayed(MSG_RESET_PLAYBACK,
- RECOVER_STOPPED_PLAYBACK_PERIOD_MS);
- }
- }
- return true;
- }
- case MSG_RESET_PLAYBACK: {
- if (DEBUG) Log.d(TAG, "MSG_RESET_PLAYBACK");
- mChannelDataManager.removeAllCallbacksAndMessages();
- resetPlayback();
- return true;
- }
- case MSG_START_PLAYBACK: {
- if (DEBUG) Log.d(TAG, "MSG_START_PLAYBACK");
- if (mChannel != null || mRecordingId != null) {
- startPlayback((int) msg.obj);
- }
- return true;
- }
- case MSG_UPDATE_PROGRAM: {
- if (mChannel != null) {
- EitItem program = (EitItem) msg.obj;
- updateTvTracks(program, false);
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- }
- return true;
- }
- case MSG_SCHEDULE_OF_PROGRAMS: {
- mHandler.removeMessages(MSG_UPDATE_PROGRAM);
- Pair<TunerChannel, List<EitItem>> pair =
- (Pair<TunerChannel, List<EitItem>>) msg.obj;
- TunerChannel channel = pair.first;
- if (mChannel == null) {
- return true;
- }
- if (mChannel != null && mChannel.compareTo(channel) != 0) {
- return true;
- }
- mPrograms = pair.second;
- EitItem currentProgram = getCurrentProgram();
- if (currentProgram == null) {
- mProgram = null;
- }
- long currentTimeMs = getCurrentPosition();
- if (mPrograms != null) {
- for (EitItem item : mPrograms) {
- if (currentProgram != null && currentProgram.compareTo(item) == 0) {
- if (DEBUG) {
- Log.d(TAG, "Update current TvTracks " + item);
- }
- if (mProgram != null && mProgram.compareTo(item) == 0) {
- continue;
- }
- mProgram = item;
- updateTvTracks(item, false);
- } else if (item.getStartTimeUtcMillis() > currentTimeMs) {
- if (DEBUG) {
- Log.d(TAG, "Update next TvTracks " + item + " "
- + (item.getStartTimeUtcMillis() - currentTimeMs));
- }
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_UPDATE_PROGRAM, item),
- item.getStartTimeUtcMillis() - currentTimeMs);
- }
- }
- }
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- return true;
- }
- case MSG_UPDATE_CHANNEL_INFO: {
- TunerChannel channel = (TunerChannel) msg.obj;
- if (mChannel != null && mChannel.compareTo(channel) == 0) {
- updateChannelInfo(channel);
- }
- return true;
- }
- case MSG_PROGRAM_DATA_RESULT: {
- TunerChannel channel = (TunerChannel) ((Pair) msg.obj).first;
-
- // If there already exists, skip it since real-time data is a top priority,
- if (mChannel != null && mChannel.compareTo(channel) == 0
- && mPrograms == null && mProgram == null) {
- sendMessage(MSG_SCHEDULE_OF_PROGRAMS, msg.obj);
- }
- return true;
- }
- case MSG_TRICKPLAY_BY_SEEK: {
- if (mPlayer == null) {
- return true;
- }
- doTrickplayBySeek(msg.arg1);
- return true;
- }
- case MSG_SMOOTH_TRICKPLAY_MONITOR: {
- if (mPlayer == null) {
- return true;
- }
- long systemCurrentTime = System.currentTimeMillis();
- long position = getCurrentPosition();
- if (mRecordingId == null) {
- // Checks if the position exceeds the upper bound when forwarding,
- // or exceed the lower bound when rewinding.
- // If the direction is not checked, there can be some issues.
- // (See b/29939781 for more details.)
- if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L)
- || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) {
- doTimeShiftResume();
- return true;
- }
- } else {
- if (position > mRecordingDuration || position < 0) {
- doTimeShiftPause();
- return true;
- }
- }
- mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
- TRICKPLAY_MONITOR_INTERVAL_MS);
- return true;
- }
- case MSG_RESCHEDULE_PROGRAMS: {
- if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) {
- mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS);
- } else {
- doReschedulePrograms();
- }
- return true;
- }
- case MSG_PARENTAL_CONTROLS: {
- doParentalControls();
- mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
- PARENTAL_CONTROLS_INTERVAL_MS);
- return true;
- }
- case MSG_UNBLOCKED_RATING: {
- mUnblockedContentRating = (TvContentRating) msg.obj;
- doParentalControls();
- mHandler.removeMessages(MSG_PARENTAL_CONTROLS);
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS,
- PARENTAL_CONTROLS_INTERVAL_MS);
- return true;
- }
- case MSG_DISCOVER_CAPTION_SERVICE_NUMBER: {
- int serviceNumber = (int) msg.obj;
- doDiscoverCaptionServiceNumber(serviceNumber);
- return true;
- }
- case MSG_SELECT_TRACK: {
- if (mChannel != null || mRecordingId != null) {
- doSelectTrack(msg.arg1, (String) msg.obj);
- }
- return true;
- }
- case MSG_UPDATE_CAPTION_TRACK: {
- if (mCaptionEnabled) {
- startCaptionTrack();
- } else {
- stopCaptionTrack();
- }
- return true;
- }
- case MSG_TIMESHIFT_PAUSE: {
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_PAUSE");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftPause();
- return true;
- }
- case MSG_TIMESHIFT_RESUME: {
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_RESUME");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftResume();
- return true;
- }
- case MSG_TIMESHIFT_SEEK_TO: {
- long position = (long) msg.obj;
- if (DEBUG) Log.d(TAG, "MSG_TIMESHIFT_SEEK_TO (position=" + position + ")");
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftSeekTo(position);
- return true;
- }
- case MSG_TIMESHIFT_SET_PLAYBACKPARAMS: {
- if (mPlayer == null) {
- return true;
- }
- setTrickplayEnabledIfNeeded();
- doTimeShiftSetPlaybackParams((PlaybackParams) msg.obj);
- return true;
- }
- case MSG_AUDIO_CAPABILITIES_CHANGED: {
- AudioCapabilities capabilities = (AudioCapabilities) msg.obj;
- if (DEBUG) {
- Log.d(TAG, "MSG_AUDIO_CAPABILITIES_CHANGED " + capabilities);
- }
- if (capabilities == null) {
- return true;
- }
- if (!capabilities.equals(mAudioCapabilities)) {
- // HDMI supported encodings are changed. restart player.
- mAudioCapabilities = capabilities;
- resetPlayback();
- }
- return true;
- }
- case MSG_SET_STREAM_VOLUME: {
- if (mPlayer != null && mPlayer.isPlaying()) {
- mPlayer.setVolume(mVolume);
- }
- return true;
- }
- case MSG_TUNER_PREFERENCES_CHANGED: {
- mHandler.removeMessages(MSG_TUNER_PREFERENCES_CHANGED);
- @TrickplaySetting int trickplaySetting =
- TunerPreferences.getTrickplaySetting(mContext);
- if (trickplaySetting != mTrickplaySetting) {
- boolean wasTrcikplayEnabled =
- mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- boolean isTrickplayEnabled =
- trickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- mTrickplaySetting = trickplaySetting;
- if (isTrickplayEnabled != wasTrcikplayEnabled) {
- sendMessage(MSG_RESET_PLAYBACK, System.identityHashCode(mPlayer));
- }
- }
- return true;
- }
- case MSG_BUFFER_START_TIME_CHANGED: {
- if (mPlayer == null) {
- return true;
- }
- mBufferStartTimeMs = (long) msg.obj;
- if (!hasEnoughBackwardBuffer()
- && (!mPlayer.isPlaying() || mPlaybackParams.getSpeed() < 1.0f)) {
- mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrackAndClosedCaption(true);
- mPlaybackParams.setSpeed(1.0f);
- }
- return true;
- }
- case MSG_BUFFER_STATE_CHANGED: {
- boolean available = (boolean) msg.obj;
- mSession.notifyTimeShiftStatusChanged(available
- ? TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
- : TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- return true;
- }
- case MSG_CHECK_SIGNAL: {
- if (mChannel == null || mPlayer == null) {
- return true;
- }
- TsDataSource source = mPlayer.getDataSource();
- long limitInBytes = source != null ? source.getBufferedPosition() : 0L;
- if (TunerDebug.ENABLED) {
- TunerDebug.calculateDiff();
- mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT,
- Html.fromHtml(
- StatusTextUtils.getStatusWarningInHTML(
- (limitInBytes - mLastLimitInBytes)
- / TS_PACKET_SIZE,
- TunerDebug.getVideoFrameDrop(),
- TunerDebug.getBytesInQueue(),
- TunerDebug.getAudioPositionUs(),
- TunerDebug.getAudioPositionUsRate(),
- TunerDebug.getAudioPtsUs(),
- TunerDebug.getAudioPtsUsRate(),
- TunerDebug.getVideoPtsUs(),
- TunerDebug.getVideoPtsUsRate()
- )));
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
- long currentTime = SystemClock.elapsedRealtime();
- long bufferingTimeMs = mBufferingStartTimeMs != INVALID_TIME
- ? currentTime - mBufferingStartTimeMs : mBufferingStartTimeMs;
- long preparingTimeMs = mPreparingStartTimeMs != INVALID_TIME
- ? currentTime - mPreparingStartTimeMs : mPreparingStartTimeMs;
- boolean isBufferingTooLong =
- bufferingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- boolean isPreparingTooLong =
- preparingTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- boolean isWeakSignal = source != null
- && mChannel.getType() != Channel.TYPE_FILE
- && (isBufferingTooLong || isPreparingTooLong);
- if (isWeakSignal && !mReportedWeakSignal) {
- if (!mHandler.hasMessages(MSG_RETRY_PLAYBACK)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
- System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
- }
- if (mPlayer != null) {
- mPlayer.setAudioTrackAndClosedCaption(false);
- }
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal due to signal check, " + String.format(
- "packetsPerSec:%d, bufferingTimeMs:%d, preparingTimeMs:%d, " +
- "videoFrameDrop:%d",
- (limitInBytes - mLastLimitInBytes) / TS_PACKET_SIZE,
- bufferingTimeMs,
- preparingTimeMs,
- TunerDebug.getVideoFrameDrop()
- ));
- } else if (!isWeakSignal && mReportedWeakSignal) {
- boolean isPlaybackStable = mReadyStartTimeMs != INVALID_TIME
- && currentTime - mReadyStartTimeMs
- > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS;
- if (!isPlaybackStable) {
- // Wait until playback becomes stable.
- } else if (mReportedDrawnToSurface) {
- mHandler.removeMessages(MSG_RETRY_PLAYBACK);
- notifyVideoAvailable();
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
- }
- mLastLimitInBytes = limitInBytes;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS);
- return true;
- }
- case MSG_SET_SURFACE: {
- if (mPlayer != null) {
- mPlayer.setSurface(mSurface);
- } else {
- // TODO: Since surface is dynamically set, we can remove the dependency of
- // playback start on mSurface nullity.
- resetPlayback();
- }
- return true;
- }
- case MSG_NOTIFY_AUDIO_TRACK_UPDATED: {
- notifyAudioTracksUpdated();
- return true;
- }
- default: {
- Log.w(TAG, "Unhandled message code: " + msg.what);
- return false;
- }
- }
- }
-
- // Private methods
- private void doSelectTrack(int type, String trackId) {
- int numTrackId = trackId != null
- ? Integer.parseInt(trackId.substring(TRACK_PREFIX_SIZE)) : -1;
- if (type == TvTrackInfo.TYPE_AUDIO) {
- if (trackId == null) {
- return;
- }
- if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) {
- mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId);
- }
- mSession.notifyTrackSelected(type, trackId);
- } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
- if (trackId == null) {
- mSession.notifyTrackSelected(type, null);
- mCaptionTrack = null;
- stopCaptionTrack();
- return;
- }
- for (TvTrackInfo track : mTvTracks) {
- if (track.getId().equals(trackId)) {
- // The service number of the caption service is used for track id of a
- // subtitle track. Passes the following track id on to TsParser.
- mSession.notifyTrackSelected(type, trackId);
- mCaptionTrack = mCaptionTrackMap.get(numTrackId);
- startCaptionTrack();
- return;
- }
- }
- }
- }
-
- private void setTrickplayEnabledIfNeeded() {
- if (mChannel == null ||
- mTrickplayModeCustomization != TvCustomizationManager.TRICKPLAY_MODE_ENABLED) {
- return;
- }
- if (mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_ENABLED;
- TunerPreferences.setTrickplaySetting(
- mContext, mTrickplaySetting);
- }
- }
-
- private MpegTsPlayer createPlayer(AudioCapabilities capabilities) {
- if (capabilities == null) {
- Log.w(TAG, "No Audio Capabilities");
- }
- long now = System.currentTimeMillis();
- if (mTrickplayModeCustomization == TvCustomizationManager.TRICKPLAY_MODE_ENABLED
- && mTrickplaySetting == TunerPreferences.TRICKPLAY_SETTING_NOT_SET) {
- if (mTrickplayExpiredMs == 0) {
- mTrickplayExpiredMs = now + TRICKPLAY_OFF_DURATION_MS;
- TunerPreferences.setTrickplayExpiredMs(mContext, mTrickplayExpiredMs);
- } else {
- if (mTrickplayExpiredMs < now) {
- mTrickplaySetting = TunerPreferences.TRICKPLAY_SETTING_DISABLED;
- TunerPreferences.setTrickplaySetting(mContext, mTrickplaySetting);
- }
- }
- }
- BufferManager bufferManager = null;
- if (mRecordingId != null) {
- StorageManager storageManager =
- new DvrStorageManager(new File(getRecordingPath()), false);
- bufferManager = new BufferManager(storageManager);
- updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles());
- } else if (!mTrickplayDisabledByStorageIssue
- && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED
- && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) {
- bufferManager = new BufferManager(new TrickplayStorageManager(mContext,
- mTrickplayBufferDir, 1024L * 1024 * mMaxTrickplayBufferSizeMb));
- } else {
- Log.w(TAG, "Trickplay is disabled.");
- }
- MpegTsPlayer player = new MpegTsPlayer(
- new MpegTsRendererBuilder(mContext, bufferManager, this),
- mHandler, mSourceManager, capabilities, this);
- Log.i(TAG, "Passthrough AC3 renderer");
- if (DEBUG) Log.d(TAG, "ExoPlayer created");
- return player;
- }
-
- private void startCaptionTrack() {
- if (mCaptionEnabled && mCaptionTrack != null) {
- mSession.sendUiMessage(
- TunerSession.MSG_UI_START_CAPTION_TRACK, mCaptionTrack);
- if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(mCaptionTrack.serviceNumber);
- }
- }
- }
-
- private void stopCaptionTrack() {
- if (mPlayer != null) {
- mPlayer.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_STOP_CAPTION_TRACK);
- }
-
- private void resetTvTracks() {
- mTvTracks.clear();
- mAudioTrackMap.clear();
- mCaptionTrackMap.clear();
- mSession.sendUiMessage(TunerSession.MSG_UI_RESET_CAPTION_TRACK);
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateTvTracks(TvTracksInterface tvTracksInterface, boolean fromPmt) {
- synchronized (tvTracksInterface) {
- if (DEBUG) {
- Log.d(TAG, "UpdateTvTracks " + tvTracksInterface);
- }
- List<AtscAudioTrack> audioTracks = tvTracksInterface.getAudioTracks();
- List<AtscCaptionTrack> captionTracks = tvTracksInterface.getCaptionTracks();
- // According to ATSC A/69 chapter 6.9, both PMT and EIT should have descriptors for audio
- // tracks, but in real world, we see some bogus audio track info in EIT, so, we trust audio
- // track info in PMT more and use info in EIT only when we have nothing.
- if (audioTracks != null && !audioTracks.isEmpty()
- && (mChannel == null || mChannel.getAudioTracks() == null || fromPmt)) {
- updateAudioTracks(audioTracks);
- }
- if (captionTracks == null || captionTracks.isEmpty()) {
- if (tvTracksInterface.hasCaptionTrack()) {
- updateCaptionTracks(captionTracks);
- }
- } else {
- updateCaptionTracks(captionTracks);
- }
- }
- }
-
- private void removeTvTracks(int trackType) {
- Iterator<TvTrackInfo> iterator = mTvTracks.iterator();
- while (iterator.hasNext()) {
- TvTrackInfo tvTrackInfo = iterator.next();
- if (tvTrackInfo.getType() == trackType) {
- iterator.remove();
- }
- }
- }
-
- private void updateVideoTrack(int width, int height) {
- removeTvTracks(TvTrackInfo.TYPE_VIDEO);
- mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID)
- .setVideoWidth(width).setVideoHeight(height).build());
- mSession.notifyTracksChanged(mTvTracks);
- mSession.notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, VIDEO_TRACK_ID);
- }
-
- private void updateAudioTracks(List<AtscAudioTrack> audioTracks) {
- if (DEBUG) {
- Log.d(TAG, "Update AudioTracks " + audioTracks);
- }
- mAudioTrackMap.clear();
- if (audioTracks != null) {
- int index = 0;
- for (AtscAudioTrack audioTrack : audioTracks) {
- audioTrack.index = index;
- mAudioTrackMap.put(index, audioTrack);
- ++index;
- }
- }
- mHandler.sendEmptyMessage(MSG_NOTIFY_AUDIO_TRACK_UPDATED);
- }
-
- private void notifyAudioTracksUpdated() {
- if (mPlayer == null) {
- // Audio tracks will be updated later once player initialization is done.
- return;
- }
- int audioTrackCount = mPlayer.getTrackCount(MpegTsPlayer.TRACK_TYPE_AUDIO);
- removeTvTracks(TvTrackInfo.TYPE_AUDIO);
- for (int i = 0; i < audioTrackCount; i++) {
- // We use language information from EIT/VCT only when the player does not provide
- // languages.
- com.google.android.exoplayer.MediaFormat infoFromPlayer =
- mPlayer.getTrackFormat(MpegTsPlayer.TRACK_TYPE_AUDIO, i);
- AtscAudioTrack infoFromEit = mAudioTrackMap.get(i);
- AtscAudioTrack infoFromVct = (mChannel != null
- && mChannel.getAudioTracks().size() == mAudioTrackMap.size()
- && i < mChannel.getAudioTracks().size())
- ? mChannel.getAudioTracks().get(i) : null;
- String language = !TextUtils.isEmpty(infoFromPlayer.language) ? infoFromPlayer.language
- : (infoFromEit != null && infoFromEit.language != null) ? infoFromEit.language
- : (infoFromVct != null && infoFromVct.language != null)
- ? infoFromVct.language : null;
- TvTrackInfo.Builder builder = new TvTrackInfo.Builder(
- TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i);
- builder.setLanguage(language);
- builder.setAudioChannelCount(infoFromPlayer.channelCount);
- builder.setAudioSampleRate(infoFromPlayer.sampleRate);
- TvTrackInfo track = builder.build();
- mTvTracks.add(track);
- }
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateCaptionTracks(List<AtscCaptionTrack> captionTracks) {
- if (DEBUG) {
- Log.d(TAG, "Update CaptionTrack " + captionTracks);
- }
- removeTvTracks(TvTrackInfo.TYPE_SUBTITLE);
- mCaptionTrackMap.clear();
- if (captionTracks != null) {
- for (AtscCaptionTrack captionTrack : captionTracks) {
- if (mCaptionTrackMap.indexOfKey(captionTrack.serviceNumber) >= 0) {
- continue;
- }
- String language = captionTrack.language;
-
- // The service number of the caption service is used for track id of a subtitle.
- // Later, when a subtitle is chosen, track id will be passed on to TsParser.
- TvTrackInfo.Builder builder =
- new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + captionTrack.serviceNumber);
- builder.setLanguage(language);
- mTvTracks.add(builder.build());
- mCaptionTrackMap.put(captionTrack.serviceNumber, captionTrack);
- }
- }
- mSession.notifyTracksChanged(mTvTracks);
- }
-
- private void updateChannelInfo(TunerChannel channel) {
- if (DEBUG) {
- Log.d(TAG, String.format("Channel Info (old) videoPid: %d audioPid: %d " +
- "audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
- mChannel.getAudioPids().size()));
- }
-
- // The list of the audio tracks resided in a channel is often changed depending on a
- // program being on the air. So, we should update the streaming PIDs and types of the
- // tuned channel according to the newly received channel data.
- int oldVideoPid = mChannel.getVideoPid();
- int oldAudioPid = mChannel.getAudioPid();
- List<Integer> audioPids = channel.getAudioPids();
- List<Integer> audioStreamTypes = channel.getAudioStreamTypes();
- int size = audioPids.size();
- mChannel.setVideoPid(channel.getVideoPid());
- mChannel.setAudioPids(audioPids);
- mChannel.setAudioStreamTypes(audioStreamTypes);
- updateTvTracks(channel, true);
- int index = audioPids.isEmpty() ? -1 : 0;
- for (int i = 0; i < size; ++i) {
- if (audioPids.get(i) == oldAudioPid) {
- index = i;
- break;
- }
- }
- mChannel.selectAudioTrack(index);
- mSession.notifyTrackSelected(TvTrackInfo.TYPE_AUDIO,
- index == -1 ? null : AUDIO_TRACK_PREFIX + index);
-
- // Reset playback if there is a change in the listening streaming PIDs.
- if (oldVideoPid != mChannel.getVideoPid()
- || oldAudioPid != mChannel.getAudioPid()) {
- // TODO: Implement a switching between tracks more smoothly.
- resetPlayback();
- }
- if (DEBUG) {
- Log.d(TAG, String.format("Channel Info (new) videoPid: %d audioPid: %d " +
- " audioSize: %d", mChannel.getVideoPid(), mChannel.getAudioPid(),
- mChannel.getAudioPids().size()));
- }
- }
-
- private void stopPlayback(boolean removeChannelDataCallbacks) {
- if (removeChannelDataCallbacks) {
- mChannelDataManager.removeAllCallbacksAndMessages();
- }
- if (mPlayer != null) {
- mPlayer.setPlayWhenReady(false);
- mPlayer.release();
- mPlayer = null;
- mPlayerState = ExoPlayer.STATE_IDLE;
- mPlaybackParams.setSpeed(1.0f);
- mPlayerStarted = false;
- mReportedDrawnToSurface = false;
- mPreparingStartTimeMs = INVALID_TIME;
- mBufferingStartTimeMs = INVALID_TIME;
- mReadyStartTimeMs = INVALID_TIME;
- mLastLimitInBytes = 0L;
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_AUDIO_UNPLAYABLE);
- mSession.notifyTimeShiftStatusChanged(TvInputManager.TIME_SHIFT_STATUS_UNAVAILABLE);
- }
- }
-
- private void startPlayback(int playerHashCode) {
- // TODO: provide hasAudio()/hasVideo() for play recordings.
- if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) {
- return;
- }
- if (mChannel != null && !mChannel.hasAudio()) {
- if (DEBUG) Log.d(TAG, "Channel " + mChannel + " does not have audio.");
- // Playbacks with video-only stream have not been tested yet.
- // No video-only channel has been found.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN);
- return;
- }
- if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio())
- || (mChannel.hasVideo() && !mPlayer.hasVideo()))
- && mChannel.getType() != Channel.TYPE_NETWORK) {
- // If the channel is from network, skip this part since the video and audio tracks
- // information for channels from network are more reliable in the extractor. Otherwise,
- // tracks haven't been detected in the extractor. Try again.
- sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
- return;
- }
- // Since mSurface is volatile, we define a local variable surface to keep the same value
- // inside this method.
- Surface surface = mSurface;
- if (surface != null && !mPlayerStarted) {
- mPlayer.setSurface(surface);
- mPlayer.setPlayWhenReady(true);
- mPlayer.setVolume(mVolume);
- if (mChannel != null && mPlayer.hasAudio() && !mPlayer.hasVideo()) {
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY);
- } else if (!mReportedWeakSignal) {
- // Doesn't show buffering during weak signal.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
- }
- mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE);
- mPlayerStarted = true;
- }
- }
-
- private void preparePlayback() {
- SoftPreconditions.checkState(mPlayer == null);
- if (mChannel == null && mRecordingId == null) {
- return;
- }
- mSourceManager.setKeepTuneStatus(true);
- MpegTsPlayer player = createPlayer(mAudioCapabilities);
- player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER);
- player.setVideoEventListener(this);
- player.setCaptionServiceNumber(mCaptionTrack != null ?
- mCaptionTrack.serviceNumber : Cea708Data.EMPTY_SERVICE_NUMBER);
- if (!player.prepare(mContext, mChannel, mHasSoftwareAudioDecoder, this)) {
- mSourceManager.setKeepTuneStatus(false);
- player.release();
- if (!mHandler.hasMessages(MSG_TUNE)) {
- // When prepare failed, there may be some errors related to hardware. In that
- // case, retry playback immediately may not help.
- notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- Log.i(TAG, "Notify weak signal due to player preparation failure");
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK,
- System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS);
- }
- } else {
- mPlayer = player;
- mPlayerStarted = false;
- mHandler.removeMessages(MSG_CHECK_SIGNAL);
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
- }
- }
-
- private void resetPlayback() {
- long timestamp, oldTimestamp;
- timestamp = SystemClock.elapsedRealtime();
- stopPlayback(false);
- stopCaptionTrack();
- if (ENABLE_PROFILER) {
- oldTimestamp = timestamp;
- timestamp = SystemClock.elapsedRealtime();
- Log.i(TAG, "[Profiler] stopPlayback() takes " + (timestamp - oldTimestamp) + " ms");
- }
- if (mChannelBlocked || mSurface == null) {
- return;
- }
- preparePlayback();
- }
-
- private void prepareTune(TunerChannel channel, String recording) {
- mChannelBlocked = false;
- mUnblockedContentRating = null;
- mRetryCount = 0;
- mChannel = channel;
- mRecordingId = recording;
- mRecordingDuration = recording != null ? getDurationForRecording(recording) : null;
- mProgram = null;
- mPrograms = null;
- if (mRecordingId != null) {
- // Workaround of b/33298048: set it to 1 instead of 0.
- mBufferStartTimeMs = mRecordStartTimeMs = 1;
- } else {
- mBufferStartTimeMs = mRecordStartTimeMs = System.currentTimeMillis();
- }
- mLastPositionMs = 0;
- mCaptionTrack = null;
- mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS);
- }
-
- private void doReschedulePrograms() {
- long currentPositionMs = getCurrentPosition();
- long forwardDifference = Math.abs(currentPositionMs - mLastPositionMs
- - RESCHEDULE_PROGRAMS_INTERVAL_MS);
- mLastPositionMs = currentPositionMs;
-
- // A gap is measured as the time difference between previous and next current position
- // periodically. If the gap has a significant difference with an interval of a period,
- // this means that there is a change of playback status and the programs of the current
- // channel should be rescheduled to new playback timeline.
- if (forwardDifference > RESCHEDULE_PROGRAMS_TOLERANCE_MS) {
- if (DEBUG) {
- Log.d(TAG, "reschedule programs size:"
- + (mPrograms != null ? mPrograms.size() : 0) + " current program: "
- + getCurrentProgram());
- }
- mHandler.obtainMessage(MSG_SCHEDULE_OF_PROGRAMS, new Pair<>(mChannel, mPrograms))
- .sendToTarget();
- }
- mHandler.removeMessages(MSG_RESCHEDULE_PROGRAMS);
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INTERVAL_MS);
- }
-
- private int getTrickPlaySeekIntervalMs() {
- return Math.max(EXPECTED_KEY_FRAME_INTERVAL_MS / (int) Math.abs(mPlaybackParams.getSpeed()),
- MIN_TRICKPLAY_SEEK_INTERVAL_MS);
- }
-
- private void doTrickplayBySeek(int seekPositionMs) {
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- if (mPlaybackParams.getSpeed() == 1.0f || !mPlayer.isPrepared()) {
- return;
- }
- if (seekPositionMs < mBufferStartTimeMs - mRecordStartTimeMs) {
- if (mPlaybackParams.getSpeed() > 1.0f) {
- // If fast forwarding, the seekPositionMs can be out of the buffered range
- // because of chuck evictions.
- seekPositionMs = (int) (mBufferStartTimeMs - mRecordStartTimeMs);
- } else {
- mPlayer.seekTo(mBufferStartTimeMs - mRecordStartTimeMs);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrackAndClosedCaption(true);
- return;
- }
- } else if (seekPositionMs > System.currentTimeMillis() - mRecordStartTimeMs) {
- // Stops trickplay when FF requested the position later than current position.
- // If RW trickplay requested the position later than current position,
- // continue trickplay.
- if (mPlaybackParams.getSpeed() > 0.0f) {
- mPlayer.seekTo(System.currentTimeMillis() - mRecordStartTimeMs);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setAudioTrackAndClosedCaption(true);
- return;
- }
- }
-
- long delayForNextSeek = getTrickPlaySeekIntervalMs();
- if (!mPlayer.isBuffering()) {
- mPlayer.seekTo(seekPositionMs);
- } else {
- delayForNextSeek = MIN_TRICKPLAY_SEEK_INTERVAL_MS;
- }
- seekPositionMs += mPlaybackParams.getSpeed() * delayForNextSeek;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_TRICKPLAY_BY_SEEK, seekPositionMs, 0), delayForNextSeek);
- }
-
- private void doTimeShiftPause() {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- if (!hasEnoughBackwardBuffer()) {
- return;
- }
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setPlayWhenReady(false);
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
-
- private void doTimeShiftResume() {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlaybackParams.setSpeed(1.0f);
- mPlayer.setPlayWhenReady(true);
- mPlayer.setAudioTrackAndClosedCaption(true);
- }
-
- private void doTimeShiftSeekTo(long timeMs) {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs));
- }
-
- private void doTimeShiftSetPlaybackParams(PlaybackParams params) {
- if (!hasEnoughBackwardBuffer() && params.getSpeed() < 1.0f) {
- return;
- }
- mPlaybackParams = params;
- float speed = mPlaybackParams.getSpeed();
- if (speed == 1.0f) {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- doTimeShiftResume();
- } else if (mPlayer.supportSmoothTrickPlay(speed)) {
- mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
- mPlayer.setAudioTrackAndClosedCaption(false);
- mPlayer.startSmoothTrickplay(mPlaybackParams);
- mHandler.sendEmptyMessageDelayed(MSG_SMOOTH_TRICKPLAY_MONITOR,
- TRICKPLAY_MONITOR_INTERVAL_MS);
- } else {
- mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
- if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) {
- mPlayer.setAudioTrackAndClosedCaption(false);
- mPlayer.setPlayWhenReady(false);
- // Initiate trickplay
- mHandler.sendMessage(mHandler.obtainMessage(MSG_TRICKPLAY_BY_SEEK,
- (int) (mPlayer.getCurrentPosition()
- + speed * getTrickPlaySeekIntervalMs()), 0));
- }
- }
- }
-
- private EitItem getCurrentProgram() {
- if (mPrograms == null || mPrograms.isEmpty()) {
- return null;
- }
- if (mChannel.getType() == Channel.TYPE_FILE) {
- // For the playback from the local file, we use the first one from the given program.
- EitItem first = mPrograms.get(0);
- if (first != null && (mProgram == null
- || first.getStartTimeUtcMillis() < mProgram.getStartTimeUtcMillis())) {
- return first;
- }
- return null;
- }
- long currentTimeMs = getCurrentPosition();
- for (EitItem item : mPrograms) {
- if (item.getStartTimeUtcMillis() <= currentTimeMs
- && item.getEndTimeUtcMillis() >= currentTimeMs) {
- return item;
- }
- }
- return null;
- }
-
- private void doParentalControls() {
- boolean isParentalControlsEnabled = mTvInputManager.isParentalControlsEnabled();
- if (isParentalControlsEnabled) {
- TvContentRating blockContentRating = getContentRatingOfCurrentProgramBlocked();
- if (DEBUG) {
- if (blockContentRating != null) {
- Log.d(TAG, "Check parental controls: blocked by content rating - "
- + blockContentRating);
- } else {
- Log.d(TAG, "Check parental controls: available");
- }
- }
- updateChannelBlockStatus(blockContentRating != null, blockContentRating);
- } else {
- if (DEBUG) {
- Log.d(TAG, "Check parental controls: available");
- }
- updateChannelBlockStatus(false, null);
- }
- }
-
- private void doDiscoverCaptionServiceNumber(int serviceNumber) {
- int index = mCaptionTrackMap.indexOfKey(serviceNumber);
- if (index < 0) {
- AtscCaptionTrack captionTrack = new AtscCaptionTrack();
- captionTrack.serviceNumber = serviceNumber;
- captionTrack.wideAspectRatio = false;
- captionTrack.easyReader = false;
- mCaptionTrackMap.put(serviceNumber, captionTrack);
- mTvTracks.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE,
- SUBTITLE_TRACK_PREFIX + serviceNumber).build());
- mSession.notifyTracksChanged(mTvTracks);
- }
- }
-
- private TvContentRating getContentRatingOfCurrentProgramBlocked() {
- EitItem currentProgram = getCurrentProgram();
- if (currentProgram == null) {
- return null;
- }
- TvContentRating[] ratings = mTvContentRatingCache
- .getRatings(currentProgram.getContentRating());
- if (ratings == null || ratings.length == 0) {
- ratings = new TvContentRating[] {TvContentRating.UNRATED};
- }
- for (TvContentRating rating : ratings) {
- if (!Objects.equals(mUnblockedContentRating, rating) && mTvInputManager
- .isRatingBlocked(rating)) {
- return rating;
- }
- }
- return null;
- }
-
- private void updateChannelBlockStatus(boolean channelBlocked,
- TvContentRating contentRating) {
- if (mChannelBlocked == channelBlocked) {
- return;
- }
- mChannelBlocked = channelBlocked;
- if (mChannelBlocked) {
- clearCallbacksAndMessagesSafely();
- stopPlayback(true);
- resetTvTracks();
- if (contentRating != null) {
- mSession.notifyContentBlocked(contentRating);
- }
- mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS);
- } else {
- clearCallbacksAndMessagesSafely();
- resetPlayback();
- mSession.notifyContentAllowed();
- mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS,
- RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS);
- mHandler.removeMessages(MSG_CHECK_SIGNAL);
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
- }
- }
-
- @WorkerThread
- private void clearCallbacksAndMessagesSafely() {
- // If MSG_RELEASE is removed, TunerSessionWorker will hang forever.
- // Do not remove messages, after release is requested from MainThread.
- synchronized (mReleaseLock) {
- if (!mReleaseRequested) {
- mHandler.removeCallbacksAndMessages(null);
- }
- }
- }
-
- private boolean hasEnoughBackwardBuffer() {
- return mPlayer.getCurrentPosition() + BUFFER_UNDERFLOW_BUFFER_MS
- >= mBufferStartTimeMs - mRecordStartTimeMs;
- }
-
- private void notifyVideoUnavailable(final int reason) {
- mReportedWeakSignal = (reason == TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
- if (mSession != null) {
- mSession.notifyVideoUnavailable(reason);
- }
- }
-
- private void notifyVideoAvailable() {
- mReportedWeakSignal = false;
- if (mSession != null) {
- mSession.notifyVideoAvailable();
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java b/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java
deleted file mode 100644
index 6ad00daa..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2016 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.tvinput;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.tv.TvContract;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.util.Log;
-
-import com.android.tv.TvApplication;
-import com.android.tv.dvr.DvrStorageStatusManager;
-import com.android.tv.util.Utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Creates {@link JobService} to clean up recorded program files which are not referenced
- * from database.
- */
-public class TunerStorageCleanUpService extends JobService {
- private static final String TAG = "TunerStorageCleanUpService";
-
- private CleanUpStorageTask mTask;
-
- @Override
- public void onCreate() {
- if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) {
- Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
- this.stopSelf();
- return;
- }
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate();
- mTask = new CleanUpStorageTask(this, this);
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- return false;
- }
-
- /**
- * Cleans up recorded program files which are not referenced from database.
- * Cleaning up will be done periodically.
- */
- public static class CleanUpStorageTask extends AsyncTask<JobParameters, Void, JobParameters[]> {
- private final static String[] mProjection = {
- TvContract.RecordedPrograms.COLUMN_PACKAGE_NAME,
- TvContract.RecordedPrograms.COLUMN_RECORDING_DATA_URI
- };
- private final static long ELAPSED_MILLIS_TO_DELETE = TimeUnit.DAYS.toMillis(1);
-
- private final Context mContext;
- private final DvrStorageStatusManager mDvrStorageStatusManager;
- private final JobService mJobService;
- private final ContentResolver mContentResolver;
-
- /**
- * Creates a recurring storage cleaning task.
- *
- * @param context {@link Context}
- * @param jobService {@link JobService}
- */
- public CleanUpStorageTask(Context context, JobService jobService) {
- mContext = context;
- mDvrStorageStatusManager =
- TvApplication.getSingletons(mContext).getDvrStorageStatusManager();
- mJobService = jobService;
- mContentResolver = mContext.getContentResolver();
- }
-
- private Set<String> getRecordedProgramsDirs() {
- try (Cursor c = mContentResolver.query(
- TvContract.RecordedPrograms.CONTENT_URI, mProjection, null, null, null)) {
- if (c == null) {
- return null;
- }
- Set<String> recordedProgramDirs = new HashSet<>();
- while (c.moveToNext()) {
- String packageName = c.getString(0);
- String dataUriString = c.getString(1);
- if (dataUriString == null) {
- continue;
- }
- Uri dataUri = Uri.parse(dataUriString);
- if (!Utils.isInBundledPackageSet(packageName)
- || dataUri == null || dataUri.getPath() == null
- || !ContentResolver.SCHEME_FILE.equals(dataUri.getScheme())) {
- continue;
- }
- File recordedProgramDir = new File(dataUri.getPath());
- try {
- recordedProgramDirs.add(recordedProgramDir.getCanonicalPath());
- } catch (IOException | SecurityException e) {
- }
- }
- return recordedProgramDirs;
- }
- }
-
- @Override
- protected JobParameters[] doInBackground(JobParameters... params) {
- if (mDvrStorageStatusManager.getDvrStorageStatus()
- == DvrStorageStatusManager.STORAGE_STATUS_MISSING) {
- return params;
- }
- File dvrRecordingDir = mDvrStorageStatusManager.getRecordingRootDataDirectory();
- if (dvrRecordingDir == null || !dvrRecordingDir.isDirectory()) {
- return params;
- }
- Set<String> recordedProgramDirs = getRecordedProgramsDirs();
- if (recordedProgramDirs == null) {
- return params;
- }
- File[] files = dvrRecordingDir.listFiles();
- if (files == null || files.length == 0) {
- return params;
- }
- for (File recordingDir : files) {
- try {
- if (!recordedProgramDirs.contains(recordingDir.getCanonicalPath())) {
- long lastModified = recordingDir.lastModified();
- long now = System.currentTimeMillis();
- if (lastModified != 0
- && lastModified < now - ELAPSED_MILLIS_TO_DELETE) {
- // To prevent current recordings from being deleted,
- // deletes recordings which was not modified for long enough time.
- Utils.deleteDirOrFile(recordingDir);
- }
- }
- } catch (IOException | SecurityException e) {
- // would not happen
- }
- }
- return params;
- }
-
- @Override
- protected void onPostExecute(JobParameters[] params) {
- for (JobParameters param : params) {
- mJobService.jobFinished(param, false);
- }
- }
- }
-}
diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
deleted file mode 100644
index 2725ddfc..00000000
--- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java
+++ /dev/null
@@ -1,123 +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.tvinput;
-
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.tv.TvContract;
-import android.media.tv.TvInputService;
-import android.util.Log;
-
-import com.google.android.exoplayer.audio.AudioCapabilities;
-import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
-import com.android.tv.TvApplication;
-import com.android.tv.common.feature.CommonFeatures;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@link TunerTvInputService} serves TV channels coming from a tuner device.
- */
-public class TunerTvInputService extends TvInputService
- implements AudioCapabilitiesReceiver.Listener{
- private static final String TAG = "TunerTvInputService";
- private static final boolean DEBUG = false;
-
- private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100;
-
- // WeakContainer for {@link TvInputSessionImpl}
- private final Set<TunerSession> mTunerSessions = Collections.newSetFromMap(new WeakHashMap<>());
- private ChannelDataManager mChannelDataManager;
- private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
- private AudioCapabilities mAudioCapabilities;
-
- @Override
- public void onCreate() {
- if (!TvApplication.getSingletons(this).getTvInputManagerHelper().hasTvInputManager()) {
- Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
- this.stopSelf();
- return;
- }
- TvApplication.setCurrentRunningProcess(this, false);
- super.onCreate();
- if (DEBUG) Log.d(TAG, "onCreate");
- mChannelDataManager = new ChannelDataManager(getApplicationContext());
- mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this);
- mAudioCapabilitiesReceiver.register();
- if (CommonFeatures.DVR.isEnabled(this)) {
- JobScheduler jobScheduler =
- (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- JobInfo pendingJob = jobScheduler.getPendingJob(DVR_STORAGE_CLEANUP_JOB_ID);
- if (pendingJob != null) {
- // storage cleaning job is already scheduled.
- } else {
- JobInfo job = new JobInfo.Builder(DVR_STORAGE_CLEANUP_JOB_ID,
- new ComponentName(this, TunerStorageCleanUpService.class))
- .setPersisted(true).setPeriodic(TimeUnit.DAYS.toMillis(1)).build();
- jobScheduler.schedule(job);
- }
- }
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Log.d(TAG, "onDestroy");
- super.onDestroy();
- mChannelDataManager.release();
- mAudioCapabilitiesReceiver.unregister();
- }
-
- @Override
- public RecordingSession onCreateRecordingSession(String inputId) {
- return new TunerRecordingSession(this, inputId, mChannelDataManager);
- }
-
- @Override
- public Session onCreateSession(String inputId) {
- if (DEBUG) Log.d(TAG, "onCreateSession");
- try {
- final TunerSession session = new TunerSession(this, mChannelDataManager);
- mTunerSessions.add(session);
- session.setAudioCapabilities(mAudioCapabilities);
- session.setOverlayViewEnabled(true);
- return session;
- } catch (RuntimeException e) {
- // There are no available DVB devices.
- Log.e(TAG, "Creating a session for " + inputId + " failed.", e);
- return null;
- }
- }
-
- @Override
- public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
- mAudioCapabilities = audioCapabilities;
- for (TunerSession session : mTunerSessions) {
- if (!session.isReleased()) {
- session.setAudioCapabilities(audioCapabilities);
- }
- }
- }
-
- public static String getInputId(Context context) {
- return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class));
- }
-}
diff --git a/src/com/android/tv/tuner/util/ByteArrayBuffer.java b/src/com/android/tv/tuner/util/ByteArrayBuffer.java
deleted file mode 100644
index da887e7d..00000000
--- a/src/com/android/tv/tuner/util/ByteArrayBuffer.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/util/ByteArrayBuffer.java $
- * $Revision: 496070 $
- * $Date: 2007-01-14 04:18:34 -0800 (Sun, 14 Jan 2007) $
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.tv.tuner.util;
-
-/**
- * An expandable byte buffer built on byte array.
- */
-public final class ByteArrayBuffer {
-
- private byte[] buffer;
- private int len;
-
- public ByteArrayBuffer(int capacity) {
- super();
- if (capacity < 0) {
- throw new IllegalArgumentException("Buffer capacity may not be negative");
- }
- this.buffer = new byte[capacity];
- }
-
- private void expand(int newlen) {
- byte newbuffer[] = new byte[Math.max(this.buffer.length << 1, newlen)];
- System.arraycopy(this.buffer, 0, newbuffer, 0, this.len);
- this.buffer = newbuffer;
- }
-
- public void append(final byte[] b, int off, int len) {
- if (b == null) {
- return;
- }
- if ((off < 0) || (off > b.length) || (len < 0) ||
- ((off + len) < 0) || ((off + len) > b.length)) {
- throw new IndexOutOfBoundsException();
- }
- if (len == 0) {
- return;
- }
- int newlen = this.len + len;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- System.arraycopy(b, off, this.buffer, this.len, len);
- this.len = newlen;
- }
-
- public void append(int b) {
- int newlen = this.len + 1;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- this.buffer[this.len] = (byte) b;
- this.len = newlen;
- }
-
- public void append(final char[] b, int off, int len) {
- if (b == null) {
- return;
- }
- if ((off < 0) || (off > b.length) || (len < 0) ||
- ((off + len) < 0) || ((off + len) > b.length)) {
- throw new IndexOutOfBoundsException();
- }
- if (len == 0) {
- return;
- }
- int oldlen = this.len;
- int newlen = oldlen + len;
- if (newlen > this.buffer.length) {
- expand(newlen);
- }
- for (int i1 = off, i2 = oldlen; i2 < newlen; i1++, i2++) {
- this.buffer[i2] = (byte) b[i1];
- }
- this.len = newlen;
- }
-
- public void clear() {
- this.len = 0;
- }
-
- public byte[] toByteArray() {
- byte[] b = new byte[this.len];
- if (this.len > 0) {
- System.arraycopy(this.buffer, 0, b, 0, this.len);
- }
- return b;
- }
-
- public int byteAt(int i) {
- return this.buffer[i];
- }
-
- public int capacity() {
- return this.buffer.length;
- }
-
- public int length() {
- return this.len;
- }
-
- public byte[] buffer() {
- return this.buffer;
- }
-
- public void setLength(int len) {
- if (len < 0 || len > this.buffer.length) {
- throw new IndexOutOfBoundsException();
- }
- this.len = len;
- }
-
- public boolean isEmpty() {
- return this.len == 0;
- }
-
- public boolean isFull() {
- return this.len == this.buffer.length;
- }
-
-}
diff --git a/src/com/android/tv/tuner/util/ConvertUtils.java b/src/com/android/tv/tuner/util/ConvertUtils.java
deleted file mode 100644
index abf18d8c..00000000
--- a/src/com/android/tv/tuner/util/ConvertUtils.java
+++ /dev/null
@@ -1,35 +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.util;
-
-/**
- * Utility class for converting date and time.
- */
-public class ConvertUtils {
- // Time diff between 1.1.1970 00:00:00 and 6.1.1980 00:00:00
- private static final long DIFF_BETWEEN_UNIX_EPOCH_AND_GPS = 315964800;
-
- private ConvertUtils() { }
-
- public static long convertGPSTimeToUnixEpoch(long gpsTime) {
- return gpsTime + DIFF_BETWEEN_UNIX_EPOCH_AND_GPS;
- }
-
- public static long convertUnixEpochToGPSTime(long epochTime) {
- return epochTime - DIFF_BETWEEN_UNIX_EPOCH_AND_GPS;
- }
-}
diff --git a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java b/src/com/android/tv/tuner/util/GlobalSettingsUtils.java
deleted file mode 100644
index 0cefcbed..00000000
--- a/src/com/android/tv/tuner/util/GlobalSettingsUtils.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import android.content.Context;
-import android.provider.Settings;
-
-/**
- * Utility class that get information of global settings.
- */
-public class GlobalSettingsUtils {
- // Since global surround setting is hided, add the related variable here for checking surround
- // sound setting when the audio is unavailable. Remove this workaround after b/31254857 fixed.
- private static final String ENCODED_SURROUND_OUTPUT = "encoded_surround_output";
- public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;
-
- private GlobalSettingsUtils () { }
-
- public static int getEncodedSurroundOutputSettings(Context context) {
- return Settings.Global.getInt(context.getContentResolver(), ENCODED_SURROUND_OUTPUT, 0);
- }
-}
diff --git a/src/com/android/tv/tuner/util/Ints.java b/src/com/android/tv/tuner/util/Ints.java
deleted file mode 100644
index 0b1be426..00000000
--- a/src/com/android/tv/tuner/util/Ints.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.android.tv.tuner.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Static utility methods pertaining to int primitives. (Referred Guava's Ints class)
- */
-public class Ints {
- private Ints() {}
-
- public static int[] toArray(List<Integer> integerList) {
- int[] intArray = new int[integerList.size()];
- int i = 0;
- for (Integer data : integerList) {
- intArray[i++] = data;
- }
- return intArray;
- }
-
- public static List<Integer> asList(int[] intArray) {
- List<Integer> integerList = new ArrayList<>(intArray.length);
- for (int data : intArray) {
- integerList.add(data);
- }
- return integerList;
- }
-}
diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/src/com/android/tv/tuner/util/PostalCodeUtils.java
deleted file mode 100644
index 9eb689a7..00000000
--- a/src/com/android/tv/tuner/util/PostalCodeUtils.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 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.util;
-
-import android.content.Context;
-import android.location.Address;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.util.LocationUtils;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * A utility class to update, get, and set the last known postal or zip code.
- */
-public class PostalCodeUtils {
- private static final String TAG = "PostalCodeUtils";
-
- // Postcode formats, where A signifies a letter and 9 a digit:
- // US zip code format: 99999
- private static final String POSTCODE_REGEX_US = "^(\\d{5})";
- // UK postcode district formats: A9, A99, AA9, AA99
- // Full UK postcode format: Postcode District + space + 9AA
- // Should be able to handle both postcode district and full postcode
- private static final String POSTCODE_REGEX_GB =
- "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$";
- private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode
-
- private static final Map<String, Pattern> REGION_PATTERN = new HashMap<>();
- private static final Map<String, Integer> REGION_MAX_LENGTH = new HashMap<>();
-
- static {
- REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US));
- REGION_PATTERN.put(
- Locale.UK.getCountry(),
- Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR));
- REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5);
- REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8);
- }
-
- // The longest postcode number is 10-character-long.
- // Use a larger number to accommodate future changes.
- private static final int DEFAULT_MAX_LENGTH = 16;
-
- /** Returns {@code true} if postal code has been changed */
- public static boolean updatePostalCode(Context context)
- throws IOException, SecurityException, NoPostalCodeException {
- String postalCode = getPostalCode(context);
- String lastPostalCode = getLastPostalCode(context);
- if (TextUtils.isEmpty(postalCode)) {
- if (TextUtils.isEmpty(lastPostalCode)) {
- throw new NoPostalCodeException();
- }
- } else if (!TextUtils.equals(postalCode, lastPostalCode)) {
- setLastPostalCode(context, postalCode);
- return true;
- }
- return false;
- }
-
- /**
- * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or
- * input by users.
- */
- public static String getLastPostalCode(Context context) {
- return TunerPreferences.getLastPostalCode(context);
- }
-
- /**
- * Sets the last stored postal or zip code. This method will overwrite the value written by
- * calling {@link #updatePostalCode(Context)}.
- */
- public static void setLastPostalCode(Context context, String postalCode) {
- Log.i(TAG, "Set Postal Code:" + postalCode);
- TunerPreferences.setLastPostalCode(context, postalCode);
- }
-
- @Nullable
- private static String getPostalCode(Context context) throws IOException, SecurityException {
- Address address = LocationUtils.getCurrentAddress(context);
- if (address != null) {
- Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", "
- + address.getPostalCode());
- return address.getPostalCode();
- }
- return null;
- }
-
- /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */
- public static class NoPostalCodeException extends Exception {
- public NoPostalCodeException() {
- }
- }
-
- /**
- * Checks whether a postcode matches the format of the specific region.
- *
- * @return {@code false} if the region is supported and the postcode doesn't match; {@code true}
- * otherwise
- */
- public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) {
- Pattern pattern = REGION_PATTERN.get(region.toUpperCase());
- return pattern == null || pattern.matcher(postcode).matches();
- }
-
- /**
- * Gets the largest possible postcode length in the region.
- *
- * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH}
- * otherwise
- */
- public static int getRegionMaxLength(Context context) {
- Integer maxLength =
- REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase());
- return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength;
- }
-} \ No newline at end of file
diff --git a/src/com/android/tv/tuner/util/StatusTextUtils.java b/src/com/android/tv/tuner/util/StatusTextUtils.java
deleted file mode 100644
index 2633834b..00000000
--- a/src/com/android/tv/tuner/util/StatusTextUtils.java
+++ /dev/null
@@ -1,119 +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.util;
-
-import java.util.Locale;
-
-/**
- * Utility class for tuner status messages.
- */
-public class StatusTextUtils {
- private static final int PACKETS_PER_SEC_YELLOW = 1500;
- private static final int PACKETS_PER_SEC_RED = 1000;
- private static final int AUDIO_POSITION_MS_RATE_DIFF_YELLOW = 100;
- private static final int AUDIO_POSITION_MS_RATE_DIFF_RED = 200;
- private static final String COLOR_RED = "red";
- private static final String COLOR_YELLOW = "yellow";
- private static final String COLOR_GREEN = "green";
- private static final String COLOR_GRAY = "gray";
-
- private StatusTextUtils() { }
-
- /**
- * Returns tuner status warning message in HTML.
- *
- * <p>This is only called for debuging and always shown in english.</p>
- */
- public static String getStatusWarningInHTML(long packetsPerSec,
- int videoFrameDrop, int bytesInQueue,
- long audioPositionUs, long audioPositionUsRate,
- long audioPtsUs, long audioPtsUsRate,
- long videoPtsUs, long videoPtsUsRate) {
- StringBuffer buffer = new StringBuffer();
-
- // audioPosition should go in rate of 1000ms.
- long audioPositionMsRate = audioPositionUsRate / 1000;
- String audioPositionColor;
- if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_RED) {
- audioPositionColor = COLOR_RED;
- } else if (Math.abs(audioPositionMsRate - 1000) > AUDIO_POSITION_MS_RATE_DIFF_YELLOW) {
- audioPositionColor = COLOR_YELLOW;
- } else {
- audioPositionColor = COLOR_GRAY;
- }
- buffer.append(String.format(Locale.US, "<font color=%s>", audioPositionColor));
- buffer.append(
- String.format(Locale.US, "audioPositionMs: %d (%d)<br>", audioPositionUs / 1000,
- audioPositionMsRate));
- buffer.append("</font>\n");
- buffer.append("<font color=" + COLOR_GRAY + ">");
- buffer.append(String.format(Locale.US, "audioPtsMs: %d (%d, %d)<br>", audioPtsUs / 1000,
- audioPtsUsRate / 1000, (audioPtsUs - audioPositionUs) / 1000));
- buffer.append(String.format(Locale.US, "videoPtsMs: %d (%d, %d)<br>", videoPtsUs / 1000,
- videoPtsUsRate / 1000, (videoPtsUs - audioPositionUs) / 1000));
- buffer.append("</font>\n");
-
- appendStatusLine(buffer, "KbytesInQueue", bytesInQueue / 1000, 1, 10);
- buffer.append("<br/>");
- appendErrorStatusLine(buffer, "videoFrameDrop", videoFrameDrop, 0, 2);
- buffer.append("<br/>");
- appendStatusLine(buffer, "packetsPerSec", packetsPerSec, PACKETS_PER_SEC_RED,
- PACKETS_PER_SEC_YELLOW);
- return buffer.toString();
- }
-
- /**
- * Returns audio unavailable warning message in HTML.
- */
- public static String getAudioWarningInHTML(String msg) {
- return String.format("<font color=%s>%s</font>\n", COLOR_YELLOW, msg);
- }
-
- private static void appendStatusLine(StringBuffer buffer, String factorName, long value,
- int minRed, int minYellow) {
- buffer.append("<font color=");
- if (value <= minRed) {
- buffer.append(COLOR_RED);
- } else if (value <= minYellow) {
- buffer.append(COLOR_YELLOW);
- } else {
- buffer.append(COLOR_GREEN);
- }
- buffer.append(">");
- buffer.append(factorName);
- buffer.append(" : ");
- buffer.append(value);
- buffer.append("</font>");
- }
-
- private static void appendErrorStatusLine(StringBuffer buffer, String factorName, int value,
- int minGreen, int minYellow) {
- buffer.append("<font color=");
- if (value <= minGreen) {
- buffer.append(COLOR_GREEN);
- } else if (value <= minYellow) {
- buffer.append(COLOR_YELLOW);
- } else {
- buffer.append(COLOR_RED);
- }
- buffer.append(">");
- buffer.append(factorName);
- buffer.append(" : ");
- buffer.append(value);
- buffer.append("</font>");
- }
-}
diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
deleted file mode 100644
index 2817ccbf..00000000
--- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
+++ /dev/null
@@ -1,77 +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.util;
-
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Proxy class that gives an access to a hidden API {@link android.os.SystemProperties#getBoolean}.
- */
-public class SystemPropertiesProxy {
- private static final String TAG = "SystemPropertiesProxy";
-
- private SystemPropertiesProxy() { }
-
- public static boolean getBoolean(String key, boolean def)
- throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getBooleanMethod = SystemPropertiesClass.getDeclaredMethod("getBoolean",
- String.class, boolean.class);
- getBooleanMethod.setAccessible(true);
- return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e);
- }
- return def;
- }
-
- public static int getInt(String key, int def)
- throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getIntMethod = SystemPropertiesClass.getDeclaredMethod("getInt",
- String.class, int.class);
- getIntMethod.setAccessible(true);
- return (int) getIntMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e);
- }
- return def;
- }
-
- public static String getString(String key, String def) throws IllegalArgumentException {
- try {
- Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
- Method getIntMethod =
- SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class);
- getIntMethod.setAccessible(true);
- return (String) getIntMethod.invoke(SystemPropertiesClass, key, def);
- } catch (InvocationTargetException
- | IllegalAccessException
- | NoSuchMethodException
- | ClassNotFoundException e) {
- Log.e(TAG, "Failed to invoke SystemProperties.get()", e);
- }
- return def;
- }
-}
diff --git a/src/com/android/tv/tuner/util/TisConfiguration.java b/src/com/android/tv/tuner/util/TisConfiguration.java
deleted file mode 100644
index ca861d67..00000000
--- a/src/com/android/tv/tuner/util/TisConfiguration.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.android.tv.tuner.util;
-
-import android.content.Context;
-
-/**
- * A helper class of tuner configuration.
- */
-public class TisConfiguration {
- private static final String LC_PACKAGE_NAME = "com.android.tv";
-
- public static boolean isPackagedWithLiveChannels(Context context) {
- return (LC_PACKAGE_NAME.equals(context.getPackageName()));
- }
-
- public static boolean isInternalTunerTvInput(Context context) {
- return (!LC_PACKAGE_NAME.equals(context.getPackageName()));
- }
-
- public static int getTunerHwDeviceId(Context context) {
- return 0; // FIXME: Make it OEM configurable
- }
-}
diff --git a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
deleted file mode 100644
index f421bf1a..00000000
--- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2016 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.util;
-
-import android.annotation.TargetApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.tv.TvInputInfo;
-import android.media.tv.TvInputManager;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.tv.common.feature.CommonFeatures;
-import com.android.tv.tuner.R;
-import com.android.tv.tuner.TunerHal;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
-
-/**
- * Utility class for providing tuner input info.
- */
-public class TunerInputInfoUtils {
- private static final String TAG = "TunerInputInfoUtils";
- private static final boolean DEBUG = false;
-
- /**
- * Builds tuner input's info.
- */
- @Nullable
- @TargetApi(Build.VERSION_CODES.N)
- public static TvInputInfo buildTunerInputInfo(Context context) {
- Pair<Integer, Integer> tunerTypeAndCount = TunerHal.getTunerTypeAndCount(context);
- if (tunerTypeAndCount.first == null || tunerTypeAndCount.second == 0) {
- return null;
- }
- int inputLabelId = 0;
- switch (tunerTypeAndCount.first) {
- case TunerHal.TUNER_TYPE_BUILT_IN:
- inputLabelId = R.string.bt_app_name;
- break;
- case TunerHal.TUNER_TYPE_USB:
- inputLabelId = R.string.ut_app_name;
- break;
- case TunerHal.TUNER_TYPE_NETWORK:
- inputLabelId = R.string.nt_app_name;
- break;
- }
- try {
- TvInputInfo.Builder builder = new TvInputInfo.Builder(context,
- new ComponentName(context, TunerTvInputService.class));
- return builder.setLabel(inputLabelId)
- .setCanRecord(CommonFeatures.DVR.isEnabled(context))
- .setTunerCount(tunerTypeAndCount.second)
- .build();
- } catch (IllegalArgumentException | NullPointerException e) {
- // TunerTvInputService is not enabled.
- return null;
- }
- }
-
- /**
- * Updates tuner input's info.
- *
- * @param context {@link Context} instance
- */
- public static void updateTunerInputInfo(Context context) {
- final Context appContext = context.getApplicationContext();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- new AsyncTask<Void, Void, TvInputInfo>() {
- @Override
- protected TvInputInfo doInBackground(Void... params) {
- if (DEBUG) Log.d(TAG, "updateTunerInputInfo()");
- return buildTunerInputInfo(appContext);
- }
-
- @Override
- @TargetApi(Build.VERSION_CODES.N)
- protected void onPostExecute(TvInputInfo info) {
- if (info != null) {
- ((TvInputManager) appContext.getSystemService(Context.TV_INPUT_SERVICE))
- .updateTvInputInfo(info);
- if (DEBUG) {
- Log.d(
- TAG,
- "TvInputInfo ["
- + info.loadLabel(appContext)
- + "] updated: "
- + info.toString());
- }
- } else {
- if (DEBUG) {
- Log.d(TAG, "Updating tuner input info failed. Input is not ready yet.");
- }
- }
- }
- }.execute();
- }
- }
-} \ No newline at end of file