1 /* 2 * Copyright 2019 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 android.media.tv.tuner.dvr; 18 19 import android.annotation.BytesLong; 20 import android.annotation.NonNull; 21 import android.annotation.SystemApi; 22 import android.media.tv.tuner.Tuner; 23 import android.media.tv.tuner.Tuner.Result; 24 import android.media.tv.tuner.TunerUtils; 25 import android.media.tv.tuner.TunerVersionChecker; 26 import android.media.tv.tuner.filter.Filter; 27 import android.os.ParcelFileDescriptor; 28 import android.os.Process; 29 import android.util.Log; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.util.FrameworkStatsLog; 33 34 import java.util.concurrent.Executor; 35 36 37 /** 38 * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer. 39 * 40 * @hide 41 */ 42 @SystemApi 43 public class DvrRecorder implements AutoCloseable { 44 private static final String TAG = "TvTunerRecord"; 45 private long mNativeContext; 46 private OnRecordStatusChangedListener mListener; 47 private Executor mExecutor; 48 private int mUserId; 49 private static int sInstantId = 0; 50 private int mSegmentId = 0; 51 private int mOverflow; 52 private final Object mIsStoppedLock = new Object(); 53 @GuardedBy("mIsStoppedLock") 54 private boolean mIsStopped = true; 55 private final Object mListenerLock = new Object(); 56 nativeAttachFilter(Filter filter)57 private native int nativeAttachFilter(Filter filter); nativeDetachFilter(Filter filter)58 private native int nativeDetachFilter(Filter filter); nativeConfigureDvr(DvrSettings settings)59 private native int nativeConfigureDvr(DvrSettings settings); nativeSetStatusCheckIntervalHint(long durationInMs)60 private native int nativeSetStatusCheckIntervalHint(long durationInMs); nativeStartDvr()61 private native int nativeStartDvr(); nativeStopDvr()62 private native int nativeStopDvr(); nativeFlushDvr()63 private native int nativeFlushDvr(); nativeClose()64 private native int nativeClose(); nativeSetFileDescriptor(int fd)65 private native void nativeSetFileDescriptor(int fd); nativeWrite(long size)66 private native long nativeWrite(long size); nativeWrite(byte[] bytes, long offset, long size)67 private native long nativeWrite(byte[] bytes, long offset, long size); 68 DvrRecorder()69 private DvrRecorder() { 70 mUserId = Process.myUid(); 71 mSegmentId = (sInstantId & 0x0000ffff) << 16; 72 sInstantId++; 73 } 74 75 /** @hide */ setListener( @onNull Executor executor, @NonNull OnRecordStatusChangedListener listener)76 public void setListener( 77 @NonNull Executor executor, @NonNull OnRecordStatusChangedListener listener) { 78 synchronized (mListenerLock) { 79 mExecutor = executor; 80 mListener = listener; 81 } 82 } 83 onRecordStatusChanged(int status)84 private void onRecordStatusChanged(int status) { 85 if (status == Filter.STATUS_OVERFLOW) { 86 mOverflow++; 87 } 88 synchronized (mListenerLock) { 89 if (mExecutor != null && mListener != null) { 90 mExecutor.execute(() -> { 91 synchronized (mListenerLock) { 92 if (mListener != null) { 93 mListener.onRecordStatusChanged(status); 94 } 95 } 96 }); 97 } 98 } 99 } 100 101 102 /** 103 * Attaches a filter to DVR interface for recording. 104 * 105 * <p>There can be multiple filters attached. Attached filters are independent, so the order 106 * doesn't matter. 107 * 108 * @param filter the filter to be attached. 109 * @return result status of the operation. 110 */ 111 @Result attachFilter(@onNull Filter filter)112 public int attachFilter(@NonNull Filter filter) { 113 return nativeAttachFilter(filter); 114 } 115 116 /** 117 * Detaches a filter from DVR interface. 118 * 119 * @param filter the filter to be detached. 120 * @return result status of the operation. 121 */ 122 @Result detachFilter(@onNull Filter filter)123 public int detachFilter(@NonNull Filter filter) { 124 return nativeDetachFilter(filter); 125 } 126 127 /** 128 * Configures the DVR. 129 * 130 * @param settings the settings of the DVR interface. 131 * @return result status of the operation. 132 */ 133 @Result configure(@onNull DvrSettings settings)134 public int configure(@NonNull DvrSettings settings) { 135 return nativeConfigureDvr(settings); 136 } 137 138 /** 139 * Set record buffer status check time interval. 140 * 141 * This status check time interval will be used by the Dvr to decide how often to evaluate 142 * data. The default value will be decided by HAL if it’s not set. 143 * 144 * <p>This functionality is only available in Tuner version 3.0 and higher and will otherwise 145 * return a {@link Tuner#RESULT_UNAVAILABLE}. Use {@link TunerVersionChecker#getTunerVersion()} 146 * to get the version information. 147 * 148 * @param durationInMs specifies the duration of the delay in milliseconds. 149 * 150 * @return one of the following results: 151 * {@link Tuner#RESULT_SUCCESS} if succeed, 152 * {@link Tuner#RESULT_UNAVAILABLE} if Dvr is unavailable or unsupported HAL versions, 153 * {@link Tuner#RESULT_NOT_INITIALIZED} if Dvr is not initialized, 154 * {@link Tuner#RESULT_INVALID_STATE} if Dvr is in a wrong state, 155 * {@link Tuner#RESULT_INVALID_ARGUMENT} if the input parameter is invalid. 156 */ 157 @Result setRecordBufferStatusCheckIntervalHint(long durationInMs)158 public int setRecordBufferStatusCheckIntervalHint(long durationInMs) { 159 if (!TunerVersionChecker.checkHigherOrEqualVersionTo( 160 TunerVersionChecker.TUNER_VERSION_3_0, "Set status check interval hint")) { 161 // no-op 162 return Tuner.RESULT_UNAVAILABLE; 163 } 164 return nativeSetStatusCheckIntervalHint(durationInMs); 165 } 166 167 /** 168 * Starts DVR. 169 * 170 * <p>Starts consuming playback data or producing data for recording. 171 * <p>Does nothing if the filter is stopped or not started.</p> 172 * 173 * @return result status of the operation. 174 */ 175 @Result start()176 public int start() { 177 mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); 178 mOverflow = 0; 179 Log.d(TAG, "Write Stats Log for Record."); 180 FrameworkStatsLog 181 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, 182 FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, 183 FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); 184 synchronized (mIsStoppedLock) { 185 int result = nativeStartDvr(); 186 if (result == Tuner.RESULT_SUCCESS) { 187 mIsStopped = false; 188 } 189 return result; 190 } 191 } 192 193 /** 194 * Stops DVR. 195 * 196 * <p>Stops consuming playback data or producing data for recording. 197 * 198 * @return result status of the operation. 199 */ 200 @Result stop()201 public int stop() { 202 Log.d(TAG, "Write Stats Log for Playback."); 203 FrameworkStatsLog 204 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, 205 FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, 206 FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow); 207 synchronized (mIsStoppedLock) { 208 int result = nativeStopDvr(); 209 if (result == Tuner.RESULT_SUCCESS) { 210 mIsStopped = true; 211 } 212 return result; 213 } 214 } 215 216 /** 217 * Flushed DVR data. 218 * 219 * <p>The data in DVR buffer is cleared. 220 * 221 * @return result status of the operation. 222 */ 223 @Result flush()224 public int flush() { 225 synchronized (mIsStoppedLock) { 226 if (mIsStopped) { 227 return nativeFlushDvr(); 228 } 229 Log.w(TAG, "Cannot flush non-stopped Record DVR."); 230 return Tuner.RESULT_INVALID_STATE; 231 } 232 } 233 234 /** 235 * Closes the DVR instance to release resources. 236 */ 237 @Override close()238 public void close() { 239 int res = nativeClose(); 240 if (res != Tuner.RESULT_SUCCESS) { 241 TunerUtils.throwExceptionForResult(res, "failed to close DVR recorder"); 242 } 243 } 244 245 /** 246 * Sets file descriptor to write data. 247 * 248 * <p>When a write operation of the filter object is happening, this method should not be 249 * called. 250 * 251 * @param fd the file descriptor to write data. 252 * @see #write(long) 253 */ setFileDescriptor(@onNull ParcelFileDescriptor fd)254 public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) { 255 nativeSetFileDescriptor(fd.getFd()); 256 } 257 258 /** 259 * Writes recording data to file. 260 * 261 * @param size the maximum number of bytes to write. 262 * @return the number of bytes written. 263 */ 264 @BytesLong write(@ytesLong long size)265 public long write(@BytesLong long size) { 266 return nativeWrite(size); 267 } 268 269 /** 270 * Writes recording data to buffer. 271 * 272 * @param buffer the byte array stores the data from DVR. 273 * @param offset the index of the first byte in {@code buffer} to write the data from DVR. 274 * @param size the maximum number of bytes to write. 275 * @return the number of bytes written. 276 */ 277 @BytesLong write(@onNull byte[] buffer, @BytesLong long offset, @BytesLong long size)278 public long write(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) { 279 if (size + offset > buffer.length) { 280 throw new ArrayIndexOutOfBoundsException( 281 "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size); 282 } 283 return nativeWrite(buffer, offset, size); 284 } 285 } 286