diff options
author | Adrian Roos <roosa@google.com> | 2017-05-05 20:16:08 +0000 |
---|---|---|
committer | Adrian Roos <roosa@google.com> | 2017-05-05 20:16:08 +0000 |
commit | 721bd0da688cd552737fbb753a00597f95103b95 (patch) | |
tree | 36da88c2d4365be7ee0a4dd0c732b9399d1c55ad /src/com/android/tv/tuner | |
parent | 3dfa929b24f38ac7836450176d88ceab41dc6ac5 (diff) | |
download | TV-721bd0da688cd552737fbb753a00597f95103b95.tar.gz |
Revert "Sync to ub-tv-dev at f0024d79653da8c8999a91f995431a645a6ff4a2"
This reverts commit 3dfa929b24f38ac7836450176d88ceab41dc6ac5.
Change-Id: I1c76f626d966b8d4793a19677a8840ed0424d3a7
Diffstat (limited to 'src/com/android/tv/tuner')
50 files changed, 898 insertions, 2408 deletions
diff --git a/src/com/android/tv/tuner/ChannelScanFileParser.java b/src/com/android/tv/tuner/ChannelScanFileParser.java index 8b06aaa9..2dd36074 100644 --- a/src/com/android/tv/tuner/ChannelScanFileParser.java +++ b/src/com/android/tv/tuner/ChannelScanFileParser.java @@ -18,7 +18,7 @@ package com.android.tv.tuner; import android.util.Log; -import com.android.tv.tuner.data.Channel; +import com.android.tv.tuner.data.nano.Channel; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/com/android/tv/tuner/TunerHal.java b/src/com/android/tv/tuner/TunerHal.java index 64394ea3..de19766e 100644 --- a/src/com/android/tv/tuner/TunerHal.java +++ b/src/com/android/tv/tuner/TunerHal.java @@ -20,9 +20,6 @@ import android.content.Context; import android.support.annotation.IntDef; import android.support.annotation.StringDef; import android.util.Log; -import android.util.Pair; - -import com.android.tv.Features; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -51,7 +48,6 @@ public abstract class TunerHal implements AutoCloseable { 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; @@ -73,33 +69,31 @@ public abstract class TunerHal implements AutoCloseable { */ public synchronized static TunerHal createInstance(Context context) { TunerHal tunerHal = null; - if (useBuiltInTuner(context)) { + if (getTunerType(context) == TUNER_TYPE_BUILT_IN) { } - if (tunerHal == null && UsbTunerHal.getNumberOfDevices(context) > 0) { + if (tunerHal == null) { tunerHal = new UsbTunerHal(context); } - return tunerHal != null && tunerHal.openFirstAvailable() ? tunerHal : null; + if (tunerHal.openFirstAvailable()) { + return tunerHal; + } + return null; } /** * Gets the number of tuner devices currently present. */ - public static Pair<Integer, Integer> getTunerTypeAndCount(Context context) { - if (useBuiltInTuner(context)) { + public static int getTunerCount(Context context) { + if (getTunerType(context) == TUNER_TYPE_BUILT_IN) { } - int usbTunerCount = UsbTunerHal.getNumberOfDevices(context); - if (usbTunerCount > 0) { - return new Pair<>(TUNER_TYPE_USB, usbTunerCount); - } - return new Pair<>(null, 0); + return UsbTunerHal.getNumberOfDevices(context); } /** - * Returns if tuner input service would use built-in tuners instead of USB tuners or network - * tuners. + * Gets the type of tuner devices currently used. */ - static boolean useBuiltInTuner(Context context) { - return false; + public static int getTunerType(Context context) { + return TUNER_TYPE_USB; } protected TunerHal(Context context) { @@ -112,14 +106,6 @@ public abstract class TunerHal implements AutoCloseable { return mIsStreaming; } - /** - * 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(); @@ -145,12 +131,9 @@ public abstract class TunerHal implements AutoCloseable { * * @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) { + public synchronized boolean tune(int frequency, @ModulationType String modulation) { if (!isDeviceOpen()) { Log.e(TAG, "There's no available device"); return false; diff --git a/src/com/android/tv/tuner/TunerInputController.java b/src/com/android/tv/tuner/TunerInputController.java index 65bbbdd0..d89b6a0c 100644 --- a/src/com/android/tv/tuner/TunerInputController.java +++ b/src/com/android/tv/tuner/TunerInputController.java @@ -16,40 +16,30 @@ package com.android.tv.tuner; -import android.app.AlarmManager; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.AsyncTask; +import android.media.tv.TvInputInfo; +import android.media.tv.TvInputManager; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.text.TextUtils; +import android.support.v4.os.BuildCompat; import android.util.Log; import android.widget.Toast; import com.android.tv.Features; -import com.android.tv.R; import com.android.tv.TvApplication; +import com.android.tv.tuner.R; 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 java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Map; -import java.util.concurrent.TimeUnit; /** * Controls the package visibility of {@link TunerTvInputService}. @@ -61,39 +51,10 @@ import java.util.concurrent.TimeUnit; public class TunerInputController extends BroadcastReceiver { private static final boolean DEBUG = true; 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"; - - /** - * Action of {@link Intent} to check network connection repeatedly when it is necessary. - */ - public static final String CHECKING_NETWORK_CONNECTION = - "com.android.tv.action.CHECKING_NETWORK_CONNECTION"; - - /** - * Action of {@link Intent} when network tuner is attached. - */ - public static final String NETWORK_TUNER_ATTACHED = - "com.android.tv.action.NETWORK_TUNER_ATTACHED"; - - /** - * Action of {@link Intent} when network tuner is detached. - */ - public static final String NETWORK_TUNER_DETACHED = - "com.android.tv.action.NETWORK_TUNER_DETACHED"; - - private static final String EXTRA_CHECKING_DURATION = - "com.android.tv.action.extra.CHECKING_DURATION"; - - 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 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) + new TunerDevice(0x2040, 0xb123), // WinTV-HVR-955Q + new TunerDevice(0x07ca, 0x0837) // AverTV Volar Hybrid Q }; private static final int MSG_ENABLE_INPUT_SERVICE = 1000; @@ -109,9 +70,7 @@ public class TunerInputController extends BroadcastReceiver { if (mDvbDeviceAccessor == null) { mDvbDeviceAccessor = new DvbDeviceAccessor(context); } - boolean enabled = mDvbDeviceAccessor.isDvbDeviceAvailable(); - enableTunerTvInputService( - context, enabled, false, enabled ? TunerHal.TUNER_TYPE_USB : null); + enableTunerTvInputService(context, mDvbDeviceAccessor.isDvbDeviceAvailable()); break; } } @@ -125,35 +84,14 @@ public class TunerInputController extends BroadcastReceiver { private final int vendorId; private final int productId; - // security patch level from which the specific tuner type is supported. - private final String minSecurityLevel; - - private TunerDevice(int vendorId, int productId, String minSecurityLevel) { + private TunerDevice(int vendorId, int productId) { this.vendorId = vendorId; this.productId = productId; - this.minSecurityLevel = minSecurityLevel; } private boolean equals(UsbDevice device) { return device.getVendorId() == vendorId && device.getProductId() == productId; } - - private boolean isSupported(String currentSecurityLevel) { - if (minSecurityLevel == null) { - return true; - } - - long supportSecurityLevelTimeStamp = 0; - long currentSecurityLevelTimestamp = 0; - try { - SimpleDateFormat format = new SimpleDateFormat(SECURITY_PATCH_LEVEL_FORMAT); - supportSecurityLevelTimeStamp = format.parse(minSecurityLevel).getTime(); - currentSecurityLevelTimestamp = format.parse(currentSecurityLevel).getTime(); - } catch (ParseException e) { - } - return supportSecurityLevelTimeStamp != 0 - && supportSecurityLevelTimeStamp <= currentSecurityLevelTimestamp; - } } @Override @@ -161,20 +99,17 @@ public class TunerInputController extends BroadcastReceiver { if (DEBUG) Log.d(TAG, "Broadcast intent received:" + intent); TvApplication.setCurrentRunningProcess(context, true); if (!Features.TUNER.isEnabled(context)) { - enableTunerTvInputService(context, false, false, null); + enableTunerTvInputService(context, false); return; } - SharedPreferences sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(context); switch (intent.getAction()) { case Intent.ACTION_BOOT_COMPLETED: - executeNetworkTunerDiscoveryAsyncTask(context, INITIAL_CHECKING_DURATION_MS); case TvApplication.ACTION_APPLICATION_FIRST_LAUNCHED: case UsbManager.ACTION_USB_DEVICE_ATTACHED: case UsbManager.ACTION_USB_DEVICE_DETACHED: - if (TunerHal.useBuiltInTuner(context)) { - enableTunerTvInputService(context, true, false, TunerHal.TUNER_TYPE_BUILT_IN); + if (TunerInputInfoUtils.isBuiltInTuner(context)) { + enableTunerTvInputService(context, true); break; } // Falls back to the below to check USB tuner devices. @@ -188,41 +123,7 @@ public class TunerInputController extends BroadcastReceiver { mHandler.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); - break; - } - enableTunerTvInputService(context, false, false, TextUtils - .equals(intent.getAction(), UsbManager.ACTION_USB_DEVICE_DETACHED) ? - TunerHal.TUNER_TYPE_USB : null); - } - 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)); - break; - case NETWORK_TUNER_ATTACHED: - // 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); - break; - case NETWORK_TUNER_DETACHED: - 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); + enableTunerTvInputService(context, false); } break; } @@ -237,15 +138,12 @@ public class TunerInputController extends BroadcastReceiver { private boolean isUsbTunerConnected(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); - 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.equals(device)) { Log.i(TAG, "Tuner found"); return true; } @@ -260,8 +158,7 @@ public class TunerInputController extends BroadcastReceiver { * @param context {@link Context} instance * @param enabled {@code true} to enable the service; otherwise {@code false} */ - private void enableTunerTvInputService(Context context, boolean enabled, - boolean forceDontKillApp, Integer tunerType) { + private void enableTunerTvInputService(Context context, boolean enabled) { if (DEBUG) Log.d(TAG, "enableTunerTvInputService: " + enabled); PackageManager pm = context.getPackageManager(); ComponentName componentName = new ComponentName(context, TunerTvInputService.class); @@ -273,8 +170,7 @@ public class TunerInputController extends BroadcastReceiver { // 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() + int flags = TvApplication.getSingletons(context).getMainActivityWrapper().isCreated() ? PackageManager.DONT_KILL_APP : 0; int newState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; @@ -283,67 +179,14 @@ public class TunerInputController extends BroadcastReceiver { TunerSetupActivity.onTvInputEnabled(context, enabled); // 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(); - } + if (!enabled) { + Toast.makeText( + context, R.string.msg_usb_device_detached, Toast.LENGTH_SHORT).show(); } if (DEBUG) Log.d(TAG, "Status updated:" + enabled); } else if (enabled) { - // When # of tuners is changed or the tuner input service is switching from/to using - // network tuners or the device just boots. + // When # of USB tuners is changed or the device just boots. TunerInputInfoUtils.updateTunerInputInfo(context); } } - - /** - * Discovers a network tuner. If the network connection is down, it won't repeatedly checking. - */ - public static void executeNetworkTunerDiscoveryAsyncTask(final Context context) { - executeNetworkTunerDiscoveryAsyncTask(context, 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. - */ - private static void executeNetworkTunerDiscoveryAsyncTask(final Context context, - final long repeatedDurationMs) { - if (!Features.NETWORK_TUNER.isEnabled(context)) { - return; - } - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - if (isNetworkConnected(context)) { - // 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, TunerInputController.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 null; - } - }.execute(); - } - - private static boolean isNetworkConnected(Context context) { - ConnectivityManager cm = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = cm.getActiveNetworkInfo(); - return networkInfo != null && networkInfo.isConnected(); - } } diff --git a/src/com/android/tv/tuner/TunerPreferences.java b/src/com/android/tv/tuner/TunerPreferences.java index a387be74..1547e3ae 100644 --- a/src/com/android/tv/tuner/TunerPreferences.java +++ b/src/com/android/tv/tuner/TunerPreferences.java @@ -39,7 +39,6 @@ public class 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"; @@ -87,7 +86,8 @@ public class TunerPreferences { /** * Releases the resources. */ - public static synchronized void release(Context context) { + @MainThread + public static void release(Context context) { if (useContentProvider(context) && sContentObserver != null) { context.getContentResolver().unregisterContentObserver(sContentObserver); } @@ -99,8 +99,7 @@ public class TunerPreferences { * This preferences is used across processes, so the preferences should be loaded again when the * databases changes. */ - @MainThread - public static void loadPreferences(Context context) { + public static synchronized void loadPreferences(Context context) { if (sLoadPreferencesTask != null && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) { sLoadPreferencesTask.cancel(true); @@ -114,7 +113,8 @@ public class TunerPreferences { return TisConfiguration.isPackagedWithLiveChannels(context); } - public static synchronized int getChannelDataVersion(Context context) { + @MainThread + public static int getChannelDataVersion(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getInt(PREFS_KEY_CHANNEL_DATA_VERSION, @@ -126,7 +126,8 @@ public class TunerPreferences { } } - public static synchronized void setChannelDataVersion(Context context, int version) { + @MainThread + public static void setChannelDataVersion(Context context, int version) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_CHANNEL_DATA_VERSION, version); } else { @@ -136,7 +137,8 @@ public class TunerPreferences { } } - public static synchronized int getScannedChannelCount(Context context) { + @MainThread + public static int getScannedChannelCount(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getInt(PREFS_KEY_SCANNED_CHANNEL_COUNT); @@ -146,7 +148,8 @@ public class TunerPreferences { } } - public static synchronized void setScannedChannelCount(Context context, int channelCount) { + @MainThread + public static void setScannedChannelCount(Context context, int channelCount) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCANNED_CHANNEL_COUNT, channelCount); } else { @@ -156,25 +159,8 @@ public class TunerPreferences { } } - 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) { + @MainThread + public static boolean isScanDone(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_SCAN_DONE); @@ -184,7 +170,8 @@ public class TunerPreferences { } } - public static synchronized void setScanDone(Context context) { + @MainThread + public static void setScanDone(Context context) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_SCAN_DONE, true); } else { @@ -194,7 +181,8 @@ public class TunerPreferences { } } - public static synchronized boolean shouldShowSetupActivity(Context context) { + @MainThread + public static boolean shouldShowSetupActivity(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP); @@ -204,7 +192,8 @@ public class TunerPreferences { } } - public static synchronized void setShouldShowSetupActivity(Context context, boolean need) { + @MainThread + public static void setShouldShowSetupActivity(Context context, boolean need) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_LAUNCH_SETUP, need); } else { @@ -214,7 +203,8 @@ public class TunerPreferences { } } - public static synchronized boolean getStoreTsStream(Context context) { + @MainThread + public static boolean getStoreTsStream(Context context) { SoftPreconditions.checkState(sInitialized); if (useContentProvider(context)) { return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false); @@ -224,7 +214,8 @@ public class TunerPreferences { } } - public static synchronized void setStoreTsStream(Context context, boolean shouldStore) { + @MainThread + public static void setStoreTsStream(Context context, boolean shouldStore) { if (useContentProvider(context)) { setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore); } else { @@ -238,23 +229,8 @@ public class TunerPreferences { 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, 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) { + @MainThread + private static void setPreference(final Context context, final String key, final String value) { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { @@ -273,6 +249,18 @@ public class TunerPreferences { }.execute(); } + @MainThread + private static void setPreference(Context context, String key, int value) { + sPreferenceValues.putInt(key, value); + setPreference(context, key, Integer.toString(value)); + } + + @MainThread + private static void setPreference(Context context, String key, boolean value) { + sPreferenceValues.putBoolean(key, value); + setPreference(context, key, Boolean.toString(value)); + } + private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> { private final Context mContext; private LoadPreferencesTask(Context context) { @@ -304,9 +292,6 @@ public class TunerPreferences { case PREFS_KEY_STORE_TS_STREAM: bundle.putBoolean(key, Boolean.parseBoolean(value)); break; - case PREFS_KEY_LAST_POSTAL_CODE: - bundle.putString(key, value); - break; } } } @@ -318,10 +303,8 @@ public class TunerPreferences { } @Override - protected synchronized void onPostExecute(Bundle bundle) { - if (bundle != null) { - sPreferenceValues.putAll(bundle); - } + protected void onPostExecute(Bundle bundle) { + sPreferenceValues.putAll(bundle); } } -}
\ No newline at end of file +} diff --git a/src/com/android/tv/tuner/UsbTunerHal.java b/src/com/android/tv/tuner/UsbTunerHal.java index b1608ede..22e35ea1 100644 --- a/src/com/android/tv/tuner/UsbTunerHal.java +++ b/src/com/android/tv/tuner/UsbTunerHal.java @@ -169,10 +169,6 @@ public class UsbTunerHal extends TunerHal { * 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; - } + return (new DvbDeviceAccessor(context)).getNumOfDvbDevices(); } } diff --git a/src/com/android/tv/tuner/cc/CaptionLayout.java b/src/com/android/tv/tuner/cc/CaptionLayout.java index c41f1014..a88538df 100644 --- a/src/com/android/tv/tuner/cc/CaptionLayout.java +++ b/src/com/android/tv/tuner/cc/CaptionLayout.java @@ -19,7 +19,7 @@ package com.android.tv.tuner.cc; import android.content.Context; import android.util.AttributeSet; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.layout.ScaledLayout; /** diff --git a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java index 3aa40982..3c75caa9 100644 --- a/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java +++ b/src/com/android/tv/tuner/cc/CaptionTrackRenderer.java @@ -27,7 +27,7 @@ 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.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import java.util.ArrayList; import java.util.concurrent.TimeUnit; diff --git a/src/com/android/tv/tuner/cc/Cea708Parser.java b/src/com/android/tv/tuner/cc/Cea708Parser.java index c43fe512..92ab0620 100644 --- a/src/com/android/tv/tuner/cc/Cea708Parser.java +++ b/src/com/android/tv/tuner/cc/Cea708Parser.java @@ -140,7 +140,6 @@ public class Cea708Parser { 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() { @@ -333,14 +332,12 @@ public class Cea708Parser { mDiscoveredNumBytes.put( serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0)); } - if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime() - || !mFirstServiceNumberDiscovered) { + if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()) { 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(); diff --git a/src/com/android/tv/tuner/data/PsiData.java b/src/com/android/tv/tuner/data/PsiData.java index 2c8a52db..67700c6a 100644 --- a/src/com/android/tv/tuner/data/PsiData.java +++ b/src/com/android/tv/tuner/data/PsiData.java @@ -17,8 +17,8 @@ package com.android.tv.tuner.data; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import java.util.List; diff --git a/src/com/android/tv/tuner/data/PsipData.java b/src/com/android/tv/tuner/data/PsipData.java index ac7fdedb..aead4be8 100644 --- a/src/com/android/tv/tuner/data/PsipData.java +++ b/src/com/android/tv/tuner/data/PsipData.java @@ -20,11 +20,11 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.text.format.DateUtils; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +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 com.android.tv.tuner.util.StringUtils; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/tv/tuner/data/TunerChannel.java b/src/com/android/tv/tuner/data/TunerChannel.java index 41f66e7d..89079d77 100644 --- a/src/com/android/tv/tuner/data/TunerChannel.java +++ b/src/com/android/tv/tuner/data/TunerChannel.java @@ -19,11 +19,12 @@ package com.android.tv.tuner.data; import android.support.annotation.NonNull; import android.util.Log; -import com.android.tv.tuner.data.Channel.TunerChannelProto; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +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.android.tv.tuner.util.StringUtils; import com.google.protobuf.nano.MessageNano; import java.io.IOException; @@ -39,11 +40,6 @@ import java.util.Objects; 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", @@ -67,7 +63,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks // 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, @@ -150,44 +145,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return new TunerChannel(channel, 0, pmtItems, Channel.TYPE_FILE); } - /** - * 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(programNumber, Collections.EMPTY_LIST); - 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; } @@ -236,7 +193,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return mProto.videoPid; } - synchronized public void setVideoPid(int videoPid) { + public void setVideoPid(int videoPid) { mProto.videoPid = videoPid; } @@ -262,7 +219,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return Ints.asList(mProto.audioPids); } - synchronized public void setAudioPids(List<Integer> audioPids) { + public void setAudioPids(List<Integer> audioPids) { mProto.audioPids = Ints.toArray(audioPids); } @@ -270,7 +227,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return Ints.asList(mProto.audioStreamTypes); } - synchronized public void setAudioStreamTypes(List<Integer> audioStreamTypes) { + public void setAudioStreamTypes(List<Integer> audioStreamTypes) { mProto.audioStreamTypes = Ints.toArray(audioStreamTypes); } @@ -282,32 +239,32 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return mProto.type; } - synchronized public void setFilepath(String filepath) { - mProto.filepath = filepath == null ? "" : filepath; + public void setFilepath(String filepath) { + mProto.filepath = filepath; } public String getFilepath() { return mProto.filepath; } - synchronized public void setVirtualMajor(int virtualMajor) { + public void setVirtualMajor(int virtualMajor) { mProto.virtualMajor = virtualMajor; } - synchronized public void setVirtualMinor(int virtualMinor) { + public void setVirtualMinor(int virtualMinor) { mProto.virtualMinor = virtualMinor; } - synchronized public void setShortName(String shortName) { - mProto.shortName = shortName == null ? "" : shortName; + public void setShortName(String shortName) { + mProto.shortName = shortName; } - synchronized public void setFrequency(int frequency) { + public void setFrequency(int frequency) { mProto.frequency = frequency; } - synchronized public void setModulation(String modulation) { - mProto.modulation = modulation == null ? "" : modulation; + public void setModulation(String modulation) { + mProto.modulation = modulation; } public boolean hasVideo() { @@ -322,18 +279,13 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return mProto.channelId; } - synchronized public void setChannelId(long channelId) { + 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); + if (mProto.virtualMajor != 0 && mProto.virtualMinor != 0) { + return String.format("%d-%d", mProto.virtualMajor, mProto.virtualMinor); } else if (mProto.virtualMajor != 0) { return Integer.toString(mProto.virtualMajor); } else { @@ -346,7 +298,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks } @Override - synchronized public void setHasCaptionTrack() { + public void setHasCaptionTrack() { mProto.hasCaptionTrack = true; } @@ -360,7 +312,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.audioTracks)); } - synchronized public void setAudioTracks(List<AtscAudioTrack> audioTracks) { + public void setAudioTracks(List<AtscAudioTrack> audioTracks) { mProto.audioTracks = audioTracks.toArray(new AtscAudioTrack[audioTracks.size()]); } @@ -369,11 +321,11 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks return Collections.unmodifiableList(Arrays.asList(mProto.captionTracks)); } - synchronized public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) { + public void setCaptionTracks(List<AtscCaptionTrack> captionTracks) { mProto.captionTracks = captionTracks.toArray(new AtscCaptionTrack[captionTracks.size()]); } - synchronized public void selectAudioTrack(int index) { + public void selectAudioTrack(int index) { if (0 <= index && index < mProto.audioPids.length) { mProto.audioTrackIndex = index; } else { @@ -381,22 +333,6 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks } } - 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) { @@ -423,10 +359,7 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks 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()); } @@ -441,19 +374,12 @@ public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracks @Override public int hashCode() { - return Objects.hash(getFrequency(), getProgramNumber(), getName(), getFilepath()); + return Objects.hash(getFrequency(), getProgramNumber(), 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 byte[] toByteArray() { + return MessageNano.toByteArray(mProto); } public static TunerChannel parseFrom(byte[] data) { diff --git a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java index 89641530..c105e222 100644 --- a/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/ExoPlayerSampleExtractor.java @@ -23,29 +23,17 @@ 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.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.extractor.ExtractorSampleSource; +import com.google.android.exoplayer.extractor.ExtractorSampleSource.EventListener; +import com.google.android.exoplayer.upstream.Allocator; 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.extractor.DefaultExtractorsFactory; -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.ac3.Ac3DefaultTrackRenderer; +import com.google.android.exoplayer.upstream.DefaultAllocator; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer; @@ -54,11 +42,10 @@ 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; +import java.util.concurrent.atomic.AtomicLong; /** * A class that extracts samples from a live broadcast stream while storing the sample on the disk. @@ -67,7 +54,11 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ExoPlayerSampleExtractor implements SampleExtractor { private static final String TAG = "ExoPlayerSampleExtracto"; - private static final int INVALID_TRACK_INDEX = -1; + // Buffer segment size for memory allocator. Copied from demo implementation of ExoPlayer. + private static final int BUFFER_SEGMENT_SIZE_IN_BYTES = 64 * 1024; + // Buffer segment count for sample source. Copied from demo implementation of ExoPlayer. + private static final int BUFFER_SEGMENT_COUNT = 256; + private final HandlerThread mSourceReaderThread; private final long mId; @@ -79,69 +70,36 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { 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, + public ExoPlayerSampleExtractor(Uri uri, DataSource source, BufferManager bufferManager, PlaybackBufferListener bufferListener, boolean isRecording) { // It'll be used as a timeshift file chunk name's prefix. mId = System.currentTimeMillis(); + Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE_IN_BYTES); EventListener eventListener = new EventListener() { + @Override - public void onLoadError(IOException error) { - mError = error; + public void onLoadError(int sourceId, IOException e) { + mError = e; } }; 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 DefaultExtractorsFactory(), + mSourceReaderWorker = new SourceReaderWorker(new ExtractorSampleSource(uri, source, + allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE_IN_BYTES, // Do not create a handler if we not on a looper. e.g. test. - Looper.myLooper() != null ? new Handler() : null, eventListener)); + Looper.myLooper() != null ? new Handler() : null, + eventListener, 0)); if (isRecording) { mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, false, RecordingSampleBuffer.BUFFER_REASON_RECORDING); } else { - if (bufferManager == null) { + if (bufferManager == null || bufferManager.isDisabled()) { mSampleBuffer = new SimpleSampleBuffer(bufferListener); } else { mSampleBuffer = new RecordingSampleBuffer(bufferManager, bufferListener, true, @@ -156,141 +114,43 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { mOnCompletionListenerHandler = handler; } - private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback { + private class SourceReaderWorker implements Handler.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 final SampleSource mSampleSource; + private SampleSource.SampleSourceReader mSampleSourceReader; private boolean[] mTrackMetEos; private boolean mMetEos = false; private long mCurrentPosition; - private DecoderInputBuffer mDecoderInputBuffer; - private SampleHolder mSampleHolder; - private boolean mPrepareRequested; - public SourceReaderWorker(MediaSource sampleSource) { + public SourceReaderWorker(SampleSource 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; - } + mPrepared = prepare(); + if (!mPrepared && mExceptionOnPrepare == null) { + mSourceReaderHandler + .sendEmptyMessageDelayed(MSG_PREPARE, RETRY_INTERVAL_MS); + } else{ + mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); } return true; case MSG_FETCH_SAMPLES: boolean didSomething = false; + SampleHolder sample = new SampleHolder( + SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); ConditionVariable conditionVariable = new ConditionVariable(); - int trackCount = mStreams.length; + int trackCount = mSampleSourceReader.getTrackCount(); for (int i = 0; i < trackCount; ++i) { - if (!mTrackMetEos[i] && C.RESULT_NOTHING_READ - != fetchSample(i, mSampleHolder, conditionVariable)) { + if (!mTrackMetEos[i] && SampleSource.NOTHING_READ + != fetchSample(i, sample, conditionVariable)) { if (mMetEos) { // If mMetEos was on during fetchSample() due to an error, // fetching from other tracks is not necessary. @@ -299,7 +159,6 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { didSomething = true; } } - mMediaPeriod.continueLoading(mCurrentPosition); if (!mMetEos) { if (didSomething) { mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES); @@ -312,10 +171,17 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } return true; case MSG_RELEASE: - if (mMediaPeriod != null) { - mSampleSource.releasePeriod(mMediaPeriod); - mSampleSource.releaseSource(); - mMediaPeriod = null; + if (mSampleSourceReader != null) { + if (mPrepared) { + // ExtractorSampleSource expects all the tracks should be disabled + // before releasing. + int count = mSampleSourceReader.getTrackCount(); + for (int i = 0; i < count; ++i) { + mSampleSourceReader.disable(i); + } + } + mSampleSourceReader.release(); + mSampleSourceReader = null; } cleanUp(); mSourceReaderHandler.removeCallbacksAndMessages(null); @@ -324,109 +190,91 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { return false; } + private boolean prepare() { + if (mSampleSourceReader == null) { + mSampleSourceReader = mSampleSource.register(); + } + if(!mSampleSourceReader.prepare(0)) { + return false; + } + if (mTrackFormats == null) { + int trackCount = mSampleSourceReader.getTrackCount(); + mTrackMetEos = new boolean[trackCount]; + List<MediaFormat> trackFormats = new ArrayList<>(); + for (int i = 0; i < trackCount; i++) { + trackFormats.add(mSampleSourceReader.getFormat(i)); + mSampleSourceReader.enable(i, 0); + + } + 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 false; + } + } + return true; + } + 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; + mSampleSourceReader.continueBuffering(track, mCurrentPosition); + + MediaFormatHolder formatHolder = new MediaFormatHolder(); + sample.clearData(); + int ret = mSampleSourceReader.readData(track, mCurrentPosition, formatHolder, sample); + if (ret == SampleSource.SAMPLE_READ) { + if (mCurrentPosition < sample.timeUs) { + mCurrentPosition = sample.timeUs; } try { Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track); if (lastExtractedPositionUs == null) { - mLastExtractedPositionUsMap.put(track, mDecoderInputBuffer.timeUs); + mLastExtractedPositionUsMap.put(track, sample.timeUs); } else { mLastExtractedPositionUsMap.put(track, - Math.max(lastExtractedPositionUs, mDecoderInputBuffer.timeUs)); + Math.max(lastExtractedPositionUs, sample.timeUs)); } - queueSample(track, conditionVariable); + queueSample(track, sample, conditionVariable); } catch (IOException e) { mLastExtractedPositionUsMap.clear(); mMetEos = true; mSampleBuffer.setEos(); } - } else if (ret == C.RESULT_END_OF_INPUT) { + } else if (ret == SampleSource.END_OF_STREAM) { mTrackMetEos[track] = true; for (int i = 0; i < mTrackMetEos.length; ++i) { if (!mTrackMetEos[i]) { break; } - if (i == mTrackMetEos.length - 1) { + if (i == mTrackMetEos.length -1) { mMetEos = true; mSampleBuffer.setEos(); } } } - // TODO: Handle C.RESULT_FORMAT_READ for dynamic resolution change. b/28169263 + // TODO: Handle SampleSource.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 - - Ac3DefaultTrackRenderer.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(); - } + private void queueSample(int index, SampleHolder sample, ConditionVariable conditionVariable) + throws IOException { + long writeStartTimeNs = SystemClock.elapsedRealtimeNanos(); + mSampleBuffer.writeSample(index, sample, conditionVariable); + + // Checks whether the storage has enough bandwidth for recording samples. + if (mSampleBuffer.isWriteSpeedSlow(sample.size, + SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { + mSampleBuffer.handleWriteSpeedSlow(); } } @@ -480,7 +328,7 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } @Override - public boolean continueBuffering(long positionUs) { + public boolean continueBuffering(long positionUs) { return mSampleBuffer.continueBuffering(positionUs); } @@ -538,14 +386,12 @@ public class ExoPlayerSampleExtractor implements SampleExtractor { } 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()); - } + long lastExtractedPositionUs = Long.MAX_VALUE; + for (long value : mLastExtractedPositionUsMap.values()) { + lastExtractedPositionUs = Math.min(lastExtractedPositionUs, value); } - if (lastExtractedPositionUs == Long.MIN_VALUE) { - lastExtractedPositionUs = com.google.android.exoplayer.C.UNKNOWN_TIME_US; + if (lastExtractedPositionUs == Long.MAX_VALUE) { + lastExtractedPositionUs = 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 index b7e42a7c..ec7b4b16 100644 --- a/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java +++ b/src/com/android/tv/tuner/exoplayer/FileSampleExtractor.java @@ -25,6 +25,7 @@ import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer; import com.android.tv.tuner.tvinput.PlaybackBufferListener; import android.os.Handler; +import android.util.Pair; import java.io.IOException; import java.util.ArrayList; @@ -60,17 +61,18 @@ public class FileSampleExtractor implements SampleExtractor{ @Override public boolean prepare() throws IOException { - List<BufferManager.TrackFormat> trackFormatList = mBufferManager.readTrackInfoFiles(); - if (trackFormatList == null || trackFormatList.isEmpty()) { + ArrayList<Pair<String, android.media.MediaFormat>> trackInfos = + mBufferManager.readTrackInfoFiles(); + if (trackInfos == null || trackInfos.isEmpty()) { throw new IOException("Cannot find meta files for the recording."); } - mTrackCount = trackFormatList.size(); + mTrackCount = trackInfos.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)); + Pair<String, android.media.MediaFormat> pair = trackInfos.get(i); + ids.add(pair.first); + mTrackFormats.add(MediaFormatUtil.createMediaFormat(pair.second)); } mSampleBuffer = new RecordingSampleBuffer(mBufferManager, mBufferListener, true, RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK); diff --git a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java index ba0edf20..381b22e9 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java @@ -39,8 +39,8 @@ 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.ac3.Ac3DefaultTrackRenderer; -import com.android.tv.tuner.exoplayer.ac3.Ac3MediaCodecTrackRenderer; +import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer; +import com.android.tv.tuner.exoplayer.ac3.Ac3TrackRenderer; import com.android.tv.tuner.source.TsDataSource; import com.android.tv.tuner.source.TsDataSourceManager; import com.android.tv.tuner.tvinput.EventDetector; @@ -48,12 +48,11 @@ import com.android.tv.tuner.tvinput.EventDetector; 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, - Ac3DefaultTrackRenderer.EventListener, - Ac3MediaCodecTrackRenderer.Ac3EventListener { +/** + * MPEG-2 TS stream player implementation using ExoPlayer. + */ +public class MpegTsPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener, + Ac3PassthroughTrackRenderer.EventListener, Ac3TrackRenderer.Ac3EventListener { private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER; /** @@ -305,10 +304,8 @@ public class MpegTsPlayer SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed())); mPlayer.setPlayWhenReady(true); mTrickplayRunning = true; - if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, - Ac3DefaultTrackRenderer.MSG_SET_PLAYBACK_SPEED, + if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED, playbackParams.getSpeed()); } else { mPlayer.sendMessage(mAudioRenderer, @@ -320,9 +317,10 @@ public class MpegTsPlayer private void stopSmoothTrickplay(boolean calledBySeek) { if (mTrickplayRunning) { mTrickplayRunning = false; - if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_PLAYBACK_SPEED, 1.0f); + if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, + Ac3PassthroughTrackRenderer.MSG_SET_PLAYBACK_SPEED, + 1.0f); } else { mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_PLAYBACK_PARAMS, @@ -425,8 +423,8 @@ public class MpegTsPlayer */ public void setVolume(float volume) { mVolume = volume; - if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) { - mPlayer.sendMessage(mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_VOLUME, volume); + if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_VOLUME, volume); } else { mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, volume); @@ -439,9 +437,9 @@ public class MpegTsPlayer * @param enable enables the audio when {@code true}, disables otherwise. */ public void setAudioTrack(boolean enable) { - if (mAudioRenderer instanceof Ac3DefaultTrackRenderer) { - mPlayer.sendMessage( - mAudioRenderer, Ac3DefaultTrackRenderer.MSG_SET_AUDIO_TRACK, enable ? 1 : 0); + if (mAudioRenderer instanceof Ac3PassthroughTrackRenderer) { + mPlayer.sendMessage(mAudioRenderer, Ac3PassthroughTrackRenderer.MSG_SET_AUDIO_TRACK, + enable ? 1 : 0); } else { mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, enable ? mVolume : 0.0f); @@ -497,28 +495,6 @@ public class MpegTsPlayer } /** - * 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() { @@ -674,4 +650,4 @@ public class MpegTsPlayer } } } -}
\ 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 index a1a97d3d..0e46c9cf 100644 --- a/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java +++ b/src/com/android/tv/tuner/exoplayer/MpegTsRendererBuilder.java @@ -21,10 +21,9 @@ import android.content.Context; 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.ac3.Ac3DefaultTrackRenderer; +import com.android.tv.tuner.exoplayer.ac3.Ac3PassthroughTrackRenderer; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.tvinput.PlaybackBufferListener; @@ -53,12 +52,10 @@ public class MpegTsRendererBuilder implements RendererBuilder { SampleSource sampleSource = new MpegTsSampleSource(extractor); MpegTsVideoTrackRenderer videoRenderer = new MpegTsVideoTrackRenderer(mContext, sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer); - // TODO: Only using Ac3DefaultTrackRenderer for A/V sync issue. We will use - // {@link Ac3MediaCodecTrackRenderer} when we use ExoPlayer's extractor. - TrackRenderer audioRenderer = - new Ac3DefaultTrackRenderer( - sampleSource, mpegTsPlayer.getMainHandler(), mpegTsPlayer, - !Features.AC3_SOFTWARE_DECODE.isEnabled(mContext)); + // TODO: Only using Ac3PassthroughTrackRenderer for A/V sync issue. We will use + // {@link Ac3TrackRenderer} when we use ExoPlayer's extractor. + TrackRenderer audioRenderer = new Ac3PassthroughTrackRenderer(sampleSource, + mpegTsPlayer.getMainHandler(), mpegTsPlayer); Cea708TextTrackRenderer textRenderer = new Cea708TextTrackRenderer(sampleSource); TrackRenderer[] renderers = new TrackRenderer[MpegTsPlayer.RENDERER_COUNT]; diff --git a/src/com/android/tv/tuner/exoplayer/ac3/Ac3DefaultTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java index d442fde8..9dae2e34 100644 --- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3DefaultTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java @@ -23,15 +23,16 @@ 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.MediaCodecAudioTrackRenderer; 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.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.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.android.tv.tuner.tvinput.TunerDebug; import java.io.IOException; @@ -39,9 +40,9 @@ import java.nio.ByteBuffer; import java.util.ArrayList; /** - * Decodes and renders AC3 audio. Supports passthrough playback and ffmpeg based software decoding. + * Decodes and renders AC3 audio. */ -public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock { +public class Ac3PassthroughTrackRenderer 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; @@ -50,14 +51,7 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock // One AC3 sample has 1536 frames, and its duration is 32ms. public static final long AC3_SAMPLE_DURATION_US = 32000; - // 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 = "Ac3DefaultTrackRenderer"; + private static final String TAG = "Ac3PassthroughTrackRenderer"; private static final boolean DEBUG = false; /** @@ -99,8 +93,6 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock private final AudioClock mAudioClock; private MediaFormat mFormat; - private boolean mFormatConfigured; - private int mSampleSize; private final ByteBuffer mOutputBuffer; private boolean mOutputReady; private int mTrackIndex; @@ -114,15 +106,10 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock private long mInterpolatedTimeUs; private long mPreviousPositionUs; private boolean mIsStopped; - private boolean mEnabled = true; - private boolean mIsMuted; private ArrayList<Integer> mTracksIndex; - public Ac3DefaultTrackRenderer( - SampleSource source, - Handler eventHandler, - EventListener listener, - boolean usePassthrough) { + public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler, + EventListener listener) { mSource = source.register(); mEventHandler = eventHandler; mEventListener = listener; @@ -338,38 +325,14 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock } } - 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 { - mFormat = formatHolder.format; - mFormatConfigured = true; + mFormat = formatHolder.format; if (DEBUG) { Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString()); } clearDecodeState(); - 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); + AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16()); } private boolean feedInputBuffer() throws IOException, ExoPlaybackException { @@ -396,11 +359,8 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock return false; } default: { - if (mSampleHolder.size != mSampleSize && mFormatConfigured) { - onSampleSizeChanged(mSampleHolder.size); - } mSampleHolder.data.flip(); - decodeDone(mSampleHolder.data, mSampleHolder.timeUs); + decodeDone(mSampleHolder.data, mSampleHolder.timeUs); return true; } } @@ -551,29 +511,24 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock 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); + AUDIO_TRACK.setVolume((Float) message); break; case MSG_SET_AUDIO_TRACK: - mEnabled = (Integer) message == 1; - setStatus(mEnabled); + boolean enabled = (Integer) message == 1; + 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()); + } break; case MSG_SET_PLAYBACK_SPEED: mAudioClock.setPlaybackSpeed((Float) message); @@ -582,21 +537,4 @@ public class Ac3DefaultTrackRenderer extends TrackRenderer implements MediaClock 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/ac3/Ac3MediaCodecTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java index 604959d1..2bf86b5a 100644 --- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3MediaCodecTrackRenderer.java +++ b/src/com/android/tv/tuner/exoplayer/ac3/Ac3TrackRenderer.java @@ -25,14 +25,14 @@ 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. + * <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 Ac3MediaCodecTrackRenderer extends MediaCodecAudioTrackRenderer { - private final String TAG = "Ac3MediaCodecTrackRenderer"; +public class Ac3TrackRenderer extends MediaCodecAudioTrackRenderer { + private final String TAG = "Ac3TrackRenderer"; private final boolean DEBUG = false; private final Ac3EventListener mListener; @@ -47,11 +47,8 @@ public class Ac3MediaCodecTrackRenderer extends MediaCodecAudioTrackRenderer { void onAudioTrackSetPlaybackParamsError(IllegalArgumentException e); } - public Ac3MediaCodecTrackRenderer( - SampleSource source, - MediaCodecSelector mediaCodecSelector, - Handler eventHandler, - EventListener eventListener) { + public Ac3TrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, + Handler eventHandler, EventListener eventListener) { super(source, mediaCodecSelector, eventHandler, eventListener); mListener = (Ac3EventListener) eventListener; } diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java index 6f152490..bfdf08ac 100644 --- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java +++ b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackMonitor.java @@ -98,8 +98,8 @@ public class AudioTrackMonitor { long now = SystemClock.elapsedRealtime(); if (mExpireMs != 0 && now >= mExpireMs) { if (DEBUG) { - long sampleDuration = - (mTotalCount - 1) * Ac3DefaultTrackRenderer.AC3_SAMPLE_DURATION_US / 1000; + long sampleDuration = (mTotalCount - 1) * + Ac3PassthroughTrackRenderer.AC3_SAMPLE_DURATION_US / 1000; long totalDuration = now - mStartMs; StringBuilder ptsBuilder = new StringBuilder(); ptsBuilder.append("PTS received ").append(mSampleCount).append(", ") diff --git a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java index 393e12c3..bc3c5d00 100644 --- a/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java +++ b/src/com/android/tv/tuner/exoplayer/ac3/AudioTrackWrapper.java @@ -18,7 +18,6 @@ package com.android.tv.tuner.exoplayer.ac3; import android.media.MediaFormat; -import com.google.android.exoplayer.C; import com.google.android.exoplayer.audio.AudioTrack; import java.nio.ByteBuffer; @@ -29,10 +28,6 @@ import java.nio.ByteBuffer; * 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 = - Ac3DefaultTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK; private final AudioTrack mAudioTrack = new AudioTrack(); private int mAudioSessionID; private boolean mIsEnabled; @@ -111,7 +106,7 @@ public class AudioTrackWrapper { mAudioTrack.setVolume(volume); } - public void reconfigure(MediaFormat format, int audioBufferSize) { + public void reconfigure(MediaFormat format) { if (!mIsEnabled || format == null) { return; } @@ -122,9 +117,9 @@ public class AudioTrackWrapper { try { pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING); } catch (Exception e) { - pcmEncoding = C.ENCODING_PCM_16BIT; + pcmEncoding = com.google.android.exoplayer.MediaFormat.NO_VALUE; } - // TODO: Handle non-AC3. + // TODO: Handle non-AC3 or non-passthrough audio. 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, @@ -132,14 +127,7 @@ public class AudioTrackWrapper { // 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); + mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding); } public void handleDiscontinuity() { diff --git a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java index 112e9dc4..eb596e93 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/BufferManager.java @@ -25,14 +25,13 @@ 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.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; -import java.util.ConcurrentModificationException; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -60,8 +59,7 @@ public class BufferManager { 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, SortedMap<Long, SampleChunk>> mChunkMap = new ArrayMap<>(); private final Map<String, Long> mStartPositionMap = new ArrayMap<>(); private final Map<String, ChunkEvictedListener> mEvictListeners = new ArrayMap<>(); private final StorageManager mStorageManager; @@ -79,11 +77,13 @@ public class BufferManager { } }; + private volatile boolean mClosed = false; private int mMinSampleSizeForSpeedCheck = MINIMUM_SAMPLE_SIZE_FOR_SPEED_CHECK; private long mTotalWriteSize; private long mTotalWriteTimeNs; private float mWriteBandwidth = 0.0f; private volatile int mSpeedCheckCount; + private boolean mDisabled = false; public interface ChunkEvictedListener { void onChunkEvicted(String id, long createdTimeMs); @@ -174,66 +174,6 @@ public class BufferManager { } /** - * 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 { @@ -246,6 +186,11 @@ public class BufferManager { File getBufferDir(); /** + * Cleans up storage. + */ + void clearStorage(); + + /** * Informs whether the storage is used for persistent use. (eg. dvr recording/play) * * @return {@code true} if stored files are persistent @@ -275,27 +220,29 @@ public class BufferManager { * Reads track name & {@link MediaFormat} from storage. * * @param isAudio {@code true} if it is for audio track - * @return {@link List} of TrackFormat + * @return {@link Pair} of track name & {@link MediaFormat} + * @throws IOException */ - List<TrackFormat> readTrackInfoFiles(boolean isAudio); + Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException; /** - * Reads key sample positions for each written sample from storage. + * Reads sample indexes 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; + ArrayList<Long> readIndexFile(String trackId) throws IOException; /** * Writes track information to storage. * - * @param formatList {@list List} of TrackFormat + * @param trackId track name + * @param format {@link android.media.MediaFormat} of the track * @param isAudio {@code true} if it is for audio track * @throws IOException */ - void writeTrackInfoFiles(List<TrackFormat> formatList, boolean isAudio) + void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) throws IOException; /** @@ -305,7 +252,7 @@ public class BufferManager { * @param index {@link SampleChunk} container * @throws IOException */ - void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index) + void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index) throws IOException; } @@ -360,6 +307,7 @@ public class BufferManager { SampleChunk.SampleChunkCreator sampleChunkCreator) { mStorageManager = storageManager; mSampleChunkCreator = sampleChunkCreator; + clearBuffer(true); } public void registerChunkEvictedListener(String id, ChunkEvictedListener listener) { @@ -370,44 +318,44 @@ public class BufferManager { mEvictListeners.remove(id); } + private void clearBuffer(boolean deleteFiles) { + mChunkMap.clear(); + if (deleteFiles) { + mStorageManager.clearStorage(); + } + mBufferSize = 0; + } + 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. + * Creates a new {@link SampleChunk} for caching samples. * * @param id the name of the track - * @param positionUs current position to write a sample in micro seconds. + * @param positionUs starting position of the {@link SampleChunk} 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 { + public SampleChunk createNewWriteFile(String id, long positionUs, + SamplePool samplePool) throws IOException { if (!maybeEvictChunk()) { throw new IOException("Not enough storage space"); } - SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(id); + SortedMap<Long, SampleChunk> 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; - } + File file = new File(mStorageManager.getBufferDir(), getFileName(id, positionUs)); + SampleChunk sampleChunk = mSampleChunkCreator.createSampleChunk(samplePool, file, + positionUs, mChunkCallback); + map.put(positionUs, sampleChunk); + return sampleChunk; } /** @@ -418,10 +366,10 @@ public class BufferManager { * @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; + ArrayList<Long> keyPositions = mStorageManager.readIndexFile(trackId); + long startPositionUs = keyPositions.size() > 0 ? keyPositions.get(0) : 0; - SortedMap<Long, Pair<SampleChunk, Integer>> map = mChunkMap.get(trackId); + SortedMap<Long, SampleChunk> map = mChunkMap.get(trackId); if (map == null) { map = new TreeMap<>(); mChunkMap.put(trackId, map); @@ -429,15 +377,11 @@ public class BufferManager { 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)); + for (long positionUs: keyPositions) { + chunk = mSampleChunkCreator.loadSampleChunkFromFile(samplePool, + mStorageManager.getBufferDir(), getFileName(trackId, positionUs), positionUs, + mChunkCallback, chunk); + map.put(positionUs, chunk); } } @@ -448,19 +392,19 @@ public class BufferManager { * @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); + public SampleChunk getReadFile(String id, long positionUs) { + SortedMap<Long, SampleChunk> map = mChunkMap.get(id); if (map == null) { return null; } - Pair<SampleChunk, Integer> ret; - SortedMap<Long, Pair<SampleChunk, Integer>> headMap = map.headMap(positionUs + 1); + SampleChunk sampleChunk; + SortedMap<Long, SampleChunk> headMap = map.headMap(positionUs + 1); if (!headMap.isEmpty()) { - ret = headMap.get(headMap.lastKey()); + sampleChunk = headMap.get(headMap.lastKey()); } else { - ret = map.get(map.firstKey()); + sampleChunk = map.get(map.firstKey()); } - return ret; + return sampleChunk; } /** @@ -495,16 +439,15 @@ public class BufferManager { // Since chunks are persistent, we cannot evict chunks. return false; } - SortedMap<Long, Pair<SampleChunk, Integer>> earliestChunkMap = null; + SortedMap<Long, SampleChunk> 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(); + for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) { + SortedMap<Long, SampleChunk> map = entry.getValue(); if (map.isEmpty()) { continue; } - SampleChunk chunk = map.get(map.firstKey()).first; + SampleChunk chunk = map.get(map.firstKey()); if (earliestChunk == null || chunk.getCreatedTimeMs() < earliestChunk.getCreatedTimeMs()) { earliestChunkMap = map; @@ -530,9 +473,8 @@ public class BufferManager { } pendingDelete = mPendingDelete.getSize(); } - for (Map.Entry<String, SortedMap<Long, Pair<SampleChunk, Integer>>> entry : - mChunkMap.entrySet()) { - SortedMap<Long, Pair<SampleChunk, Integer>> map = entry.getValue(); + for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) { + SortedMap<Long, SampleChunk> map = entry.getValue(); if (map.isEmpty()) { continue; } @@ -547,74 +489,70 @@ public class BufferManager { * @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"); + public ArrayList<Pair<String, MediaFormat>> readTrackInfoFiles() throws IOException { + ArrayList<Pair<String, MediaFormat>> trackInfos = new ArrayList<>(); + try { + trackInfos.add(mStorageManager.readTrackInfoFile(false)); + } catch (FileNotFoundException e) { + // There can be a single track only recording. (eg. audio-only, video-only) + // So the exception should not stop the read. } - return trackFormatList; + try { + trackInfos.add(mStorageManager.readTrackInfoFile(true)); + } catch (FileNotFoundException e) { + // See above catch block. + } + return trackInfos; } /** * Writes track information and index information for all tracks. * - * @param audios list of audio track information - * @param videos list of audio track information + * @param audio audio information. + * @param video video information. * @throws IOException */ - public void writeMetaFiles(List<TrackFormat> audios, List<TrackFormat> videos) + public void writeMetaFiles(Pair<String, MediaFormat> audio, Pair<String, MediaFormat> video) 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 (audio != null) { + mStorageManager.writeTrackInfoFile(audio.first, audio.second, true); + SortedMap<Long, SampleChunk> map = mChunkMap.get(audio.first); + if (map == null) { + throw new IOException("Audio track index missing"); } + mStorageManager.writeIndexFile(audio.first, 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); + if (video != null) { + mStorageManager.writeTrackInfoFile(video.first, video.second, false); + SortedMap<Long, SampleChunk> map = mChunkMap.get(video.first); + if (map == null) { + throw new IOException("Video track index missing"); } + mStorageManager.writeIndexFile(video.first, map); } } /** + * Marks it is closed and it is not used anymore. + */ + public void close() { + // Clean-up may happen after this is called. + mClosed = true; + } + + /** * 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()); - } - } + mPendingDelete.release(); + for (Map.Entry<String, SortedMap<Long, SampleChunk>> entry : mChunkMap.entrySet()) { + for (SampleChunk chunk : entry.getValue().values()) { + SampleChunk.IoState.release(chunk, !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()); + } + mChunkMap.clear(); + if (mClosed) { + clearBuffer(!mStorageManager.isPersistent()); } } @@ -673,6 +611,20 @@ public class BufferManager { } /** + * Marks {@link BufferManager} object disabled to prevent it from the future use. + */ + public void disable() { + mDisabled = true; + } + + /** + * Returns if {@link BufferManager} object is disabled. + */ + public boolean isDisabled() { + return mDisabled; + } + + /** * Returns if {@link BufferManager} has checked the write speed, * which is suitable for Trickplay. */ diff --git a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java index bea3defd..6a0502a7 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/DvrStorageManager.java @@ -17,12 +17,8 @@ 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.Track.AtscCaptionTrack; -import com.google.protobuf.nano.MessageNano; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -32,25 +28,18 @@ 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. @@ -70,6 +59,18 @@ public class DvrStorageManager implements BufferManager.StorageManager { } @Override + public void clearStorage() { + if (mIsRecording) { + File[] files = mBufferDir.listFiles(); + if (files != null && files.length > 0) { + for (File file : files) { + file.delete(); + } + } + } + } + + @Override public File getBufferDir() { return mBufferDir; } @@ -131,17 +132,6 @@ public class DvrStorageManager implements BufferManager.StorageManager { } } - 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) { @@ -165,104 +155,39 @@ public class DvrStorageManager implements BufferManager.StorageManager { } @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; + public Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) throws IOException { + File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); + 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); } - 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; + readFormatLong(in, format, MediaFormat.KEY_DURATION); + return new Pair<>(name, format); } } - private ArrayList<BufferManager.PositionHolder> readNewIndexFile(File indexFile) - throws IOException { - ArrayList<BufferManager.PositionHolder> indices = new ArrayList<>(); - try (DataInputStream in = new DataInputStream(new FileInputStream(indexFile))) { + @Override + public ArrayList<Long> readIndexFile(String trackId) throws IOException { + ArrayList<Long> indices = new ArrayList<>(); + File file = new File(getBufferDir(), trackId + IDX_FILE_SUFFIX); + try (DataInputStream in = new DataInputStream(new FileInputStream(file))) { 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)); + indices.add(in.readLong()); } 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)) { @@ -329,63 +254,33 @@ public class DvrStorageManager implements BufferManager.StorageManager { } @Override - public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) + public void writeTrackInfoFile(String trackId, MediaFormat format, 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); + File file = new File(getBufferDir(), (isAudio ? "audio" : "video") + META_FILE_SUFFIX); + try (DataOutputStream out = new DataOutputStream(new FileOutputStream(file))) { + writeString(out, trackId); + writeFormatString(out, format, MediaFormat.KEY_MIME); + writeFormatInt(out, format, MediaFormat.KEY_MAX_INPUT_SIZE); + writeFormatInt(out, format, MediaFormat.KEY_WIDTH); + writeFormatInt(out, format, MediaFormat.KEY_HEIGHT); + writeFormatInt(out, format, MediaFormat.KEY_CHANNEL_COUNT); + writeFormatInt(out, format, MediaFormat.KEY_SAMPLE_RATE); + writeFormatFloat(out, format, KEY_PIXEL_WIDTH_HEIGHT_RATIO); + for (int i = 0; i < 3; ++i) { + writeFormatByteBuffer(out, format, "csd-" + i); } + writeFormatLong(out, format, MediaFormat.KEY_DURATION); } } @Override - public void writeIndexFile(String trackName, SortedMap<Long, Pair<SampleChunk, Integer>> index) + public void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index) throws IOException { - File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX_V2); + File indexFile = new File(getBufferDir(), trackName + IDX_FILE_SUFFIX); 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); + for (Long key : index.keySet()) { + out.writeLong(key); } } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java index af0c3f0d..4869b49f 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/RecordingSampleBuffer.java @@ -66,14 +66,9 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, public static final int BUFFER_REASON_RECORDING = 2; /** - * The minimum duration to support seek in Trickplay. + * The duration of a chunk of samples, {@link SampleChunk}. */ - 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 + static final long CHUNK_DURATION_US = TimeUnit.MILLISECONDS.toMicros(500); 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); @@ -84,6 +79,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, private int mTrackCount; private boolean[] mTrackSelected; + private List<String> mIds; private List<SampleQueue> mReadSampleQueues; private final SamplePool mSamplePool = new SamplePool(); private long mLastBufferedPositionUs = C.UNKNOWN_TIME_US; @@ -134,6 +130,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (mTrackCount <= 0) { throw new IOException("No tracks to initialize"); } + mIds = ids; mTrackSelected = new boolean[mTrackCount]; mReadSampleQueues = new ArrayList<>(); mSampleChunkIoHelper = new SampleChunkIoHelper(ids, mediaFormats, mBufferReason, @@ -142,9 +139,6 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, mReadSampleQueues.add(i, new SampleQueue(mSamplePool)); } mSampleChunkIoHelper.init(); - for (int i = 0; i < mTrackCount; ++i) { - mBufferManager.registerChunkEvictedListener(ids.get(i), RecordingSampleBuffer.this); - } } @Override @@ -152,6 +146,8 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (!mTrackSelected[index]) { mTrackSelected[index] = true; mReadSampleQueues.get(index).clear(); + mBufferManager.registerChunkEvictedListener(mIds.get(index), + RecordingSampleBuffer.this); mSampleChunkIoHelper.openRead(index, mCurrentPlaybackPositionUs); } } @@ -161,7 +157,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, if (mTrackSelected[index]) { mTrackSelected[index] = false; mReadSampleQueues.get(index).clear(); - mSampleChunkIoHelper.closeRead(index); + mBufferManager.unregisterChunkEvictedListener(mIds.get(index)); } } @@ -197,6 +193,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, } // Disables buffering samples afterwards, and notifies the disk speed is slow. Log.w(TAG, "Disk is too slow for trickplay"); + mBufferManager.disable(); mBufferListener.onDiskTooSlow(); } @@ -208,7 +205,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, private boolean maybeReadSample(SampleQueue queue, int index) { if (queue.getLastQueuedPositionUs() != null && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US - && queue.isDurationGreaterThan(MIN_SEEK_DURATION_US)) { + && queue.isDurationGreaterThan(CHUNK_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. @@ -303,7 +300,7 @@ public class RecordingSampleBuffer implements BufferManager.SampleBuffer, public void onChunkEvicted(String id, long createdTimeMs) { if (mBufferListener != null) { mBufferListener.onBufferStartTimeChanged( - createdTimeMs + TimeUnit.MICROSECONDS.toMillis(MIN_SEEK_DURATION_US)); + createdTimeMs + TimeUnit.MICROSECONDS.toMillis(CHUNK_DURATION_US)); } } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java index ab6d1a75..552caaef 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunk.java @@ -151,23 +151,18 @@ public class SampleChunk { 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 { + void openRead(SampleChunk chunk) throws IOException { if (mChunk != null) { mChunk.closeRead(); } chunk.openRead(); - reset(chunk, offset); + reset(chunk); } /** @@ -246,20 +241,6 @@ public class SampleChunk { } /** - * 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 diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java index ca97a91a..37ae4022 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleChunkIoHelper.java @@ -21,7 +21,6 @@ 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; @@ -32,9 +31,7 @@ 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; /** @@ -49,13 +46,11 @@ public class SampleChunkIoHelper implements Handler.Callback { 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 static final int MSG_CLOSE_WRITE = 3; + private static final int MSG_READ = 4; + private static final int MSG_WRITE = 5; + private static final int MSG_RELEASE = 6; - private final long mSampleChunkDurationUs; private final int mTrackCount; private final List<String> mIds; private final List<MediaFormat> mMediaFormats; @@ -67,11 +62,9 @@ public class SampleChunkIoHelper implements Handler.Callback { private Handler mIoHandler; private final ConcurrentLinkedQueue<SampleHolder> mReadSampleBuffers[]; private final ConcurrentLinkedQueue<SampleHolder> mHandlerReadSampleBuffers[]; - private final long[] mWriteIndexEndPositionUs; - private final long[] mWriteChunkEndPositionUs; + private final long[] mWriteEndPositionUs; 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; @@ -136,20 +129,11 @@ public class SampleChunkIoHelper implements Handler.Callback { mReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; mHandlerReadSampleBuffers = new ConcurrentLinkedQueue[mTrackCount]; - mWriteIndexEndPositionUs = new long[mTrackCount]; - mWriteChunkEndPositionUs = new long[mTrackCount]; + mWriteEndPositionUs = 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; + mWriteEndPositionUs[i] = RecordingSampleBuffer.CHUNK_DURATION_US; mReadIoStates[i] = new SampleChunk.IoState(); mWriteIoStates[i] = new SampleChunk.IoState(); } @@ -220,15 +204,6 @@ public class SampleChunkIoHelper implements Handler.Callback { } /** - * 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() { @@ -254,19 +229,21 @@ public class SampleChunkIoHelper implements Handler.Callback { 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<>(); + Pair<String, android.media.MediaFormat> audio = null, video = null; 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)); + if (audio == null && MimeTypes.isAudio(mMediaFormats.get(i).mimeType)) { + audio = new Pair<>(mIds.get(i), format); + } else if (video == null && MimeTypes.isVideo(mMediaFormats.get(i).mimeType)) { + video = new Pair<>(mIds.get(i), format); + } + if (audio != null && video != null) { + break; } } - mBufferManager.writeMetaFiles(audios, videos); + mBufferManager.writeMetaFiles(audio, video); } } finally { mBufferManager.release(); @@ -288,9 +265,6 @@ public class SampleChunkIoHelper implements Handler.Callback { 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; @@ -317,16 +291,14 @@ public class SampleChunkIoHelper implements Handler.Callback { 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) { + SampleChunk chunk = mBufferManager.getReadFile(mIds.get(index), params.positionUs); + if (chunk == null) { String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs + "is not found"; - SoftPreconditions.checkNotNull(readPosition, TAG, errorMessage); + SoftPreconditions.checkNotNull(chunk, TAG, errorMessage); throw new IOException(errorMessage); } - mSelectedTracks.add(index); - mReadIoStates[index].openRead(readPosition.first, (long) readPosition.second); + mReadIoStates[index].openRead(chunk); if (mHandlerReadSampleBuffers[index] != null) { SampleHolder sample; while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { @@ -338,22 +310,10 @@ public class SampleChunkIoHelper implements Handler.Callback { } private void doOpenWrite(int index) throws IOException { - SampleChunk chunk = mBufferManager.createNewWriteFileIfNeeded(mIds.get(index), 0, - mSamplePool, null, 0); + SampleChunk chunk = mBufferManager.createNewWriteFile(mIds.get(index), 0, mSamplePool); 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) { @@ -397,21 +357,13 @@ public class SampleChunkIoHelper implements Handler.Callback { 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; - } + + if (sample.timeUs >= mWriteEndPositionUs[index]) { + nextChunk = mBufferManager.createNewWriteFile(mIds.get(index), + mWriteEndPositionUs[index], mSamplePool); + mWriteEndPositionUs[index] = + ((sample.timeUs / RecordingSampleBuffer.CHUNK_DURATION_US) + 1) * + RecordingSampleBuffer.CHUNK_DURATION_US; } } mWriteIoStates[params.index].write(params.sample, nextChunk); @@ -439,22 +391,15 @@ public class SampleChunkIoHelper implements Handler.Callback { mIoHandler.removeCallbacksAndMessages(null); mFinished = true; conditionVariable.open(); - mSelectedTracks.clear(); } private void releaseEvictedChunks() { - if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK - || mSelectedTracks.isEmpty()) { + if (mBufferReason != RecordingSampleBuffer.BUFFER_REASON_LIVE_PLAYBACK) { 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); + mReadIoStates[i].getStartPositionUs()); mBufferManager.evictChunks(mIds.get(i), evictEndPositionUs); } } diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java index 75eac5a2..7b098f40 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SampleQueue.java @@ -43,7 +43,6 @@ public class SampleQueue { if (sampleFromQueue == null) { return SampleSource.NOTHING_READ; } - sample.ensureSpaceForWrite(sampleFromQueue.size); sample.size = sampleFromQueue.size; sample.flags = sampleFromQueue.flags; sample.timeUs = sampleFromQueue.timeUs; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java index 0b219b41..40c4ef95 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/SimpleSampleBuffer.java @@ -19,7 +19,6 @@ 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; diff --git a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java index 9fe921b8..258a5cd0 100644 --- a/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java +++ b/src/com/android/tv/tuner/exoplayer/buffer/TrickplayStorageManager.java @@ -17,23 +17,20 @@ package com.android.tv.tuner.exoplayer.buffer; import android.content.Context; +import android.media.MediaFormat; import android.os.AsyncTask; +import android.os.Looper; 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) @@ -46,68 +43,53 @@ public class TrickplayStorageManager implements BufferManager.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 File mBufferDir; private final long mMaxBufferSize; + private final long mStorageBufferBytes; - 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; + private static long getStorageBufferBytes(Context context, File path) { long lowPercentage = Settings.Global.getInt(context.getContentResolver(), SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); - long lowPercentageToBytes = path.getTotalSpace() * lowPercentage / 100; + long lowBytes = 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); + return Math.min(lowBytes, maxLowBytes); } - public TrickplayStorageManager(Context context, @NonNull File baseDir, long maxBufferSize) { - initParamsIfNeeded(context, new File(baseDir, BUFFER_DIR)); - sBufferDir.mkdirs(); + public TrickplayStorageManager(Context context, File baseDir, long maxBufferSize) { + mBufferDir = new File(baseDir, BUFFER_DIR); + mBufferDir.mkdirs(); mMaxBufferSize = maxBufferSize; clearStorage(); + mStorageBufferBytes = getStorageBufferBytes(context, mBufferDir); } - private void clearStorage() { - long now = System.currentTimeMillis(); - if (sLastCacheCleanUpTask != null) { - sLastCacheCleanUpTask.cancel(true); + @Override + public void clearStorage() { + File files[] = mBufferDir.listFiles(); + if (files == null || files.length == 0) { + return; } - 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) { + if (Looper.myLooper() == Looper.getMainLooper()) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + for (File file : files) { file.delete(); } + return null; } - return null; + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + for (File file : files) { + file.delete(); } - }; - sLastCacheCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } } @Override public File getBufferDir() { - return sBufferDir; + return mBufferDir; } @Override @@ -122,26 +104,25 @@ public class TrickplayStorageManager implements BufferManager.StorageManager { @Override public boolean hasEnoughBuffer(long pendingDelete) { - return sBufferDir.getUsableSpace() + pendingDelete >= sStorageBufferBytes; + return mBufferDir.getUsableSpace() + pendingDelete >= mStorageBufferBytes; } @Override - public List<BufferManager.TrackFormat> readTrackInfoFiles(boolean isAudio) { + public Pair<String, MediaFormat> readTrackInfoFile(boolean isAudio) { return null; } @Override - public ArrayList<BufferManager.PositionHolder> readIndexFile(String trackId) { + public ArrayList<Long> readIndexFile(String trackId) { return null; } @Override - public void writeTrackInfoFiles(List<BufferManager.TrackFormat> formatList, boolean isAudio) { + public void writeTrackInfoFile(String trackId, MediaFormat format, boolean isAudio) { } @Override - public void writeIndexFile(String trackName, - SortedMap<Long, Pair<SampleChunk, Integer>> index) { + public void writeIndexFile(String trackName, SortedMap<Long, SampleChunk> index) { } } diff --git a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java index 53678a85..97d9ece3 100644 --- a/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java +++ b/src/com/android/tv/tuner/setup/ConnectionTypeFragment.java @@ -36,24 +36,6 @@ public class ConnectionTypeFragment extends SetupMultiPaneFragment { "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(); } 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 a4dd494c..00000000 --- a/src/com/android/tv/tuner/setup/PostalCodeFragment.java +++ /dev/null @@ -1,184 +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.Spanned; -import android.text.TextUtils; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -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 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(); - if (postalCode != null && postalCode.length() == 5) { - 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) { - mEditedActionView = getView().findViewById(R.id.guidedactions_editable); - ((TextView) mEditedActionView.findViewById(R.id.guidedactions_item_title)) - .setFilters(new InputFilter[]{new InputFilter() { - @Override - public CharSequence filter(CharSequence source, int start, - int end, Spanned dest, int dstart, int dend) { - try { - Integer.parseInt(source.toString()); - return null; - } catch (NumberFormatException e) { - return ""; - } - } - }, new InputFilter.LengthFilter(5)}); - } - 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 index 75b28e32..3b61debb 100644 --- a/src/com/android/tv/tuner/setup/ScanFragment.java +++ b/src/com/android/tv/tuner/setup/ScanFragment.java @@ -21,7 +21,6 @@ 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; @@ -36,13 +35,14 @@ import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.tv.common.AutoCloseableUtils; 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.R; import com.android.tv.tuner.TunerPreferences; -import com.android.tv.tuner.data.Channel; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.PsipData; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.source.FileTsStreamer; @@ -51,6 +51,7 @@ 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 com.android.tv.tuner.util.TunerInputInfoUtils; import junit.framework.Assert; @@ -66,7 +67,6 @@ import java.util.concurrent.TimeUnit; 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; @@ -98,7 +98,6 @@ public class ScanFragment extends SetupFragment { @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()); @@ -121,19 +120,13 @@ public class ScanFragment extends SetupFragment { } }); 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); + if (TunerInputInfoUtils.isBuiltInTuner(getActivity())){ + scanTitleView.setText(R.string.bt_channel_scan); + } else { + scanTitleView.setText(R.string.ut_channel_scan); } return view; } @@ -154,14 +147,12 @@ public class ScanFragment extends SetupFragment { } @Override - public void onPause() { - Log.d(TAG, "onPause"); + public void onDetach() { if (mChannelScanTask != null) { // Ensure scan task will stop. - Log.w(TAG, "The activity went to the background. Stopping channel scan."); mChannelScanTask.stopScan(); } - super.onPause(); + super.onDetach(); } /** @@ -177,9 +168,7 @@ public class ScanFragment extends SetupFragment { new Handler().postDelayed(new Runnable() { @Override public void run() { - if (mChannelScanTask != null) { - mChannelScanTask.showFinishingProgressDialog(); - } + mChannelScanTask.showFinishingProgressDialog(); } }, SHOW_PROGRESS_DIALOG_DELAY_MS); @@ -266,7 +255,7 @@ public class ScanFragment extends SetupFragment { if (FAKE_MODE) { mScanTsStreamer = new FakeTsStreamer(this); } else { - TunerHal hal = ((TunerSetupActivity) mActivity).getTunerHal(); + TunerHal hal = TunerHal.createInstance(mActivity.getApplicationContext()); if (hal == null) { throw new RuntimeException("Failed to open a DVB device"); } @@ -327,17 +316,10 @@ public class ScanFragment extends SetupFragment { @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]); - } + mProgressBar.setProgress(values[0]); } private void stopScan() { - if (mLatch != null) { - mLatch.countDown(); - } mConditionStopped.open(); } @@ -378,7 +360,11 @@ public class ScanFragment extends SetupFragment { if (mConditionStopped.block(-1)) { break; } - publishProgress(MAX_PROGRESS * i++ / mScanChannelList.size()); + onProgressUpdate(MAX_PROGRESS * i++ / mScanChannelList.size()); + } + if (mScanTsStreamer instanceof TunerTsStreamer) { + AutoCloseableUtils.closeQuietly( + ((TunerTsStreamer) mScanTsStreamer).getTunerHal()); } mChannelDataManager.notifyScanCompleted(); if (!mConditionStopped.block(-1)) { @@ -468,13 +454,7 @@ public class ScanFragment extends SetupFragment { 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(); - } + onActionClick(ACTION_CATEGORY, mIsCanceled ? ACTION_CANCEL : ACTION_FINISH); mChannelScanTask = null; } } diff --git a/src/com/android/tv/tuner/setup/ScanResultFragment.java b/src/com/android/tv/tuner/setup/ScanResultFragment.java index 3b8cd823..068543cd 100644 --- a/src/com/android/tv/tuner/setup/ScanResultFragment.java +++ b/src/com/android/tv/tuner/setup/ScanResultFragment.java @@ -26,7 +26,6 @@ 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; @@ -77,19 +76,11 @@ public class ScanResultFragment extends SetupMultiPaneFragment { 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); + if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { + description = getString(R.string.bt_result_not_found_description); + } else { + description = getString(R.string.ut_result_not_found_description); } breadcrumb = getString(R.string.ut_setup_breadcrumb); } diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java index f618c699..78121bc5 100644 --- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java +++ b/src/com/android/tv/tuner/setup/TunerSetupActivity.java @@ -29,53 +29,35 @@ 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.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; 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.TvApplication; -import com.android.tv.common.AutoCloseableUtils; 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 com.android.tv.util.LocationUtils; - -import java.util.Locale; -import java.util.concurrent.Executor; +import com.android.tv.tuner.util.TunerInputInfoUtils; /** * 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"; - + private final String TAG = "TunerSetupActivity"; // For the recommendation card private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity"; 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, @@ -87,13 +69,9 @@ public class TunerSetupActivity extends SetupActivity { R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256}; private ScanFragment mLastScanFragment; - private Integer mTunerType; - private TunerHalFactory mTunerHalFactory; - private boolean mNeedToShowPostalCodeFragment; @Override protected void onCreate(Bundle savedInstanceState) { - if (DEBUG) Log.d(TAG, "onCreate"); TvApplication.setCurrentRunningProcess(this, false); super.onCreate(savedInstanceState); // TODO: check {@link shouldShowRequestPermissionRationale}. @@ -101,49 +79,13 @@ public class TunerSetupActivity extends SetupActivity { != PackageManager.PERMISSION_GRANTED) { // No need to check the request result. requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION}, - PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION); - } - mTunerType = TunerHal.getTunerTypeAndCount(this).first; - if (mTunerType == null) { - finish(); - } else { - mTunerHalFactory = new TunerHalFactory(getApplicationContext()); - } - try { - // Updating postal code takes time, therefore we called it here for "warm-up". - 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 - } - } + 0); } } @Override protected Fragment onCreateInitialFragment() { 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; @@ -160,41 +102,33 @@ public class TunerSetupActivity extends SetupActivity { finish(); break; default: { - if (mNeedToShowPostalCodeFragment - || Locale.US.getCountry().equalsIgnoreCase( - LocationUtils.getCurrentCountry(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(); - } + SetupFragment fragment = new ConnectionTypeFragment(); + fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION + | SetupFragment.FRAGMENT_RETURN_TRANSITION); + showFragment(fragment, true); 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.get() == null) { + TunerHal hal = TunerHal.createInstance(getApplicationContext()); + if (hal == null) { finish(); Toast.makeText(getApplicationContext(), R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show(); return true; } + try { + hal.close(); + } catch (Exception e) { + Log.e(TAG, "Tuner hal close failed", e); + return true; + } mLastScanFragment = new ScanFragment(); - Bundle args1 = new Bundle(); - args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, + Bundle args = new Bundle(); + args.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]); - args1.putInt(KEY_TUNER_TYPE, mTunerType); - mLastScanFragment.setArguments(args1); + mLastScanFragment.setArguments(args); showFragment(mLastScanFragment, true); return true; case ScanFragment.ACTION_CATEGORY: @@ -203,11 +137,7 @@ public class TunerSetupActivity extends SetupActivity { 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); @@ -283,7 +213,7 @@ public class TunerSetupActivity extends SetupActivity { String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(), TunerTvInputService.class.getName())); - // Make an intent to launch the setup activity of TV tuner input. + // Make an intent to launch the setup activity of USB tuner TV input. Intent intent = TvCommonUtils.createSetupIntent( new Intent(context, TunerSetupActivity.class), inputId); intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId); @@ -294,27 +224,6 @@ public class TunerSetupActivity extends SetupActivity { } /** - * Gets the currently used tuner HAL. - */ - TunerHal getTunerHal() { - return mTunerHalFactory.get(); - } - - /** - * 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 @@ -333,19 +242,12 @@ public class TunerSetupActivity extends SetupActivity { Resources resources = context.getResources(); String focusedTitle = resources.getString( R.string.ut_setup_recommendation_card_focused_title); - int titleStringId = 0; - switch (TunerHal.getTunerTypeAndCount(context).first) { - case TunerHal.TUNER_TYPE_BUILT_IN: - titleStringId = R.string.bt_setup_recommendation_card_title; - break; - case TunerHal.TUNER_TYPE_USB: - titleStringId = R.string.ut_setup_recommendation_card_title; - break; - case TunerHal.TUNER_TYPE_NETWORK: - titleStringId = R.string.nt_setup_recommendation_card_title; - break; + String title; + if (TunerInputInfoUtils.isBuiltInTuner(context)) { + title = resources.getString(R.string.bt_setup_recommendation_card_title); + } else { + title = resources.getString(R.string.ut_setup_recommendation_card_title); } - String title = resources.getString(titleStringId); Bitmap largeIcon = BitmapFactory.decodeResource(resources, R.drawable.recommendation_antenna); @@ -367,20 +269,6 @@ public class TunerSetupActivity extends SetupActivity { 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 recommendation card. * @@ -391,80 +279,4 @@ public class TunerSetupActivity extends SetupActivity { .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. - */ - TunerHal get() { - 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. - */ - void generate() { - if (mGenerateTunerHalTask == null && mTunerHal == null) { - mGenerateTunerHalTask = new GenerateTunerHalTask(); - mGenerateTunerHalTask.executeOnExecutor(mExecutor); - } - } - - /** - * Clears the currently used tuner hal. - */ - void clear() { - if (mGenerateTunerHalTask != null) { - mGenerateTunerHalTask.cancel(true); - mGenerateTunerHalTask = null; - } - if (mTunerHal != null) { - AutoCloseableUtils.closeQuietly(mTunerHal); - mTunerHal = null; - } - } - - 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 index a3dddc72..7e809411 100644 --- a/src/com/android/tv/tuner/setup/WelcomeFragment.java +++ b/src/com/android/tv/tuner/setup/WelcomeFragment.java @@ -27,7 +27,6 @@ import android.view.ViewGroup; 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; @@ -42,9 +41,7 @@ public class WelcomeFragment extends SetupMultiPaneFragment { @Override protected SetupGuidedStepFragment onCreateContentFragment() { - ContentFragment fragment = new ContentFragment(); - fragment.setArguments(getArguments()); - return fragment; + return new ContentFragment(); } @Override @@ -73,33 +70,20 @@ public class WelcomeFragment extends SetupMultiPaneFragment { 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); + if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { + title = getString(R.string.bt_setup_new_title); + description = getString(R.string.bt_setup_new_description); + } else { + title = getString(R.string.ut_setup_new_title); + description = getString(R.string.ut_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); + if (TunerInputInfoUtils.isBuiltInTuner(getActivity())) { + description = getString(R.string.bt_setup_again_description); + } else { + description = getString(R.string.ut_setup_again_description); } } return new Guidance(title, description, null, null); diff --git a/src/com/android/tv/tuner/source/FileTsStreamer.java b/src/com/android/tv/tuner/source/FileTsStreamer.java index 80ec8a56..14997ee4 100644 --- a/src/com/android/tv/tuner/source/FileTsStreamer.java +++ b/src/com/android/tv/tuner/source/FileTsStreamer.java @@ -256,7 +256,7 @@ public class FileTsStreamer implements TsStreamer { * Returns whether the current pid filter is empty or not. */ public boolean isFilterEmpty() { - return mPids.size() == 0; + return mPids.size() > 0; } /** diff --git a/src/com/android/tv/tuner/source/TsDataSourceManager.java b/src/com/android/tv/tuner/source/TsDataSourceManager.java index 32504b95..ccbb75ba 100644 --- a/src/com/android/tv/tuner/source/TsDataSourceManager.java +++ b/src/com/android/tv/tuner/source/TsDataSourceManager.java @@ -17,10 +17,8 @@ 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.Channel; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.tvinput.EventDetector; @@ -129,14 +127,6 @@ public class TsDataSourceManager { } /** - * Add tuner hal into TunerTsStreamerManager for test. - */ - @VisibleForTesting - public void addTunerHalForTest(TunerHal tunerHal) { - mTunerStreamerManager.addTunerHal(tunerHal, mId); - } - - /** * Releases persistent resources. */ public void release() { diff --git a/src/com/android/tv/tuner/source/TunerTsStreamer.java b/src/com/android/tv/tuner/source/TunerTsStreamer.java index 65b11a5a..b24048e6 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamer.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamer.java @@ -42,17 +42,15 @@ public class TunerTsStreamer implements TsStreamer { 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 mEndOfStreamSent; private boolean mStreaming; private final TunerHal mTunerHal; @@ -61,7 +59,6 @@ public class TunerTsStreamer implements TsStreamer { private final EventDetector mEventDetector; private final TsStreamWriter mTsStreamWriter; - private String mChannelNumber; public static class TunerDataSource extends TsDataSource { private final TunerTsStreamer mTsStreamer; @@ -106,15 +103,6 @@ public class TunerTsStreamer implements TsStreamer { 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; } @@ -126,10 +114,7 @@ public class TunerTsStreamer implements TsStreamer { */ public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) { mTunerHal = tunerHal; - mEventDetector = new EventDetector(mTunerHal); - if (eventListener != null) { - mEventDetector.registerListener(eventListener); - } + mEventDetector = new EventDetector(mTunerHal, eventListener); mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ? new TsStreamWriter(context) : null; } @@ -140,8 +125,7 @@ public class TunerTsStreamer implements TsStreamer { @Override public boolean startStream(TunerChannel channel) { - if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(), - channel.getDisplayNumber(false))) { + if (mTunerHal.tune(channel.getFrequency(), channel.getModulation())) { if (channel.hasVideo()) { mTunerHal.addPidFilter(channel.getVideoPid(), TunerHal.FILTER_TYPE_VIDEO); @@ -164,7 +148,6 @@ public class TunerTsStreamer implements TsStreamer { channel.getProgramNumber()); } mChannel = channel; - mChannelNumber = channel.getDisplayNumber(); synchronized (mCircularBufferMonitor) { if (mStreaming) { Log.w(TAG, "Streaming should be stopped before start streaming"); @@ -173,6 +156,7 @@ public class TunerTsStreamer implements TsStreamer { mStreaming = true; mBytesFetched = 0; mLastReadPosition.set(0L); + mEndOfStreamSent = false; } if (mTsStreamWriter != null) { mTsStreamWriter.setChannel(mChannel); @@ -188,7 +172,7 @@ public class TunerTsStreamer implements TsStreamer { @Override public boolean startStream(ChannelScanFileParser.ScanChannel channel) { - if (mTunerHal.tune(channel.frequency, channel.modulation, null)) { + if (mTunerHal.tune(channel.frequency, channel.modulation)) { mEventDetector.startDetecting( channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS); synchronized (mCircularBufferMonitor) { @@ -199,6 +183,7 @@ public class TunerTsStreamer implements TsStreamer { mStreaming = true; mBytesFetched = 0; mLastReadPosition.set(0L); + mEndOfStreamSent = false; } mStreamingThread = new StreamingThread(); mStreamingThread.start(); @@ -273,22 +258,6 @@ public class TunerTsStreamer implements TsStreamer { } } - public String getStreamerInfo() { - return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming; - } - - public void registerListener(EventListener listener) { - if (mEventDetector != null && listener != null) { - mEventDetector.registerListener(listener); - } - } - - public void unregisterListener(EventListener listener) { - if (mEventDetector != null) { - mEventDetector.unregisterListener(listener); - } - } - private class StreamingThread extends Thread { @Override public void run() { @@ -352,14 +321,21 @@ public class TunerTsStreamer implements TsStreamer { * @throws IOException */ public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException { + long readStartTime = System.currentTimeMillis(); while (true) { synchronized (mCircularBufferMonitor) { - if (!mStreaming) { - return READ_ERROR_STREAMING_ENDED; + if (mEndOfStreamSent || !mStreaming) { + return -1; } if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) { - Log.w(TAG, "Demux is requesting the data which is already overwritten."); - return READ_ERROR_BUFFER_OVERWRITTEN; + Log.e(TAG, "Demux is requesting the data which is already overwritten."); + return -1; + } + if (System.currentTimeMillis() - readStartTime > READ_TIMEOUT_MS) { + // Nothing was received during READ_TIMEOUT_MS before. + mEndOfStreamSent = true; + mCircularBufferMonitor.notifyAll(); + return -1; } if (mBytesFetched < pos + amount) { try { diff --git a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java index fcd14116..cf1f6dcf 100644 --- a/src/com/android/tv/tuner/source/TunerTsStreamerManager.java +++ b/src/com/android/tv/tuner/source/TunerTsStreamerManager.java @@ -42,7 +42,6 @@ class TunerTsStreamerManager { 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; @@ -69,8 +68,6 @@ class TunerTsStreamerManager { 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; } @@ -86,8 +83,6 @@ class TunerTsStreamerManager { if (!creator.isCancelledLocked()) { mStreamerFinder.putLocked(channel, sessionId, streamer); TsDataSource source = streamer.createDataSource(); - mListeners.put(sessionId, listener); - streamer.registerListener(listener); mSourceToStreamerMap.put(source, streamer); return source; } @@ -109,8 +104,6 @@ class TunerTsStreamerManager { 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); @@ -132,13 +125,6 @@ class TunerTsStreamerManager { } } - /** - * 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); } @@ -275,16 +261,16 @@ class TunerTsStreamerManager { } private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) { - if (!reuse || !hal.isReusable()) { + if (!reuse) { AutoCloseableUtils.closeQuietly(hal); return; } TunerHal cachedHal = mTunerHals.get(sessionId); if (cachedHal != hal) { mTunerHals.put(sessionId, hal); - if (cachedHal != null) { - AutoCloseableUtils.closeQuietly(cachedHal); - } + } + if (cachedHal != null && cachedHal != hal) { + AutoCloseableUtils.closeQuietly(cachedHal); } } @@ -297,9 +283,5 @@ class TunerTsStreamerManager { 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 index fe972cd1..8c1f6a1b 100644 --- a/src/com/android/tv/tuner/ts/SectionParser.java +++ b/src/com/android/tv/tuner/ts/SectionParser.java @@ -22,7 +22,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; -import com.android.tv.tuner.data.Channel; +import com.android.tv.tuner.data.nano.Channel; import com.android.tv.tuner.data.PsiData.PatItem; import com.android.tv.tuner.data.PsiData.PmtItem; import com.android.tv.tuner.data.PsipData.Ac3AudioDescriptor; @@ -39,8 +39,8 @@ import com.android.tv.tuner.data.PsipData.RatingRegion; import com.android.tv.tuner.data.PsipData.RegionalRating; import com.android.tv.tuner.data.PsipData.TsDescriptor; import com.android.tv.tuner.data.PsipData.VctItem; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +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.ibm.icu.text.UnicodeDecompressor; @@ -367,10 +367,6 @@ public class SectionParser { mParsedEttItems.clear(); } - public void resetVersionNumbers() { - mSectionVersionMap.clear(); - } - private void parseSection(byte[] data) { if (!checkSanity(data)) { Log.d(TAG, "Bad CRC!"); @@ -514,8 +510,10 @@ public class SectionParser { pos += 11 + descriptorsLength; results.add(new MgtItem(tableType, tableTypePid)); } - // Skip the remaining descriptor part which we don't use. - + if ((data[pos] & 0xf0) != 0xf0) { + Log.e(TAG, "Broken MGT."); + return false; + } if (mListener != null) { mListener.onMgtParsed(results); } @@ -719,9 +717,6 @@ public class SectionParser { 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(); @@ -953,7 +948,6 @@ public class SectionParser { pos += 3; boolean ccType = (data[pos] & 0x80) != 0; if (!ccType) { - pos +=3; continue; } int captionServiceNumber = data[pos] & 0x3f; diff --git a/src/com/android/tv/tuner/ts/TsParser.java b/src/com/android/tv/tuner/ts/TsParser.java index 21b5a942..c24c2a21 100644 --- a/src/com/android/tv/tuner/ts/TsParser.java +++ b/src/com/android/tv/tuner/ts/TsParser.java @@ -102,7 +102,6 @@ public class TsParser { } protected abstract void handleData(byte[] data, boolean startIndicator); - protected abstract void resetDataVersions(); } private class SectionStream extends Stream { @@ -139,11 +138,6 @@ public class TsParser { mSectionParser.parseSections(mPacket); } - @Override - protected void resetDataVersions() { - mSectionParser.resetVersionNumbers(); - } - private final OutputListener mSectionListener = new OutputListener() { @Override public void onPatParsed(List<PatItem> items) { @@ -457,16 +451,4 @@ public class TsParser { } 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 index 885cef9f..a16bc522 100644 --- a/src/com/android/tv/tuner/tvinput/ChannelDataManager.java +++ b/src/com/android/tv/tuner/tvinput/ChannelDataManager.java @@ -30,7 +30,6 @@ import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; import android.support.annotation.Nullable; -import android.support.v4.os.BuildCompat; import android.text.format.DateUtils; import android.util.Log; @@ -38,7 +37,6 @@ 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; @@ -194,14 +192,11 @@ public class ChannelDataManager implements Handler.Callback { public void release() { mHandler.removeCallbacksAndMessages(null); - releaseSafely(); + mHandlerThread.quitSafely(); } public void releaseSafely() { mHandlerThread.quitSafely(); - mListener = null; - mChannelScanListener = null; - mChannelScanHandler = null; } public TunerChannel getChannel(long channelId) { @@ -440,7 +435,7 @@ public class ChannelDataManager implements Handler.Callback { } } ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), newItem, channel)); + TvContract.Programs.CONTENT_URI), newItem, channel.getChannelId())); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -510,7 +505,7 @@ public class ChannelDataManager implements Handler.Callback { continue; } ops.add(buildContentProviderOperation(ContentProviderOperation.newInsert( - TvContract.Programs.CONTENT_URI), item, channel)); + TvContract.Programs.CONTENT_URI), item, channel.getChannelId())); if (ops.size() >= BATCH_OPERATION_COUNT) { applyBatch(channel.getName(), ops); ops.clear(); @@ -521,13 +516,9 @@ public class ChannelDataManager implements Handler.Callback { } private ContentProviderOperation buildContentProviderOperation( - ContentProviderOperation.Builder builder, EitItem item, TunerChannel channel) { - if (channel != null) { - builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channel.getChannelId()); - if (BuildCompat.isAtLeastN()) { - builder.withValue(TvContract.Programs.COLUMN_RECORDING_PROHIBITED, - channel.isRecordingProhibited() ? 1 : 0); - } + ContentProviderOperation.Builder builder, EitItem item, Long channelId) { + if (channelId != null) { + builder.withValue(TvContract.Programs.COLUMN_CHANNEL_ID, channelId); } if (item != null) { builder.withValue(TvContract.Programs.COLUMN_TITLE, item.getTitleText()) @@ -565,10 +556,7 @@ public class ChannelDataManager implements Handler.Callback { 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); @@ -610,29 +598,13 @@ public class ChannelDataManager implements Handler.Callback { } 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; - } - } - } + 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(); } } } diff --git a/src/com/android/tv/tuner/tvinput/EventDetector.java b/src/com/android/tv/tuner/tvinput/EventDetector.java index 96b20a4b..a132398f 100644 --- a/src/com/android/tv/tuner/tvinput/EventDetector.java +++ b/src/com/android/tv/tuner/tvinput/EventDetector.java @@ -21,8 +21,8 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.tv.tuner.TunerHal; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.ts.TsParser; import com.android.tv.tuner.data.PsiData; @@ -51,7 +51,7 @@ public class EventDetector { 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 final EventListener mEventListener; private int mFrequency; private String mModulation; private int mProgramNumber = ALL_PROGRAM_NUMBERS; @@ -105,10 +105,8 @@ public class EventDetector { item.setHasCaptionTrack(); } } - if (tunerChannel != null && !mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onEventDetected(tunerChannel, items); - } + if (tunerChannel != null && mEventListener != null) { + mEventListener.onEventDetected(tunerChannel, items); } } @@ -119,10 +117,8 @@ public class EventDetector { @Override public void onAllVctItemsParsed() { - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelScanDone(); - } + if (mEventListener != null) { + mEventListener.onChannelScanDone(); } } @@ -165,10 +161,8 @@ public class EventDetector { if (!found) { mVctProgramNumberSet.add(channelProgramNumber); } - if (!mEventListeners.isEmpty()) { - for (EventListener eventListener : mEventListeners) { - eventListener.onChannelDetected(tunerChannel, !found); - } + if (mEventListener != null) { + mEventListener.onChannelDetected(tunerChannel, !found); } } }; @@ -203,9 +197,11 @@ public class EventDetector { /** * Creates a detector for ATSC TV channles and program information. * @param usbTunerInteface {@link TunerHal} + * @param listener for ATSC TV channels and program information */ - public EventDetector(TunerHal usbTunerInteface) { + public EventDetector(TunerHal usbTunerInteface, EventListener listener) { mTunerHal = usbTunerInteface; + mEventListener = listener; } private void reset() { @@ -262,28 +258,4 @@ public class EventDetector { 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 index 46ff4ea1..61de24f4 100644 --- a/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java +++ b/src/com/android/tv/tuner/tvinput/FileSourceEventDetector.java @@ -24,8 +24,8 @@ 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.VctItem; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscAudioTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.source.FileTsStreamer; import com.android.tv.tuner.ts.TsParser; diff --git a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java index 0be29f25..6ec55e4f 100644 --- a/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java @@ -33,17 +33,13 @@ 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.dvr.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.Track.AtscCaptionTrack; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor; import com.android.tv.tuner.exoplayer.SampleExtractor; @@ -57,10 +53,10 @@ 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.CountDownLatch; import java.util.concurrent.TimeUnit; /** @@ -75,7 +71,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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; @@ -85,23 +80,20 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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}) + @IntDef({STATE_IDLE, 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 int STATE_TUNED = 2; + private static final int STATE_RECORDING = 3; private static final long CHANNEL_ID_NONE = -1; - private static final int MAX_TUNING_RETRY = 6; private final Context mContext; private final ChannelDataManager mChannelDataManager; @@ -116,16 +108,13 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, private long mRecordStartTime; private long mRecordEndTime; private boolean mRecorderRunning; + private BufferManager mBufferManager; 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()); @@ -168,7 +157,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, if (mChannel == null || mChannel.compareTo(channel) != 0) { return; } - mHandler.obtainMessage(MSG_UPDATE_CC_INFO, new Pair<>(channel, items)).sendToTarget(); mChannelDataManager.notifyEventDetected(channel, items); } @@ -190,7 +178,7 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, @MainThread public void tune(Uri channelUri) { mHandler.removeCallbacksAndMessages(null); - mHandler.obtainMessage(MSG_TUNE, 0, 0, channelUri).sendToTarget(); + mHandler.obtainMessage(MSG_TUNE, channelUri).sendToTarget(); } /** @@ -223,22 +211,11 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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(); - } - } + mSession.onTuned(channelUri); + } else { + reset(); } return true; } @@ -304,12 +281,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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; } @@ -339,17 +310,20 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, mRecorder.release(); mRecorder = null; } + if (mBufferManager != null) { + mBufferManager.close(); + mBufferManager = 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) { + if (mSessionState != STATE_IDLE) { mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN); Log.e(TAG, "Tuning was requested from wrong status."); return false; @@ -359,10 +333,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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); @@ -371,9 +341,9 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } mTunerSource = mSourceManager.createDataSource(mContext, mChannel, this); if (mTunerSource == null) { - // Retry tuning in this case. - mSessionState = STATE_TUNING; - return true; + mSession.onError(TvInputManager.RECORDING_ERROR_RESOURCE_BUSY); + Log.w(TAG, "Tuner stream cannot be created due to resource shortage."); + return false; } mSessionState = STATE_TUNED; return true; @@ -395,10 +365,10 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, } // Since tuning might be happened a while ago, shifts the start position of tuned source. mTunerSource.shiftStartPosition(mTunerSource.getBufferedPosition()); + mBufferManager = new BufferManager(new DvrStorageManager(mStorageDir, true)); mRecordStartTime = System.currentTimeMillis(); - mDvrStorageManager = new DvrStorageManager(mStorageDir, true); - mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, - new BufferManager(mDvrStorageManager), this, true); + mRecorder = new ExoPlayerSampleExtractor(Uri.EMPTY, mTunerSource, mBufferManager, this, + true); mRecorder.setOnCompletionListener(this, mHandler); mProgramUri = programUri; mSessionState = STATE_RECORDING; @@ -422,34 +392,6 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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; @@ -624,25 +566,15 @@ public class TunerRecordingSessionWorker implements PlaybackBufferListener, 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); + Uri uri = insertRecordedProgram(getRecordedProgram(), mChannel.getChannelId(), + Uri.fromFile(mStorageDir).toString(), 1024 * 1024, mRecordStartTime, + mRecordStartTime + TimeUnit.MICROSECONDS.toMillis(lastExtractedPositionUs)); 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); } diff --git a/src/com/android/tv/tuner/tvinput/TunerSession.java b/src/com/android/tv/tuner/tvinput/TunerSession.java index d1ee3c6f..5c61402e 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSession.java +++ b/src/com/android/tv/tuner/tvinput/TunerSession.java @@ -41,7 +41,7 @@ import com.android.tv.tuner.R; 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.Track.AtscCaptionTrack; +import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack; import com.android.tv.tuner.exoplayer.buffer.BufferManager; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.util.GlobalSettingsUtils; @@ -81,7 +81,8 @@ public class TunerSession extends TvInputService.Session implements Handler.Call private boolean mPlayPaused; private long mTuneStartTimestamp; - public TunerSession(Context context, ChannelDataManager channelDataManager) { + public TunerSession(Context context, ChannelDataManager channelDataManager, + BufferManager bufferManager) { super(context); mContext = context; mUiHandler = new Handler(this); @@ -96,9 +97,12 @@ public class TunerSession extends TvInputService.Session implements Handler.Call mStatusView.setVisibility(showDebug ? View.VISIBLE : View.INVISIBLE); mAudioStatusView = (TextView) mOverlayView.findViewById(R.id.audio_status); mAudioStatusView.setVisibility(View.INVISIBLE); + mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( + context.getString(R.string.ut_surround_sound_disabled)))); CaptionLayout captionLayout = (CaptionLayout) mOverlayView.findViewById(R.id.caption); mCaptionTrackRenderer = new CaptionTrackRenderer(captionLayout); - mSessionWorker = new TunerSessionWorker(context, channelDataManager, this); + mSessionWorker = new TunerSessionWorker(context, channelDataManager, + bufferManager, this); } public boolean isReleased() { @@ -268,13 +272,10 @@ public class TunerSession extends TvInputService.Session implements Handler.Call // 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)))); + mAudioStatusView.setVisibility(View.VISIBLE); } else { - mAudioStatusView.setText(Html.fromHtml(StatusTextUtils.getAudioWarningInHTML( - mContext.getString(R.string.audio_passthrough_not_supported)))); + Log.e(TAG, "Audio is unavailable, surround sound setting is " + value); } - mAudioStatusView.setVisibility(View.VISIBLE); return true; } case MSG_UI_HIDE_AUDIO_UNPLAYABLE: { diff --git a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java index 41f8ce5f..5230298e 100644 --- a/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java +++ b/src/com/android/tv/tuner/tvinput/TunerSessionWorker.java @@ -35,7 +35,6 @@ 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; @@ -48,30 +47,28 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvContentRatingCache; import com.android.tv.tuner.TunerPreferences; import com.android.tv.tuner.data.Cea708Data; -import com.android.tv.tuner.data.Channel; import com.android.tv.tuner.data.PsipData.EitItem; import com.android.tv.tuner.data.PsipData.TvTracksInterface; -import com.android.tv.tuner.data.Track.AtscAudioTrack; -import com.android.tv.tuner.data.Track.AtscCaptionTrack; 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.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.io.FileNotFoundException; +import java.io.IOException; 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; +import java.util.concurrent.CountDownLatch; /** * {@link TunerSessionWorker} implements a handler thread which processes TV input jobs @@ -85,9 +82,6 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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; @@ -153,18 +147,10 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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; - - // 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 volatile Surface mSurface; private volatile float mVolume = 1.0f; private volatile boolean mCaptionEnabled; @@ -173,7 +159,6 @@ public class TunerSessionWorker implements PlaybackBufferListener, private volatile Long mRecordingDuration; private volatile long mRecordStartTimeMs; private volatile long mBufferStartTimeMs; - private volatile boolean mTrickplayDisabled; private String mRecordingId; private final Handler mHandler; private int mRetryCount; @@ -192,19 +177,19 @@ public class TunerSessionWorker implements PlaybackBufferListener, private TvContentRating mUnblockedContentRating; private long mLastPositionMs; private AudioCapabilities mAudioCapabilities; + private final CountDownLatch mReleaseLatch = new CountDownLatch(1); private long mLastLimitInBytes; + private long mLastPositionInBytes; + private final BufferManager mBufferManager; private final TvContentRatingCache mTvContentRatingCache = TvContentRatingCache.getInstance(); private final TunerSession mSession; 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) { + BufferManager bufferManager, TunerSession tunerSession) { if (DEBUG) Log.d(TAG, "TunerSessionWorker created"); mContext = context; @@ -226,10 +211,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); mCaptionEnabled = captioningManager.isEnabled(); mPlaybackParams.setSpeed(1.0f); - mMaxTrickplayBufferSizeMb = - SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); - mTrickplayBufferDir = context.getCacheDir(); - mTrickplayDisabled = mTrickplayBufferDir == null; + mBufferManager = bufferManager; mPreparingStartTimeMs = INVALID_TIME; mBufferingStartTimeMs = INVALID_TIME; mReadyStartTimeMs = INVALID_TIME; @@ -303,21 +285,24 @@ public class TunerSessionWorker implements PlaybackBufferListener, } private Long getDurationForRecording(String recordingId) { - DvrStorageManager storageManager = + try { + 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); + Pair<String, MediaFormat> trackInfo = null; + try { + trackInfo = storageManager.readTrackInfoFile(false); + } catch (FileNotFoundException e) { + } + if (trackInfo == null) { + trackInfo = storageManager.readTrackInfoFile(true); + } + Long durationUs = trackInfo.second.getLong(MediaFormat.KEY_DURATION); // we need duration by milli for trickplay notification. return durationUs != null ? durationUs / 1000 : null; + } catch (IOException e) { + Log.e(TAG, "meta file for recording was not found: " + recordingId); + return null; } - Log.e(TAG, "meta file for recording was not found: " + recordingId); - return null; } @MainThread @@ -356,12 +341,16 @@ public class TunerSessionWorker implements PlaybackBufferListener, @MainThread public void release() { if (DEBUG) Log.d(TAG, "release()"); - synchronized (mReleaseLock) { - mReleaseRequested = true; - } mChannelDataManager.setListener(null); mHandler.removeCallbacksAndMessages(null); mHandler.sendEmptyMessage(MSG_RELEASE); + try { + mReleaseLatch.await(); + } catch (InterruptedException e) { + Log.e(TAG, "Couldn't wait for finish of MSG_RELEASE", e); + } finally { + mHandler.getLooper().quitSafely(); + } } // MpegTsPlayer.Listener @@ -378,7 +367,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (playbackState == ExoPlayer.STATE_READY) { if (DEBUG) Log.d(TAG, "ExoPlayer ready"); if (!mPlayerStarted) { - sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer)); + sendMessage(MSG_START_PLAYBACK, mPlayer); } mReadyStartTimeMs = SystemClock.elapsedRealtime(); } else if (playbackState == ExoPlayer.STATE_PREPARING) { @@ -390,7 +379,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, // 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)); + sendMessage(MSG_RETRY_PLAYBACK, mPlayer); } } mPlayerState = playbackState; @@ -408,8 +397,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, // 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(); + mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer).sendToTarget(); } } @@ -427,12 +415,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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(); - } + mBufferStartTimeMs = mRecordStartTimeMs = + (mRecordingId != null) ? 0 : System.currentTimeMillis(); notifyVideoAvailable(); mReportedDrawnToSurface = true; @@ -515,8 +499,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, @Override public void onDiskTooSlow() { - mTrickplayDisabled = true; - sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); + sendMessage(MSG_RETRY_PLAYBACK, mPlayer); } // EventDetector.EventListener @@ -619,28 +602,6 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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); @@ -655,8 +616,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); return true; } - clearCallbacksAndMessagesSafely(); - mChannelDataManager.removeAllCallbacksAndMessages(); + mHandler.removeCallbacksAndMessages(null); if (channel != null) { mChannelDataManager.requestProgramsData(channel); } @@ -664,8 +624,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, // TODO: Need to refactor. notifyContentAllowed() should not be called if parental // control is turned on. mSession.notifyContentAllowed(); - resetTvTracks(); resetPlayback(); + resetTvTracks(); mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, RESCHEDULE_PROGRAMS_INITIAL_DELAY_MS); return true; @@ -673,7 +633,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, case MSG_STOP_TUNE: { if (DEBUG) Log.d(TAG, "MSG_STOP_TUNE"); mChannel = null; - stopPlayback(true); + stopPlayback(); stopCaptionTrack(); resetTvTracks(); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN); @@ -682,17 +642,14 @@ public class TunerSessionWorker implements PlaybackBufferListener, case MSG_RELEASE: { if (DEBUG) Log.d(TAG, "MSG_RELEASE"); mHandler.removeCallbacksAndMessages(null); - stopPlayback(true); + stopPlayback(); stopCaptionTrack(); mSourceManager.release(); - mHandler.getLooper().quitSafely(); - if (mIsActiveSession) { - sActiveSessionSemaphore.release(); - } + mReleaseLatch.countDown(); return true; } case MSG_RETRY_PLAYBACK: { - if (System.identityHashCode(mPlayer) == (int) msg.obj) { + if (mPlayer == 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. @@ -701,14 +658,13 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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); + stopPlayback(); stopCaptionTrack(); notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL); @@ -723,14 +679,13 @@ public class TunerSessionWorker implements PlaybackBufferListener, } 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); + startPlayback(msg.obj); } return true; } @@ -835,11 +790,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, return true; } case MSG_RESCHEDULE_PROGRAMS: { - if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) { - mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS); - } else { - doReschedulePrograms(); - } + doReschedulePrograms(); return true; } case MSG_PARENTAL_CONTROLS: { @@ -863,8 +814,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, return true; } case MSG_SELECT_TRACK: { - if (mChannel != null || mRecordingId != null) { + if (mChannel != null) { doSelectTrack(msg.arg1, (String) msg.obj); + } else if (mRecordingId != null) { + // TODO : mChannel == null && mRecordingId != null + Log.d(TAG, "track selected for recording"); } return true; } @@ -955,6 +909,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, } TsDataSource source = mPlayer.getDataSource(); long limitInBytes = source != null ? source.getBufferedPosition() : 0L; + long positionInBytes = source != null ? source.getLastReadPosition() : 0L; if (TunerDebug.ENABLED) { TunerDebug.calculateDiff(); mSession.sendUiMessage(TunerSession.MSG_UI_SET_STATUS_TEXT, @@ -972,8 +927,14 @@ public class TunerSessionWorker implements PlaybackBufferListener, TunerDebug.getVideoPtsUsRate() ))); } + if (DEBUG) { + Log.d(TAG, String.format("MSG_CHECK_SIGNAL position: %d, limit: %d", + positionInBytes, limitInBytes)); + } mSession.sendUiMessage(TunerSession.MSG_UI_HIDE_MESSAGE); long currentTime = SystemClock.elapsedRealtime(); + boolean noBufferRead = positionInBytes == mLastPositionInBytes + && limitInBytes == mLastLimitInBytes; boolean isBufferingTooLong = mBufferingStartTimeMs != INVALID_TIME && currentTime - mBufferingStartTimeMs > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; @@ -982,11 +943,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, > PLAYBACK_STATE_CHANGED_WAITING_THRESHOLD_MS; boolean isWeakSignal = source != null && mChannel.getType() == Channel.TYPE_TUNER - && (isBufferingTooLong || isPreparingTooLong); + && (noBufferRead || 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); + mHandler.sendMessageDelayed(mHandler.obtainMessage( + MSG_RETRY_PLAYBACK, mPlayer), PLAYBACK_RETRY_DELAY_MS); } if (mPlayer != null) { mPlayer.setAudioTrack(false); @@ -1005,6 +966,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } mLastLimitInBytes = limitInBytes; + mLastPositionInBytes = positionInBytes; mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_PERIOD_MS); return true; } @@ -1037,8 +999,15 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (trackId == null) { return; } - if (numTrackId != mPlayer.getSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO)) { - mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, numTrackId); + AtscAudioTrack audioTrack = mAudioTrackMap.get(numTrackId); + if (audioTrack == null) { + return; + } + int oldAudioPid = mChannel.getAudioPid(); + mChannel.selectAudioTrack(audioTrack.index); + int newAudioPid = mChannel.getAudioPid(); + if (oldAudioPid != newAudioPid) { + mPlayer.setSelectedTrack(MpegTsPlayer.TRACK_TYPE_AUDIO, audioTrack.index); } mSession.notifyTrackSelected(type, trackId); } else if (type == TvTrackInfo.TYPE_SUBTITLE) { @@ -1061,22 +1030,11 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - private MpegTsPlayer createPlayer(AudioCapabilities capabilities) { + private MpegTsPlayer createPlayer(AudioCapabilities capabilities, BufferManager bufferManager) { if (capabilities == null) { Log.w(TAG, "No Audio Capabilities"); } - BufferManager bufferManager = null; - if (mRecordingId != null) { - StorageManager storageManager = - new DvrStorageManager(new File(getRecordingPath()), false); - bufferManager = new BufferManager(storageManager); - updateCaptionTracks(((DvrStorageManager)storageManager).readCaptionInfoFiles()); - } else if (!mTrickplayDisabled && 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); @@ -1111,26 +1069,24 @@ public class TunerSessionWorker implements PlaybackBufferListener, } 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 { + 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.getAudioTracks() == null || fromPmt)) { + updateAudioTracks(audioTracks); + } + if (captionTracks == null || captionTracks.isEmpty()) { + if (tvTracksInterface.hasCaptionTrack()) { updateCaptionTracks(captionTracks); } + } else { + updateCaptionTracks(captionTracks); } } @@ -1176,24 +1132,25 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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; + AtscAudioTrack audioTrack = mAudioTrackMap.get(i); + if (audioTrack == null) { + continue; + } + String language = audioTrack.language; + if (language == null && mChannel.getAudioTracks() != null + && mChannel.getAudioTracks().size() == mAudioTrackMap.size()) { + // If a language is not present, use a language field in PMT section parsed. + language = mChannel.getAudioTracks().get(i).language; + } + // Save the index to the audio track. + // Later, when an audio track is selected, both the audio pid and its audio stream + // type reside in the selected index position of the tuner channel's audio data. + audioTrack.index = i; TvTrackInfo.Builder builder = new TvTrackInfo.Builder( TvTrackInfo.TYPE_AUDIO, AUDIO_TRACK_PREFIX + i); builder.setLanguage(language); - builder.setAudioChannelCount(infoFromPlayer.channelCount); - builder.setAudioSampleRate(infoFromPlayer.sampleRate); + builder.setAudioChannelCount(audioTrack.channelCount); + builder.setAudioSampleRate(audioTrack.sampleRate); TvTrackInfo track = builder.build(); mTvTracks.add(track); } @@ -1269,10 +1226,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - private void stopPlayback(boolean removeChannelDataCallbacks) { - if (removeChannelDataCallbacks) { - mChannelDataManager.removeAllCallbacksAndMessages(); - } + private void stopPlayback() { + mChannelDataManager.removeAllCallbacksAndMessages(); if (mPlayer != null) { mPlayer.setPlayWhenReady(false); mPlayer.release(); @@ -1289,9 +1244,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - private void startPlayback(int playerHashCode) { + private void startPlayback(Object playerObj) { // TODO: provide hasAudio()/hasVideo() for play recordings. - if (mPlayer == null || System.identityHashCode(mPlayer) != playerHashCode) { + if (mPlayer == null || mPlayer != playerObj) { return; } if (mChannel != null && !mChannel.hasAudio()) { @@ -1304,7 +1259,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, if (mChannel != null && ((mChannel.hasAudio() && !mPlayer.hasAudio()) || (mChannel.hasVideo() && !mPlayer.hasVideo()))) { // Tracks haven't been detected in the extractor. Try again. - sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)); + sendMessage(MSG_RETRY_PLAYBACK, mPlayer); return; } // Since mSurface is volatile, we define a local variable surface to keep the same value @@ -1331,7 +1286,9 @@ public class TunerSessionWorker implements PlaybackBufferListener, return; } mSourceManager.setKeepTuneStatus(true); - MpegTsPlayer player = createPlayer(mAudioCapabilities); + BufferManager bufferManager = mChannel != null ? mBufferManager : new BufferManager( + new DvrStorageManager(new File(getRecordingPath()), false)); + MpegTsPlayer player = createPlayer(mAudioCapabilities, bufferManager); player.setCaptionServiceNumber(Cea708Data.EMPTY_SERVICE_NUMBER); player.setVideoEventListener(this); player.setCaptionServiceNumber(mCaptionTrack != null ? @@ -1343,8 +1300,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, // 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); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, - System.identityHashCode(mPlayer)), PLAYBACK_RETRY_DELAY_MS); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY_PLAYBACK, mPlayer), + PLAYBACK_RETRY_DELAY_MS); } } else { mPlayer = player; @@ -1357,7 +1314,7 @@ public class TunerSessionWorker implements PlaybackBufferListener, private void resetPlayback() { long timestamp, oldTimestamp; timestamp = SystemClock.elapsedRealtime(); - stopPlayback(false); + stopPlayback(); stopCaptionTrack(); if (ENABLE_PROFILER) { oldTimestamp = timestamp; @@ -1379,12 +1336,8 @@ public class TunerSessionWorker implements PlaybackBufferListener, 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(); - } + mBufferStartTimeMs = mRecordStartTimeMs = + (mRecordingId != null) ? 0 : System.currentTimeMillis(); mLastPositionMs = 0; mCaptionTrack = null; mHandler.sendEmptyMessage(MSG_PARENTAL_CONTROLS); @@ -1591,15 +1544,15 @@ public class TunerSessionWorker implements PlaybackBufferListener, } mChannelBlocked = channelBlocked; if (mChannelBlocked) { - clearCallbacksAndMessagesSafely(); - stopPlayback(true); + mHandler.removeCallbacksAndMessages(null); + stopPlayback(); resetTvTracks(); if (contentRating != null) { mSession.notifyContentBlocked(contentRating); } mHandler.sendEmptyMessageDelayed(MSG_PARENTAL_CONTROLS, PARENTAL_CONTROLS_INTERVAL_MS); } else { - clearCallbacksAndMessagesSafely(); + mHandler.removeCallbacksAndMessages(null); resetPlayback(); mSession.notifyContentAllowed(); mHandler.sendEmptyMessageDelayed(MSG_RESCHEDULE_PROGRAMS, @@ -1609,17 +1562,6 @@ public class TunerSessionWorker implements PlaybackBufferListener, } } - @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; diff --git a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java index 6594e089..684ebdbd 100644 --- a/src/com/android/tv/tuner/tvinput/TunerTvInputService.java +++ b/src/com/android/tv/tuner/tvinput/TunerTvInputService.java @@ -28,6 +28,9 @@ 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 com.android.tv.tuner.exoplayer.buffer.BufferManager; +import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager; +import com.android.tv.tuner.util.SystemPropertiesProxy; import java.util.Collections; import java.util.Set; @@ -42,6 +45,9 @@ public class TunerTvInputService extends TvInputService private static final String TAG = "TunerTvInputService"; private static final boolean DEBUG = false; + 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 private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100; // WeakContainer for {@link TvInputSessionImpl} @@ -49,6 +55,7 @@ public class TunerTvInputService extends TvInputService private ChannelDataManager mChannelDataManager; private AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; private AudioCapabilities mAudioCapabilities; + private BufferManager mBufferManager; @Override public void onCreate() { @@ -58,6 +65,7 @@ public class TunerTvInputService extends TvInputService mChannelDataManager = new ChannelDataManager(getApplicationContext()); mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(getApplicationContext(), this); mAudioCapabilitiesReceiver.register(); + mBufferManager = createBufferManager(); if (CommonFeatures.DVR.isEnabled(this)) { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -71,6 +79,11 @@ public class TunerTvInputService extends TvInputService jobScheduler.schedule(job); } } + if (mBufferManager == null) { + Log.i(TAG, "Trickplay is disabled"); + } else { + Log.i(TAG, "Trickplay is enabled"); + } } @Override @@ -79,6 +92,9 @@ public class TunerTvInputService extends TvInputService super.onDestroy(); mChannelDataManager.release(); mAudioCapabilitiesReceiver.unregister(); + if (mBufferManager != null) { + mBufferManager.close(); + } } @Override @@ -90,7 +106,8 @@ public class TunerTvInputService extends TvInputService public Session onCreateSession(String inputId) { if (DEBUG) Log.d(TAG, "onCreateSession"); try { - final TunerSession session = new TunerSession(this, mChannelDataManager); + final TunerSession session = new TunerSession( + this, mChannelDataManager, mBufferManager); mTunerSessions.add(session); session.setAudioCapabilities(mAudioCapabilities); session.setOverlayViewEnabled(true); @@ -112,6 +129,17 @@ public class TunerTvInputService extends TvInputService } } + private BufferManager createBufferManager() { + int maxBufferSizeMb = + SystemPropertiesProxy.getInt(MAX_BUFFER_SIZE_KEY, MAX_BUFFER_SIZE_DEF); + if (maxBufferSizeMb >= MIN_BUFFER_SIZE_DEF) { + return new BufferManager( + new TrickplayStorageManager(getApplicationContext(), getCacheDir(), + 1024L * 1024 * maxBufferSizeMb)); + } + return null; + } + public static String getInputId(Context context) { return TvContract.buildInputId(new ComponentName(context, TunerTvInputService.class)); } 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 3942ce95..00000000 --- a/src/com/android/tv/tuner/util/PostalCodeUtils.java +++ /dev/null @@ -1,89 +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.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.Locale; - -/** - * A utility class to update, get, and set the last known postal or zip code. - */ -public class PostalCodeUtils { - private static final String TAG = "PostalCodeUtils"; - private static final String SUPPORTED_COUNTRY_CODE = Locale.US.getCountry(); - - /** 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()); - if (TextUtils.equals(address.getCountryCode(), SUPPORTED_COUNTRY_CODE)) { - 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() { - } - } -}
\ No newline at end of file diff --git a/src/com/android/tv/tuner/util/StringUtils.java b/src/com/android/tv/tuner/util/StringUtils.java new file mode 100644 index 00000000..15571e75 --- /dev/null +++ b/src/com/android/tv/tuner/util/StringUtils.java @@ -0,0 +1,38 @@ +/* + * 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 handling {@link String}. + */ +public final class StringUtils { + + private StringUtils() { } + + /** + * Returns compares two strings lexicographically and handles null values quietly. + */ + public static int compare(String a, String b) { + if (a == null) { + return b == null ? 0 : -1; + } + if (b == null) { + return 1; + } + return a.compareTo(b); + } +} diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java index 2817ccbf..62a64361 100644 --- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java +++ b/src/com/android/tv/tuner/util/SystemPropertiesProxy.java @@ -58,20 +58,4 @@ public class SystemPropertiesProxy { } 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/TunerInputInfoUtils.java b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java index fd9ec77d..5c411f64 100644 --- a/src/com/android/tv/tuner/util/TunerInputInfoUtils.java +++ b/src/com/android/tv/tuner/util/TunerInputInfoUtils.java @@ -25,7 +25,6 @@ import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; import android.util.Log; -import android.util.Pair; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.tuner.R; @@ -44,31 +43,23 @@ public class TunerInputInfoUtils { */ @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) { + public static TvInputInfo buildTunerInputInfo(Context context, boolean fromBuiltInTuner) { + int numOfDevices = TunerHal.getTunerCount(context); + if (numOfDevices == 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; + TvInputInfo.Builder builder = new TvInputInfo.Builder(context, new ComponentName(context, + TunerTvInputService.class)); + if (fromBuiltInTuner) { + builder.setLabel(R.string.bt_app_name); + } else { + builder.setLabel(R.string.ut_app_name); } 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) + return builder.setCanRecord(CommonFeatures.DVR.isEnabled(context)) + .setTunerCount(numOfDevices) .build(); - } catch (IllegalArgumentException | NullPointerException e) { + } catch (NullPointerException e) { // TunerTvInputService is not enabled. return null; } @@ -82,7 +73,7 @@ public class TunerInputInfoUtils { public static void updateTunerInputInfo(Context context) { if (BuildCompat.isAtLeastN()) { if (DEBUG) Log.d(TAG, "updateTunerInputInfo()"); - TvInputInfo info = buildTunerInputInfo(context); + TvInputInfo info = buildTunerInputInfo(context, isBuiltInTuner(context)); if (info != null) { ((TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE)) .updateTvInputInfo(info); @@ -97,4 +88,13 @@ public class TunerInputInfoUtils { } } } -}
\ No newline at end of file + + /** + * Returns if the current tuner service is for a built-in tuner. + * + * @param context {@link Context} instance + */ + public static boolean isBuiltInTuner(Context context) { + return TunerHal.getTunerType(context) == TunerHal.TUNER_TYPE_BUILT_IN; + } +} |