|
|
|
/*
|
|
|
|
demux pes mpeg stream
|
|
|
|
Copyright (C) 2001 Martin Vogt
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Library General Public License as published by
|
|
|
|
the Free Software Foundation.
|
|
|
|
|
|
|
|
For more information look at the file COPYRIGHT in this package
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "pesSystemStream.h"
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
PESSystemStream::PESSystemStream(InputStream* input) {
|
|
|
|
this->input=input;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PESSystemStream::~PESSystemStream() {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int PESSystemStream::getByteDirect() {
|
|
|
|
unsigned char byte;
|
|
|
|
if (input->read((char*)&byte,1) != 1) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
bytes_read++;
|
|
|
|
return (int)byte;
|
|
|
|
}
|
|
|
|
|
|
|
|
int PESSystemStream::read(char* pointer,int bytes) {
|
|
|
|
if (input->read(pointer,bytes) != bytes) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bytes_read+=bytes;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int PESSystemStream::processStartCode(unsigned int startCode,
|
|
|
|
MpegSystemHeader* mpegHeader) {
|
|
|
|
int lok=true;
|
|
|
|
bytes_read=4; // startcode
|
|
|
|
mpegHeader->setPacketLen(0);
|
|
|
|
mpegHeader->setPacketID(_PAKET_ID_NUKE);
|
|
|
|
|
|
|
|
// handle default
|
|
|
|
bytes_read=processPacket(startCode,mpegHeader);
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Returns:
|
|
|
|
0 - no error, but not video packet we want
|
|
|
|
-1 - error
|
|
|
|
>0 - length of packet
|
|
|
|
*/
|
|
|
|
int PESSystemStream::processPacket(unsigned int startCode,
|
|
|
|
MpegSystemHeader* mpegHeader) {
|
|
|
|
|
|
|
|
int ioBytes;
|
|
|
|
unsigned short packetLength;
|
|
|
|
int packetDataLength;
|
|
|
|
|
|
|
|
/* Leftovers from previous video packets */
|
|
|
|
|
|
|
|
int packetID=startCode & 0xff;
|
|
|
|
mpegHeader->setPacketID(packetID);
|
|
|
|
int lPacket=startCode & _PACKET_START_CODE_MASK &_PACKET_START_CODE_PREFIX;
|
|
|
|
if ((lPacket == false) || (packetID < 0xbc)) {
|
|
|
|
//printf("unknown startcode,packet or packetID:%8x\n",startCode);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packetID == _NOT_PACKET_ID) {
|
|
|
|
cout << "(vid_stream->mpegVideoStream)->makeEnd()"<<endl;
|
|
|
|
} else if (packetID==_KILL_BUFFER) {
|
|
|
|
printf("packetID==_KILL_BUFFER\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read((char*)&packetLength, 2) == false) return false;
|
|
|
|
packetLength = htons(packetLength);
|
|
|
|
|
|
|
|
mpegHeader->setPTSFlag(false);
|
|
|
|
mpegHeader->setPacketID(packetID);
|
|
|
|
mpegHeader->setPESPacketLen(packetLength);
|
|
|
|
switch (packetID>>4) {
|
|
|
|
case _PAKET_ID_AUDIO_1>>4:
|
|
|
|
case _PAKET_ID_AUDIO_2>>4:
|
|
|
|
case _PAKET_ID_VIDEO>>4:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
switch(packetID) {
|
|
|
|
case _PRIVATE_STREAM_1_ID:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
switch (packetID) {
|
|
|
|
case _PRIVATE_STREAM_2_ID:
|
|
|
|
case _PADDING_STREAM_ID:
|
|
|
|
case _RESERVED_STREAM_ID:
|
|
|
|
case _ECM_STREAM_ID:
|
|
|
|
case _EMM_STREAM_ID:
|
|
|
|
case _PROGRAM_STREAM_DIRECTORY_ID:
|
|
|
|
case _DSMCC_STREAM_ID:
|
|
|
|
case _ITUTRECH222TYPEE_STREAM_ID:
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
printf("\nUnknown packet type. (%x) at %ld\n",
|
|
|
|
packetID,input->getBytePosition());
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// this is only processed if audio or video found
|
|
|
|
|
|
|
|
if (mpegHeader->getMPEG2()==false) {
|
|
|
|
packetDataLength = packetLength-processPacketHeader(mpegHeader);
|
|
|
|
} else {
|
|
|
|
int len=processMPEG2PacketHeader(mpegHeader);
|
|
|
|
|
|
|
|
if (len < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
packetDataLength = packetLength-len;
|
|
|
|
|
|
|
|
// now check in private stream for AC3
|
|
|
|
if ( packetID == _PRIVATE_STREAM_1_ID ) {
|
|
|
|
packetDataLength = packetDataLength-processPrivateHeader(mpegHeader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packetDataLength <= 0) {
|
|
|
|
if (mpegHeader->hasPSHeader()) return false;
|
|
|
|
// -> buggy TS stream
|
|
|
|
packetDataLength=0;
|
|
|
|
}
|
|
|
|
mpegHeader->setPESPacketLen(packetDataLength);
|
|
|
|
|
|
|
|
return bytes_read;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PESSystemStream::processPrivateHeader(MpegSystemHeader* mpegHeader) {
|
|
|
|
char nukeBuffer[30];
|
|
|
|
int pos=0;
|
|
|
|
int one=getByteDirect();
|
|
|
|
pos++;
|
|
|
|
mpegHeader->setSubStreamID(one);
|
|
|
|
switch(one>>4) {
|
|
|
|
case _SUBSTREAM_AC3_ID>>4:
|
|
|
|
if (read(nukeBuffer,3) == false) return false;
|
|
|
|
mpegHeader->addAvailableLayer(one);
|
|
|
|
cout << "addAvailableLayer:"<<one<<endl;
|
|
|
|
pos+=3;
|
|
|
|
break;
|
|
|
|
case _SUBSTREAM_LPCM_ID>>4:
|
|
|
|
if (read(nukeBuffer,6) == false) return false;
|
|
|
|
pos+=6;
|
|
|
|
break;
|
|
|
|
case _SUBSTREAM_SUBPIC_ID>>4:
|
|
|
|
if (read(nukeBuffer,3) == false) return false;
|
|
|
|
pos+=3;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printf("unknown sub id :%8x\n",one);
|
|
|
|
}
|
|
|
|
return pos;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PESSystemStream::processMPEG2PacketHeader(MpegSystemHeader* mpegHeader){
|
|
|
|
|
|
|
|
int stdCnt=0;
|
|
|
|
int pos=0;
|
|
|
|
|
|
|
|
// 1. Byte
|
|
|
|
/*
|
|
|
|
FROM FLASK:
|
|
|
|
int getbits(2);
|
|
|
|
encrypted = getbits(2); // PES_scrambling_control
|
|
|
|
getbits(4);
|
|
|
|
//LIVID
|
|
|
|
u_char original_or_copy : 1;
|
|
|
|
u_char copyright : 1;
|
|
|
|
u_char data_alignment_indicator : 1;
|
|
|
|
u_char pes_priority : 1;
|
|
|
|
u_char pes_scrambling_control : 2;
|
|
|
|
u_char start_code_prefix : 2; // 0x02
|
|
|
|
|
|
|
|
*/
|
|
|
|
int first=getByteDirect();
|
|
|
|
stdCnt++;
|
|
|
|
mpegHeader->setOriginalOrCopy(first&(128)>>7);
|
|
|
|
mpegHeader->setCopyRight(first&(64)>>6);
|
|
|
|
mpegHeader->setDataAlignmentIndicator(first&(32)>>5);
|
|
|
|
mpegHeader->setPesPriority(first&(16)>>4);
|
|
|
|
mpegHeader->setEncrypted((first&(8+4))>>2);
|
|
|
|
mpegHeader->setStartCodePrefix(first&(1+2));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Byte
|
|
|
|
/*
|
|
|
|
PTS_DTS_flags = getbits(2);
|
|
|
|
ESCR_flag = get1bit();
|
|
|
|
ES_rate_flag = get1bit();
|
|
|
|
DSM_trick_mode_flag = get1bit();
|
|
|
|
additional_copy_info_flag = get1bit();
|
|
|
|
PES_CRC_flag = get1bit();
|
|
|
|
PES_extension_flag = get1bit();
|
|
|
|
*/
|
|
|
|
int second=getByteDirect();
|
|
|
|
stdCnt++;
|
|
|
|
|
|
|
|
mpegHeader->setPTSDTSFlag((second&(128+64))>>6);
|
|
|
|
mpegHeader->setESCRFlag((second&(32))>>5);
|
|
|
|
mpegHeader->setES_RATE_Flag((second%(16))>>4);
|
|
|
|
mpegHeader->setDMSTRICKFLAG((second&(8))>>3);
|
|
|
|
mpegHeader->setADDITIONAL_COPY_FLAG((second&(4))>>2);
|
|
|
|
mpegHeader->setPES_CRC_FLAG((second&(2))>>1);
|
|
|
|
mpegHeader->setPES_EXT_FLAG(second&(1));
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Byte
|
|
|
|
/*
|
|
|
|
PES_header_data_length = getbits(8);
|
|
|
|
*/
|
|
|
|
int third=getByteDirect();
|
|
|
|
stdCnt++;
|
|
|
|
mpegHeader->setPES_HEADER_DATA_LENGTH(third);
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// PARSING MPEG 2 HEADER FLAGS [START]
|
|
|
|
//
|
|
|
|
unsigned char nukeBuffer[300];
|
|
|
|
|
|
|
|
int PTS_DTS_flags=mpegHeader->getPTSDTSFlag();
|
|
|
|
if (PTS_DTS_flags == 0) {
|
|
|
|
mpegHeader->setPTSFlag(false);
|
|
|
|
} else {
|
|
|
|
mpegHeader->setPTSFlag(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PTS_DTS_flags > 0x1) {
|
|
|
|
if (read((char*)nukeBuffer,5) == false) return false;
|
|
|
|
double pts=GET_MPEG2_PTS(nukeBuffer);
|
|
|
|
pts=(pts*300.0)/(double)MPEG2_CLK_REF;
|
|
|
|
mpegHeader->setPTSTimeStamp(pts);
|
|
|
|
pos+=5;
|
|
|
|
}
|
|
|
|
if (PTS_DTS_flags > 0x2) {
|
|
|
|
if (read((char*)nukeBuffer,5) == false) return false;
|
|
|
|
double dts=GET_MPEG2_PTS(nukeBuffer);
|
|
|
|
mpegHeader->setDTSTimeStamp((dts*300.0)/(double)MPEG2_CLK_REF);
|
|
|
|
pos+=5;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ESCRFlag=mpegHeader->getESCRFlag();
|
|
|
|
if (ESCRFlag == 1){
|
|
|
|
cout << "ESCRFlag == 1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,6) == false) return false;
|
|
|
|
pos+=6;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ES_rate_flag=mpegHeader->getES_RATE_Flag();
|
|
|
|
if (ES_rate_flag == 1){
|
|
|
|
cout << "ES_rate_flag == 1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,3) == false) return false;
|
|
|
|
pos+=3;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DSM_trick_mode_flag=mpegHeader->getDMSTRICKFLAG();
|
|
|
|
if (DSM_trick_mode_flag == 1){
|
|
|
|
cout << "DSM_trick_mode_flag == 1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,1) == false) return false;
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
int additional_copy_info_flag=mpegHeader->getADDITIONAL_COPY_FLAG();
|
|
|
|
if (additional_copy_info_flag == 1) {
|
|
|
|
cout << "additional_copy_info_flag == 1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,1) == false) return false;
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
int PES_CRC_flag=mpegHeader->getPES_CRC_FLAG();
|
|
|
|
if (PES_CRC_flag == 1) {
|
|
|
|
cout << "PES_CRC_flag == 1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,2) == false) return false;
|
|
|
|
pos+=2;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// PES Extension [START]
|
|
|
|
//
|
|
|
|
|
|
|
|
int PES_extension_flag=mpegHeader->getPES_EXT_FLAG();
|
|
|
|
if (PES_extension_flag == 1) {
|
|
|
|
/*
|
|
|
|
FLASK:
|
|
|
|
PES_private_data_flag = get1bit();
|
|
|
|
pack_header_field_flag = get1bit();
|
|
|
|
program_packet_sequence_counter_flag = get1bit();
|
|
|
|
PSTD_buffer_flag = get1bit();
|
|
|
|
getbits(3);
|
|
|
|
PES_extension_flag_2 = get1bit();
|
|
|
|
*/
|
|
|
|
int extensionByte=getByteDirect();
|
|
|
|
|
|
|
|
pos++;
|
|
|
|
mpegHeader->setPrivateDataFlag((extensionByte&(128))>>7);
|
|
|
|
mpegHeader->setPackHeaderFieldFlag((extensionByte&(64))>>6);
|
|
|
|
mpegHeader->setSequenceCounterFlag((extensionByte&(32))>>5);
|
|
|
|
mpegHeader->setSTDBufferFlag((extensionByte&(16))>>4);
|
|
|
|
mpegHeader->setPES_EXT_FLAG_2(extensionByte&(1));
|
|
|
|
|
|
|
|
int PES_private_data_flag=mpegHeader->getPrivateDataFlag();
|
|
|
|
if (PES_private_data_flag == 1) {
|
|
|
|
if (read((char*)nukeBuffer,128) == false) return false;
|
|
|
|
pos+=128;
|
|
|
|
}
|
|
|
|
|
|
|
|
int pack_header_field_flag=mpegHeader->getPackHeaderFieldFlag();
|
|
|
|
if (pack_header_field_flag == 1) {
|
|
|
|
printf("pack header field flag value not allowed in program streams\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sequence_counter_flag=mpegHeader->getSequenceCounterFlag();
|
|
|
|
if (sequence_counter_flag==1) {
|
|
|
|
cout<<"sequence_counter_flag ==1"<<endl;
|
|
|
|
if (read((char*)nukeBuffer,2) == false) return false;
|
|
|
|
pos+=2;
|
|
|
|
}
|
|
|
|
|
|
|
|
int PSTD_buffer_flag=mpegHeader->getSTDBufferFlag();
|
|
|
|
if (PSTD_buffer_flag==1) {
|
|
|
|
if (read((char*)nukeBuffer,2) == false) return false;
|
|
|
|
pos+=2;
|
|
|
|
}
|
|
|
|
|
|
|
|
int PES_extension_flag_2=mpegHeader->getPES_EXT_FLAG_2();
|
|
|
|
if (PES_extension_flag_2 == 1) {
|
|
|
|
int extension2_byte=getByteDirect();
|
|
|
|
pos++;
|
|
|
|
mpegHeader->setPES_EXT_FIELD_LENGTH(extension2_byte&(254));
|
|
|
|
|
|
|
|
|
|
|
|
int PES_field_length=mpegHeader->getPES_EXT_FIELD_LENGTH();
|
|
|
|
int j;
|
|
|
|
for (j=0;j<PES_field_length ; j++) {
|
|
|
|
cout << "PES_field_length (nuke)"<<endl;
|
|
|
|
getByteDirect();
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// PES Extension [END]
|
|
|
|
//
|
|
|
|
|
|
|
|
// now nuke remaining bytes from PES DATA Length
|
|
|
|
int PES_HEADER_DATA_LENGTH=mpegHeader->getPES_HEADER_DATA_LENGTH();
|
|
|
|
int tmp=PES_HEADER_DATA_LENGTH-pos;
|
|
|
|
if (tmp>0) {
|
|
|
|
if (read((char*)nukeBuffer,tmp) == false) return false;
|
|
|
|
pos+=tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// PARSING MPEG 2 HEADER FLAGS [START]
|
|
|
|
//
|
|
|
|
|
|
|
|
int parsed=stdCnt+pos;
|
|
|
|
return parsed;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PESSystemStream::processPacketHeader(MpegSystemHeader* mpegHeader) {
|
|
|
|
unsigned char nextByte;
|
|
|
|
int pos;
|
|
|
|
int val;
|
|
|
|
unsigned char scratch[10];
|
|
|
|
|
|
|
|
|
|
|
|
nextByte=getByteDirect();
|
|
|
|
|
|
|
|
mpegHeader->setPTSFlag(false);
|
|
|
|
|
|
|
|
pos = 1;
|
|
|
|
while (nextByte & 0x80) {
|
|
|
|
++pos;
|
|
|
|
val=getByteDirect();
|
|
|
|
if (val == -1) return false;
|
|
|
|
scratch[0]=val;
|
|
|
|
|
|
|
|
nextByte=scratch[0];
|
|
|
|
}
|
|
|
|
if ((nextByte >> 6) == 0x01) {
|
|
|
|
pos += 2;
|
|
|
|
scratch[1]=getByteDirect();
|
|
|
|
scratch[2]=getByteDirect();
|
|
|
|
nextByte=scratch[2];
|
|
|
|
}
|
|
|
|
if ((nextByte >> 4) == 0x02) {
|
|
|
|
scratch[0] = nextByte;
|
|
|
|
if (read((char*)&scratch[1],4) == false) return false;
|
|
|
|
/* presentation time stamps */
|
|
|
|
unsigned char hiBit;
|
|
|
|
unsigned long low4Bytes;
|
|
|
|
double ptsTimeStamp;
|
|
|
|
double dtsTimeStamp=0.0;
|
|
|
|
readTimeStamp((unsigned char*)scratch,&hiBit,&low4Bytes);
|
|
|
|
makeClockTime(hiBit,low4Bytes,&ptsTimeStamp);
|
|
|
|
mpegHeader->setPTSFlag(true);
|
|
|
|
mpegHeader->setPTSTimeStamp(ptsTimeStamp);
|
|
|
|
mpegHeader->setDTSTimeStamp(dtsTimeStamp);
|
|
|
|
|
|
|
|
pos += 4;
|
|
|
|
}
|
|
|
|
else if ((nextByte >> 4) == 0x03) {
|
|
|
|
scratch[0] = nextByte;
|
|
|
|
if (read((char*)&scratch[1],9) == false) return false;
|
|
|
|
/* presentation and decoding time stamps */
|
|
|
|
unsigned char hiBit;
|
|
|
|
unsigned long low4Bytes;
|
|
|
|
double ptsTimeStamp;
|
|
|
|
double dtsTimeStamp;
|
|
|
|
readTimeStamp((unsigned char*)scratch,&hiBit,&low4Bytes);
|
|
|
|
makeClockTime(hiBit,low4Bytes,&ptsTimeStamp);
|
|
|
|
|
|
|
|
readTimeStamp((unsigned char*)&(scratch[5]),&hiBit,&low4Bytes);
|
|
|
|
makeClockTime(hiBit,low4Bytes,&dtsTimeStamp);
|
|
|
|
mpegHeader->setPTSFlag(true);
|
|
|
|
mpegHeader->setPTSTimeStamp(ptsTimeStamp);
|
|
|
|
mpegHeader->setDTSTimeStamp(dtsTimeStamp);
|
|
|
|
|
|
|
|
pos += 9;
|
|
|
|
}
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void PESSystemStream::readTimeStamp(unsigned char* inputBuffer,
|
|
|
|
unsigned char* hiBit,
|
|
|
|
unsigned long* low4Bytes) {
|
|
|
|
*hiBit = ((unsigned long)inputBuffer[0] >> 3) & 0x01;
|
|
|
|
*low4Bytes = (((unsigned long)inputBuffer[0] >> 1) & 0x03) << 30;
|
|
|
|
*low4Bytes |= (unsigned long)inputBuffer[1] << 22;
|
|
|
|
*low4Bytes |= ((unsigned long)inputBuffer[2] >> 1) << 15;
|
|
|
|
*low4Bytes |= (unsigned long)inputBuffer[3] << 7;
|
|
|
|
*low4Bytes |= ((unsigned long)inputBuffer[4]) >> 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PESSystemStream::readSTD(unsigned char* inputBuffer,
|
|
|
|
MpegSystemHeader* mpegHeader) {
|
|
|
|
int stdBufferScale;
|
|
|
|
unsigned long stdBufferSize;
|
|
|
|
stdBufferScale = ((int)(inputBuffer[0] & 0x20) >> 5);
|
|
|
|
stdBufferSize = ((unsigned long)inputBuffer[0] & 0x1f) << 8;
|
|
|
|
stdBufferSize |= (unsigned long)inputBuffer[1];
|
|
|
|
mpegHeader->setStdBufferScale(stdBufferScale);
|
|
|
|
mpegHeader->setStdBufferSize(stdBufferSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int PESSystemStream::makeClockTime(unsigned char hiBit,
|
|
|
|
unsigned long low4Bytes,
|
|
|
|
double * clockTime) {
|
|
|
|
if (hiBit != 0 && hiBit != 1) {
|
|
|
|
*clockTime = 0.0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
*clockTime = (double)hiBit*FLOAT_0x10000*FLOAT_0x10000 + (double)low4Bytes;
|
|
|
|
*clockTime /= (double)_STD_SYSTEM_CLOCK_FREQ;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|