| /* |
| * Copyright 2019 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 android.media.tv.tuner.filter; |
| |
| import android.annotation.BytesLong; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.hardware.tv.tuner.V1_0.Constants; |
| import android.media.tv.tuner.Tuner; |
| import android.media.tv.tuner.Tuner.Result; |
| import android.media.tv.tuner.TunerUtils; |
| import android.media.tv.tuner.TunerVersionChecker; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * Tuner data filter. |
| * |
| * <p>This class is used to filter wanted data according to the filter's configuration. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public class Filter implements AutoCloseable { |
| /** @hide */ |
| @IntDef(prefix = "TYPE_", |
| value = {TYPE_TS, TYPE_MMTP, TYPE_IP, TYPE_TLV, TYPE_ALP}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Type {} |
| |
| /** |
| * Undefined filter type. |
| */ |
| public static final int TYPE_UNDEFINED = 0; |
| /** |
| * TS filter type. |
| */ |
| public static final int TYPE_TS = Constants.DemuxFilterMainType.TS; |
| /** |
| * MMTP filter type. |
| */ |
| public static final int TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; |
| /** |
| * IP filter type. |
| */ |
| public static final int TYPE_IP = Constants.DemuxFilterMainType.IP; |
| /** |
| * TLV filter type. |
| */ |
| public static final int TYPE_TLV = Constants.DemuxFilterMainType.TLV; |
| /** |
| * ALP filter type. |
| */ |
| public static final int TYPE_ALP = Constants.DemuxFilterMainType.ALP; |
| |
| /** @hide */ |
| @IntDef(prefix = "SUBTYPE_", |
| value = {SUBTYPE_UNDEFINED, SUBTYPE_SECTION, SUBTYPE_PES, SUBTYPE_AUDIO, SUBTYPE_VIDEO, |
| SUBTYPE_DOWNLOAD, SUBTYPE_RECORD, SUBTYPE_TS, SUBTYPE_PCR, SUBTYPE_TEMI, |
| SUBTYPE_MMTP, SUBTYPE_NTP, SUBTYPE_IP_PAYLOAD, SUBTYPE_IP, |
| SUBTYPE_PAYLOAD_THROUGH, SUBTYPE_TLV, SUBTYPE_PTP, }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Subtype {} |
| /** |
| * Filter subtype undefined. |
| */ |
| public static final int SUBTYPE_UNDEFINED = 0; |
| /** |
| * Section filter subtype. |
| */ |
| public static final int SUBTYPE_SECTION = 1; |
| /** |
| * PES filter subtype. |
| */ |
| public static final int SUBTYPE_PES = 2; |
| /** |
| * Audio filter subtype. |
| */ |
| public static final int SUBTYPE_AUDIO = 3; |
| /** |
| * Video filter subtype. |
| */ |
| public static final int SUBTYPE_VIDEO = 4; |
| /** |
| * Download filter subtype. |
| */ |
| public static final int SUBTYPE_DOWNLOAD = 5; |
| /** |
| * Record filter subtype. |
| */ |
| public static final int SUBTYPE_RECORD = 6; |
| /** |
| * TS filter subtype. |
| */ |
| public static final int SUBTYPE_TS = 7; |
| /** |
| * PCR filter subtype. |
| */ |
| public static final int SUBTYPE_PCR = 8; |
| /** |
| * TEMI filter subtype. |
| */ |
| public static final int SUBTYPE_TEMI = 9; |
| /** |
| * MMTP filter subtype. |
| */ |
| public static final int SUBTYPE_MMTP = 10; |
| /** |
| * NTP filter subtype. |
| */ |
| public static final int SUBTYPE_NTP = 11; |
| /** |
| * Payload filter subtype. |
| */ |
| public static final int SUBTYPE_IP_PAYLOAD = 12; |
| /** |
| * IP filter subtype. |
| */ |
| public static final int SUBTYPE_IP = 13; |
| /** |
| * Payload through filter subtype. |
| */ |
| public static final int SUBTYPE_PAYLOAD_THROUGH = 14; |
| /** |
| * TLV filter subtype. |
| */ |
| public static final int SUBTYPE_TLV = 15; |
| /** |
| * PTP filter subtype. |
| */ |
| public static final int SUBTYPE_PTP = 16; |
| |
| |
| /** @hide */ |
| @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER, |
| STATUS_HIGH_WATER, STATUS_OVERFLOW}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface Status {} |
| |
| /** |
| * The status of a filter that the data in the filter buffer is ready to be read. |
| */ |
| public static final int STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY; |
| /** |
| * The status of a filter that the amount of available data in the filter buffer is at low |
| * level. |
| * |
| * The value is set to 25 percent of the buffer size by default. It can be changed when |
| * configuring the filter. |
| */ |
| public static final int STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER; |
| /** |
| * The status of a filter that the amount of available data in the filter buffer is at high |
| * level. |
| * The value is set to 75 percent of the buffer size by default. It can be changed when |
| * configuring the filter. |
| */ |
| public static final int STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER; |
| /** |
| * The status of a filter that the filter buffer is full and newly filtered data is being |
| * discarded. |
| */ |
| public static final int STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW; |
| |
| /** @hide */ |
| @IntDef(flag = true, |
| prefix = "SCRAMBLING_STATUS_", |
| value = {SCRAMBLING_STATUS_UNKNOWN, SCRAMBLING_STATUS_NOT_SCRAMBLED, |
| SCRAMBLING_STATUS_SCRAMBLED}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ScramblingStatus {} |
| |
| /** |
| * Content’s scrambling status is unknown |
| */ |
| public static final int SCRAMBLING_STATUS_UNKNOWN = |
| android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.UNKNOWN; |
| /** |
| * Content is not scrambled. |
| */ |
| public static final int SCRAMBLING_STATUS_NOT_SCRAMBLED = |
| android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.NOT_SCRAMBLED; |
| /** |
| * Content is scrambled. |
| */ |
| public static final int SCRAMBLING_STATUS_SCRAMBLED = |
| android.hardware.tv.tuner.V1_1.Constants.ScramblingStatus.SCRAMBLED; |
| |
| /** @hide */ |
| @IntDef(flag = true, |
| prefix = "MONITOR_EVENT_", |
| value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface MonitorEventMask {} |
| |
| /** |
| * Monitor scrambling status change. |
| */ |
| public static final int MONITOR_EVENT_SCRAMBLING_STATUS = |
| android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.SCRAMBLING_STATUS; |
| /** |
| * Monitor ip cid change. |
| */ |
| public static final int MONITOR_EVENT_IP_CID_CHANGE = |
| android.hardware.tv.tuner.V1_1.Constants.DemuxFilterMonitorEventType.IP_CID_CHANGE; |
| |
| private static final String TAG = "Filter"; |
| |
| private long mNativeContext; |
| private FilterCallback mCallback; |
| private Executor mExecutor; |
| private final Object mCallbackLock = new Object(); |
| private final long mId; |
| private int mMainType; |
| private int mSubtype; |
| private Filter mSource; |
| private boolean mStarted; |
| private boolean mIsClosed = false; |
| private final Object mLock = new Object(); |
| |
| private native int nativeConfigureFilter( |
| int type, int subType, FilterConfiguration settings); |
| private native int nativeGetId(); |
| private native long nativeGetId64Bit(); |
| private native int nativeConfigureMonitorEvent(int monitorEventMask); |
| private native int nativeSetDataSource(Filter source); |
| private native int nativeStartFilter(); |
| private native int nativeStopFilter(); |
| private native int nativeFlushFilter(); |
| private native int nativeRead(byte[] buffer, long offset, long size); |
| private native int nativeClose(); |
| |
| // Called by JNI |
| private Filter(long id) { |
| mId = id; |
| } |
| |
| private void onFilterStatus(int status) { |
| synchronized (mCallbackLock) { |
| if (mCallback != null && mExecutor != null) { |
| mExecutor.execute(() -> mCallback.onFilterStatusChanged(this, status)); |
| } |
| } |
| } |
| |
| private void onFilterEvent(FilterEvent[] events) { |
| synchronized (mCallbackLock) { |
| if (mCallback != null && mExecutor != null) { |
| mExecutor.execute(() -> mCallback.onFilterEvent(this, events)); |
| } |
| } |
| } |
| |
| /** @hide */ |
| public void setType(@Type int mainType, @Subtype int subtype) { |
| mMainType = mainType; |
| mSubtype = TunerUtils.getFilterSubtype(mainType, subtype); |
| } |
| |
| /** @hide */ |
| public void setCallback(FilterCallback cb, Executor executor) { |
| synchronized (mCallbackLock) { |
| mCallback = cb; |
| mExecutor = executor; |
| } |
| } |
| |
| /** @hide */ |
| public FilterCallback getCallback() { |
| synchronized (mCallbackLock) { |
| return mCallback; |
| } |
| } |
| |
| /** |
| * Configures the filter. |
| * |
| * <p>Recofiguring must happen after stopping the filter. |
| * |
| * <p>When stopping, reconfiguring and restarting the filter, the client should discard all |
| * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid |
| * using the events from the previous configuration. |
| * |
| * @param config the configuration of the filter. |
| * @return result status of the operation. |
| */ |
| @Result |
| public int configure(@NonNull FilterConfiguration config) { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| Settings s = config.getSettings(); |
| int subType = (s == null) ? mSubtype : s.getType(); |
| if (mMainType != config.getType() || mSubtype != subType) { |
| throw new IllegalArgumentException("Invalid filter config. filter main type=" |
| + mMainType + ", filter subtype=" + mSubtype + ". config main type=" |
| + config.getType() + ", config subtype=" + subType); |
| } |
| return nativeConfigureFilter(config.getType(), subType, config); |
| } |
| } |
| |
| /** |
| * Gets the filter Id in 32-bit. For any Tuner SoC that supports 64-bit filter architecture, |
| * use {@link #getIdLong()}. |
| */ |
| public int getId() { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| return nativeGetId(); |
| } |
| } |
| |
| /** |
| * Gets the 64-bit filter Id. For any Tuner SoC that supports 32-bit filter architecture, |
| * use {@link #getId()}. |
| */ |
| public long getIdLong() { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| return nativeGetId64Bit(); |
| } |
| } |
| |
| /** |
| * Configure the Filter to monitor scrambling status and ip cid change. Set corresponding bit |
| * to monitor the change. Reset to stop monitoring. |
| * |
| * <p>{@link ScramblingStatusEvent} should be sent at the following two scenarios: |
| * <ul> |
| * <li>When this method is called with {@link #MONITOR_EVENT_SCRAMBLING_STATUS}, the first |
| * detected scrambling status should be sent. |
| * <li>When the Scrambling status transits into different status, event should be sent. |
| * <ul/> |
| * |
| * <p>{@link IpCidChangeEvent} should be sent at the following two scenarios: |
| * <ul> |
| * <li>When this method is called with {@link #MONITOR_EVENT_IP_CID_CHANGE}, the first |
| * detected CID for the IP should be sent. |
| * <li>When the CID is changed to different value for the IP filter, event should be sent. |
| * <ul/> |
| * |
| * <p>This configuration is only supported in Tuner 1.1 or higher version. Unsupported version |
| * will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version |
| * information. |
| * |
| * @param monitorEventMask Types of event to be monitored. Set corresponding bit to |
| * monitor it. Reset to stop monitoring. |
| * @return result status of the operation. |
| */ |
| @Result |
| public int setMonitorEventMask(@MonitorEventMask int monitorEventMask) { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| if (!TunerVersionChecker.checkHigherOrEqualVersionTo( |
| TunerVersionChecker.TUNER_VERSION_1_1, "setMonitorEventMask")) { |
| return Tuner.RESULT_UNAVAILABLE; |
| } |
| return nativeConfigureMonitorEvent(monitorEventMask); |
| } |
| } |
| |
| /** |
| * Sets the filter's data source. |
| * |
| * A filter uses demux as data source by default. If the data was packetized |
| * by multiple protocols, multiple filters may need to work together to |
| * extract all protocols' header. Then a filter's data source can be output |
| * from another filter. |
| * |
| * @param source the filter instance which provides data input. Switch to |
| * use demux as data source if the filter instance is NULL. |
| * @return result status of the operation. |
| * @throws IllegalStateException if the data source has been set. |
| */ |
| @Result |
| public int setDataSource(@Nullable Filter source) { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| if (mSource != null) { |
| throw new IllegalStateException("Data source is existing"); |
| } |
| int res = nativeSetDataSource(source); |
| if (res == Tuner.RESULT_SUCCESS) { |
| mSource = source; |
| } |
| return res; |
| } |
| } |
| |
| /** |
| * Starts filtering data. |
| * |
| * <p>Does nothing if the filter is already started. |
| * |
| * <p>When stopping, reconfiguring and restarting the filter, the client should discard all |
| * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid |
| * using the events from the previous configuration. |
| * |
| * @return result status of the operation. |
| */ |
| @Result |
| public int start() { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| return nativeStartFilter(); |
| } |
| } |
| |
| |
| /** |
| * Stops filtering data. |
| * |
| * <p>Does nothing if the filter is stopped or not started. |
| * |
| * <p>Filter must be stopped to reconfigure. |
| * |
| * <p>When stopping, reconfiguring and restarting the filter, the client should discard all |
| * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid |
| * using the events from the previous configuration. |
| * |
| * @return result status of the operation. |
| */ |
| @Result |
| public int stop() { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| return nativeStopFilter(); |
| } |
| } |
| |
| /** |
| * Flushes the filter. |
| * |
| * <p>The data which is already produced by filter but not consumed yet will |
| * be cleared. |
| * |
| * @return result status of the operation. |
| */ |
| @Result |
| public int flush() { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| return nativeFlushFilter(); |
| } |
| } |
| |
| /** |
| * Copies filtered data from filter output to the given byte array. |
| * |
| * @param buffer the buffer to store the filtered data. |
| * @param offset the index of the first byte in {@code buffer} to write. |
| * @param size the maximum number of bytes to read. |
| * @return the number of bytes read. |
| */ |
| public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) { |
| synchronized (mLock) { |
| TunerUtils.checkResourceState(TAG, mIsClosed); |
| size = Math.min(size, buffer.length - offset); |
| return nativeRead(buffer, offset, size); |
| } |
| } |
| |
| /** |
| * Stops filtering data and releases the Filter instance. |
| */ |
| @Override |
| public void close() { |
| synchronized (mLock) { |
| if (mIsClosed) { |
| return; |
| } |
| int res = nativeClose(); |
| if (res != Tuner.RESULT_SUCCESS) { |
| TunerUtils.throwExceptionForResult(res, "Failed to close filter."); |
| } else { |
| mIsClosed = true; |
| } |
| } |
| } |
| } |