/membrane_mpegts_plugin

MPEG-TS demuxer

Primary LanguageElixirApache License 2.0Apache-2.0

Membrane Multimedia Framework: MPEG-TS

Hex.pm CircleCI

This package provides an element that can be used for demuxing MPEG-TS.

It is part of Membrane Multimedia Framework.

Installation

The package can be installed by adding membrane_mpegts_plugin to your list of dependencies in mix.exs:

def deps do
  [
    {:membrane_mpegts_plugin, "~> 0.4.0"}
  ]
end

The docs can be found at HexDocs.

Abbreviations

PAT - Program Association Table PMT - Program Mapping Table

Usage

Demuxer is an element that has one :input and variable amount of outputs depending on the stream. In this particular example we are demuxing a file that contains MPEG1 audio and H264 video.

@impl true
def handle_init(path) do
  children = [
    source_file: %File.Source{location: path, chunk_size: 64_000},
    demuxer: MPEG.TS.Demuxer,
    video_parser: %H264.Parser{framerate: {24, 1}},
    video_decoder: H264.Decoder,
    player: SDL.Player,
    audio_decoder: Mad.Decoder,
    audio_converter: %SWResample.Converter{
      output_caps: %Raw{channels: 2, format: :s16le, sample_rate: 48_000}
    },
    portaudio: PortAudio.Sink
  ]

  links = [
    link(:source_file) |> to(:demuxer),
    link(:demuxer) |> via_out(Pad.ref(:output, 256)) |> to(:video_parser),
    link(:demuxer) |> via_out(Pad.ref(:output, 257)) |> to(:audio_decoder),
    link(:video_parser) |> to(:video_decoder),
    link(:video_decoder) |> to(:player),
    link(:audio_decoder) |> to(:audio_converter),
    link(:audio_converter) |> to(:portaudio)
  ]

  spec = %Spec{
    children: children,
    links: links,
    stream_sync: :sinks
  }

  {{:ok, spec: spec}, %{}}
end

Upon successful parsing of MPEG Transport stream specific information, demuxer will notify its parent (usally a pipeline). When the parent receives {:mpeg_ts_stream_info, prog_map_tables} message it will need to link the demuxer outputs. The demuxer will continue its work when either every stream will have its corresponding pad linked or after receiving :pads_ready message. If demuxer receives :pads_ready it will continue its work even though some pads might not be linked.

prog_map_tables that is received by pipeline has following format:

%{
  program_id => %Membrane.MPEG.TS.ProgramMapTable{
    streams: %{
      packet_identifier => %{
        type: atom,
        type_id: 0..255
      }
    }
  }
}

So, far example, if we wanted to have as simple behaviour as use first stream with matching type we would do it like this:

@impl true
def handle_notification({:mpeg_ts_stream_info, pmt}, _from, state) do
  {audio_pid, video_pid} = parse_mapping(pmt)

  children = [
    audio: An.Audio.Element,
    video: A.Video.Element
  ]

  links = [
    link(:demuxer) |> via_out(Pad.ref(:output, audio_pid)) |> to(:audio),
    link(:demuxer) |> via_out(Pad.ref(:output, video_pid)) |> to(:video)
  ]

  spec = %Spec{
    children: children,
    links: links,
  }

  {{:ok, spec: spec}, state}
end

defp parse_mapping(mapping) do
  mapping = mapping[1]

  with {:ok, audio_pid} <- first_matching_stream(mapping.streams, :MPEG1_AUDIO),
      {:ok, video_pid} <- first_matching_stream(mapping.streams, :H264) do
    {audio_pid, video_pid}
  end
end

def first_matching_stream(streams, type) do
  streams
  |> Enum.find(fn {_, value} -> value.type == type end)
  |> case do
    nil -> {:error, :no_stream}
    {pid, _stream_spec} -> {:ok, pid}
  end
end

Copyright and License

Copyright 2019, Software Mansion

Software Mansion

Licensed under the Apache License, Version 2.0