/* * Copyright (C) 2025 Amlogic Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "audio_mpegh" //#define LOG_NDEBUG 0 #include #include #include #include #include "audio_buffer.h" #include "audio_hw.h" #include "audio_mpegh.h" #include "aml_mpegh_uimanager.h" #include "aml_audio_log.h" #include "aml_dump_debug.h" #include "aml_malloc_debug.h" #include "aml_audio_stream.h" #define AML_MPEGH_BASE64_BUFSIZE ((AML_MPEGH_PERSISTENCE_BUFSIZE + 2) / 3) * 4 #define AML_MPEGH_UIMANAGER_PROP_DUMP_FLAG "vendor.media.audio.mpegh.uimanager.dump" #define AML_MPEGH_UIMANAGER_PROP_DEBUG_FLAG "vendor.media.audio.mpegh.uimanager.debug" typedef struct ActionEventNode { char* event; struct ActionEventNode* next; } ActionEventNode; typedef struct { ActionEventNode* head; ActionEventNode* tail; size_t size; } ActionEventQueue; typedef struct KeyValue { char* key; char* value; struct KeyValue* next; } KeyValue; static KeyValue* uiManagerSystemSetting = NULL; static ActionEventQueue g_actionQueue = {NULL, NULL, 0}; int str_parms_has_key(struct str_parms *str_parms, const char *key); static KeyValue* findKey(const char* key) { if (key == NULL || *key == '\0') { AM_LOGE("findKey: invalid key (NULL or empty)!"); return NULL; } KeyValue* current = uiManagerSystemSetting; while (current != NULL) { if (strcmp(current->key, key) == 0) { return current; } current = current->next; } return NULL; } static void freeKeyValue(KeyValue* kv) { if (kv == NULL) return; aml_audio_free(kv->key); aml_audio_free(kv->value); aml_audio_free(kv); } int saveMpeghSysActioneventXml(const char* key, const char* value) { if (key == NULL || key[0] == '\0' || value == NULL) { return -1; } KeyValue* existing = findKey(key); if (existing != NULL) { aml_audio_free(existing->value); existing->value = strdup(value); if (existing->value == NULL) { return -1; } return 0; } else { KeyValue* newNode = (KeyValue*)aml_audio_malloc(sizeof(KeyValue)); if (newNode == NULL) { return -1; } newNode->key = strdup(key); newNode->value = strdup(value); if (newNode->key == NULL || newNode->value == NULL) { freeKeyValue(newNode); return -1; } newNode->next = uiManagerSystemSetting; uiManagerSystemSetting = newNode; return 0; } } void freeUiManagerSystemSetting() { KeyValue* current = uiManagerSystemSetting; while (current != NULL) { KeyValue* next = current->next; freeKeyValue(current); current = next; } uiManagerSystemSetting = NULL; } const char *mpeghSystemActionEventXml[] = { "", "", "", "", "", "", "", "" }; static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; int aml_mpegh_base64_encode(const unsigned char* input, int input_length, struct aml_audio_device *adev) { if (!input || !input_length || !adev) { ALOGE("%s parameters invalid", __func__); return -1; } int enc_len = 4 * ((input_length + 2) / 3); adev->mpegh_base64_encode_mem = aml_audio_realloc(adev->mpegh_base64_encode_mem, enc_len + 1); char * enc_buf = (char*)adev->mpegh_base64_encode_mem; int i, j = 0; for (i = 0; i < input_length; i += 3) { int val = (input[i] << 16); if (i + 1 < input_length) { val |= (input[i + 1] << 8); } if (i + 2 < input_length) { val |= input[i + 2]; } enc_buf[j++] = base64_table[(val >> 18) & 0x3F]; enc_buf[j++] = base64_table[(val >> 12) & 0x3F]; enc_buf[j++] = (i + 1 < input_length) ? base64_table[(val >> 6) & 0x3F] : '='; enc_buf[j++] = (i + 2 < input_length) ? base64_table[val & 0x3F] : '='; } enc_buf[j] = '\0'; return 0; } int base64_decode(char* input, int input_length, struct aml_audio_device *adev) { if (!input || !input_length || !adev) { ALOGE("%s input param invalid", __func__); return -1; } if (input_length % 4 != 0) { ALOGE("%s input length not a multiple of 4", __func__); return -1; } int output_len = input_length / 4 * 3; if (input_length > 0 && input[input_length - 1] == '=') output_len--; if (input_length > 1 && input[input_length - 2] == '=') output_len--; //The size of the persistencemem must be a multiple of 1024. if (output_len % 1024) { output_len -= (output_len % 1024); } ALOGI("%s input_length = %d, output_len = %d", __func__, input_length, output_len); unsigned char* decoded_data = (unsigned char*)adev->mpegh_ui_persistencemem; unsigned char decoding_table[256]; memset(decoding_table, 0xFF, 256); for (int i = 0; i < 64; i++) { decoding_table[(unsigned char)base64_table[i]] = i; } for (int i = 0, j = 0; i < input_length;) { unsigned char a = input[i] == '=' ? 0 & i++ : decoding_table[(unsigned char)input[i++]]; unsigned char b = input[i] == '=' ? 0 & i++ : decoding_table[(unsigned char)input[i++]]; unsigned char c = input[i] == '=' ? 0 & i++ : decoding_table[(unsigned char)input[i++]]; unsigned char d = input[i] == '=' ? 0 & i++ : decoding_table[(unsigned char)input[i++]]; if (a == 0xFF || b == 0xFF || c == 0xFF || d == 0xFF) { return -1; } uint32_t triple = (a << 3 * 6) | (b << 2 * 6) | (c << 1 * 6) | (d << 0 * 6); if (j < output_len) decoded_data[j++] = (unsigned char)((triple >> 2 * 8) & 0xFF); if (j < output_len) decoded_data[j++] = (unsigned char)((triple >> 1 * 8) & 0xFF); if (j < output_len) decoded_data[j++] = (unsigned char)((triple >> 0 * 8) & 0xFF); } return 0; } int aml_mpegh_applyxmlaction(struct aml_audio_device *adev, char *xmlstr, int xmlsize) { struct aml_stream_out *aml_out = NULL; int ret = -1; AM_LOGI("enter:xmlsize:%d", xmlsize); AM_LOGV("xmlinstr::\r\n%s\r\n", xmlstr); for (int i = 0 ; i < STREAM_NUM_MAX; i++) { aml_out = adev->active_outputs[i]; if (aml_out && (AUDIO_FORMAT_MPEGH == aml_out->hal_internal_format)) { AM_LOGI("got mpegh stream: aml_out:%p format:0x%x", aml_out, aml_out->hal_internal_format); mpegh_uimanager_oper_t *uioper = aml_out->mpegh_uimanager_handle; int flagsout = 0; ret = aml_mpegh_uimanager_applyxmlaction(uioper, xmlstr, xmlsize, &flagsout); break; } } return ret; } int assembleMpeghSystemActionEventXml(int key, char* value, char **xmlbuf) { if (key < 0 || key > 7 || !value || !xmlbuf) { AM_LOGI("Invalid param key:%d, value:%p, xmlbuf:%p\n", key, value, (void *)xmlbuf); return -1; } int len = snprintf(NULL, 0, mpeghSystemActionEventXml[key], value) + 1; *xmlbuf = (char *)aml_audio_malloc(len); if (!*xmlbuf) return -1; snprintf(*xmlbuf, len, mpeghSystemActionEventXml[key], value); return 0; } void initActionEventQueue() { g_actionQueue.head = NULL; g_actionQueue.tail = NULL; g_actionQueue.size = 0; } void addNewActionEvent(char* event) { if (event == NULL) { return; } ActionEventNode* newNode = (ActionEventNode*)aml_audio_malloc(sizeof(ActionEventNode)); if (newNode == NULL) { AM_LOGI("no memory for new node."); return; } newNode->event = (char*)aml_audio_malloc(strlen(event) + 1); if (newNode->event == NULL) { AM_LOGI("event memory alloc fail."); aml_audio_free(newNode); return; } strcpy(newNode->event, event); newNode->next = NULL; if (g_actionQueue.tail == NULL) { g_actionQueue.head = newNode; g_actionQueue.tail = newNode; } else { g_actionQueue.tail->next = newNode; g_actionQueue.tail = newNode; } g_actionQueue.size++; AM_LOGI("ActionEventQueue size = %zu", g_actionQueue.size); } int isActionEventQueueEmpty() { return (g_actionQueue.size == 0) ? 1 : 0; } char* getActionEventQueueFront() { if (isActionEventQueueEmpty()) { return NULL; } return g_actionQueue.head->event; } void popActionEventQueue() { if (isActionEventQueueEmpty()) { return; } ActionEventNode* temp = g_actionQueue.head; g_actionQueue.head = g_actionQueue.head->next; if (!g_actionQueue.head) { g_actionQueue.tail = NULL; } aml_audio_free(temp->event); aml_audio_free(temp); g_actionQueue.size--; } void destroyActionEventQueue() { ActionEventNode* current = g_actionQueue.head; while (current != NULL) { ActionEventNode* next = current->next; aml_audio_free(current->event); aml_audio_free(current); current = next; } initActionEventQueue(); } int aml_mpegh_savepersistencememory(struct aml_audio_device *adev, void *persistencemem, int persistencememsize) { if (!persistencemem || persistencememsize <= 0) return -1; memset(adev->mpegh_ui_persistencemem, 0, adev->mpegh_ui_persistencememsize); int ret = base64_decode((char *)persistencemem, persistencememsize, adev); if (ret < 0) { AM_LOGE("base64 decode error"); return -1; } return 0; } bool aml_get_is_exist_active_mpegh_stream(struct aml_audio_device *adev) { struct aml_stream_out *aml_out = NULL; bool retValue = false; for (int i = 0 ; i < STREAM_NUM_MAX; i++) { aml_out = adev->active_outputs[i]; if (aml_out && !(aml_out->standby) && is_mpegh_format(aml_out->hal_internal_format)) { retValue = true; break; } } AM_LOGI("aml_out:%p, retValue:%d", aml_out, retValue); return retValue; } static int getActionType(const char* xml) { if (xml == NULL || *xml == '\0') { AM_LOGE("XML string is NULL or empty"); return -1; } const char* key = "actionType=\""; const char* pos = strstr(xml, key); if (pos == NULL) { AM_LOGE("Not found actionType"); return -1; } pos += strlen(key); const char* end = strchr(pos, '"'); if (end == NULL) { AM_LOGE("Actionevent format error (missing end quote)"); return -1; } size_t val_len = end - pos; char* valueStr = (char*)malloc(val_len + 1); // +1 用于 '\0' if (valueStr == NULL) { AM_LOGE("Malloc failed (out of memory)"); return -1; } strncpy(valueStr, pos, val_len); valueStr[val_len] = '\0'; char* converted = NULL; errno = 0; long value = strtol(valueStr, &converted, 10); int ret = -1; if (valueStr == converted) { AM_LOGE("actionType is not a valid number"); } else if (errno == ERANGE) { AM_LOGE("actionType out of range (overflow)"); } else if (value < 0 || value > 90) { AM_LOGE("actionType invalid (value=%ld, must be 0-90)", value); } else { ret = (int)value; AM_LOGE("actionType = %d", ret); } free(valueStr); valueStr = NULL; return ret; } int aml_mpegh_process(void **mpegh_uimanager_handle, struct audio_buffer *abuffer, int *asiupdate) { struct audio_buffer *abuffer_out = abuffer; struct aml_audio_device *adev = adev_get_handle(); void *mpegh_uiop = *mpegh_uimanager_handle; int ret = -1; int isupdate = 0; AM_LOGV("enter"); if (NULL == mpegh_uimanager_handle) { AM_LOGE("mpegh_uimanager_handle:%p, error!", mpegh_uimanager_handle); goto exit; } if (false == check_audio_buffer_valid(abuffer)) { AM_LOGE("audio buffer checked fail"); goto exit; } if (!mpegh_uiop) { ret = aml_mpegh_uimanager_open(&mpegh_uiop); if (ret != 0) { AM_LOGE(" aml_mpegh_uimanager_open fail, ret:%d, mpegh_uimanager_handle:%p", ret, mpegh_uiop); goto exit; } *mpegh_uimanager_handle = mpegh_uiop; //set persistency ctx if (adev->mpegh_ui_persistencemem && adev->mpegh_ui_persistencememsize > 0) { ret = aml_mpegh_uimanager_setpersistencememory(mpegh_uiop, (char*)adev->mpegh_ui_persistencemem, adev->mpegh_ui_persistencememsize); if (ret != 0) { AM_LOGE(" aml_mpegh_uimanager_setpersistencememory fail, ret:%d", ret); } } //set system setting KeyValue *current = uiManagerSystemSetting; while (current != NULL) { char *system_setting_xml = current->value; aml_mpegh_applyxmlaction(adev, system_setting_xml, strlen(system_setting_xml)); current = current->next; } } while (!isActionEventQueueEmpty()) { char* xml = getActionEventQueueFront(); if (xml) { aml_mpegh_applyxmlaction(adev, xml, strlen(xml)); } popActionEventQueue(); } ret = aml_mpegh_uimanager_feedmhas(mpegh_uiop, abuffer_out->buffer, abuffer_out->size); if (ret != 0) { AM_LOGE(" aml_mpegh_uimanager_feedmhas fail, ret:%d", ret); goto exit; } void *outbuf = NULL; int outlen = 0; ret = aml_mpegh_uimanager_updatemhas(mpegh_uiop, abuffer_out->buffer, abuffer_out->size, &outbuf, &outlen); if (ret > 0 && ret != MPEGH_UI_NOT_ALLOWED) { AM_LOGE(" aml_mpegh_uimanager_updatemhas fail, ret:%d", ret); goto exit; } abuffer_out->buffer = outbuf; abuffer_out->size = outlen; if (MPEGH_UI_OK == ret) { ret = aml_mpegh_uimanager_getxmlscenestate(mpegh_uiop, &isupdate); if ((0 == ret) && (1 == isupdate)) { mpegh_uimanager_oper_t *uioper = mpegh_uiop; AM_LOGI("getxml::::\r\n%s \r\n ::::end", uioper->xmloutbuf); *asiupdate = 1; if (get_debug_value(AML_DUMP_AUDIOHAL_IN)) { static int count = 0; aml_dump_audio_bitstreams_with_id("xmlscenestate.txt", uioper->xmloutbuf, strlen(uioper->xmloutbuf), count++); } } } exit: return ret; } int aml_mpegh_close(struct aml_audio_device *adev, void *handle) { AM_LOGI("enter!"); if (!handle || !adev) { AM_LOGE("NULL handle or adev"); return -1; } mpegh_uimanager_oper_t * uioper = (mpegh_uimanager_oper_t *) handle; aml_mpegh_uimanager_getpersistencememory(uioper, &adev->mpegh_ui_persistencemem, &adev->mpegh_ui_persistencememsize); if (property_get_bool(AML_MPEGH_UIMANAGER_PROP_DUMP_FLAG, 0) && adev->mpegh_ui_persistencememsize) { aml_dump_audio_bitstreams("mpegh_ui_persistency.txt", adev->mpegh_ui_persistencemem, adev->mpegh_ui_persistencememsize); } aml_mpegh_uimanager_close(uioper); return 0; } char *aml_mpegh_getxmlsceneinfo(struct aml_audio_device *adev) { struct aml_stream_out *aml_out = NULL; mpegh_uimanager_oper_t *uioper = NULL; AM_LOGI("enter"); for (int i = 0 ; i < STREAM_NUM_MAX; i++) { aml_out = adev->active_outputs[i]; if (aml_out && (AUDIO_FORMAT_MPEGH == aml_out->hal_internal_format)) { uioper = aml_out->mpegh_uimanager_handle; AM_LOGI("got mpegh stream:aml_out:%p format:0x%x, uioper:%p", aml_out, aml_out->hal_internal_format, uioper); break; } } if ((NULL == uioper) || (NULL == uioper->xmloutbuf)) { return NULL; } AM_LOGV("xmloutstr::\r\n%s\r\n", uioper->xmloutbuf); return uioper->xmloutbuf; } int set_mpegh_parameters(struct aml_audio_device *adev, struct str_parms *parms) { int ret = -1; int actionType = -1; char value[128] = {'\0'}; //view setting ret = str_parms_get_str(parms, "mpegh_action_event", value, sizeof(value)); if (ret >= 0) { actionType = getActionType(value); if (actionType == 0) { memset(adev->mpegh_audioscenexml, 0, adev->mpegh_audioscenexmlsize); AM_LOGI("mpegh reset, clear mpegh_audioscenexml!"); } //save event to queue addNewActionEvent(value); return ret; } //set persistency ctx if (str_parms_has_key(parms, "mpegh_persistency_ctx")) { char *mpegh_persictency_buf = (char *)aml_audio_calloc(1, (AML_MPEGH_BASE64_BUFSIZE + 1)); ret = str_parms_get_str(parms, "mpegh_persistency_ctx", mpegh_persictency_buf, (AML_MPEGH_BASE64_BUFSIZE + 1)); if (ret >= 0) { ret = aml_mpegh_savepersistencememory(adev, (void*)mpegh_persictency_buf, AML_MPEGH_BASE64_BUFSIZE); AM_LOGD("set mpegh persistency ctx:, ret:%d", ret); } aml_audio_free(mpegh_persictency_buf); mpegh_persictency_buf = NULL; return ret; } //system setting ret = str_parms_get_str(parms, "mpegh_audio_lang", value, sizeof(value)); if (ret >= 0) { //Splicing system actionevent XML char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_AUDIO_LANG, value, &mpegh_action_event); //store system setting to queue when exist active mpegh stream. if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } //store or update system setting if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_audio_lang", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_label_lang", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_LABEL_LANG, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_label_lang", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_drc_att", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_DRC_ATT, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_drc_att", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_accessibility", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_ACCESSIBILITY, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_accessibility", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_drc_effect", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_DRC_EFFECT, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_drc_effect", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_drc_album", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_DRC_ALBUM, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_drc_album", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_drc_boost", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_DRC_BOOST, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_drc_boost", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } ret = str_parms_get_str(parms, "mpegh_tl", value, sizeof(value)); if (ret >= 0) { char * mpegh_action_event = NULL; ret = assembleMpeghSystemActionEventXml(AML_MPEGH_TL, value, &mpegh_action_event); if (aml_get_is_exist_active_mpegh_stream(adev)) { addNewActionEvent(mpegh_action_event); } if (mpegh_action_event) { saveMpeghSysActioneventXml("mpegh_tl", mpegh_action_event); } aml_audio_free(mpegh_action_event); mpegh_action_event = NULL; return ret; } return ret; }