Files

414 lines
14 KiB
C

/***************************************************************************
* Copyright (C) 2014-2025 Amlogic, Inc. All rights reserved.
*
* All information contained herein is Amlogic confidential.
*
* This software is provided to you pursuant to Software License Agreement
* (SLA) with Amlogic Inc ("Amlogic"). This software may be used
* only in accordance with the terms of this agreement.
*
* Redistribution and use in source and binary forms, with or without
* modification is strictly prohibited without prior written permission from
* Amlogic.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Description:
* Linux DVB demux driver
***************************************************************************/
#define LOG_TAG "Demux"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/dvb/dmx.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "AmlogicUtil.h"
#include "DemuxDriver.h"
#include "SubtitleLog.h"
#define open(a...) \
({ \
int ret, times = 3; \
do { \
ret = open(a); \
if (ret == -1) { \
usleep(100 * 1000); \
} \
} while (ret == -1 && times--); \
ret; \
})
typedef struct {
char dev_name[32];
int fd[DEMUX_FILTER_COUNT];
int event_fd;
} DVBDmx_t;
static int dvb_open(DemuxDeviceType *dev, const AmlogicDemuxOpenParameterType *para);
static int dvb_close(DemuxDeviceType *dev);
static int dvb_wake(DemuxDeviceType *dev);
static int dvb_alloc_filter(DemuxDeviceType *dev, DemuxFilterType *filter);
static int dvb_free_filter(DemuxDeviceType *dev, DemuxFilterType *filter);
static int dvb_set_sec_filter(DemuxDeviceType *dev, DemuxFilterType *filter,
const struct dmx_sct_filter_params *params);
static int dvb_set_pes_filter(DemuxDeviceType *dev, DemuxFilterType *filter,
const struct dmx_pes_filter_params *params);
static int dvb_enable_filter(DemuxDeviceType *dev, DemuxFilterType *filter, bool enable);
static int dvb_set_buf_size(DemuxDeviceType *dev, DemuxFilterType *filter, int size);
static int dvb_poll(DemuxDeviceType *dev, Demux_FilterMaskType *mask, int timeout);
static int dvb_poll_exit(DemuxDeviceType *dev);
static int dvb_read(DemuxDeviceType *dev, DemuxFilterType *filter, uint8_t *buf, int *size);
static int dvb_set_source(DemuxDeviceType *dev, AmlogicDemuxSourceType src);
static int dvb_get_newest_pts(DemuxDeviceType *dev, amdmx_filter_mem_info *demuxFilterMemInfo);
const DemuxDriverType linux_dvb_dmx_drv = {.open = dvb_open,
.close = dvb_close,
.alloc_filter = dvb_alloc_filter,
.free_filter = dvb_free_filter,
.set_sec_filter = dvb_set_sec_filter,
.set_pes_filter = dvb_set_pes_filter,
.enable_filter = dvb_enable_filter,
.set_buf_size = dvb_set_buf_size,
.poll = dvb_poll,
.poll_exit = dvb_poll_exit,
.read = dvb_read,
.set_source = dvb_set_source,
.get_newest_pts = dvb_get_newest_pts};
/****************************************************************************
* Static functions
***************************************************************************/
static int dvb_open(DemuxDeviceType *dev, const AmlogicDemuxOpenParameterType *para) {
UNUSED(para);
DVBDmx_t *dmx;
dmx = (DVBDmx_t *)malloc(sizeof(DVBDmx_t));
if (!dmx) {
SUBTITLE_LOGE("malloc fail: %m");
return DEMUX_ERROR_NO_MEM;
}
snprintf(dmx->dev_name, sizeof(dmx->dev_name), "/dev/dvb0.demux%d", dev->dev_no);
for (int i = 0; i < DEMUX_FILTER_COUNT; ++i) {
dmx->fd[i] = -1;
}
dmx->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (dmx->event_fd == -1) {
SUBTITLE_LOGE("eventfd fail: %m");
}
SUBTITLE_LOGI("event_fd = %d", dmx->event_fd);
dev->drv_data = dmx;
return AM_SUCCESS;
}
static int dvb_close(DemuxDeviceType *dev) {
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
if (dmx->event_fd >= 0) {
SUBTITLE_LOGI("event_fd = %d", dmx->event_fd);
close(dmx->event_fd);
dmx->event_fd = -1;
}
free(dmx);
return AM_SUCCESS;
}
static int dvb_alloc_filter(DemuxDeviceType *dev, DemuxFilterType *filter) {
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
int fd = open(dmx->dev_name, O_RDWR);
if (fd == -1) {
SUBTITLE_LOGE("fail to open %s: %m", dmx->dev_name);
return DEMUX_ERROR_CANNOT_OPEN_DEV;
}
dmx->fd[filter->id] = fd;
filter->drv_data = (void *)(long)fd;
if (dmx->event_fd == -1) {
dmx->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (dmx->event_fd == -1) {
SUBTITLE_LOGE("eventfd fail: %m");
}
}
SUBTITLE_LOGI("fd = %d, event_fd = %d", fd, dmx->event_fd);
return AM_SUCCESS;
}
static int dvb_free_filter(DemuxDeviceType *dev, DemuxFilterType *filter) {
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
int fd = (long)filter->drv_data;
SUBTITLE_LOGI("fd = %d, event_fd = %d", fd, dmx->event_fd);
if (fd >= 0) {
close(fd);
dmx->fd[filter->id] = -1;
}
return AM_SUCCESS;
}
static int dvb_set_sec_filter(DemuxDeviceType *dev, DemuxFilterType *filter,
const struct dmx_sct_filter_params *params) {
struct dmx_sct_filter_params p;
int fd = (long)filter->drv_data;
int ret;
UNUSED(dev);
p = *params;
/*
if (p.filter.mask[0] == 0) {
p.filter.filter[0] = 0x00;
p.filter.mask[0] = 0x80;
}
*/
ret = ioctl(fd, DMX_SET_FILTER, &p);
if (ret == -1) {
SUBTITLE_LOGE("set section filter failed (%s)", strerror(errno));
return DEMUX_ERROR_SYS;
}
return AM_SUCCESS;
}
static int dvb_set_pes_filter(DemuxDeviceType *dev, DemuxFilterType *filter,
const struct dmx_pes_filter_params *params) {
int fd = (long)filter->drv_data;
int ret;
UNUSED(dev);
// TODO for coverity check_return
ret = fcntl(fd, F_SETFL, O_NONBLOCK);
if (ret == -1) {
SUBTITLE_LOGE("fcntl failed (%s)", strerror(errno));
return DEMUX_ERROR_SYS;
}
ret = ioctl(fd, DMX_SET_PES_FILTER, params);
if (ret == -1) {
SUBTITLE_LOGE("set section filter failed (%s)", strerror(errno));
return DEMUX_ERROR_SYS;
}
return AM_SUCCESS;
}
static int dvb_enable_filter(DemuxDeviceType *dev, DemuxFilterType *filter, bool enable) {
int fd = (long)filter->drv_data;
int ret;
UNUSED(dev);
if (enable) {
ret = ioctl(fd, DMX_START, 0);
} else {
ret = ioctl(fd, DMX_STOP, 0);
}
if (ret == -1) {
SUBTITLE_LOGE("start filter failed (%s)", strerror(errno));
return DEMUX_ERROR_SYS;
}
return AM_SUCCESS;
}
static int dvb_set_buf_size(DemuxDeviceType *dev, DemuxFilterType *filter, int size) {
int fd = (long)filter->drv_data;
int ret;
UNUSED(dev);
ret = ioctl(fd, DMX_SET_BUFFER_SIZE, size);
if (ret == -1) {
SUBTITLE_LOGE("set buffer size failed (%s)", strerror(errno));
return DEMUX_ERROR_SYS;
}
return AM_SUCCESS;
}
static int dvb_poll(DemuxDeviceType *dev, Demux_FilterMaskType *mask, int timeout) {
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
struct pollfd fds[DEMUX_FILTER_COUNT_EXT];
int fids[DEMUX_FILTER_COUNT_EXT] = {0};
int i, cnt = 0, ret;
for (i = 0; i < DEMUX_FILTER_COUNT; i++) {
if (dmx->fd[i] != -1) {
fds[cnt].events = POLLIN | POLLERR;
fds[cnt].fd = dmx->fd[i];
fids[cnt] = i;
cnt++;
}
}
// add notify event fd
if (dmx->event_fd != -1) {
fds[cnt].events = POLLIN | POLLERR;
fds[cnt].fd = dmx->event_fd;
cnt++;
}
if (!cnt) return DEMUX_ERROR_TIMEOUT;
ret = poll(fds, cnt, timeout);
if (ret <= 0) {
return DEMUX_ERROR_TIMEOUT;
}
for (i = 0; i < cnt; i++) {
if (fds[i].revents & (POLLIN | POLLERR)) {
DEMUX_FILTER_MASK_SET(mask, fids[i]);
if (fds[i].fd == dmx->event_fd) {
int64_t data = 0;
int event_fd = dmx->event_fd;
if (event_fd != -1) {
ret = read(event_fd, &data, sizeof(int64_t));
if (ret < 0 && errno == EBADF) { // Bad File Descriptor
SUBTITLE_LOGW("event_fd was closed, ignoring read error");
} else {
SUBTITLE_LOGI("dvb_poll_exit read:%d\n", ret);
}
}
}
}
}
return AM_SUCCESS;
}
static int dvb_poll_exit(DemuxDeviceType *dev) {
SUBTITLE_LOGI_FUNC_IN();
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
int64_t pad = 1;
ssize_t ret = write(dmx->event_fd, &pad, sizeof(int64_t));
if (ret != sizeof(int64_t)) {
SUBTITLE_LOGE("%m");
}
SUBTITLE_LOGI_FUNC_OUT();
return AM_SUCCESS;
}
static int dvb_read(DemuxDeviceType *dev, DemuxFilterType *filter, uint8_t *buf, int *size) {
int fd = (long)filter->drv_data;
int len = *size;
int ret;
struct pollfd pfd;
UNUSED(dev);
if (fd == -1) return DEMUX_ERROR_NOT_ALLOCATED;
pfd.events = POLLIN | POLLERR;
pfd.fd = fd;
ret = poll(&pfd, 1, 0);
if (ret <= 0) return DEMUX_ERROR_NO_DATA;
ret = read(fd, buf, len);
if (ret <= 0) {
if (errno == ETIMEDOUT) return DEMUX_ERROR_TIMEOUT;
SUBTITLE_LOGE("read demux failed (%s) %d", strerror(errno), errno);
return DEMUX_ERROR_SYS;
}
*size = ret;
return AM_SUCCESS;
}
static int dvb_set_source(DemuxDeviceType *dev, AmlogicDemuxSourceType src) {
char buf[32];
char *cmd;
snprintf(buf, sizeof(buf), "/sys/class/stb/demux%d_source", dev->dev_no);
switch (src) {
case DEMUX_SRC_TS0:
cmd = "ts0";
break;
case DEMUX_SRC_TS1:
cmd = "ts1";
break;
#if defined(CHIP_8226M) || defined(CHIP_8626X)
case DEMUX_SRC_TS2:
cmd = "ts2";
break;
#endif
case DEMUX_SRC_TS3:
cmd = "ts3";
break;
case DEMUX_SRC_HIU:
cmd = "hiu";
break;
case DEMUX_SRC_HIU1:
cmd = "hiu1";
break;
default:
SUBTITLE_LOGE("do not support demux source %d", src);
return DEMUX_ERROR_NOT_SUPPORTED;
}
return AM_SUCCESS;
}
static void dump_newest_pts(char *deviceName) {
amdmx_filter_mem_info memInfo = {0};
int fd = open(deviceName, O_RDWR);
if (fd == -1) {
SUBTITLE_LOGE("[newest_avpts_tag] fail to open device %s: %m", deviceName);
return;
}
SUBTITLE_LOGI("[newest_avpts_tag] device=%s, fd=%d", deviceName, fd);
int ret = ioctl(fd, AMDMX_GET_FILTER_MEM_INFO, &memInfo);
if (ret == -1) {
SUBTITLE_LOGE(
"[newest_avpts_tag] ioctl DMX_GET_FILTER_MEM_INFO failed,"
" fd=%d, error: %m",
fd);
}
SUBTITLE_LOGI("[newest_avpts_tag] filter_num = %d", memInfo.filter_num);
for (__u32 i = 0; i < memInfo.filter_num; ++i) {
amfilter_mem_info info = memInfo.info[i];
SUBTITLE_LOGI("[newest_avpts_tag] [%2d] type = %d, pts = %lld", i, info.type,
info.filter_info.newest_pts);
}
close(fd);
}
static int dvb_get_newest_pts(DemuxDeviceType *dev, amdmx_filter_mem_info *demuxFilterMemInfo) {
if (!dev || !demuxFilterMemInfo || !dev->drv_data) {
SUBTITLE_LOGE("[newest_avpts_tag] wrong input param");
return AM_FAILURE;
}
memset(demuxFilterMemInfo, 0, sizeof(*demuxFilterMemInfo));
DVBDmx_t *dmx = (DVBDmx_t *)dev->drv_data;
if (!dmx) {
SUBTITLE_LOGE("[newest_avpts_tag] no demux device");
return AM_FAILURE;
}
int fd = open(dmx->dev_name, O_RDWR);
if (fd == -1) {
SUBTITLE_LOGE("[newest_avpts_tag] fail to open device %s: %m", dmx->dev_name);
return AM_FAILURE;
}
int ret = ioctl(fd, AMDMX_GET_FILTER_MEM_INFO, demuxFilterMemInfo);
if (ret == -1) {
SUBTITLE_LOGE(
"[newest_avpts_tag] ioctl DMX_GET_FILTER_MEM_INFO failed,"
" device=%s fd=%d, error: %m",
dmx->dev_name, fd);
close(fd);
return AM_FAILURE;
}
close(fd);
return AM_SUCCESS;
}