357 lines
13 KiB
C
357 lines
13 KiB
C
/*
|
|
* 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)¶ms);
|
|
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;
|
|
}
|
|
|