상세 컨텐츠

본문 제목

[펌]EPG 란 무엇인가?

카테고리 없음

by 동동주1123 2011. 3. 14. 11:33

본문


EPG 내용을 알아볼 필요가 있어 검색 하던중 나온 사이트 내용입니다.

이지지가 제대로 안보여 기록을 위해 블로그에 남겨 봅니다...


EPG?

Eelctronic Program Guide

간단하게 말하면 'TV 편성표'이다. 텔레비전 방송 프로그램의 편성표를 텔레비전 화면 상에 표시하는 것으로, 텔레비전을 시청하는 사람은 이 편성표를 통해 원하는 프로그램을 선택하거나 시간, 제목, 채널, 장르 등의 기준을 통해 원하는 프로그램을 검색할 수 있는 서비스를 말한다.

EPG 는 디지털 텔레비전에서 주로 사용되고 있으나, 아날로그 텔레비전에서도 수직 귀선 기간을 이용한 유사한 서비스를 제공하고 있다.

PSIP의 가장 중요한 정보인 MGT(Master Guide Table) 위주로 알아본다.

EPG의 구조
PSIP 데이터를 이용해 정보가 표현된 형태가 EPG이다. 따라서 EPG의 원리와 구조를 알기 위해서는 PSIP를 분석하면 되는 것이다.

TS는 크게 MPEG-2, AC3, PSIP로 이뤄졌다. MPEG-2 AC3가 합해져 PES 스트림을 구성하고 이 PES 스트림과 PSIP 데이터가 합해져 TS를 구성한다. 거꾸로 말하면 TS에서 PSIP 데이터만을 따로 추출할 수 있다.
PSIP
STT(System Time Table), MGT(Master Guide Table), VCT(Virtual Channel Table), RRT(Rating Region Table), EIT(Event Information Table), ETT(Extended Text Table)의 총  6개의 테이블(섹션)로 구성된다.

STT

현재의 날짜와 시간에 대한 정보

MGT

STT를 제외한 모든 테이블의 PID 값과 버전 정보

VCT

가상 채널에 대한 정보

RRT

프로그램 컨텐츠에 대한 등급 정보

EIT

VCT에 있는 모든 채널에 대한 최소 3시간 동안의 프로그램의 제목과 시작 시간에 정보

ETT

프로그램에 대한 배경, 줄거리, 등장 인물 같은 상세한 설명의 표현


PSIP 헤더 신텍스


첫 번째로 해야 할 일은 TS에서 PSIP를 추출하는 것이다. < 1> PSIP 테이블이 갖는 값을 요약한 것이다. table_id는 각각의 테이블에 대한 고유 ID 값이고, section_length는 각각의 테이블의 크기를 지정해 놓은 값이다. 이 크기는 TS 헤더를 포함하는 값이 아닌 section_length 다음부터의 크기를 지정해 놓은 값이다. TS의 크기는 188바이트로 고정됐는데, 최고 크기의 값을 갖는 것을 보면 4093바이트이다. 어떻게 188바이트로 4093바이트를 표현할 수 있을까. TS 23개가 하나의 테이블로 표현되기 때문이다. 


version_number는 각각의 테이블에 대한 버전 값을 갖는다. STT의 경우 최소 1초에 한 번씩 TS에 포함돼야 하며, MGT의 경우 0.15, RRT 60, VCT 0.4초에 한 번씩 포함돼야 한다. 이때 내용이 갱신됐는지 여부는 이 version_number를 이용해 확인한다. 만약 version_number가 변경되지 않았다면 갱신되지 않은 내용의 같은 데이터가 계속적으로 포함된다는 것이다. 여기서 STT의 버전 값이 항상 0인 이유는 시간은 1초 단위로 항상 변경돼 version_number가 필요 없기 때문이다


