/* * 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 #include #include #include #include #include #include #include #include #include #include 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; }