aboutsummaryrefslogtreecommitdiff
path: root/tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java')
-rw-r--r--tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java303
1 files changed, 303 insertions, 0 deletions
diff --git a/tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java b/tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
new file mode 100644
index 00000000..44fb41e6
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.tuner.source;
+
+import android.content.Context;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.util.AutoCloseableUtils;
+import com.android.tv.tuner.TunerHal;
+import com.android.tv.tuner.data.TunerChannel;
+import com.android.tv.tuner.tvinput.EventDetector;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages {@link TunerTsStreamer} for playback and recording. The class hides handling of {@link
+ * TunerHal} from other classes. This class is used by {@link TsDataSourceManager}. Don't use this
+ * class directly.
+ */
+class TunerTsStreamerManager {
+ // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator
+ // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from
+ // the same session.
+ private final Object mCancelLock = new Object();
+ private final StreamerFinder mStreamerFinder = new StreamerFinder();
+ private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>();
+ private final Map<Integer, EventDetector.EventListener> mListeners = new HashMap<>();
+ private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>();
+ private final TunerHalManager mTunerHalManager = new TunerHalManager();
+ private static TunerTsStreamerManager sInstance;
+
+ /**
+ * Returns the singleton instance for the class
+ *
+ * @return TunerTsStreamerManager
+ */
+ static synchronized TunerTsStreamerManager getInstance() {
+ if (sInstance == null) {
+ sInstance = new TunerTsStreamerManager();
+ }
+ return sInstance;
+ }
+
+ private TunerTsStreamerManager() {}
+
+ synchronized TsDataSource createDataSource(
+ Context context,
+ TunerChannel channel,
+ EventDetector.EventListener listener,
+ int sessionId,
+ boolean reuse) {
+ TsStreamerCreator creator;
+ synchronized (mCancelLock) {
+ if (mStreamerFinder.containsLocked(channel)) {
+ mStreamerFinder.appendSessionLocked(channel, sessionId);
+ TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel);
+ TsDataSource source = streamer.createDataSource();
+ mListeners.put(sessionId, listener);
+ streamer.registerListener(listener);
+ mSourceToStreamerMap.put(source, streamer);
+ return source;
+ }
+ creator = new TsStreamerCreator(context, channel, listener);
+ mCreators.put(sessionId, creator);
+ }
+ TunerTsStreamer streamer = creator.create(sessionId, reuse);
+ synchronized (mCancelLock) {
+ mCreators.remove(sessionId);
+ if (streamer == null) {
+ return null;
+ }
+ if (!creator.isCancelledLocked()) {
+ mStreamerFinder.putLocked(channel, sessionId, streamer);
+ TsDataSource source = streamer.createDataSource();
+ mListeners.put(sessionId, listener);
+ mSourceToStreamerMap.put(source, streamer);
+ return source;
+ }
+ }
+ // Created streamer was cancelled by a new tune request.
+ streamer.stopStream();
+ TunerHal hal = streamer.getTunerHal();
+ hal.setHasPendingTune(false);
+ mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
+ return null;
+ }
+
+ synchronized void releaseDataSource(TsDataSource source, int sessionId, boolean reuse) {
+ TunerTsStreamer streamer;
+ synchronized (mCancelLock) {
+ streamer = mSourceToStreamerMap.get(source);
+ mSourceToStreamerMap.remove(source);
+ if (streamer == null) {
+ return;
+ }
+ EventDetector.EventListener listener = mListeners.remove(sessionId);
+ streamer.unregisterListener(listener);
+ TunerChannel channel = streamer.getChannel();
+ SoftPreconditions.checkState(channel != null);
+ mStreamerFinder.removeSessionLocked(channel, sessionId);
+ if (mStreamerFinder.containsLocked(channel)) {
+ return;
+ }
+ }
+ streamer.stopStream();
+ TunerHal hal = streamer.getTunerHal();
+ hal.setHasPendingTune(false);
+ mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
+ }
+
+ void setHasPendingTune(int sessionId) {
+ synchronized (mCancelLock) {
+ if (mCreators.containsKey(sessionId)) {
+ mCreators.get(sessionId).cancelLocked();
+ }
+ }
+ }
+
+ /** Add tuner hal into TunerHalManager for test. */
+ void addTunerHal(TunerHal tunerHal, int sessionId) {
+ mTunerHalManager.addTunerHal(tunerHal, sessionId);
+ }
+
+ synchronized void release(int sessionId) {
+ mTunerHalManager.releaseCachedHal(sessionId);
+ }
+
+ private static class StreamerFinder {
+ private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>();
+ private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>();
+
+ // @GuardedBy("mCancelLock")
+ private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) {
+ Set<Integer> sessions = new HashSet<>();
+ sessions.add(sessionId);
+ mSessions.put(channel, sessions);
+ mStreamers.put(channel, streamer);
+ }
+
+ // @GuardedBy("mCancelLock")
+ private void appendSessionLocked(TunerChannel channel, int sessionId) {
+ if (mSessions.containsKey(channel)) {
+ mSessions.get(channel).add(sessionId);
+ }
+ }
+
+ // @GuardedBy("mCancelLock")
+ private void removeSessionLocked(TunerChannel channel, int sessionId) {
+ Set<Integer> sessions = mSessions.get(channel);
+ sessions.remove(sessionId);
+ if (sessions.size() == 0) {
+ mSessions.remove(channel);
+ mStreamers.remove(channel);
+ }
+ }
+
+ // @GuardedBy("mCancelLock")
+ private boolean containsLocked(TunerChannel channel) {
+ return mSessions.containsKey(channel);
+ }
+
+ // @GuardedBy("mCancelLock")
+ private TunerTsStreamer getStreamerLocked(TunerChannel channel) {
+ return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null;
+ }
+ }
+
+ /**
+ * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same session.
+ * The class supports the cancellation in creating new {@link TunerTsStreamer}.
+ */
+ private class TsStreamerCreator {
+ private final Context mContext;
+ private final TunerChannel mChannel;
+ private final EventDetector.EventListener mEventListener;
+ // mCancelled will be {@code true} if a new tune request for the same session
+ // cancels create().
+ private boolean mCancelled;
+ private TunerHal mTunerHal;
+
+ private TsStreamerCreator(
+ Context context, TunerChannel channel, EventDetector.EventListener listener) {
+ mContext = context;
+ mChannel = channel;
+ mEventListener = listener;
+ }
+
+ private TunerTsStreamer create(int sessionId, boolean reuse) {
+ TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId);
+ if (hal == null) {
+ return null;
+ }
+ boolean canceled = false;
+ synchronized (mCancelLock) {
+ if (!mCancelled) {
+ mTunerHal = hal;
+ } else {
+ canceled = true;
+ }
+ }
+ if (!canceled) {
+ TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext);
+ if (tsStreamer.startStream(mChannel)) {
+ return tsStreamer;
+ }
+ synchronized (mCancelLock) {
+ mTunerHal = null;
+ }
+ }
+ hal.setHasPendingTune(false);
+ // Since TunerTsStreamer is not properly created, closes TunerHal.
+ // And do not re-use TunerHal when it is not cancelled.
+ mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse);
+ return null;
+ }
+
+ // @GuardedBy("mCancelLock")
+ private void cancelLocked() {
+ if (mCancelled) {
+ return;
+ }
+ mCancelled = true;
+ if (mTunerHal != null) {
+ mTunerHal.setHasPendingTune(true);
+ }
+ }
+
+ // @GuardedBy("mCancelLock")
+ private boolean isCancelledLocked() {
+ return mCancelled;
+ }
+ }
+
+ /**
+ * Supports sharing {@link TunerHal} among multiple sessions. The class also supports session
+ * affinity for {@link TunerHal} allocation.
+ */
+ private static class TunerHalManager {
+ private final Map<Integer, TunerHal> mTunerHals = new HashMap<>();
+
+ private TunerHal getOrCreateTunerHal(Context context, int sessionId) {
+ // Handles session affinity.
+ TunerHal hal = mTunerHals.get(sessionId);
+ if (hal != null) {
+ mTunerHals.remove(sessionId);
+ return hal;
+ }
+ // Finds a TunerHal which is cached for other sessions.
+ Iterator it = mTunerHals.keySet().iterator();
+ if (it.hasNext()) {
+ Integer key = (Integer) it.next();
+ hal = mTunerHals.get(key);
+ mTunerHals.remove(key);
+ return hal;
+ }
+ return TunerHal.createInstance(context);
+ }
+
+ private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
+ if (!reuse || !hal.isReusable()) {
+ AutoCloseableUtils.closeQuietly(hal);
+ return;
+ }
+ TunerHal cachedHal = mTunerHals.get(sessionId);
+ if (cachedHal != hal) {
+ mTunerHals.put(sessionId, hal);
+ if (cachedHal != null) {
+ AutoCloseableUtils.closeQuietly(cachedHal);
+ }
+ }
+ }
+
+ private void releaseCachedHal(int sessionId) {
+ TunerHal hal = mTunerHals.get(sessionId);
+ if (hal != null) {
+ mTunerHals.remove(sessionId);
+ }
+ if (hal != null) {
+ AutoCloseableUtils.closeQuietly(hal);
+ }
+ }
+
+ private void addTunerHal(TunerHal tunerHal, int sessionId) {
+ mTunerHals.put(sessionId, tunerHal);
+ }
+ }
+}