blob: cc932da4a9f6a85e042112e8c8cf8dee32c2c6b4 [file] [log] [blame]
/*
* 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 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;
private static final String TAG = "Filter";
private long mNativeContext;
private FilterCallback mCallback;
private Executor mExecutor;
private final int 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 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(int id) {
mId = id;
}
private void onFilterStatus(int status) {
if (mCallback != null && mExecutor != null) {
mExecutor.execute(() -> mCallback.onFilterStatusChanged(this, status));
}
}
private void onFilterEvent(FilterEvent[] events) {
if (mCallback != null && mExecutor != null) {
mExecutor.execute(() -> mCallback.onFilterEvent(this, events));
}
}
/** @hide */
public void setMainType(@Type int mainType) {
mMainType = mainType;
}
/** @hide */
public void setSubtype(@Subtype int subtype) {
mSubtype = subtype;
}
/** @hide */
public void setCallback(FilterCallback cb, Executor executor) {
mCallback = cb;
mExecutor = executor;
}
/** @hide */
public FilterCallback getCallback() {
return mCallback;
}
/**
* Configures the filter.
*
* @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.
*/
public int getId() {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
return nativeGetId();
}
}
/**
* 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.
*
* @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.
*
* @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;
}
}
}
}