DirectShow DirectShow 의 사용법 DirectShow 의 MPEG-2 지원 MPEG-2 PSI 테이블의 취득   [목차열람] [주소복사] [슬롯비우기]
MPEG-2 PSI 테이블의 취득
 
Microsoft DirectX 9.0

MPEG-2 PSI 테이블의 취득

MPEG-2 섹션과 테이블 필터는, MPEG-2 트랜스포트 스트림으로부터 프로그램 고유 정보 (PSI) 테이블을 얻어온다. 이 필터를 사용하면 ATSC 프로그램과 시스템 정보 프로토콜 (PSIP) 테이블, DVB 서비스 정보 (SI), 조건부 액세스 테이블 (CAT), DSM-CC 메시지, Private 테이블 데이터 등, 모든 종류의 PSI 데이터를 취득할 수 있다.

이 필터를 사용하려면 , 필터를 MPEG-2 디멀티플렉서 ("demux")에 접속한다. 디지털 TV 그래프에서는, 이 필터는 디폴트로 demux 의 핀 5 에 접속된다. 다른 종류의 필터 그래프의 경우, 애플리케이션은 demux 에 새로운 출력 핀을 생성 해, 그 핀을 필터에 접속한다. 출력 핀을 생성 하려면 , demux 에 대해서 IMpeg2Demultiplexer::CreateOutputPin 메서드를 호출한다. 메이저 타입을 MEDIATYPE_MPEG2_SECTIONS 에, 서브 타입을 MEDIASUBTYPE_MPEG2DATA 로 설정한다.

트랜스포트 스트림으로부터 PSI 테이블을 얻어오려면, 필터의 IMpeg2Data 인터페이스를 사용한다. 그래프의 생성에 의해 필터의 포인터가 이미 있는 경우는,QueryInterface 를 호출해 인터페이스 포인터를 얻어온다. 그 이외의 경우,IFilterGraph::EnumFilters 를 사용해 그래프의 필터를 열거해, 각 필터로 인터페이스를 문의한다. ( 「필터 또는 핀의 인터페이스의 검색」의 샘플 코드를 참조할것. ) 디지털 TV 애플리케이션에서는, 애플리케이션으로 비디오 컨트롤을 사용해 그래프를 생성 하는 경우,IMSVidGraphSegmentContainer::get_Graph 메서드를 사용해 필터 그래프의 포인터를 얻어온다.

애플리케이션은 테이블의 1 개의 섹션, 테이블 전체, 또는 테이블 섹션이 연속하는 스트림을 취득할 수 있다.

1 개의 섹션 또는 테이블 전체의 취득

테이블에서 1 개의 섹션을 얻어오려면,IMpeg2Data::GetSection 메서드를 호출한다.

#include <mpeg2data.h>

IMpeg2Data *pMPEG = NULL; // 섹션과 테이블 필터의 포인터.
ISectionList *pSectionList = NULL;

// IMpeg2Data 인터페이스를 공개하는 필터를 그래프로 검색하는 것으로,
// pMPEG 를 초기화한다 (생략). 그래프를 실행해, MPEG-2 데이터를 얻어온다.

// PID 0 을 가지는 다음의 섹션을 얻어온다. 이 PID 는 PAT 에 예약되고 있다.
PID pid = 0x00;
TID tid = 0x00;
DWORD dwTimeout = 5000; // 밀리 세컨드.

hr = pMPEG->GetSection(pid, tid, NULL, dwTimeout, &pSectionList);
if (SUCCEEDED(hr))
{
    // pSectionList 를 사용해 데이터에 액세스 한다.
    pSectionList->Release();
}

