/* * 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_llp" //#define LOG_NDEBUG 0 #include #include #include #include #include #include #include #include #include "aml_audio_log.h" #include "aml_audio_output.h" #include "audio_buffer.h" #ifndef NO_AUDIO_CAP #include #endif #include "audio_hw.h" #include "audio_hw_utils.h" #include "aml_audio_stream.h" #include "audio_hw_ms12_v2.h" #include "audio_llp.h" int llp_init(struct aml_audio_device *adev, int nChannel) { int ret = 0; #ifndef NO_AUDIO_CAP do { audio_llp_ctx_t *llp_ctx = aml_audio_calloc(1, sizeof (audio_llp_ctx_t)); if (NULL == llp_ctx) { AM_LOGW("llp_ctx malloc error!"); ret = -1; break; } char *llp_buf = IpcBuffer_create(LLP_BUFFER_NAME, LLP_BUFFER_SIZE); if (!llp_buf) { AM_LOGE("LLP IPC buffer create failed."); aml_audio_free(llp_ctx); ret = -1; break; } llp_ctx->llp_buf = llp_buf; llp_ctx->nChannel = nChannel; pthread_mutex_lock(&(adev->llp_lock)); adev->llp_ctx = llp_ctx; pthread_mutex_unlock(&(adev->llp_lock)); AM_LOGI("LLP INIT llp_buf:%p, llp_ctx:%p", llp_buf, llp_ctx); if (6 == nChannel) { /* Allow MS12 MC output when LLP input is 6ch */ adev->sink_allow_max_channel = true; } aml_alsa_set_llp(true); if (eDolbyMS12Lib == adev->dolby_lib_type) { set_ms12_alsa_limit_frame(&adev->ms12, MS12_ALSA_LOW_LIMIT_FRAME); struct aml_arc_hdmi_desc* hdmi_descs = NULL; hdmi_descs = get_arc_hdmi_cap(adev); if (((PCM == adev->hdmi_format) || (BYPASS == adev->hdmi_format)) && (AUDIO_FORMAT_PCM_16_BIT == adev->optical_format) && (hdmi_descs->pcm_fmt.max_channels >= 6)) { set_ms12_mch_enable(&(adev->ms12), true); } else { set_ms12_mch_enable(&(adev->ms12), false); } AM_LOGI("hdmi_format:%d, optical:%#x, max_channel:%d, mch_exist:%d", adev->hdmi_format, adev->optical_format, hdmi_descs->pcm_fmt.max_channels, aml_mch_pcm_stream_exist(adev) ); } } while (0); #endif return ret; } void llp_deinit(struct aml_audio_device *adev) { #ifndef NO_AUDIO_CAP pthread_mutex_lock(&(adev->llp_lock)); if (adev->llp_ctx) { audio_llp_ctx_t *llp_ctx = (audio_llp_ctx_t *)(adev->llp_ctx); if (llp_ctx->llp_buf) { IpcBuffer_destroy(llp_ctx->llp_buf); } aml_audio_free(llp_ctx); adev->llp_ctx = NULL; } pthread_mutex_unlock(&(adev->llp_lock)); aml_alsa_set_llp(false); adev->sink_allow_max_channel = false; #endif return; } void llp_injection_and_mixer(struct aml_audio_device *adev, void *main_buffer, int main_channels) { #ifndef NO_AUDIO_CAP int ret = -1; audio_llp_ctx_t *llp_ctx = (audio_llp_ctx_t *)adev->llp_ctx; if (NULL == llp_ctx) { return; } pthread_mutex_lock(&(adev->llp_lock)); if (!llp_ctx->llp_buf) { pthread_mutex_unlock(&(adev->llp_lock)); AM_LOGW("llp_buf NULL!"); return; } char llp_bufin[256*6*2] = {0}; char llp_bufout[256*8*2] = {0}; int outsamples = 0; ret = llp_input_process(adev->llp_ctx, llp_bufin, &outsamples, (int)aml_audio_pcm_out_get_delayframe(adev, true)); pthread_mutex_unlock(&(adev->llp_lock)); AM_LOGI("outsamples:%d, ret:%d, output_multipcm_ch:%d, main_channels:%d", outsamples, ret, adev->output_multipcm_ch, main_channels); if ((0 == ret) && (0 != outsamples)) { /*dump llp input*/ if (get_debug_value(AML_DUMP_AUDIOHAL_IN)) { aml_dump_audio_bitstreams_with_id("input_llp.raw", llp_bufin, 256*6*2, 0); } switch (main_channels) { case 2: extend_channel_6_2(llp_bufout, llp_bufin, 256, 2, 6); break; case 8: extend_channel_6_8(llp_bufout, llp_bufin, 256, 8, 6); break; default: AM_LOGW("unsupport channels:%d", main_channels); break; } do_mixing_specified_channel_cnt(main_buffer, llp_bufout, 256, AUDIO_FORMAT_PCM_16_BIT, AUDIO_FORMAT_PCM_16_BIT, main_channels); } #endif return; } int llp_input_process(audio_llp_ctx_t *llp_ctx, void *outbuffer, int *outsamples, int alsa_level) { #ifndef NO_AUDIO_CAP short *in; unsigned offset, level; uint64_t wp; int nChannel; bool fill_silence = false; struct timespec ts; if (NULL == outbuffer || NULL == outsamples || NULL == llp_ctx) { AM_LOGE("outbuffer:%p, outsamples:%p, llp_ctx:%p, NULL ERR!!", outbuffer, outsamples, llp_ctx); return -1; } *outsamples = 0; wp = IpcBuffer_get_wr_pos(LLP_BUFFER_NAME); nChannel = llp_ctx->nChannel; AM_LOGI("get buffer, llp_ctx:%p, llp_ctx->llp_buf:%p, wp:%"PRIu64", llp_ctx->llp_rp:%"PRIu64", nChannel:%d", llp_ctx, llp_ctx->llp_buf, wp, llp_ctx->llp_rp, nChannel); if (0 == nChannel) { AM_LOGE("error nChannel:0, need check!!"); return -1; } level = (wp - llp_ctx->llp_rp) % LLP_BUFFER_SIZE; if (level < 256 * nChannel * 2) { if (alsa_level > 48 * 8) { IpcBuffer_setMeta(llp_ctx->llp_buf, wp / nChannel / 2, alsa_level + level / nChannel / 2); clock_gettime(CLOCK_MONOTONIC_RAW, &ts); AM_LOGI("[%lld.%.9ld]Holding silence, alsa_level = %d, input_level = %d", (long long)ts.tv_sec, ts.tv_nsec, alsa_level, level / nChannel / 2); return 0; } fill_silence = true; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); AM_LOGI("[%lld.%.9ld]llp underrun, fill silence", (long long)ts.tv_sec, ts.tv_nsec); if (llp_ctx->llp_underrun_wp != wp) { IpcBuffer_inc_underrun(llp_ctx->llp_buf); llp_ctx->llp_underrun_wp = wp; AM_LOGI("[%lld.%.9ld] inc underrun", (long long)ts.tv_sec, ts.tv_nsec); } } llp_ctx->llp_rp = wp - level; // normalize offset = llp_ctx->llp_rp % LLP_BUFFER_SIZE; in = (short *)(IpcBuffer_get_ptr(LLP_BUFFER_NAME) + offset); if (!fill_silence) { memcpy(outbuffer, in, 256*2*nChannel); level -= 256 * nChannel * 2; llp_ctx->llp_rp += nChannel * 2 * 256; *outsamples = 256; } IpcBuffer_setMeta(llp_ctx->llp_buf, wp / nChannel / 2, alsa_level + level / nChannel / 2); clock_gettime(CLOCK_MONOTONIC_RAW, &ts); AM_LOGI("[%lld.%.9ld] llp wp = %" PRIu64 ", rp = %" PRIu64 ", alsa_level = %d, llp input level = %d, nChannel:%d", (long long)ts.tv_sec, ts.tv_nsec, wp / 12, llp_ctx->llp_rp / 12, alsa_level, level / nChannel / 2, nChannel); #endif return 0; }