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.filter.Filter; 26 import android.os.ParcelFileDescriptor; 27 import android.os.Process; 28 import android.util.Log; 29 30 import com.android.internal.util.FrameworkStatsLog; 31 32 import java.util.concurrent.Executor; 33 34 35 /** 36 * Digital Video Record (DVR) recorder class which provides record control on Demux's output buffer. 37 * 38 * @hide 39 */ 40 @SystemApi 41 public class DvrRecorder implements AutoCloseable { 42 private static final String TAG = "TvTunerRecord"; 43 private long mNativeContext; 44 private OnRecordStatusChangedListener mListener; 45 private Executor mExecutor; 46 private int mUserId; 47 private static int sInstantId = 0; 48 private int mSegmentId = 0; 49 private int mOverflow; 50 private Boolean mIsStopped = true; 51 private final Object mListenerLock = new Object(); 52 nativeAttachFilter(Filter filter)53 private native int nativeAttachFilter(Filter filter); nativeDetachFilter(Filter filter)54 private native int nativeDetachFilter(Filter filter); nativeConfigureDvr(DvrSettings settings)55 private native int nativeConfigureDvr(DvrSettings settings); nativeStartDvr()56 private native int nativeStartDvr(); nativeStopDvr()57 private native int nativeStopDvr(); nativeFlushDvr()58 private native int nativeFlushDvr(); nativeClose()59 private native int nativeClose(); nativeSetFileDescriptor(int fd)60 private native void nativeSetFileDescriptor(int fd); nativeWrite(long size)61 private native long nativeWrite(long size); nativeWrite(byte[] bytes, long offset, long size)62 private native long nativeWrite(byte[] bytes, long offset, long size); 63 DvrRecorder()64 private DvrRecorder() { 65 mUserId = Process.myUid(); 66 mSegmentId = (sInstantId & 0x0000ffff) << 16; 67 sInstantId++; 68 } 69 70 /** @hide */ setListener( @onNull Executor executor, @NonNull OnRecordStatusChangedListener listener)71 public void setListener( 72 @NonNull Executor executor, @NonNull OnRecordStatusChangedListener listener) { 73 synchronized (mListenerLock) { 74 mExecutor = executor; 75 mListener = listener; 76 } 77 } 78 onRecordStatusChanged(int status)79 private void onRecordStatusChanged(int status) { 80 if (status == Filter.STATUS_OVERFLOW) { 81 mOverflow++; 82 } 83 synchronized (mListenerLock) { 84 if (mExecutor != null && mListener != null) { 85 mExecutor.execute(() -> mListener.onRecordStatusChanged(status)); 86 } 87 } 88 } 89 90 91 /** 92 * Attaches a filter to DVR interface for recording. 93 * 94 * <p>There can be multiple filters attached. Attached filters are independent, so the order 95 * doesn't matter. 96 * 97 * @param filter the filter to be attached. 98 * @return result status of the operation. 99 */ 100 @Result attachFilter(@onNull Filter filter)101 public int attachFilter(@NonNull Filter filter) { 102 return nativeAttachFilter(filter); 103 } 104 105 /** 106 * Detaches a filter from DVR interface. 107 * 108 * @param filter the filter to be detached. 109 * @return result status of the operation. 110 */ 111 @Result detachFilter(@onNull Filter filter)112 public int detachFilter(@NonNull Filter filter) { 113 return nativeDetachFilter(filter); 114 } 115 116 /** 117 * Configures the DVR. 118 * 119 * @param settings the settings of the DVR interface. 120 * @return result status of the operation. 121 */ 122 @Result configure(@onNull DvrSettings settings)123 public int configure(@NonNull DvrSettings settings) { 124 return nativeConfigureDvr(settings); 125 } 126 127 /** 128 * Starts DVR. 129 * 130 * <p>Starts consuming playback data or producing data for recording. 131 * <p>Does nothing if the filter is stopped or not started.</p> 132 * 133 * @return result status of the operation. 134 */ 135 @Result start()136 public int start() { 137 mSegmentId = (mSegmentId & 0xffff0000) | (((mSegmentId & 0x0000ffff) + 1) & 0x0000ffff); 138 mOverflow = 0; 139 Log.d(TAG, "Write Stats Log for Record."); 140 FrameworkStatsLog 141 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, 142 FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, 143 FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STARTED, mSegmentId, 0); 144 synchronized (mIsStopped) { 145 int result = nativeStartDvr(); 146 if (result == Tuner.RESULT_SUCCESS) { 147 mIsStopped = false; 148 } 149 return result; 150 } 151 } 152 153 /** 154 * Stops DVR. 155 * 156 * <p>Stops consuming playback data or producing data for recording. 157 * 158 * @return result status of the operation. 159 */ 160 @Result stop()161 public int stop() { 162 Log.d(TAG, "Write Stats Log for Playback."); 163 FrameworkStatsLog 164 .write(FrameworkStatsLog.TV_TUNER_DVR_STATUS, mUserId, 165 FrameworkStatsLog.TV_TUNER_DVR_STATUS__TYPE__RECORD, 166 FrameworkStatsLog.TV_TUNER_DVR_STATUS__STATE__STOPPED, mSegmentId, mOverflow); 167 synchronized (mIsStopped) { 168 int result = nativeStopDvr(); 169 if (result == Tuner.RESULT_SUCCESS) { 170 mIsStopped = true; 171 } 172 return result; 173 } 174 } 175 176 /** 177 * Flushed DVR data. 178 * 179 * <p>The data in DVR buffer is cleared. 180 * 181 * @return result status of the operation. 182 */ 183 @Result flush()184 public int flush() { 185 synchronized (mIsStopped) { 186 if (mIsStopped) { 187 return nativeFlushDvr(); 188 } 189 Log.w(TAG, "Cannot flush non-stopped Record DVR."); 190 return Tuner.RESULT_INVALID_STATE; 191 } 192 } 193 194 /** 195 * Closes the DVR instance to release resources. 196 */ 197 @Override close()198 public void close() { 199 int res = nativeClose(); 200 if (res != Tuner.RESULT_SUCCESS) { 201 TunerUtils.throwExceptionForResult(res, "failed to close DVR recorder"); 202 } 203 } 204 205 /** 206 * Sets file descriptor to write data. 207 * 208 * <p>When a write operation of the filter object is happening, this method should not be 209 * called. 210 * 211 * @param fd the file descriptor to write data. 212 * @see #write(long) 213 * @see #write(byte[], long, long) 214 */ setFileDescriptor(@onNull ParcelFileDescriptor fd)215 public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) { 216 nativeSetFileDescriptor(fd.getFd()); 217 } 218 219 /** 220 * Writes recording data to file. 221 * 222 * @param size the maximum number of bytes to write. 223 * @return the number of bytes written. 224 */ 225 @BytesLong write(@ytesLong long size)226 public long write(@BytesLong long size) { 227 return nativeWrite(size); 228 } 229 230 /** 231 * Writes recording data to buffer. 232 * 233 * @param bytes the byte array stores the data to be written to DVR. 234 * @param offset the index of the first byte in {@code bytes} to be written to DVR. 235 * @param size the maximum number of bytes to write. 236 * @return the number of bytes written. 237 */ 238 @BytesLong write(@onNull byte[] bytes, @BytesLong long offset, @BytesLong long size)239 public long write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) { 240 if (size + offset > bytes.length) { 241 throw new ArrayIndexOutOfBoundsException( 242 "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); 243 } 244 return nativeWrite(bytes, offset, size); 245 } 246 } 247