2803 lines
80 KiB
C++
2803 lines
80 KiB
C++
/*
|
|
* Copyright (c) 2020 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* This source code is subject to the terms and conditions defined in the
|
|
* file 'LICENSE' which is part of this source code package.
|
|
*
|
|
* Description:
|
|
*/
|
|
|
|
#define LOG_NDEBUG 0
|
|
#define LOG_TAG "AmlMpPlayerImpl"
|
|
#include "Aml_MP_PlayerImpl.h"
|
|
#include <utils/AmlMpLog.h>
|
|
#include <utils/AmlMpUtils.h>
|
|
#include <utils/AmlMpConfig.h>
|
|
#include <utils/AmlMpBuffer.h>
|
|
#include <sstream>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include "AmlPlayerBase.h"
|
|
#include "utils/Amlsysfsutils.h"
|
|
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_VNDK__
|
|
#ifndef __ANDROID_RECOVERY__
|
|
#include <gui/Surface.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
#endif
|
|
#endif
|
|
#include <cutils/properties.h>
|
|
#endif
|
|
#include <utils/AmlMpEventLooper.h>
|
|
|
|
|
|
namespace aml_mp {
|
|
|
|
#define TS_BUFFER_SIZE (2 * 1024 * 1024)
|
|
#define TEMP_BUFFER_SIZE (188 * 100)
|
|
|
|
#define START_ALL_PENDING (1 << 0)
|
|
#define START_VIDEO_PENDING (1 << 1)
|
|
#define START_AUDIO_PENDING (1 << 2)
|
|
#define START_SUBTITLE_PENDING (1 << 3)
|
|
|
|
#define FAST_PLAY_THRESHOLD 2.0f
|
|
|
|
#define DEFAULT_DEMUX_MEM_SEC_LEVEL AML_MP_DEMUX_MEM_SEC_LEVEL2
|
|
#define DEFAULT_DEMUX_MEM_SEC_SIZE (20<<20)//20M
|
|
#define PIP_DEMUX_MEM_SEC_SIZE (36<<20)//36M
|
|
#define FCC_DEMUX_MEM_SEC_SIZE (54<<20)//54M
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#ifdef CONFIG_AMP_SERVER
|
|
void AmpMpPlayerDumpFile(const char *name, FILE** dump, const uint8_t* data, int32_t len)
|
|
{
|
|
if (dump == nullptr) {
|
|
return;
|
|
}
|
|
|
|
FILE *dumpFile = *dump;
|
|
if (!property_get_bool("vendor.media.aml_mp.server.dump", false)) {
|
|
if (dumpFile != nullptr) {
|
|
fclose(dumpFile);
|
|
*dump = nullptr;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (dumpFile == nullptr) {
|
|
char saveName[100] = { 0 };
|
|
snprintf(saveName, sizeof(saveName), "/data/%s_server.ts", name);
|
|
dumpFile = fopen(saveName, "wb+");
|
|
if (dumpFile == nullptr) {
|
|
ALOGE("%s open dump ts failed:%s", name, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
fwrite(data, 1, len, dumpFile);
|
|
*dump = dumpFile;
|
|
}
|
|
#endif
|
|
|
|
AmlMpPlayerImpl::AmlMpPlayerImpl(const Aml_MP_PlayerCreateParams* createParams, int32_t clientId)
|
|
: mInstanceId(AmlMpPlayerRoster::instance().registerPlayer(this))
|
|
, mClientId(clientId)
|
|
, mCreateParams(*createParams)
|
|
{
|
|
snprintf(mName, sizeof(mName), "%s_%p_%d_%d", LOG_TAG, this, mClientId >= 0 ? mClientId : mInstanceId, mInstanceId);
|
|
AmlMpConfig::instance().init();
|
|
|
|
#ifdef ANDROID
|
|
int sdkVersion = ANDROID_PLATFORM_SDK_VERSION;
|
|
const char* platform =
|
|
#ifdef __ANDROID_VNDK__
|
|
"vendor";
|
|
#elif __ANDROID_RECOVERY__
|
|
"recovery";
|
|
#else
|
|
"system";
|
|
#endif
|
|
MLOG("sdk:%d, platform:%s, mTsPlayerNonTunnel: %d", sdkVersion, platform, AmlMpConfig::instance().mTsPlayerNonTunnel);
|
|
#endif
|
|
|
|
#ifdef HAVE_VERSION_INFO
|
|
MLOGI("%s", getCommitInfo().c_str());
|
|
#endif
|
|
|
|
MLOG("drmMode:%s, sourceType:%s, options:%#" PRIx64, mpInputStreamType2Str(createParams->drmMode), mpInputSourceType2Str(createParams->sourceType),
|
|
createParams->options);
|
|
|
|
memset(&mVideoParams, 0, sizeof(mVideoParams));
|
|
mVideoParams.pid = AML_MP_INVALID_PID;
|
|
mVideoParams.videoCodec = AML_MP_CODEC_UNKNOWN;
|
|
memset(&mAudioParams, 0, sizeof(mAudioParams));
|
|
mAudioParams.pid = AML_MP_INVALID_PID;
|
|
mAudioParams.audioCodec = AML_MP_CODEC_UNKNOWN;
|
|
memset(&mSubtitleParams, 0, sizeof(mSubtitleParams));
|
|
mSubtitleParams.pid = AML_MP_INVALID_PID;
|
|
mSubtitleParams.subtitleCodec = AML_MP_CODEC_UNKNOWN;
|
|
memset(&mADParams, 0, sizeof(mADParams));
|
|
mADParams.pid = AML_MP_INVALID_PID;
|
|
mADParams.audioCodec = AML_MP_CODEC_UNKNOWN;
|
|
memset(&mIptvCasParams, 0, sizeof(mIptvCasParams));
|
|
memset(&mTeletextCtrlParam, 0, sizeof(mTeletextCtrlParam));
|
|
mTeletextCtrlParam.event = AML_MP_TT_EVENT_INVALID;
|
|
|
|
memset(&mAudioLanguage, 0, sizeof(mAudioLanguage));
|
|
|
|
mUserWaitingEcmMode = mWaitingEcmMode = (WaitingEcmMode)AmlMpConfig::instance().mWaitingEcmMode;
|
|
MLOGI("mWaitingEcmMode:%d", mWaitingEcmMode);
|
|
|
|
mTsBuffer.init(AmlMpConfig::instance().mWriteBufferSize * 1024 * 1024);
|
|
|
|
mWriteBuffer = new AmlMpBuffer(TEMP_BUFFER_SIZE);
|
|
mWriteBuffer->setRange(0, 0);
|
|
mZorder = kZorderBase + mInstanceId;
|
|
|
|
mPlayer = AmlPlayerBase::create(&mCreateParams, mInstanceId);
|
|
|
|
// in CAS PIP case we need increase the sec buffer size
|
|
increaseDmxSecMemSize();
|
|
}
|
|
|
|
AmlMpPlayerImpl::~AmlMpPlayerImpl()
|
|
{
|
|
MLOG();
|
|
|
|
#ifdef CONFIG_AMP_SERVER
|
|
freeBuffs();
|
|
#endif
|
|
|
|
if (mState != STATE_IDLE) {
|
|
MLOG("Waring!! mState is not STATE_IDLE. Force to stop enter.");
|
|
stop();
|
|
MLOG("Force to stop exit.");
|
|
}
|
|
|
|
// try release mPlayer as it may be retained during the stop phase.
|
|
mPlayer.clear();
|
|
|
|
// recover demux sec mem size to default, this value same as DMC_MEM_DEFAULT_SIZE
|
|
recoverDmxSecMemSize();
|
|
|
|
/* Set Aspect Mode to NONE & Disable AFD when exit playback */
|
|
if (mVideoAFDAspectMode >= 0) {
|
|
setVideoAFDAspectMode_l(AML_MP_VIDEO_AFD_ASPECT_MODE_NONE);
|
|
}
|
|
|
|
AmlMpPlayerRoster::instance().unregisterPlayer(mInstanceId);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::registerEventCallback(Aml_MP_PlayerEventCallback cb, void* userData)
|
|
{
|
|
MLOG();
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
if (mState != STATE_IDLE) {
|
|
MLOGW("can't registerEventCallback now!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
std::unique_lock<std::mutex> _eventLock(mEventLock);
|
|
mEventCb = cb;
|
|
mUserData = userData;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setVideoParams(const Aml_MP_VideoParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) != AML_MP_DECODING_STATE_STOPPED) {
|
|
MLOGE("video started already!");
|
|
return -1;
|
|
}
|
|
|
|
mVideoParams.pid = params->pid;
|
|
mVideoParams.videoCodec = params->videoCodec;
|
|
mVideoParams.width = params->width;
|
|
mVideoParams.height = params->height;
|
|
mVideoParams.frameRate = params->frameRate;
|
|
memcpy(mVideoParams.extraData, params->extraData, sizeof(mVideoParams.extraData));
|
|
mVideoParams.extraDataSize = params->extraDataSize;
|
|
mVideoParams.secureLevel = params->secureLevel;
|
|
|
|
MLOGI("setVideoParams vpid: 0x%x, fmt: %s", params->pid, mpCodecId2Str(params->videoCodec));
|
|
|
|
#if 1 // This commit commit config the secure level 2.
|
|
if (mVideoParams.secureLevel == AML_MP_DEMUX_MEM_SEC_NONE && mCreateParams.drmMode != AML_MP_INPUT_STREAM_NORMAL) {
|
|
mVideoParams.secureLevel = DEFAULT_DEMUX_MEM_SEC_LEVEL;
|
|
MLOGI("change video secure level to %d", mVideoParams.secureLevel);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setAudioParams(const Aml_MP_AudioParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) != AML_MP_DECODING_STATE_STOPPED) {
|
|
MLOGE("audio started already!");
|
|
return -1;
|
|
}
|
|
|
|
return setAudioParams_l(params);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setAudioParams_l(const Aml_MP_AudioParams* params)
|
|
{
|
|
mAudioParams.pid = params->pid;
|
|
mAudioParams.audioCodec = params->audioCodec;
|
|
mAudioParams.nChannels = params->nChannels;
|
|
mAudioParams.nSampleRate = params->nSampleRate;
|
|
memcpy(mAudioParams.extraData, params->extraData, sizeof(mAudioParams.extraData));
|
|
mAudioParams.extraDataSize = params->extraDataSize;
|
|
mAudioParams.secureLevel = params->secureLevel;
|
|
|
|
MLOGI("setAudioParams apid: 0x%x, fmt: %s", params->pid, mpCodecId2Str(params->audioCodec));
|
|
|
|
#if 1 // This commit config the secure level 2.
|
|
if (mAudioParams.secureLevel == AML_MP_DEMUX_MEM_SEC_NONE && mCreateParams.drmMode != AML_MP_INPUT_STREAM_NORMAL) {
|
|
mAudioParams.secureLevel = DEFAULT_DEMUX_MEM_SEC_LEVEL;
|
|
MLOGI("change audio secure level to %d", mAudioParams.secureLevel);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setSubtitleParams(const Aml_MP_SubtitleParams* params)
|
|
{
|
|
if (AmlMpConfig::instance().mDisableSubtitle) {
|
|
MLOGI("subtitle disabled!");
|
|
return 0;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) != AML_MP_DECODING_STATE_STOPPED) {
|
|
MLOGE("subtitle started already!");
|
|
return -1;
|
|
}
|
|
|
|
return setSubtitleParams_l(params);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setSubtitleParams_l(const Aml_MP_SubtitleParams* params)
|
|
{
|
|
memcpy(&mSubtitleParams, params, sizeof(Aml_MP_SubtitleParams));
|
|
|
|
MLOGI("setSubtitleParams spid: 0x%x, channelId:%d, fmt:%s", params->pid, params->channelId, mpCodecId2Str(params->subtitleCodec));
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setADParams(Aml_MP_AudioParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) != AML_MP_DECODING_STATE_STOPPED) {
|
|
MLOGE("AD started already!");
|
|
return -1;
|
|
}
|
|
|
|
mADParams.pid = params->pid;
|
|
mADParams.audioCodec = params->audioCodec;
|
|
mADParams.nChannels = params->nChannels;
|
|
mADParams.nSampleRate = params->nSampleRate;
|
|
memcpy(mADParams.extraData, params->extraData, sizeof(mADParams.extraData));
|
|
mADParams.extraDataSize = params->extraDataSize;
|
|
mADParams.secureLevel = params->secureLevel;
|
|
|
|
MLOGI("setADParams apid: 0x%x, fmt:%s", params->pid, mpCodecId2Str(params->audioCodec));
|
|
|
|
#if 1 // This commit config the secure level 2.
|
|
if (mADParams.secureLevel == AML_MP_DEMUX_MEM_SEC_NONE && mCreateParams.drmMode != AML_MP_INPUT_STREAM_NORMAL) {
|
|
mADParams.secureLevel = DEFAULT_DEMUX_MEM_SEC_LEVEL;
|
|
MLOGI("change ad secure level to %d", mADParams.secureLevel);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setCasSession(AML_MP_CASSESSION casSession)
|
|
{
|
|
AML_MP_TRACE(10);
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG();
|
|
#ifdef BUILD_AMP_SERVER
|
|
MLOGE("server interface, not here.");
|
|
#else
|
|
sptr<AmlCasBase> casBase = aml_handle_cast<AmlCasBase>(casSession);
|
|
RETURN_IF(-1, casBase == nullptr);
|
|
|
|
if (casBase->serviceType() < AML_MP_CAS_SERVICE_TYPE_IPTV) {
|
|
MLOGE("do not support bind DVB caseSession!");
|
|
} else if (casBase->serviceType() == AML_MP_CAS_SERVICE_VERIMATRIX_WEB) {
|
|
MLOGW("verimatrix web casSession don't need bind!");
|
|
} else if (casBase->serviceType() == AML_MP_CAS_SERVICE_NAGRA_WEB) {
|
|
MLOGW("nagra web casSession don't need bind!");
|
|
}
|
|
|
|
mCasHandle = casBase;
|
|
#ifndef __ANDROID_RECOVERY__
|
|
casBase->getEcmPids(mEcmPids);
|
|
#endif
|
|
mIsStandaloneCas = true;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setIptvCASParams(Aml_MP_CASServiceType serviceType, const Aml_MP_IptvCASParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG();
|
|
#ifndef BUILD_AMP_SERVER
|
|
mCasServiceType = serviceType;
|
|
mIptvCasParams = *params;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::start()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG();
|
|
|
|
return start_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::start_l()
|
|
{
|
|
MLOGI("AmlMpPlayerImpl start_l\n");
|
|
if (mState == STATE_IDLE) {
|
|
if (prepare_l() < 0) {
|
|
MLOGE("prepare failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_START_PENDING);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// dvr play case need set secure level to tsplayer
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID ||
|
|
mCreateParams.options & AML_MP_OPTION_DVR_PLAYBACK) {
|
|
mPlayer->setVideoParams(&mVideoParams);
|
|
}
|
|
|
|
if (mAudioParams.pid != AML_MP_INVALID_PID ||
|
|
mCreateParams.options & AML_MP_OPTION_DVR_PLAYBACK) {
|
|
mPlayer->setAudioParams(&mAudioParams);
|
|
setAudioCoreParameters_l();
|
|
}
|
|
|
|
if (mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
mPlayer->setSubtitleParams(&mSubtitleParams);
|
|
}
|
|
|
|
int ret = 0;
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID ||
|
|
mAudioParams.pid != AML_MP_INVALID_PID ||
|
|
mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
ret = mPlayer->start();
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return ret;
|
|
}
|
|
}
|
|
setState_l(STATE_RUNNING);
|
|
|
|
//CHECK: assume start always be success if param exist
|
|
if (mAudioParams.pid != AML_MP_INVALID_PID) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
if (mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
startADDecoding_l();
|
|
MLOGI("<<<< AmlMpPlayerImpl start_l\n");
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stop()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return stop_l(lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stop_l(std::unique_lock<std::mutex>& lock, bool clearCasSession)
|
|
{
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
if (mPlayer) {
|
|
mPlayer->stop();
|
|
|
|
Aml_MP_AudioParams dummyAudioParam;
|
|
memset(&dummyAudioParam, 0, sizeof(dummyAudioParam));
|
|
dummyAudioParam.pid = AML_MP_INVALID_PID;
|
|
dummyAudioParam.audioCodec = AML_MP_CODEC_UNKNOWN;
|
|
mPlayer->setAudioParams(&dummyAudioParam);
|
|
|
|
Aml_MP_AudioParams &dummyADParam = dummyAudioParam;
|
|
mPlayer->setADParams(&dummyADParam, false);
|
|
}
|
|
}
|
|
|
|
// ensure stream stopped if mState isn't running or paused
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STOPPED);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_STOPPED);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_STOPPED);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STOPPED);
|
|
|
|
int ret = resetIfNeeded_l(lock, clearCasSession);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::pause()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG();
|
|
|
|
return pause_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::pause_l() {
|
|
if (mState != STATE_RUNNING) {
|
|
return 0;
|
|
}
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
if (mPlayer->pause() < 0) {
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_PAUSED);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_STARTED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_PAUSED);
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_STARTED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_PAUSED);
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) == AML_MP_DECODING_STATE_STARTED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_PAUSED);
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) == AML_MP_DECODING_STATE_STARTED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_PAUSED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::resume()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
return resume_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::resume_l() {
|
|
if (mState != STATE_PAUSED) {
|
|
return 0;
|
|
}
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
int ret = 0;
|
|
// video state
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) == AML_MP_DECODING_STATE_PAUSED) {
|
|
ret += mPlayer->resumeVideoDecoding();
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
// audio state
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_PAUSED) {
|
|
ret += mPlayer->resumeAudioDecoding();
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STARTED);
|
|
} else if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_STOPPED && mAudioStoppedInSwitching) {
|
|
ret += startAudioDecoding_l();
|
|
}
|
|
// ad state
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_PAUSED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
// subtitle state
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) == AML_MP_DECODING_STATE_PAUSED) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_RUNNING);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::flush()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
if (mState != STATE_RUNNING && mState != STATE_PAUSED) {
|
|
MLOGI("State is %s, can't call flush when not play", stateString(mState));
|
|
return 0;
|
|
}
|
|
|
|
int ret;
|
|
#ifdef CONFIG_AMP_SERVER
|
|
mCallingFlush = true;
|
|
#endif
|
|
ret = mPlayer->flush();
|
|
|
|
if (ret != AML_MP_ERROR_DEAD_OBJECT) {
|
|
return ret;
|
|
}
|
|
|
|
ret = mPlayer->stop();
|
|
if (ret < 0) {
|
|
MLOGI("[%s, %d] stop play fail ret: %d", __FUNCTION__, __LINE__, ret);
|
|
}
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) == AML_MP_DECODING_STATE_STARTED ||
|
|
getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) == AML_MP_DECODING_STATE_PAUSED) {
|
|
mPlayer->setVideoParams(&mVideoParams);
|
|
}
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_STARTED ||
|
|
getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_PAUSED) {
|
|
mPlayer->setAudioParams(&mAudioParams);
|
|
setAudioCoreParameters_l();
|
|
}
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_STARTED ||
|
|
getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_PAUSED) {
|
|
mPlayer->setADParams(&mADParams, true);
|
|
}
|
|
|
|
if (tryWaitEcm_l()) {
|
|
setState_l(STATE_PREPARING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_START_PENDING);
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_START_PENDING);
|
|
return ret;
|
|
}
|
|
|
|
ret = mPlayer->start();
|
|
if (ret < 0) {
|
|
MLOGI("[%s, %d] start play fail ret: %d", __FUNCTION__, __LINE__, ret);
|
|
}
|
|
|
|
if (mState == STATE_PAUSED) {
|
|
ret = mPlayer->pause();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Smooth playback between 0 and 2x speed (0 < speed <= 2), audio will be resample with play speed
|
|
// I-frmae playback when speed > 2 or speed < 0, audio will not be processed when I-frmae playback
|
|
int AmlMpPlayerImpl::setPlaybackRate(float rate)
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
MLOG("rate:%f", rate);
|
|
|
|
Aml_MP_VideoDecodeMode newDecodeMode;
|
|
|
|
//newDecodeMode = (mPlaybackRate > FAST_PLAY_THRESHOLD)?AML_MP_VIDEO_DECODE_MODE_IONLY:AML_MP_VIDEO_DECODE_MODE_NONE;
|
|
if (rate > FAST_PLAY_THRESHOLD || rate < 0) {
|
|
newDecodeMode = (mCreateParams.sourceType == AML_MP_INPUT_SOURCE_ES_MEMORY) ?
|
|
AML_MP_VIDEO_DECODE_MODE_IONLY : AML_MP_VIDEO_DECODE_MODE_PAUSE_NEXT;
|
|
} else {
|
|
newDecodeMode = AML_MP_VIDEO_DECODE_MODE_NONE;
|
|
}
|
|
|
|
return setPlaybackRate_l(rate, newDecodeMode, lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setPlaybackRate2(float rate, Aml_MP_VideoDecodeMode decodeMode)
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
MLOG("rate: %f mode: %d", rate, decodeMode);
|
|
|
|
return setPlaybackRate_l(rate, decodeMode, lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setPlaybackRate_l(float rate, Aml_MP_VideoDecodeMode decodeMode, std::unique_lock<std::mutex>& lock)
|
|
{
|
|
int ret = 0;
|
|
bool decodeModeChanged = false;
|
|
|
|
mPlaybackRate = rate;
|
|
decodeModeChanged = (decodeMode != mVideoDecodeMode);
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
if (mPlaybackRate == 0.0f) {
|
|
ret = pause_l();
|
|
return ret;
|
|
}
|
|
|
|
if (mState == STATE_PAUSED) {
|
|
resume_l();
|
|
}
|
|
|
|
if (decodeModeChanged) {
|
|
switchDecodeMode_l(decodeMode, lock);
|
|
}
|
|
|
|
ret = mPlayer->setPlaybackRate(rate);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getPlaybackRate(float* rate) {
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
if (rate == nullptr) {
|
|
return AML_MP_ERROR;
|
|
}
|
|
|
|
int ret = mPlayer->getPlaybackRate(rate);
|
|
if (ret != AML_MP_OK) {
|
|
*rate = mPlaybackRate;
|
|
}
|
|
//MLOG("rate: %f, mPlaybackRate: %f", *rate, mPlaybackRate);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::switchAudioTrack(const Aml_MP_AudioParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG("new apid: 0x%x, fmt:%s", params->pid, mpCodecId2Str(params->audioCodec));
|
|
int ret = -1;
|
|
|
|
setAudioParams_l(params);
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
ret = mPlayer->switchAudioTrack(params);
|
|
|
|
if (ret != AML_MP_ERROR_DEAD_OBJECT) {
|
|
return ret;
|
|
}
|
|
|
|
ret = mPlayer->stopAudioDecoding();
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STOPPED);
|
|
if (mState == STATE_RUNNING) {
|
|
ret = startAudioDecoding_l();
|
|
} else {
|
|
mAudioStoppedInSwitching = true;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::switchSubtitleTrack(const Aml_MP_SubtitleParams* params)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG("new spid: 0x%x, channelId:%d, fmt:%s", params->pid, params->channelId, mpCodecId2Str(params->subtitleCodec));
|
|
int ret = 0;
|
|
|
|
setSubtitleParams_l(params);
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
ret = mPlayer->switchSubtitleTrack(params);
|
|
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
if (mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_AMP_SERVER
|
|
void AmlMpPlayerImpl::NotifyEcmData(bool async, int32_t pid, const uint8_t *data, int32_t len) {
|
|
if (data == nullptr || len <= 0) {
|
|
return;
|
|
}
|
|
|
|
Amp_Mp_Player_Notify_EcmData ecmEventData;
|
|
ecmEventData.async = async;
|
|
ecmEventData.pid = pid;
|
|
ecmEventData.data = data;
|
|
ecmEventData.len = len;
|
|
notifyListener((Aml_MP_PlayerEventType)AML_MP_PLAYER_EVENT_ECM_NOTIFY, (uint64_t)&ecmEventData);
|
|
}
|
|
|
|
void AmlMpPlayerImpl::freeBuffs() {
|
|
if (!mBufPoolHasInit) {
|
|
return;
|
|
}
|
|
|
|
if (mWriteThread != 0) {
|
|
mWriteThreadExit = true;
|
|
pthread_join(mWriteThread, NULL);
|
|
mWriteThread = 0;
|
|
}
|
|
|
|
if (mBufPoolBufs != nullptr) {
|
|
free(mBufPoolBufs);
|
|
mBufPoolBufs = nullptr;
|
|
}
|
|
|
|
if (mBufPoolPtr != nullptr) {
|
|
munmap(mBufPoolPtr, mBufPoolBufCnt * mBufPoolBufOne);
|
|
mBufPoolPtr = nullptr;
|
|
}
|
|
|
|
if (mBufPoolFd >= 0) {
|
|
close(mBufPoolFd);
|
|
mBufPoolFd = -1;
|
|
}
|
|
|
|
if (mBufPoolDataMq != nullptr) {
|
|
delete mBufPoolDataMq;
|
|
mBufPoolDataMq = nullptr;
|
|
}
|
|
|
|
if (mBufPoolFreeMq != nullptr) {
|
|
delete mBufPoolFreeMq;
|
|
mBufPoolFreeMq = nullptr;
|
|
}
|
|
|
|
mBufPoolHasInit = false;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::setEcmPids(const std::vector<int>& ecmPids) {
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
mEcmPids = ecmPids;
|
|
mWaitingEcmByServer = true;
|
|
|
|
std::string dump;
|
|
for (auto pid : mEcmPids) {
|
|
dump += std::to_string(pid) + " ";
|
|
}
|
|
MLOGI("client set ecm pids:%s", dump.c_str());
|
|
}
|
|
|
|
int32_t AmlMpPlayerImpl::setBuffs(int32_t fd, int32_t bufPoolBufCnt, int32_t bufPoolBufOne,
|
|
DataQueueMQ *bufPoolDataMq, FreeQueueMQ *bufPoolFreeMq) {
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
MLOGI("setbuffs, cnt:%d, size:%d, fd:%d", bufPoolBufCnt, bufPoolBufOne, fd);
|
|
|
|
if (mBufPoolHasInit) {
|
|
MLOGE("why setbuffs again???");
|
|
return -1;
|
|
}
|
|
|
|
mBufPoolDataMq = bufPoolDataMq;
|
|
mBufPoolFreeMq = bufPoolFreeMq;
|
|
|
|
BufPoolNode *bufs = (BufPoolNode *)malloc(sizeof(BufPoolNode) * bufPoolBufCnt);
|
|
if (bufs == nullptr) {
|
|
MLOGE("malloc fail");
|
|
return -1;
|
|
}
|
|
|
|
|
|
void* ptr = mmap(NULL, bufPoolBufCnt * bufPoolBufOne, PROT_READ, MAP_SHARED, fd, 0);
|
|
if (ptr == MAP_FAILED) {
|
|
free(bufs);
|
|
return -1;
|
|
}
|
|
|
|
mBufPoolFd = fd;
|
|
mBufPoolPtr = ptr;
|
|
mBufPoolBufCnt = bufPoolBufCnt;
|
|
mBufPoolBufOne = bufPoolBufOne;
|
|
mBufPoolBufs = bufs;
|
|
for (int i = 0; i < mBufPoolBufCnt; i++) {
|
|
mBufPoolBufs[i].ptr = (uint8_t *)ptr + i * mBufPoolBufOne;
|
|
mBufPoolBufs[i].size = mBufPoolBufOne;
|
|
mBufPoolFreeMq->write(&i);
|
|
MLOGI("set free buf:%d", i);
|
|
}
|
|
|
|
mBufPoolHasInit = true;
|
|
|
|
if (pthread_create(&mWriteThread, NULL, writeDataThread, this) != 0) {
|
|
MLOGE("create encoder output thread fail");
|
|
freeBuffs();
|
|
return -1;
|
|
}
|
|
|
|
MLOGI("setbuffer ok, fd:%d, cnt:%d, oneSize:%d, ptr:%p this:%p",
|
|
mBufPoolFd, mBufPoolBufCnt, mBufPoolBufOne, mBufPoolPtr, this);
|
|
return 0;
|
|
}
|
|
|
|
void* AmlMpPlayerImpl::writeDataThread(void *arg) {
|
|
AmlMpPlayerImpl *player = (AmlMpPlayerImpl *)arg;
|
|
if (player == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
char mName[50] = { 0 };
|
|
snprintf(mName, sizeof(mName), "%s", player->mName);
|
|
|
|
MLOGI("enter write data thread");
|
|
|
|
size_t needFlushDataCnt = 0;
|
|
FILE *dumpFile = nullptr;
|
|
|
|
while (!player->mWriteThreadExit) {
|
|
if (player->mCallingFlush) {
|
|
needFlushDataCnt = player->mBufPoolDataMq->availableToRead();
|
|
MLOGI("calling flush, need clear buf:%d", needFlushDataCnt);
|
|
player->mCallingFlush = false;
|
|
}
|
|
AmlMpBufPoolDataPacket aidlPkt = {}; //BufPoolDataPacket
|
|
if (!player->mBufPoolDataMq->read(&aidlPkt)) {
|
|
usleep(10 * 1000);
|
|
continue;
|
|
}
|
|
|
|
if (needFlushDataCnt > 0) {
|
|
player->mBufPoolFreeMq->write(&aidlPkt.index);
|
|
needFlushDataCnt--;
|
|
continue;
|
|
}
|
|
|
|
uint8_t* ptr = player->mBufPoolBufs[aidlPkt.index].ptr;
|
|
uint32_t len = aidlPkt.size;
|
|
|
|
uint32_t send = 0;
|
|
int retry = 0;
|
|
while (len > 0 && !player->mWriteThreadExit) {
|
|
int32_t ret = 0;
|
|
{
|
|
std::unique_lock<std::mutex> lock(player->mLock);
|
|
if (player->mCallingFlush) {
|
|
break;
|
|
}
|
|
//MLOGI("will write, idx:%d, size:%d, send:%d, left:%d", aidlPkt.index, aidlPkt.size, send, len);
|
|
if (player->mPlayer != nullptr && player->mState != STATE_STOPPED) {
|
|
if (aidlPkt.isTsData) {
|
|
ret = player->writeData_l(lock, ptr + send, len);
|
|
} else {
|
|
ret = player->writeEsData((Aml_MP_StreamType)aidlPkt.streamType, ptr + send, len, aidlPkt.pts);
|
|
}
|
|
}
|
|
}
|
|
if (ret <= 0) {
|
|
usleep(10 * 1000);
|
|
if (++retry > 200) {
|
|
MLOGE("write timeout, maybe player is full or player is null, ret:%d", ret);
|
|
retry = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//AmpMpPlayerDumpFile(mName, &dumpFile, ptr + send, len);
|
|
|
|
retry = 0;
|
|
len -= ret;
|
|
send += ret;
|
|
//MLOGI("write ok, idx:%d, size:%d, send:%d, left:%d ret:%d, dump:%p", aidlPkt.index, aidlPkt.size, send, len, ret, dumpFile);
|
|
}
|
|
|
|
player->mBufPoolFreeMq->write(&aidlPkt.index);
|
|
}
|
|
|
|
if (dumpFile != nullptr) {
|
|
fclose(dumpFile);
|
|
dumpFile = nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
|
|
int AmlMpPlayerImpl::writeData(const uint8_t* buffer, size_t size) {
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return writeData_l(lock, buffer, size);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::writeData_l(std::unique_lock<std::mutex>& lock, const uint8_t* buffer, size_t size) {
|
|
int written = 0;
|
|
|
|
bool needBuffering = false;
|
|
if ((mCasHandle || mWaitingEcmByServer) && mCreateParams.drmMode == AML_MP_INPUT_STREAM_ENCRYPTED && mWaitingEcmMode == kWaitingEcmSynchronous && !mFirstEcmWritten) {
|
|
size_t ecmOffset = size;
|
|
size_t ecmSize = 188;
|
|
//coverity[value_overwrite]
|
|
ecmOffset = findEcmPacket(buffer, size, mEcmPids, &ecmSize);
|
|
if (ecmSize > 0) {
|
|
#ifdef BUILD_AMP_SERVER
|
|
NotifyEcmData(false, 0, buffer + ecmOffset, ecmSize);
|
|
#else
|
|
mCasHandle->processEcm(false, 0, buffer + ecmOffset, ecmSize);
|
|
#endif
|
|
mFirstEcmWritten = true;
|
|
MLOGI("first ECM written, offset:%zu", mTsBuffer.size() + ecmOffset);
|
|
} else {
|
|
needBuffering = true;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING || needBuffering) {
|
|
//is waiting for start_delay, writeData into mTsBuffer
|
|
if (mTsBuffer.space() < size) {
|
|
MLOGW("mTsBuffer full!");
|
|
return -1;
|
|
}
|
|
written = mTsBuffer.put(buffer, size); //TODO: check buffer overflow
|
|
if (mParser != nullptr) {
|
|
written = mParser->writeData(buffer, size);
|
|
}
|
|
} else {
|
|
//already start, need move data from mTsBuffer to player
|
|
if (!mTsBuffer.empty() || mWriteBuffer->size() != 0) {
|
|
if (drainDataFromBuffer_l(lock) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
written = doWriteData_l(buffer, size, lock);
|
|
}
|
|
|
|
if (written == 0) {
|
|
written = -1;
|
|
}
|
|
|
|
if (written > 0) {
|
|
statisticWriteDataRate_l(written);
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::drainDataFromBuffer_l(std::unique_lock<std::mutex>& lock)
|
|
{
|
|
int written = 0;
|
|
int retry = 0;
|
|
do {
|
|
if (mWriteBuffer->size() == 0) {
|
|
size_t readSize = mTsBuffer.get(mWriteBuffer->base(), mWriteBuffer->capacity());
|
|
mWriteBuffer->setRange(0, readSize);
|
|
}
|
|
|
|
written = doWriteData_l(mWriteBuffer->data(), mWriteBuffer->size(), lock);
|
|
|
|
if (written > 0) {
|
|
//coverity[overflow_const]
|
|
mWriteBuffer->setRange(mWriteBuffer->offset()+written, mWriteBuffer->size()-written);
|
|
} else {
|
|
if (retry >= 4) {
|
|
break;
|
|
}
|
|
retry++;
|
|
usleep(50 * 1000);
|
|
}
|
|
} while (!mTsBuffer.empty() || mWriteBuffer->size() != 0);
|
|
|
|
if (mTsBuffer.empty() && mWriteBuffer->size() == 0) {
|
|
MLOGI("writeData from buffer done");
|
|
return 0;
|
|
}
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::doWriteData_l(const uint8_t* buffer, size_t size, std::unique_lock<std::mutex>& lock)
|
|
{
|
|
int written = 0;
|
|
if (mCreateParams.drmMode == AML_MP_INPUT_STREAM_ENCRYPTED) {
|
|
if ((mCasHandle == nullptr && !mWaitingEcmByServer) || mWaitingEcmMode == kWaitingEcmASynchronous) {
|
|
lock.unlock();
|
|
written = mPlayer->writeData(buffer, size);
|
|
lock.lock();
|
|
} else {
|
|
size_t totalSize = size;
|
|
size_t ecmOffset = size;
|
|
size_t ecmSize = 188;
|
|
int ecmCount = 0;
|
|
|
|
while (size) {
|
|
//coverity[value_overwrite]
|
|
ecmOffset = findEcmPacket(buffer, size, mEcmPids, &ecmSize);
|
|
ecmCount += ecmSize != 0;
|
|
|
|
size_t partialSize = ecmOffset;
|
|
int ret = 0;
|
|
int retryCount = 0;
|
|
while (partialSize) {
|
|
lock.unlock();
|
|
ret = mPlayer->writeData(buffer, partialSize);
|
|
lock.lock();
|
|
if (ret <= 0) {
|
|
if (written == 0) {
|
|
goto exit;
|
|
}
|
|
usleep(50 * 1000);
|
|
|
|
++retryCount;
|
|
if (retryCount%40 == 0) {
|
|
MLOGI("writeData %d/%zu(%zu), ecmOffset:%zu(%d), return:%d", written, totalSize, size, ecmOffset, ecmCount, ret);
|
|
}
|
|
} else {
|
|
buffer += ret;
|
|
partialSize -= ret;
|
|
written += ret;
|
|
size -= ret;
|
|
}
|
|
};
|
|
if (ecmSize > 0) {
|
|
#ifdef BUILD_AMP_SERVER
|
|
NotifyEcmData(false, 0, buffer, ecmSize);
|
|
#else
|
|
mCasHandle->processEcm(false, 0, buffer, ecmSize);
|
|
#endif
|
|
buffer += ecmSize;
|
|
written += ecmSize;
|
|
size -= ecmSize;
|
|
}
|
|
}
|
|
}
|
|
} else if (mCreateParams.drmMode == AML_MP_INPUT_STREAM_SECURE_MEMORY) {
|
|
lock.unlock();
|
|
written = mPlayer->writeData(buffer, size);
|
|
lock.lock();
|
|
} else {
|
|
//normal stream
|
|
lock.unlock();
|
|
written = mPlayer->writeData(buffer, size);
|
|
lock.lock();
|
|
}
|
|
|
|
exit:
|
|
return written;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::writeEsData(Aml_MP_StreamType type, const uint8_t* buffer, size_t size, int64_t pts)
|
|
{
|
|
MLOGD("[%s_%s] buffer:%p, size:%zu, pts:%#" PRIx64 "(%.3fs)", __FUNCTION__, mpStreamType2Str(type), buffer, size, pts, pts/9e4);
|
|
//audio isn`t support non-blocking mode, must different threads to write av es
|
|
//std::unique_lock<std::mutex> _l(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->writeEsData(type, buffer, size, pts);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getCurrentPts(Aml_MP_StreamType streamType, int64_t* pts)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->getCurrentPts(streamType, pts);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getFirstPts(Aml_MP_StreamType streamType, int64_t* pts)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->getFirstPts(streamType, pts);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getBufferStat(Aml_MP_BufferStat* bufferStat)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->getBufferStat(bufferStat);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setANativeWindow(ANativeWindow* nativeWindow)
|
|
{
|
|
AML_MP_UNUSED(nativeWindow);
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_RECOVERY__
|
|
MLOGI("setAnativeWindow: %p, mNativewindow: %p", nativeWindow, mNativeWindow.get());
|
|
mNativeWindow = nativeWindow;
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
mPlayer->setANativeWindow(mNativeWindow.get());
|
|
setSidebandIfNeeded_l();
|
|
}
|
|
#endif
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setSidebandIfNeeded_l()
|
|
{
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_RECOVERY__
|
|
if (mNativeWindow == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
if (AmlMpConfig::instance().mTsPlayerNonTunnel || mCreateParams.sourceType == AML_MP_INPUT_SOURCE_ES_MEMORY) {
|
|
if (AmlMpConfig::instance().mUseVideoTunnel == 1) {
|
|
if (mNativeWindowHelper.setSidebandNonTunnelMode(mNativeWindow.get(), &mVideoTunnelId) == 0) {
|
|
MLOGI("allocated video tunnel id:%d", mVideoTunnelId);
|
|
}
|
|
}
|
|
} else {
|
|
mNativeWindowHelper.setSidebandTunnelMode(mNativeWindow.get());
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setVideoWindow(int x, int y, int width, int height)
|
|
{
|
|
AML_MP_TRACE(10);
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
if (width < 0 || height < 0) {
|
|
MLOGI("Invalid windowsize: %dx%d, return fail", width, height);
|
|
return -1;
|
|
}
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_VNDK__
|
|
#ifndef __ANDROID_RECOVERY__
|
|
if (mNativeWindow == nullptr) {
|
|
MLOGI("Nativewindow is null, create it");
|
|
mComposerClient = new android::SurfaceComposerClient;
|
|
mComposerClient->initCheck();
|
|
|
|
mSurfaceControl = mComposerClient->createSurface(android::String8("AmlMpPlayer"), width, height, android::PIXEL_FORMAT_RGB_565, 0);
|
|
if (mSurfaceControl->isValid()) {
|
|
mSurface = mSurfaceControl->getSurface();
|
|
android::SurfaceComposerClient::Transaction()
|
|
.setFlags(mSurfaceControl, android::layer_state_t::eLayerOpaque, android::layer_state_t::eLayerOpaque)
|
|
.show(mSurfaceControl)
|
|
.apply();
|
|
}
|
|
mNativeWindow = mSurface;
|
|
}
|
|
|
|
if (mSurfaceControl != nullptr) {
|
|
MLOGI("Set video window size: x %d, y %d, width: %d, height: %d", x, y, width, height);
|
|
auto transcation = android::SurfaceComposerClient::Transaction();
|
|
if (x >= 0 && y >= 0) {
|
|
transcation.setPosition(mSurfaceControl, x, y);
|
|
}
|
|
|
|
transcation.setSize(mSurfaceControl, width, height);
|
|
#if ANDROID_PLATFORM_SDK_VERSION >= 31
|
|
transcation.setCrop(mSurfaceControl, android::Rect(width, height));
|
|
#elif ANDROID_PLATFORM_SDK_VERSION >= 29
|
|
transcation.setCrop_legacy(mSurfaceControl, android::Rect(width, height));
|
|
#endif
|
|
transcation.setLayer(mSurfaceControl, mZorder);
|
|
transcation.apply();
|
|
|
|
// setVideoWindow is used for create surface now, don't need to call the underlying implementation.
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
mVideoWindow = {x, y, width, height};
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
if (mVideoWindow.width >= 0 && mVideoWindow.height >= 0) {
|
|
mPlayer->setVideoWindow(x, y, width, height);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setVolume(float volume)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
int ret = 0;
|
|
if (volume < 0) {
|
|
MLOGI("volume is %f, set to 0.0", volume);
|
|
volume = 0.0;
|
|
}
|
|
mVolume = volume;
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
ret = mPlayer->setVolume(volume);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getVolume(float* volume)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->getVolume(volume);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::showVideo()
|
|
{
|
|
MLOG();
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
mVideoShowState = true;
|
|
return mPlayer->showVideo();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::hideVideo()
|
|
{
|
|
MLOG();
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
mVideoShowState = false;
|
|
return mPlayer->hideVideo();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::showSubtitle()
|
|
{
|
|
MLOG();
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->showSubtitle();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::hideSubtitle()
|
|
{
|
|
MLOG();
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->hideSubtitle();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setParameter(Aml_MP_PlayerParameterKey key, void* parameter)
|
|
{
|
|
AML_MP_TRACE(10);
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
return setParameter_l(key, parameter, lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setParameter_l(Aml_MP_PlayerParameterKey key, void* parameter, std::unique_lock<std::mutex>& lock) {
|
|
int ret = 0;
|
|
|
|
switch (key) {
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_DISPLAY_MODE:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mVideoDisplayMode = *(Aml_MP_VideoDisplayMode*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_BLACK_OUT:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mBlackOut = *(bool*)parameter?1:0;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_DECODE_MODE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
Aml_MP_VideoDecodeMode decodeMode = *(Aml_MP_VideoDecodeMode*)parameter;
|
|
switchDecodeMode_l(decodeMode, lock);
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_PTS_OFFSET:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mVideoPtsOffset = *(int*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_OUTPUT_MODE:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioOutputMode = *(Aml_MP_AudioOutputMode*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_OUTPUT_DEVICE:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioOutputDevice = *(Aml_MP_AudioOutputDevice*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_PTS_OFFSET:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioPtsOffset = *(int*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_BALANCE:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioBalance = *(Aml_MP_AudioBalance*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_MUTE:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioMute = *(bool*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_NETWORK_JITTER:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mNetworkJitter = *(int*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AD_STATE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
int ADState = *(int*)parameter;
|
|
if (ADState) {
|
|
startADDecoding_l();
|
|
} else {
|
|
stopADDecoding_l(lock);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AD_MIX_LEVEL:
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mADMixLevel = *(Aml_MP_ADVolume*)parameter;
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_WORK_MODE:
|
|
{
|
|
mWorkMode = *(Aml_MP_PlayerWorkMode*)parameter;
|
|
MLOGI("Set work mode: %s", mpPlayerWorkMode2Str(mWorkMode));
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_WINDOW_ZORDER:
|
|
{
|
|
mZorder = *(int*)parameter;
|
|
MLOGI("Set zorder: %d", mZorder);
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_VNDK__
|
|
#ifndef __ANDROID_RECOVERY__
|
|
if (mSurfaceControl != nullptr) {
|
|
auto transcation = android::SurfaceComposerClient::Transaction();
|
|
|
|
transcation.setLayer(mSurfaceControl, mZorder);
|
|
|
|
transcation.apply();
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_CREATE_PARAMS:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
Aml_MP_PlayerCreateParams* createParams = (Aml_MP_PlayerCreateParams*)parameter;
|
|
size_t checkSize = offsetof(Aml_MP_PlayerCreateParams, reserved);
|
|
|
|
if (memcmp(createParams, &mCreateParams, checkSize)) {
|
|
mCreateParams = *createParams;
|
|
mNeedRecreatePlayer = true;
|
|
} else {
|
|
mNeedRecreatePlayer = false;
|
|
}
|
|
|
|
|
|
MLOGI("Set mCreateParams drmmode:%s, options:%#" PRIx64 ", mNeedRecreatePlayer:%d", mpInputStreamType2Str(mCreateParams.drmMode), mCreateParams.options, mNeedRecreatePlayer);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_TUNNEL_ID:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mVideoTunnelId = *(int*)parameter;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_SURFACE_HANDLE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
MLOGI("set surface handle: %p", parameter);
|
|
mSurfaceHandle = parameter;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_PRESENTATION_ID:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioPresentationId = *(int*)parameter;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_USE_TIF:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mUseTif = *(bool*)parameter;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_TELETEXT_CONTROL:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mTeletextCtrlParam = *(AML_MP_TeletextCtrlParam*)parameter;
|
|
MLOGW("ttx key: %s", mpPlayerParameterKey2Str(key));
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_SPDIF_PROTECTION:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mSPDIFStatus = *(int*)parameter;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_CROP:
|
|
{
|
|
Aml_MP_Rect videoCrop = *(Aml_MP_Rect*)parameter;
|
|
mVideoCrop = videoCrop;
|
|
MLOGI("set source crop:[%d, %d, %d, %d]", mVideoCrop.left, mVideoCrop.top, mVideoCrop.right, mVideoCrop.bottom);
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_ERROR_RECOVERY_MODE:
|
|
{
|
|
mVideoErrorRecoveryMode = *(Aml_MP_VideoErrorRecoveryMode*)parameter;
|
|
MLOGI("Set error recovery mode: %s", mpVideoErrorRecoveryMode2Str(mVideoErrorRecoveryMode));
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_AFD_ASPECT_MODE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
int ret = 0;
|
|
mVideoAFDAspectMode = *(Aml_MP_VideoAFDAspectMode*)parameter;
|
|
MLOGI("Set video AFD aspect mode: %s", mpVideoAFDAspectMode2Str(mVideoAFDAspectMode));
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
ret = setVideoAFDAspectMode_l(mVideoAFDAspectMode);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_LANGUAGE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioLanguage = *(Aml_MP_AudioLanguage*)parameter;
|
|
MLOGI("set audio language:[%#x, %#x]", mAudioLanguage.firstLanguage, mAudioLanguage.secondLanguage);
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_BLOCK_ALIGN:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioBlockAlign = *(int*)parameter;
|
|
MLOGI("set audio block_align:%d", mAudioBlockAlign);
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_PATCH_ADDRESS:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioPatchAddress = *(int*)parameter;
|
|
MLOGI("set audio patch address:%d", mAudioPatchAddress);
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_CACHE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mVideoCache = *(int*)parameter;
|
|
MLOGI("set VideoCache:%d", mVideoCache);
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_AUDIO_CACHE:
|
|
{
|
|
RETURN_IF(-1, parameter == nullptr);
|
|
mAudioCache = *(int*)parameter;
|
|
MLOGI("set mAudioCache:%d", mAudioCache);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MLOGW("unhandled key: %s", mpPlayerParameterKey2Str(key));
|
|
return ret;
|
|
}
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
ret = mPlayer->setParameter(key, parameter);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getParameter(Aml_MP_PlayerParameterKey key, void* parameter)
|
|
{
|
|
AML_MP_TRACE(10);
|
|
bool locked = false;
|
|
|
|
//allow call getParameter in event callback.
|
|
pid_t eventCbTid = mEventCbTid.load(std::memory_order_relaxed);
|
|
if (eventCbTid == -1 || eventCbTid != gettid()) {
|
|
mLock.lock();
|
|
locked = true;
|
|
} else {
|
|
MLOGW("don't acquire lock for inner event cb thread!");
|
|
}
|
|
|
|
int ret = -1;
|
|
if (mPlayer != nullptr) {
|
|
ret = mPlayer->getParameter(key, parameter);
|
|
}
|
|
|
|
if (ret != AML_MP_OK) {
|
|
switch (key) {
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_SHOW_STATE:
|
|
{
|
|
*static_cast<bool*>(parameter) = mVideoShowState;
|
|
ret = AML_MP_OK;
|
|
break;
|
|
}
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_TUNNEL_ID:
|
|
{
|
|
*static_cast<int*>(parameter) = mVideoTunnelId;
|
|
ret = AML_MP_OK;
|
|
break;
|
|
}
|
|
|
|
case AML_MP_PLAYER_PARAMETER_VIDEO_AFD_ASPECT_MODE:
|
|
{
|
|
*static_cast<Aml_MP_VideoAFDAspectMode*>(parameter) = mVideoAFDAspectMode;
|
|
ret = AML_MP_OK;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (locked) {
|
|
mLock.unlock();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setAVSyncSource(Aml_MP_AVSyncSource syncSource)
|
|
{
|
|
MLOGI("%s ! mSyncSource %d", __FUNCTION__, syncSource);
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
mSyncSource = syncSource;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setPcrPid(int pid)
|
|
{
|
|
MLOGI("%s ! setPcrPid %d", __FUNCTION__, pid);
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
mPcrPid = pid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startVideoDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
return startVideoDecoding_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startVideoDecoding_l()
|
|
{
|
|
MLOG();
|
|
|
|
if (mState == STATE_IDLE) {
|
|
if (prepare_l() < 0) {
|
|
MLOGE("prepare failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_START_PENDING);
|
|
return 0;
|
|
}
|
|
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID) {
|
|
mPlayer->setVideoParams(&mVideoParams);
|
|
}
|
|
|
|
int ret = mPlayer->startVideoDecoding();
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_RUNNING);
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopVideoDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
MLOGI("stopVideoDecoding\n");
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
if (mPlayer) {
|
|
mPlayer->stopVideoDecoding();
|
|
}
|
|
}
|
|
|
|
// ensure stream stopped if mState isn't running or paused
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_VIDEO, AML_MP_DECODING_STATE_STOPPED);
|
|
|
|
int ret = resetIfNeeded_l(lock);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startAudioDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOGI("startAudioDecoding\n");
|
|
return startAudioDecoding_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startAudioDecoding_l()
|
|
{
|
|
MLOG();
|
|
int ret;
|
|
|
|
if (mState == STATE_IDLE) {
|
|
if (prepare_l() < 0) {
|
|
MLOGE("prepare failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_START_PENDING);
|
|
return 0;
|
|
}
|
|
|
|
if (mAudioParams.pid == AML_MP_INVALID_PID) {
|
|
return 0;
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_STARTED) {
|
|
resetADCodec_l(false);
|
|
}
|
|
|
|
mPlayer->setAudioParams(&mAudioParams);
|
|
setAudioCoreParameters_l();
|
|
|
|
ret = mPlayer->startAudioDecoding();
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_RUNNING);
|
|
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STARTED);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopAudioDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
return stopAudioDecoding_l(lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopAudioDecoding_l(std::unique_lock<std::mutex>& lock)
|
|
{
|
|
MLOG();
|
|
int ret;
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
mAudioPresentationId = -1;
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
if (mPlayer) {
|
|
mPlayer->stopAudioDecoding();
|
|
|
|
Aml_MP_AudioParams dummyAudioParam;
|
|
memset(&dummyAudioParam, 0, sizeof(dummyAudioParam));
|
|
dummyAudioParam.pid = AML_MP_INVALID_PID;
|
|
dummyAudioParam.audioCodec = AML_MP_CODEC_UNKNOWN;
|
|
mPlayer->setAudioParams(&dummyAudioParam);
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_STARTED) {
|
|
resetADCodec_l(true);
|
|
}
|
|
}
|
|
|
|
// ensure stream stopped if mState isn't running or paused
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AUDIO, AML_MP_DECODING_STATE_STOPPED);
|
|
mAudioStoppedInSwitching = false;
|
|
|
|
ret = resetIfNeeded_l(lock);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startADDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
return startADDecoding_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startADDecoding_l()
|
|
{
|
|
MLOG();
|
|
int ret;
|
|
|
|
if (mState == STATE_IDLE) {
|
|
if (prepare_l() < 0) {
|
|
MLOGE("prepare failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_START_PENDING);
|
|
return 0;
|
|
}
|
|
|
|
if (mADParams.pid == AML_MP_INVALID_PID) {
|
|
return 0;
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_STARTED) {
|
|
resetAudioCodec_l(false);
|
|
}
|
|
|
|
mPlayer->setADParams(&mADParams, true);
|
|
|
|
ret = mPlayer->startADDecoding();
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_RUNNING);
|
|
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_STARTED);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopADDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
return stopADDecoding_l(lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopADDecoding_l(std::unique_lock<std::mutex>& lock)
|
|
{
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
int ret;
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
if (mPlayer) {
|
|
mPlayer->stopADDecoding();
|
|
|
|
Aml_MP_AudioParams dummyADParam;
|
|
memset(&dummyADParam, 0, sizeof(dummyADParam));
|
|
dummyADParam.pid = AML_MP_INVALID_PID;
|
|
dummyADParam.audioCodec = AML_MP_CODEC_UNKNOWN;
|
|
mPlayer->setADParams(&dummyADParam, false);
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_STARTED) {
|
|
resetAudioCodec_l(true);
|
|
}
|
|
}
|
|
|
|
// ensure stream stopped if mState isn't running or paused
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_AD, AML_MP_DECODING_STATE_STOPPED);
|
|
|
|
ret = resetIfNeeded_l(lock);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startSubtitleDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
return startSubtitleDecoding_l();
|
|
}
|
|
|
|
int AmlMpPlayerImpl::startSubtitleDecoding_l()
|
|
{
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) == AML_MP_DECODING_STATE_STARTED) {
|
|
MLOGE("subtitle already started!");
|
|
return 0;
|
|
}
|
|
|
|
if (mState == STATE_IDLE) {
|
|
if (prepare_l() < 0) {
|
|
MLOGE("prepare failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mState == STATE_PREPARING) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_START_PENDING);
|
|
return 0;
|
|
}
|
|
|
|
if (mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
mPlayer->setSubtitleParams(&mSubtitleParams);
|
|
}
|
|
|
|
int ret = mPlayer->startSubtitleDecoding();
|
|
if (ret < 0) {
|
|
MLOGE("%s failed!", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
setState_l(STATE_RUNNING);
|
|
if (mSubtitleParams.subtitleCodec != AML_MP_CODEC_UNKNOWN) {
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STARTED);
|
|
}
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopSubtitleDecoding()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mLock);
|
|
|
|
return stopSubtitleDecoding_l(lock);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::stopSubtitleDecoding_l(std::unique_lock<std::mutex>& lock) {
|
|
MLOG();
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) == AML_MP_DECODING_STATE_STOPPED) {
|
|
MLOGE("subtitle already stopped!");
|
|
return 0;
|
|
}
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
if (mPlayer) {
|
|
mPlayer->stopSubtitleDecoding();
|
|
}
|
|
}
|
|
|
|
// ensure stream stopped if mState isn't running or paused
|
|
setDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE, AML_MP_DECODING_STATE_STOPPED);
|
|
|
|
int ret = resetIfNeeded_l(lock);
|
|
|
|
MLOG("end");
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setSubtitleWindow(int x, int y, int width, int height)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
MLOG("subtitle window:(%d %d %d %d)", x, y, width, height);
|
|
|
|
mSubtitleWindow = {x, y, width, height};
|
|
int ret = 0;
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
ret = mPlayer->setSubtitleWindow(x, y, width, height);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
//internal function
|
|
int AmlMpPlayerImpl::startDescrambling_l()
|
|
{
|
|
int ret = -1;
|
|
if (mCasServiceType == AML_MP_CAS_SERVICE_TYPE_INVALID) {
|
|
MLOGE("unknown cas type!");
|
|
return -1;
|
|
}
|
|
#ifndef BUILD_AMP_SERVER
|
|
#ifndef __ANDROID_RECOVERY__
|
|
if (mCasHandle == nullptr) {
|
|
mCasHandle = AmlCasBase::create(mCasServiceType);
|
|
}
|
|
#endif
|
|
|
|
if (mCasHandle == nullptr) {
|
|
MLOGE("create CAS handle failed!");
|
|
return -1;
|
|
}
|
|
|
|
ret = mCasHandle->startDescrambling(&mIptvCasParams);
|
|
#ifndef __ANDROID_RECOVERY__
|
|
mCasHandle->getEcmPids(mEcmPids);
|
|
#endif
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
//internal function
|
|
int AmlMpPlayerImpl::stopDescrambling_l()
|
|
{
|
|
#ifndef BUILD_AMP_SERVER
|
|
if (mCasHandle) {
|
|
mCasHandle->stopDescrambling();
|
|
mCasHandle.clear();
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
const char* AmlMpPlayerImpl::stateString(State state)
|
|
{
|
|
switch (state) {
|
|
case STATE_IDLE:
|
|
return "STATE_IDLE";
|
|
case STATE_PREPARING:
|
|
return "STATE_PREPARING";
|
|
case STATE_PREPARED:
|
|
return "STATE_PREPARED";
|
|
case STATE_RUNNING:
|
|
return "STATE_RUNNING";
|
|
case STATE_PAUSED:
|
|
return "STATE_PAUSED";
|
|
case STATE_STOPPED:
|
|
return "STATE_STOPPED";
|
|
default:
|
|
break;
|
|
}
|
|
return "STATE_IDLE";
|
|
}
|
|
|
|
std::string AmlMpPlayerImpl::decodingStateString(uint32_t streamState)
|
|
{
|
|
AML_MP_UNUSED(streamState);
|
|
std::stringstream ss;
|
|
bool hasValue = false;
|
|
|
|
for (size_t i = 0; i < AML_MP_STREAM_TYPE_NB; ++i) {
|
|
int value = (mStreamState >> i*kStreamStateBits) & kStreamStateMask;
|
|
|
|
if (value != AML_MP_DECODING_STATE_STOPPED) {
|
|
if (hasValue) ss << "|";
|
|
|
|
ss << mpStreamType2Str((Aml_MP_StreamType)i);
|
|
switch (value) {
|
|
case AML_MP_DECODING_STATE_START_PENDING:
|
|
ss << "_START_PENDING";
|
|
break;
|
|
|
|
case AML_MP_DECODING_STATE_STARTED:
|
|
ss << "_STARTED";
|
|
break;
|
|
|
|
case AML_MP_DECODING_STATE_PAUSED:
|
|
ss << "_PAUSED";
|
|
break;
|
|
|
|
//coverity[dead_error_begin]
|
|
default:
|
|
break;
|
|
}
|
|
hasValue = true;
|
|
}
|
|
}
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
void AmlMpPlayerImpl::setState_l(State state)
|
|
{
|
|
if (mState != state) {
|
|
MLOGI("state: %s -> %s", stateString(mState), stateString(state));
|
|
mLastState = mState;
|
|
mState = state;
|
|
}
|
|
}
|
|
|
|
void AmlMpPlayerImpl::setDecodingState_l(Aml_MP_StreamType streamType, int state)
|
|
{
|
|
unsigned int offset = streamType * kStreamStateBits;
|
|
|
|
if (offset >= sizeof(mStreamState) * 8) {
|
|
MLOGE("streamType(%s) is overflow!", mpStreamType2Str(streamType));
|
|
return;
|
|
}
|
|
|
|
mStreamState &= ~(kStreamStateMask<<offset);
|
|
mStreamState |= state<<offset;
|
|
}
|
|
|
|
AML_MP_DecodingState AmlMpPlayerImpl::getDecodingState_l(Aml_MP_StreamType streamType)
|
|
{
|
|
int offset = streamType * kStreamStateBits;
|
|
|
|
return AML_MP_DecodingState((mStreamState >> offset) & kStreamStateMask);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::prepare_l()
|
|
{
|
|
MLOG();
|
|
|
|
if (mCreateParams.drmMode != AML_MP_INPUT_STREAM_NORMAL) {
|
|
if (!mIsStandaloneCas) {
|
|
startDescrambling_l();
|
|
}
|
|
|
|
// in trick mode play, ecm may not match ts packets well
|
|
// if use asynchronous wait ecm mode,
|
|
// because inject speed is faster than normal play,
|
|
// so force change to synchronous mode here.
|
|
if (mVideoDecodeMode == AML_MP_VIDEO_DECODE_MODE_IONLY) {
|
|
mWaitingEcmMode = kWaitingEcmSynchronous;
|
|
} else {
|
|
mWaitingEcmMode = mUserWaitingEcmMode;
|
|
}
|
|
}
|
|
|
|
if (mPlayer == nullptr || mNeedRecreatePlayer) {
|
|
mPlayer.clear();
|
|
mPlayer = AmlPlayerBase::create(&mCreateParams, mInstanceId);
|
|
if (mPlayer == nullptr) {
|
|
MLOGE("AmlPlayerBase create failed!");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (mPlayer->initCheck() != AML_MP_OK) {
|
|
MLOGE("AmlPlayerBase initCheck failed!");
|
|
return -1;
|
|
}
|
|
mNeedRecreatePlayer = false;
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mWorkMode >= 0) {
|
|
MLOGI("mWorkMode: %s", mpPlayerWorkMode2Str(mWorkMode));
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_WORK_MODE, &mWorkMode);
|
|
}
|
|
|
|
mPlayer->registerEventCallback([](void* userData, Aml_MP_PlayerEventType event, int64_t param) {
|
|
AmlMpPlayerImpl* thiz = static_cast<AmlMpPlayerImpl*>(userData);
|
|
return thiz->notifyListener(event, param);
|
|
}, this);
|
|
|
|
tryMonitorPidChange_l();
|
|
bool ret1 = tryWaitCodecId_l();
|
|
bool ret2 = tryWaitEcm_l();
|
|
if (ret1 || ret2) {
|
|
setState_l(STATE_PREPARING);
|
|
return 0;
|
|
}
|
|
|
|
applyParameters_l();
|
|
|
|
setState_l(STATE_PREPARED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::openParser_l() {
|
|
if (mParser == nullptr) {
|
|
mParser = new Parser(mCreateParams.demuxId, mCreateParams.sourceType == AML_MP_INPUT_SOURCE_TS_DEMOD, AML_MP_DEMUX_TYPE_HARDWARE, mCreateParams.drmMode == AML_MP_INPUT_STREAM_SECURE_MEMORY);
|
|
mParser->open();
|
|
mParser->selectProgram(mVideoParams.pid, mAudioParams.pid);
|
|
mParser->setEventCallback([this] (Parser::ProgramEventType event, int param1, int param2, void* data) {
|
|
return programEventCallback(event, param1, param2, data);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool AmlMpPlayerImpl::tryWaitEcm_l() {
|
|
bool isNeedWaitEcm = false;
|
|
if (mCreateParams.sourceType == AML_MP_INPUT_SOURCE_TS_MEMORY &&
|
|
mCreateParams.drmMode == AML_MP_INPUT_STREAM_ENCRYPTED &&
|
|
(mCasHandle || mWaitingEcmByServer)) {
|
|
for (size_t i = 0; i < mEcmPids.size(); ++i) {
|
|
int ecmPid = mEcmPids[i];
|
|
if (ecmPid > 0 && ecmPid < AML_MP_INVALID_PID) {
|
|
isNeedWaitEcm = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isNeedWaitEcm) {
|
|
return false;
|
|
}
|
|
|
|
resetDrmVariables_l();
|
|
|
|
if (mWaitingEcmMode != kWaitingEcmASynchronous) {
|
|
return false;
|
|
}
|
|
|
|
openParser_l();
|
|
int lastEcmPid = AML_MP_INVALID_PID;
|
|
for (size_t i = 0; i < mEcmPids.size(); ++i) {
|
|
int ecmPid = mEcmPids[i];
|
|
if (ecmPid <= 0 ||ecmPid >= AML_MP_INVALID_PID) {
|
|
continue;
|
|
}
|
|
|
|
if (ecmPid != lastEcmPid) {
|
|
mParser->removeSectionFilter(ecmPid);
|
|
mParser->addSectionFilter(ecmPid, [] (int pid, size_t size, const uint8_t* data, void* userData) -> int {
|
|
AmlMpPlayerImpl* playerImpl = (AmlMpPlayerImpl*)userData;
|
|
if (playerImpl) {
|
|
playerImpl->programEventCallback(Parser::ProgramEventType::EVENT_ECM_DATA_PARSED, pid, size, (void*)data);
|
|
}
|
|
return 0;
|
|
}, this, false);
|
|
lastEcmPid = ecmPid;
|
|
}
|
|
}
|
|
|
|
if (lastEcmPid == AML_MP_INVALID_PID) {
|
|
MLOGE("no valid ecm pid!");
|
|
}
|
|
mPrepareWaitingType |= kPrepareWaitingEcm;
|
|
return true;
|
|
}
|
|
|
|
bool AmlMpPlayerImpl::tryWaitCodecId_l() {
|
|
if (mCreateParams.sourceType != AML_MP_INPUT_SOURCE_ES_MEMORY &&
|
|
((mVideoParams.videoCodec == AML_MP_CODEC_UNKNOWN && mVideoParams.pid != AML_MP_INVALID_PID) ||
|
|
(mAudioParams.audioCodec == AML_MP_CODEC_UNKNOWN && mAudioParams.pid != AML_MP_INVALID_PID) ||
|
|
(mADParams.audioCodec == AML_MP_CODEC_UNKNOWN && mADParams.pid != AML_MP_INVALID_PID))) {
|
|
openParser_l();
|
|
mParser->parseProgramInfoAsync();
|
|
mPrepareWaitingType |= kPrepareWaitingCodecId;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AmlMpPlayerImpl::tryMonitorPidChange_l() {
|
|
if (mCreateParams.options & AML_MP_OPTION_MONITOR_PID_CHANGE) {
|
|
openParser_l();
|
|
mParser->parseProgramInfoAsync();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::programEventCallback(Parser::ProgramEventType event, int param1, int param2, void* data)
|
|
{
|
|
AML_MP_UNUSED(param1);
|
|
switch (event)
|
|
{
|
|
case Parser::ProgramEventType::EVENT_PROGRAM_PARSED:
|
|
{
|
|
ProgramInfo* programInfo = (ProgramInfo*)data;
|
|
MLOGI("programEventCallback: program(programNumber=%d,pid= 0x%x) parsed", programInfo->programNumber, programInfo->pmtPid);
|
|
programInfo->debugLog();
|
|
|
|
std::lock_guard<std::mutex> _l(mLock);
|
|
for (auto it : programInfo->videoStreams) {
|
|
if (it.pid == mVideoParams.pid) {
|
|
mVideoParams.videoCodec = it.codecId;
|
|
}
|
|
}
|
|
|
|
for (auto it : programInfo->audioStreams) {
|
|
if (it.pid == mAudioParams.pid) {
|
|
mAudioParams.audioCodec = it.codecId;
|
|
}
|
|
}
|
|
|
|
mPrepareWaitingType &= ~kPrepareWaitingCodecId;
|
|
finishPreparingIfNeeded_l();
|
|
|
|
break;
|
|
}
|
|
case Parser::ProgramEventType::EVENT_AV_PID_CHANGED:
|
|
{
|
|
Aml_MP_PlayerEventPidChangeInfo* info = (Aml_MP_PlayerEventPidChangeInfo*)data;
|
|
MLOGI("programEventCallback: program(programNumber=%d,pid= 0x%x) pidchangeInfo: oldPid: 0x%x --> newPid: 0x%x",
|
|
info->programNumber, info->programPid, info->oldStreamPid, info->newStreamPid);
|
|
notifyListener(AML_MP_PLAYER_EVENT_PID_CHANGED, (uint64_t)data);
|
|
break;
|
|
}
|
|
case Parser::ProgramEventType::EVENT_ECM_DATA_PARSED:
|
|
{
|
|
MLOGI("programEventCallback: ecmData parsed, size:%d", param2);
|
|
uint8_t* ecmData = (uint8_t*)data;
|
|
std::string ecmDataStr;
|
|
char hex[3];
|
|
for (int i = 0; i < param2; i++) {
|
|
snprintf(hex, sizeof(hex), "%02X", ecmData[i]);
|
|
ecmDataStr.append(hex);
|
|
ecmDataStr.append(" ");
|
|
}
|
|
MLOGI("programEventCallback: ecmPid: %d, ecmData: size:%d, hexStr:%s", param1, param2, ecmDataStr.c_str());
|
|
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
if ((mCasHandle || mWaitingEcmByServer) && mWaitingEcmMode == kWaitingEcmASynchronous) {
|
|
#ifdef BUILD_AMP_SERVER
|
|
NotifyEcmData(true, param1, ecmData, param2);
|
|
#else
|
|
mCasHandle->processEcm(true, param1, ecmData, param2);
|
|
#endif
|
|
}
|
|
mPrepareWaitingType &= ~kPrepareWaitingEcm;
|
|
finishPreparingIfNeeded_l();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int AmlMpPlayerImpl::finishPreparingIfNeeded_l()
|
|
{
|
|
if (mState != STATE_PREPARING || mPrepareWaitingType != kPrepareWaitingNone) {
|
|
return 0;
|
|
}
|
|
|
|
bool needPause = (mLastState == STATE_PAUSED);
|
|
|
|
applyParameters_l();
|
|
setState_l(STATE_PREPARED);
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_VIDEO) == AML_MP_DECODING_STATE_START_PENDING) {
|
|
startVideoDecoding_l();
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AUDIO) == AML_MP_DECODING_STATE_START_PENDING) {
|
|
startAudioDecoding_l();
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_SUBTITLE) == AML_MP_DECODING_STATE_START_PENDING) {
|
|
startSubtitleDecoding_l();
|
|
if (mSubtitleParams.subtitleCodec == AML_MP_SUBTITLE_CODEC_TELETEXT && mTeletextCtrlParam.event != AML_MP_TT_EVENT_INVALID) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_TELETEXT_CONTROL, &mTeletextCtrlParam);
|
|
}
|
|
}
|
|
|
|
if (getDecodingState_l(AML_MP_STREAM_TYPE_AD) == AML_MP_DECODING_STATE_START_PENDING) {
|
|
startADDecoding_l();
|
|
}
|
|
|
|
if (needPause) {
|
|
pause_l();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::resetIfNeeded_l(std::unique_lock<std::mutex>& lock, bool clearCasSession)
|
|
{
|
|
if (mStreamState == 0) {
|
|
setState_l(STATE_STOPPED);
|
|
} else {
|
|
MLOGI("current decodingState:%s", decodingStateString(mStreamState).c_str());
|
|
}
|
|
|
|
if (mState == STATE_STOPPED) {
|
|
reset_l(lock, clearCasSession);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::reset_l(std::unique_lock<std::mutex>& lock, bool clearCasSession)
|
|
{
|
|
MLOG();
|
|
if (mParser) {
|
|
lock.unlock();
|
|
mParser->close();
|
|
lock.lock();
|
|
|
|
}
|
|
|
|
mParser.clear();
|
|
|
|
if (!(mCreateParams.options & AML_MP_OPTION_RETAIN_RESOURCE_IN_STOP)) {
|
|
mPlayer.clear();
|
|
} else {
|
|
MLOGI("retain mPlayer handle!");
|
|
}
|
|
|
|
if (!mIsStandaloneCas) {
|
|
stopDescrambling_l();
|
|
}
|
|
|
|
if (clearCasSession) {
|
|
mIsStandaloneCas = false;
|
|
#ifndef BUILD_AMP_SERVER
|
|
mCasHandle.clear();
|
|
#endif
|
|
}
|
|
|
|
resetVariables_l();
|
|
|
|
setState_l(STATE_IDLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::applyParameters_l()
|
|
{
|
|
MLOG();
|
|
|
|
if ((mSubtitleWindow.width >= 0) && (mSubtitleWindow.height >= 0)) {
|
|
mPlayer->setSubtitleWindow(mSubtitleWindow.x, mSubtitleWindow.y, mSubtitleWindow.width, mSubtitleWindow.height);
|
|
}
|
|
|
|
if (mSurfaceHandle != nullptr) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_SURFACE_HANDLE, mSurfaceHandle);
|
|
}
|
|
#ifdef ANDROID
|
|
#ifndef __ANDROID_RECOVERY__
|
|
MLOGI("mNativeWindow:%p", mNativeWindow.get());
|
|
if (mNativeWindow != nullptr) {
|
|
mPlayer->setANativeWindow(mNativeWindow.get());
|
|
setSidebandIfNeeded_l();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if (mVideoWindow.width >= 0 && mVideoWindow.height >= 0) {
|
|
mPlayer->setVideoWindow(mVideoWindow.x, mVideoWindow.y, mVideoWindow.width, mVideoWindow.height);
|
|
}
|
|
|
|
if (mVideoTunnelId >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_TUNNEL_ID, &mVideoTunnelId);
|
|
}
|
|
|
|
if (mSyncSource == AML_MP_AVSYNC_SOURCE_DEFAULT) {
|
|
mSyncSource = AML_MP_AVSYNC_SOURCE_PCR;
|
|
}
|
|
|
|
Aml_MP_AVSyncSource tmpSyncSource = mSyncSource;
|
|
|
|
//If the middleware only calls the setparameter(decodeMode) interface during speed playback,
|
|
//we need to put tsplayerrender in freerun state
|
|
if (mVideoDecodeMode == AML_MP_VIDEO_DECODE_MODE_IONLY) {
|
|
tmpSyncSource = AML_MP_AVSYNC_SOURCE_NOSYNC;
|
|
} else if (mVideoDecodeMode == AML_MP_VIDEO_DECODE_MODE_IONLY_SYNC) {
|
|
tmpSyncSource = AML_MP_AVSYNC_SOURCE_VIDEO;
|
|
}
|
|
|
|
mPlayer->setAVSyncSource(tmpSyncSource);
|
|
|
|
if (mSyncSource == AML_MP_AVSYNC_SOURCE_PCR && mPcrPid != AML_MP_INVALID_PID) {
|
|
mPlayer->setPcrPid(mPcrPid);
|
|
}
|
|
|
|
mPlayer->setPlaybackRate(mPlaybackRate);
|
|
|
|
if (mVolume >= 0) {
|
|
mPlayer->setVolume(mVolume);
|
|
}
|
|
|
|
if (mADVolume >= 0) {
|
|
mPlayer->setADVolume(mADVolume);
|
|
}
|
|
|
|
if (mVideoAFDAspectMode >= 0) {
|
|
setVideoAFDAspectMode_l(mVideoAFDAspectMode);
|
|
}
|
|
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_DECODE_MODE, &mVideoDecodeMode);
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mVideoDisplayMode >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_DISPLAY_MODE, &mVideoDisplayMode);
|
|
}
|
|
|
|
if (mBlackOut >= 0) {
|
|
bool param = (0 == mBlackOut)?false:true;
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_BLACK_OUT, ¶m);
|
|
}
|
|
|
|
if (mVideoPtsOffset >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_PTS_OFFSET, &mVideoPtsOffset);
|
|
}
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mAudioOutputMode >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_OUTPUT_MODE, &mAudioOutputMode);
|
|
}
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mAudioOutputDevice >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_OUTPUT_DEVICE, &mAudioOutputDevice);
|
|
}
|
|
|
|
if (mAudioPtsOffset >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_PTS_OFFSET, &mAudioPtsOffset);
|
|
}
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mAudioBalance >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_BALANCE, &mAudioBalance);
|
|
}
|
|
|
|
if (mAudioMute >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_MUTE, &mAudioMute);
|
|
}
|
|
|
|
if (mNetworkJitter >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_NETWORK_JITTER, &mNetworkJitter);
|
|
}
|
|
|
|
//coverity[unsigned_compare]
|
|
if (mVideoErrorRecoveryMode >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_ERROR_RECOVERY_MODE, &mVideoErrorRecoveryMode);
|
|
}
|
|
|
|
if (mADMixLevel.masterVolume >= 0 && mADMixLevel.slaveVolume >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AD_MIX_LEVEL, &mADMixLevel);
|
|
}
|
|
|
|
if (mUseTif != -1) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_USE_TIF, &mUseTif);
|
|
}
|
|
|
|
if (mVideoCrop.right >= 0 && mVideoCrop.bottom >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_CROP, &mVideoCrop);
|
|
}
|
|
|
|
if (mAudioLanguage.firstLanguage != 0 || mAudioLanguage.secondLanguage != 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_LANGUAGE, &mAudioLanguage);
|
|
}
|
|
|
|
if (mVideoCache >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_VIDEO_CACHE, &mVideoCache);
|
|
}
|
|
|
|
if (mAudioCache >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_CACHE, &mAudioCache);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::setAudioCoreParameters_l()
|
|
{
|
|
if (mAudioPresentationId >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_PRESENTATION_ID, &mAudioPresentationId);
|
|
}
|
|
|
|
if (mSPDIFStatus != -1) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_SPDIF_PROTECTION, &mSPDIFStatus);
|
|
}
|
|
|
|
if (mAudioBlockAlign > 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_BLOCK_ALIGN, &mAudioBlockAlign);
|
|
}
|
|
|
|
if (mAudioPatchAddress >= 0) {
|
|
mPlayer->setParameter(AML_MP_PLAYER_PARAMETER_AUDIO_PATCH_ADDRESS, &mAudioPatchAddress);
|
|
}
|
|
}
|
|
|
|
void AmlMpPlayerImpl::notifyListener(Aml_MP_PlayerEventType eventType, int64_t param)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mEventLock);
|
|
mEventCbTid = gettid();
|
|
|
|
if (mEventCb) {
|
|
mEventCb(mUserData, eventType, param);
|
|
} else {
|
|
MLOGW("mEventCb is NULL, eventType: %s, param:%" PRId64, mpPlayerEventType2Str(eventType), param);
|
|
}
|
|
|
|
mEventCbTid = -1;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::resetADCodec_l(bool callStart)
|
|
{
|
|
int ret = 0;
|
|
MLOG("callStart:%d", callStart);
|
|
|
|
ret = mPlayer->stopADDecoding();
|
|
if (ret < 0) {
|
|
MLOGW("stopADDecoding failed while resetADCodec");
|
|
}
|
|
|
|
if (mADParams.pid != AML_MP_INVALID_PID) {
|
|
mPlayer->setADParams(&mADParams, true);
|
|
}
|
|
|
|
if (callStart) {
|
|
ret = mPlayer->startADDecoding();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::resetAudioCodec_l(bool callStart)
|
|
{
|
|
int ret = 0;
|
|
MLOG("callStart:%d", callStart);
|
|
|
|
ret = mPlayer->stopAudioDecoding();
|
|
if (ret < 0) {
|
|
MLOGW("stopAudioDecoding failed while resetAudioCodec");
|
|
}
|
|
|
|
if (mAudioParams.pid != AML_MP_INVALID_PID) {
|
|
mPlayer->setAudioParams(&mAudioParams);
|
|
setAudioCoreParameters_l();
|
|
}
|
|
|
|
if (callStart) {
|
|
ret = mPlayer->startAudioDecoding();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::switchDecodeMode_l(Aml_MP_VideoDecodeMode decodeMode, std::unique_lock<std::mutex>& lock) {
|
|
|
|
int ret = 0;
|
|
MLOG("decodeMode: %s", mpVideoDecodeMode2Str(decodeMode));
|
|
|
|
if (mVideoDecodeMode == decodeMode) {
|
|
MLOGI("Decode mode not change, no need do process");
|
|
return 0;
|
|
}
|
|
|
|
mVideoDecodeMode = decodeMode;
|
|
|
|
if (mState != STATE_RUNNING) {
|
|
MLOGI("Now not in play, no Need to do process");
|
|
return 0;
|
|
}
|
|
|
|
//Not clear cas session while switch decode mode
|
|
stop_l(lock, false);
|
|
//When start video decoding will set work mode to player, no need to set here
|
|
if (AML_MP_VIDEO_DECODE_MODE_NONE != mVideoDecodeMode) {
|
|
ret = startVideoDecoding_l();
|
|
} else {
|
|
ret = start_l();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::statisticWriteDataRate_l(size_t size)
|
|
{
|
|
mLastBytesWritten += size;
|
|
|
|
int64_t nowUs = AmlMpEventLooper::GetNowUs();
|
|
if (mLastWrittenTimeUs == 0) {
|
|
mLastWrittenTimeUs = nowUs;
|
|
} else {
|
|
int64_t diffUs = nowUs - mLastWrittenTimeUs;
|
|
if (diffUs > 2 * 1000000ll) {
|
|
int64_t bitrate = mLastBytesWritten * 1000000 / diffUs;
|
|
MLOGI("writeData rate:%.2fKB/s", bitrate/1024.0);
|
|
|
|
mLastWrittenTimeUs = nowUs;
|
|
mLastBytesWritten = 0;
|
|
|
|
collectBuffingInfos_l();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AmlMpPlayerImpl::collectBuffingInfos_l()
|
|
{
|
|
//coverity[var_decl]
|
|
Aml_MP_BufferStat bufferStat;
|
|
mPlayer->getBufferStat(&bufferStat);
|
|
|
|
int64_t vpts = 0, apts = 0;
|
|
mPlayer->getCurrentPts(AML_MP_STREAM_TYPE_VIDEO, &vpts);
|
|
mPlayer->getCurrentPts(AML_MP_STREAM_TYPE_AUDIO, &apts);
|
|
|
|
if (mVideoParams.pid != AML_MP_INVALID_PID) {
|
|
MLOGI("Video(%#x) buffer stat:%d/%d, %.2fms, pts:%f", mVideoParams.pid, bufferStat.videoBuffer.dataLen, bufferStat.videoBuffer.size, bufferStat.videoBuffer.bufferedMs*1.0, vpts/9e4);
|
|
}
|
|
|
|
if (mAudioParams.pid != AML_MP_INVALID_PID) {
|
|
MLOGI("Audio(%#x) buffer stat:%d/%d, %.2fms, pts:%f", mAudioParams.pid, bufferStat.audioBuffer.dataLen, bufferStat.audioBuffer.size, bufferStat.audioBuffer.bufferedMs*1.0, apts/9e4);
|
|
}
|
|
}
|
|
|
|
void AmlMpPlayerImpl::resetVariables_l()
|
|
{
|
|
mLastBytesWritten = 0;
|
|
mLastWrittenTimeUs = 0;
|
|
mAudioStoppedInSwitching = false;
|
|
|
|
resetDrmVariables_l();
|
|
}
|
|
|
|
void AmlMpPlayerImpl::resetDrmVariables_l()
|
|
{
|
|
mFirstEcmWritten = false;
|
|
mTsBuffer.reset();
|
|
mWriteBuffer->setRange(0, 0);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::getDecodingState(Aml_MP_StreamType streamType, AML_MP_DecodingState* streamState) {
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
|
|
*streamState = getDecodingState_l(streamType);
|
|
|
|
return AML_MP_OK;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::increaseDmxSecMemSize() {
|
|
MLOGI("supportPIP=%d supportFCC=%d secMemSize=%d isCasProject=%d", AmlMpConfig::instance().mCasPipSupport,
|
|
AmlMpConfig::instance().mCasFCCSupport, AmlMpConfig::instance().mSecMemSize, AmlMpConfig::instance().mCasType != "none");
|
|
|
|
if (AmlMpConfig::instance().mCasType == "none") {
|
|
return ;
|
|
}
|
|
|
|
if (AmlMpConfig::instance().mCasFCCSupport) {
|
|
if (AmlMpConfig::instance().mSecMemSize < FCC_DEMUX_MEM_SEC_SIZE)
|
|
AmlMpConfig::instance().mSecMemSize = FCC_DEMUX_MEM_SEC_SIZE;
|
|
} else if (AmlMpConfig::instance().mCasPipSupport) {
|
|
if (AmlMpConfig::instance().mSecMemSize < PIP_DEMUX_MEM_SEC_SIZE)
|
|
AmlMpConfig::instance().mSecMemSize = PIP_DEMUX_MEM_SEC_SIZE;
|
|
} else {
|
|
return ;
|
|
}
|
|
|
|
char valuebuf[64] = {0};
|
|
sprintf(valuebuf, "%d %d", DEFAULT_DEMUX_MEM_SEC_LEVEL>>10, AmlMpConfig::instance().mSecMemSize);
|
|
|
|
int ret = amsysfs_set_sysfs_str("/sys/class/stb/dmc_mem", valuebuf);
|
|
MLOGI("dmc mem ret=%d level=%d value=%d", ret, DEFAULT_DEMUX_MEM_SEC_LEVEL,
|
|
AmlMpConfig::instance().mSecMemSize);
|
|
|
|
return;
|
|
}
|
|
|
|
void AmlMpPlayerImpl::recoverDmxSecMemSize() {
|
|
if (AmlMpConfig::instance().mCasType != "none"
|
|
&& (AmlMpConfig::instance().mCasFCCSupport || AmlMpConfig::instance().mCasPipSupport)) {
|
|
char valuebuf[64] = {0};
|
|
sprintf(valuebuf, "%d %d", DEFAULT_DEMUX_MEM_SEC_LEVEL>>10, DEFAULT_DEMUX_MEM_SEC_SIZE);
|
|
int ret = amsysfs_set_sysfs_str("/sys/class/stb/dmc_mem", valuebuf);
|
|
MLOGI("recover dmc mem ret=%d level=%d value=%d", ret,
|
|
DEFAULT_DEMUX_MEM_SEC_LEVEL, DEFAULT_DEMUX_MEM_SEC_SIZE);
|
|
}
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setADVolume(float volume) {
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
int ret = 0;
|
|
if (volume < 0) {
|
|
MLOGI("volume is %f, set to 0.0", volume);
|
|
volume = 0.0;
|
|
} else if (volume > 100) {
|
|
MLOGI("volume is %f, set to 100.0", volume);
|
|
volume = 100.0;
|
|
}
|
|
mADVolume = volume;
|
|
|
|
if (mState == STATE_RUNNING || mState == STATE_PAUSED) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
ret = mPlayer->setADVolume(volume);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int AmlMpPlayerImpl::getADVolume(float* volume)
|
|
{
|
|
std::unique_lock<std::mutex> _l(mLock);
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
|
|
return mPlayer->getADVolume(volume);
|
|
}
|
|
|
|
int AmlMpPlayerImpl::enableAFD_l(bool enable)
|
|
{
|
|
MLOGI("[%s] enable: %d", __FUNCTION__, enable);
|
|
|
|
int ret = 0;
|
|
char enableParam[64] = {0};
|
|
|
|
if (enable) {
|
|
RETURN_IF(-1, mPlayer == nullptr);
|
|
sprintf(enableParam, "%d %d %d", 0, mPlayer->getPlayerId(), 1);
|
|
} else {
|
|
sprintf(enableParam, "%d %d %d", 0, 0, 0);
|
|
}
|
|
ret = amsysfs_set_sysfs_str("/sys/class/afd_module/enable", enableParam);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int AmlMpPlayerImpl::setVideoAFDAspectMode_l(Aml_MP_VideoAFDAspectMode aspectMode)
|
|
{
|
|
int ret = 0;
|
|
char aspectModeParam[64] = {0};
|
|
|
|
if (aspectMode >= AML_MP_VIDEO_AFD_ASPECT_MODE_AUTO && aspectMode < AML_MP_VIDEO_AFD_ASPECT_MODE_MAX) {
|
|
ret = enableAFD_l(true);
|
|
sprintf(aspectModeParam, "%d %d", 0, aspectMode);
|
|
} else {
|
|
MLOGE("[%s] Invalid aspect mode: %d", __FUNCTION__, aspectMode);
|
|
ret = enableAFD_l(false);
|
|
sprintf(aspectModeParam, "%d %d", 0, AML_MP_VIDEO_AFD_ASPECT_MODE_NONE);
|
|
}
|
|
ret += amsysfs_set_sysfs_str("/sys/class/afd_module/aspect_mode", aspectModeParam);
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|