GetSection 의 최초의 2 개의 파라미터는 패킷 식별자 (PID)와 테이블 식별자 (TID)를 지정한다. 이러한 값에 일치하는 섹션의 취득 또는 메서드의 타임 아웃의 어느 쪽인지가 먼저 행해지는 시점까지, 이 메서드는 동작을 정지한다. demux 필터에 PID 를 맵 할 필요는 없는 점에 주의 해야 한다. GetSection 메서드는 자동적으로 이 처리를 실시한다. 3 번째의 파라미터는 MPEG2_FILTER 구조체에의 생략 가능한 포인터이다. 이 구조체를 사용해, 버전 번호, 현재/다음의 인디케이터(indicator) 등, 일치시키는 추가 필드를 지정할 수 있다. GetSection 메서드는 ISectionList 인터페이스의 포인터를 돌려준다. 이 인터페이스는 섹션 데이터의 취득에 사용된다.

테이블 전체를 얻어오려면,IMpeg2Data::GetTable 메서드를 호출한다. 이 메서드는 GetSection 메서드와 같은 의미들을 갖는다. 그러나, 필터가 테이블 전체의 데이터를 축적하는 (또는 메서드가 타임 아웃이 된다)까지, 메서드는 동작을 정지한다. 메서드가 성공하면ISectionList 인터페이스는 1 개의 섹션은 아니고, 테이블의 모든 섹션의 리스트를 참조한다.

ISectionList 인터페이스를 사용해 섹션 데이터에 액세스 한다. ISectionList::GetNumberOfSections 를 호출해, 캡춰 된 섹션수를 조사한다. 다음에,ISectionList::GetSectionData 를 호출해, 각 섹션으로부터 데이터를 얻어온다.

hr = pMPEG->GetTable(pid, tid, NULL, dwTimeout, &pSectionList);
if (SUCCEEDED(hr))
{
    WORD cSections;
    hr = pSectionList->GetNumberOfSections(&cSections);
    for (WORD i = 0; i < cSections; i++)
    {
        // 섹션의 리스트 전체를 차례로 처리한다.
        SECTION *pSection;
        DWORD cbSize;
        hr = pSectionList->GetSectionData(i, &cbSize, &pSection);
        if (SUCCEEDED(hr))
        {
            // pSection 에 저장 된 데이터를 조사한다.
        }
    }
    pSectionList->Release();
}

GetSectionData 메서드는 SECTION 구조체로서 데이터를 돌려준다. 이 구조체에는, 섹션 헤더 필드 및 데이터의 나머지의 부분의 포인터가 포함되어 있다. 헤더의 section_syntax_indicator 비트가 1 으로 설정되어 있는 경우, 섹션은 확장 헤더 정보를 포함하고 있는 것을 의미한다. SECTION 구조체를 LONG_SECTION 형에 캐스트 하는 것으로 확장 헤더 필드를 취득할 수 있다. DSM-CC 메시지의 경우,SECTION 구조체를 DSMCC_SECTION 구조체에 캐스트 할 수 있다.

이하의 코드는, 디버거 윈도우에 섹션 데이터를 표시하는 함수이다. 이 함수는, 모든 헤더 필드에 액세스 하는 방법을 나타낸다. 나머지의 데이터에 대해서는, 미처리의 바이트값을 표시한다.

#include <mpeg2data.h>
#include <mpeg2bits.h>

void PrintByteArray(const BYTE *pData, long cbSize); // 앞쪽 선언.

