diff options
Diffstat (limited to 'tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java')
-rw-r--r-- | tuner/src/com/android/tv/tuner/source/TunerTsStreamerManager.java | 303 |
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); + } + } +} |