@@ -12,16 +12,20 @@ defmodule Membrane.RTMP.Sink do
12
12
require Membrane.Logger
13
13
14
14
alias __MODULE__ . Native
15
- alias Membrane . { AAC , MP4 }
15
+ alias Membrane . { AAC , Buffer , MP4 }
16
16
17
17
@ supported_protocols [ "rtmp://" , "rtmps://" ]
18
18
@ connection_attempt_interval 500
19
19
@ default_state % {
20
20
attempts: 0 ,
21
21
native: nil ,
22
- buffered_frame: nil ,
23
- ready: false ,
24
- current_timestamps: % { }
22
+ # Keys here are the pad names.
23
+ frame_buffer: % { audio: nil , video: nil } ,
24
+ ready?: false ,
25
+ # Activated when one of the source inputs gets closed. Interleaving is
26
+ # disabled, frame buffer is flushed and from that point buffers on the
27
+ # remaining pad are simply forwarded to the output.
28
+ forward_mode?: false
25
29
}
26
30
27
31
def_input_pad :audio ,
@@ -130,52 +134,52 @@ defmodule Membrane.RTMP.Sink do
130
134
131
135
@ impl true
132
136
def handle_write ( pad , buffer , _ctx , % { ready: false } = state ) do
133
- state = Map . put ( state , :buffered_frame , { pad , buffer } )
134
- { :ok , state }
137
+ { :ok , fill_frame_buffer ( state , pad , buffer ) }
135
138
end
136
139
137
- @ impl true
138
- def handle_write (
139
- pad ,
140
- buffer ,
141
- _ctx ,
142
- % { ready: true , buffered_frame: { frame_pad , frame } } = state
143
- ) do
144
- state = write_frame ( state , frame_pad , frame ) |> write_frame ( pad , buffer )
145
- { { :ok , demand: get_demand ( state ) } , Map . put ( state , :buffered_frame , nil ) }
140
+ def handle_write ( pad , buffer , _ctx , % { forward_mode?: true } = state ) do
141
+ { { :ok , demand: pad } , write_frame ( state , pad , buffer ) }
146
142
end
147
143
148
- @ impl true
149
144
def handle_write ( pad , buffer , _ctx , state ) do
150
- state = write_frame ( state , pad , buffer )
151
- { { :ok , demand: get_demand ( state ) } , state }
145
+ state
146
+ |> fill_frame_buffer ( pad , buffer )
147
+ |> write_frame_interleaved ( )
152
148
end
153
149
154
150
@ impl true
155
- def handle_end_of_stream ( pad , ctx , state ) do
156
- if ctx . pads |> Map . values ( ) |> Enum . all? ( & & 1 . end_of_stream? ) do
151
+ def handle_end_of_stream ( pad , _ctx , state ) do
152
+ if state . forward_mode? do
157
153
Native . finalize_stream ( state . native )
158
154
{ :ok , state }
159
155
else
160
- state = Map . update! ( state , :current_timestamps , & Map . delete ( & 1 , pad ) )
161
- { { :ok , demand: get_demand ( state ) } , state }
156
+ # The interleave logic does not work if either one of the inputs does not
157
+ # produce buffers. From this point on we act as a "forward" filter.
158
+ other_pad =
159
+ case pad do
160
+ :audio -> :video
161
+ :video -> :audio
162
+ end
163
+
164
+ state = flush_frame_buffer ( state )
165
+ { { :ok , demand: other_pad } , % { state | forward_mode?: true } }
162
166
end
163
167
end
164
168
165
169
@ impl true
166
170
def handle_other ( :try_connect , _ctx , % { attempts: attempts , max_attempts: max_attempts } = state )
167
171
when max_attempts != :infinity and attempts >= max_attempts do
168
- raise "Failed to connect to '#{ state . rtmp_url } ' #{ attempts } times, aborting"
172
+ raise "failed to connect to '#{ state . rtmp_url } ' #{ attempts } times, aborting"
169
173
end
170
174
171
- def handle_other ( :try_connect , ctx , state ) do
175
+ def handle_other ( :try_connect , _ctx , state ) do
172
176
state = % { state | attempts: state . attempts + 1 }
173
177
174
178
case Native . try_connect ( state . native ) do
175
179
:ok ->
176
180
Membrane.Logger . debug ( "Correctly initialized connection with: #{ state . rtmp_url } " )
177
- demands = ctx . pads |> Map . keys ( ) |> Enum . map ( & { :demand , & 1 } )
178
- { { :ok , [ { :playback_change , :resume } | demands ] } , state }
181
+
182
+ { { :ok , [ { :playback_change , :resume } | build_demand ( state ) ] } , state }
179
183
180
184
{ :error , error } when error in [ :econnrefused , :etimedout ] ->
181
185
Process . send_after ( self ( ) , :try_connect , @ connection_attempt_interval )
@@ -187,22 +191,70 @@ defmodule Membrane.RTMP.Sink do
187
191
{ :ok , state }
188
192
189
193
{ :error , reason } ->
190
- raise "Failed to connect to '#{ state . rtmp_url } ': #{ reason } "
194
+ raise "failed to connect to '#{ state . rtmp_url } ': #{ reason } "
195
+ end
196
+ end
197
+
198
+ defp build_demand ( % { frame_buffer: frame_buffer } ) do
199
+ frame_buffer
200
+ |> Enum . filter ( fn { _pad , buffer } -> buffer == nil end )
201
+ |> Enum . map ( fn { pad , _ } -> { :demand , pad } end )
202
+ end
203
+
204
+ defp fill_frame_buffer ( state , pad , buffer ) do
205
+ if get_in ( state , [ :frame_buffer , pad ] ) == nil do
206
+ put_in ( state , [ :frame_buffer , pad ] , buffer )
207
+ else
208
+ raise "attempted to overwrite frame buffer on pad #{ inspect ( pad ) } "
191
209
end
192
210
end
193
211
212
+ defp write_frame_interleaved ( state = % { frame_buffer: % { audio: audio , video: video } } )
213
+ when audio == nil or video == nil do
214
+ # We still have to wait for the other frame.
215
+ { :ok , state }
216
+ end
217
+
218
+ defp write_frame_interleaved ( % { frame_buffer: frame_buffer } = state ) do
219
+ { pad , buffer } =
220
+ Enum . min_by ( frame_buffer , fn { _ , buffer } ->
221
+ buffer
222
+ |> Buffer . get_dts_or_pts ( )
223
+ |> Ratio . ceil ( )
224
+ end )
225
+
226
+ state =
227
+ state
228
+ |> write_frame ( pad , buffer )
229
+ |> put_in ( [ :frame_buffer , pad ] , nil )
230
+
231
+ { { :ok , build_demand ( state ) } , state }
232
+ end
233
+
234
+ defp flush_frame_buffer ( % { frame_buffer: frame_buffer } = state ) do
235
+ pads_with_buffer =
236
+ frame_buffer
237
+ |> Enum . filter ( fn { _pad , buffer } -> buffer != nil end )
238
+ |> Enum . sort ( fn { _ , left } , { _ , right } ->
239
+ Buffer . get_dts_or_pts ( left ) <= Buffer . get_dts_or_pts ( right )
240
+ end )
241
+
242
+ Enum . reduce ( pads_with_buffer , state , fn { pad , buffer } , state ->
243
+ state
244
+ |> write_frame ( pad , buffer )
245
+ |> put_in ( [ :frame_buffer , pad ] , nil )
246
+ end )
247
+ end
248
+
194
249
defp write_frame ( state , :audio , buffer ) do
195
250
buffer_pts = buffer . pts |> Ratio . ceil ( )
196
251
197
252
case Native . write_audio_frame ( state . native , buffer . payload , buffer_pts ) do
198
253
{ :ok , native } ->
199
254
Map . put ( state , :native , native )
200
- |> Map . update! ( :current_timestamps , fn curr_tmps ->
201
- Map . put ( curr_tmps , :audio , buffer_pts )
202
- end )
203
255
204
256
{ :error , reason } ->
205
- raise ( "Writing audio frame failed with reason: #{ reason } " )
257
+ raise "writing audio frame failed with reason: #{ inspect ( reason ) } "
206
258
end
207
259
end
208
260
@@ -216,19 +268,9 @@ defmodule Membrane.RTMP.Sink do
216
268
) do
217
269
{ :ok , native } ->
218
270
Map . put ( state , :native , native )
219
- |> Map . update! ( :current_timestamps , fn curr_tmps ->
220
- Map . put ( curr_tmps , :video , buffer . dts )
221
- end )
222
271
223
272
{ :error , reason } ->
224
- raise ( "Writing video frame failed with reason: #{ reason } " )
273
+ raise "writing video frame failed with reason: #{ inspect ( reason ) } "
225
274
end
226
275
end
227
-
228
- defp get_demand ( state ) do
229
- { pad , _timestamp } =
230
- state . current_timestamps |> Enum . min_by ( fn { _pad , timestamp } -> timestamp end )
231
-
232
- { pad , 1 }
233
- end
234
276
end
0 commit comments