HRESULT PrintMpeg2Section(SECTION *pSection, DWORD dwPacketLength)
{
    if (! pSection)
    {
        return E_POINTER;
    }
    if (dwPacketLength < sizeof(SECTION))
    {
        ATLTRACE(L"Malformed MPEG-2 section data. \n");
        return E_FAIL;
    }

    // 헤더 바이트를 비트 필드 구조체에 강제 변환한다.
    MPEG_HEADER_BITS *pHeader = (MPEG_HEADER_BITS*) &pSection->Header.W;

    ATLTRACE(L"Packet Length: %d bytes. \n", dwPacketLength);
    ATLTRACE(L"Table ID: 0x%. 2x\n", pSection->TableId);
    ATLTRACE(L"Section Syntax Indicator: 0x%x\n", 
        pHeader->SectionSyntaxIndicator);
    ATLTRACE(L"Private Indicator: 0x%x\n", pHeader->PrivateIndicator);
    ATLTRACE(L"Reserved: 0x%x\n", pHeader->Reserved);
    ATLTRACE(L"Section Length: %d\n", pHeader->SectionLength);

    if (pHeader->SectionSyntaxIndicator)
    {
        // 섹션 구조체를 장 섹션 헤더에 강제 변환한다.
        LONG_SECTION *pLong = (LONG_SECTION*) pSection;

        MPEG_HEADER_VERSION_BITS *pVersion = 
            (MPEG_HEADER_VERSION_BITS*) &pLong->Version.B;

        ATLTRACE(L"Long section fields ...\n");
        ATLTRACE(L"TID Extension: 0x%. 4x\n", pLong->TableIdExtension);
        ATLTRACE(L"Reserved: 0x%x\n", pVersion->Reserved);
        ATLTRACE(L"Version: %d\n", pVersion->VersionNumber);
        ATLTRACE(L"Current/Next: 0x%x\n", pVersion->CurrentNextIndicator);
        ATLTRACE(L"Section Number: %d\n", pLong->SectionNumber);
        ATLTRACE(L"Last Section Number: %d\n", pLong->LastSectionNumber);

        // DSM-CC 메시지 타입을 찾는다.
        if (pSection->TableId == 0x3B || pSection->TableId == 0x3C)
        {
            // 섹션 구조체를 DSM-CC 헤더에 강제 변환한다.
            DSMCC_SECTION *pDsmcc = (DSMCC_SECTION*) pSection;

            ATLTRACE(L"DSM-CC section fields ...\n");
            ATLTRACE(L"Protocol Discriminator: 0x%. 2x\n", 
                pDsmcc->ProtocolDiscriminator);
            ATLTRACE(L"Type: 0x%. 2x\n", pDsmcc->DsmccType);
            ATLTRACE(L"MessageId: 0x%. 4x\n", pDsmcc->MessageId);
            ATLTRACE(L"TransactionId: 0x%. 8x\n", pDsmcc->TransactionId);
            ATLTRACE(L"Reserved: 0x%. 2x\n", pDsmcc->Reserved);
            ATLTRACE(L"AdaptationLength: 0x%. 2x\n", 
                pDsmcc->AdaptationLength);
            ATLTRACE(L"MessageLength: 0x%. 4x\n", pDsmcc->MessageLength);
            ATLTRACE(L"Remaining bytes ...\n");
            PrintByteArray(pDsmcc->RemainingData, 
                pDsmcc->MessageLength + pDsmcc->AdaptationLength);
        }
        else
        {
            // 이것은 DSM-CC 메시지는 아니다. 나머지의 바이트를 출력한다.
            ATLTRACE(L"Remaining bytes ...\n");
            PrintByteArray(pLong->RemainingData, pHeader->SectionLength - 5);
        }
    }
    else
    {
        // 장 섹션 헤더는 아니다. 나머지의 바이트를 출력한다.
        ATLTRACE(L"Section bytes ...\n");
        PrintByteArray(pSection->SectionData, pHeader->SectionLength);
    }

    return S_OK;
}

void PrintByteArray(const BYTE *pData, long cbSize)
{
    for (int iter = 0; iter < cbSize; iter++)
    {
        ATLTRACE(L"0x%. 2x ", pData[iter]);
        if (iter % 8 == 7)
        {
            ATLTRACE(L"\n");
        }
    }
}

일부의 헤더 정보는 비트 필드에 팩 되는 점에 주의 해야 한다. 헤더 구조 체내에서, 이러한 필드는 단순한 바이트형 및 WORD 형으로서 정의된다. 각각의 비트 필드를 얻어오려면, 예로 가리키도록(듯이), 구조체 멤버의 일부를 강제 변환할 필요가 있다. 예를 들어,SECTION 구조체의 Header.W 멤버는 MPEG_HEADER_BITS 구조체에 캐스트 할 수 있다.

