Skip to content

Commit 3445712

Browse files
committed
Allow specifing demuxer output pad codec
1 parent 72d38a0 commit 3445712

File tree

6 files changed

+158
-28
lines changed

6 files changed

+158
-28
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The package can be installed by adding `membrane_mp4_plugin` to your list of dep
1212
```elixir
1313
defp deps do
1414
[
15-
{:membrane_mp4_plugin, "~> 0.35.0"}
15+
{:membrane_mp4_plugin, "~> 0.35.1"}
1616
]
1717
end
1818
```

examples/demuxer_isom.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ defmodule Example do
2424
hackney_opts: [follow_redirect: true]
2525
})
2626
|> child(:demuxer, Membrane.MP4.Demuxer.ISOM)
27-
|> via_out(Pad.ref(:output, 1))
27+
|> via_out(Pad.ref(:output, 1), options: [codec: Membrane.H264])
2828
|> child(:parser_video, %Membrane.H264.Parser{output_stream_structure: :annexb})
2929
|> child(:sink_video, %Membrane.File.Sink{location: @output_video}),
3030
get_child(:demuxer)
31-
|> via_out(Pad.ref(:output, 2))
31+
|> via_out(Pad.ref(:output, 2), options: [codec: Membrane.AAC])
3232
|> child(:audio_parser, %Membrane.AAC.Parser{
3333
out_encapsulation: :ADTS
3434
})

lib/membrane_mp4/demuxer/isom.ex

Lines changed: 133 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,19 @@ defmodule Membrane.MP4.Demuxer.ISOM do
3535
%Membrane.Opus{self_delimiting?: false}
3636
),
3737
availability: :on_request,
38-
flow_control: :auto
38+
options: [
39+
codec: [
40+
spec: Membrane.H264 | Membrane.H265 | Membrane.Opus | Membrane.AAC | nil,
41+
default: nil,
42+
description: """
43+
Specifies, what kind of codec can be handled by a pad.
44+
45+
Pad with `:codec` option set to `:all` can handle all codecs.
46+
47+
Defaults to `:all`
48+
"""
49+
]
50+
]
3951

4052
def_options optimize_for_non_fast_start?: [
4153
default: false,
@@ -82,7 +94,9 @@ defmodule Membrane.MP4.Demuxer.ISOM do
8294
boxes_size: 0,
8395
mdat_beginning: nil,
8496
mdat_size: nil,
85-
mdat_header_size: nil
97+
mdat_header_size: nil,
98+
track_to_pad_id: %{},
99+
track_notifications_sent?: false
86100
}
87101

88102
{[], state}
@@ -147,7 +161,7 @@ defmodule Membrane.MP4.Demuxer.ISOM do
147161
state.partial <> buffer.payload
148162
)
149163

150-
buffers = get_buffer_actions(samples)
164+
buffers = get_buffer_actions(samples, state)
151165

152166
{buffers, %{state | samples_info: samples_info, partial: rest}}
153167
end
@@ -356,22 +370,26 @@ defmodule Membrane.MP4.Demuxer.ISOM do
356370

357371
state = %{state | samples_info: samples_info, partial: rest}
358372

373+
state = match_tracks_with_pads(ctx, state)
374+
359375
all_pads_connected? = all_pads_connected?(ctx, state)
360376

361377
{buffers, state} =
362378
if all_pads_connected? do
363-
{get_buffer_actions(samples), state}
379+
{get_buffer_actions(samples, state), state}
364380
else
365381
{[], store_samples(state, samples)}
366382
end
367383

368384
notifications = get_track_notifications(state)
385+
369386
stream_format = if all_pads_connected?, do: get_stream_format(state), else: []
370387

371388
state =
372389
%{
373390
state
374-
| all_pads_connected?: all_pads_connected?
391+
| all_pads_connected?: all_pads_connected?,
392+
track_notifications_sent?: true
375393
}
376394
|> update_fsm_state()
377395

@@ -385,9 +403,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do
385403
end)
386404
end
387405

388-
defp get_buffer_actions(samples) do
406+
defp get_buffer_actions(samples, state) do
389407
Enum.map(samples, fn {buffer, track_id} ->
390-
{:buffer, {Pad.ref(:output, track_id), buffer}}
408+
pad_id = state.track_to_pad_id[track_id]
409+
{:buffer, {Pad.ref(:output, pad_id), buffer}}
391410
end)
392411
end
393412

@@ -398,12 +417,59 @@ defmodule Membrane.MP4.Demuxer.ISOM do
398417
end
399418
end
400419

