Skip to content

Commit 2320456

Browse files
authored
Add codec information to manifest (#5)
* add codec info to manifest * serialize, deserialize * format * add tests, apply suggestions * credo, format
1 parent a01510e commit 2320456

File tree

3 files changed

+315
-7
lines changed

3 files changed

+315
-7
lines changed

lib/ex_webrtc_recorder.ex

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ defmodule ExWebRTC.Recorder do
77
Can optionally upload the saved files to S3-compatible storage.
88
See `ExWebRTC.Recorder.S3` and `t:options/0` for more info.
99
"""
10-
1110
alias ExWebRTC.MediaStreamTrack
1211
alias __MODULE__.S3
1312

@@ -121,11 +120,12 @@ defmodule ExWebRTC.Recorder do
121120
recorder(),
122121
MediaStreamTrack.id(),
123122
MediaStreamTrack.rid() | nil,
123+
ExWebRTC.RTPCodecParameters.t() | nil,
124124
ExRTP.Packet.t()
125125
) :: :ok
126-
def record(recorder, track_id, rid, %ExRTP.Packet{} = packet) do
126+
def record(recorder, track_id, rid, codec, %ExRTP.Packet{} = packet) do
127127
recv_time = System.monotonic_time(:millisecond)
128-
GenServer.cast(recorder, {:record, track_id, rid, recv_time, packet})
128+
GenServer.cast(recorder, {:record, track_id, rid, codec, recv_time, packet})
129129
end
130130

131131
@doc """
@@ -220,10 +220,12 @@ defmodule ExWebRTC.Recorder do
220220
end
221221

222222
@impl true
223-
def handle_cast({:record, track_id, rid, recv_time, packet}, state)
223+
def handle_cast({:record, track_id, rid, codec, recv_time, packet}, state)
224224
when is_map_key(state.track_data, track_id) do
225225
%{file: file, rid_map: rid_map} = state.track_data[track_id]
226226

227+
state = if codec, do: update_codec(state, track_id, codec), else: state
228+
227229
with {:ok, rid_idx} <- Map.fetch(rid_map, rid),
228230
false <- is_nil(file) do
229231
:ok = IO.binwrite(file, serialize_packet(packet, rid_idx, recv_time))
@@ -243,7 +245,7 @@ defmodule ExWebRTC.Recorder do
243245
end
244246

245247
@impl true
246-
def handle_cast({:record, track_id, _rid, _recv_time, _packet}, state) do
248+
def handle_cast({:record, track_id, _rid, _codec, _recv_time, _packet}, state) do
247249
Logger.warning("""
248250
Tried to save packet for unknown track id. Ignoring. Track id: #{inspect(track_id)}.\
249251
""")
@@ -290,6 +292,7 @@ defmodule ExWebRTC.Recorder do
290292
start_time: start_time,
291293
kind: track.kind,
292294
streams: track.streams,
295+
codec: nil,
293296
rid_map: (track.rids || [nil]) |> Enum.with_index() |> Map.new(),
294297
location: file_path,
295298
file: File.open!(file_path, [:write])
@@ -302,7 +305,7 @@ defmodule ExWebRTC.Recorder do
302305

303306
state = %{state | track_data: Map.merge(state.track_data, new_track_data)}
304307

305-
:ok = File.write!(state.manifest_path, state.track_data |> to_manifest() |> Jason.encode!())
308+
:ok = write_manifest(state)
306309

307310
{manifest_diff, state}
308311
end
@@ -342,6 +345,20 @@ defmodule ExWebRTC.Recorder do
342345
end)
343346
end
344347

348+
defp update_codec(state, track_id, codec) do
349+
case get_in(state, [:track_data, track_id, :codec]) do
350+
nil ->
351+
state = put_in(state, [:track_data, track_id, :codec], codec)
352+
353+
:ok = write_manifest(state)
354+
Logger.info("Updated manifest with codec info for track #{track_id}")
355+
state
356+
357+
_ ->
358+
state
359+
end
360+
end
361+
345362
defp serialize_packet(packet, rid_idx, recv_time) do
346363
packet = ExRTP.Packet.encode(packet)
347364
packet_size = byte_size(packet)
@@ -355,4 +372,14 @@ defmodule ExWebRTC.Recorder do
355372
:io_lib.format("~4..0w~2..0w~2..0w-~2..0w~2..0w~2..0w", [y, mo, d, h, m, s])
356373
|> to_string()
357374
end
375+
376+
defp write_manifest(state) do
377+
File.write!(
378+
state.manifest_path,
379+
state.track_data
380+
|> to_manifest()
381+
|> ExWebRTC.Recorder.Manifest.to_json!()
382+
|> Jason.encode!()
383+
)
384+
end
358385
end

