次の方法で共有


MPEG-2 PSI テーブルの取得

MPEG-2 セクションとテーブル フィルタは、MPEG-2 トランスポート ストリームから プログラム固有情報 (PSI) テーブルを取得する。このフィルタを使うと、ATSC プログラムとシステム情報プロトコル (PSIP) テーブル、DVB サービス情報 (SI)、条件付きアクセス テーブル (CAT)、DSM-CC メッセージ、プライベート テーブル データなど、あらゆる種類の 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 構造体への省略可能なポインタである。この構造体を使い、バージョン番号、現在/次のインジケータなど、一致させる追加フィールドを指定できる。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 にセクションデータが入っている。
        }
    }
}

参照