@@ -5,7 +5,22 @@ defmodule Membrane.MP4.Demuxer.ISOM do
55 The MP4 must have `fast start` enabled, i.e. the `moov` box must precede the `mdat` box.
66 Once the Demuxer identifies the tracks in the MP4, `t:new_tracks_t/0` notification is sent for each of the tracks.
77
8- All the tracks in the MP4 must have a corresponding output pad linked (`Pad.ref(:output, track_id)`).
8+ All pads has to be linked either before `handle_playing/2` callback or after the Element sends `{:new_tracks, ...}`
9+ notification.
10+
11+ Number of pads has to be equal to the number of demuxed tracks.
12+
13+ If the demuxed data contains only one track, linked pad doesn't have to specify `:kind` option.
14+
15+ If there are more than one track and pads are linked before `handle_playing/2`, every pad has to specify `:kind`
16+ option.
17+
18+ If any of pads isn't linked before `handle_playing/2`, #{ inspect ( __MODULE__ ) } will send `{:new_tracks, ...}`
19+ notification to the parent. Otherwise, if any of them is linked before `handle_playing/3`, this notification won't
20+ be sent.
21+
22+ If pads are linked after the `{:new_tracks, ...}` notfitaction, their references must match MP4 tracks ids
23+ (`Pad.ref(:output, track_id)`).
924 """
1025 use Membrane.Filter
1126
@@ -35,7 +50,15 @@ defmodule Membrane.MP4.Demuxer.ISOM do
3550 % Membrane.Opus { self_delimiting?: false }
3651 ) ,
3752 availability: :on_request ,
38- flow_control: :auto
53+ options: [
54+ kind: [
55+ spec: :video | :audio | nil ,
56+ default: nil ,
57+ description: """
58+ Specifies, what kind of data can be handled by a pad.
59+ """
60+ ]
61+ ]
3962
4063 def_options optimize_for_non_fast_start?: [
4164 default: false ,
@@ -82,7 +105,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do
82105 boxes_size: 0 ,
83106 mdat_beginning: nil ,
84107 mdat_size: nil ,
85- mdat_header_size: nil
108+ mdat_header_size: nil ,
109+ track_to_pad_id: % { } ,
110+ track_notifications_sent?: false ,
111+ pads_linked_before_notification?: false
86112 }
87113
88114 { [ ] , state }
@@ -147,7 +173,7 @@ defmodule Membrane.MP4.Demuxer.ISOM do
147173 state . partial <> buffer . payload
148174 )
149175
150- buffers = get_buffer_actions ( samples )
176+ buffers = get_buffer_actions ( samples , state )
151177
152178 { buffers , % { state | samples_info: samples_info , partial: rest } }
153179 end
@@ -356,22 +382,26 @@ defmodule Membrane.MP4.Demuxer.ISOM do
356382
357383 state = % { state | samples_info: samples_info , partial: rest }
358384
385+ state = match_tracks_with_pads ( ctx , state )
386+
359387 all_pads_connected? = all_pads_connected? ( ctx , state )
360388
361389 { buffers , state } =
362390 if all_pads_connected? do
363- { get_buffer_actions ( samples ) , state }
391+ { get_buffer_actions ( samples , state ) , state }
364392 else
365393 { [ ] , store_samples ( state , samples ) }
366394 end
367395
368- notifications = get_track_notifications ( state )
396+ notifications = maybe_get_track_notifications ( state )
397+
369398 stream_format = if all_pads_connected? , do: get_stream_format ( state ) , else: [ ]
370399
371400 state =
372401 % {
373402 state
374- | all_pads_connected?: all_pads_connected?
403+ | all_pads_connected?: all_pads_connected? ,
404+ track_notifications_sent?: true
375405 }
376406 |> update_fsm_state ( )
377407
@@ -385,9 +415,10 @@ defmodule Membrane.MP4.Demuxer.ISOM do
385415 end )
386416 end
387417
388- defp get_buffer_actions ( samples ) do
418+ defp get_buffer_actions ( samples , state ) do
389419 Enum . map ( samples , fn { buffer , track_id } ->
390- { :buffer , { Pad . ref ( :output , track_id ) , buffer } }
420+ pad_id = state . track_to_pad_id [ track_id ]
421+ { :buffer , { Pad . ref ( :output , pad_id ) , buffer } }
391422 end )
392423 end
393424
@@ -398,12 +429,98 @@ defmodule Membrane.MP4.Demuxer.ISOM do
398429 end
399430 end
400431
401- defp get_track_notifications ( state ) do
432+ defp match_tracks_with_pads ( ctx , state ) do
433+ sample_tables = state . samples_info . sample_tables
434+
435+ output_pads_data =
436+ ctx . pads
437+ |> Map . values ( )
438+ |> Enum . filter ( & ( & 1 . direction == :output ) )
439+
440+ if length ( output_pads_data ) not in [ 0 , map_size ( sample_tables ) ] do
441+ raise_pads_not_matching_codecs_error! ( ctx , state )
442+ end
443+
444+ track_to_pad_id =
445+ case output_pads_data do
446+ [ ] ->
447+ sample_tables
448+ |> Map . new ( fn { track_id , _table } -> { track_id , track_id } end )
449+
450+ [ pad_data ] ->
451+ { track_id , table } = Enum . at ( sample_tables , 0 )
452+
453+ if pad_data . options . kind not in [
454+ nil ,
455+ sample_description_to_kind ( table . sample_description )
456+ ] do
457+ raise_pads_not_matching_codecs_error! ( ctx , state )
458+ end
459+
460+ % { track_id => pad_data_to_pad_id ( pad_data ) }
461+
462+ _many ->
463+ kind_to_pads_data = output_pads_data |> Enum . group_by ( & & 1 . options . kind )
464+
465+ kind_to_tracks =
466+ sample_tables
467+ |> Enum . group_by (
468+ fn { _track_id , table } -> sample_description_to_kind ( table . sample_description ) end ,
469+ fn { track_id , _table } -> track_id end
470+ )
471+
472+ raise? =
473+ Enum . any? ( kind_to_pads_data , fn { kind , pads } ->
474+ length ( pads ) != length ( kind_to_tracks [ kind ] )
475+ end )
476+
477+ if raise? , do: raise_pads_not_matching_codecs_error! ( ctx , state )
478+
479+ kind_to_tracks
480+ |> Enum . flat_map ( fn { kind , tracks } ->
481+ pad_refs = kind_to_pads_data [ kind ] |> Enum . map ( & pad_data_to_pad_id / 1 )
482+ Enum . zip ( tracks , pad_refs )
483+ end )
484+ |> Map . new ( )
485+ end
486+
487+ % { state | track_to_pad_id: Map . new ( track_to_pad_id ) }
488+ end
489+
490+ defp pad_data_to_pad_id ( % { ref: Pad . ref ( _name , id ) } ) , do: id
491+
492+ @ spec raise_pads_not_matching_codecs_error! ( map ( ) , map ( ) ) :: no_return ( )
493+ defp raise_pads_not_matching_codecs_error! ( ctx , state ) do
494+ pads_kinds =
495+ ctx . pads
496+ |> Enum . flat_map ( fn
497+ { :input , _pad_data } -> [ ]
498+ { _pad_ref , % { options: % { kind: kind } } } -> [ kind ]
499+ end )
500+
501+ tracks_codecs =
502+ state . samples_info . sample_tables
503+ |> Enum . map ( fn { _track , table } -> table . sample_description . __struct__ end )
504+
505+ raise """
506+ Pads kinds don't match with tracks codecs. Pads kinds are #{ inspect ( pads_kinds ) } . \
507+ Tracks codecs are #{ inspect ( tracks_codecs ) }
508+ """
509+ end
510+
511+ defp sample_description_to_kind ( % Membrane.H264 { } ) , do: :video
512+ defp sample_description_to_kind ( % Membrane.H265 { } ) , do: :video
513+ defp sample_description_to_kind ( % Membrane.AAC { } ) , do: :audio
514+ defp sample_description_to_kind ( % Membrane.Opus { } ) , do: :audio
515+
516+ defp maybe_get_track_notifications ( % { pads_linked_before_notification?: true } ) , do: [ ]
517+
518+ defp maybe_get_track_notifications ( % { pads_linked_before_notification?: false } = state ) do
402519 new_tracks =
403520 state . samples_info . sample_tables
404521 |> Enum . map ( fn { track_id , table } ->
405- content = table . sample_description
406- { track_id , content }
522+ pad_id = state . track_to_pad_id [ track_id ]
523+ { pad_id , table . sample_description }
407524 end )
408525
409526 [ { :notify_parent , { :new_tracks , new_tracks } } ]
@@ -412,7 +529,8 @@ defmodule Membrane.MP4.Demuxer.ISOM do
412529 defp get_stream_format ( state ) do
413530 state . samples_info . sample_tables
414531 |> Enum . map ( fn { track_id , table } ->
415- { :stream_format , { Pad . ref ( :output , track_id ) , table . sample_description } }
532+ pad_id = state . track_to_pad_id [ track_id ]
533+ { :stream_format , { Pad . ref ( :output , pad_id ) , table . sample_description } }
416534 end )
417535 end
418536
@@ -425,7 +543,23 @@ defmodule Membrane.MP4.Demuxer.ISOM do
425543 raise "All tracks have corresponding pad already connected"
426544 end
427545
428- def handle_pad_added ( Pad . ref ( :output , _track_id ) , ctx , state ) do
546+ def handle_pad_added ( Pad . ref ( :output , _track_id ) = pad_ref , ctx , state ) do
547+ state =
548+ case ctx . playback do
549+ :stopped ->
550+ % { state | pads_linked_before_notification?: true }
551+
552+ :playing when state . track_notifications_sent? ->
553+ state
554+
555+ :playing ->
556+ raise """
557+ Pads can be linked either before #{ inspect ( __MODULE__ ) } enters :playing playback or after it \
558+ sends {:new_tracks, ...} notification
559+ """
560+ end
561+
562+ :ok = validate_pad_kind! ( pad_ref , ctx . pad_options . kind , ctx , state )
429563 all_pads_connected? = all_pads_connected? ( ctx , state )
430564
431565 { actions , state } =
@@ -444,6 +578,55 @@ defmodule Membrane.MP4.Demuxer.ISOM do
444578 { actions , state }
445579 end
446580
581+ defp validate_pad_kind! ( pad_ref , pad_kind , ctx , state ) do
582+ allowed_kinds = [ nil , :audio , :video ]
583+
584+ if pad_kind not in allowed_kinds do
585+ raise """
586+ Pad #{ inspect ( pad_ref ) } has :kind option set to #{ inspect ( pad_kind ) } , while it has te be one of \
587+ #{ [ :audio , :video ] |> inspect ( ) } or be unspecified.
588+ """
589+ end
590+
591+ if not state . track_notifications_sent? and
592+ Enum . count ( ctx . pads , & match? ( { Pad . ref ( :output , _id ) , % { options: % { kind: nil } } } , & 1 ) ) > 1 do
593+ raise """
594+ If pads are linked before :new_tracks notifications and there are more then one of them, pad option \
595+ :kind has to be specyfied.
596+ """
597+ end
598+
599+ if state . track_notifications_sent? do
600+ Pad . ref ( :output , pad_id ) = pad_ref
601+
602+ related_track =
603+ state . track_to_pad_id
604+ |> Map . keys ( )
605+ |> Enum . find ( & ( state . track_to_pad_id [ & 1 ] == pad_id ) )
606+
607+ if related_track == nil do
608+ raise """
609+ Pad #{ inspect ( pad_ref ) } doesn't have a related track. If you link pads after #{ inspect ( __MODULE__ ) } \
610+ sent the track notification, you have to restrict yourself to the pad occuring in this notification. \
611+ Tracks, that occured in this notification are: #{ Map . keys ( state . track_to_pad_id ) |> inspect ( ) }
612+ """
613+ end
614+
615+ track_kind =
616+ state . samples_info . sample_tables [ related_track ] . sample_description
617+ |> sample_description_to_kind ( )
618+
619+ if pad_kind != nil and pad_kind != track_kind do
620+ raise """
621+ Pad option :kind must match with the kind of the related track or be equal nil, but pad #{ inspect ( pad_ref ) } \
622+ kind is #{ inspect ( pad_kind ) } , while the related track kind is #{ inspect ( track_kind ) }
623+ """
624+ end
625+ end
626+
627+ :ok
628+ end
629+
447630 @ impl true
448631 def handle_end_of_stream ( :input , _ctx , % { all_pads_connected?: false } = state ) do
449632 { [ ] , % { state | end_of_stream?: true } }
@@ -465,12 +648,6 @@ defmodule Membrane.MP4.Demuxer.ISOM do
465648 _pad -> [ ]
466649 end )
467650
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 )
473-
474651 Range . size ( tracks ) == length ( pads )
475652 end
476653
@@ -482,7 +659,8 @@ defmodule Membrane.MP4.Demuxer.ISOM do
482659 |> Enum . reverse ( )
483660 |> Enum . map ( fn { buffer , ^ track_id } -> buffer end )
484661
485- { :buffer , { Pad . ref ( :output , track_id ) , buffers } }
662+ pad_id = state . track_to_pad_id [ track_id ]
663+ { :buffer , { Pad . ref ( :output , pad_id ) , buffers } }
486664 end )
487665
488666 state = % { state | buffered_samples: % { } }
0 commit comments