1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.tuner.source; 18 19 import android.content.Context; 20 21 import com.android.tv.common.AutoCloseableUtils; 22 import com.android.tv.common.SoftPreconditions; 23 import com.android.tv.tuner.TunerHal; 24 import com.android.tv.tuner.data.TunerChannel; 25 import com.android.tv.tuner.tvinput.EventDetector; 26 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.Iterator; 30 import java.util.Map; 31 import java.util.Set; 32 33 /** 34 * Manages {@link TunerTsStreamer} for playback and recording. 35 * The class hides handling of {@link TunerHal} from other classes. 36 * This class is used by {@link TsDataSourceManager}. Don't use this class directly. 37 */ 38 class TunerTsStreamerManager { 39 // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator 40 // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from 41 // the same session. 42 private final Object mCancelLock = new Object(); 43 private final StreamerFinder mStreamerFinder = new StreamerFinder(); 44 private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>(); 45 private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>(); 46 private final TunerHalManager mTunerHalManager = new TunerHalManager(); 47 private static TunerTsStreamerManager sInstance; 48 49 /** 50 * Returns the singleton instance for the class 51 * @return TunerTsStreamerManager 52 */ getInstance()53 static synchronized TunerTsStreamerManager getInstance() { 54 if (sInstance == null) { 55 sInstance = new TunerTsStreamerManager(); 56 } 57 return sInstance; 58 } 59 TunerTsStreamerManager()60 private TunerTsStreamerManager() { } 61 createDataSource( Context context, TunerChannel channel, EventDetector.EventListener listener, int sessionId, boolean reuse)62 synchronized TsDataSource createDataSource( 63 Context context, TunerChannel channel, EventDetector.EventListener listener, 64 int sessionId, boolean reuse) { 65 TsStreamerCreator creator; 66 synchronized (mCancelLock) { 67 if (mStreamerFinder.containsLocked(channel)) { 68 mStreamerFinder.appendSessionLocked(channel, sessionId); 69 TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); 70 TsDataSource source = streamer.createDataSource(); 71 mSourceToStreamerMap.put(source, streamer); 72 return source; 73 } 74 creator = new TsStreamerCreator(context, channel, listener); 75 mCreators.put(sessionId, creator); 76 } 77 TunerTsStreamer streamer = creator.create(sessionId, reuse); 78 synchronized (mCancelLock) { 79 mCreators.remove(sessionId); 80 if (streamer == null) { 81 return null; 82 } 83 if (!creator.isCancelledLocked()) { 84 mStreamerFinder.putLocked(channel, sessionId, streamer); 85 TsDataSource source = streamer.createDataSource(); 86 mSourceToStreamerMap.put(source, streamer); 87 return source; 88 } 89 } 90 // Created streamer was cancelled by a new tune request. 91 streamer.stopStream(); 92 TunerHal hal = streamer.getTunerHal(); 93 hal.setHasPendingTune(false); 94 mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); 95 return null; 96 } 97 releaseDataSource(TsDataSource source, int sessionId, boolean reuse)98 synchronized void releaseDataSource(TsDataSource source, int sessionId, 99 boolean reuse) { 100 TunerTsStreamer streamer; 101 synchronized (mCancelLock) { 102 streamer = mSourceToStreamerMap.get(source); 103 mSourceToStreamerMap.remove(source); 104 if (streamer == null) { 105 return; 106 } 107 TunerChannel channel = streamer.getChannel(); 108 SoftPreconditions.checkState(channel != null); 109 mStreamerFinder.removeSessionLocked(channel, sessionId); 110 if (mStreamerFinder.containsLocked(channel)) { 111 return; 112 } 113 } 114 streamer.stopStream(); 115 TunerHal hal = streamer.getTunerHal(); 116 hal.setHasPendingTune(false); 117 mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); 118 } 119 setHasPendingTune(int sessionId)120 void setHasPendingTune(int sessionId) { 121 synchronized (mCancelLock) { 122 if (mCreators.containsKey(sessionId)) { 123 mCreators.get(sessionId).cancelLocked(); 124 } 125 } 126 } 127 release(int sessionId)128 synchronized void release(int sessionId) { 129 mTunerHalManager.releaseCachedHal(sessionId); 130 } 131 132 private class StreamerFinder { 133 private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>(); 134 private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>(); 135 136 // @GuardedBy("mCancelLock") putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer)137 private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) { 138 Set<Integer> sessions = new HashSet<>(); 139 sessions.add(sessionId); 140 mSessions.put(channel, sessions); 141 mStreamers.put(channel, streamer); 142 } 143 144 // @GuardedBy("mCancelLock") appendSessionLocked(TunerChannel channel, int sessionId)145 private void appendSessionLocked(TunerChannel channel, int sessionId) { 146 if (mSessions.containsKey(channel)) { 147 mSessions.get(channel).add(sessionId); 148 } 149 } 150 151 // @GuardedBy("mCancelLock") removeSessionLocked(TunerChannel channel, int sessionId)152 private void removeSessionLocked(TunerChannel channel, int sessionId) { 153 Set<Integer> sessions = mSessions.get(channel); 154 sessions.remove(sessionId); 155 if (sessions.size() == 0) { 156 mSessions.remove(channel); 157 mStreamers.remove(channel); 158 } 159 } 160 161 // @GuardedBy("mCancelLock") containsLocked(TunerChannel channel)162 private boolean containsLocked(TunerChannel channel) { 163 return mSessions.containsKey(channel); 164 } 165 166 // @GuardedBy("mCancelLock") getStreamerLocked(TunerChannel channel)167 private TunerTsStreamer getStreamerLocked(TunerChannel channel) { 168 return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null; 169 } 170 } 171 172 /** 173 * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same 174 * session. The class supports the cancellation in creating new {@link TunerTsStreamer}. 175 */ 176 private class TsStreamerCreator { 177 private final Context mContext; 178 private final TunerChannel mChannel; 179 private final EventDetector.EventListener mEventListener; 180 // mCancelled will be {@code true} if a new tune request for the same session 181 // cancels create(). 182 private boolean mCancelled; 183 private TunerHal mTunerHal; 184 TsStreamerCreator(Context context, TunerChannel channel, EventDetector.EventListener listener)185 private TsStreamerCreator(Context context, TunerChannel channel, 186 EventDetector.EventListener listener) { 187 mContext = context; 188 mChannel = channel; 189 mEventListener = listener; 190 } 191 create(int sessionId, boolean reuse)192 private TunerTsStreamer create(int sessionId, boolean reuse) { 193 TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId); 194 if (hal == null) { 195 return null; 196 } 197 boolean canceled = false; 198 synchronized (mCancelLock) { 199 if (!mCancelled) { 200 mTunerHal = hal; 201 } else { 202 canceled = true; 203 } 204 } 205 if (!canceled) { 206 TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext); 207 if (tsStreamer.startStream(mChannel)) { 208 return tsStreamer; 209 } 210 synchronized (mCancelLock) { 211 mTunerHal = null; 212 } 213 } 214 hal.setHasPendingTune(false); 215 // Since TunerTsStreamer is not properly created, closes TunerHal. 216 // And do not re-use TunerHal when it is not cancelled. 217 mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse); 218 return null; 219 } 220 221 // @GuardedBy("mCancelLock") cancelLocked()222 private void cancelLocked() { 223 if (mCancelled) { 224 return; 225 } 226 mCancelled = true; 227 if (mTunerHal != null) { 228 mTunerHal.setHasPendingTune(true); 229 } 230 } 231 232 // @GuardedBy("mCancelLock") isCancelledLocked()233 private boolean isCancelledLocked() { 234 return mCancelled; 235 } 236 } 237 238 /** 239 * Supports sharing {@link TunerHal} among multiple sessions. 240 * The class also supports session affinity for {@link TunerHal} allocation. 241 */ 242 private class TunerHalManager { 243 private final Map<Integer, TunerHal> mTunerHals = new HashMap<>(); 244 getOrCreateTunerHal(Context context, int sessionId)245 private TunerHal getOrCreateTunerHal(Context context, int sessionId) { 246 // Handles session affinity. 247 TunerHal hal = mTunerHals.get(sessionId); 248 if (hal != null) { 249 mTunerHals.remove(sessionId); 250 return hal; 251 } 252 // Finds a TunerHal which is cached for other sessions. 253 Iterator it = mTunerHals.keySet().iterator(); 254 if (it.hasNext()) { 255 Integer key = (Integer) it.next(); 256 hal = mTunerHals.get(key); 257 mTunerHals.remove(key); 258 return hal; 259 } 260 return TunerHal.createInstance(context); 261 } 262 releaseTunerHal(TunerHal hal, int sessionId, boolean reuse)263 private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) { 264 if (!reuse) { 265 AutoCloseableUtils.closeQuietly(hal); 266 return; 267 } 268 TunerHal cachedHal = mTunerHals.get(sessionId); 269 if (cachedHal != hal) { 270 mTunerHals.put(sessionId, hal); 271 } 272 if (cachedHal != null && cachedHal != hal) { 273 AutoCloseableUtils.closeQuietly(cachedHal); 274 } 275 } 276 releaseCachedHal(int sessionId)277 private void releaseCachedHal(int sessionId) { 278 TunerHal hal = mTunerHals.get(sessionId); 279 if (hal != null) { 280 mTunerHals.remove(sessionId); 281 } 282 if (hal != null) { 283 AutoCloseableUtils.closeQuietly(hal); 284 } 285 } 286 } 287 }