1053 lines
34 KiB
C
1053 lines
34 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.
|
|
*/
|
|
#define LOG_TAG "Userdata"
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <poll.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/utsname.h>
|
|
#include <unistd.h>
|
|
|
|
#include "CcSourceManager.h"
|
|
#include "MediaSyncInterface.h"
|
|
#include "StreamUtils.h"
|
|
#include "SubtitleLog.h"
|
|
#include "UserdataDriver.h"
|
|
|
|
#define MAX_CC_NUM 16
|
|
#define MAX_CC_DATA_LEN (1024 * 5 + 4)
|
|
#define MAX_DUMP_DATA_LENGTH 1024
|
|
|
|
#define CC_DEFAULT_PLAYER_ID 0
|
|
|
|
#define PTS_DIFF_DURATION_S (60 * 90000) // 60s
|
|
|
|
#define IS_H264(p) ((p[0] == 0xb5 && p[3] == 0x47 && p[4] == 0x41 && p[5] == 0x39 && p[6] == 0x34))
|
|
#define IS_H265(p) ((p[0] == 0xb5 && p[3] == 0x47 && p[4] == 0x41 && p[5] == 0x39 && p[6] == 0x34))
|
|
#define IS_DIRECTV(p) ((p[0] == 0xb5 && p[1] == 0x00 && p[2] == 0x2f))
|
|
#define IS_AVS(p) ((p[0] == 0x47) && (p[1] == 0x41) && (p[2] == 0x39) && (p[3] == 0x34))
|
|
#define IS_ATSC(p) ((p[0] == 0x47) && (p[1] == 0x41) && (p[2] == 0x39) && (p[3] == 0x34))
|
|
#define IS_SCTE(p) ((p[0] == 0x3) && ((p[1] & 0x7f) == 1))
|
|
|
|
#define IS_AFD(p) ((p[0] == 0x44) && (p[1] == 0x54) && (p[2] == 0x47) && (p[3] == 0x31))
|
|
#define IS_H264_AFD(p) \
|
|
((p[0] == 0xb5) && (p[3] == 0x44) && (p[4] == 0x54) && (p[5] == 0x47) && (p[6] == 0x31))
|
|
|
|
/****************************************************************************
|
|
* Type definitions
|
|
***************************************************************************/
|
|
|
|
typedef enum {
|
|
INVALID_TYPE = 0,
|
|
CC_TYPE_FOR_JUDGE = 10,
|
|
MPEG_CC_TYPE,
|
|
H264_CC_TYPE,
|
|
DIRECTV_CC_TYPE,
|
|
AVS_CC_TYPE,
|
|
SCTE_CC_TYPE,
|
|
AFD_TYPE_FOR_JUDGE = 100,
|
|
MPEG_AFD_TYPE,
|
|
H264_AFD_TYPE,
|
|
USERDATA_TYPE_MAX,
|
|
} userdata_type;
|
|
|
|
enum VideoDecoderType {
|
|
VIDEO_DECODER_TYPE_INVALID = -1,
|
|
VIDEO_DECODER_TYPE_AMSTREAM_USERDATA = 0,
|
|
VIDEO_DECODER_TYPE_V4L = 1,
|
|
};
|
|
|
|
typedef enum {
|
|
ORDER_TYPE_PTS = 0,
|
|
ORDER_TYPE_POC = 1,
|
|
} FrameOrderType;
|
|
|
|
#define IS_AFD_TYPE(p) (p > AFD_TYPE_FOR_JUDGE && p < USERDATA_TYPE_MAX)
|
|
#define IS_CC_TYPE(p) (p > CC_TYPE_FOR_JUDGE && p < AFD_TYPE_FOR_JUDGE)
|
|
|
|
#define GET_ATSC_FLAG(hdr_ptr, type) \
|
|
((type == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA) \
|
|
? ((aml_ud_header_t *)(hdr_ptr))->atsc_flag \
|
|
: ((aml_v4l_ud_header_t *)(hdr_ptr))->atsc_flag)
|
|
|
|
#define GET_CC_DATA_START(hdr_ptr, type) \
|
|
((type == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA) \
|
|
? ((aml_ud_header_t *)(hdr_ptr))->cc_data_start \
|
|
: ((aml_v4l_ud_header_t *)(hdr_ptr))->cc_data_start)
|
|
|
|
typedef enum {
|
|
/* 0 forbidden */
|
|
I_TYPE = 1,
|
|
P_TYPE = 2,
|
|
B_TYPE = 3,
|
|
D_TYPE = 4,
|
|
/* 5 ... 7 reserved */
|
|
} picture_coding_type;
|
|
|
|
typedef struct AM_CCData_s AM_CCData;
|
|
struct AM_CCData_s {
|
|
AM_CCData *next;
|
|
uint8_t *buf;
|
|
int64_t pts;
|
|
int64_t duration;
|
|
int pts_valid;
|
|
int size;
|
|
int cap;
|
|
int poc;
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t picture_structure : 16;
|
|
uint32_t temporal_reference : 10;
|
|
uint32_t picture_coding_type : 3;
|
|
uint32_t reserved : 3;
|
|
uint32_t index : 16;
|
|
uint32_t offset : 16;
|
|
uint8_t atsc_flag[4];
|
|
uint8_t cc_data_start[4];
|
|
} aml_ud_header_t;
|
|
|
|
typedef struct {
|
|
uint8_t atsc_flag[4];
|
|
uint8_t cc_data_start[4];
|
|
} aml_v4l_ud_header_t;
|
|
|
|
typedef struct {
|
|
pthread_t th;
|
|
int fd;
|
|
int vfmt;
|
|
int playerId;
|
|
int mediasync_id;
|
|
int videoDecoderType;
|
|
bool running;
|
|
AM_CCData *cc_list;
|
|
AM_CCData *free_list;
|
|
int cc_num;
|
|
userdata_type format;
|
|
int curr_poc;
|
|
int64_t curr_pts;
|
|
int64_t curr_duration;
|
|
int scte_enable;
|
|
int mode;
|
|
void *mediaSync;
|
|
} AM_UDDrvData;
|
|
|
|
#define MOD_ON(__mod, __mask) (__mod & __mask)
|
|
#define MOD_ON_CC(__mod) MOD_ON(__mod, USERDATA_MODE_CC)
|
|
#define MOD_ON_AFD(__mod) MOD_ON(__mod, USERDATA_MODE_AFD)
|
|
|
|
/****************************************************************************
|
|
* Static data definitions
|
|
***************************************************************************/
|
|
|
|
static int aml_open(UserdataDeviceType *dev, const UserdataOpenParaType *para);
|
|
static int aml_close(UserdataDeviceType *dev);
|
|
static int aml_set_mode(UserdataDeviceType *dev, int mode);
|
|
static int aml_get_mode(UserdataDeviceType *dev, int *mode);
|
|
static int aml_set_param(UserdataDeviceType *dev, int para);
|
|
|
|
const UserdataDriverType aml_ud_drv = {
|
|
.open = aml_open,
|
|
.close = aml_close,
|
|
.set_mode = aml_set_mode,
|
|
.get_mode = aml_get_mode,
|
|
.set_param = aml_set_param,
|
|
};
|
|
|
|
int64_t am_get_video_pts(void *media_sync) {
|
|
int64_t value = 0;
|
|
if (media_sync != NULL) {
|
|
MediaSync_getTrackMediaTime(media_sync, &value);
|
|
return us2Pts(value);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dump_cc_data(char *who, uint8_t *buff, int size) {
|
|
int i = 0;
|
|
char buf[4096] = {'\0'};
|
|
if (size > 1024) size = 1024;
|
|
for (i = 0; i < size; i++) {
|
|
sprintf(buf + i * 3, "%02x ", buff[i]);
|
|
}
|
|
SUBTITLE_LOGI("CC DUMP:who:%s :%s", who, buf);
|
|
}
|
|
|
|
static void aml_free_cc_data(AM_CCData *cc) {
|
|
if (cc->buf) free(cc->buf);
|
|
free(cc);
|
|
}
|
|
|
|
static uint8_t scte20_char_map[256];
|
|
static uint8_t scte20_get_char(uint8_t c) {
|
|
// Builds a lookup table (`scte20_char_map`)
|
|
// that maps each 8-bit value to its bit-reversed counterpart
|
|
if (scte20_char_map[1] == 0) {
|
|
int i;
|
|
for (i = 0; i < 256; i++) {
|
|
uint8_t v1, v2;
|
|
int b;
|
|
v1 = i;
|
|
v2 = 0;
|
|
for (b = 0; b < 8; b++) {
|
|
if (v1 & (1 << b)) v2 |= (1 << (7 - b));
|
|
}
|
|
scte20_char_map[i] = v2;
|
|
}
|
|
}
|
|
return scte20_char_map[c];
|
|
}
|
|
|
|
static userdata_type aml_check_userdata_format(uint8_t *buf, int vfmt, int len,
|
|
int videoDecoderType) {
|
|
if (len < 8) return INVALID_TYPE;
|
|
// vfmt:
|
|
// 0 for MPEG
|
|
// 2 for H264
|
|
// 7 for AVS
|
|
|
|
if (vfmt == 2) {
|
|
if (IS_H264(buf) || IS_H265(buf)) {
|
|
SUBTITLE_LOGI("CC format is h264_or_h265_cc_type");
|
|
return H264_CC_TYPE;
|
|
} else if (IS_DIRECTV(buf)) {
|
|
SUBTITLE_LOGI("CC format is directv_cc_type");
|
|
return DIRECTV_CC_TYPE;
|
|
} else if (IS_H264_AFD(buf)) {
|
|
return H264_AFD_TYPE;
|
|
}
|
|
} else if (vfmt == 7) {
|
|
if (IS_AVS(buf)) {
|
|
SUBTITLE_LOGI("CC format is avs_cc_type");
|
|
return AVS_CC_TYPE;
|
|
}
|
|
} else if (vfmt == 0) {
|
|
int size = videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA
|
|
? (int)sizeof(aml_ud_header_t)
|
|
: (int)sizeof(aml_v4l_ud_header_t);
|
|
if (len >= size) {
|
|
void *hdr = NULL;
|
|
if (videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA) {
|
|
hdr = (aml_ud_header_t *)buf;
|
|
} else {
|
|
hdr = (aml_v4l_ud_header_t *)buf;
|
|
}
|
|
|
|
if (IS_ATSC(GET_ATSC_FLAG(hdr, videoDecoderType))) {
|
|
SUBTITLE_LOGI("CC format is mpeg_cc_type");
|
|
return MPEG_CC_TYPE;
|
|
} else if (IS_SCTE(GET_ATSC_FLAG(hdr, videoDecoderType))) {
|
|
SUBTITLE_LOGI("CC format is scte_cc_type");
|
|
return SCTE_CC_TYPE;
|
|
} else if (IS_AFD(GET_ATSC_FLAG(hdr, videoDecoderType))) {
|
|
return MPEG_AFD_TYPE;
|
|
}
|
|
}
|
|
} // else {
|
|
// SUBTITLE_LOGE("vfmt not handled");
|
|
// }
|
|
return INVALID_TYPE;
|
|
}
|
|
|
|
static void aml_write_userdata(UserdataDeviceType *dev, uint8_t *buffer, int buffer_len,
|
|
int64_t pts, int pts_valid, int64_t duration, int poc) {
|
|
uint8_t userdata_with_pts[MAX_CC_DATA_LEN];
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
if (pts_valid == 0) {
|
|
pts = ud->curr_pts + ud->curr_duration;
|
|
}
|
|
|
|
memcpy(userdata_with_pts, &pts, sizeof(pts));
|
|
memcpy(userdata_with_pts + sizeof(pts), buffer, buffer_len);
|
|
if (isSubtitleDumpAvailable()) {
|
|
char display_buffer[MAX_DUMP_DATA_LENGTH * 4] = {0};
|
|
int size = buffer_len;
|
|
if (size > 1024) size = 1024;
|
|
for (int i = 0; i < size && i < MAX_DUMP_DATA_LENGTH; i++) {
|
|
sprintf(&display_buffer[i * 3], " %02x", buffer[i]);
|
|
}
|
|
SUBTITLE_LOGI("after reorder: poc:%d, pts:%" PRId64 ", dataSize:%d data:%s", poc, pts, size,
|
|
display_buffer);
|
|
}
|
|
dev->write_package(dev, userdata_with_pts, buffer_len + sizeof(pts));
|
|
|
|
ud->curr_pts = pts;
|
|
ud->curr_duration = duration;
|
|
}
|
|
|
|
static void aml_flush_cc_data(UserdataDeviceType *dev) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
AM_CCData *cc, *ncc;
|
|
char buf[256];
|
|
char *pp = buf;
|
|
int left = sizeof(buf), i, pr;
|
|
|
|
for (cc = ud->cc_list; cc; cc = ncc) {
|
|
ncc = cc->next;
|
|
|
|
for (i = 0; i < cc->size; i++) {
|
|
pr = snprintf(pp, left, "%02x ", cc->buf[i]);
|
|
if (pr < left) {
|
|
pp += pr;
|
|
left -= pr;
|
|
}
|
|
}
|
|
|
|
SUBTITLE_LOGD("cc_write_package decode:%s", buf);
|
|
|
|
aml_write_userdata(dev, cc->buf, cc->size, cc->pts, cc->pts_valid, cc->duration, cc->poc);
|
|
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
}
|
|
|
|
ud->cc_list = NULL;
|
|
ud->cc_num = 0;
|
|
ud->curr_poc = -1;
|
|
}
|
|
|
|
static void aml_add_cc_data(UserdataDeviceType *dev, int poc, int type, uint8_t *p, int len,
|
|
int64_t pts, int pts_valid, int64_t duration, FrameOrderType oderType) {
|
|
if (!dev) {
|
|
SUBTITLE_LOGE("dev is NULL!");
|
|
return;
|
|
}
|
|
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
if (!ud) {
|
|
SUBTITLE_LOGE("dev->drv_data is NULL!");
|
|
return;
|
|
}
|
|
|
|
if (!p) {
|
|
SUBTITLE_LOGE("Input data pointer p is NULL!");
|
|
return;
|
|
}
|
|
|
|
AM_CCData **pcc, *cc;
|
|
// When CC data count reaches maximum limit, remove the oldest entry
|
|
if (ud->cc_num >= MAX_CC_NUM) {
|
|
#if 0
|
|
aml_flush_cc_data(dev);
|
|
#else
|
|
cc = ud->cc_list;
|
|
if (cc) {
|
|
// Write user data and move node to free list
|
|
aml_write_userdata(dev, cc->buf, cc->size, cc->pts, cc->pts_valid, cc->duration,
|
|
cc->poc);
|
|
ud->cc_list = cc->next;
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
ud->cc_num--;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (isSubtitleDumpAvailable()) {
|
|
char buf[256] = {0};
|
|
char *pp = buf;
|
|
int left = sizeof(buf), i = 0, pr = 0;
|
|
for (i = 0; i < len; i++) {
|
|
pr = snprintf(pp, left, "%02x ", p[i]);
|
|
if (pr < left) {
|
|
pp += pr;
|
|
left -= pr;
|
|
}
|
|
}
|
|
|
|
SUBTITLE_LOGI("CC oder type:%d, poc:%d ptype:%d data:%s", oderType, poc, type, buf);
|
|
}
|
|
|
|
pcc = &ud->cc_list;
|
|
if (*pcc && poc < ((*pcc)->poc - 30)) {
|
|
aml_flush_cc_data(dev);
|
|
}
|
|
|
|
// Traverse list to find insertion position for current CC data (sorted by pts and poc)
|
|
while ((cc = *pcc)) {
|
|
if (oderType == ORDER_TYPE_POC) {
|
|
if (cc->poc > poc) break;
|
|
} else {
|
|
if (cc->pts == pts) {
|
|
if (cc->poc > poc) break;
|
|
} else if ((cc->pts > pts) || (cc->poc > poc)) {
|
|
if (cc->poc > poc) {
|
|
SUBTITLE_LOGI("cc->pts:%" PRId64 ", pts:%" PRId64 ", cc->poc:%d, poc:%d",
|
|
cc->pts, pts, cc->poc, poc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pcc = &cc->next;
|
|
}
|
|
|
|
// Get node from free list; allocate new node if no free nodes available
|
|
if (ud->free_list) {
|
|
cc = ud->free_list;
|
|
ud->free_list = cc->next;
|
|
} else {
|
|
cc = malloc(sizeof(AM_CCData));
|
|
if (!cc) {
|
|
SUBTITLE_LOGI("Error! CC data node memory allocation failed!");
|
|
return;
|
|
}
|
|
// Initialize new node members
|
|
cc->buf = NULL;
|
|
cc->size = 0;
|
|
cc->cap = 0;
|
|
cc->poc = 0;
|
|
cc->next = NULL;
|
|
}
|
|
|
|
// Reallocate memory if current node buffer capacity is insufficient
|
|
if (cc->cap < len) {
|
|
void *new_buf = realloc(cc->buf, len);
|
|
if (!new_buf) {
|
|
SUBTITLE_LOGE("Error! CC buffer reallocation failed!");
|
|
// Recycle node to free list (prevent memory leak)
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
return;
|
|
}
|
|
cc->buf = new_buf;
|
|
cc->cap = len;
|
|
}
|
|
|
|
if (!p) {
|
|
SUBTITLE_LOGE("Error! Input data pointer p is NULL!");
|
|
// Recycle node to free list
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
return;
|
|
}
|
|
|
|
// Copy data to node buffer
|
|
memcpy(cc->buf, p, len);
|
|
|
|
// Set node properties and insert into list
|
|
cc->size = len;
|
|
cc->poc = poc;
|
|
cc->pts = pts;
|
|
cc->pts_valid = pts_valid;
|
|
cc->duration = duration;
|
|
cc->next = *pcc;
|
|
*pcc = cc;
|
|
|
|
ud->cc_num++;
|
|
}
|
|
|
|
static void aml_mpeg_userdata_package(UserdataDeviceType *dev, int poc, int type, uint8_t *p,
|
|
int len, int64_t pts, int pts_valid, int64_t duration) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
|
|
if (isSubtitleDumpAvailable()) {
|
|
char display_buffer[MAX_DUMP_DATA_LENGTH * 4];
|
|
for (int i = 0; i < len && i < MAX_DUMP_DATA_LENGTH; i++) {
|
|
sprintf(&display_buffer[i * 3], " %02x", p[i]);
|
|
}
|
|
SUBTITLE_LOGI("mpeg_process type:%d, pts:%" PRId64 ", ud->curr_pts:%" PRId64
|
|
", pts_valid:%d,"
|
|
"len:%d, poc:%d, curr_poc:%d, data:%s",
|
|
type, pts, ud->curr_pts, pts_valid, len, poc, ud->curr_poc, display_buffer);
|
|
}
|
|
|
|
if (len < 5) return;
|
|
if (p[4] != 3) return;
|
|
|
|
if (type == I_TYPE) aml_flush_cc_data(dev);
|
|
|
|
// now we judge stream from header flag based on pts diff,
|
|
// then flush cc data.
|
|
if ((ud->curr_pts - pts) > PTS_DIFF_DURATION_S) {
|
|
SUBTITLE_LOGI("Pts diff(%" PRId64 ") is more than %d, currt_pts:%" PRId64 ", pts:%" PRId64
|
|
", flush cc data!",
|
|
ud->curr_pts - pts, PTS_DIFF_DURATION_S, ud->curr_pts, pts);
|
|
aml_flush_cc_data(dev);
|
|
}
|
|
|
|
if (poc == ud->curr_poc + 1 || poc == ud->curr_poc) {
|
|
AM_CCData *cc, **pcc;
|
|
aml_write_userdata(dev, p, len, pts, pts_valid, duration, poc);
|
|
ud->curr_poc = poc;
|
|
pcc = &ud->cc_list;
|
|
while ((cc = *pcc)) {
|
|
if (ud->curr_poc + 1 != cc->poc) break;
|
|
aml_write_userdata(dev, cc->buf, cc->size, cc->pts, cc->pts_valid, cc->duration,
|
|
cc->poc);
|
|
*pcc = cc->next;
|
|
ud->curr_poc++;
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
}
|
|
return;
|
|
}
|
|
aml_add_cc_data(dev, poc, type, p, len, pts, pts_valid, duration, ORDER_TYPE_PTS);
|
|
}
|
|
|
|
static int aml_process_scte_userdata(UserdataDeviceType *dev, uint8_t *data, int len,
|
|
uint32_t ptsVersion, struct userdata_meta_info_t *meta_info) {
|
|
int cc_count = 0, cnt;
|
|
uint8_t cc_data[64] = {0};
|
|
uint8_t *scte_head;
|
|
uint8_t *scte_head_search_position;
|
|
int head_posi = 0;
|
|
int prio, field, line, cc1, cc2, mark, size, ptype, ref;
|
|
int bits = 0, array_position = 0;
|
|
uint8_t *p = NULL;
|
|
int left_bits;
|
|
uint32_t v;
|
|
int flags;
|
|
int64_t pts;
|
|
int top_bit_value, top_bit_valid;
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
flags = meta_info->flags;
|
|
pts = 0; // meta_info->vpts;
|
|
|
|
if (ptsVersion == 2) {
|
|
pts = meta_info->vpts_64;
|
|
} else {
|
|
SUBTITLE_LOGE(
|
|
"[subtitle_unsupported_flag] doesn't support 32bit PTS any more,"
|
|
"please update the latest decoder driver, ptsVersion = %d",
|
|
ptsVersion);
|
|
return -1;
|
|
}
|
|
|
|
if (ud->scte_enable != 1) return len;
|
|
v = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
|
|
top_bit_valid = flags & (1 << 10);
|
|
top_bit_value = flags & (1 << 11);
|
|
if (top_bit_valid == 0) top_bit_value = 1;
|
|
ref = meta_info->poc_number;
|
|
ptype = (meta_info->flags >> 7) & 0x7;
|
|
|
|
if (isSubtitleDumpAvailable()) {
|
|
char display_buffer[MAX_DUMP_DATA_LENGTH] = {0};
|
|
int maxDumpBytes = (MAX_DUMP_DATA_LENGTH - 1) / 3;
|
|
for (int i = 0; i < len && i < maxDumpBytes; i++) {
|
|
snprintf(&display_buffer[i * 3], MAX_DUMP_DATA_LENGTH - i * 3, " %02x", data[i]);
|
|
}
|
|
SUBTITLE_LOGI("mpeg_process type:%d, pts:%" PRId64 ", ud->curr_pts:%" PRId64
|
|
", pts_valid:%d"
|
|
", top_bit_value:%d"
|
|
", len:%d,ref:%d poc:%d, curr_poc:%d, data:%s",
|
|
ptype, pts, ud->curr_pts, meta_info->vpts_valid, top_bit_value, len, ref,
|
|
meta_info->poc_number, ud->curr_poc, display_buffer);
|
|
}
|
|
|
|
if (ptype == I_TYPE) aml_flush_cc_data(dev);
|
|
|
|
scte_head = data;
|
|
while (head_posi < len) {
|
|
scte_head_search_position = &scte_head[head_posi];
|
|
if (IS_SCTE(scte_head_search_position)) break;
|
|
head_posi += 8;
|
|
}
|
|
|
|
if ((len - head_posi) < 8) return len;
|
|
|
|
p = &data[head_posi + 2];
|
|
cc_data[0] = 0x47;
|
|
cc_data[1] = 0x41;
|
|
cc_data[2] = 0x39;
|
|
cc_data[3] = 0x34;
|
|
cc_data[4] = 0x3;
|
|
|
|
left_bits = (len - head_posi) << 3;
|
|
|
|
#define NST_BITS(v, b, l) (((v) >> (b)) & (0xff >> (8 - (l))))
|
|
#define GET(n, b) \
|
|
do { \
|
|
int off, bs; \
|
|
if (bits + b > left_bits) goto error; \
|
|
off = bits >> 3; \
|
|
bs = bits & 7; \
|
|
if (8 - bs >= b) { \
|
|
n = NST_BITS(p[off], 8 - bs - b, b); \
|
|
} else { \
|
|
int n1, n2, b1 = 8 - bs, b2 = b - b1; \
|
|
n1 = NST_BITS(p[off], 0, b1); \
|
|
n2 = NST_BITS(p[off + 1], 8 - b + b1, b - b1); \
|
|
n = (n1 << b2) | n2; \
|
|
} \
|
|
bits += b; \
|
|
} while (0)
|
|
|
|
GET(cnt, 5);
|
|
array_position = 7;
|
|
for (int i = 0; i < cnt; i++) {
|
|
GET(prio, 2);
|
|
GET(field, 2);
|
|
GET(line, 5);
|
|
GET(cc1, 8);
|
|
GET(cc2, 8);
|
|
GET(mark, 1);
|
|
if (field == 3) field = 1;
|
|
SUBTITLE_LOGD("loop %d field %d line %d cc1 %x cc2 %x", i, field, line, cc1, cc2);
|
|
if (field == 1)
|
|
line = (top_bit_value) ? line + 10 : line + 273;
|
|
else if (field == 2)
|
|
line = (top_bit_value) ? line + 273 : line + 10;
|
|
else
|
|
continue;
|
|
|
|
uint8_t cc_data1 = 0;
|
|
uint8_t cc_data2 = 0;
|
|
if (line == 21) {
|
|
cc_data[array_position] = 4;
|
|
cc_data1 = scte20_get_char(cc1);
|
|
cc_data2 = scte20_get_char(cc2);
|
|
cc_data[array_position + 1] = cc_data1;
|
|
cc_data[array_position + 2] = cc_data2;
|
|
array_position += 3;
|
|
cc_count++;
|
|
} else if (line == 284) {
|
|
cc_data[array_position] = 4 | 1;
|
|
cc_data1 = scte20_get_char(cc1);
|
|
cc_data2 = scte20_get_char(cc2);
|
|
cc_data[array_position + 1] = cc_data1;
|
|
cc_data[array_position + 2] = cc_data2;
|
|
array_position += 3;
|
|
cc_count++;
|
|
}
|
|
|
|
SUBTITLE_LOGD("cc char info: field %d line %d cc_data1 %x cc_data2 %x, cc_count:%d", field,
|
|
line, cc_data1, cc_data2, cc_count);
|
|
}
|
|
cc_data[5] = 0x40 | cc_count;
|
|
size = 7 + cc_count * 3;
|
|
|
|
if (cc_count > 0)
|
|
aml_add_cc_data(dev, ref, ptype, cc_data, size, pts, meta_info->vpts_valid,
|
|
meta_info->duration, ORDER_TYPE_POC);
|
|
error:
|
|
return len;
|
|
}
|
|
|
|
static int aml_process_mpeg_userdata(UserdataDeviceType *dev, uint8_t *data, int len,
|
|
uint32_t ptsVersion, struct userdata_meta_info_t *meta_info) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
uint8_t *pd = data;
|
|
int left = len;
|
|
int r = 0;
|
|
int userdata_length = 0;
|
|
int flag = 0;
|
|
int poc = -1;
|
|
int ptype = -1;
|
|
int64_t pts = -1;
|
|
|
|
flag = meta_info->flags;
|
|
if (ptsVersion == 2) {
|
|
pts = meta_info->vpts_64;
|
|
} else {
|
|
SUBTITLE_LOGE(
|
|
"[subtitle_unsupported_flag] doesn't support 32bit PTS any more,"
|
|
"please update the latest decoder driver, ptsVersion = %d",
|
|
ptsVersion);
|
|
return -1;
|
|
}
|
|
|
|
int size = ud->videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA
|
|
? (int)sizeof(aml_ud_header_t)
|
|
: (int)sizeof(aml_v4l_ud_header_t);
|
|
|
|
while (left >= size) {
|
|
void *hdr = NULL;
|
|
if (ud->videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA) {
|
|
hdr = (aml_ud_header_t *)pd;
|
|
} else {
|
|
hdr = (aml_v4l_ud_header_t *)pd;
|
|
}
|
|
|
|
if (MOD_ON_CC(ud->mode) && IS_ATSC(GET_ATSC_FLAG(hdr, ud->videoDecoderType))) {
|
|
uint8_t *pp;
|
|
|
|
if (ud->videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA) {
|
|
pp = pd + 8; // For coverity, in case p[4] atsc flag overrun
|
|
userdata_length = len - r - 8;
|
|
} else if (ud->videoDecoderType == VIDEO_DECODER_TYPE_V4L) {
|
|
// New v4l2 architecture, no 8 bytes header
|
|
pp = pd;
|
|
userdata_length = len - r;
|
|
}
|
|
poc = meta_info->poc_number;
|
|
ptype = (meta_info->flags >> 7) & 0x7;
|
|
|
|
/* We read one packet in one time, so treat entire buffer */
|
|
if (!(flag & (1 << 2))) return len;
|
|
|
|
aml_mpeg_userdata_package(dev, poc, ptype, pp, userdata_length, pts,
|
|
meta_info->vpts_valid, meta_info->duration);
|
|
r = len;
|
|
|
|
break;
|
|
} else if (MOD_ON_AFD(ud->mode) && IS_AFD(GET_ATSC_FLAG(hdr, ud->videoDecoderType))) {
|
|
uint8_t *pafd_hdr = (uint8_t *)GET_CC_DATA_START(hdr, ud->videoDecoderType);
|
|
UserdataAFDType afd = *((UserdataAFDType *)(pafd_hdr));
|
|
// afd.reserved = afd.pts = 0;
|
|
AmlogicEventSignal(dev->dev_no, USERDATA_EVENT_AFD, (void *)&afd);
|
|
pd += 8;
|
|
left -= 8;
|
|
r += 8;
|
|
break;
|
|
} else {
|
|
pd += 8;
|
|
left -= 8;
|
|
r += 8;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void aml_h264_userdata_package(UserdataDeviceType *dev, int poc, int type, uint8_t *p,
|
|
int len, int64_t pts, int pts_valid, int64_t duration,
|
|
void *media_sync) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
AM_CCData *cc;
|
|
|
|
if (isSubtitleDumpAvailable()) {
|
|
char display_buffer[MAX_DUMP_DATA_LENGTH * 4] = {0};
|
|
for (int i = 0; i < len && i < MAX_DUMP_DATA_LENGTH; i++) {
|
|
sprintf(&display_buffer[i * 3], " %02x", p[i]);
|
|
}
|
|
SUBTITLE_LOGI("h264_process type:%d, pts:%" PRId64 ", ud->curr_pts:%" PRId64
|
|
","
|
|
"pts_valid:%d, len:%d, poc:%d, curr_poc:%d, data:%s",
|
|
type, pts, ud->curr_pts, pts_valid, len, poc, ud->curr_poc, display_buffer);
|
|
}
|
|
// if (poc == 0) aml_flush_cc_data(dev);
|
|
aml_add_cc_data(dev, poc, I_TYPE, p, len, pts, pts_valid, duration, ORDER_TYPE_PTS);
|
|
cc = ud->cc_list;
|
|
while (cc) {
|
|
if (am_get_video_pts(media_sync) > cc->pts) {
|
|
aml_write_userdata(dev, cc->buf, cc->size, cc->pts, cc->pts_valid, cc->duration,
|
|
cc->poc);
|
|
ud->curr_poc = cc->poc;
|
|
// If node is chain head, reset chain head
|
|
if (cc == ud->cc_list) {
|
|
ud->cc_list = cc->next;
|
|
} else {
|
|
// Delete node from chain
|
|
AM_CCData *cc_tmp = ud->cc_list;
|
|
while (cc_tmp) {
|
|
if (cc_tmp->next == cc) {
|
|
cc_tmp->next = cc->next;
|
|
break;
|
|
} else {
|
|
cc_tmp = cc_tmp->next;
|
|
}
|
|
}
|
|
}
|
|
cc->next = ud->free_list;
|
|
ud->free_list = cc;
|
|
ud->cc_num--;
|
|
cc = ud->cc_list;
|
|
} else {
|
|
cc = cc->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int aml_process_h264_userdata(UserdataDeviceType *dev, uint8_t *data, int len,
|
|
uint32_t ptsVersion, struct userdata_meta_info_t *meta_info,
|
|
void *media_sync) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
uint8_t *pd = data;
|
|
int left = len;
|
|
int r = 0;
|
|
int poc;
|
|
int64_t pts = 0;
|
|
|
|
poc = meta_info->poc_number;
|
|
if (ptsVersion == 2) {
|
|
pts = meta_info->vpts_64;
|
|
} else {
|
|
SUBTITLE_LOGE(
|
|
"[subtitle_unsupported_flag] doesn't support 32bit PTS any more,"
|
|
"please update the latest decoder driver, ptsVersion = %d",
|
|
ptsVersion);
|
|
return -1;
|
|
}
|
|
|
|
while (left >= 8) {
|
|
if (MOD_ON_CC(ud->mode) && ((IS_H264(pd) || IS_H265(pd) || IS_DIRECTV(pd) || IS_AVS(pd)))) {
|
|
int hdr = (ud->format == H264_CC_TYPE) ? 3 : 0;
|
|
int pl;
|
|
|
|
pd += hdr;
|
|
|
|
pl = 8 + (pd[5] & 0x1f) * 3;
|
|
|
|
if (pl + hdr > left) {
|
|
break;
|
|
}
|
|
|
|
// SUBTITLE_LOGI("CC poc_number:%x hdr:%d pl:%d", poc, hdr, pl);
|
|
if (poc == 0) {
|
|
aml_flush_cc_data(dev);
|
|
}
|
|
|
|
// aml_add_cc_data(dev, poc, I_TYPE, pd, pl, pts, meta_info->vpts_valid,
|
|
// meta_info->duration);
|
|
|
|
// aml_add_cc_data(dev, poc, I_TYPE, userdata_with_pts, pl+4);
|
|
aml_h264_userdata_package(dev, poc, I_TYPE, pd, pl, pts, meta_info->vpts_valid,
|
|
meta_info->duration, media_sync);
|
|
|
|
pd += pl;
|
|
left -= pl + hdr;
|
|
r += pl + hdr;
|
|
} else if (MOD_ON_AFD(ud->mode) && IS_H264_AFD(pd)) {
|
|
UserdataAFDType afd = *((UserdataAFDType *)(pd + 7));
|
|
// afd.reserved = afd.pts = 0;
|
|
AmlogicEventSignal(dev->dev_no, USERDATA_EVENT_AFD, (void *)&afd);
|
|
pd += 8;
|
|
left -= 8;
|
|
r += 8;
|
|
break;
|
|
} else {
|
|
pd += 8;
|
|
left -= 8;
|
|
r += 8;
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void userdata_callback(CcSourceMgrHnd handle, void *data) {
|
|
int left = 0;
|
|
uint8_t *pd;
|
|
CcSourceCtx *ctx;
|
|
AM_UDDrvData *drvData;
|
|
UserdataDeviceType *dev;
|
|
struct userdata_meta_info_t metaInfo;
|
|
|
|
ctx = (CcSourceCtx *)handle;
|
|
if (ctx == NULL) {
|
|
SUBTITLE_LOGE("ctx is null, return!");
|
|
return;
|
|
}
|
|
dev = (UserdataDeviceType *)ctx->privData;
|
|
if (dev == NULL) {
|
|
SUBTITLE_LOGE("dev is null, return!");
|
|
return;
|
|
}
|
|
|
|
if (ctx->param.srcType != CC_SOURCE_TYPE_AMSTREAM_DEVICE &&
|
|
ctx->param.srcType != CC_SOURCE_TYPE_MEDIA_PROXY &&
|
|
ctx->param.srcType != CC_SOURCE_TYPE_CCDATASERVER_AMSTREAM_DEVICE &&
|
|
ctx->param.srcType != CC_SOURCE_TYPE_CCDATASERVER_MEDIA_PROXY) {
|
|
SUBTITLE_LOGE("CC Source type(%d) invalid, don't process data!", ctx->param.srcType);
|
|
return;
|
|
}
|
|
drvData = (AM_UDDrvData *)dev->drv_data;
|
|
if (!drvData->running) return;
|
|
|
|
uint32_t ptsVersion = 0;
|
|
if (ctx->param.srcType == CC_SOURCE_TYPE_CCDATASERVER_AMSTREAM_DEVICE ||
|
|
ctx->param.srcType == CC_SOURCE_TYPE_CCDATASERVER_MEDIA_PROXY) {
|
|
ds_data_userdata_t *dsUd = (ds_data_userdata_t *)data;
|
|
pd = dsUd->data;
|
|
left = dsUd->data_size;
|
|
ptsVersion = dsUd->version;
|
|
metaInfo = dsUd->meta_info;
|
|
SUBTITLE_LOGI("seq ud[version=%d]: %zd+%d", dsUd->version, sizeof(ds_data_userdata_t),
|
|
dsUd->data_size);
|
|
} else {
|
|
struct CcSourceUserdataParam *devUd = (struct CcSourceUserdataParam *)data;
|
|
pd = (uint8_t *)devUd->pbuf_addr;
|
|
left = devUd->data_size;
|
|
ptsVersion = devUd->version;
|
|
metaInfo = devUd->meta_info;
|
|
}
|
|
|
|
if (isSubtitleDumpAvailable()) {
|
|
dump_cc_data("userdata_callback", pd, left);
|
|
}
|
|
|
|
int r = 0;
|
|
do {
|
|
if (!drvData->running) return;
|
|
|
|
drvData->format = INVALID_TYPE;
|
|
while (drvData->format == INVALID_TYPE || IS_AFD_TYPE(drvData->format)) {
|
|
if (left < 8) break;
|
|
|
|
drvData->format =
|
|
aml_check_userdata_format(pd, drvData->vfmt, left, drvData->videoDecoderType);
|
|
if (!IS_CC_TYPE(drvData->format) && !IS_AFD_TYPE(drvData->format)) {
|
|
pd += 8;
|
|
left -= 8;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (drvData->format == MPEG_CC_TYPE || drvData->format == MPEG_AFD_TYPE) {
|
|
r = aml_process_mpeg_userdata(dev, pd, left, ptsVersion, &metaInfo);
|
|
} else if (drvData->format == SCTE_CC_TYPE) {
|
|
r = aml_process_scte_userdata(dev, pd, left, ptsVersion, &metaInfo);
|
|
} else if (drvData->format != INVALID_TYPE) {
|
|
r = aml_process_h264_userdata(dev, pd, left, ptsVersion, &metaInfo, drvData->mediaSync);
|
|
}
|
|
|
|
pd += r;
|
|
left -= r;
|
|
} while (left >= 8);
|
|
}
|
|
|
|
static CcSourceCtx *ccSourceCtx = NULL;
|
|
static void start_cc_data_source(UserdataDeviceType *dev, int playerId, int videoDecoderType) {
|
|
SUBTITLE_LOGI("playerid:%d, videoDecoderType:%d", playerId, videoDecoderType);
|
|
int srcType = CC_SOURCE_TYPE_AMSTREAM_DEVICE; // Default CC_SOURCE_TYPE_AMSTREAM_DEVICE.
|
|
#ifdef NEED_CCDATA_FROM_CCDATABASE
|
|
srcType = videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA
|
|
? CC_SOURCE_TYPE_CCDATASERVER_AMSTREAM_DEVICE
|
|
: CC_SOURCE_TYPE_CCDATASERVER_MEDIA_PROXY;
|
|
#else
|
|
srcType = videoDecoderType == VIDEO_DECODER_TYPE_AMSTREAM_USERDATA
|
|
? CC_SOURCE_TYPE_AMSTREAM_DEVICE
|
|
: CC_SOURCE_TYPE_MEDIA_PROXY;
|
|
#endif
|
|
CcSourceStartParam param = {
|
|
.srcType = srcType,
|
|
.playerId = playerId,
|
|
.uDataCb = userdata_callback,
|
|
.owner = "subtitleservice",
|
|
};
|
|
|
|
ccSourceCtx = ccSourceManagerCreate();
|
|
if (ccSourceCtx != NULL) {
|
|
ccSourceCtx->privData = (void *)dev;
|
|
ccSourceManagerStart(ccSourceCtx, ¶m);
|
|
}
|
|
}
|
|
|
|
static int aml_open(UserdataDeviceType *dev, const UserdataOpenParaType *para) {
|
|
AM_UDDrvData *ud = malloc(sizeof(AM_UDDrvData));
|
|
if (!ud) {
|
|
SUBTITLE_LOGI("not enough memory");
|
|
return USERDATA_ERROR_NO_MEM;
|
|
}
|
|
ud->vfmt = para->vfmt;
|
|
ud->playerId = para->playerid >= 0 ? para->playerid : CC_DEFAULT_PLAYER_ID;
|
|
ud->mediasync_id = para->mediasyncid;
|
|
ud->videoDecoderType = para->videoDecoderType;
|
|
ud->format = INVALID_TYPE;
|
|
ud->cc_list = NULL;
|
|
ud->free_list = NULL;
|
|
ud->mediaSync = NULL;
|
|
ud->running = true;
|
|
ud->cc_num = 0;
|
|
ud->curr_poc = -1;
|
|
ud->scte_enable = 1;
|
|
SUBTITLE_LOGI("mediasync id:%d, para->videoDecoderType:%d, ud->playerId:%d", ud->mediasync_id,
|
|
para->videoDecoderType, ud->playerId);
|
|
if (!para->cc_default_stop) ud->mode = USERDATA_MODE_CC;
|
|
|
|
if (ud->mediasync_id >= 0) {
|
|
ud->mediaSync = MediaSync_create();
|
|
SUBTITLE_LOGI("create mediaSync:%p", ud->mediaSync);
|
|
if (NULL != ud->mediaSync) {
|
|
MediaSync_bindInstance(ud->mediaSync, ud->mediasync_id, MEDIA_SUBTITLE);
|
|
}
|
|
}
|
|
dev->drv_data = ud;
|
|
start_cc_data_source(dev, ud->playerId, ud->videoDecoderType);
|
|
AmlogicSigHandlerInit();
|
|
return AM_SUCCESS;
|
|
}
|
|
|
|
static int aml_close(UserdataDeviceType *dev) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
AM_CCData *cc, *cc_next;
|
|
ud->running = 0;
|
|
SUBTITLE_LOGI("aml_close mediaSync:%p, ud->mediasync_id:%d", ud->mediaSync, ud->mediasync_id);
|
|
if (ud->mediaSync != NULL) {
|
|
MediaSync_destroy(ud->mediaSync);
|
|
ud->mediaSync = NULL;
|
|
}
|
|
|
|
ccSourceManagerStop(ccSourceCtx);
|
|
ccSourceManagerDestroy(ccSourceCtx);
|
|
ccSourceCtx = NULL;
|
|
|
|
for (cc = ud->cc_list; cc; cc = cc_next) {
|
|
cc_next = cc->next;
|
|
aml_free_cc_data(cc);
|
|
}
|
|
for (cc = ud->free_list; cc; cc = cc_next) {
|
|
cc_next = cc->next;
|
|
aml_free_cc_data(cc);
|
|
}
|
|
free(ud);
|
|
return AM_SUCCESS;
|
|
}
|
|
|
|
static int aml_set_mode(UserdataDeviceType *dev, int mode) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
if (MOD_ON_CC(mode) != MOD_ON_CC(ud->mode)) {
|
|
if (MOD_ON_CC(mode)) {
|
|
ud->mode |= USERDATA_MODE_CC;
|
|
} else {
|
|
ud->mode &= ~USERDATA_MODE_CC;
|
|
}
|
|
}
|
|
if (MOD_ON_AFD(mode) != MOD_ON_AFD(ud->mode)) {
|
|
if (MOD_ON_AFD(mode)) {
|
|
ud->mode |= USERDATA_MODE_AFD;
|
|
} else {
|
|
ud->mode &= ~USERDATA_MODE_AFD;
|
|
}
|
|
}
|
|
return AM_SUCCESS;
|
|
}
|
|
|
|
static int aml_get_mode(UserdataDeviceType *dev, int *mode) {
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
*mode = ud->mode;
|
|
return AM_SUCCESS;
|
|
}
|
|
|
|
static int aml_set_param(UserdataDeviceType *dev, int para) {
|
|
if (dev == NULL) return USERDATA_ERROR_INVALID_ARG;
|
|
AM_UDDrvData *ud = dev->drv_data;
|
|
ud->mediasync_id = para;
|
|
return AM_SUCCESS;
|
|
}
|