| /* |
| * 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), |
| mDvrFd(-1), |
| mPatFilterFd(-1), |
| mDvbApiVersion(DVB_API_VERSION_UNDEFINED), |
| mDeliverySystemType(-1), |
| mFeHasLock(false), |
| mHasPendingTune(false) { |
| jclass clazz = env->FindClass("com/android/tv/tuner/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() { |
| if (mDvbApiVersion == DVB_API_VERSION5) { |
| fe_status_t status; |
| if (ioctl(mFeFd, FE_READ_STATUS, &status) < 0) { |
| return false; |
| } |
| if (status & FE_HAS_LOCK) { |
| return true; |
| } |
| } else { |
| struct pollfd pollFd; |
| pollFd.fd = mFeFd; |
| pollFd.events = POLLIN; |
| pollFd.revents = 0; |
| int poll_result = poll(&pollFd, NUM_POLLFDS, FE_POLL_TIMEOUT_MS); |
| if (poll_result > 0 && (pollFd.revents & POLLIN)) { |
| struct dvb_frontend_event kevent; |
| memset(&kevent, 0, sizeof(kevent)); |
| if (ioctl(mFeFd, FE_GET_EVENT, &kevent) == 0) { |
| return (kevent.status & FE_HAS_LOCK); |
| } |
| } |
| } |
| return false; |
| } |
| |
| // This function gets the signal strength from tuner. |
| // Output can be: |
| // -3 means File Descriptor invalid, |
| // or DVB version is not supported, |
| // or ERROR while communicate with hardware via ioctl. |
| // int signal returns the raw signal strength value. |
| int DvbManager::getSignalStrength() { |
| // TODO(b/74197177): add support for DVB V5. |
| if (mFeFd == -1 || mDvbApiVersion != DVB_API_VERSION3) { |
| return -3; |
| } |
| uint16_t strength = 0; |
| // ERROR code from ioctl can be: |
| // EBADF means fd is not a valid open file descriptor |
| // EFAULT means status points to invalid address |
| // ENOSIGNAL means there is no signal, thus no meaningful signal strength |
| // ENOSYS means function not available for this device |
| // |
| // The function used to communicate with tuner in DVB v3 is |
| // ioctl(fd, request, &strength) |
| // int fd is the File Descriptor, can't be -1 |
| // int request is the request type, |
| // FE_READ_SIGNAL_STRENGTH for getting signal strength |
| // uint16_t *strength stores the strength value returned from tuner |
| if (ioctl(mFeFd, FE_READ_SIGNAL_STRENGTH, &strength) == -1) { |
| ALOGD("FE_READ_SIGNAL_STRENGTH failed, %s", strerror(errno)); |
| return -3; |
| } |
| return strength; |
| } |
| |
| int DvbManager::tune(JNIEnv *env, jobject thiz, |
| const int frequency, const char *modulationStr, int timeout_ms) { |
| resetExceptFe(); |
| |
| if (openDvbFe(env, thiz) != 0) { |
| return -1; |
| } |
| |
| if (frequency < 0) { |
| return -1; |
| } |
| |
| if (mDvbApiVersion == DVB_API_VERSION_UNDEFINED) { |
| struct dtv_property testProps[1] = { |
| { .cmd = DTV_DELIVERY_SYSTEM } |
| }; |
| struct dtv_properties feProp = { |
| .num = 1, .props = testProps |
| }; |
| // On fugu, DVB_API_VERSION is 5 but it doesn't support FE_SET_PROPERTY. Checking the device |
| // support FE_GET_PROPERTY or not to determine the DVB API version is greater than 5 or not. |
| if (ioctl(mFeFd, FE_GET_PROPERTY, &feProp) == -1) { |
| ALOGD("FE_GET_PROPERTY failed, %s", strerror(errno)); |
| mDvbApiVersion = DVB_API_VERSION3; |
| } else { |
| mDvbApiVersion = DVB_API_VERSION5; |
| } |
| } |
| |
| if (mDvbApiVersion == DVB_API_VERSION5) { |
| struct dtv_property deliverySystemProperty = { |
| .cmd = DTV_DELIVERY_SYSTEM |
| }; |
| deliverySystemProperty.u.data = SYS_ATSC; |
| struct dtv_property frequencyProperty = { |
| .cmd = DTV_FREQUENCY |
| }; |
| frequencyProperty.u.data = static_cast<__u32>(frequency); |
| struct dtv_property modulationProperty = { .cmd = DTV_MODULATION }; |
| if (strncmp(modulationStr, "QAM", 3) == 0) { |
| modulationProperty.u.data = QAM_AUTO; |
| } else if (strcmp(modulationStr, "8VSB") == 0) { |
| modulationProperty.u.data = VSB_8; |
| } else { |
| ALOGE("Unrecognized modulation mode : %s", modulationStr); |
| return -1; |
| } |
| struct dtv_property tuneProperty = { .cmd = DTV_TUNE }; |
| |
| struct dtv_property props[] = { |
| deliverySystemProperty, frequencyProperty, modulationProperty, tuneProperty |
| }; |
| struct dtv_properties dtvProperty = { |
| .num = 4, .props = props |
| }; |
| |
| if (mHasPendingTune) { |
| return -1; |
| } |
| if (ioctl(mFeFd, FE_SET_PROPERTY, &dtvProperty) != 0) { |
| ALOGD("Can't set Frontend : %s", strerror(errno)); |
| return -1; |
| } |
| } else { |
| struct dvb_frontend_parameters feParams; |
| memset(&feParams, 0, sizeof(struct dvb_frontend_parameters)); |
| feParams.frequency = frequency; |
| 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; |
| } |
| } |
| switch (feInfo.type) { |
| case FE_ATSC: |
| if (strcmp(modulationStr, "8VSB") == 0) { |
| feParams.u.vsb.modulation = VSB_8; |
| } else if (strncmp(modulationStr, "QAM", 3) == 0) { |
| feParams.u.vsb.modulation = QAM_AUTO; |
| } else { |
| ALOGE("Unrecognized modulation mode : %s", modulationStr); |
| return -1; |
| } |
| break; |
| case FE_OFDM: |
| if (strcmp(modulationStr, "8VSB") == 0) { |
| feParams.u.ofdm.constellation = VSB_8; |
| } else if (strcmp(modulationStr, "QAM16") == 0) { |
| feParams.u.ofdm.constellation = QAM_16; |
| } else if (strcmp(modulationStr, "QAM64") == 0) { |
| feParams.u.ofdm.constellation = QAM_64; |
| } else if (strcmp(modulationStr, "QAM256") == 0) { |
| feParams.u.ofdm.constellation = QAM_256; |
| } else if (strcmp(modulationStr, "QPSK") == 0) { |
| feParams.u.ofdm.constellation = QPSK; |
| } else { |
| ALOGE("Unrecognized modulation mode : %s", modulationStr); |
| return -1; |
| } |
| break; |
| default: |
| ALOGE("Unsupported delivery system."); |
| return -1; |
| } |
| |
| if (mHasPendingTune) { |
| return -1; |
| } |
| |
| 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) { |
| if (mHasPendingTune) { |
| // Return 0 here since we already call FE_SET_FRONTEND, and return due to having pending |
| // tune request. And the frontend setting could be successful. |
| mFeHasLock = true; |
| return 0; |
| } |
| 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; |
| } |
| |
| if (mHasPendingTune) { |
| return -1; |
| } |
| |
| 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 (mDvbApiVersion == DVB_API_VERSION5) { |
| ioctl(demuxFd, DMX_START, 0); |
| } |
| |
| 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(); |
| // Close mDvrFd to make sure there is not buffer from previous channel left. |
| closeDvbDvr(); |
| } |
| |
| 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); |
| } |
| |
| void DvbManager::setHasPendingTune(bool hasPendingTune) { |
| mHasPendingTune = hasPendingTune; |
| } |
| |
| int DvbManager::getDeliverySystemType(JNIEnv *env, jobject thiz) { |
| if (mDeliverySystemType != -1) { |
| return mDeliverySystemType; |
| } |
| if (mFeFd == -1) { |
| if ((mFeFd = openDvbFeFromSystemApi(env, thiz)) < 0) { |
| ALOGD("Can't open FE file : %s", strerror(errno)); |
| return DELIVERY_SYSTEM_UNDEFINED; |
| } |
| } |
| struct dtv_property testProps[1] = { |
| { .cmd = DTV_DELIVERY_SYSTEM } |
| }; |
| struct dtv_properties feProp = { |
| .num = 1, .props = testProps |
| }; |
| mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; |
| if (ioctl(mFeFd, FE_GET_PROPERTY, &feProp) == -1) { |
| mDvbApiVersion = DVB_API_VERSION3; |
| if (openDvbFe(env, thiz) == 0) { |
| struct dvb_frontend_info info; |
| if (ioctl(mFeFd, FE_GET_INFO, &info) == 0) { |
| switch (info.type) { |
| case FE_QPSK: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBS; |
| break; |
| case FE_QAM: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBC; |
| break; |
| case FE_OFDM: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBT; |
| break; |
| case FE_ATSC: |
| mDeliverySystemType = DELIVERY_SYSTEM_ATSC; |
| break; |
| default: |
| mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; |
| break; |
| } |
| } |
| } |
| } else { |
| mDvbApiVersion = DVB_API_VERSION5; |
| switch (feProp.props[0].u.data) { |
| case SYS_DVBT: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBT; |
| break; |
| case SYS_DVBT2: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBT2; |
| break; |
| case SYS_DVBS: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBS; |
| break; |
| case SYS_DVBS2: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBS2; |
| break; |
| case SYS_DVBC_ANNEX_A: |
| case SYS_DVBC_ANNEX_B: |
| case SYS_DVBC_ANNEX_C: |
| mDeliverySystemType = DELIVERY_SYSTEM_DVBC; |
| break; |
| case SYS_ATSC: |
| mDeliverySystemType = DELIVERY_SYSTEM_ATSC; |
| break; |
| default: |
| mDeliverySystemType = DELIVERY_SYSTEM_UNDEFINED; |
| break; |
| } |
| } |
| return mDeliverySystemType; |
| } |