TSパケットからはじめる(1) - TS概要
目的
地上波/BS/CS放送はMPEG2-TS(MPEG2 Transport Stream)形式で送信されてくるのでそれをチューナで基本的にそのまま受信して保存しています。そのため、録画されたTSファイルには動画データ、音声データ、字幕データの他にも様々な情報が含まれた形で保存されています。今回目的としているのは番組情報に関するデータを取得することです。
TSパケット構成
地デジ放送等では複数の動作データ、複数の音声データ、複数の字幕データ等が含まれていたりしますが、これらのデータを管理するための情報も必要になります。データとして送信されてくるのは固定サイズ188バイト単位(TSパケット)で様々なデータが入り混じった状況になります。TSパケットを識別するための情報がTSヘッダ情報に記載されており、その中のPID(パケット識別子)と呼ばれるものです。
![](https://assets.st-note.com/img/1643260027893-NygJ4k7ZZs.png?width=800)
このTSパケットのヘッダは4バイトで構成されており、先ほど紹介したPIDもヘッダの中に含まれています。さらに拡張ヘッダを持つ場合と持たない場合があり、この拡張ヘッダのことをアダプテーションフィールドと呼びます。拡張ヘッダがあるかどうかは4バイトヘッダの中から判断できます。
TSパケット構成
TSパケットは以下のような定義がされております。"sync byte"から"continuity counter"までが4バイトで、アダプテーションフィールドは可変長、残りはデータ領域です。
![](https://assets.st-note.com/img/1643260580026-OQ8mneEDjq.png?width=800)
PIDは3バイト目の下位5ビットと4バイト目で13ビット構成であることが分かります。188バイトを読み込み続けてPIDを取得してみたいと思います。
using System;
using System.Collections.Generic;
using System.IO;
namespace TsSample1
{
class Program
{
static void Main(string[] args)
{
SortedDictionary<int, int> packet_list = new SortedDictionary<int, int>();
using (FileStream fs = new FileStream(args[0], FileMode.Open, FileAccess.Read))
{
using (BinaryReader br = new BinaryReader(fs))
{
byte[] pktt;
while((pktt = br.ReadBytes(188)).Length != 0)
{
int PID = (pktt[1] & 0x1f) << 8 | pktt[2];
if (packet_list.ContainsKey(PID))
{
packet_list[PID]++;
}
else
{
packet_list.Add(PID, 1);
}
}
}
}
foreach(var key in packet_list.Keys)
{
Console.WriteLine("PID = 0x{0}\t\tCount = {1}", key.ToString("X2"), packet_list[key]);
}
}
}
}
引数にTSファイルを指定すると以下のようになりました。
PID = 0x00 Count = 3085
PID = 0x01 Count = 32
PID = 0x10 Count = 309
PID = 0x11 Count = 154
PID = 0x12 Count = 15049
PID = 0x14 Count = 62
PID = 0x23 Count = 35
PID = 0x24 Count = 308
PID = 0x28 Count = 10
PID = 0x29 Count = 8
PID = 0x100 Count = 2718777
PID = 0x110 Count = 59847
PID = 0x111 Count = 139
PID = 0x130 Count = 244
PID = 0x138 Count = 307
PID = 0x140 Count = 45646
PID = 0x160 Count = 183298
PID = 0x161 Count = 17182
PID = 0x162 Count = 24668
PID = 0x170 Count = 55432
PID = 0x171 Count = 15542
PID = 0x172 Count = 571
PID = 0x1F0 Count = 6158
PID = 0x1FF Count = 5165
PID = 0x901 Count = 3078
色んなPIDが入り混じっていることが分かりますね。188バイトを受け取ってヘッダをデコードするクラスを生成してみます。
public class TsPacket
{
public int sync_byte { get; private set; }
public int transport_error_indicator { get; private set; }
public int payload_unit_start_indicator { get; private set; }
public int transport_priority { get; private set; }
public int PID { get; private set; }
public int transport_scrambling_control { get; private set; }
public int adaptation_field_control { get; private set; }
public int continuity_counter { get; private set; }
public int adaptation_field_length { get; private set; } = 0;
public TsPacket(byte[] rows)
{
this.sync_byte = rows[0];
this.transport_error_indicator = (rows[1] & 0x80) >> 7;
this.payload_unit_start_indicator = (rows[1] & 0x40) >> 6;
this.transport_priority = (rows[1] & 0x20) >> 5;
this.PID = (rows[1] & 0x1f) << 8 | rows[2];
this.transport_scrambling_control = (rows[3] & 0xc0) >> 6;
this.adaptation_field_control = (rows[3] & 0x30) >> 4;
this.continuity_counter = (rows[3] & 0x0f);
if (this.adaptation_field_control == 0x02 || this.adaptation_field_control == 0x03)
{
this.adaptation_field_length = rows[4];
}
}
}
adaptation field controlが0x02ですが2進数は10となります。TSパケット構造では2進数10の先頭ビットが1の場合はアダプテーションフィールドが存在することを意味します。2ビット目が1の場合はデータ(ペイロード)が存在しています。
パケット種別PESとPSI
動画データ、音声データ、字幕データ等は常時データとして流れており、一般的にストリームと呼ばれます。これらのストリームのデータのことをPES(Packetized Elementary Stream)と呼びます。ストリーム以外のプログラム(番組)情報など、PAT、PMT、EIT等のことをPSI(Program Specific Information)と呼ばれます。
何が言いたいかというとTSパケットにはヘッダ(アダプテーションフィールド含めて)以外にペイロード(要はデータ)が含まれますが、このペイロードの扱い方がPES、PSIで若干異なります。
PESペイロード: ヘッダ(アダプテーションフィールドを含めて)を除いたデータが全てペイロードとなります。
![](https://assets.st-note.com/img/1643334407374-ICoIrSDwVU.png?width=800)
PSIペイロード: payload unit start indicatorが0の時はヘッダ(アダプテーションフィールドを含めて)を除いたデータが全てペイロードとなります。payload unit start indicatorが1の時は1バイト目(ポインターフィールドと呼ぶ)が0の時は、2バイト目以降は全てペイロードとなります。X(0以外)の時は2バイト目以降にX分だけ以前のペイロードが含まれており、2バイト目からX分進んだ先に先頭ペイロードが格納されています。
![](https://assets.st-note.com/img/1643334441459-VJryOsK6Ra.png?width=800)
コメント : 途中から何言っているか分かりませんね。書いている本人もどんだけ説明下手なんだと思います。ということで上の図を追加しました。
![](https://assets.st-note.com/img/1643334822021-CUlTthLI2G.png?width=800)
補足するとTSパケットは188バイト単位で送られて来ますが、1つのパケットで完結しているデータもあれば複数のパケットにまたがっている場合もあります。複数パケットの先頭であることを意味するのがpayload unit start indicatorとなります。このpayload unit start indicatorフラグが有効な時に最初の1バイト目がポインターフィールド値となり、0以外の場合には上記図の下段のような形でペイロードが格納されています。
TSヘッダ補足
長くなりそうですのでヘッダに含まれるcontinuity_counterを最後に紹介しておきます。continuity_counterは4ビットなので0x0~0xFの16個となります。元々通信環境が補償されているわけではないため、パケットロスする可能性があります。それをこのcontinuity_counterで判断しています。
![](https://assets.st-note.com/img/1643336535090-dcSS8K8bPB.png?width=800)
このcontinuity_counterはPID毎にカウントされ、0x00の次は0x01、0x01の次は0x02となり、0x0Fの次は0x00となります。16個のパケットをロスするとパットロスを検知できない問題はありますが、そこまで綺麗にドロップすることはないかと思います。
ポイントをまとめると、
TSパケットは188バイトで4バイトのヘッダ
先頭パケットは0x47
PID(パケット識別子)でTSパケットを識別
拡張ヘッダ(アダプテーションフィールド)はある時と無い時があり可変長
PES、PSI(ポインターフィールド考慮が必要)はペイロードの扱いが異なる
continuity_counterでパケットロスを検知
これらの情報から目的のパケットを収集することができるかというと、以下の点が解決していません。
TSパケットがPESかPSIかはどのように区別するのか?
パケット収集にはどのくらいのサイズまで集めれば良いのか?(次のpayload unit start indicatorフラグが有効なパケットが来るまで?)
この記事が気に入ったらサポートをしてみませんか?