420+
defp match_tracks_with_pads(ctx, state) do
421+
codec_to_pads_data =
422+
ctx.pads
423+
|> Map.values()
424+
|> Enum.reject(fn %{ref: pad_ref} -> pad_ref == :input end)
425+
|> Enum.group_by(& &1.options.codec)
426+
427+
{track_to_pad_id, empty_codec_to_pads_data} =
428+
state.samples_info.sample_tables
429+
|> Enum.map_reduce(codec_to_pads_data, fn {track_id, table}, codec_to_pads_data ->
430+
%track_codec{} = table.sample_description
431+
432+
case codec_to_pads_data[track_codec] do
433+
[pad_data | tail] ->
434+
codec_to_pads_data = Map.put(codec_to_pads_data, track_codec, tail)
435+
Pad.ref(:output, pad_id) = pad_data.ref
436+
{{track_id, pad_id}, codec_to_pads_data}
437+
438+
_no_pad ->
439+
{{track_id, nil}, codec_to_pads_data}
440+
end
441+
end)
442+
443+
max_pad_id =
444+
track_to_pad_id
445+
|> Enum.flat_map(fn {_track, pad_id} -> if pad_id != nil, do: [pad_id], else: [] end)
446+
|> Enum.max(&>=/2, fn -> 0 end)
447+
448+
{track_to_pad_id, _max_pad_id} =
449+
track_to_pad_id
450+
|> Enum.map_reduce(max_pad_id, fn
451+
{track_id, nil}, max_pad_id -> {{track_id, max_pad_id + 1}, max_pad_id + 1}
452+
track_pad_pair, max_pad_id -> {track_pad_pair, max_pad_id}
453+
end)
454+
455+
raise? =
456+
empty_codec_to_pads_data
457+
|> Map.values()
458+
|> Enum.any?(&(&1 != []))
459+
460+
if raise? do
461+
"dupaaaa"
462+
end
463+
464+
%{state | track_to_pad_id: Map.new(track_to_pad_id)}
465+
end
466+
401467
defp get_track_notifications(state) do
402468
new_tracks =
403469
state.samples_info.sample_tables
404470
|> Enum.map(fn {track_id, table} ->
405-
content = table.sample_description
406-
{track_id, content}
471+
pad_id = state.track_to_pad_id[track_id]
472+
{pad_id, table.sample_description}
407473
end)
408474

409475
[{:notify_parent, {:new_tracks, new_tracks}}]
@@ -412,7 +478,8 @@ defmodule Membrane.MP4.Demuxer.ISOM do
412478
defp get_stream_format(state) do
413479
state.samples_info.sample_tables
414480
|> Enum.map(fn {track_id, table} ->
415-
{:stream_format, {Pad.ref(:output, track_id), table.sample_description}}
481+
pad_id = state.track_to_pad_id[track_id]
482+
{:stream_format, {Pad.ref(:output, pad_id), table.sample_description}}
416483
end)
417484
end
418485

@@ -425,7 +492,8 @@ defmodule Membrane.MP4.Demuxer.ISOM do
425492
raise "All tracks have corresponding pad already connected"
426493
end
427494

428-
def handle_pad_added(Pad.ref(:output, _track_id), ctx, state) do
495+
def handle_pad_added(Pad.ref(:output, _track_id) = pad_ref, ctx, state) do
496+
:ok = validate_pad_codec!(pad_ref, ctx.pad_options.codec, ctx, state)
429497
all_pads_connected? = all_pads_connected?(ctx, state)
430498

431499
{actions, state} =
@@ -444,6 +512,53 @@ defmodule Membrane.MP4.Demuxer.ISOM do
444512
{actions, state}
445513
end
446514

515+
defp validate_pad_codec!(pad_ref, pad_codec, ctx, state) do
516+
allowed_codecs = [nil, Membrane.H264, Membrane.H265, Membrane.Opus, Membrane.AAC]
517+
518+
if pad_codec not in allowed_codecs do
519+
raise """
520+
Pad #{inspect(pad_ref)} has :codec option set to #{inspect(pad_codec)}, while it has te be one of \
521+
#{List.delete(allowed_codecs, nil) |> inspect()} or be unspecified.
522+
"""
523+
end
524+
525+
if not state.track_notifications_sent? and
526+
Enum.count(ctx.pads, &match?({Pad.ref(:output, _id), %{options: %{codec: nil}}}, &1)) > 1 do
527+
raise """
528+
If pads are linked before :new_tracks notifications and there are more then one of them, pad option \
529+
:codec has to be specyfied.
530+
"""
531+
end
532+
533+
if state.track_notifications_sent? do
534+
Pad.ref(:output, pad_id) = pad_ref
535+
536+
related_track =
537+
state.track_to_pad_id
538+
|> Map.keys()
539+
|> Enum.find(&(state.track_to_pad_id[&1] == pad_id))
540+
541+
if related_track == nil do
542+
raise """
543+
Pad #{inspect(pad_ref)} doesn't have a related track. If you link pads after #{inspect(__MODULE__)} \
544+
sent the track notification, you have to restrict yourself to the pad occuring in this notification. \
545+
Tracks, that occured in this notification are: #{Map.keys(state.track_to_pad_id) |> inspect()}
546+
"""
547+
end
548+
549+
track_codec = state.samples_info.sample_tables[related_track].sample_description
550+
551+
if pad_codec != nil and track_codec != pad_codec do
552+
raise """
553+
Pad option :codec must point on the related track codec or be equal nil, but pad #{inspect(pad_ref)} \
554+
codec is #{inspect(pad_codec)}, while the related track codec is #{inspect(track_codec)}
555+
"""
556+
end
557+
end
558+
559+
:ok
560+
end
561+
447562
@impl true
448563
def handle_end_of_stream(:input, _ctx, %{all_pads_connected?: false} = state) do
449564
{[], %{state | end_of_stream?: true}}
@@ -465,11 +580,11 @@ defmodule Membrane.MP4.Demuxer.ISOM do
465580
_pad -> []
466581
end)
467582

