TSパケットからはじめる(6) - SDT

SDT(Service Description Table)

前回文字列のデコード処理は解決したのでSDTやEITの情報を取得してみます。SDTのPIDは0x11でtable_idが0x42(自ストリーム)、0x46(他ストリーム)となります。今回は自ストリームを取得したいので0x42のテーブルIDのSDTを取得します。

SDTデータ構造

SDTのdescriptor()という項目がありますがこの部分はタグ記述子と言われます。SDTには0x48(サービス記述子)と言われる情報が含まれています。

サービス記述子(0x48)のデータ構造

TSファイル読み込み処理のクラスも作成しておきたいと思います。188バイト毎読み込みするのは効率的ではないので、一括(94000個)を読み込みしています。

public class TsReader: IDisposable
{
    private FileStream FStream { get; set; } = null;
    private BinaryReader Reader { get; set; } = null;

    public string Filename { 
        get {
            if (this.FStream != null)
            {
                return this.FStream.Name;
            }

            return string.Empty;
        } }

    private int Packet_Size { get; set; } = 188;
    private int StockCount { get; set; } = 94000;
    private byte[] StockPacket { get; set; }
    private int StockPositon { get; set; } = -1;

    private int ReadCount { get; set; } = 0;
    private int Correction { get; set; } = 0;

    public Int64 TSLength 
    { 
        get { return (this.FStream == null) ? -1 : this.FStream.Length / this.Packet_Size; } 
    }

    public Int64 TSPosition
    {
        get { return (this.FStream == null) ? -1 : this.FStream.Position / this.Packet_Size; }
        set
        {
            if (this.FStream != null)
            {
                this.FStream.Position = this.Packet_Size * value;
                this.StockPositon = -1;
            }
        }
    }

    public TsReader(string Filename) : this(Filename, 188)
    {
    }

    public TsReader(string Filename, int Packet_Length)
    {
        this.FStream = new FileStream(Filename, FileMode.Open, FileAccess.Read);
        this.Reader = new BinaryReader(this.FStream);
        this.Packet_Size = Packet_Length;
        this.ReadCount = 0;
    }

    public TsPacket Read()
    {
        if (this.StockPositon == -1 || (this.StockPacket.Length  == this.StockPositon * this.Packet_Size))
        {
            this.StockPacket = this.Reader.ReadBytes(this.StockCount * this.Packet_Size);
            this.StockPositon = 0;

            if (this.StockPacket.Length == 0)
            {
                return null;
            }
        }

        // Console.WriteLine("StockLength = {0}, StockPosion x PacketSize = {1}, {2}", this.StockPacket.Length, this.StockPositon * this.Packet_Size, ReadCount);

        byte[] rows = new byte[this.Packet_Size];
        Array.Copy(this.StockPacket, this.StockPositon * this.Packet_Size, rows, 0, this.Packet_Size);
        
        if (rows[0] != 0x47)
        {
            // 位置ずれを補正処理はまだ入れていない
            return null;
        }

        this.StockPositon++;
        this.ReadCount++;

        return new TsPacket(rows);
    }

    private bool CheckTsFormat()
    {
        if ((this.StockPacket[0] == 0x47) && (this.StockPacket[this.Packet_Size * 1] == 0x47) && (this.StockPacket[this.Packet_Size * 2] == 0x47))
        {
            return true;
        }

        return false;
    }

    public void Close()
    {
        this.Dispose();
    }

    public void Dispose()
    {
        if (this.Reader != null)
        {
            this.Reader.Close();
            this.Reader = null;
        }

        if (this.FStream != null)
        {
            this.FStream.Close();
            this.FStream = null;
        }
    }
}

SDTデータ構造からTsSDTクラスを作成してみます。

public class TsSDT
{
    public Dictionary<int, string> ServiceNames { get; set; }
    public int transport_stream_id { get; private set; }
    public int tableid { get; private set; }

    public TsSDT(byte[] rows)
    {
        using (MemoryStream ms = new MemoryStream(rows))
        {
            this.tableid = ms.ReadByte();
            int section_length = (ms.ReadByte() & 0x0f) << 8 | ms.ReadByte();
            this.transport_stream_id = ms.ReadByte() << 8 | ms.ReadByte();
            ms.Position += 6;

            section_length -= (8 + 4);
            B24Decoder b24convert;
            this.ServiceNames = new Dictionary<int, string>();

            while (section_length != 0)
            {
                int service_id = ms.ReadByte() << 8 | ms.ReadByte();
                ms.Position++;
                byte b = (byte)ms.ReadByte();
                int running_status = (b & 0xe0) >> 5;
                bool free_CA_mode = ((b & 0x20) != 0) ? true : false;
                int descriptor_loop_length = (b & 0x0f) << 8 | ms.ReadByte();
                section_length -= (5 + descriptor_loop_length);

                while (descriptor_loop_length != 0)
                {
                    int tag = ms.ReadByte();
                    int tag_length = ms.ReadByte();

                    switch (tag)
                    {
                        case 0x48: // サービス記述子
                            int service_type = ms.ReadByte();
                            b24convert = new B24Decoder();
                            string service_provider_name = b24convert.AtriToString(Utils.ReadBytes(ms, ms.ReadByte()));
                            this.ServiceNames.Add(service_id, b24convert.AtriToString(Utils.ReadBytes(ms, ms.ReadByte())));
                            break;

                        default:
                            ms.Position += tag_length;
                            break;
                    }

                    descriptor_loop_length -= (tag_length + 2);
                }
            }
        }
    }
}

PAT、PMT、SDTを取得する処理で読み出しする。

public bool GetProgramInfo()
{
    int count = 0;
    List<byte> rows = new List<byte>();

    // TSファイルの真ん中に移動
    tsr.TSPosition = tsr.TSLength / 2;
    TsPacket pktt = null;
    PSICollector pat_collector = new PSICollector(0x00);
    PSICollector pmt_collector = null;
    PSICollector sdt_collector = new PSICollector(0x11, 0x42);

    Dictionary<PSICollector, bool> collects = new Dictionary<PSICollector, bool>();
    collects.Add(sdt_collector, false);

    while ((pktt = tsr.Read()) != null)
    {
        count++;
        if (!pat_collector.IsFinished && pat_collector.AddPacket(pktt))
        {
            TsPAT pat = new TsPAT(pat_collector.Payload.ToArray());
            pmt_collector = new PSICollector(pat.program_pid[pat.program_pid.Keys.First()]);
            collects.Add(pmt_collector, false);
        }

        if (pat_collector.IsFinished)
        {
            bool IsFinish = true;
            for (int i = 0; i < collects.Count; i++)
            {
                if (!collects[collects.Keys.ElementAt(i)])
                {
                    if (collects.Keys.ElementAt(i).AddPacket(pktt))
                    {
                        collects[collects.Keys.ElementAt(i)] = true;
                    }
                    else
                    {
                        IsFinish = false;
                    }
                }
            }
            if (IsFinish) { break; }
        }

        if (count >= limit_packet)
        {
            break;
        }
    }

    this.SDT = new TsSDT(collects.Keys.ElementAt(0).Payload.ToArray());
    this.PMT = new TsPMT(collects.Keys.ElementAt(2).Payload.ToArray());

    return true;
}

SDTではチャンネル名称を取得することができます。

SDTの取得例


この記事が気に入ったらサポートをしてみませんか?