current_next_indicator
VCT(TVCT는 지상파의 VCT, CVCT는 케이블 방송에서의 VCT)에서만 사용하는 것으로‘1’로 설정됐으면 현재 전송되는 VCT가 유효하다는 것을 나타내고‘0’으로 설정됐으면 현재는 유효하지 않지만 다음에 유효할 것이라는 것을 의미한다. 예를 들면 현재 가상 채널의 정보(VCT) 11-1로 설정되어 전송되고 있는 상태이다. 이때는 current_next_indicator가‘1’로 설정되어 있을 것이다. 그러다가 가상 채널의 정보를 12-1 10분 후에 변경할 예정이다. 이런 경우 디지털 TV의 정상적인 동작을 위해 TV에게 미리 알려주는 것이다. current_next_indicator의 값을‘0’으로 설정해 다음에 가상 채널을 12-1로 변경할 예정이라고 미리 알려주는 것이다. 가상 채널이 변경되더라도 당황하지 말고, 미리 준비하라는 의미가 포함된 것이다.actual_table_data는 각각의 테이블이 갖는 실제 데이터를 의미한다. < 2>를 예로 들면 table_defined부터 CRC_32 전까지의 내용을 의미하는 것이다.


여기까지 PSIP의 기본 헤더에 대한 내용을 살펴보았다. 이 헤더는 6개의 테이블에서 공통으로 사용되는 내용이다. 6개의 모든 테이블의 내용을 살펴보아야 하지만, MGT 테이블 만을 살펴보겠다. MGT의 내용을 토대로 나머지 테이블의 내용도 ATSC A/65 스펙을 참고하여 공부하면 쉽게 알 수 있을 것이다.



MGT의 구조