lib/ex_webrtc_recorder/manifest.ex

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,63 @@ defmodule ExWebRTC.Recorder.Manifest do
2020
kind: :video | :audio,
2121
streams: [MediaStreamTrack.stream_id()],
2222
rid_map: %{MediaStreamTrack.rid() => integer()},
23+
codec: ExWebRTC.RTPCodecParameters.t() | nil,
2324
location: location()
2425
}
2526

2627
@type t :: %{MediaStreamTrack.id() => track_manifest()}
2728

29+
@doc false
30+
@spec to_json!(t()) :: map()
31+
def to_json!(manifest) do
32+
Map.new(manifest, fn {id, entry} ->
33+
{
34+
id,
35+
%{
36+
"start_time" => DateTime.to_iso8601(entry.start_time),
37+
"kind" => Atom.to_string(entry.kind),
38+
"streams" => entry.streams,
39+
"rid_map" => encode_rid_map(entry.rid_map),
40+
"codec" => encode_codec(entry.codec),
41+
"location" => entry.location
42+
}
43+
}
44+
end)
45+
end
46+
47+
defp encode_rid_map(rid_map) do
48+
Map.new(rid_map, fn
49+
{nil, v} -> {"nil", v}
50+
{layer, v} -> {layer, v}
51+
end)
52+
end
53+
54+
defp encode_codec(nil), do: nil
55+
56+
defp encode_codec(%ExWebRTC.RTPCodecParameters{} = codec) do
57+
%{
58+
"payload_type" => codec.payload_type,
59+
"mime_type" => codec.mime_type,
60+
"clock_rate" => codec.clock_rate,
61+
"channels" => codec.channels,
62+
"sdp_fmtp_line" => fmtp_to_string(codec.sdp_fmtp_line),
63+
"rtcp_fbs" => rtcp_fbs_to_strings(codec.rtcp_fbs)
64+
}
65+
end
66+
67+
defp fmtp_to_string([]), do: nil
68+
defp fmtp_to_string(nil), do: nil
69+
defp fmtp_to_string(fmtp), do: fmtp |> to_string() |> String.replace_prefix("fmtp:", "")
70+
71+
defp rtcp_fbs_to_strings(nil), do: nil
72+
defp rtcp_fbs_to_strings([]), do: nil
73+
74+
defp rtcp_fbs_to_strings(list) when is_list(list) do
75+
list
76+
|> Enum.map(&to_string/1)
77+
|> Enum.map(&String.replace_prefix(&1, "rtcp-fb:", ""))
78+
end
79+
2880
@doc false
2981
@spec from_json!(map()) :: t()
3082
def from_json!(json_manifest) do
@@ -38,14 +90,16 @@ defmodule ExWebRTC.Recorder.Manifest do
3890
"kind" => kind,
3991
"streams" => streams,
4092
"rid_map" => rid_map,
93+
"codec" => codec,
4194
"location" => location
4295
}) do
4396
%{
4497
streams: streams,
4598
location: location,
4699
start_time: parse_start_time(start_time),
47100
rid_map: parse_rid_map(rid_map),
48-
kind: parse_kind(kind)
101+
kind: parse_kind(kind),
102+
codec: parse_codec(codec)
49103
}
50104
end
51105

@@ -63,4 +117,40 @@ defmodule ExWebRTC.Recorder.Manifest do
63117

64118
defp parse_kind("video"), do: :video
65119
defp parse_kind("audio"), do: :audio
120+
121+
defp parse_codec(%{
122+
"payload_type" => payload_type,
123+
"mime_type" => mime_type,
124+
"clock_rate" => clock_rate,
125+
"channels" => channels,
126+
"sdp_fmtp_line" => sdp_fmtp_line,
127+
"rtcp_fbs" => rtcp_fbs
128+
}) do
129+
%ExWebRTC.RTPCodecParameters{
130+
payload_type: payload_type,
131+
mime_type: mime_type,
132+
clock_rate: clock_rate,
133+
channels: channels,
134+
sdp_fmtp_line: parse_sdp_fmtp_line(sdp_fmtp_line),
135+
rtcp_fbs: parse_rtcp_fbs(rtcp_fbs)
136+
}
137+
end
138+
139+
defp parse_codec(nil), do: nil
140+
141+
defp parse_sdp_fmtp_line(sdp_fmtp_line) when is_binary(sdp_fmtp_line) do
142+
{:ok, fmtp} = ExSDP.Attribute.FMTP.parse(sdp_fmtp_line)
143+
fmtp
144+
end
145+
146+
defp parse_sdp_fmtp_line(nil), do: []
147+
148+
defp parse_rtcp_fbs(rtcp_fbs) when is_list(rtcp_fbs) do
149+
Enum.map(rtcp_fbs, fn fb ->
150+
{:ok, rtcp_fb} = ExSDP.Attribute.RTCPFeedback.parse(fb)
151+
rtcp_fb
152+
end)
153+
end
154+
155+
defp parse_rtcp_fbs(nil), do: []
66156
end

0 commit comments

Comments
 (0)