Files
2026-08-24 10:03:24 +08:00

1087 lines
36 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_TAG "AmlVMXIptvMediaCas"
#include <utils/AmlMpLog.h>
#include <utils/AmlMpUtils.h>
#include <utils/Amlsysfsutils.h>
#include <cutils/ashmem.h>
#include <cutils/properties.h>
#include "AmlVMXIptvMediaCas.h"
#ifndef VMX_CA_SYSTEM_ID
#define VMX_CA_SYSTEM_ID 0x5601
#endif
#define VMX_PROP_PRE_INIT_MEDIACAS_ENABLE "vendor.media.cas.vmx.pre.init.enable"
#define VMX_PROP_PROVISION_SAVE_SERVER_INFO_ENABLE "vendor.media.cas.vmx.save.server.info.enable"
#define VMX_PROP_PROVISION_SAVE_SERVER_INFO_PATH "vendor.media.cas.vmx.save.server.info.path"
#define VMX_PROP_PROVISION_SAVE_SERVER_INFO_DEFAULT "/data/misc/media/saved_provision_server_iptv.json"
#define VMX_PROP_PROVISION_SERVER_IP_PRIMARY "vendor.media.cas.vmx.iptv.serverip.primary"
#define VMX_PROP_PROVISION_SERVER_IP_SECONDARY "vendor.media.cas.vmx.iptv.serverip.secondary"
#define VMX_PROP_PROVISION_INVALID_VALUE " "
#define VMX_PROP_PROVISION_SERVER_IP_FORCE "vendor.media.cas.vmx.iptv.serverip.force"
#define VMX_PROP_PROVISION_SERVER_PORT "vendor.media.cas.vmx.iptv.serverport"
#define VMX_PROP_PROVISION_STORE_PATH "vendor.media.cas.vmx.iptv.storepath"
#define VMX_PROP_PROVISION_KID "vendor.media.cas.vmx.iptv.kid"
#define VMX_PROP_PROVISION_STB_ID "vendor.media.cas.vmx.iptv.stbid"
#define VMX_PROP_PROVISION_CHIP_ID "vendor.media.cas.vmx.iptv.chipid"
#define VMX_PROP_PROVISION_IS_ONLINE "vendor.media.cas.vmx.iptv.isonline"
#define VMX_PROP_PROVISION_TIMEOUT "vendor.media.cas.vmx.iptv.timeout"
#define VMX_PROP_PROVISION_RETRY_COUNT "vendor.media.cas.vmx.iptv.retrycount"
#define VMX_PROP_PROVISION_IP_VERSION "vendor.media.cas.vmx.iptv.ipversion"
#define VMX_PROP_PROVISION_SERVER_IP_PRIMARY_DEFAULT "client-test-3.verimatrix.com"
#define VMX_PROP_PROVISION_SERVER_IP_SECONDARY_DEFAULT "client-test-4.verimatrix.com"
#define VMX_PROP_PROVISION_SERVER_PORT_DEFAULT "12686"
#define VMX_PROP_PROVISION_STORE_PATH_DEFAULT "/data/vendor/mediacas/Verimatrix.store"
#define VMX_PROP_PROVISION_KID_DEFAULT ""
#define VMX_PROP_PROVISION_STB_ID_DEFAULT ""
#define VMX_PROP_PROVISION_CHIP_ID_DEFAULT ""
#define VMX_PROP_PROVISION_IS_ONLINE_DEFAULT "true"
#define VMX_PROP_PROVISION_TIMEOUT_DEFAULT "2"
#define VMX_PROP_PROVISION_RETRY_COUNT_DEFAULT "2"
#define VMX_PROP_PROVISION_IP_VERSION_DEFAULT "0"
static const char* mName = LOG_TAG;
#define RETURN_ERROR_IF_NULLPTR(ptr) do { \
if (ptr == nullptr) { \
MLOGE("func:%s, line:%d, %s is nullptr", __FUNCTION__, __LINE__, #ptr); \
return VMX_CAS_ERROR;} \
}while(0)
namespace aml_mp {
typedef enum
{
VMX_CAS_SUCCESS = 0,
VMX_CAS_ERROR = 1,
VMX_CAS_GET_SERVICE_FAIL = 2,
VMX_CAS_UNSUPPORT = 3,
VMX_CAS_CREATE_PLUGIN_FAIL = 4,
//provision status
VMX_CAS_PROVISION_SUC = 1010,
VMX_CAS_PROVISION_FAIL = 1011,
VMX_CAS_SERVER_UNAVAILABLE = 1012,
} VMXCasState;
typedef struct {
//create a thread to do initialization to optimize first play KPI
pthread_t initThread;
int initThreadExited;
std::mutex initThreadLock;
//provision server
std::string currentProvisionServer;
std::string primaryServer;
std::string secondaryServer;
//make sure there is only one initialization process
std::mutex initLock;
//event listener for vmx cas
shared_ptr<MediaCasListener> listener;
//vmx cas plugin
shared_ptr<ICas> iCas;
shared_ptr<IMediaCasService> casService;
SpAIBinder casBinder;
} VMXCasInitCtrl;
static VMXCasInitCtrl vmxCasInitCtrl = {
.initThread = 0,
.initThreadExited = 0,
.currentProvisionServer = VMX_PROP_PROVISION_INVALID_VALUE,
.primaryServer = VMX_PROP_PROVISION_INVALID_VALUE,
.secondaryServer = VMX_PROP_PROVISION_INVALID_VALUE,
.listener = nullptr,
.iCas = nullptr,
.casService = nullptr
};
inline unsigned int convertSessionIdtoInt(const std::vector<uint8_t> & sessionId)
{
unsigned int cnt = 0;
unsigned int result = 0;
for (auto d : sessionId) {
result = (d << (8 * cnt)) | result;
cnt++;
if (cnt >= 4) {
break;
}
}
return result;
}
static void getProperty(const char* propertyName, std::string& value)
{
std::string name = propertyName;
char bufs[PROP_VALUE_MAX] = {0};
#ifdef __linux__
#ifndef ANDROID
for (auto it = name.begin(); it != name.end(); ++it) {
if (*it == '.') {
*it = '_';
}
}
#endif
#endif
if (property_get(name.c_str(), bufs, 0) > 0) {
value = bufs;
}
}
static void printVector(const char *name, const std::vector<uint8_t> &data)
{
const size_t len = data.size();
const size_t hexLen = len * 2 + 1;
unsigned char* hexString = new unsigned char[hexLen];
if (!hexString) {
MLOGE("new fail");
return;
}
const char hexChars[] = "0123456789ABCDEF";
for (size_t i = 0; i < len; ++i) {
uint8_t byte = data[i];
hexString[i * 2] = static_cast<unsigned char>(hexChars[byte >> 4]); // high 4 bits
hexString[i * 2 + 1] = static_cast<unsigned char>(hexChars[byte & 0x0F]); // low 4 bits
}
hexString[hexLen - 1] = '\0';
MLOGI("%s: 0x%s", name, hexString);
delete []hexString;
}
static std::string convertVertorToHexString(const std::vector<uint8_t> &data)
{
const size_t len = data.size();
const size_t hexLen = len * 2 + 1;
unsigned char* hexString = new unsigned char[hexLen];
if (!hexString) {
MLOGE("new fail");
std::string resultTmp;
return resultTmp;
}
const char hexChars[] = "0123456789ABCDEF";
for (size_t i = 0; i < len; ++i) {
uint8_t byte = data[i];
hexString[i * 2] = static_cast<unsigned char>(hexChars[byte >> 4]); // high 4 bits
hexString[i * 2 + 1] = static_cast<unsigned char>(hexChars[byte & 0x0F]); // low 4 bits
}
hexString[hexLen - 1] = '\0';
std::string result(reinterpret_cast<const char*>(hexString));
delete []hexString;
return result;
}
static void storeProvisionServerToJson(bool isProvisionSuc, const std::string provisionServer)
{
std::string storeServerProp = "0";
getProperty(VMX_PROP_PROVISION_SAVE_SERVER_INFO_ENABLE, storeServerProp);
if (storeServerProp != "1") {
MLOGI("vendor.media.cas.vmx.save.server.info.enable is not 1, skip storing provision server");
return;
}
std::string storeServerPath = VMX_PROP_PROVISION_SAVE_SERVER_INFO_DEFAULT;
getProperty(VMX_PROP_PROVISION_SAVE_SERVER_INFO_PATH, storeServerPath);
//use "w" mode to clear all data
FILE* file = fopen(storeServerPath.c_str(), "w");
if (file == nullptr) {
MLOGE("Failed to open file %s, %s", storeServerPath.c_str(), strerror(errno));
return;
}
if (!isProvisionSuc) {
fclose(file);
MLOGI("Clear provision server info json");
return;
}
fprintf(file, "{\"serverAddr\":\"%s\"}", provisionServer.c_str());
fclose(file);
MLOGI("Stored provision server: %s to %s", provisionServer.c_str(), storeServerPath.c_str());
}
static void getProvisionServer(std::string& primaryServer, std::string& secondaryServer)
{
primaryServer = VMX_PROP_PROVISION_SERVER_IP_PRIMARY_DEFAULT;
getProperty(VMX_PROP_PROVISION_SERVER_IP_PRIMARY, primaryServer);
secondaryServer = VMX_PROP_PROVISION_SERVER_IP_SECONDARY_DEFAULT;
getProperty(VMX_PROP_PROVISION_SERVER_IP_SECONDARY, secondaryServer);
}
static string getVmxProvisionString(const std::string provisionServer)
{
//The provisionString contains the necessary provisioning information.
//it contain:
//bskid, stbid, chipid, ipaddress:port, storefile location, isOnline,timeout, retries, and ipVersion
//sample:
//,,,client-test-3.verimatrix.com:12686,/data/vendor/mediacas/Verimatrix.store,true,2,2,0
std::string propertyValue;
std::string provisionStr;
provisionStr.clear();
propertyValue = VMX_PROP_PROVISION_KID_DEFAULT;
getProperty(VMX_PROP_PROVISION_KID, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_STB_ID_DEFAULT;
getProperty(VMX_PROP_PROVISION_STB_ID, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_CHIP_ID_DEFAULT;
getProperty(VMX_PROP_PROVISION_CHIP_ID, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
provisionStr.append(provisionServer);
provisionStr.append(":");
propertyValue = VMX_PROP_PROVISION_SERVER_PORT_DEFAULT;
getProperty(VMX_PROP_PROVISION_SERVER_PORT, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_STORE_PATH_DEFAULT;
getProperty(VMX_PROP_PROVISION_STORE_PATH, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_IS_ONLINE_DEFAULT;
getProperty(VMX_PROP_PROVISION_IS_ONLINE, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_TIMEOUT_DEFAULT;
getProperty(VMX_PROP_PROVISION_TIMEOUT, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_RETRY_COUNT_DEFAULT;
getProperty(VMX_PROP_PROVISION_RETRY_COUNT, propertyValue);
provisionStr.append(propertyValue);
provisionStr.append(",");
propertyValue = VMX_PROP_PROVISION_IP_VERSION_DEFAULT;
getProperty(VMX_PROP_PROVISION_IP_VERSION, propertyValue);
provisionStr.append(propertyValue);
return provisionStr;
}
static int checkNetPhyConnectStatus(void)
{
const char* netEth0Carrier = "/sys/class/net/eth0/carrier";
const char* netWlan0Carrier = "/sys/class/net/wlan0/carrier";
int isEthConn = amsysfs_get_sysfs_int(netEth0Carrier);
int isWifiConn = amsysfs_get_sysfs_int(netWlan0Carrier);
if (isEthConn > 0 || isWifiConn > 0) {
return VMX_CAS_SUCCESS;
}
return VMX_CAS_ERROR;
}
static int vmxProvision(const std::string provisionServer, int & provisionResult)
{
int ret = VMX_CAS_ERROR;
provisionResult = VMX_CAS_PROVISION_FAIL;
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
std::string provisionStr = getVmxProvisionString(provisionServer);
MLOGI("provision string:%s", provisionStr.c_str());
auto status = vmxCasInitCtrl.iCas->provision(provisionStr);
if (!status.isOk()) {
MLOGE("provision error, ServiceSpecificError:%d", status.getServiceSpecificError());
if (status.getServiceSpecificError() == aidl::android::hardware::cas::Status::ERROR_CAS_NOT_PROVISIONED) {
MLOGE("not able to complete provision");
provisionResult = VMX_CAS_PROVISION_FAIL;
ret = VMX_CAS_ERROR;
} else if (status.getServiceSpecificError() == aidl::android::hardware::cas::Status::ERROR_CAS_DEVICE_REVOKED) {
MLOGE("no connection to VCAS server");
provisionResult = VMX_CAS_SERVER_UNAVAILABLE;
ret = VMX_CAS_ERROR;
} else {
MLOGE("provision fail, unknown reason");
provisionResult = VMX_CAS_PROVISION_FAIL;
ret = VMX_CAS_ERROR;
}
} else {
ret = VMX_CAS_SUCCESS;
provisionResult = VMX_CAS_PROVISION_SUC;
MLOGI("vmx provision success");
}
return ret;
}
static void reportProvisionResult(int provisionResult)
{
std::vector<uint8_t> dataTmp;
shared_ptr<MediaCasListener> listener = vmxCasInitCtrl.listener;
if (listener != nullptr) {
listener->onEvent(provisionResult, 0, dataTmp);
}
}
static VMXCasState createVmxMediaCasPlugin(AmlVMXIptvMediaCas *instance)
{
if (vmxCasInitCtrl.listener == nullptr) {
vmxCasInitCtrl.listener = SharedRefBase::make<MediaCasListener>();
if (vmxCasInitCtrl.listener == nullptr) {
MLOGE("new MediaCasListener fail");
} else {
if (instance != nullptr) {
vmxCasInitCtrl.listener->setEventCallback(instance->mCallBack, instance->mUserData);
}
}
}
if (AServiceManager_isDeclared("android.hardware.cas.IMediaCasService/default")) {
MLOGI("AServiceManager_waitForService");
SpAIBinder binder(AServiceManager_waitForService("android.hardware.cas.IMediaCasService/default"));
vmxCasInitCtrl.casBinder = binder;
vmxCasInitCtrl.casService = IMediaCasService::fromBinder(vmxCasInitCtrl.casBinder);
} else {
vmxCasInitCtrl.casService = nullptr;
}
if (vmxCasInitCtrl.casService == nullptr) {
MLOGE("Get Cas service fail");
return VMX_CAS_GET_SERVICE_FAIL;
} else {
MLOGI("Get Cas service: %p", vmxCasInitCtrl.casService.get());
}
bool is_supported = false;
auto status = vmxCasInitCtrl.casService->isSystemIdSupported(VMX_CA_SYSTEM_ID, &is_supported);
if (!status.isOk() || !is_supported) {
MLOGE("Not support VMX cas");
return VMX_CAS_UNSUPPORT;
}
auto statusTmp = vmxCasInitCtrl.casService->createPlugin(VMX_CA_SYSTEM_ID, vmxCasInitCtrl.listener, &(vmxCasInitCtrl.iCas));
if (!statusTmp.isOk() || !(vmxCasInitCtrl.iCas)) {
MLOGE("Create plugin failed!");
return VMX_CAS_CREATE_PLUGIN_FAIL;
}
MLOGI("VMX Plugin Created, %p", vmxCasInitCtrl.iCas.get());
return VMX_CAS_SUCCESS;
}
static void releaseVmxMediaCasPlugin(void)
{
if (vmxCasInitCtrl.iCas != nullptr) {
MLOGI("VMX Plugin Release, %p", vmxCasInitCtrl.iCas.get());
auto status = vmxCasInitCtrl.iCas->release();
if (!status.isOk()) {
MLOGE("ICas release fail");
}
vmxCasInitCtrl.iCas = nullptr;
}
vmxCasInitCtrl.listener = nullptr;
vmxCasInitCtrl.casService = nullptr;
vmxCasInitCtrl.casBinder.set(nullptr);
}
static VMXCasState InitVmxMediaCas(AmlVMXIptvMediaCas *instance)
{
int provisionResult;
std::string forceServerIp = VMX_PROP_PROVISION_INVALID_VALUE;
std::lock_guard<std::mutex> lock(vmxCasInitCtrl.initLock);
if (vmxCasInitCtrl.iCas != nullptr) {
getProperty(VMX_PROP_PROVISION_SERVER_IP_FORCE, forceServerIp);
if (forceServerIp != VMX_PROP_PROVISION_INVALID_VALUE && \
forceServerIp != vmxCasInitCtrl.currentProvisionServer) {
releaseVmxMediaCasPlugin();
//when VMX_PROP_PROVISION_SERVER_IP_FORCE is set, set primaryServer and
//secondaryServer to same value.
vmxCasInitCtrl.primaryServer = forceServerIp;
vmxCasInitCtrl.secondaryServer = forceServerIp;
vmxCasInitCtrl.currentProvisionServer = VMX_PROP_PROVISION_INVALID_VALUE;
} else {
MLOGI("gICvmxCasInitCtrl.iCas inited");
return VMX_CAS_SUCCESS;
}
}
VMXCasState retCreatePlugin = createVmxMediaCasPlugin(instance);
if (retCreatePlugin != VMX_CAS_SUCCESS) {
return retCreatePlugin;
}
if (VMX_CAS_SUCCESS != checkNetPhyConnectStatus()) {
MLOGI("Network is not connected");
releaseVmxMediaCasPlugin();
return VMX_CAS_SERVER_UNAVAILABLE;
}
getProperty(VMX_PROP_PROVISION_SERVER_IP_FORCE, forceServerIp);
if (forceServerIp != VMX_PROP_PROVISION_INVALID_VALUE) {
vmxCasInitCtrl.primaryServer = forceServerIp;
vmxCasInitCtrl.secondaryServer = forceServerIp;
vmxCasInitCtrl.currentProvisionServer = VMX_PROP_PROVISION_INVALID_VALUE;
} else if (vmxCasInitCtrl.primaryServer == VMX_PROP_PROVISION_INVALID_VALUE || \
vmxCasInitCtrl.secondaryServer == VMX_PROP_PROVISION_INVALID_VALUE) {
getProvisionServer(vmxCasInitCtrl.primaryServer, vmxCasInitCtrl.secondaryServer);
}
MLOGI("Primary Server:%s, Secondary Server:%s", vmxCasInitCtrl.primaryServer.c_str(),
vmxCasInitCtrl.secondaryServer.c_str());
int ret = vmxProvision(vmxCasInitCtrl.primaryServer, provisionResult);
if (ret == VMX_CAS_SUCCESS) {
//Primary Server Provision Success
vmxCasInitCtrl.currentProvisionServer = vmxCasInitCtrl.primaryServer;
storeProvisionServerToJson(true, vmxCasInitCtrl.primaryServer);
reportProvisionResult(provisionResult);
return VMX_CAS_SUCCESS;
} else {
//Primary Server Provision Fail
if (vmxCasInitCtrl.secondaryServer != VMX_PROP_PROVISION_INVALID_VALUE &&
vmxCasInitCtrl.secondaryServer != vmxCasInitCtrl.primaryServer) {
releaseVmxMediaCasPlugin();
retCreatePlugin = createVmxMediaCasPlugin(instance);
if (retCreatePlugin != VMX_CAS_SUCCESS) {
reportProvisionResult(provisionResult);
return retCreatePlugin;
}
ret = vmxProvision(vmxCasInitCtrl.secondaryServer, provisionResult);
if (ret == VMX_CAS_SUCCESS) {
vmxCasInitCtrl.currentProvisionServer = vmxCasInitCtrl.secondaryServer;
storeProvisionServerToJson(true, vmxCasInitCtrl.secondaryServer);
reportProvisionResult(provisionResult);
return VMX_CAS_SUCCESS;
} else {
reportProvisionResult(provisionResult);
releaseVmxMediaCasPlugin();
vmxCasInitCtrl.currentProvisionServer = VMX_PROP_PROVISION_INVALID_VALUE;
storeProvisionServerToJson(false, vmxCasInitCtrl.secondaryServer);
return VMX_CAS_ERROR;
}
} else {
reportProvisionResult(provisionResult);
releaseVmxMediaCasPlugin();
return VMX_CAS_ERROR;
}
}
return VMX_CAS_SUCCESS;
}
static void *VmxInitThread(void *arg)
{
AML_MP_UNUSED(arg);
//Set max try count to avoid endless attempts.
const int MAX_TRY_COUNT = 30;
MLOGI("VmxInitThread start");
int currentTryCnt = 0;
while (!vmxCasInitCtrl.initThreadExited) {
VMXCasState ret = InitVmxMediaCas(nullptr);
if (VMX_CAS_SUCCESS == ret) {
MLOGI("InitVmxMediaCas success, try count:%d", currentTryCnt);
vmxCasInitCtrl.initThreadExited = 1;
} else if (VMX_CAS_UNSUPPORT == ret) {
MLOGI("InitVmxMediaCas Not Support");
vmxCasInitCtrl.initThreadExited = 1;
} else {
currentTryCnt++;
MLOGE("InitVmxMediaCas fail, try again, ret:%d, try count:%d", ret, currentTryCnt);
if (currentTryCnt > MAX_TRY_COUNT) {
vmxCasInitCtrl.initThreadExited = 1;
MLOGE("InitVmxMediaCas reach max count");
break;
}
//wait 5 seconds and try again.
usleep(5000 * 1000);
}
}
MLOGI("VmxInitThread exit");
return NULL;
}
void PreInitVmxMediaCas(void)
{
int ret;
std::string preInitEnable = "0";
getProperty(VMX_PROP_PRE_INIT_MEDIACAS_ENABLE, preInitEnable);
if (preInitEnable == "0") {
MLOGI("pre init for mediacas is disable");
return;
}
std::lock_guard<std::mutex> lock(vmxCasInitCtrl.initThreadLock);
if (vmxCasInitCtrl.initThread != 0) {
MLOGI("vmx init thread already created");
return;
}
vmxCasInitCtrl.initThread = 0;
vmxCasInitCtrl.initThreadExited = 0;
ret = pthread_create(&(vmxCasInitCtrl.initThread), NULL, VmxInitThread, NULL);
if (ret < 0)
MLOGE("Create vmx init thread failed! ret=%d", ret);
MLOGI("gVmxInitThread:0x%lx", vmxCasInitCtrl.initThread);
}
void DeinitVmxMediaCas(void)
{
MLOGI("DeinitVmxMediaCas");
{
std::lock_guard<std::mutex> lock(vmxCasInitCtrl.initThreadLock);
vmxCasInitCtrl.initThreadExited = 1;
if (vmxCasInitCtrl.initThread != 0) {
pthread_join(vmxCasInitCtrl.initThread, NULL);
}
vmxCasInitCtrl.initThread = 0;
}
std::lock_guard<std::mutex> lock(vmxCasInitCtrl.initLock);
releaseVmxMediaCasPlugin();
vmxCasInitCtrl.currentProvisionServer = VMX_PROP_PROVISION_INVALID_VALUE;
vmxCasInitCtrl.primaryServer = VMX_PROP_PROVISION_INVALID_VALUE;
vmxCasInitCtrl.secondaryServer = VMX_PROP_PROVISION_INVALID_VALUE;
}
ScopedAStatus MediaCasListener::onEvent(int32_t event, int32_t arg,
const std::vector<uint8_t>& data) {
android::Mutex::Autolock autoLock(mLock);
mEvent = event;
mEventArg = arg;
mEventData = data;
MLOGI("onEvent, event:%d, arg:%d", event, arg);
printVector("onEvent", data);
switch (event) {
default:
MLOGE("Event:Unknown event %d", event);
}
std::vector<uint8_t> unusedSessionId;
reportEvent("Event", event, arg, data, unusedSessionId);
return ScopedAStatus::ok();
}
ScopedAStatus MediaCasListener::onSessionEvent(const vector<uint8_t>& sessionId, int32_t event,
int32_t arg, const vector<uint8_t>& data) {
android::Mutex::Autolock autoLock(mLock);
mSessionId = sessionId;
mEvent = event;
mEventArg = arg;
mEventData = data;
printVector("sessionId", sessionId);
MLOGI("onSessionEvent, event:%d, arg:%d", event, arg);
printVector("onSessionEvent", data);
if (event == 4) {
if (arg == 64) {
MLOGI("VCI Process: VOD movie is not active");
}
}
reportEvent("SessionEvent", event, arg, data, sessionId);
return ScopedAStatus::ok();
}
ScopedAStatus MediaCasListener::onStatusUpdate(StatusEvent event, int32_t arg) {
android::Mutex::Autolock autoLock(mLock);
mStatusEvent = event;
mEventArg = arg;
MLOGI("onStatusUpdate, event:0x%hhx, arg:%d", event, arg);
std::vector<uint8_t> unusedSessionId;
std::vector<uint8_t> unusedData;
reportEvent("StatusUpdate", static_cast<int32_t>(event), arg, unusedData, unusedSessionId);
return ScopedAStatus::ok();
}
int MediaCasListener::setEventCallback(Aml_MP_CAS_EventCallback cb, void* userData)
{
android::Mutex::Autolock autoLock(mLock);
if (mCB == cb && mUserData == userData) {
return 0;
}
mCB = cb;
mUserData = userData;
MLOGI("mCB:0x%p, mUserData:0x%p", mCB, mUserData);
return 0;
}
int MediaCasListener::reportEvent(const char *type, int32_t event, int32_t arg, \
const vector<uint8_t>& data, const vector<uint8_t>& sessionId)
{
if (mCB == NULL) {
return 0;
}
std::string dataStr;
std::string sessionIdStr;
size_t jsonLen = 0;
char* jsonStr = NULL;
if (0 == strcmp(type, "Event")) {
dataStr = convertVertorToHexString(data);
jsonLen = dataStr.size() + 128;
} else if (0 == strcmp(type, "SessionEvent")) {
dataStr = convertVertorToHexString(data);
sessionIdStr = convertVertorToHexString(sessionId);
jsonLen = dataStr.size() + sessionIdStr.size() + 128;
} else if (0 == strcmp(type, "StatusUpdate")) {
jsonLen = 128;
} else {
MLOGE("unsupport type:%s", type);
return 0;
}
jsonStr = new char[jsonLen];
if (!jsonStr) {
MLOGE("new fail");
return 0;
}
memset(jsonStr, 0, jsonLen);
if (0 == strcmp(type, "Event")) {
snprintf(jsonStr, jsonLen, "{\"type\":\"%s\", \"event\":\"%d\", \"arg\":\"%d\", \"data\":\"0x%s\"}", \
type, event, arg, dataStr.c_str());
} else if (0 == strcmp(type, "SessionEvent")) {
snprintf(jsonStr, jsonLen, "{\"type\":\"%s\", \"sessionID\":\"0x%s\", \"event\":\"%d\", \"arg\":\"%d\", \"data\":\"0x%s\"}", \
type, sessionIdStr.c_str(), event, arg, dataStr.c_str());
} else if (0 == strcmp(type, "StatusUpdate")) {
snprintf(jsonStr, jsonLen, "{\"type\":\"%s\", \"event\":\"%d\", \"arg\":\"%d\"}", \
type, event, arg);
}
int ret = mCB(mUserData, jsonStr);
MLOGI("mCB ret:%d, json:%s", ret, jsonStr);
if (jsonStr != NULL) {
delete []jsonStr;
}
return 0;
}
AmlVMXIptvMediaCas::AmlVMXIptvMediaCas(Aml_MP_CASServiceType serviceType)
:AmlCasBase(serviceType)
{
MLOGI("ctor AmlVMXIptvMediaCas");
mCaSetKeyThread = 0;
mLastEcmProcessSuc = 0;
mEcmData.clear();
mSessionId.clear();
CaClearMediacasDsmParamsState(&mMediaCasDsmParams);
}
AmlVMXIptvMediaCas::~AmlVMXIptvMediaCas()
{
MLOGI("dtor AmlVMXIptvMediaCas");
registerEventCallback(NULL, NULL);
}
int AmlVMXIptvMediaCas::registerEventCallback(Aml_MP_CAS_EventCallback cb, void* userData)
{
if (vmxCasInitCtrl.listener != nullptr) {
vmxCasInitCtrl.listener->setEventCallback(cb, userData);
} else {
MLOGE("vmxCasInitCtrl.listener not init");
}
mCallBack = cb;
mUserData = userData;
return 0;
}
bool AmlVMXIptvMediaCas::isSystemIdSupported(int32_t CASystemId)
{
shared_ptr<IMediaCasService> casServiceTmp;
if (AServiceManager_isDeclared("android.hardware.cas.IMediaCasService/default")) {
SpAIBinder binder(AServiceManager_waitForService("android.hardware.cas.IMediaCasService/default"));
casServiceTmp = IMediaCasService::fromBinder(binder);
} else {
casServiceTmp = nullptr;
}
if (casServiceTmp == nullptr) {
MLOGE("Get Cas service fail");
return false;
} else {
MLOGD("Get Cas service: %p", casServiceTmp.get());
}
bool is_supported = false;
auto status = casServiceTmp->isSystemIdSupported(CASystemId, &is_supported);
if (!status.isOk() || !is_supported) {
MLOGE("Not support VMX cas, %d", CASystemId);
return false;
}
return true;
}
int AmlVMXIptvMediaCas::openSession(int intent, int scramblingMode)
{
AML_MP_UNUSED(intent);
AML_MP_UNUSED(scramblingMode);
MLOGI("%s", __func__);
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
vector<uint8_t> sessionId;
auto ret_status_v = vmxCasInitCtrl.iCas->openSessionDefault(&sessionId);
if (!ret_status_v.isOk() ) {
MLOGE("vmx media cas open session error:%s", ret_status_v.getDescription().c_str());
binder_exception_t exception = ret_status_v.getExceptionCode();
MLOGE("Exception code: %d", exception);
return VMX_CAS_ERROR;
}
if (!sessionId.empty()) {
mSessionId = sessionId;
}
printVector("sessionId", mSessionId);
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::closeSession()
{
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
if (mSessionId.empty()) {
MLOGI("%s mSessionId is empty", __func__);
return VMX_CAS_SUCCESS;
}
printVector("close sessionId", mSessionId);
auto status = vmxCasInitCtrl.iCas->closeSession(mSessionId);
if (!status.isOk()) {
MLOGE("Close session failed!");
return VMX_CAS_ERROR;
}
mSessionId.clear();
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::sendEvent(int32_t event, int32_t arg, std::vector<uint8_t>& data)
{
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
auto status = vmxCasInitCtrl.iCas->sendEvent(event, arg, data);
if (!status.isOk()) {
MLOGE("sendEvent error");
return VMX_CAS_ERROR;
}
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::sendSessionEvent(int32_t event, int32_t arg, std::vector<uint8_t>& data)
{
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
auto status = vmxCasInitCtrl.iCas->sendSessionEvent(mSessionId, event, arg, data);
if (!status.isOk()) {
MLOGE("sendSessionEvent error");
return VMX_CAS_ERROR;
}
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::startDescrambling(const Aml_MP_IptvCASParams* params)
{
MLOG();
int ret;
AmlCasBase::startDescrambling(params);
MLOGI("videoCodec:%d, audioCodec:%d, caSystemId:0x%x, videoPid:0x%x, audioPid:0x%x, ecmPid[0]:0x%x, ecmPid[1]:0x%x", \
params->videoCodec, params->audioCodec, params->caSystemId, params->videoPid,
params->audioPid, params->ecmPid[0], params->ecmPid[1]);
VMXCasState vmxState = InitVmxMediaCas(this);
if (VMX_CAS_SUCCESS != vmxState) {
MLOGE("InitVmxMediaCas fail, vmxState:%d", vmxState);
if (vmxState == VMX_CAS_SERVER_UNAVAILABLE) {
shared_ptr<MediaCasListener> listener = vmxCasInitCtrl.listener;
std::vector<uint8_t> dataTmp;
MLOGE("no connection to VCAS server");
if (listener != nullptr) {
listener->onEvent(VMX_CAS_SERVER_UNAVAILABLE, 0, dataTmp);
}
}
return VMX_CAS_ERROR;
}
if (VMX_CAS_SUCCESS != openSession(0, 1)) {
MLOGE("Open session failed!");
return VMX_CAS_ERROR;
}
CaClearMediacasDsmParamsState(&mMediaCasDsmParams);
mMediaCasDsmParams.dev_no = params->demuxId;
mMediaCasDsmParams.vid = params->videoPid;
mMediaCasDsmParams.aid[0] = params->audioPid;
mMediaCasDsmParams.mediacasSessionId = convertSessionIdtoInt(mSessionId);
bool dsm_ret = caDsmOpenSession(mMediaCasDsmParams.dev_no, mMediaCasDsmParams.mediacasSessionId, mMediaCasDsmParams.mDsmFd);
if (dsm_ret == false) {
MLOGE("caDsmOpenSession fail!");
return VMX_CAS_ERROR;
}
ret = pthread_create(&mCaSetKeyThread, NULL, caSetKey, this);
if (ret < 0)
MLOGI("Create ca set key thread failed! ret=%d", ret);
MLOGI("mCaSetKeyThread:0x%lx", mCaSetKeyThread);
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::stopDescrambling()
{
MLOGI("stopDescrambling");
{
std::lock_guard<std::mutex> lock(mMediaCasDsmLock);
if (mMediaCasDsmParams.hasAllocDscChannels == true) {
MLOGI("caFreeDscChannels hasAllocDscChannels %d", mMediaCasDsmParams.hasAllocDscChannels);
caFreeDscChannels(&mMediaCasDsmParams);
}
caDsmCloseSession(&mMediaCasDsmParams);
CaClearMediacasDsmParamsState(&mMediaCasDsmParams);
}
if (mCaSetKeyThread != 0) {
void *thread_result;
int ret = pthread_join(mCaSetKeyThread, &thread_result);
if (ret != 0) {
MLOGE("pthread_join fail:%d", ret);
}
mCaSetKeyThread = 0;
}
mLastEcmProcessSuc = 0;
mEcmData.clear();
mLastFailedEcmData.clear();
mLastFailedTimeMs = 0;
closeSession();
std::string preInitEnable = "0";
getProperty(VMX_PROP_PRE_INIT_MEDIACAS_ENABLE, preInitEnable);
if (preInitEnable == "0") {
//if pre-init for VMX media cas is disable,
//release VMX media cas plugin.
DeinitVmxMediaCas();
}
return VMX_CAS_SUCCESS;
}
void* AmlVMXIptvMediaCas::caSetKey(void *arg)
{
AmlVMXIptvMediaCas *mediaCas = (AmlVMXIptvMediaCas*)arg;
bool caReady = false;
int num = 0;
bool ret = false;
while (mediaCas && !caReady) {
if (num > 0 && num % 100 == 0)
MLOGI("Waiting for CA ready %ds", num/100);
std::lock_guard<std::mutex> lock(mediaCas->mMediaCasDsmLock);
if (mediaCas->mMediaCasDsmParams.mDsmFd == -1) {
MLOGI("Exit ca set key thread!");
caReady = false;
break;
}
caReady = isDsmDecSlotReady(mediaCas->mMediaCasDsmParams.mDsmFd);
if (caReady) {
MLOGI("CA is ready! num:%d", num);
break;
}
usleep(10 * 1000);
num ++;
}
if (caReady == false) {
MLOGE("CA is not ready!");
return NULL;
}
std::lock_guard<std::mutex> lock2(mediaCas->mMediaCasDsmLock);
Aml_MP_MediaCasDsmParams *params = &(mediaCas->mMediaCasDsmParams);
ret = getKeySlots(mediaCas->mMediaCasDsmParams.mDsmFd, params);
if (ret == false) {
MLOGE("getKeySlots fail");
return NULL;
}
params->mDscType = CA_DSC_COMMON_TYPE;
MLOGI("mKeyslotList count:%d", params->mKeyslotList.count);
for (uint32_t i = 0; i < params->mKeyslotList.count; i++) {
if (params->mKeyslotList.keyslots[i].is_enc == 0) {
params->mDscAlgo = params->mKeyslotList.keyslots[i].algo;
break;
}
}
if (params->mDscAlgo == DSM_ALGO_UNKNOWN) {
MLOGE("DSM_ALGO_UNKNOWN");
return NULL;
}
MLOGI("mDscAlgo:%d", params->mDscAlgo);
if (caAllocDscChannels(params) != true) {
caFreeDscChannels(params);
MLOGE("caAllocDscChannels fail.");
return NULL;
}
if (caSetDscChannelsKeyslot(params) != true) {
MLOGE("caSetDscChannelsKeyslot fail.");
return NULL;
}
return NULL;
}
int AmlVMXIptvMediaCas::setPrivateData(const uint8_t* data, size_t size)
{
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
std::vector<uint8_t> privatedata(data, data + size);
auto status = vmxCasInitCtrl.iCas->setPrivateData(privatedata);
if (!status.isOk()) {
MLOGE("setPrivateData error");
return VMX_CAS_ERROR;
}
return VMX_CAS_SUCCESS;
}
int64_t AmlVMXIptvMediaCas::getTimeMs()
{
int64_t systime = 0;
int64_t ret = 0;
int rret = 0;
struct timeval tv;
gettimeofday(&tv, NULL);
systime = (int64_t)tv.tv_sec * 1000 + (int64_t)(tv.tv_usec / 1000);
return systime;
}
int AmlVMXIptvMediaCas::processEcm(bool isSection, int ecmPid, const uint8_t* data, size_t size)
{
AML_MP_UNUSED(isSection);
AML_MP_UNUSED(ecmPid);
MLOGI("ecm size:%zu, data: 0x%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", size,
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9]);
if (data == NULL) {
MLOGE("parameter error, data:%p, size:%zu", data, size);
return VMX_CAS_ERROR;
}
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
if (mSessionId.empty()) {
MLOGE("mSessionId is empty");
return VMX_CAS_SUCCESS;
}
const uint8_t* dataStart = data;
const uint8_t* dataEnd = data + size;
if (data[0] == 0x47) {
size_t i = 0;
int ecmDataLen = 0;
//jump TS header
dataStart = data + 5;
//required ECM data length
ecmDataLen = dataStart[2] + 3;
//find the last value that is not 0xFF
while (data[size - 1 - i] == 0xFF) {
i++;
if (i == size - 1) {
break;
}
}
dataEnd = data + size - i;
if (dataEnd < dataStart) {
MLOGE("invalid ECM data");
return VMX_CAS_ERROR;
}
if ((dataEnd > dataStart) && ((dataEnd - dataStart) < ecmDataLen)) {
//actual length is less than required ECM data length.
//maybe 0xFF was cut, add difference to dataEnd.
int n = ecmDataLen - (dataEnd - dataStart);
dataEnd += n;
}
}
std::vector<uint8_t> ecmData(dataStart, dataEnd);
if (!mLastFailedEcmData.empty()) {
int64_t nowMs = getTimeMs();
int64_t elapsedMs = nowMs - mLastFailedTimeMs;
if (elapsedMs < 500 && mLastFailedEcmData == ecmData) {
MLOGI("Skip same ECM data within 500 ms after last failure");
return VMX_CAS_ERROR;
}
if (elapsedMs >= 500) {
mLastFailedEcmData.clear();
}
}
if (1 == mLastEcmProcessSuc && mEcmData == ecmData) {
//MLOGI("same ecm data");
return VMX_CAS_SUCCESS;
} else {
auto status = vmxCasInitCtrl.iCas->processEcm(mSessionId, ecmData);
if (!status.isOk()) {
MLOGE("processEcm error:%s", status.getDescription().c_str());
binder_exception_t exception = status.getExceptionCode();
MLOGE("Exception code: %d", exception);
mLastEcmProcessSuc = 0;
mEcmData.clear();
mLastFailedEcmData = ecmData;
mLastFailedTimeMs = getTimeMs();
return VMX_CAS_ERROR;
}
mLastFailedEcmData.clear();
mLastFailedTimeMs = 0;
mLastEcmProcessSuc = 1;
mEcmData = ecmData;
}
return VMX_CAS_SUCCESS;
}
int AmlVMXIptvMediaCas::processEmm(bool isSection, int emmPid, const uint8_t* data, size_t size)
{
AML_MP_UNUSED(isSection);
AML_MP_UNUSED(emmPid);
RETURN_ERROR_IF_NULLPTR(vmxCasInitCtrl.iCas);
std::vector<uint8_t> emmData(data, data + size);
auto status = vmxCasInitCtrl.iCas->processEmm(emmData);
if (!status.isOk()) {
MLOGE("processEmm error");
return VMX_CAS_ERROR;
}
return VMX_CAS_SUCCESS;
}
}