468-
Enum.each(pads, fn pad ->
469-
if pad not in tracks do
470-
raise "An output pad connected with #{pad} id, however no matching track exists"
471-
end
472-
end)
583+
# Enum.each(pads, fn pad ->
584+
# if pad not in tracks do
585+
# raise "An output pad connected with #{pad} id, however no matching track exists"
586+
# end
587+
# end)
473588

474589
Range.size(tracks) == length(pads)
475590
end
@@ -482,7 +597,8 @@ defmodule Membrane.MP4.Demuxer.ISOM do
482597
|> Enum.reverse()
483598
|> Enum.map(fn {buffer, ^track_id} -> buffer end)
484599

485-
{:buffer, {Pad.ref(:output, track_id), buffers}}
600+
pad_id = state.track_to_pad_id[track_id]
601+
{:buffer, {Pad.ref(:output, pad_id), buffers}}
486602
end)
487603

488604
state = %{state | buffered_samples: %{}}

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Membrane.MP4.Plugin.MixProject do
22
use Mix.Project
33

4-
@version "0.35.0"
4+
@version "0.35.1"
55
@github_url "https://github.com/membraneframework/membrane_mp4_plugin"
66

77
def project do

test/membrane_mp4/demuxer/isom/demuxer_test.exs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -284,28 +284,42 @@ defmodule Membrane.MP4.Demuxer.ISOM.DemuxerTest do
284284
end
285285

286286
defp start_testing_pipeline!(opts) do
287-
structure = [
287+
spec =
288288
child(:file, %Membrane.File.Source{location: opts[:input_file]})
289289
|> child(:demuxer, Membrane.MP4.Demuxer.ISOM)
290+
291+
pipeline = Pipeline.start_link_supervised!(spec: spec)
292+
assert_pipeline_notified(pipeline, :demuxer, {:new_tracks, _notification})
293+
294+
spec =
295+
get_child(:demuxer)
290296
|> via_out(Pad.ref(:output, 1))
291297
|> child(:sink, %Membrane.File.Sink{location: opts[:output_file]})
292-
]
293298

294-
Pipeline.start_link_supervised!(spec: structure)
299+
Pipeline.execute_actions(pipeline, spec: spec)
300+
pipeline
295301
end
296302

297303
defp start_testing_pipeline_with_two_tracks!(opts) do
298-
structure = [
304+
spec =
299305
child(:file, %Membrane.File.Source{location: opts[:input_file]})
300306
|> child(:demuxer, Membrane.MP4.Demuxer.ISOM)
307+
308+
pipeline = Pipeline.start_link_supervised!(spec: spec)
309+
310+
assert_pipeline_notified(pipeline, :demuxer, {:new_tracks, _notification})
311+
312+
spec = [
313+
get_child(:demuxer)
301314
|> via_out(Pad.ref(:output, 1))
302315
|> child(:video_sink, %Membrane.File.Sink{location: opts[:video_output_file]}),
303316
get_child(:demuxer)
304317
|> via_out(Pad.ref(:output, 2))
305318
|> child(:audio_sink, %Membrane.File.Sink{location: opts[:audio_output_file]})
306319
]
307320

308-
Pipeline.start_link_supervised!(spec: structure)
321+
Pipeline.execute_actions(pipeline, spec: spec)
322+
pipeline
309323
end
310324

311325
defp start_remote_pipeline!(opts) do

test/membrane_mp4/demuxer/isom/integration_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,11 @@ defmodule Membrane.MP4.Demuxer.ISOM.IntegrationTest do
168168
demuxing_spec = [
169169
child(:file, %Membrane.File.Source{location: mp4_path})
170170
|> child(:demuxer, Membrane.MP4.Demuxer.ISOM)
171-
|> via_out(Pad.ref(:output, 1))
171+
|> via_out(Pad.ref(:output, 1), options: [codec: Membrane.H264])
172172
|> child(:parser_video, %Membrane.H264.Parser{output_stream_structure: :annexb})
173173
|> child(:sink_video, %Membrane.File.Sink{location: out_video_path}),
174174
get_child(:demuxer)
175-
|> via_out(Pad.ref(:output, 2))
175+
|> via_out(Pad.ref(:output, 2), options: [codec: Membrane.AAC])
176176
|> child(:audio_parser, %Membrane.AAC.Parser{
177177
out_encapsulation: :ADTS
178178
})

0 commit comments

Comments
 (0)