defmodule MPEG.TS.PMT do @behaviour MPEG.TS.Unmarshaler alias MPEG.TS.PSI @moduledoc """ Program Map Table. """ @type stream_type_id_t :: 0..255 @type stream_id_t :: 0..8191 @type stream_t :: %{ stream_type: atom, stream_type_id: stream_type_id_t } @type streams_t :: %{ required(stream_id_t) => stream_t } defstruct [:pcr_pid, program_info: [], streams: %{}] @type t :: %__MODULE__{ streams: streams_t(), program_info: list(), pcr_pid: 0..8191 } @impl true def is_unmarshable?(data, is_start_unit = true) do case PSI.unmarshal_header(data, is_start_unit) do {:ok, {%{table_id: 0x02}, _rest}} -> true _ -> false end end def is_unmarshable?(_data, false), do: false @impl true def unmarshal(data, is_start_unit) do with {:ok, %PSI{table: table}} <- PSI.unmarshal(data, is_start_unit), {:ok, table} <- unmarshal_table(table) do {:ok, table} end end @spec unmarshal_table(binary()) :: {:ok, t()} | {:error, :invalid_data} def unmarshal_table(<< _reserved::3, pcr_pid::13, _reserved2::4, _::2, program_info_length::10, rest::binary >>) do with {:ok, {program_info, rest}} <- parse_program_info(program_info_length, rest), {:ok, streams} <- parse_streams(rest) do result = %__MODULE__{ program_info: program_info, streams: streams, pcr_pid: pcr_pid } {:ok, result} end end defp parse_program_info(0, date), do: {:ok, {[], date}} defp parse_program_info(program_info_length, data) do # TODO: implement parsing <<_program_descriptors::binary-size(program_info_length), rest::binary>> = data {:ok, {[], rest}} end defp parse_streams(data, acc \\ %{}) defp parse_streams(<<>>, acc), do: {:ok, acc} # TODO handle es_info (Page 54, Rec. ITU-T H.222.0 (03/2017)) defp parse_streams( << stream_type_id::8, _reserved::3, elementary_pid::13, _reserved1::4, program_info_length::12, _program_info::binary-size(program_info_length), rest::binary >>, acc ) do stream = %{ stream_type_id: stream_type_id, stream_type: parse_stream_type(stream_type_id) } result = Map.put(acc, elementary_pid, stream) parse_streams(rest, result) end defp parse_streams(_, _) do {:error, :invalid_data} end @stream_id_to_atom %{ 0x00 => :RESERVED, 0x01 => :MPEG1_VIDEO, 0x02 => :MPEG2_VIDEO, 0x03 => :MPEG1_AUDIO, 0x04 => :MPEG2_AUDIO, 0x05 => :MPEG2_PRIVATE_SECTIONS, 0x06 => :MPEG2_PES_PRIVATE_DATA, 0x07 => :MHEG, 0x08 => :MPEG2_DSM_CC, 0x09 => :H222_1, 0x0A => :ISO_13818_6_TYPE_A, 0x0B => :ISO_13818_6_TYPE_B, 0x0C => :ISO_13818_6_TYPE_C, 0x0D => :ISO_13818_6_TYPE_D, 0x0E => :MPEG2_AUX, 0x0F => :AAC, 0x10 => :MPEG4_VISUAL, 0x11 => :MPEG4_AUDIO, 0x12 => :ISO_14496_1_IN_PES, 0x13 => :ISO_14496_1_IN_SECTIONS, 0x14 => :ISO_13818_6_DOWNLOAD, 0x15 => :METADATA_IN_PES, 0x16 => :METADATA_IN_SECTIONS, 0x17 => :METADATA_IN_DATA_CAROUSEL, 0x18 => :METADATA_IN_OBJECT_CAROUSEL, 0x19 => :METADATA_IN_SYNC_DOWNLOAD, 0x1A => :IPMP, 0x1B => :H264, 0x1C => :ISO_14496_10_TEXT, 0x1D => :AUX_VIDEO, 0x1E => :SVC, 0x1F => :MPEG4_SVC, 0x20 => :MPEG4_MVC, 0x21 => :JPEG_2000_VIDEO, 0x22 => :S3D_MPEG2_VIDEO, 0x23 => :S3D_AVC_VIDEO, 0x24 => :HEVC, 0x25 => :HEVC_TEMPORAL_VIDEO, 0x26 => :MVCD, 0x27 => :HEVC_TEMPORAL_SCALABLE, 0x28 => :HEVC_STEPWISE_TEMPORAL_SCALABLE, 0x29 => :HEVC_LAYERED_TEMPORAL_SCALABLE, 0x2A => :HEVC_LAYERED_TEMPORAL_SCALABLE_MVC, 0x2B => :VVC, 0x2C => :VVC_TEMPORAL_SCALABLE, 0x2D => :VVC_TEMPORAL_SCALABLE_SUB_BITSTREAM, 0x80 => :BLURAY_PCM_AUDIO, 0x81 => :AC3_AUDIO, 0x82 => :DTS_AUDIO, 0x83 => :TRUEHD_AUDIO, 0x84 => :EAC3_AUDIO, 0x85 => :HDMV_DTS_AUDIO, 0x86 => :DTS_HD_HRA_AUDIO, 0x87 => :DTS_HD_MA_AUDIO, 0x8A => :DTS_UHD_AUDIO, 0x90 => :PGS_SUBTITLE, 0x91 => :IGS_SUBTITLE, 0x92 => :HDMV_TEXT_SUBTITLE, 0x42 => :DVB_SUBTITLE, 0x59 => :DVB_SUBTITLE_HD, 0x73 => :ATSC_DVD_CONTROL, 0x77 => :ATSC_DOLBY_E, 0x7F => :IPMP_CONTROL, 0xC0 => :SCTE_35_SPLICE, 0xC1 => :SCTE_35_RESERVED, 0xC2 => :SCTE_35_RESERVED } @atom_to_stream_id @stream_id_to_atom |> Enum.map(fn {k, v} -> {v, k} end) |> Map.new() def parse_stream_type(val) when val >= 0xBC and val <= 0xEF, do: {:USER_PRIVATE, val} def parse_stream_type(val) do case Map.get(@stream_id_to_atom, val) do nil -> :undefined other -> other end end def encode_stream_type(val), do: Map.fetch!(@atom_to_stream_id, val) end