/*
* Copyright (C) 2015 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#define LOG_TAG "DvbManager"
#include "logging.h"
#include "DvbManager.h"
static double currentTimeMillis() {
struct timeval tv;
gettimeofday(&tv, (struct timezone *) NULL);
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
}
DvbManager::DvbManager(JNIEnv *env, jobject)
: mFeFd(-1),
mDemuxFd(-1),
mDvrFd(-1),
mPatFilterFd(-1),
mFeHasLock(false) {
jclass clazz = env->FindClass(
"com/android/usbtuner/TunerHal");
mOpenDvbFrontEndMethodID = env->GetMethodID(
clazz, "openDvbFrontEndFd", "()I");
mOpenDvbDemuxMethodID = env->GetMethodID(
clazz, "openDvbDemuxFd", "()I");
mOpenDvbDvrMethodID = env->GetMethodID(
clazz, "openDvbDvrFd", "()I");
}
DvbManager::~DvbManager() {
reset();
}
bool DvbManager::isFeLocked() {
struct dvb_frontend_event kevent;
memset(&kevent, 0, sizeof(kevent));
if (ioctl(mFeFd, FE_READ_STATUS, &kevent.status) == 0) {
return (kevent.status & FE_HAS_LOCK);
}
return false;
}
int DvbManager::tune(JNIEnv *env, jobject thiz,
const int frequency, const char *modulationStr, int timeout_ms) {
resetExceptFe();
struct dvb_frontend_parameters feParams;
memset(&feParams, 0, sizeof(struct dvb_frontend_parameters));
feParams.frequency = frequency;
if (strcmp(modulationStr, "8VSB") == 0) {
feParams.u.vsb.modulation = VSB_8;
} else if (strcmp(modulationStr, "QAM256") == 0) {
feParams.u.vsb.modulation = QAM_256;
} else {
ALOGE("Unrecognized modulation mode : %s", modulationStr);
return -1;
}
if (openDvbFe(env, thiz) != 0) {
return -1;
}
feParams.inversion = INVERSION_AUTO;
/* Check frontend capability */
struct dvb_frontend_info feInfo;
if (ioctl(mFeFd, FE_GET_INFO, &feInfo) != -1) {
if (!(feInfo.caps & FE_CAN_INVERSION_AUTO)) {
// FE can't do INVERSION_AUTO, trying INVERSION_OFF instead
feParams.inversion = INVERSION_OFF;
}
}
if (ioctl(mFeFd, FE_SET_FRONTEND, &feParams) != 0) {
ALOGD("Can't set Frontend : %s", strerror(errno));
return -1;
}
int lockSuccessCount = 0;
double tuneClock = currentTimeMillis();
while (currentTimeMillis() - tuneClock < timeout_ms) {
usleep(FE_LOCK_CHECK_INTERNAL_US);
bool lockStatus = isFeLocked();
if (lockStatus) {
lockSuccessCount++;
} else {
lockSuccessCount = 0;
}
ALOGI("Lock status : %s", lockStatus ? "true" : "false");
if (lockSuccessCount >= FE_CONSECUTIVE_LOCK_SUCCESS_COUNT) {
mFeHasLock = true;
openDvbDvr(env, thiz);
return 0;
}
}
return -1;
}
int DvbManager::stopTune() {
reset();
usleep(DVB_TUNE_STOP_DELAY_MS);
return 0;
}
int DvbManager::openDvbFeFromSystemApi(JNIEnv *env, jobject thiz) {
int fd = (int) env->CallIntMethod(thiz, mOpenDvbFrontEndMethodID);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
return fd;
}
int DvbManager::openDvbDemuxFromSystemApi(JNIEnv *env, jobject thiz) {
int fd = (int) env->CallIntMethod(thiz, mOpenDvbDemuxMethodID);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
return fd;
}
int DvbManager::openDvbDvrFromSystemApi(JNIEnv *env, jobject thiz) {
int fd = (int) env->CallIntMethod(thiz, mOpenDvbDvrMethodID);
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
return fd;
}
int DvbManager::openDvbFe(JNIEnv *env, jobject thiz) {
if (mFeFd == -1) {
if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) {
ALOGD("Can't open FE file : %s", strerror(errno));
return -1;
}
}
struct dvb_frontend_info info;
if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) {
const char *types;
switch (info.type) {
case FE_QPSK:
types = "DVB-S";
break;
case FE_QAM:
types = "DVB-C";
break;
case FE_OFDM:
types = "DVB-T";
break;
case FE_ATSC:
types = "ATSC";
break;
default:
types = "Unknown";
}
ALOGI("Using frontend \"%s\", type %s", info.name, types);
}
return 0;
}
int DvbManager::startTsPidFilter(JNIEnv *env, jobject thiz, int pid, int filterType) {
Mutex::Autolock autoLock(mFilterLock);
if (mPidFilters.find(pid) != mPidFilters.end() || (mPatFilterFd != -1 && pid == PAT_PID)) {
return 0;
}
int demuxFd;
if ((demuxFd = openDvbDemuxFromSystemApi(env, thiz)) < 0) {
ALOGD("Can't open DEMUX file : %s", strerror(errno));
return -1;
}
struct dmx_pes_filter_params filter;
memset(&filter, 0, sizeof(filter));
filter.pid = pid;
filter.input = DMX_IN_FRONTEND;
switch (filterType) {
case FILTER_TYPE_AUDIO:
filter.pes_type = DMX_PES_AUDIO;
break;
case FILTER_TYPE_VIDEO:
filter.pes_type = DMX_PES_VIDEO;
break;
case FILTER_TYPE_PCR:
filter.pes_type = DMX_PES_PCR;
break;
default:
filter.pes_type = DMX_PES_OTHER;
break;
}
filter.output = DMX_OUT_TS_TAP;
filter.flags |= (DMX_CHECK_CRC | DMX_IMMEDIATE_START);
// create a pes filter
if (ioctl(demuxFd, DMX_SET_PES_FILTER, &filter)) {
close(demuxFd);
return -1;
}
if (pid != PAT_PID) {
mPidFilters.insert(std::pair<int, int>(pid, demuxFd));
} else {
mPatFilterFd = demuxFd;
}
return 0;
}
void DvbManager::closeAllDvbPidFilter() {
// Close all dvb pid filters except PAT filter to maintain the opening status of the device.
Mutex::Autolock autoLock(mFilterLock);
for (std::map<int, int>::iterator it(mPidFilters.begin());
it != mPidFilters.end(); it++) {
close(it->second);
}
mPidFilters.clear();
}
void DvbManager::closePatFilter() {
Mutex::Autolock autoLock(mFilterLock);
if (mPatFilterFd != -1) {
close(mPatFilterFd);
mPatFilterFd = -1;
}
}
int DvbManager::openDvbDvr(JNIEnv *env, jobject thiz) {
if ((mDvrFd = openDvbDvrFromSystemApi(env, thiz)) < 0) {
ALOGD("Can't open DVR file : %s", strerror(errno));
return -1;
}
return 0;
}
void DvbManager::closeDvbFe() {
if (mFeFd != -1) {
close(mFeFd);
mFeFd = -1;
}
}
void DvbManager::closeDvbDvr() {
if (mDvrFd != -1) {
close(mDvrFd);
mDvrFd = -1;
}
}
void DvbManager::reset() {
mFeHasLock = false;
closeDvbDvr();
closeAllDvbPidFilter();
closePatFilter();
closeDvbFe();
}
void DvbManager::resetExceptFe() {
mFeHasLock = false;
closeDvbDvr();
closeAllDvbPidFilter();
closePatFilter();
}
int DvbManager::readTsStream(JNIEnv *env, jobject thiz,
uint8_t *tsBuffer, int tsBufferSize, int timeout_ms) {
if (!mFeHasLock) {
usleep(DVB_ERROR_RETRY_INTERVAL_MS);
return -1;
}
if (mDvrFd == -1) {
openDvbDvr(env, thiz);
}
struct pollfd pollFd;
pollFd.fd = mDvrFd;
pollFd.events = POLLIN|POLLPRI|POLLERR;
pollFd.revents = 0;
int poll_result = poll(&pollFd, NUM_POLLFDS, timeout_ms);
if (poll_result == 0) {
return 0;
} else if (poll_result == -1 || pollFd.revents & POLLERR) {
ALOGD("Can't read DVR : %s", strerror(errno));
// TODO: Find how to recover this situation correctly.
closeDvbDvr();
usleep(DVB_ERROR_RETRY_INTERVAL_MS);
return -1;
}
return read(mDvrFd, tsBuffer, tsBufferSize);
}