aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2021-10-06 22:54:01 +0000
committerXin Li <delphij@google.com>2021-10-06 22:54:01 +0000
commit8aad9d2eedd214747b8191e9581d82629d4d34e0 (patch)
treea63013eb2d6d7a5c2e416f9609f18d0aa0d0cedc
parent20322236e664b6ff86d334ca538038175fd972a3 (diff)
parentd54318f5411ffb789b2adc23a4bb8aa1a4026725 (diff)
downloadTV-8aad9d2eedd214747b8191e9581d82629d4d34e0.tar.gz
Merge Android 12
Bug: 202323961 Merged-In: Ia566dc434543755b829d1a80dd5740d0648b3dd3 Change-Id: I2f6ff4de35897ede7213f4584eebeeec52fbce02
-rw-r--r--AndroidManifest.xml9
-rw-r--r--com.android.tv.xml3
-rw-r--r--lint-baseline.xml16
-rw-r--r--src/com/android/tv/MainActivity.java7
-rw-r--r--src/com/android/tv/util/GtvUtils.java56
-rw-r--r--tests/common/Android.mk4
-rw-r--r--tests/input/Android.mk4
-rw-r--r--tuner/sampletunertvinput/AndroidManifest.xml6
-rw-r--r--tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml6
-rw-r--r--tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java385
10 files changed, 464 insertions, 32 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 854f2883..2cff92d3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_TV_LISTINGS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA"/>
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
@@ -115,6 +116,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="com.android.tv.MainActivity"
@@ -165,7 +167,12 @@
<activity android:name="com.android.tv.SelectInputActivity"
android:configChanges="keyboard|keyboardHidden"
android:launchMode="singleTask"
- android:theme="@style/Theme.SelectInputActivity"/>
+ android:theme="@style/Theme.SelectInputActivity">
+ <intent-filter>
+ <action android:name="com.android.tv.action.VIEW_INPUTS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<activity android:name="com.android.tv.onboarding.OnboardingActivity"
android:configChanges="keyboard|keyboardHidden"
android:launchMode="singleTop"
diff --git a/com.android.tv.xml b/com.android.tv.xml
index 245d275d..5ac6c0c3 100644
--- a/com.android.tv.xml
+++ b/com.android.tv.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<permissions>
+ <feature name="com.google.android.tv.installed" />
+
<privapp-permissions package="com.android.tv">
<permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
<permission name="android.permission.DVB_DEVICE"/>
@@ -7,6 +9,7 @@
<permission name="android.permission.HDMI_CEC"/>
<permission name="android.permission.MODIFY_PARENTAL_CONTROLS"/>
<permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+ <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
<permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
</privapp-permissions>
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 2a8be311..d91a1894 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -118,7 +118,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="529"
+ line="534"
column="28"/>
</issue>
@@ -129,7 +129,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="997"
+ line="1002"
column="28"/>
</issue>
@@ -140,7 +140,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="1024"
+ line="1029"
column="48"/>
</issue>
@@ -151,7 +151,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="1032"
+ line="1037"
column="28"/>
</issue>
@@ -162,7 +162,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="1060"
+ line="1065"
column="28"/>
</issue>
@@ -173,7 +173,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="1539"
+ line="1544"
column="35"/>
</issue>
@@ -184,7 +184,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="2397"
+ line="2402"
column="27"/>
</issue>
@@ -195,7 +195,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/TV/src/com/android/tv/MainActivity.java"
- line="2808"
+ line="2813"
column="27"/>
</issue>
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 9c615b93..8dbafe47 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -149,6 +149,7 @@ import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
import com.android.tv.util.AsyncDbTask;
import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.CaptionSettings;
+import com.android.tv.util.GtvUtils;
import com.android.tv.util.OnboardingUtils;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
@@ -456,7 +457,11 @@ public class MainActivity extends Activity
}
@Override
- public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
+ public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+ if (currentChannel != null) {
+ GtvUtils.broadcastInputId(MainActivity.this, currentChannel.getInputId());
+ }
+ }
};
private final Runnable mRestoreMainViewRunnable = this::restoreMainTvView;
diff --git a/src/com/android/tv/util/GtvUtils.java b/src/com/android/tv/util/GtvUtils.java
new file mode 100644
index 00000000..eb50e062
--- /dev/null
+++ b/src/com/android/tv/util/GtvUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+/** A utility class for Google TV */
+public class GtvUtils {
+ private static final String TAG = "GtvUtils";
+ private static final String AMATI_FEATURE = "com.google.android.feature.AMATI_EXPERIENCE";
+ private static final String PERMISSION_WRITE_EPG_DATA =
+ "com.android.providers.tv.permission.WRITE_EPG_DATA";
+ private static final String ACTION_INPUT_SELECTED = "android.apps.tv.launcherx.INPUT_SELECTED";
+ private static final String EXTRA_INPUT_ID = "extra_input_id";
+ private static final String LAUNCHERX_PACKAGE_NAME = "com.google.android.apps.tv.launcherx";
+ private static Boolean mEnabled = null;
+
+ private static boolean isEnabled(Context context) {
+ if (mEnabled == null) {
+ PackageManager pm = context.getPackageManager();
+ mEnabled = pm.hasSystemFeature(AMATI_FEATURE);
+ }
+ return mEnabled;
+ }
+
+ /** Broadcasts the intent with inputId to the Launcher */
+ public static void broadcastInputId(Context context, String inputId) {
+ if (isEnabled(context)) {
+ if (inputId == null) {
+ Log.e(TAG, "Will not broadcast inputId because it is null");
+ } else {
+ Intent intent = new Intent(ACTION_INPUT_SELECTED);
+ intent.putExtra(EXTRA_INPUT_ID, inputId);
+ intent.setPackage(LAUNCHERX_PACKAGE_NAME);
+ context.sendBroadcast(intent, PERMISSION_WRITE_EPG_DATA);
+ }
+ }
+ }
+}
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
index b74a65d3..7a232ff7 100644
--- a/tests/common/Android.mk
+++ b/tests/common/Android.mk
@@ -17,6 +17,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
mockito-robolectric-prebuilt \
tv-test-common \
+# Disable dexpreopt and <uses-library> check for test.
+LOCAL_ENFORCE_USES_LIBRARIES := false
+LOCAL_DEX_PREOPT := false
+
LOCAL_INSTRUMENTATION_FOR := LiveTv
LOCAL_MODULE_TAGS := optional
diff --git a/tests/input/Android.mk b/tests/input/Android.mk
index c15e4a49..4011dec8 100644
--- a/tests/input/Android.mk
+++ b/tests/input/Android.mk
@@ -16,6 +16,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
tv-test-common \
tv-common
+# Disable dexpreopt and <uses-library> check for test.
+LOCAL_ENFORCE_USES_LIBRARIES := false
+LOCAL_DEX_PREOPT := false
+
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res $(LOCAL_PATH)/res
LOCAL_AAPT_FLAGS := --auto-add-overlay \
--extra-packages com.android.tv.testing
diff --git a/tuner/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/AndroidManifest.xml
index d282889a..8b25d0bf 100644
--- a/tuner/sampletunertvinput/AndroidManifest.xml
+++ b/tuner/sampletunertvinput/AndroidManifest.xml
@@ -43,7 +43,8 @@
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
<uses-library android:name="com.android.libraries.tv.tvsystem" android:required="false" />
- <activity android:name=".SampleTunerTvInputSetupActivity" >
+ <activity android:name=".SampleTunerTvInputSetupActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -51,7 +52,8 @@
<service android:name=".SampleTunerTvInputService"
android:permission="android.permission.BIND_TV_INPUT"
android:label="@string/sample_tuner_tv_input"
- android:process="com.android.tv.samples.sampletunertvinput">
+ android:process="com.android.tv.samples.sampletunertvinput"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.tv.TvInputService" />
</intent-filter>
diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
index 8fc96b2a..909e2431 100644
--- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
+++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
@@ -39,7 +39,8 @@
android:icon="@mipmap/ic_launcher"
android:theme="@android:style/Theme.Holo.Light.NoActionBar"
android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
- <activity android:name=".SampleTunerTvInputSetupActivity" >
+ <activity android:name=".SampleTunerTvInputSetupActivity"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -47,7 +48,8 @@
<service android:name=".SampleTunerTvInputService"
android:permission="android.permission.BIND_TV_INPUT"
android:label="@string/sample_tuner_tv_input"
- android:process="com.android.tv.samples.sampletunertvinput">
+ android:process="com.android.tv.samples.sampletunertvinput"
+ android:exported="true">
<intent-filter>
<action android:name="android.media.tv.TvInputService" />
</intent-filter>
diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
index 6ac95353..e86ced14 100644
--- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
+++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
@@ -1,13 +1,39 @@
package com.android.tv.samples.sampletunertvinput;
+import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+
import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+import android.media.tv.tuner.dvr.DvrPlayback;
+import android.media.tv.tuner.dvr.DvrSettings;
+import android.media.tv.tuner.filter.AvSettings;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.MediaEvent;
+import android.media.tv.tuner.filter.TsFilterConfiguration;
import android.media.tv.tuner.frontend.AtscFrontendSettings;
+import android.media.tv.tuner.frontend.DvbtFrontendSettings;
import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tuner.frontend.OnTuneEventListener;
import android.media.tv.tuner.Tuner;
import android.media.tv.TvInputService;
import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.Surface;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
/** SampleTunerTvInputService */
@@ -15,8 +41,39 @@ public class SampleTunerTvInputService extends TvInputService {
private static final String TAG = "SampleTunerTvInput";
private static final boolean DEBUG = true;
+ private static final int AUDIO_TPID = 257;
+ private static final int VIDEO_TPID = 256;
+ private static final int STATUS_MASK = 0xf;
+ private static final int LOW_THRESHOLD = 0x1000;
+ private static final int HIGH_THRESHOLD = 0x07fff;
+ private static final int FREQUENCY = 578000;
+ private static final int FILTER_BUFFER_SIZE = 16000000;
+ private static final int DVR_BUFFER_SIZE = 4000000;
+ private static final int INPUT_FILE_MAX_SIZE = 700000;
+ private static final int PACKET_SIZE = 188;
+
+ private static final int TIMEOUT_US = 100000;
+ private static final boolean SAVE_DATA = true;
+ private static final String ES_PATH = "/data/local/tmp/test.es";
+ private static final MediaFormat VIDEO_FORMAT;
+
+ static {
+ // format extracted for the specific input file
+ VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 320, 240);
+ VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1);
+ VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333);
+ VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32);
+ VIDEO_FORMAT.setInteger(MediaFormat.KEY_PROFILE, 65536);
+ ByteBuffer csd = ByteBuffer.wrap(
+ new byte[] {0, 0, 0, 1, 103, 66, -64, 20, -38, 5, 7, -24, 64, 0, 0, 3, 0, 64, 0,
+ 0, 15, 35, -59, 10, -88});
+ VIDEO_FORMAT.setByteBuffer("csd-0", csd);
+ csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -50, 60, -128});
+ VIDEO_FORMAT.setByteBuffer("csd-1", csd);
+ }
+
public static final String INPUT_ID =
- "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService";
+ "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService";
private String mSessionId;
@Override
@@ -36,9 +93,19 @@ public class SampleTunerTvInputService extends TvInputService {
class TvInputSessionImpl extends Session {
- private Surface surface;
private final Context mContext;
- Tuner tuner;
+ private Handler mHandler;
+
+ private Surface mSurface;
+ private Filter mAudioFilter;
+ private Filter mVideoFilter;
+ private DvrPlayback mDvr;
+ private Tuner mTuner;
+ private MediaCodec mMediaCodec;
+ private Thread mDecoderThread;
+ private Deque<MediaEvent> mDataQueue;
+ private List<MediaEvent> mSavedData;
+ private boolean mDataReady = false;
public TvInputSessionImpl(Context context) {
@@ -51,6 +118,28 @@ public class SampleTunerTvInputService extends TvInputService {
if (DEBUG) {
Log.d(TAG, "onRelease");
}
+ if (mDecoderThread != null) {
+ mDecoderThread.interrupt();
+ mDecoderThread = null;
+ }
+ if (mMediaCodec != null) {
+ mMediaCodec.release();
+ mMediaCodec = null;
+ }
+ if (mAudioFilter != null) {
+ mAudioFilter.close();
+ }
+ if (mVideoFilter != null) {
+ mVideoFilter.close();
+ }
+ if (mDvr != null) {
+ mDvr.close();
+ }
+ if (mTuner != null) {
+ mTuner.close();
+ }
+ mDataQueue = null;
+ mSavedData = null;
}
@Override
@@ -58,7 +147,7 @@ public class SampleTunerTvInputService extends TvInputService {
if (DEBUG) {
Log.d(TAG, "onSetSurface");
}
- this.surface = surface;
+ this.mSurface = surface;
return true;
}
@@ -74,20 +163,16 @@ public class SampleTunerTvInputService extends TvInputService {
if (DEBUG) {
Log.d(TAG, "onTune " + uri);
}
- tuner = new Tuner(mContext, mSessionId,
- TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-
- int feCount = tuner.getFrontendIds().size();
- if (feCount <= 0) return false;
-
- AtscFrontendSettings settings =
- AtscFrontendSettings
- .builder()
- .setFrequency(2000)
- .setModulation(AtscFrontendSettings.MODULATION_AUTO)
- .build();
- tuner.tune(settings);
-
+ if (!initCodec()) {
+ Log.e(TAG, "null codec!");
+ return false;
+ }
+ mHandler = new Handler();
+ mDecoderThread =
+ new Thread(
+ this::decodeInternal,
+ "sample-tuner-tis-decoder-thread");
+ mDecoderThread.start();
return true;
}
@@ -97,5 +182,269 @@ public class SampleTunerTvInputService extends TvInputService {
Log.d(TAG, "onSetCaptionEnabled " + b);
}
}
+
+ private Filter audioFilter() {
+ Filter audioFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO,
+ FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
+ new FilterCallback() {
+ @Override
+ public void onFilterEvent(Filter filter, FilterEvent[] events) {
+ if (DEBUG) {
+ Log.d(TAG, "onFilterEvent audio, size=" + events.length);
+ }
+ for (int i = 0; i < events.length; i++) {
+ if (DEBUG) {
+ Log.d(TAG, "events[" + i + "] is "
+ + events[i].getClass().getSimpleName());
+ }
+ }
+ }
+
+ @Override
+ public void onFilterStatusChanged(Filter filter, int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onFilterEvent audio, status=" + status);
+ }
+ }
+ });
+ AvSettings settings =
+ AvSettings.builder(Filter.TYPE_TS, true).setPassthrough(false).build();
+ audioFilter.configure(
+ TsFilterConfiguration.builder().setTpid(AUDIO_TPID)
+ .setSettings(settings).build());
+ return audioFilter;
+ }
+
+ private Filter videoFilter() {
+ Filter videoFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO,
+ FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
+ new FilterCallback() {
+ @Override
+ public void onFilterEvent(Filter filter, FilterEvent[] events) {
+ if (DEBUG) {
+ Log.d(TAG, "onFilterEvent video, size=" + events.length);
+ }
+ for (int i = 0; i < events.length; i++) {
+ if (DEBUG) {
+ Log.d(TAG, "events[" + i + "] is "
+ + events[i].getClass().getSimpleName());
+ }
+ if (events[i] instanceof MediaEvent) {
+ MediaEvent me = (MediaEvent) events[i];
+ mDataQueue.add(me);
+ if (SAVE_DATA) {
+ mSavedData.add(me);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onFilterStatusChanged(Filter filter, int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onFilterEvent video, status=" + status);
+ if (status == Filter.STATUS_DATA_READY) {
+ mDataReady = true;
+ }
+ }
+ }
+ });
+ AvSettings settings =
+ AvSettings.builder(Filter.TYPE_TS, false).setPassthrough(false).build();
+ videoFilter.configure(
+ TsFilterConfiguration.builder().setTpid(VIDEO_TPID)
+ .setSettings(settings).build());
+ return videoFilter;
+ }
+
+ private DvrPlayback dvrPlayback() {
+ DvrPlayback dvr = mTuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler),
+ status -> {
+ if (DEBUG) {
+ Log.d(TAG, "onPlaybackStatusChanged status=" + status);
+ }
+ });
+ int res = dvr.configure(
+ DvrSettings.builder()
+ .setStatusMask(STATUS_MASK)
+ .setLowThreshold(LOW_THRESHOLD)
+ .setHighThreshold(HIGH_THRESHOLD)
+ .setDataFormat(DvrSettings.DATA_FORMAT_ES)
+ .setPacketSize(PACKET_SIZE)
+ .build());
+ if (DEBUG) {
+ Log.d(TAG, "config res=" + res);
+ }
+ File file = new File(ES_PATH);
+ if (file.exists()) {
+ try {
+ dvr.setFileDescriptor(
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE));
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to create FD");
+ }
+ } else {
+ Log.w(TAG, "File not existing");
+ }
+ return dvr;
+ }
+
+ private void tune() {
+ DvbtFrontendSettings feSettings = DvbtFrontendSettings.builder()
+ .setFrequency(FREQUENCY)
+ .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_AUTO)
+ .setBandwidth(DvbtFrontendSettings.BANDWIDTH_8MHZ)
+ .setConstellation(DvbtFrontendSettings.CONSTELLATION_AUTO)
+ .setHierarchy(DvbtFrontendSettings.HIERARCHY_AUTO)
+ .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
+ .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
+ .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_AUTO)
+ .setHighPriority(true)
+ .setStandard(DvbtFrontendSettings.STANDARD_T)
+ .build();
+ mTuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() {
+ @Override
+ public void onTuneEvent(int tuneEvent) {
+ if (DEBUG) {
+ Log.d(TAG, "onTuneEvent " + tuneEvent);
+ }
+ long read = mDvr.read(INPUT_FILE_MAX_SIZE);
+ if (DEBUG) {
+ Log.d(TAG, "read=" + read);
+ }
+ }
+ });
+ mTuner.tune(feSettings);
+ }
+
+ private boolean initCodec() {
+ if (mMediaCodec != null) {
+ mMediaCodec.release();
+ mMediaCodec = null;
+ }
+ try {
+ mMediaCodec = MediaCodec.createDecoderByType("video/avc");
+ mMediaCodec.configure(VIDEO_FORMAT, mSurface, null, 0);
+ } catch (IOException e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+
+ if (mMediaCodec == null) {
+ Log.e(TAG, "null codec!");
+ notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+ return false;
+ }
+ return true;
+ }
+
+ private void decodeInternal() {
+ mDataQueue = new ArrayDeque<>();
+ mSavedData = new ArrayList<>();
+ mTuner = new Tuner(mContext, mSessionId,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+
+ mAudioFilter = audioFilter();
+ mVideoFilter = videoFilter();
+ mAudioFilter.start();
+ mVideoFilter.start();
+ // use dvr playback to feed the data on platform without physical tuner
+ mDvr = dvrPlayback();
+ tune();
+ mDvr.start();
+ mMediaCodec.start();
+
+ try {
+ while (!Thread.interrupted()) {
+ if (!mDataReady) {
+ Thread.sleep(100);
+ }
+ if (!mDataQueue.isEmpty()) {
+ if (queueCodecInputBuffer(mDataQueue.getFirst())) {
+ // data consumed, remove.
+ mDataQueue.pollFirst();
+ }
+ } else if (SAVE_DATA) {
+ mDataQueue.addAll(mSavedData);
+ }
+ releaseCodecOutputBuffer();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error: " + e.getMessage());
+ }
+ }
+
+ private boolean queueCodecInputBuffer(MediaEvent mediaEvent) {
+ int res = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
+ if (res >= 0) {
+ ByteBuffer buffer = mMediaCodec.getInputBuffer(res);
+ if (buffer == null) {
+ throw new RuntimeException("Null decoder input buffer");
+ }
+
+ ByteBuffer data = mediaEvent.getLinearBlock().map();
+ int sampleSize = (int) mediaEvent.getDataLength();
+ int offset = (int) mediaEvent.getOffset();
+ long pts = mediaEvent.getPts();
+
+ if (offset > 0 && offset < data.limit()) {
+ data.position(offset);
+ } else {
+ data.position(0);
+ }
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "Decoder: Send data to decoder."
+ + " Sample size="
+ + sampleSize
+ + " pts="
+ + pts
+ + " limit="
+ + data.limit()
+ + " pos="
+ + data.position()
+ + " size="
+ + (data.limit() - data.position()));
+ }
+ while (data.position() < data.limit()) {
+ // fill codec input buffer
+ buffer.put(data.get());
+ }
+
+ mMediaCodec.queueInputBuffer(res, 0, sampleSize, pts, 0);
+ } else {
+ Log.d(TAG, "queueCodecInputBuffer res=" + res);
+ return false;
+ }
+ return true;
+ }
+
+ private void releaseCodecOutputBuffer() {
+ // play frames
+ BufferInfo bufferInfo = new BufferInfo();
+ int res = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
+ if (res >= 0) {
+ mMediaCodec.releaseOutputBuffer(res, true);
+ notifyVideoAvailable();
+ if (DEBUG) {
+ Log.d(TAG, "notifyVideoAvailable");
+ }
+ } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat format = mMediaCodec.getOutputFormat();
+ if (DEBUG) {
+ Log.d(TAG, "releaseCodecOutputBuffer: Output format changed:" + format);
+ }
+ } else if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ if (DEBUG) {
+ Log.d(TAG, "releaseCodecOutputBuffer: timeout");
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Return value of releaseCodecOutputBuffer:" + res);
+ }
+ }
+ }
+
}
} \ No newline at end of file