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