헤더에 계속되는 데이터는 미해석의 바이트 배열로 반환된다. 데이터를 해석하는 것은, 클라이언트의 책임이다. 따라서, 수신하는 트랜스포트 스트림의 포맷을 이해해 둘 필요가 있다. 많은 경우, 포맷은 사용하는 네트워크 규격에 따라서 다르다. 예를 들어, ATSC 및 DVB 는 서비스 정보 테이블에 다른 사양을 사용한다. 보통, 비트 마스크를 적용해 필드의 일부를 취득해, 네트워크 바이트순서로부터 호스트 바이트순서에 데이터를 변환할 필요가 있다.

섹션의 스트림의 취득

1 회에 1 개의 섹션이나 1 개의 테이블의 동기 요구를 실시하는 대신에,IMpeg2Data::GetStreamOfSections 메서드를 사용해, 섹션의 연속 스트림을 취득할 수 있다. GetStreamOfSections 메서드는 곧바로 돌아간다. 호출원래는, 새로운 데이터가 도착할 때 마다 통지되는 이벤트를 제공한다. 최초로 이벤트를 생성 해, 다음에 일치 대상의 PID 값과 TID 값을 사용해 GetStreamOfSections 를 호출한다.

HANDLE hCompleted = CreateEvent(NULL, FALSE, FALSE, NULL);
if (! hCompleted) 
{
    // 이벤트는 생성 되지 않았다. 에러를 처리한다.
}
IMpeg2Stream *pStream = 0;
hr = pMPEG->GetStreamOfSections(pid, tid, NULL, hCompleted, &pStream);

성공했을 경우, 메서드는 IMpeg2Stream 인터페이스 포인터를 돌려준다. 이 인터페이스를 사용해 섹션 데이터를 얻어온다.

데이터를 보관 유지하는 1 개 또는 복수의 버퍼를 할당하는 것. 각 버퍼는 4096 바이트 이상으로 할 필요가 있다. 또, 버퍼를 관리하기 위한 MPEG_STREAM_BUFFER 구조체를 선언한다. 얻어온다 섹션 마다, 구조체의 pDataBuffer 멤버가 버퍼를 가리키도록(듯이) 설정해,dwDataBufferSize 를 버퍼와 같은 사이즈로 설정한다. 그 외의 멤버는 제로로 설정하는 것.

const int cBufferSize = 4096; // 버퍼 사이즈.
BYTE Buffer1[cBufferSize];    // 버퍼.
MPEG_STREAM_BUFFER streamBuffer;  // 버퍼를 관리하는 구조체.

ZeroMemory(&streamBuffer, sizeof(MPEG_STREAM_BUFFER));
streamBuffer.dwDataBufferSize = cBufferSize;
streamBuffer.pDataBuffer = (BYTE*) Buffer1;

IMpeg2Stream::SupplyDataBuffer 를 호출해, 이벤트가 통지될 때까지 대기한다. 이벤트가 통지되면MPEG_STREAM_BUFFER 구조체의 hr 필드를 조사한다. 이 필드에 성공 코드가 들어가 있는 경우, 요구는 성공하고 있어 버퍼에는 1 개의 섹션이 저장 되고 있다. dwSizeOfDataRead 필드는 섹션 데이터의 사이즈를 지정한다. 데이터는 네트워크 바이트순서로 미해석이다.

hr = pStream->SupplyDataBuffer(&streamBuffer);
if (SUCCEEDED(hr))
{
    DWORD dwWait = WaitForSingleObject(hRequestCompleted, dwTimeout);
    if (dwWait == WAIT_OBJECT_0)
    {
        if (SUCCEEDED(streamBuffer.hr))
        {
            // Buffer1 에 섹션 데이터가 들어가 있다.
        }
    }
}

참조

↑TOP