391 lines
11 KiB
C
391 lines
11 KiB
C
/*
|
|
* 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 <errno.h>
|
|
#include <system/audio.h>
|
|
#include <audio_utils/primitives.h>
|
|
|
|
#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;
|
|
}
|