Files
2026-08-24 10:03:24 +08:00

224 lines
7.5 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <inttypes.h>
#include "aml_audio_log.h"
#include "aml_audio_output.h"
#include "audio_buffer.h"
#ifndef NO_AUDIO_CAP
#include <IpcBuffer/IpcBuffer_c.h>
#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;
}