You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

827 lines
21 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2019. All rights reserved.
* Description: MPI function file for Huanglong audio vir capture
* Author: audio
* Create: 2019-05-30
*/
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "mpi_ao_debug.h"
#include "mpi_ao_vir.h"
#include "securec.h"
#include "mpi_memory_ext.h"
#include "soc_math.h"
#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* __cplusplus */
#define virtrack_lock(mutex) (td_void)pthread_mutex_lock(&(mutex))
#define virtrack_unlock(mutex) (td_void)pthread_mutex_unlock(&(mutex))
#define VIR_BUF_PCM_SAMPLES_NUMBER 1024
#define VT_INVALID_PTS 0xffffffff
#define MS_TO_US_RATE 1000
#define pts_us_to_ms(pts) (td_u32)((pts) / MS_TO_US_RATE)
#define pts_ms_to_us(pts) ((td_s64)(pts) * MS_TO_US_RATE)
static inline td_u32 vt_input_pts(td_s64 pts)
{
return ((pts < 0) || (pts == TD_INVALID_PTS)) ? VT_INVALID_PTS : pts_us_to_ms(pts);
}
static inline td_s64 vt_output_pts(td_u32 pts)
{
return (pts == VT_INVALID_PTS) ? TD_INVALID_PTS : pts_ms_to_us(pts);
}
typedef struct {
pthread_mutex_t mutex;
td_u32 track_flag;
td_void *track[AO_MAX_VIRTUAL_TRACK_NUM];
} vir_track_rs_state;
static vir_track_rs_state g_vir_track = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.track_flag = 0,
};
/* add x and y, saturate to saturation */
static inline td_u32 saturate_add(td_u32 x, td_u32 y, td_u32 saturation)
{
td_u32 res = x + y;
return (res >= saturation) ? (res - saturation) : res;
}
static inline td_u32 vir_track_buf_size(const vir_buffer *buf)
{
return buf->end - buf->start;
}
static inline td_u32 vir_track_buf_data_size(const vir_buffer *buf)
{
td_u32 size = vir_track_buf_size(buf);
return (buf->write >= buf->read) ? (buf->write - buf->read) : (size - buf->read + buf->write);
}
static inline td_u32 vir_track_buf_free_size(const vir_buffer *buf)
{
return vir_track_buf_size(buf) - vir_track_buf_data_size(buf);
}
static td_u32 vir_track_buf_write_data(const vir_buffer *buf, const td_u8 *from,
td_u32 len, td_u32 off)
{
errno_t ret;
td_u32 size = vir_track_buf_size(buf);
td_u32 l = min2(len, size - off);
ret = memcpy_s(buf->buf_base + off, size - off, from, l);
if (ret != EOK) {
return 0;
}
if (len - l == 0) {
return len;
}
ret = memcpy_s(buf->buf_base, buf->read, from + l, len - l);
if (ret != EOK) {
return 0;
}
return len;
}
static td_u32 vir_track_buf_write(vir_buffer *buf, const td_u8 *from, td_u32 len)
{
if (vir_track_buf_write_data(buf, from, len, buf->write) != len) {
return 0;
}
buf->write = saturate_add(buf->write, len, vir_track_buf_size(buf));
return len;
}
static td_u32 ao_frame_pcm_data_size(const ext_ao_frame *ao_frame)
{
if (ao_frame->bit_depth == EXT_BIT_DEPTH_16) {
return ao_frame->pcm_samples * ao_frame->channels * sizeof(td_s16);
} else {
return ao_frame->pcm_samples * ao_frame->channels * sizeof(td_s32);
}
}
static td_void vt_mutex_lock(td_void)
{
if (pthread_mutex_lock(&g_vir_track.mutex) != 0) {
soc_log_err("Lock mutex failed\n");
}
}
static td_void vt_mutex_unlock(td_void)
{
if (pthread_mutex_unlock(&g_vir_track.mutex) != 0) {
soc_log_err("Unlock mutex failed\n");
}
}
static td_void vt_free(td_void *buf)
{
if (buf == TD_NULL) {
return;
}
free(buf);
}
static td_void *vt_malloc(td_u32 size)
{
td_s32 ret;
td_void *buffer = TD_NULL;
if (size == 0) {
soc_log_err("invalid size\n");
return TD_NULL;
}
buffer = malloc(size);
if (buffer == TD_NULL) {
soc_log_err("call malloc( failed\n");
return TD_NULL;
}
ret = memset_s(buffer, size, 0, size);
if (ret != EOK) {
soc_err_print_call_fun_err(memset_s, ret);
free(buffer);
return TD_NULL;
}
return buffer;
}
static td_void vir_reset_pts_que(vir_pts_que *pts_que)
{
pts_que->read = 0;
pts_que->write = 0;
pts_que->last_pts_ms = VT_INVALID_PTS;
}
static td_void vir_find_pts(const vir_buffer *buf, td_u32 *p_found_pts, td_u32 *p_found_pos)
{
td_u32 found_pos;
td_u32 found_pts = VT_INVALID_PTS;
td_u32 read_ptr = buf->read;
const vir_pts_que *pts_que = &(buf->pts_que);
const vir_pts *pts = pts_que->pts_arry;
td_u32 rd_pos = pts_que->read;
td_u32 wt_pos = pts_que->write;
for (found_pos = rd_pos; found_pos != wt_pos; found_pos = (found_pos + 1) % VIR_MAX_STORED_PTS_NUM) {
if (pts[found_pos].beg_ptr < pts[found_pos].end_ptr) {
if ((pts[found_pos].beg_ptr <= read_ptr) && (pts[found_pos].end_ptr >= read_ptr)) {
found_pts = pts[found_pos].pts_ms;
break;
}
} else {
if ((pts[found_pos].beg_ptr <= read_ptr) || (pts[found_pos].end_ptr >= read_ptr)) {
found_pts = pts[found_pos].pts_ms;
break;
}
}
}
if (found_pos == wt_pos) {
found_pos = (td_u32)-1;
}
*p_found_pos = found_pos;
*p_found_pts = found_pts;
}
static td_u32 vir_acquire_pts(const vir_buffer *buf, td_u32 pcm_samples,
td_u32 pcm_sample_rate)
{
td_u32 pts_ms = VT_INVALID_PTS;
td_u32 found_pos = 0;
td_u32 found_pts = 0;
const vir_pts_que *pts_que = &buf->pts_que;
vir_find_pts(buf, &found_pts, &found_pos);
if (found_pts != VT_INVALID_PTS) {
return found_pts;
}
/* can not find a valid PTS */
if (pts_que->last_pts_ms != VT_INVALID_PTS) {
td_u32 delta;
if (pcm_sample_rate == 0) {
return VT_INVALID_PTS;
}
delta = (pcm_samples * 1000) / pcm_sample_rate; /* this 1000 for s to ms */
pts_ms = pts_que->last_pts_ms + delta;
if (pts_ms == VT_INVALID_PTS) {
pts_ms = 0;
}
}
return pts_ms;
}
static td_void vir_release_pts(vir_buffer *buf, td_u32 pts_ms)
{
td_u32 found_pos = 0;
td_u32 found_pts = VT_INVALID_PTS;
vir_pts_que *pts_que = &buf->pts_que;
vir_pts *pts = pts_que->pts_arry;
pts_que->last_pts_ms = pts_ms;
vir_find_pts(buf, &found_pts, &found_pos);
if (found_pos < VIR_MAX_STORED_PTS_NUM) {
pts[found_pos].pts_ms = VT_INVALID_PTS;
pts_que->read = (found_pos + 1) % VIR_MAX_STORED_PTS_NUM;
}
}
static td_void vir_store_pts(vir_buffer *buf, td_u32 pts_ms, td_u32 size)
{
vir_pts_que *pts_que = &(buf->pts_que);
vir_pts *pts_array = pts_que->pts_arry;
td_u32 calc_end_ptr;
/* make sure there are space to store */
if ((pts_que->write + 1) % VIR_MAX_STORED_PTS_NUM == pts_que->read) {
soc_log_warn("not enough PTS buffer, discard current PTS(%d)\n", pts_ms);
return;
}
if ((buf->write + size) < buf->end) {
calc_end_ptr = buf->write + size;
} else {
calc_end_ptr = (((buf->write) + size) - ((buf->end) - (buf->start)));
}
pts_array[pts_que->write].pts_ms = pts_ms;
pts_array[pts_que->write].beg_ptr = buf->write;
pts_array[pts_que->write].end_ptr = calc_end_ptr;
pts_que->write = (pts_que->write + 1) % VIR_MAX_STORED_PTS_NUM;
}
static td_s32 vir_init_buf(vir_buffer *buf, td_u32 size)
{
if ((size < VIR_MIN_OUTBUF_SIZE) || (size > VIR_MAX_OUTBUF_SIZE)) {
soc_log_err(" invalid input buffer size(%d) minsize(%d) maxsize(%d)!\n", size,
VIR_MIN_OUTBUF_SIZE, VIR_MAX_OUTBUF_SIZE);
return SOC_ERR_AO_INVALID_PARA;
}
buf->buf_base = (td_u8 *)vt_malloc(size + VIR_MAX_FRAME_SIZE);
if (buf->buf_base == TD_NULL) {
soc_log_fatal("vt_malloc failed\n");
return SOC_ERR_AO_MALLOC_FAILED;
}
buf->start = 0;
buf->end = size;
buf->read = 0;
buf->write = 0;
buf->bit_depth = EXT_BIT_DEPTH_16;
buf->sample_rate = EXT_SAMPLE_RATE_48K;
buf->channel = EXT_AUDIO_CH_STEREO;
buf->pcm_samples = VIR_BUF_PCM_SAMPLES_NUMBER;
vir_reset_pts_que(&buf->pts_que);
return TD_SUCCESS;
}
static td_void vir_deinit_buf(vir_buffer *buf)
{
if (buf->buf_base != TD_NULL) {
vt_free(buf->buf_base);
buf->buf_base = TD_NULL;
}
vt_free(buf);
}
static td_void vir_flush_buf(vir_buffer *buf)
{
buf->write = buf->read;
vir_reset_pts_que(&(buf->pts_que));
}
static td_u32 vir_get_free_id(td_void)
{
td_u32 i;
for (i = 0; i < AO_MAX_VIRTUAL_TRACK_NUM; i++) {
if (!((g_vir_track.track_flag & ((td_u32)1L << i)) != 0)) {
return i;
}
}
return AO_MAX_VIRTUAL_TRACK_NUM;
}
static td_u32 vir_track2_vir_id(td_handle h_track)
{
return ((h_track & AO_TRACK_CHNID_MASK) - AO_MAX_REAL_TRACK_NUM);
}
static vir_track_state *vir_track_find_by_handle(td_handle h_track)
{
td_u32 id;
vir_track_state *track = TD_NULL;
id = vir_track2_vir_id(h_track);
if (id >= AO_MAX_VIRTUAL_TRACK_NUM) {
soc_log_err("invalid virtual track ID!\n");
return TD_NULL;
}
track = (vir_track_state *)g_vir_track.track[id];
if ((track == TD_NULL) || (track->buf == TD_NULL)) {
soc_log_err("virtual track(%d) is null!\n", id);
return TD_NULL;
}
return track;
}
td_void vir_init_rs(td_void)
{
td_u32 i;
vt_mutex_lock();
g_vir_track.track_flag = 0;
for (i = 0; i < AO_MAX_VIRTUAL_TRACK_NUM; i++) {
g_vir_track.track[i] = TD_NULL;
}
vt_mutex_unlock();
}
td_void vir_de_init_rs(td_void)
{
td_u32 i;
vt_mutex_lock();
g_vir_track.track_flag = 0;
for (i = 0; i < AO_MAX_VIRTUAL_TRACK_NUM; i++) {
g_vir_track.track[i] = TD_NULL;
}
vt_mutex_unlock();
}
static td_s32 virtual_track_state_init(const ext_ao_track_attr *track_attr, vir_track_state *vir)
{
td_s32 ret;
ret = memcpy_s(&vir->track_attr, sizeof(vir->track_attr),
track_attr, sizeof(*track_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
vir->buf_size = track_attr->output_buf_size;
return TD_SUCCESS;
}
static td_s32 virtual_track_buf_init(vir_track_state *vir)
{
td_s32 ret;
vir->buf = (vir_buffer *)vt_malloc(sizeof(vir_buffer));
if (vir->buf == TD_NULL) {
return SOC_ERR_AO_MALLOC_FAILED;
}
ret = vir_init_buf(vir->buf, vir->buf_size);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(vir_init_buf, ret);
goto out;
}
return TD_SUCCESS;
out:
vt_free(vir->buf);
return ret;
}
static td_s32 virtual_track_create(const ext_ao_track_attr *track_attr, td_handle *ph_track)
{
td_s32 ret;
vir_track_state *vir = TD_NULL;
td_u32 vir_id;
vir_id = vir_get_free_id();
if (vir_id == AO_MAX_VIRTUAL_TRACK_NUM) {
soc_log_err("vir_get_free_id failed\n");
return TD_FAILURE;
}
vir = (vir_track_state *)vt_malloc(sizeof(vir_track_state));
if (vir == TD_NULL) {
soc_log_err("vt_malloc failed\n");
return SOC_ERR_AO_MALLOC_FAILED;
}
ret = virtual_track_state_init(track_attr, vir);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(virtual_track_state_init, ret);
goto out0;
}
ret = virtual_track_buf_init(vir);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(virtual_track_buf_init, ret);
goto out0;
}
g_vir_track.track_flag |= (td_u32)1L << vir_id;
g_vir_track.track[vir_id] = (td_void *)vir;
*ph_track = (SOC_ID_AO << 16) | /* this 16 is shift number */
(SOC_ID_MASTER_SLAVE_TRACK << 8) | /* this 8 is shift number */
(AO_MAX_REAL_TRACK_NUM + vir_id);
return TD_SUCCESS;
out0:
vt_free(vir);
return ret;
}
td_s32 vir_create_track(const ext_ao_track_attr *track_attr, td_handle *track)
{
td_s32 ret;
check_ao_null_ptr(track);
check_ao_null_ptr(track_attr);
vt_mutex_lock();
ret = virtual_track_create(track_attr, track);
vt_mutex_unlock();
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(virtual_track_create, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 vir_destroy_track(td_handle track)
{
vir_track_state *vir = TD_NULL;
td_u32 vir_id;
check_track(track);
vt_mutex_lock();
vir_id = vir_track2_vir_id(track);
if (vir_id >= AO_MAX_VIRTUAL_TRACK_NUM) {
soc_log_err("invalid virtual track ID!\n");
vt_mutex_unlock();
return TD_FAILURE;
}
vir = (vir_track_state *)g_vir_track.track[vir_id];
if (vir == TD_NULL) {
soc_log_err("virtual track(%d) is null!\n", vir_id);
vt_mutex_unlock();
return TD_FAILURE;
}
if (vir->buf != TD_NULL) {
vir_deinit_buf(vir->buf);
vir->buf = TD_NULL;
}
vt_free(vir);
g_vir_track.track[vir_id] = TD_NULL;
g_vir_track.track_flag &= ~((td_u32)1L << vir_id);
vt_mutex_unlock();
return TD_SUCCESS;
}
static td_s32 vir_track_get_attr(td_handle h_track, ext_ao_track_attr *attr)
{
td_s32 ret;
vir_track_state *track = TD_NULL;
track = vir_track_find_by_handle(h_track);
if (track == TD_NULL) {
soc_log_err("Call vir_track_find_by_handle failed\n");
return SOC_ERR_AO_INVALID_PARA;
}
ret = memcpy_s(attr, sizeof(*attr),
&track->track_attr, sizeof(ext_ao_track_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 vir_get_attr(td_handle track, ext_ao_track_attr *attr)
{
td_s32 ret;
check_track(track);
vt_mutex_lock();
ret = vir_track_get_attr(track, attr);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(vir_track_get_attr, ret);
}
vt_mutex_unlock();
return ret;
}
static td_bool ao_frame_not_change(const ext_ao_frame *ao_frame, const vir_buffer *buf)
{
return (buf->channel == ao_frame->channels) &&
(buf->bit_depth == ao_frame->bit_depth) &&
(buf->sample_rate == ao_frame->sample_rate);
}
static td_void vir_track_check_ao_frame(const ext_ao_frame *ao_frame, vir_buffer *buf)
{
if (ao_frame_not_change(ao_frame, buf) == TD_TRUE) {
return;
}
/* channel, sample rate or bit depth changed, reset track buffer */
vir_flush_buf(buf);
buf->channel = ao_frame->channels;
buf->bit_depth = ao_frame->bit_depth;
buf->sample_rate = ao_frame->sample_rate;
}
static td_s32 vir_track_convert_ao_frame(const ext_ao_frame *ao_frame_in, ext_ao_frame *ao_frame_out)
{
td_s32 ret;
ret = memcpy_s(ao_frame_out, sizeof(*ao_frame_out),
ao_frame_in, sizeof(*ao_frame_in));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
if (ao_frame_out->pcm_samples == 0) {
soc_log_warn("no pcm data in ao frame\n");
return SOC_ERR_AO_NOTSUPPORT;
}
if (ao_frame_out->sample_rate == 0) {
soc_log_warn("invalid sample rate in ao frame\n");
return SOC_ERR_AO_INVALID_INFRAME;
}
ao_frame_out->channels = min2(ao_frame_out->channels, EXT_AUDIO_CH_STEREO);
return TD_SUCCESS;
}
static td_s32 vir_track_save_ao_frame_data(vir_buffer *buf, const ext_ao_frame *ao_frame)
{
td_u32 size;
size = ao_frame_pcm_data_size(ao_frame);
if (vir_track_buf_free_size(buf) <= size) {
soc_log_info("track buf is full\n");
return SOC_ERR_AO_OUT_BUF_FULL;
}
if (vir_track_buf_write(buf, (td_u8 *)ao_frame->pcm_buffer, size) != size) {
soc_log_err("call vir_track_buf_write failed\n");
return TD_FAILURE;
}
vir_store_pts(buf, vt_input_pts(ao_frame->pts), size);
return TD_SUCCESS;
}
static td_s32 vir_track_send_data(td_handle h_track, const ext_ao_frame *ao_frame)
{
td_s32 ret;
ext_ao_frame frame;
vir_track_state *track = TD_NULL;
track = vir_track_find_by_handle(h_track);
if (track == TD_NULL) {
soc_log_err("Call vir_track_find_by_handle failed\n");
return SOC_ERR_AO_INVALID_PARA;
}
check_ao_null_ptr(track->buf);
ret = vir_track_convert_ao_frame(ao_frame, &frame);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(vir_track_convert_ao_frame, ret);
return ret;
}
vir_track_check_ao_frame(&frame, track->buf);
return vir_track_save_ao_frame_data(track->buf, &frame);
}
td_s32 vir_send_data(td_handle track, const ext_ao_frame *ao_frame)
{
td_s32 ret;
check_track(track);
vt_mutex_lock();
ret = vir_track_send_data(track, ao_frame);
vt_mutex_unlock();
return ret;
}
static td_void vir_track_init_ao_frame(const vir_buffer *buf, ext_ao_frame *ao_frame)
{
ao_frame->sample_rate = buf->sample_rate;
ao_frame->bit_depth = buf->bit_depth;
ao_frame->channels = buf->channel;
ao_frame->pcm_samples = buf->pcm_samples;
ao_frame->pcm_buffer = TD_NULL;
ao_frame->pts = TD_INVALID_PTS;
ao_frame->interleaved = TD_TRUE;
ao_frame->frame_index = 0;
ao_frame->bits_bytes = 0;
ao_frame->bits_buffer = TD_NULL;
ao_frame->iec_data_type = 0;
}
static td_s32 vir_track_build_linear_frame(const vir_buffer *buf, td_u32 acquire_size)
{
td_s32 ret;
td_u32 tail_size;
if (buf->write >= buf->read) {
return TD_SUCCESS;
}
tail_size = buf->end - buf->read;
if (acquire_size <= tail_size) {
return TD_SUCCESS;
}
ret = memcpy_s(buf->buf_base + buf->end, VIR_MAX_FRAME_SIZE,
buf->buf_base + buf->start, acquire_size - tail_size);
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 vir_track_acquire_data(const vir_buffer *buf, ext_ao_frame *ao_frame)
{
td_s32 ret;
td_u32 acquire_size;
td_u32 pts_ms;
check_ao_null_ptr(buf->buf_base);
acquire_size = ao_frame_pcm_data_size(ao_frame);
if (vir_track_buf_data_size(buf) < acquire_size) {
return SOC_ERR_AO_VIRTUALBUF_EMPTY;
}
ao_frame->pcm_buffer = (td_s32 *)(buf->buf_base + (buf->read - buf->start));
ret = vir_track_build_linear_frame(buf, acquire_size);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(vir_track_build_linear_frame, ret);
return ret;
}
pts_ms = vir_acquire_pts(buf, ao_frame->pcm_samples, ao_frame->sample_rate);
ao_frame->pts = vt_output_pts(pts_ms);
return TD_SUCCESS;
}
static td_s32 vir_track_acquire_frame(td_handle h_track, ext_ao_frame *ao_frame)
{
td_s32 ret;
vir_track_state *track = TD_NULL;
track = vir_track_find_by_handle(h_track);
if (track == TD_NULL) {
soc_log_err("Call vir_track_find_by_handle failed\n");
return SOC_ERR_AO_INVALID_PARA;
}
check_ao_null_ptr(track->buf);
vir_track_init_ao_frame(track->buf, ao_frame);
ret = vir_track_acquire_data(track->buf, ao_frame);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(vir_track_acquire_data, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 vir_acquire_frame(td_handle track, ext_ao_frame *ao_frame)
{
td_s32 ret;
check_track(track);
vt_mutex_lock();
ret = vir_track_acquire_frame(track, ao_frame);
vt_mutex_unlock();
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(vir_track_acquire_frame, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 vir_track_release_frame(td_handle h_track, const ext_ao_frame *ao_frame)
{
vir_track_state *track = TD_NULL;
vir_buffer *buf = TD_NULL;
track = vir_track_find_by_handle(h_track);
if (track == TD_NULL) {
soc_log_err("Call vir_track_find_by_handle failed\n");
return SOC_ERR_AO_INVALID_PARA;
}
buf = track->buf;
check_ao_null_ptr(buf);
if ((buf->channel != ao_frame->channels) ||
(buf->bit_depth != ao_frame->bit_depth) ||
(buf->sample_rate != ao_frame->sample_rate)) {
return TD_SUCCESS;
}
vir_release_pts(buf, vt_input_pts(ao_frame->pts));
buf->read = saturate_add(buf->read, ao_frame_pcm_data_size(ao_frame),
vir_track_buf_size(buf));
return TD_SUCCESS;
}
td_s32 vir_release_frame(td_handle track, const ext_ao_frame *ao_frame)
{
td_s32 ret;
check_track(track);
vt_mutex_lock();
ret = vir_track_release_frame(track, ao_frame);
vt_mutex_unlock();
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(vir_track_release_frame, ret);
return ret;
}
return TD_SUCCESS;
}
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */