/* * 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 #include #include #include #include #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 listener; //vmx cas plugin shared_ptr iCas; shared_ptr 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 & 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 &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(hexChars[byte >> 4]); // high 4 bits hexString[i * 2 + 1] = static_cast(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 &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(hexChars[byte >> 4]); // high 4 bits hexString[i * 2 + 1] = static_cast(hexChars[byte & 0x0F]); // low 4 bits } hexString[hexLen - 1] = '\0'; std::string result(reinterpret_cast(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 dataTmp; shared_ptr listener = vmxCasInitCtrl.listener; if (listener != nullptr) { listener->onEvent(provisionResult, 0, dataTmp); } } static VMXCasState createVmxMediaCasPlugin(AmlVMXIptvMediaCas *instance) { if (vmxCasInitCtrl.listener == nullptr) { vmxCasInitCtrl.listener = SharedRefBase::make(); 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 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 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 lock(vmxCasInitCtrl.initThreadLock); vmxCasInitCtrl.initThreadExited = 1; if (vmxCasInitCtrl.initThread != 0) { pthread_join(vmxCasInitCtrl.initThread, NULL); } vmxCasInitCtrl.initThread = 0; } std::lock_guard 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& 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 unusedSessionId; reportEvent("Event", event, arg, data, unusedSessionId); return ScopedAStatus::ok(); } ScopedAStatus MediaCasListener::onSessionEvent(const vector& sessionId, int32_t event, int32_t arg, const vector& 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 unusedSessionId; std::vector unusedData; reportEvent("StatusUpdate", static_cast(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& data, const vector& 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 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 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& 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& 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 listener = vmxCasInitCtrl.listener; std::vector 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 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 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 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 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 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 emmData(data, data + size); auto status = vmxCasInitCtrl.iCas->processEmm(emmData); if (!status.isOk()) { MLOGE("processEmm error"); return VMX_CAS_ERROR; } return VMX_CAS_SUCCESS; } }