MGT STT를 제외한 모든 PSIP 테이블에 대한 version number, length(바이트 단위), PID를 기술한 테이블이다. MGT 0xC7 table ID를 가지는 하나의 섹션으로 전송되며 그 구조는 < 2>와 같다. < 2>에 대한 세부적인 내용을 알아보자.
table_id : 8-bit field 0xC7이며 MGT임을 나타낸다.
section_syntax_indicator : 1-bit field로‘1’로 한다.
private_indicator : 1-bit field로‘1’로 한다.
section_length : section_length 필드 이후부터 이 섹션 끝까지의 길이를 바이트 단위로 나타내는 12-bit field이다. section_length의 최대 길이는 4093을 넘지 않는다.
table_id_extension : 16-bit field 0x0000으로 한다.
version_number : 5-bit field MGT version number이다. MGT의 내용이 바뀌었을 때 1만큼 증가시킨 후 32로 나눈 나머지로 표시된다.
current_next_indicator : 1-bit indicator로‘1’로 한다.
section_number : 8-bit field 0x00으로 한다(MGT 테이블은 오직 하나의 섹션 version number를 나타낸다. field의 값은 각 테이블에 있는 version_number와 같다. 예를 들면, EIT-3에 대한 이 field의 값은 실제 EIT-3 안에 있는 version_number의 값과 같아야 한다. current_next_indicator = 0인 다음의 VCT current_next_indicator=1인 현재의 VCT보다 1만큼 증가시킨 후 32로 나눈 나머지로 표시된다.
number_bytes : 32-bit unsigned integer field loop 내 기술된 table_type을 나타내는 데 사용되는 전체 바이트 수를 나타낸다.
table_type_descriptors_length : loop 내 기술되어 있는 table_type에 대한 descriptor의 전체 길이를 바이트수로 나타낸다.
descriptors_length : MGT descriptort에 대한 전체 길이를 바이트 수로 나타낸다.
CRC_32 : 오류 정정용 32비트 CRC 값이다.


여기까지 MGT 테이블에 사용되는 구성 요소 및 각 항목이 하는 역할에 대해 모두 살펴봤다. 실제로 TS에서 MGT를 추출해 사용되는 데이터를 표현한다면 < 4>와 같이 표현할 수 있다. MGT에서 모든 테이블의 버전을 관리하기 때문에 다른 테이블들에 대한 정보를 갱신 할 때, MGT의 버전 값만을 확인해 다른 테이블의 내용이 갱신되었는지 확인할 수 있다. 실제 제품에서 사용될 때도 시스템은 MGT의 값만을 감시해 다른 테이블들의 정보의 갱신 여부를 확인한다.

PSIP 분석기 만들기
지금까지 PSIP를 분석하기 위한 기본적인 지식을 습득했다. 이제 비주얼 C++로 구현해 보자. 솔직히 지금까지의 내용을 처음 접하는 독자에게는 내용이 너무 어렵고 난해했을 것이다. 실제로 구현해보면서 이해를 돕자.

자료형부터 고려
우리가 만약 C를 사용한다면 구조체로 각각의 섹션을 정의해 이용할것이다. 하지만 C++로 프로그래밍할 것이므로 C++의 장점인 상속을 이용하고 C의 구조체 대신 클래스를 사용한다. 각 섹션에는 모두가 공통적으로 가진 헤더는 TS 헤더와 PSIP 헤더이다. 또한 PSIP 헤더는 TS 헤더의 내용을 포함한다는 것을 알고 있다. 그럼 어떻게 계층을 이룰 것인가.

계층도 만들기
우리가 만들어 사용해야 할 클래스는 기본적으로 6(MGT, SST, VCT, RRT, EIT, ETT)이다. 이 클래스들의 이름을 다음과 같이 부여할 것이라고 가정해 보자. STTSection, VCTSection, MGTSection, RRTSection, EITSection, ETTSection라고 할 수 있다. 이 클래스에 공통적으로 들어 갈 헤더 클래스를 TSHeader PSIP Header라고 하자.

188바이트의 TS를 살펴보면 어느 패킷에나 포함되는 것이 TS 헤더이고 PSIP의 모든 섹션에 공통적으로 사용되는 것이 PSIP 헤더이기 때문에 최상위 클래스가 TS 헤더가 되고 PSIP 헤더는 TS 헤더를 상속하게 된다. 그 외의 6개의 실제 사용할 클래스는 PSIP 헤더를 상속받게 된다. 따라서 <그림 1>과 같이 계층도를 그릴 수 있다. 계층도가 완성됐다면 다음으로 구조체와 같은 데이터로서 사용할 클래스를 만들어야 한다. 각 클래스의 멤버 변수는 스펙을 참고해 스펙에 정의된 비트의 크기에 맞는 데이터형을 선언한다. sync_byte 8비트를 사용하기 때문에 byte형으로 선언하고, transport_error_indicator 1비트를 사용하므로 bool형으로 선언한다. TSHeader 클래스는 다음과 같이 정의할 수 있다.
class CTSHeader //: public CObject
{
public:
BYTE sync_byte;
BOOL transport_error_indicator;
BOOL payload_unit_start_indicator;
BOOL transport_priority;
WORD PID;
BYTE transport_scrambling_control;
BYTE adaptation_field_control;
BYTE continuity_counter;
}
다음으로 PSIP 헤더의 값을 갖는 클래스를 생성한다. 이 클래스 역시 TS의 헤더 값을 갖어야 하므로 CTSHeader를 상속받는다. 이 클래스는 PSIP 헤더의 값을 갖는 클래스이기 때문에 PSIP로 사용하게 될 클래스는 모두 이 클래스를 상속받게 된다.

class CPSIPHeader : public CTSHeader
{
public:
BYTE table_id;
BOOL section_syntax_indicator;
BOOL private_indicator;
BYTE reserved1;
WORD section_length;
WORD table_id_extension;
BYTE reserved2;
BYTE version_number;
BOOL current_next_indicator;

BYTE section_number;
BYTE last_section_number;
BYTE protocol_version;
}

마지막으로 최하위 클래스이자 직접적으로 사용하게 될 클래스인 CMGTSection 클래스를 생성한다. 이 클래스는 CPSIPHeader 클래스를 상속받음으로써 CPSIPHeader 클래스의 값과 CTSHeader 클래스의 값까지 사용할 수 있게 된다. MGT_Table이란 내부 클래스(C에서는 구조체 안의 구조체)를 정의한 후, Clist를 이용해 링크드 리스트로 MGTbody를 선언한다. 링크드 리스트를 사용하는 이유는 MGTbody의 값이 일정하게 고정돼 있지 않고 갯수가 가변적이기 때문이다.

class CMGTSection : public CPSIPHeader
{
class MGT_Table
{
public:
WORD table_type;
BYTE mgt_reserved1;
WORD table_type_pid;
BYTE mgt_reserved2;
BYTE table_type_version_number;
UINT number_bytes;
BYTE mgt_reserved3;
WORD table_type_descriptors_length;
CString name
// table type meaning
}
public:
WORD tables_defined;
BYTE
reserved4;
WORD
descriptors_length;
UINT
CRC_32;
CList<MGT_Table,MGT_Table&>
MGTbody;
MGT_Table MGTbodyStr;
}

실제 1바이트를 사용하기 위한 구조체 클래스를 정의한다. TS의 값은 바이너리로 되어 있고, 바이너리 값을 직접 이용할 수 없으므로 다음과 같은 클래스를 만들어서 하나의 변수처럼 사용한다. , 188바이트의 값을 모두 가져오기 위해서는‘ONE_BYTE oneTS Byte[188];’과 같이 선언해 사용
할 수 있다.

class ONE_BYTE
{
public:
unsigned one : 1;
unsigned two : 1;
unsigned three : 1;
unsigned four : 1;
unsigned five : 1;
unsigned six : 1;
unsigned seven : 1;
unsigned eight : 1;
};
여기까지 앞으로 사용하게 될 자료형을 모두 만들었다. MGT를 제외한 5개의 섹션 역시 MGT와 같은 방법으로 생성하면 된다. 다음으로 구현 알고리즘에 대해 알아보자.


PSIP 분석 방법
이제는 어떠한 방법으로 TS에서 PSIP를 빼내어 분석할 지를 생각할 차례이다. TS PES 스트림과 PSIP 섹션으로 구성되어 있다. 우리는 TS에서 PSIP 섹션만을 추출할 것이다. PSIP TS 패킷(PSIP의 데이터를 갖는 TS 패킷)을 빼내기 위한 가장 기본적이며 포괄적인 순서도는 <그림 2>와 같다. 이것은 TS에서 PSIP를 추출한 후 링크드 리스트를 이용해 메모리에 각각의 PSIP 데이터를 저장하거나 파일을 이용해 결과 값을 저장하는 순서도이다.
STT, MGT, RRT, VCT
는 각각의 고유한 PID와 고유 Table_id를 이용하면 추출할 수 있지만 EIT ETT PID 값은 정해져 있지 않고 MGT가 가지고 있기 때문에 EIT ETT PID 값을 알기 위해서는 먼저 MGT를 분석해 EIT ETT PID를 구해야 한다. 그렇기 때문에 EIT ETT를 구성하기 전에 먼저 MGT 섹션을 구성한 후 MGT가 지정한 EIT ETT PID 값을 확인해 EIT ETT의 값을 갖는 PSIP TS 패킷을 찾아야 한다.
<
그림 3> <그림 2>보다 좀더 구체적으로 표현한 순서도이다. 파일을 열고, Sync를 맞추기 위해 1바이트씩 읽으며 0x47을 찾는다.
0x47
TS Sync 바이트로서 TS의 시작을 의미하는 1바이트이다. Sync가 맞았다면 0x47부터 188바이트를 가지고 온 후, 가져온 188바이트의 PID 값을 확인한다. MGT, STT, VCT, RRT PID는‘0x1FFB’이고, EIT ETT PID 값은 MGT 섹션이 가진 값을 확인해야 한다. MGT를 먼저 분석해 EIT, ETT PID를 확인한다. MGTSTT, VCT, RRT PID 값이 같기 때문에 PSIP TS 패킷이 가진 각각의 Table_id 값을 확인해 구별한다. , MGT Table_id 값은 0xC7이고, STT 0xCE, TVCT 0xC8, RRT 0xCA의 값을 갖는다.
주의할 점은 모든 섹션은 버전 업이 이뤄지지 않은 상태에서도 같은 테이블의 내용을 계속적으로 보내주기 때문에 첫 번째의 테이블 구성이 완료됐다면 version_number 값을 확인해 같은 버전인 경우 받아들이지 않고 PASS하도록 해야 한다. 이때 모든 섹션의 버전을 확인하는 것이 아니라 MGT가 모든 섹션의 버전을 관리하기 때문에 우리는 MGT 섹션만 감시하고 있으면 모든 섹션의 버전 값에 대한 변경 내용을 인지할 수 있게 된다.

MGT 섹션 구성
MGT
섹션의 최대 크기는 4096바이트이다. MGT 섹션의 최대 크기를 참고한다면 최대 크기의 MGT 섹션을 구성하기 위해 필요한 TS의 최대 개수가 23개임을 이미 알아보았다. 이를 감안해 처음부터 차근차근 살펴보자.
188
바이트의 TS 1개를 읽어서 PID Table_id 값을 확인해 MGT인지 판별한다. MGT로 판별되면 이 TS 패킷의 version_number 값을 확인한다. 처음으로 나타난 MGT이거나 version_number가 현재 가지고 있는 MGT의 버전보다 크다면 분석을 시도하고 그렇지 않다면 이미 분석된 것과 같은 것으로 인식해 다음의 TS 188바이트를 가져와 방금했던 작업을 또 다시 실행한다. 이 작업은 파일의 끝까지 이뤄진다.
다음으로 MGT로 판별된 TS MGT 섹션으로 분석하기 위해 메모리에 잡아 놓은 4096바이트의 배열에 MGT로 판별된 TS를 복사 한다. Section_length의 값을 확인한 후에 이 값을 계산해 MGT 섹션을 구성하기 위해 필요한 TS의 개수를 구한다. PID Continuity_counter 값으로 필요한 만큼의 TS를 받아 이전에 복사해 놓은 TS에 이어서 붙여넣기를 한다. 이런 반복 작업이 끝났다면 4096 크기의 배열에는 순수한 MGT 섹션의 값이 들어가게 되는 것이다. 주의할점은 4096 크기의 배열에는 TS 헤더의 값은 제외시킨다는 것이다.
여기까지 설명한 내용의 순서도를 <그림 4>와 같이 나타낼 수 있다.

실제 PSIP 분석기를 완성해봐야
‘이달의 디스켓’에 있는 TS 분석기의 주요 함수가 하는 역할에 대해 알아보도록 하자. ReadFileChangeONEBYTE(CFile *TsFile,ONE_BYTE *oneTSByte)의 역할은 바이너리로 되어 있는 TS 1바이트씩 읽으며, 싱크 바이트를 확인한다. 싱크 바이트를 찾은 후, 싱크 바이트부터 188바이트를 읽어와 프로그램에서 사용할 수 있는 ONE_BYTE형으로 변환해 변환된 값을 포인터로서 반환하는 역할을 한다. 이 작업을 성공적으로 마쳤으면 True를 반환하고, 실패했다면 False를 반환한다.
CheckHeader(ONE_BYTE *oneTSByte, WORD *PID, int *ConCount, UINT *sectionLen, UINT *versionNumber)
의 역할은 ONE_BYTE로 변환된 188바이트의 TS의 값을 읽어와 PID와 테이블 id를 확인해 PSIP의 어떤 섹션인지를 알려주는 역할을 한다. 리턴되는 값은 STT 1, MGT 2, VCT 3, RRT 4, EIT 5, ETT 6 그리고 STT, MGT, VCT, RRT, EIT, ETT의 첫 번째 TS가 아니면 -1이다. 포인터로서 반환되는 값은 continurity_counter,section_length, version_number의 값이다.
여기까지 우리는 PSIP 분석기를 만들기 위한 준비를 마쳤다. 자료형과 순서도를 참조해 코딩과 디버깅의 과정을 열심히 반복한다면 멋진 PSIP 분석기를 제작할 수 있을 것이다. ‘이달의 디스켓(세 개의 파일이 나오는데 소스와 PSIP 스펙과 샘플 데이터)’에 MGT만 분석되게 만든 PSIP 분석기가 있으니 참고하기 바란다. 주석을 충분히달아 이해하기 쉽게 작성했다.
이론을 이론으로 끝내지 않고 적용해야만 그 이론은 나의 것이 된다. TCP/IP를 공부할 때는 간단한 채팅 프로그램이라도 만들어봐야 하고, 웹을 공부할 때는 간단한 홈페이지라도 만들어야 한다. PSIP 분석기를 완성한 당신은 이 시대의 진정한 프로그래머이다.

STT : 오직 1개의 섹션으로 구성, Section_length MAX 1021바이트
MGT :
오직 1개의 섹션으로 구성, Section_lengthMAX 4093바이트
VCT : Section_length
MAX 1021바이트
RRT :
오직 1개의 섹션으로 구성, Section_length MAX 1021바이트
EIT : Section_length
MAX 4093바이트
ETT :
오직 1개의 섹션으로 구성, Section_length MAX 4093바이트