513 lines
19 KiB
C
513 lines
19 KiB
C
/*
|
|
* Copyright (c) 2024 Quaming Intelligent Technology Co., Ltd.
|
|
*
|
|
* All Rights Reserved.
|
|
* Confidential and Proprietary - Quaming Intelligent Technology Co., Ltd.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "common/qua_mm_common.h"
|
|
#include "common/qua_sys_platform.h"
|
|
#include "avext/qua_media_extractor.h"
|
|
#include "avext/qua_media_decoder.h"
|
|
#include "avext/qua_mm_avext.h"
|
|
|
|
void printf_meta(qua_metaformat_t* tinyformat)
|
|
{
|
|
if (!tinyformat)
|
|
return;
|
|
printf("[%s] meta start=============\n", tinyformat->name);
|
|
Pairs *head = tinyformat->list;
|
|
Pairs *cur = head;
|
|
while(cur != NULL) {
|
|
if (cur->valueType == TinyFormat_kTypeInt32) {
|
|
printf("key: %s value: %d \n", cur->key, cur->u.value_int32);
|
|
} else if (cur->valueType == TinyFormat_kTypeInt64) {
|
|
printf("key: %s value: %d \n", cur->key, (int32_t)cur->u.value_int64);
|
|
} else if (cur->valueType == TinyFormat_kTypeString) {
|
|
printf("key: %s value: %s \n", cur->key, (char *)(cur->u.value));
|
|
} else {
|
|
printf("key: %s \n", cur->key);
|
|
}
|
|
cur = cur->next;
|
|
}
|
|
printf("[%s] meta end=============\n", tinyformat->name);
|
|
}
|
|
|
|
void quaPacket_set_default(qua_packet_t* quaPkt)
|
|
{
|
|
memset(quaPkt->data, 0x00, quaPkt->size);
|
|
quaPkt->offset = 0;
|
|
quaPkt->capacity = 0;
|
|
quaPkt->pts = -1;
|
|
quaPkt->dts = -1;
|
|
quaPkt->duration = 0;
|
|
quaPkt->baseTime = 0;
|
|
quaPkt->streamIndex = -1;
|
|
}
|
|
|
|
qua_packet_t* alloc_quaPacket(int size)
|
|
{
|
|
qua_packet_t* quaPkt = (qua_packet_t*)calloc(1, sizeof(qua_packet_t));
|
|
if (quaPkt == NULL)
|
|
return NULL;
|
|
|
|
quaPkt->data = (uint8_t*)calloc(size, sizeof(uint8_t));
|
|
if (quaPkt->data == NULL) {
|
|
free(quaPkt);
|
|
quaPkt = NULL;
|
|
return NULL;
|
|
}
|
|
quaPkt->size = size;
|
|
quaPacket_set_default(quaPkt);
|
|
return quaPkt;
|
|
}
|
|
|
|
void free_quaPacket(qua_packet_t* quaPkt)
|
|
{
|
|
if (quaPkt && quaPkt->data) {
|
|
free(quaPkt->data);
|
|
quaPkt->data = NULL;
|
|
}
|
|
free(quaPkt);
|
|
quaPkt = NULL;
|
|
}
|
|
|
|
void audioFrame_set_default(qua_audio_frame_ext_t* frame)
|
|
{
|
|
memset(frame->data, 0x00, frame->size);
|
|
frame->offset = 0;
|
|
frame->capacity = 0;
|
|
frame->nb_samples = 0;
|
|
frame->sample_fmt = 0;
|
|
frame->sample_rate = 0;
|
|
frame->nb_channels = 0;
|
|
frame->flags = 0;
|
|
frame->pts = 0;
|
|
}
|
|
|
|
qua_audio_frame_ext_t* alloc_quaAudioFrame(int size)
|
|
{
|
|
qua_audio_frame_ext_t *audioFrame = (qua_audio_frame_ext_t *)calloc(1, sizeof(qua_audio_frame_ext_t));
|
|
if (audioFrame == NULL) {
|
|
printf("OOM audioFrame calloc\n");
|
|
return NULL;
|
|
}
|
|
audioFrame->data = (uint8_t *)calloc(size, sizeof(uint8_t));
|
|
if (audioFrame->data == NULL) {
|
|
printf("OOM audioFrame->data calloc\n");
|
|
free(audioFrame);
|
|
audioFrame = NULL;
|
|
return NULL;
|
|
}
|
|
audioFrame->size = size;
|
|
|
|
audioFrame_set_default(audioFrame);
|
|
return audioFrame;
|
|
}
|
|
|
|
qua_video_frame_ext_t* alloc_quaVideoFrame(int size)
|
|
{
|
|
qua_video_frame_ext_t *videoFrame = (qua_video_frame_ext_t *)calloc(1, sizeof(qua_video_frame_ext_t));
|
|
if (videoFrame == NULL) {
|
|
printf("OOM audioFrame calloc\n");
|
|
return NULL;
|
|
}
|
|
uint8_t *buffer = (uint8_t *)calloc(size, sizeof(uint8_t));
|
|
videoFrame->data[0] = buffer;
|
|
if (videoFrame->data[0] == NULL) {
|
|
free(videoFrame);
|
|
videoFrame = NULL;
|
|
return NULL;
|
|
}
|
|
videoFrame->data[1] = buffer + size * 2 / 3;
|
|
printf("data [%p, %p] \n", videoFrame->data[0], videoFrame->data[1]);
|
|
printf("linesize [%d, %d] \n", videoFrame->linesize[0], videoFrame->linesize[1]);
|
|
return videoFrame;
|
|
}
|
|
|
|
void free_audioFrame(qua_audio_frame_ext_t *frame)
|
|
{
|
|
if (frame && frame->data) {
|
|
free(frame->data);
|
|
frame->data = NULL;
|
|
}
|
|
free(frame);
|
|
frame = NULL;
|
|
}
|
|
|
|
qua_mm_decoder_device_t* get_dec_device(char *chip, char *os)
|
|
{
|
|
int ret = 0;
|
|
QUA_CHAR platform[32] = { 0 };
|
|
ret = qua_make_platform(chip, os, platform, sizeof(platform));
|
|
if (ret == QUA_FAILURE) {
|
|
printf("make platform name error!\n");
|
|
return NULL;
|
|
}
|
|
const qua_mm_module_t *ext_module = NULL;
|
|
qua_mm_system_t *system = NULL;
|
|
ret = qua_mm_init(QUA_TRUE, platform, &system);
|
|
if (ret != QUA_SUCCESS || system == NULL) {
|
|
ALOGD("system module init fail");
|
|
return NULL;
|
|
}
|
|
ret = qua_mm_load_module(QUA_MM_MODULE_AVEXT, &ext_module);
|
|
if (ret != QUA_SUCCESS || ext_module == NULL)
|
|
return NULL;
|
|
|
|
printf("Module %s, API version %d\n", ext_module->id, ext_module->api_version);
|
|
qua_mm_device_t *mm_device = NULL;
|
|
ret = ext_module->open_device(ext_module, QUA_MM_EXT_DECODER, 0, &mm_device);
|
|
if (ret != QUA_SUCCESS || mm_device == NULL)
|
|
return NULL;
|
|
qua_mm_decoder_device_t *dec_device = (qua_mm_decoder_device_t *)mm_device;
|
|
|
|
printf("Module %s, API version %d dec_device: %p\n",
|
|
ext_module->id, ext_module->api_version, dec_device);
|
|
return dec_device;
|
|
}
|
|
|
|
qua_mm_extractor_device_t* get_extractor_device(char *chip, char *os)
|
|
{
|
|
int ret = 0;
|
|
QUA_CHAR platform[32] = { 0 };
|
|
ret = qua_make_platform(chip, os, platform, sizeof(platform));
|
|
if (ret == QUA_FAILURE) {
|
|
printf("make platform name error!\n");
|
|
return NULL;
|
|
}
|
|
const qua_mm_module_t *ext_module = NULL;
|
|
qua_mm_system_t *system = NULL;
|
|
ret = qua_mm_init(QUA_TRUE, platform, &system);
|
|
if (ret != QUA_SUCCESS || system == NULL) {
|
|
ALOGD("system module init fail");
|
|
return NULL;
|
|
}
|
|
ret = qua_mm_load_module(QUA_MM_MODULE_AVEXT, &ext_module);
|
|
if (ret != QUA_SUCCESS || ext_module == NULL)
|
|
return NULL;
|
|
|
|
printf("Module %s, API version %d\n", ext_module->id, ext_module->api_version);
|
|
qua_mm_device_t *mm_device = NULL;
|
|
ret = ext_module->open_device(ext_module, QUA_MM_EXT_EXTRACTOR, 0, &mm_device);
|
|
if (ret != QUA_SUCCESS || mm_device == NULL)
|
|
return NULL;
|
|
qua_mm_extractor_device_t *extractor_device = (qua_mm_extractor_device_t *)mm_device;
|
|
|
|
printf("Module %s, API version %d extractor_device: %p\n",
|
|
ext_module->id, ext_module->api_version, extractor_device);
|
|
return extractor_device;
|
|
}
|
|
|
|
static QUA_U64 get_ms()
|
|
{
|
|
// us
|
|
struct timeval timestamp = {};
|
|
if (0 == gettimeofday(×tamp, NULL)) {
|
|
return timestamp.tv_sec * 1000000 + timestamp.tv_usec;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
char *chip_name = NULL;
|
|
char *os_name = NULL;
|
|
char *src_filename = NULL;
|
|
char *video_dst_filename = NULL;
|
|
char *audio_dst_filename = NULL;
|
|
FILE *video_dst_file = NULL, *audio_dst_file = NULL;
|
|
|
|
int trackCnt = 0, videoTrackIndex = -1, audioTrackIndex = -1;
|
|
int audioCnt = 0, videoCnt = 0;
|
|
int audioSize = 0, videoSize = 0;
|
|
int audioeof = 1, videoeof = 1;
|
|
int width = 0, height = 0;
|
|
int fps = 0, profile = 0, level = 0;
|
|
qua_metaformat_t *meta = NULL, *audioTrackMeta = NULL, *videoTrackMeta = NULL;
|
|
ssize_t trackSize = 0;
|
|
qua_packet_t *quaPacket = NULL;
|
|
qua_audio_frame_ext_t *audioFrame = NULL;
|
|
qua_video_frame_ext_t *videoFrame = NULL;
|
|
qua_mm_channel_t *mm_chn = NULL;
|
|
qua_mm_extractor_device_t *extractor_device = NULL;
|
|
qua_mm_extractor_channel_t *extra_chn = NULL;
|
|
qua_mm_decoder_device_t *dec_device = NULL;
|
|
qua_mm_decoder_channel_t *dec_chn = NULL;
|
|
qua_mm_decoder_channel_t *video_dec_chn = NULL;
|
|
qua_ext_codec_chn_attr_t codec_chn_attr;
|
|
QUA_S32 chn_id = 0;
|
|
QUA_U64 pos = 0;
|
|
int ret = 0;
|
|
|
|
if (argc != 6) {
|
|
fprintf(stderr, "usage: %s \n"
|
|
"chip name and os name.\n"
|
|
"input_file video_output_file audio_output_file.\n"
|
|
"API example program to show how to read frames from an input file.\n"
|
|
"This program reads avpacket from a file, decodes them, and writes demuxer\n"
|
|
"video packet to a video file named video_output_file, and demuxer\n"
|
|
"audio packet to a audio file named audio_output_file.\n",
|
|
argv[0]);
|
|
exit(1);
|
|
}
|
|
chip_name = argv[1];
|
|
os_name = argv[2];
|
|
src_filename = argv[3];
|
|
video_dst_filename = argv[4];
|
|
audio_dst_filename = argv[5];
|
|
|
|
printf("\n\n");
|
|
printf("src_filename: %s \n", src_filename);
|
|
printf("video_dst_filename: %s \n", video_dst_filename);
|
|
printf("audio_dst_filename: %s \n", audio_dst_filename);
|
|
printf("test work start >>>>\n");
|
|
|
|
extractor_device = get_extractor_device(chip_name, os_name);
|
|
if (extractor_device != NULL) {
|
|
printf("using moudle type\n");
|
|
ret = extractor_device->parent.create_channel(&extractor_device->parent, &chn_id, (QUA_VOID_PTR)src_filename, &mm_chn);
|
|
if (ret != 0) {
|
|
printf("create chn failed ret = %d\n", ret);
|
|
exit(1);
|
|
}
|
|
extra_chn = (qua_mm_extractor_channel_t *)mm_chn;
|
|
}
|
|
if (extra_chn == NULL) {
|
|
printf("extra_chn is null\n");
|
|
exit(1);
|
|
}
|
|
trackCnt = extra_chn->track_count(extra_chn->extractor);
|
|
if (trackCnt == 0) {
|
|
printf("WARNING %s has not any track !!! \n", src_filename);
|
|
extra_chn->parent.release(&extra_chn->parent);
|
|
extractor_device->parent.close((qua_mm_device_t *)extractor_device);
|
|
exit(1);
|
|
}
|
|
video_dst_file = fopen(video_dst_filename, "wb+");
|
|
if (video_dst_file == NULL) {
|
|
printf("open %s file failed\n", video_dst_filename);
|
|
exit(1);
|
|
}
|
|
audio_dst_file = fopen(audio_dst_filename, "wb+");
|
|
if (audio_dst_file == NULL) {
|
|
printf("open %s file failed\n", audio_dst_filename);
|
|
exit(1);
|
|
}
|
|
videoTrackIndex = extra_chn->get_video_track_index(extra_chn->extractor);
|
|
audioTrackIndex = extra_chn->get_audio_track_index(extra_chn->extractor);
|
|
printf("trackCnt: %d, videoTrackIndex: %d, audioTrackIndex: %d\n",
|
|
trackCnt, videoTrackIndex, audioTrackIndex);
|
|
ret = extra_chn->meta_data(extra_chn->extractor, &meta);
|
|
if (ret == 0) {
|
|
printf_meta(meta);
|
|
}
|
|
if (videoTrackIndex >= 0) {
|
|
ret = extra_chn->track_meta_data(extra_chn->extractor, videoTrackIndex, &videoTrackMeta);;
|
|
if (ret == 0) {
|
|
printf_meta(videoTrackMeta);
|
|
}
|
|
int32_t bit_rate = 0;
|
|
qua_video_config_t videoCfg;
|
|
char mime[128] = { 0 };
|
|
void *extra_data = NULL;
|
|
size_t extra_data_size = 0;
|
|
|
|
videoTrackMeta->get_int32(videoTrackMeta, "width", &width);
|
|
videoTrackMeta->get_int32(videoTrackMeta, "height", &height);
|
|
videoTrackMeta->get_int32(videoTrackMeta, "profile", &profile);
|
|
videoTrackMeta->get_int32(videoTrackMeta, "level", &level);
|
|
videoTrackMeta->get_int32(videoTrackMeta, "fps", &fps);
|
|
videoTrackMeta->get_string(videoTrackMeta, "mime", &mime[0]);
|
|
videoTrackMeta->get_int32(videoTrackMeta, "bitrate", &bit_rate);
|
|
videoTrackMeta->get_buffer(videoTrackMeta, "csd-avc", &extra_data, &extra_data_size);
|
|
videoCfg.width = width;
|
|
videoCfg.height = height;
|
|
videoCfg.fps = fps;
|
|
videoCfg.bitrate = bit_rate;
|
|
videoCfg.pix_fmt = 23;
|
|
videoCfg.profile = profile;
|
|
videoCfg.level = level;
|
|
memcpy(videoCfg.extra_data, (uint8_t*)extra_data, extra_data_size);
|
|
videoCfg.extra_data_size = extra_data_size;
|
|
|
|
dec_device = get_dec_device(chip_name, os_name);
|
|
if (dec_device == NULL) {
|
|
printf("dec_device is NULL\n");
|
|
exit(1);
|
|
}
|
|
codec_chn_attr.mimetype = &mime[0];
|
|
codec_chn_attr.mime_extra = NULL;
|
|
codec_chn_attr.param = (void *)&videoCfg;
|
|
ret = dec_device->parent.create_channel(&dec_device->parent, &chn_id, (QUA_VOID_PTR)&codec_chn_attr, &mm_chn);
|
|
if (ret != 0 || mm_chn == NULL) {
|
|
printf("create_channel dec_chn fail\n");
|
|
exit(1);
|
|
}
|
|
video_dec_chn = (qua_mm_decoder_channel_t *)mm_chn;
|
|
videoeof = 0;
|
|
videoFrame = alloc_quaVideoFrame(width*height*3/2);
|
|
if (videoFrame == NULL) {
|
|
printf("alloc videoFrame fail OOM??\n");
|
|
exit(1);
|
|
}
|
|
videoFrame->linesize[0] = videoCfg.width;
|
|
videoFrame->linesize[1] = videoCfg.width;
|
|
}
|
|
if (audioTrackIndex >= 0) {
|
|
ret = extra_chn->track_meta_data(extra_chn->extractor, audioTrackIndex, &audioTrackMeta);
|
|
if (ret == 0) {
|
|
printf_meta(audioTrackMeta);
|
|
}
|
|
char mime[128] = { 0 };
|
|
qua_audio_config_t audioCfg;
|
|
void *extra_data = NULL;
|
|
size_t extra_data_size = 0;
|
|
|
|
int32_t nb_channels, bit_rate, sample_rate, sample_fmt, bits_per_coded_sample, block_align;
|
|
audioTrackMeta->get_int32(audioTrackMeta, "channel-count", &nb_channels);
|
|
audioTrackMeta->get_int32(audioTrackMeta, "bitrate", &bit_rate);
|
|
audioTrackMeta->get_int32(audioTrackMeta, "sample-rate", &sample_rate);
|
|
audioTrackMeta->get_int32(audioTrackMeta, "sample-format", &sample_fmt);
|
|
audioTrackMeta->get_int32(audioTrackMeta, "bits_per_coded_sample", &bits_per_coded_sample);
|
|
audioTrackMeta->get_int32(audioTrackMeta, "block-align", &block_align);
|
|
audioTrackMeta->get_string(audioTrackMeta, "mime", &mime[0]);
|
|
audioTrackMeta->get_buffer(audioTrackMeta, "raw-codec-specific-data", &extra_data, &extra_data_size);
|
|
|
|
audioCfg.out_nb_channels = audioCfg.in_nb_channels = nb_channels;
|
|
audioCfg.out_sample_rate = audioCfg.in_sample_rate = sample_rate;
|
|
audioCfg.in_sample_fmt = sample_fmt;
|
|
audioCfg.out_sample_fmt = sample_fmt; // QUA_SAMPLE_FMT_S16;
|
|
audioCfg.bit_rate = bit_rate;
|
|
audioCfg.bits_per_coded_sample = bits_per_coded_sample;
|
|
audioCfg.block_align = block_align;
|
|
memcpy(audioCfg.extra_data, (uint8_t*)extra_data, extra_data_size);
|
|
audioCfg.extra_data_size = extra_data_size;
|
|
|
|
dec_device = get_dec_device(chip_name, os_name);
|
|
if (dec_device == NULL) {
|
|
printf("dec_device is NULL\n");
|
|
exit(1);
|
|
}
|
|
codec_chn_attr.mimetype = &mime[0];
|
|
codec_chn_attr.mime_extra = NULL;
|
|
codec_chn_attr.param = (void *)&audioCfg;
|
|
ret = dec_device->parent.create_channel(&dec_device->parent, &chn_id, (QUA_VOID_PTR)&codec_chn_attr, &mm_chn);
|
|
if (ret != 0 || mm_chn == NULL) {
|
|
printf("create_channel dec_chn fail\n");
|
|
exit(1);
|
|
}
|
|
dec_chn = (qua_mm_decoder_channel_t *)mm_chn;
|
|
printf("dec_device: %p, dec_chn: %p, \n", dec_device, dec_chn);
|
|
audioeof = 0;
|
|
audioFrame = alloc_quaAudioFrame(128*1000);
|
|
if (audioFrame == NULL) {
|
|
printf("alloc audioFrame fail OOM??\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
if (videoTrackIndex >= 0) {
|
|
quaPacket = alloc_quaPacket(width * height);
|
|
} else if (audioTrackIndex >= 0) {
|
|
quaPacket = alloc_quaPacket(128*1000);
|
|
}
|
|
if (quaPacket == NULL) {
|
|
printf("alloc quaPacket fail OOM??\n");
|
|
exit(1);
|
|
}
|
|
|
|
while (true) {
|
|
quaPacket_set_default(quaPacket);
|
|
if (videoTrackIndex >= 0 && videoeof == 0) {
|
|
trackSize = extra_chn->read_sample_data(extra_chn->extractor, videoTrackIndex, quaPacket);
|
|
if (trackSize > 0) {
|
|
videoSize += quaPacket->capacity;
|
|
// printf("[video] == trackSizeTotle: %d, pts: %ld\n", quaPacket->capacity, quaPacket->pts);
|
|
// if (quaPacket->streamIndex == videoTrackIndex) {
|
|
// fwrite(quaPacket->data, 1, quaPacket->capacity, video_dst_file);
|
|
// }
|
|
videoCnt++;
|
|
ret = video_dec_chn->send_packet(video_dec_chn->decoder, quaPacket);
|
|
while (ret >= 0) {
|
|
pos = get_ms();
|
|
ret = video_dec_chn->receive_frame(video_dec_chn->decoder, videoFrame);
|
|
printf("cost %lld us\n", get_ms() - pos);
|
|
if (ret < 0) {
|
|
printf("Error during decoding ret = %d \n", ret);
|
|
} else {
|
|
printf("[video] wxh: %dx%d, pts: %llu\n", videoFrame->width, videoFrame->height, videoFrame->pts);
|
|
fwrite(videoFrame->data[0], 1, videoFrame->width * videoFrame->height, video_dst_file);
|
|
fwrite(videoFrame->data[1], 1, videoFrame->width * videoFrame->height / 2, video_dst_file);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
printf("video EOF!\n");
|
|
videoeof = 1;
|
|
}
|
|
}
|
|
quaPacket_set_default(quaPacket);
|
|
if (audioTrackIndex >= 0 && audioeof == 0) {
|
|
trackSize = extra_chn->read_sample_data(extra_chn->extractor, audioTrackIndex, quaPacket);
|
|
if (trackSize > 0) {
|
|
audioSize += quaPacket->capacity;
|
|
audioCnt++;
|
|
ret = dec_chn->send_packet(dec_chn->decoder, quaPacket);
|
|
// printf("qua_avdecoder_sendPacket ret = %d \n", ret);
|
|
if (ret < 0) {
|
|
printf("Error submitting a packet for decoding ret = %d \n", ret);
|
|
// return ret;
|
|
}
|
|
while (ret >= 0) {
|
|
audioFrame_set_default(audioFrame);
|
|
ret = dec_chn->receive_frame(dec_chn->decoder, audioFrame);
|
|
// printf("qua_avdecoder_receiveFrame ret = %d \n", ret);
|
|
if (ret < 0) {
|
|
printf("Error during decoding ret = %d \n", ret);
|
|
//if (ret == -11) {
|
|
// continue;
|
|
//}
|
|
//return ret;
|
|
} else {
|
|
printf("[audio] pts = %llu, capacity = %d \n",
|
|
audioFrame->pts, audioFrame->capacity);
|
|
fwrite(audioFrame->data, 1, audioFrame->capacity, audio_dst_file);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
printf("audio EOF!\n");
|
|
audioeof = 1;
|
|
}
|
|
}
|
|
if (audioeof == 1 && videoeof == 1)
|
|
break;
|
|
};
|
|
dec_chn->flush(dec_chn->decoder);
|
|
|
|
free_quaPacket(quaPacket);
|
|
free_audioFrame(audioFrame);
|
|
|
|
dec_chn->parent.release(&dec_chn->parent);
|
|
dec_device->parent.close((qua_mm_device_t *)dec_device);
|
|
|
|
extra_chn->parent.release(&extra_chn->parent);
|
|
extractor_device->parent.close((qua_mm_device_t *)extractor_device);
|
|
printf("test work end audioCnt = %d, audioSize = %d, videoCnt = %d, videoSize = %d<<<<\n",
|
|
audioCnt, audioSize, videoCnt, videoSize);
|
|
if (audio_dst_file)
|
|
fclose(audio_dst_file);
|
|
if (video_dst_file)
|
|
fclose(video_dst_file);
|
|
|
|
printf("well done. good job!\n\n\n");
|
|
|
|
return 0;
|
|
}
|