/* * Copyright (C) 2026 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_hw_sdec" #include #include #include #include "aml_audio_log.h" #include "aml_audio_sdec_wrapper.h" #include "aml_malloc_debug.h" #include "audio_hw.h" #include "audio_hw_resource_mgr.h" #include "audio_post_process.h" #include "audio_sdec.h" #include "dolby_lib_api.h" /* match mixer MIXER_FRAME_COUNT */ #define AUDIO_SDEC_BLOCK_FRAMES 256 struct audio_sdec { SDEC_Context *ctx; uint32_t in_mask; uint32_t out_mask; void *out_buf; size_t out_buf_sz; }; static uint32_t audio_sdec_mask_from_buffer(const struct audio_buffer *abuf) { uint32_t mask; if (!abuf) return 0; mask = (uint32_t)abuf->channel_mask; if (mask != 0 && mask != (uint32_t)AUDIO_CHANNEL_INVALID) return mask; if (abuf->channel > 0) return (uint32_t)audio_channel_out_mask_from_count((uint32_t)abuf->channel); return 0; } static int audio_sdec_ensure_route(struct audio_sdec *sdec, uint32_t in_mask) { if (!sdec || !sdec->ctx) return -1; if (in_mask == 0) return -1; if (in_mask == sdec->in_mask && sdec->out_mask != 0) return 0; if (aml_audio_sdec_reconfigure(in_mask, sdec->out_mask, sdec->ctx) != SDEC_OK) { AM_LOGE("sdec reconfigure failed in 0x%x out 0x%x", in_mask, sdec->out_mask); return -1; } sdec->in_mask = in_mask; AM_LOGI("sdec reconfigured in 0x%x out 0x%x", in_mask, sdec->out_mask); return 0; } int audio_sdec_init(struct aml_audio_device *adev) { struct audio_sdec *sdec; SDEC_Context *ctx = NULL; uint32_t out_mask; SDEC_Error err; if (!adev) return -EINVAL; audio_sdec_deinit(adev); if (!need_speaker_effect_path(adev)) { AM_LOGI("sdec skip: not TV/SBR"); return 0; } /* Always on TV/SBR. out_mask == speaker layout. */ out_mask = (uint32_t)adev->real_spk_layout_mask; if (out_mask == 0 || out_mask == (uint32_t)AUDIO_CHANNEL_INVALID) { if (!adev->is_multi_channel_spk) { out_mask = AUDIO_CHANNEL_OUT_STEREO; AM_LOGI("sdec 2ch speaker, use STEREO out_mask"); } else { AM_LOGE("sdec skip: real_spk_layout_mask invalid 0x%x", out_mask); return 0; } } if (!aml_audio_sdec_in_mask_is_supported(out_mask)) { AM_LOGE("sdec skip: real_spk_layout_mask 0x%x not supported", out_mask); return 0; } sdec = (struct audio_sdec *)aml_audio_calloc(1, sizeof(*sdec)); if (!sdec) { AM_LOGE("sdec alloc failed"); return -ENOMEM; } err = aml_audio_sdec_create(out_mask, out_mask, AUDIO_SDEC_BLOCK_FRAMES, SDEC_LFE_DOWNMIX_DEFAULT, &ctx); if (err != SDEC_OK || !ctx) { AM_LOGE("sdec create failed err=%d", err); aml_audio_free(sdec); return -1; } sdec->ctx = ctx; sdec->in_mask = out_mask; sdec->out_mask = out_mask; adev->sdec = sdec; AM_LOGI("sdec init ok out_mask 0x%x ch %d block %d", out_mask, aml_audio_sdec_out_channels(ctx), AUDIO_SDEC_BLOCK_FRAMES); return 0; } void audio_sdec_deinit(struct aml_audio_device *adev) { struct audio_sdec *sdec; if (!adev || !adev->sdec) return; sdec = adev->sdec; if (sdec->ctx) { aml_audio_sdec_destroy(sdec->ctx); sdec->ctx = NULL; } if (sdec->out_buf) { aml_audio_free(sdec->out_buf); sdec->out_buf = NULL; } aml_audio_free(sdec); adev->sdec = NULL; AM_LOGI("sdec deinit"); } int audio_sdec_process(struct aml_audio_device *adev, struct audio_buffer *in, struct audio_buffer *out) { struct audio_sdec *sdec; uint32_t in_mask; int in_ch; int out_ch; int bps; int total_frames; int processed_frames; int block_frames; size_t need_out_bytes; SDEC_Error err; if (!adev || !adev->sdec || !in || !out) return -1; if (!check_audio_buffer_valid(in)) return -1; sdec = adev->sdec; if (!sdec->ctx) return -1; if (in->format != AUDIO_FORMAT_PCM_16_BIT && in->format != AUDIO_FORMAT_PCM_32_BIT) { AM_LOGE("sdec unsupported format 0x%x", in->format); return -1; } in_mask = audio_sdec_mask_from_buffer(in); if (in_mask == 0) { AM_LOGE("sdec invalid in_mask ch %d mask 0x%x", in->channel, in->channel_mask); return -1; } if (in_mask == sdec->out_mask) { *out = *in; return 0; } if (audio_sdec_ensure_route(sdec, in_mask) != 0) return -1; in_ch = aml_audio_sdec_in_channels(sdec->ctx); out_ch = aml_audio_sdec_out_channels(sdec->ctx); if (in_ch <= 0 || out_ch <= 0) return -1; if (in->channel != in_ch) { AM_LOGE("sdec in ch %d != ctx %d mask 0x%x", in->channel, in_ch, in_mask); return -1; } bps = audio_bytes_per_sample(in->format); if (bps <= 0) return -1; if (in->size % ((size_t)in_ch * (size_t)bps) != 0) { AM_LOGE("sdec size %d not aligned to %dch bps %d", in->size, in_ch, bps); return -1; } total_frames = in->size / (in_ch * bps); if (total_frames <= 0) return -1; need_out_bytes = (size_t)total_frames * (size_t)out_ch * (size_t)bps; if (aml_audio_check_and_realloc(&sdec->out_buf, &sdec->out_buf_sz, need_out_bytes)) { AM_LOGE("sdec realloc out_buf %zu fail", need_out_bytes); return -1; } if (in->buffer == sdec->out_buf) { AM_LOGE("sdec in/out buffer overlap"); return -1; } processed_frames = 0; block_frames = AUDIO_SDEC_BLOCK_FRAMES; while (processed_frames < total_frames) { int frames = total_frames - processed_frames; if (frames > block_frames) frames = block_frames; if (in->format == AUDIO_FORMAT_PCM_16_BIT) { const int16_t *in_s16 = (const int16_t *)in->buffer; int16_t *out_s16 = (int16_t *)sdec->out_buf; err = aml_audio_sdec_process_s16(sdec->ctx, in_s16 + (size_t)processed_frames * (size_t)in_ch, out_s16 + (size_t)processed_frames * (size_t)out_ch, frames); } else { const int32_t *in_s32 = (const int32_t *)in->buffer; int32_t *out_s32 = (int32_t *)sdec->out_buf; err = aml_audio_sdec_process_s32(sdec->ctx, in_s32 + (size_t)processed_frames * (size_t)in_ch, out_s32 + (size_t)processed_frames * (size_t)out_ch, frames); } if (err != SDEC_OK) { AM_LOGE("sdec process failed at frame %d err=%d", processed_frames, err); return -1; } processed_frames += frames; } *out = *in; out->buffer = sdec->out_buf; out->size = (int32_t)need_out_bytes; out->channel = out_ch; out->channel_mask = (int)sdec->out_mask; AM_LOGV("sdec in %dch(0x%x) out %dch(0x%x) frames %d bytes %zu", in_ch, in_mask, out_ch, sdec->out_mask, total_frames, need_out_bytes); return 0; } /* * Packed layout is fixed by MS12 lib version: * 8ch (MS12 2.4): L R C LFE LS RS LRS RRS * 10ch (MS12 2.8): L R C LFE LS RS LRS RRS LTF RTF * Indices match SDEC_CH_L .. SDEC_CH_RTF. Height roles exist only on 2.8. */ static int dap_role_to_src_index(SDEC_ChannelRole role) { switch (role) { case SDEC_ROLE_MONO: return SDEC_CH_C; case SDEC_ROLE_FL: return SDEC_CH_L; case SDEC_ROLE_FR: return SDEC_CH_R; case SDEC_ROLE_FC: return SDEC_CH_C; case SDEC_ROLE_LFE: return SDEC_CH_LFE; case SDEC_ROLE_SL: return SDEC_CH_LS; case SDEC_ROLE_SR: return SDEC_CH_RS; case SDEC_ROLE_RL: return SDEC_CH_LRS; case SDEC_ROLE_RR: return SDEC_CH_RRS; case SDEC_ROLE_TFL: return SDEC_CH_LTF; case SDEC_ROLE_TFR: return SDEC_CH_RTF; case SDEC_ROLE_TRL: return SDEC_CH_LRS; case SDEC_ROLE_TRR: return SDEC_CH_RRS; default: return -1; } } int audio_sdec_remap_ms12_dap_raw(struct aml_audio_device *adev, struct audio_buffer *in, struct audio_buffer *out) { struct dolby_ms12_desc *ms12; uint32_t dst_mask; SDEC_LayoutDesc dst_layout; int8_t remap_table[SDEC_MAX_CHANNELS] = { 0 }; int dst_ch; int src_ch; int sample_size; int frames; size_t need_bytes; int i; if (!adev || !in || !out || !in->buffer || in->size <= 0) return -1; ms12 = &adev->ms12; src_ch = in->channel; if (src_ch <= 0) return -1; dst_mask = (uint32_t)in->channel_mask; if (dst_mask == 0 || dst_mask == (uint32_t)AUDIO_CHANNEL_INVALID) dst_mask = (uint32_t)audio_channel_out_mask_from_count((uint32_t)src_ch); dst_ch = audio_channel_count_from_out_mask((audio_channel_mask_t)dst_mask); if (dst_ch <= 0 || dst_ch > src_ch) { AM_LOGW("dap raw remap skip src %d dst %d mask 0x%x", src_ch, dst_ch, dst_mask); return -1; } if (aml_audio_sdec_layout_from_mask(dst_mask, &dst_layout) != SDEC_OK) return -1; if (dst_layout.count != dst_ch) return -1; for (i = 0; i < dst_ch; i++) { remap_table[i] = (int8_t)dap_role_to_src_index(dst_layout.roles[i]); if (remap_table[i] < 0 || remap_table[i] >= src_ch) { AM_LOGW("dap raw remap invalid dst[%d] role %d src_ch %d", i, dst_layout.roles[i], src_ch); return -1; } } if (in->format != AUDIO_FORMAT_PCM_16_BIT && in->format != AUDIO_FORMAT_PCM_32_BIT) { AM_LOGW("dap raw remap expect pcm16/32, got 0x%x", in->format); return -1; } sample_size = audio_bytes_per_sample((audio_format_t)in->format); if (sample_size <= 0) return -1; if (in->size % (src_ch * sample_size) != 0) { AM_LOGW("dap raw remap size %d not aligned to %dch bps %d", in->size, src_ch, sample_size); return -1; } frames = in->size / (src_ch * sample_size); if (frames <= 0) return -1; need_bytes = (size_t)frames * (size_t)dst_ch * (size_t)sample_size; if (aml_audio_check_and_realloc(&ms12->dap_raw_remap_buf, &ms12->dap_raw_remap_buf_sz, need_bytes)) return -1; memcpy_by_index_array(ms12->dap_raw_remap_buf, (uint32_t)dst_ch, in->buffer, (uint32_t)src_ch, remap_table, sample_size, frames); *out = *in; out->buffer = ms12->dap_raw_remap_buf; out->size = (int32_t)need_bytes; out->channel = dst_ch; out->channel_mask = (int)dst_mask; AM_LOGV("dap raw remap %dch -> %dch(0x%x) frames %d", src_ch, dst_ch, dst_mask, frames); return 0; }