/* * Copyright (C) 2014-2026 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 "DemuxSource" #include "DemuxSource.h" #include #include #include #include #include #include #include #include #include #include #include "ParserFactory.h" #include "SubtitleLog.h" extern "C" { #include "MediaSyncInterface.h" mediasync_result MediaSync_bindInstance(void *handle, uint32_t SyncInsId, sync_stream_type streamtype); mediasync_result MediaSync_getTrackMediaTime(void *handle, int64_t *outMediaUs); mediasync_result MediaSync_getFirstVideoFrameInfo(void *handle, mediasync_frameinfo *arg); } // Lagecy tsync is only used in: // - Old MM demux architecture before Android T // - New MM demux architecture before Android R // tsync/pts_video returns 32bit 90kHz Pts. // tsync/pts_video_u64 returns 33bit 90kHz Pts. static const std::string SYSFS_VIDEO_PTS = "/sys/class/tsync/pts_video_u64"; static const std::string SYSFS_VIDEO_FIRSTRRAME = "/sys/module/amvideo/parameters/first_frame_toggled"; static const std::string SYSFS_SUBTITLE_TYPE = "/sys/class/subtitle/subtype"; static const std::string SYSFS_SUBTITLE_SIZE = "/sys/class/subtitle/size"; static const std::string SYSFS_SUBTITLE_TOTAL = "/sys/class/subtitle/total"; static const std::string SUBTITLE_READ_DEVICE = "/dev/amstream_sub_read"; #define AMSTREAM_IOC_MAGIC 'S' #define AMSTREAM_IOC_SUB_LENGTH _IOR(AMSTREAM_IOC_MAGIC, 0x1b, int) #define AMSTREAM_IOC_SUB_RESET _IOW(AMSTREAM_IOC_MAGIC, 0x1a, int) #define SECTION_DEMUX_INDEX 2 #define DMX_SUBTITLE_NO_SEC 0 #define DMX_SUBTITLE_SEC (2 << 10) #ifdef USE_FB #define DMX_SUBTITLE_BUFFER_SIZE 0x20000 #else #define DMX_SUBTITLE_BUFFER_SIZE 0x80000 #endif DemuxSource *DemuxSource::sInstance = nullptr; DemuxSource *DemuxSource::getCurrentInstance() { return DemuxSource::sInstance; } namespace { static inline int64_t sysfsReadInt(const char *path, int base) { int fd; int64_t val = 0; char bcmd[64] = {0}; fd = ::open(path, O_RDONLY); if (fd >= 0) { int c = read(fd, bcmd, sizeof(bcmd)); if (c > 0) { val = strtoll(bcmd, NULL, base); } ::close(fd); } else { SUBTITLE_LOGE("unable to open file %s,err: %s", path, strerror(errno)); } return val; } static void pes_data_cb(int dev_no, int fhandle, const uint8_t *data, int len, void *user_data) { (void)dev_no; (void)fhandle; (void)user_data; std::shared_ptr spBuf(new char[len], [](char *buf) { delete[] buf; }); memcpy(spBuf.get(), data, len); DemuxSource::getCurrentInstance()->mSegment->push(std::move(spBuf), len); DemuxSource::getCurrentInstance()->onPesCallback(); // coverity[RESOURCE_LEAK] if (isSubtitleDumpAvailable()) { if (DemuxSource::getCurrentInstance()->mDumpFd == -1) { DemuxSource::getCurrentInstance()->mDumpFd = ::open(DUMP_DATA_FILE_PATH, O_RDWR | O_CREAT, 0666); if (DemuxSource::getCurrentInstance()->mDumpFd < 0) { SUBTITLE_LOGI("mDumpFd=%d %m", DemuxSource::getCurrentInstance()->mDumpFd); return; } } auto ret = write(DemuxSource::getCurrentInstance()->mDumpFd, data, len); if (ret != len) { SUBTITLE_LOGE("Failed to write data: ret=%zd, %m", ret); } } } static int open_dvb_dmx(TVSubtitleData *data, int dmx_id, int pid, int flag) { SUBTITLE_LOGI_FUNC_IN(); int ret; AmlogicDemuxOpenParameterType op; struct dmx_pes_filter_params pesp; struct dmx_sct_filter_params param; int subtype = DemuxSource::getCurrentInstance()->mSubType; SUBTITLE_LOGI("dmx_id=%d, pid=%d, flag=%d, subtype=%d", dmx_id, pid, flag, subtype); data->dmx_id = -1; data->filter_handle = -1; memset(&op, 0, sizeof(op)); data->dmx_id = dmx_id; if (data->dmx_id == -1) { SUBTITLE_LOGE("invalid argument, data->dmx_id is -1"); return -1; } ret = DemuxOpen(dmx_id, &op); if (ret != AM_SUCCESS) goto error; ret = DemuxAllocateFilter(dmx_id, &data->filter_handle); if (ret != AM_SUCCESS) goto error; SUBTITLE_LOGI("data->filter_handle:%d", data->filter_handle); ret = DemuxSetBufferSize(dmx_id, data->filter_handle, DMX_SUBTITLE_BUFFER_SIZE); if (ret != AM_SUCCESS) goto error; memset(&pesp, 0, sizeof(pesp)); pesp.pid = pid; pesp.output = DMX_OUT_TAP; if (flag) { pesp.flags = DMX_SUBTITLE_SEC; } else { pesp.flags = DMX_SUBTITLE_NO_SEC; } if (TYPE_SUBTITLE_DVB == subtype) { SUBTITLE_LOGI("use PES filter subtype=%d", subtype); pesp.pes_type = DMX_PES_SUBTITLE; pesp.input = DMX_IN_FRONTEND; ret = DemuxSetPesFilter(dmx_id, data->filter_handle, &pesp); if (ret != AM_SUCCESS) goto error; } else if (TYPE_SUBTITLE_DVB_TELETEXT == subtype) { SUBTITLE_LOGI("use PES filter subtype=%d", subtype); pesp.pes_type = DMX_PES_SUBTITLE; // DMX_PES_TELETEXT; pesp.input = DMX_IN_FRONTEND; ret = DemuxSetPesFilter(dmx_id, data->filter_handle, &pesp); if (ret != AM_SUCCESS) goto error; } else if (TYPE_SUBTITLE_SCTE27 == subtype) { // the scte27 data is section SUBTITLE_LOGI("use section filter subtype=%d", subtype); memset(¶m, 0, sizeof(param)); param.pid = pid; param.filter.filter[0] = SCTE27_TID; param.filter.mask[0] = 0xff; param.flags = DMX_CHECK_CRC; ret = DemuxSetSecFilter(dmx_id, data->filter_handle, ¶m); if (ret != AM_SUCCESS) goto error; } else if (TYPE_SUBTITLE_ARIB_B24 == subtype) { // the arib data is section SUBTITLE_LOGI("use PES filter subtype=%d", subtype); pesp.pes_type = DMX_PES_SUBTITLE; pesp.input = DMX_IN_FRONTEND; ret = DemuxSetPesFilter(dmx_id, data->filter_handle, &pesp); if (ret != AM_SUCCESS) goto error; } else if (TYPE_SUBTITLE_DVB_TTML == subtype) { // the ttml data is section SUBTITLE_LOGI("use PES filter subtype=%d", subtype); pesp.pes_type = DMX_PES_SUBTITLE; pesp.input = DMX_IN_FRONTEND; ret = DemuxSetPesFilter(dmx_id, data->filter_handle, &pesp); if (ret != AM_SUCCESS) goto error; } ret = DemuxSetCallback(dmx_id, data->filter_handle, pes_data_cb, data); if (ret != AM_SUCCESS) goto error; ret = DemuxStartFilter(dmx_id, data->filter_handle); if (ret != AM_SUCCESS) goto error; SUBTITLE_LOGI_FUNC_OUT(); return 0; error: SUBTITLE_LOGE("ERROR !!!!!!!!!!!!!!!!!!!!!!!!!!!!"); if (data->filter_handle != -1) { DemuxFreeFilter(dmx_id, data->filter_handle); } if (data->dmx_id != -1) { DemuxClose(dmx_id); } SUBTITLE_LOGI_FUNC_OUT(); return -1; } static void close_dvb_dmx(TVSubtitleData *data, int dmx_id) { SUBTITLE_LOGI_FUNC_IN(); if (data == nullptr) { SUBTITLE_LOGE("data is nullptr"); return; } if (data->filter_handle != -1) { DemuxStopFilter(dmx_id, data->filter_handle); DemuxSetCallback(dmx_id, data->filter_handle, NULL, NULL); DemuxFreeFilter(dmx_id, data->filter_handle); data->filter_handle = -1; } if (dmx_id != -1) { DemuxClose(dmx_id); } data->dmx_id = -1; SUBTITLE_LOGI_FUNC_OUT(); } } // namespace DemuxSource::DemuxSource() { sInstance = this; mMediaSync = MediaSync_create(); SUBTITLE_LOGI("subtitle mediasync created"); } DemuxSource::~DemuxSource() { SUBTITLE_LOGI_FUNC_IN(); mExitRequested = true; if (mRenderTimeThread != nullptr) { mRenderTimeThread->join(); mRenderTimeThread = nullptr; } if (mMediaSync != nullptr) { MediaSync_destroy(mMediaSync); mMediaSync = nullptr; } mPlayerId = -1; mMediaSyncId = -1; mMediaSyncDestroyFlag = false; if (mDemuxContext != nullptr) { close_dvb_dmx(mDemuxContext, mDemuxId); delete mDemuxContext; mDemuxContext = nullptr; } if (mDumpFd > 0) { ::close(mDumpFd); mDumpFd = -1; } mSegment = nullptr; SUBTITLE_LOGI_FUNC_OUT(); } bool DemuxSource::notifyInfoChange() { std::unique_lock autolock(mLock); for (auto it = mInfoListeners.begin(); it != mInfoListeners.end(); it++) { auto wk_listener = (*it); if (auto lstn = wk_listener.lock()) { lstn->onTypeChanged(mSubType); return true; } } return false; } void DemuxSource::loopRenderTime() { while (!mExitRequested) { mLock.lock(); for (auto it = mInfoListeners.begin(); it != mInfoListeners.end();) { // Using weak_ Ptr. lock() to obtain shared_ Ptr, ensuring that null pointer exceptions // are not caused by expired listeners during processing auto wk_listener = it->lock(); if (!wk_listener) { SUBTITLE_LOGI("Listener is null or expired."); // Use erase to remove expired weeks_ PTR it = mInfoListeners.erase(it); continue; } int64_t value = 0; if (-1 == mMediaSyncId) { mSyncPts = sysfsReadInt(SYSFS_VIDEO_PTS.c_str(), 16); } else if (mMediaSync != nullptr && !mMediaSyncDestroyFlag) { MediaSync_getTrackMediaTime(mMediaSync, &value); mSyncPts = us2Pts(value); } static int i = 0; if (i++ % 300 == 0) { SUBTITLE_LOGE("read pts: %" PRId64 ", mMediaSyncId: %d", mSyncPts, mMediaSyncId); } if (!mExitRequested && mSyncPts > 0) { wk_listener->onRenderTimeChanged(mSyncPts); } ++it; } mLock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(16)); } } SubtitleIOType DemuxSource::type() { return E_SUBTITLE_DEMUX; } bool DemuxSource::start() { if (E_SOURCE_STARTED == mState) { SUBTITLE_LOGE("already stated"); return false; } SUBTITLE_LOGI("DemuxSource start mPid:%d mSecureLevelFlag:%d", mPid, mSecureLevelFlag); mState = E_SOURCE_STARTED; mSegment = std::shared_ptr(new BufferSegment()); mDemuxContext = new TVSubtitleData(); mRenderTimeThread = std::shared_ptr(new std::thread(&DemuxSource::loopRenderTime, this)); int ret = open_dvb_dmx(mDemuxContext, mDemuxId, mPid, mSecureLevelFlag); if (ret == -1) { return false; } notifyInfoChange(); return true; } void DemuxSource::updateParameter(int type, void *data) { SUBTITLE_LOGI("type = %d ", type); bool restartDemux = false; if (TYPE_SUBTITLE_DVB == type) { DvbParam *pDvbParam = (DvbParam *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pDvbParam->pid)) { restartDemux = true; } mDemuxId = pDvbParam->demuxId; mPid = pDvbParam->pid; mSecureLevelFlag = pDvbParam->flag; mParam1 = pDvbParam->compositionId; mParam2 = pDvbParam->ancillaryId; } else if (TYPE_SUBTITLE_DVB_TELETEXT == type) { TeletextParam *pTeletextParam = (TeletextParam *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pTeletextParam->pid)) { restartDemux = true; } mDemuxId = pTeletextParam->demuxId; mPid = pTeletextParam->pid; mSecureLevelFlag = pTeletextParam->flag; mParam1 = pTeletextParam->magazine; mParam2 = pTeletextParam->pageNo; } else if (TYPE_SUBTITLE_SCTE27 == type) { Scte27Param *pScte27Param = (Scte27Param *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pScte27Param->SCTE27_PID)) { restartDemux = true; } mDemuxId = pScte27Param->demuxId; mPid = pScte27Param->SCTE27_PID; mSecureLevelFlag = pScte27Param->flag; } else if (TYPE_SUBTITLE_ARIB_B24 == type) { Arib24Param *pArib24Param = (Arib24Param *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pArib24Param->pid)) { restartDemux = true; } mDemuxId = pArib24Param->demuxId; mPid = pArib24Param->pid; mSecureLevelFlag = pArib24Param->flag; mParam1 = pArib24Param->languageCodeId; } else if (TYPE_SUBTITLE_DVB_TTML == type) { TtmlParam *pTtmlParam = (TtmlParam *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pTtmlParam->pid)) { restartDemux = true; } mDemuxId = pTtmlParam->demuxId; mPid = pTtmlParam->pid; mSecureLevelFlag = pTtmlParam->flag; } else if (TYPE_SUBTITLE_SMPTE_TTML == type) { SmpteTtmlParam *pSmpteTtmlParam = (SmpteTtmlParam *)data; if ((mState == E_SOURCE_STARTED) && (mPid != pSmpteTtmlParam->pid)) { restartDemux = true; } mDemuxId = pSmpteTtmlParam->demuxId; mPid = pSmpteTtmlParam->pid; mSecureLevelFlag = pSmpteTtmlParam->flag; } SUBTITLE_LOGI("restartDemux=%d ", restartDemux); mSubType = type; if (restartDemux) { mExitRequested = true; if (mRenderTimeThread != nullptr) { mRenderTimeThread->join(); mRenderTimeThread = nullptr; } if (mDemuxContext != nullptr) { close_dvb_dmx(mDemuxContext, mDemuxId); delete mDemuxContext; mDemuxContext = nullptr; } mDemuxContext = new TVSubtitleData(); int ret = open_dvb_dmx(mDemuxContext, mDemuxId, mPid, mSecureLevelFlag); if (ret == 0) { mExitRequested = false; mRenderTimeThread = std::shared_ptr(new std::thread(&DemuxSource::loopRenderTime, this)); notifyInfoChange(); } else { delete mDemuxContext; mDemuxContext = nullptr; } } SUBTITLE_LOGI("mPid:%d, demuxId = %d, mSecureLevelFlag = %d", mPid, mDemuxId, mSecureLevelFlag); return; } void DemuxSource::setPipId(int mode, int id) { SUBTITLE_LOGI("mode:%d, id = %d", mode, id); if (PIP_PLAYER_ID == mode) { mPlayerId = id; } else if (PIP_MEDIASYNC_ID == mode) { if ((-1 != mMediaSyncId) || (mMediaSyncId != id)) { mMediaSyncId = id; mMediaSyncDestroyFlag = true; if (mMediaSync != nullptr) { MediaSync_destroy(mMediaSync); mMediaSync = MediaSync_create(); } MediaSync_bindInstance(mMediaSync, mMediaSyncId, MEDIA_SUBTITLE); mMediaSyncDestroyFlag = false; } } } bool DemuxSource::stop() { SUBTITLE_LOGI_FUNC_IN(); mState = E_SOURCE_STOPPED; mExitRequested = true; if (mRenderTimeThread != nullptr) { mRenderTimeThread->join(); mRenderTimeThread = nullptr; } // If parser has problem, it read more data than provided // then stop cannot stopped. need notify exit here. if (mSegment != nullptr) { mSegment->notifyExit(); } if (mDemuxContext != nullptr) { close_dvb_dmx(mDemuxContext, mDemuxId); delete mDemuxContext; mDemuxContext = nullptr; } mDemuxId = -1; SUBTITLE_LOGI_FUNC_OUT(); return false; } bool DemuxSource::isFileAvailable() { auto firstFrameToggle = sysfsReadInt(SYSFS_VIDEO_FIRSTRRAME.c_str(), 10); mediasync_frameinfo tempInfo; tempInfo.framePts = -1; SUBTITLE_LOGI("mMediaSyncId:%d, mMediaSync:%p", mMediaSyncId, mMediaSync); if (mMediaSyncId != -1 && mMediaSync != nullptr) { MediaSync_getFirstVideoFrameInfo(mMediaSync, &tempInfo); } SUBTITLE_LOGI("firstFrameToggle:%" PRId64 ",tempInfo.framePts:%" PRId64, firstFrameToggle, tempInfo.framePts); return (firstFrameToggle == 1 || tempInfo.framePts > 0); } size_t DemuxSource::availableDataSize() { if (mSegment == nullptr) return 0; return mSegment->availableDataSize(); } size_t DemuxSource::read(void *buffer, size_t size) { size_t read = 0; int isReadItemEnd = 0; // Current design of Parser Read, do not need add lock protection. // because all the read, is in Parser's parser thread. // We only need add lock here, is for protect access the mCurrentItem's // member function multi-thread. // Current Impl do not need lock, if required, then redesign the SegmentBuffer. // in case of applied size more than 1024*2, such as dvb subtitle, // and data process error for two reads. // so add until read applied data then exit. while (read != size && mState == E_SOURCE_STARTED) { if (mSegment == nullptr) { return 0; } if (mCurrentItem != nullptr && !mCurrentItem->isEmpty()) { if (size == 0xffff) { // if size is 0xffff, it means to get all the data of the current item size = mCurrentItem->getSize(); } read += mCurrentItem->read_check(((char *)buffer + read), size - read, &isReadItemEnd, E_SUBTITLE_DEMUX); // SUBTITLE_LOGV("read:%d,size:%d isReadItemEnd:%d SourceType:%d", read, size, // isReadItemEnd, E_SUBTITLE_DEMUX); if (read == size || isReadItemEnd == -1) return read; } else { // SUBTITLE_LOGV("mCurrentItem null, pop next buffer item"); mCurrentItem = mSegment->pop(); } } return read; } void DemuxSource::dump(int fd, const char *prefix) { dprintf(fd, "%s nDemuxSource:\n", prefix); { std::unique_lock autolock(mLock); for (auto it = mInfoListeners.begin(); it != mInfoListeners.end(); it++) { auto wk_listener = (*it); if (auto lstn = wk_listener.lock()) dprintf(fd, "%s InfoListener: %p\n", prefix, lstn.get()); } } dprintf(fd, "%s state:%d\n\n", prefix, mState); dprintf(fd, "%s Total Subtitle Tracks: (%s)%" PRId64 "\n", prefix, SYSFS_SUBTITLE_TOTAL.c_str(), sysfsReadInt(SYSFS_SUBTITLE_TOTAL.c_str(), 10)); dprintf(fd, "%s Current Subtitle type: (%s)%" PRId64 "\n", prefix, SYSFS_SUBTITLE_TYPE.c_str(), sysfsReadInt(SYSFS_SUBTITLE_TYPE.c_str(), 10)); dprintf(fd, "\n%s Current Unconsumed Data Size: %zu\n", prefix, availableDataSize()); } void DemuxSource::onPesCallback() { // See DataSource::checkParsedPts(), mNewestDemuxAVPts is updated within some conditions. if (mNewestDemuxAVPts != 0) { return; } updateNewestAVPts(0, 0); } void DemuxSource::updateNewestAVPts(int64_t subtitlePts, int64_t syncedPts) { amdmx_filter_mem_info demuxFilterMemInfo; memset(&demuxFilterMemInfo, 0, sizeof(demuxFilterMemInfo)); auto ret = DemuxGetNewestPts(mDemuxId, &demuxFilterMemInfo); if (ret != AM_SUCCESS) { SUBTITLE_LOGE("[newest_avpts_tag] fail to DemuxGetNewestPts"); return; } int64_t audioPts = 0; int64_t videoPts = 0; int64_t subtitleDemuxPts = 0; int64_t teletextPts = 0; for (__u32 i = 0; i < demuxFilterMemInfo.filter_num; ++i) { auto info = demuxFilterMemInfo.info[i]; if (info.type == AMDMX_AUDIO_TYPE) { audioPts = info.filter_info.newest_pts; } else if (info.type == AMDMX_VIDEO_TYPE) { videoPts = info.filter_info.newest_pts; } else if (info.type == AMDMX_SUBTITLE_TYPE) { subtitleDemuxPts = info.filter_info.newest_pts; } else if (info.type == AMDMX_TELETEXT_TYPE) { teletextPts = info.filter_info.newest_pts; } } if (subtitlePts != 0 && syncedPts != 0) { SUBTITLE_LOGI("[newest_avpts_tag_check] PES's PTS difference: video-audio = %" PRId64 " ms, video-subtitle = %" PRId64 " ms, audio-subtitle = %" PRId64 " ms, video-syncedPts = %" PRId64 " ms", pts2Ms(videoPts - audioPts), pts2Ms(videoPts - subtitlePts), pts2Ms(audioPts - subtitlePts), pts2Ms(videoPts - syncedPts)); } if (audioPts == 0 || videoPts == 0) { if (subtitlePts != 0 && syncedPts != 0) { SUBTITLE_LOGE("[newest_avpts_tag] audioPts = %" PRId64 " ms, videoPts = %" PRId64 " ms, teletextPts = %" PRId64 " ms, subtitlePts = %" PRId64 " ms", pts2Ms(audioPts), pts2Ms(videoPts), pts2Ms(teletextPts), pts2Ms(subtitleDemuxPts)); } mNewestDemuxAVPts = std::max(audioPts, videoPts); } else { mNewestDemuxAVPts = std::min(audioPts, videoPts); } }