Files

357 lines
13 KiB
C
Raw Permalink Normal View History

2026-08-13 11:28:40 +08:00
/*
* Copyright (C) 2017 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_project_dtv"
//#define LOG_NDEBUG 0
#include "dmx_v2.h"
#include "audio_mediasync.h"
#include "dtv_input.h"
#include <errno.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/prctl.h>
static int init_demux_by_id(int dmx_id, int* dmx_fd)
{
int fd = 0;
int ret = -1;
aml_audio_control *aml_actrl = get_audio_control();
struct dmx_pes_filter_params params = {
.pid = 0x6000,
.input = DMX_IN_FRONTEND,
.output = DMX_OUT_TAP,
.pes_type = DMX_PES_AUDIO0,
.flags = DMX_ES_OUTPUT,
};
//1. open filter
if (dmx_id == 0) {
fd = open(DEMUX0_NAME, O_NONBLOCK | O_RDWR);
} else {
fd = open(DEMUX1_NAME, O_NONBLOCK | O_RDWR);
}
if (fd <= 0) {
AM_LOGE("open DEMUX%d fail, Error:%s", dmx_id, strerror(errno));
goto open_fail;
} else {
AM_LOGD("open DEMUX%d ok, fd:%d", dmx_id, fd);
}
//2. set pes filter
ret = ioctl(fd, DMX_SET_PES_FILTER, (long)&params);
if (ret != 0) {
AM_LOGE("set pes filter failed ret = %d(%s)", ret, strerror(errno));
goto ioctl_fail;
}
//5. set the size of the circular buffer used for filtered data. default is 8k bytes.
ret = ioctl(fd, DMX_SET_BUFFER_SIZE, 1024 * 1024);
if (ret != 0) {
AM_LOGE("set buffer size failed ret = %d(%s)", ret, strerror(errno));
goto ioctl_fail;
}
//6. start filter
ret = ioctl(fd, DMX_START, 0);
if (ret != 0) {
AM_LOGE("start filter failed ret = %d(%s)", ret, strerror(errno));
goto ioctl_fail;
}
*dmx_fd = fd;
AM_LOGD("init dmx%d ok, fd(%d), ret = %d", dmx_id, fd, ret);
return ret;
ioctl_fail:
close(fd);
open_fail:
return -1;
}
static int destroy_demux(int dmx_fd)
{
int ret = -1;
if (-1 != dmx_fd) {
ret = ioctl(dmx_fd, DMX_STOP, 0);
if (ret != 0) {
AM_LOGE("stop filter%d failed ret = %d(%s)", dmx_fd, ret, strerror(errno));
}
ret = close(dmx_fd);
if (ret != 0) {
AM_LOGE("close fd %d fail, Error:%s", dmx_fd, strerror(errno));
} else {
AM_LOGD("close demux_fd[%d] ok", dmx_fd);
}
}
return ret;
}
static int get_demux_data(int fd, struct mAudioEsData *es_data, int *exit)
{
/**
* struct dmx_non_sec_es_header - non-sec Elementary Stream (ES) Header
*
* @pts_dts_flag:[1:0], 01:pts valid, 10:dts valid
* @pts: pts value
* @dts: dts value
* @len: data len
* struct dmx_non_sec_es_header {
* __u8 pts_dts_flag;
* __u64 pts;
* __u64 dts;
* __u32 len;
};*/
int ret = 0;
char head_buf[sizeof(struct dmx_non_sec_es_header)] = {0};
struct dmx_non_sec_es_header *header = NULL;
if (fd == 0 || es_data == NULL) {
AM_LOGE("fd is %d, es_data is %p", fd, es_data);
return -1;
}
//read es header
int read_len = 0, need_read = 0;
do {
need_read = sizeof(struct dmx_non_sec_es_header) - read_len;
ret = read(fd, head_buf + read_len, need_read);
if (ret >= 0) {
read_len += ret;
}
if (read_len < sizeof(struct dmx_non_sec_es_header)) {
usleep (1000);
}
AM_LOGV("%p, %d. ret %d", exit, *exit, ret);
} while (*exit == 0 && read_len < sizeof(struct dmx_non_sec_es_header));
if (*exit || read_len < sizeof(struct dmx_non_sec_es_header)) {
AM_LOGE("read es header fail, read_len=%d(%d), exit %d, err:%s", read_len, sizeof(struct dmx_non_sec_es_header), *exit, strerror(errno));
return -1;
} else {
header = (struct dmx_non_sec_es_header *)head_buf;
AM_LOGV("header->len %d, header->pts %llu, header->pts_dts_flag %d", header->len, header->pts, header->pts_dts_flag);
if (header->len < 0 || header->len > ES_BUF_SIZE) {
AM_LOGE("data len invalid %d ", header->len);
header->len = 0;
}
es_data->pts = header->pts;
es_data->pts_dts_flag = header->pts_dts_flag;
if (header->len) {
// read es data
read_len = 0;
do {
need_read = header->len - read_len;
ret = read(fd, es_data->data + read_len, need_read);
if (ret >= 0) {
read_len += ret;
}
if (read_len < header->len) {
AM_LOGV("ret %d read_len %d, frame len %d", ret, read_len, header->len);
usleep (1000);
}
} while (*exit == 0 && read_len < header->len);
es_data->size = read_len;
}
}
return 0;
}
static void *dtv_output_threadloop(void *data)
{
struct aml_audio_device *aml_dev = (struct aml_audio_device *)adev_get_handle();
struct dtv_play_handle *play_handle = (struct dtv_play_handle*)data;
struct audio_stream_out *stream_out = NULL;
struct aml_stream_out *aml_out = NULL;
struct audio_config stream_config;
int ret = -1, nRet = 0, sync_enable = 0;
int dmx_fd = play_handle->demux_fd;
struct mAudioEsData mEsData = { 0 };
char es_buf[ES_BUF_SIZE + sizeof(struct hw_sync_header_v3)] = {0};
char thread_name[128] = { 0 };
struct hw_sync_header_v3 *hw_sync;
//set name for this thread
snprintf(thread_name, 128, "dtv_output_data");
prctl(PR_SET_NAME, (unsigned long)thread_name);
AM_LOGI("created. thread id:%ld, play_handle %p", pthread_self(), play_handle);
//1.prepare a output stream
stream_config.sample_rate = 48000;
stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
stream_config.format = play_handle->codec;
if (aml_dev->hw_device.open_output_stream) {
ret = aml_dev->hw_device.open_output_stream((struct audio_hw_device *)aml_dev, 0,
AUDIO_DEVICE_OUT_SPEAKER, // devices_t
AUDIO_OUTPUT_FLAG_DIRECT | AUDIO_OUTPUT_FLAG_HW_AV_SYNC, // flags
&stream_config, &stream_out, "AML_DTV_SOURCE");
}
if (ret < 0 || !stream_out) {
AM_LOGE("open output stream fail, ret = %d, out = %p. open func: %p", ret, stream_out, aml_dev->hw_device.open_output_stream);
goto exit_open;
}
aml_out = (struct aml_stream_out *)stream_out;
aml_out->dec_param.dec_gain = 1.0;
//init mediasync for this output stream
if (aml_out->avsync_ctx) {
aml_out->avsync_type = AVSYNC_TYPE_MEDIASYNC;
hwsync_mediasync_outset(aml_out, &sync_enable, TEMP_SET_MEDIASYNC_INSTANCE_ID, stream_config.format);
if (NULL == aml_out->avsync_ctx->mediasync_ctx || NULL == aml_out->avsync_ctx->mediasync_ctx->handle) {
goto exit_sync;
}
} else {
AM_LOGE("aml_out->avsync_ctx NULL error!");
goto exit_sync;
}
aml_out->with_header = true;
mEsData.data = es_buf;
AM_LOGI("create output stream(%p) success, thread_exit %d, buf %p. sync_enable %d.avsync ctx %p, mediasync ctx %p, handle %p",
aml_out, play_handle->dtv_output_thread_exit, es_buf, sync_enable, aml_out->avsync_ctx, aml_out->avsync_ctx->mediasync_ctx, aml_out->avsync_ctx->mediasync_ctx->handle);
while (!play_handle->dtv_output_thread_exit) {
AM_LOGI_IF(aml_dev->debug_flag, "exit: %p (%d)", &(play_handle->dtv_output_thread_exit), play_handle->dtv_output_thread_exit);
nRet = get_demux_data(dmx_fd, &mEsData, &(play_handle->dtv_output_thread_exit));
aml_out->pts_dts_flag = mEsData.pts_dts_flag;
if (nRet != 0 || !mEsData.data || !mEsData.size) {
AM_LOGI_IF(aml_dev->debug_flag, "nRet %d, mEsData %p(data %p, size %d)", nRet, &mEsData, mEsData.data, mEsData.size);
usleep(5000);
continue;
} else {
AM_LOGI_IF(aml_dev->debug_flag, "%p, main size %d, pts %"PRIx64"(%"PRIx64"), diff %"PRId64"ms, pts flag %d",
&mEsData, mEsData.size, mEsData.pts, aml_out->last_pts, (mEsData.pts - aml_out->last_pts)/90, mEsData.pts_dts_flag);
// if first package pts is invalid, we need drop it
if ((!aml_out->first_pts_set) && (NULL_INT64 == mEsData.pts)) {
AM_LOGE("first data pts is NULL_INT64");
continue;
}
aml_out->first_pts_set = 1;
if (NULL_INT64 != mEsData.pts) {
aml_out->last_pts = mEsData.pts;
} else {
mEsData.pts = aml_out->last_pts;
}
if (get_debug_value(AML_DUMP_AUDIOHAL_DTV)) {
aml_dump_audio_bitstreams("dtv_main_audio_dmx.es", mEsData.data, mEsData.size);
}
}
hw_sync = (struct hw_sync_header_v3 *)es_buf;
memmove(es_buf + sizeof (*hw_sync), es_buf, mEsData.size);
hw_sync_set_ver_v3(hw_sync);
hw_sync_set_header_size(hw_sync->size, mEsData.size);
hw_sync_set_header_pts(hw_sync->pts, mEsData.pts * MS_TO_NS / 90);
hw_sync_set_header_pts(hw_sync->c_start_duration, 0);
hw_sync_set_header_pts(hw_sync->c_end_duration, 0);
hw_sync_set_header_offset(hw_sync->offset, 0);
//decode data
ret = stream_out->write(stream_out, es_buf, mEsData.size + sizeof (*hw_sync));
if (ret < 0) {
AM_LOGE("stream_out->write ret %d, need check!!", ret);
}
}
exit_sync:
AM_LOGI("exit %d", play_handle->dtv_output_thread_exit);
if (aml_out && aml_dev->hw_device.close_output_stream) {
aml_dev->hw_device.close_output_stream((struct audio_hw_device *)aml_dev, stream_out);
}
exit_open:
AM_LOGI("--");
return (void *)0;
}
int dtv_start(adec_play_info_t* dtv_playback_info)
{
int dmx_id = -1, ret = -1, dmx_fd = -1;
struct dtv_play_handle* p_handle = NULL;
if (dtv_playback_info->src_id == ADEC_SRC_ATP0) {
dmx_id = 0;
} else if (dtv_playback_info->src_id == ADEC_SRC_ATP1) {
dmx_id = 1;
} else {
AM_LOGE("invalid source:%d", dtv_playback_info->src_id);
return -1;
}
ret = init_demux_by_id(dmx_id, &dmx_fd);
if (!ret && dmx_fd != -1) {
p_handle = aml_audio_calloc(1, sizeof(struct dtv_play_handle));
if (p_handle == NULL) {
AM_LOGE("calloc play_handle failed");
goto calloc_err;
}
p_handle->demux_fd = dmx_fd;
p_handle->codec = dtv_playback_info->adec_codec;
p_handle->dtv_output_thread_exit = 0;
ret = pthread_create(&(p_handle->dtv_output_threadID), NULL,
&dtv_output_threadloop, p_handle);
if (ret != 0) {
AM_LOGE("Create dtv_output thread failed");
goto thread_err;
} else {
p_handle->dtv_thread_created = 1;
AM_LOGD("Create dtv_output thread success(%d), handle %p", p_handle->dtv_thread_created, p_handle);
dtv_playback_info->handle = p_handle;
}
return ret;
}
thread_err:
aml_audio_free(p_handle);
calloc_err:
destroy_demux(dmx_fd);
return -1;
}
int dtv_stop(adec_play_info_t* dtv_playback_info)
{
int ret = -1;
if (dtv_playback_info->src_id != ADEC_SRC_ATP0 && dtv_playback_info->src_id != ADEC_SRC_ATP1) {
AM_LOGE("invalid source:%d", dtv_playback_info->src_id);
return ret;
}
AM_LOGD("handle:%p", dtv_playback_info->handle);
if (dtv_playback_info->handle) {
struct dtv_play_handle* p_handle = (struct dtv_play_handle*)dtv_playback_info->handle;
p_handle->dtv_output_thread_exit = 1;
AM_LOGD("handle:%p, %p, %d", dtv_playback_info->handle, &(p_handle->dtv_output_thread_exit), p_handle->dtv_output_thread_exit);
if (p_handle->dtv_thread_created) {
pthread_join(p_handle->dtv_output_threadID, NULL);
p_handle->dtv_thread_created = 0;
}
ret = destroy_demux(p_handle->demux_fd);
p_handle->demux_fd = 0;
aml_audio_free(p_handle);
dtv_playback_info->handle = NULL;
}
return ret;
}