blob: cc4c61c581e47922d3ad350f03986a3686965f68 [file] [log] [blame]
Jacob Erlbecke8ae1ac2013-12-13 13:18:20 +01001#!/usr/bin/env escript
2%% -*- erlang -*-
3%%! -smp disable
4-module(gen_rtp_header).
5
6% -mode(compile).
7
8-define(VERSION, "0.1").
9
10-export([main/1]).
11
12-record(rtp_packet,
13 {
14 version = 2,
15 padding = 0,
16 marker = 0,
17 payload_type = 0,
18 seqno = 0,
19 timestamp = 0,
20 ssrc = 0,
21 csrcs = [],
22 extension = <<>>,
23 payload = <<>>,
24 realtime
25 }).
26
27
28main(Args) ->
29 DefaultOpts = [{format, state},
30 {ssrc, 16#11223344},
31 {pt, 98}],
32 {PosArgs, Opts} = getopts_checked(Args, DefaultOpts),
33 log(debug, fun (Dev) ->
34 io:format(Dev, "Initial options:~n", []),
35 dump_opts(Dev, Opts),
36 io:format(Dev, "~s: ~p~n", ["Args", PosArgs])
37 end, [], Opts),
38 main(PosArgs, Opts).
39
40main([First | RemArgs], Opts) ->
41 try
42 F = list_to_integer(First),
43 Format = proplists:get_value(format, Opts, state),
44 PayloadData = proplists:get_value(payload, Opts, undef),
45 InFile = proplists:get_value(file, Opts, undef),
46
47 Payload = case {PayloadData, InFile} of
Jacob Erlbeckeddaa9f2013-12-18 12:43:18 +010048 {undef, undef} ->
49 % use default value
50 #rtp_packet{}#rtp_packet.payload;
Jacob Erlbecke8ae1ac2013-12-13 13:18:20 +010051 {P, undef} -> P;
52 {_, File} ->
53 log(info, "Loading file '~s'~n", [File], Opts),
54 {ok, InDev} = file:open(File, [read]),
55 DS = [ Pl#rtp_packet.payload || {_T, Pl} <- read_packets(InDev, Opts)],
56 file:close(InDev),
57 log(debug, "File '~s' closed, ~w packets read.~n", [File, length(DS)], Opts),
58 DS
59 end,
60 Dev = standard_io,
61 write_packet_pre(Dev, Format),
62 do_groups(Dev, Payload, F, RemArgs, Opts),
63 write_packet_post(Dev, Format),
64 0
65 catch
66 _:_ ->
67 log(debug, "~p~n", [hd(erlang:get_stacktrace())], Opts),
68 usage(),
69 halt(1)
70 end
71 ;
72
73main(_, _Opts) ->
74 usage(),
75 halt(1).
76
77%%% group (count + offset) handling %%%
78
79do_groups(_Dev, _Pl, _F, [], _Opts) ->
80 ok;
81
82do_groups(Dev, Pl, F, [L], Opts) ->
83 do_groups(Dev, Pl, F, [L, 0], Opts);
84
85do_groups(Dev, Pl, First, [L, O | Args], Opts) ->
86 Ssrc = proplists:get_value(ssrc, Opts, #rtp_packet.ssrc),
87 PT = proplists:get_value(pt, Opts, #rtp_packet.payload_type),
88 Len = list_to_num(L),
89 Offs = list_to_num(O),
90 log(info, "Starting group: Ssrc=~.16B, PT=~B, First=~B, Len=~B, Offs=~B~n",
91 [Ssrc, PT, First, Len, Offs], Opts),
92 Pkg = #rtp_packet{ssrc = Ssrc, payload_type = PT},
93 Pl2 = write_packets(Dev, Pl, Pkg, First, Len, Offs, Opts),
94 {Args2, Opts2} = getopts_checked(Args, Opts),
95 log(debug, fun (Io) ->
96 io:format(Io, "Changed options:~n", []),
97 dump_opts(Io, Opts2 -- Opts)
98 end, [], Opts),
99 do_groups(Dev, Pl2, First+Len, Args2, Opts2).
100
101%%% error handling helpers %%%
102
103getopts_checked(Args, Opts) ->
104 try
105 getopts(Args, Opts)
106 catch
107 C:R ->
108 log(error, "~s~n",
109 [explain_error(C, R, erlang:get_stacktrace(), Opts)], Opts),
110 usage(),
111 halt(1)
112 end.
113
114explain_error(error, badarg, [{erlang,list_to_integer,[S,B]} | _ ], _Opts) ->
115 io_lib:format("Invalid number '~s' (base ~B)", [S, B]);
116explain_error(error, badarg, [{erlang,list_to_integer,[S]} | _ ], _Opts) ->
117 io_lib:format("Invalid decimal number '~s'", [S]);
118explain_error(C, R, [Hd | _ ], _Opts) ->
119 io_lib:format("~p, ~p:~p", [Hd, C, R]);
120explain_error(_, _, [], _Opts) ->
121 "".
122
123%%% usage and options %%%
124
125myname() ->
126 filename:basename(escript:script_name()).
127
128usage(Text) ->
129 io:format(standard_error, "~s: ~s~n", [myname(), Text]),
130 usage().
131
132usage() ->
133 io:format(standard_error,
134 "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n",
135 [myname()]).
136
137show_version() ->
138 io:format(standard_io,
139 "~s ~s~n", [myname(), ?VERSION]).
140
141show_help() ->
142 io:format(standard_io,
143 "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n~n" ++
144 "Options:~n" ++
145 " -h, --help this text~n" ++
146 " --version show version info~n" ++
147 " -i, --file=FILE reads payload from state file~n" ++
148 " -p, --payload=HEX set constant payload~n" ++
149 " --verbose=N set verbosity~n" ++
150 " -v increase verbosity~n" ++
151 " --format=state use state format for output (default)~n" ++
152 " -C, --format=c use simple C lines for output~n" ++
153 " --format=carray use a C array for output~n" ++
154 " -s, --ssrc=SSRC set the SSRC~n" ++
155 " -t, --type=N set the payload type~n" ++
156 " -d, --delay=FLOAT add offset to playout timestamp~n" ++
157 "~n" ++
158 "Arguments:~n" ++
159 " Start initial packet (sequence) number~n" ++
160 " Count number of packets~n" ++
161 " Offs timestamp offset (in RTP units)~n" ++
162 "", [myname()]).
163
164getopts([ "--file=" ++ File | R], Opts) ->
165 getopts(R, [{file, File} | Opts]);
166getopts([ "-i" ++ T | R], Opts) ->
167 getopts_alias_arg("--file", T, R, Opts);
168getopts([ "--version" | _], _Opts) ->
169 show_version(),
170 halt(0);
171getopts([ "--help" | _], _Opts) ->
172 show_help(),
173 halt(0);
174getopts([ "-h" ++ T | R], Opts) ->
175 getopts_alias_no_arg("--help", T, R, Opts);
176getopts([ "--verbose=" ++ V | R], Opts) ->
177 Verbose = list_to_integer(V),
178 getopts(R, [{verbose, Verbose} | Opts]);
179getopts([ "-v" ++ T | R], Opts) ->
180 Verbose = proplists:get_value(verbose, Opts, 0),
181 getopts_short_no_arg(T, R, [ {verbose, Verbose+1} | Opts]);
182getopts([ "--format=state" | R], Opts) ->
183 getopts(R, [{format, state} | Opts]);
184getopts([ "--format=c" | R], Opts) ->
185 getopts(R, [{format, c} | Opts]);
186getopts([ "-C" ++ T | R], Opts) ->
187 getopts_alias_no_arg("--format=c", T, R, Opts);
188getopts([ "--format=carray" | R], Opts) ->
189 getopts(R, [{format, carray} | Opts]);
190getopts([ "--payload=" ++ Hex | R], Opts) ->
191 getopts(R, [{payload, hex_to_bin(Hex)} | Opts]);
192getopts([ "--ssrc=" ++ Num | R], Opts) ->
193 getopts(R, [{ssrc, list_to_num(Num)} | Opts]);
194getopts([ "-s" ++ T | R], Opts) ->
195 getopts_alias_arg("--ssrc", T, R, Opts);
196getopts([ "--type=" ++ Num | R], Opts) ->
197 getopts(R, [{pt, list_to_num(Num)} | Opts]);
198getopts([ "-t" ++ T | R], Opts) ->
199 getopts_alias_arg("--type", T, R, Opts);
200getopts([ "--delay=" ++ Num | R], Opts) ->
201 getopts(R, [{delay, list_to_float(Num)} | Opts]);
202getopts([ "-d" ++ T | R], Opts) ->
203 getopts_alias_arg("--delay", T, R, Opts);
204
205% parsing helpers
206getopts([ "--" | R], Opts) ->
207 {R, normalize_opts(Opts)};
208getopts([ O = "--" ++ _ | _], _Opts) ->
209 usage("Invalid option: " ++ O),
210 halt(1);
211getopts([ [ $-, C | _] | _], _Opts) when C < $0; C > $9 ->
212 usage("Invalid option: -" ++ [C]),
213 halt(1);
214
215getopts(R, Opts) ->
216 {R, normalize_opts(Opts)}.
217
218getopts_short_no_arg([], R, Opts) -> getopts(R, Opts);
219getopts_short_no_arg(T, R, Opts) -> getopts([ "-" ++ T | R], Opts).
220
221getopts_alias_no_arg(A, [], R, Opts) -> getopts([A | R], Opts);
222getopts_alias_no_arg(A, T, R, Opts) -> getopts([A, "-" ++ T | R], Opts).
223
224getopts_alias_arg(A, [], [T | R], Opts) -> getopts([A ++ "=" ++ T | R], Opts);
225getopts_alias_arg(A, T, R, Opts) -> getopts([A ++ "=" ++ T | R], Opts).
226
227normalize_opts(Opts) ->
228 [ proplists:lookup(E, Opts) || E <- proplists:get_keys(Opts) ].
229
230%%% conversions %%%
231
232bin_to_hex(Bin) -> [hd(integer_to_list(N,16)) || <<N:4>> <= Bin].
233hex_to_bin(Hex) -> << <<(list_to_integer([Nib],16)):4>> || Nib <- Hex>>.
234
235list_to_num("-" ++ Str) -> -list_to_num(Str);
236list_to_num("0x" ++ Str) -> list_to_integer(Str, 16);
237list_to_num("0b" ++ Str) -> list_to_integer(Str, 2);
238list_to_num(Str = [ $0 | _ ]) -> list_to_integer(Str, 8);
239list_to_num(Str) -> list_to_integer(Str, 10).
240
241%%% dumping data %%%
242
243dump_opts(Dev, Opts) ->
244 dump_opts2(Dev, Opts, proplists:get_keys(Opts)).
245
246dump_opts2(Dev, Opts, [OptName | R]) ->
247 io:format(Dev, " ~-10s: ~p~n",
248 [OptName, proplists:get_value(OptName, Opts)]),
249 dump_opts2(Dev, Opts, R);
250dump_opts2(_Dev, _Opts, []) -> ok.
251
252%%% logging %%%
253
254log(L, Fmt, Args, Opts) when is_list(Opts) ->
255 log(L, Fmt, Args, proplists:get_value(verbose, Opts, 0), Opts).
256
257log(debug, Fmt, Args, V, Opts) when V > 2 -> log2("DEBUG", Fmt, Args, Opts);
258log(info, Fmt, Args, V, Opts) when V > 1 -> log2("INFO", Fmt, Args, Opts);
259log(notice, Fmt, Args, V, Opts) when V > 0 -> log2("NOTICE", Fmt, Args, Opts);
260log(warn, Fmt, Args, _V, Opts) -> log2("WARNING", Fmt, Args, Opts);
261log(error, Fmt, Args, _V, Opts) -> log2("ERROR", Fmt, Args, Opts);
262
263log(Lvl, Fmt, Args, V, Opts) when V >= Lvl -> log2("", Fmt, Args, Opts);
264
265log(_, _, _, _i, _) -> ok.
266
267log2(Type, Fmt, Args, _Opts) when is_list(Fmt) ->
268 io:format(standard_error, "~s: " ++ Fmt, [Type | Args]);
269log2("", Fmt, Args, _Opts) when is_list(Fmt) ->
270 io:format(standard_error, Fmt, Args);
271log2(_Type, Fun, _Args, _Opts) when is_function(Fun, 1) ->
272 Fun(standard_error).
273
274%%% RTP packets %%%
275
276make_rtp_packet(P = #rtp_packet{version = 2}) ->
277 << (P#rtp_packet.version):2,
278 0:1, % P
279 0:1, % X
280 0:4, % CC
281 (P#rtp_packet.marker):1,
282 (P#rtp_packet.payload_type):7,
283 (P#rtp_packet.seqno):16,
284 (P#rtp_packet.timestamp):32,
285 (P#rtp_packet.ssrc):32,
286 (P#rtp_packet.payload)/bytes
287 >>.
288
289parse_rtp_packet(
290 << 2:2, % Version 2
291 0:1, % P (not supported yet)
292 0:1, % X (not supported yet)
293 0:4, % CC (not supported yet)
294 M:1,
295 PT:7,
296 SeqNo: 16,
297 TS:32,
298 Ssrc:32,
299 Payload/bytes >>) ->
300 #rtp_packet{
301 version = 0,
302 marker = M,
303 payload_type = PT,
304 seqno = SeqNo,
305 timestamp = TS,
306 ssrc = Ssrc,
307 payload = Payload}.
308
309%%% payload generation %%%
310
311next_payload(F) when is_function(F) ->
312 {F(), F};
313next_payload({F, D}) when is_function(F) ->
314 {P, D2} = F(D),
315 {P, {F, D2}};
316next_payload([P | R]) ->
317 {P, R};
318next_payload([]) ->
319 undef;
320next_payload(Bin = <<_/bytes>>) ->
321 {Bin, Bin}.
322
323%%% real writing work %%%
324
325write_packets(_Dev, DS, _P, _F, 0, _O, _Opts) ->
326 DS;
327write_packets(Dev, DataSource, P = #rtp_packet{}, F, L, O, Opts) ->
328 Format = proplists:get_value(format, Opts, state),
329 Ptime = proplists:get_value(duration, Opts, 160),
330 Delay = proplists:get_value(delay, Opts, 0),
331 case next_payload(DataSource) of
332 {Payload, DataSource2} ->
333 write_packet(Dev, 0.020 * F + Delay,
334 P#rtp_packet{seqno = F, timestamp = F*Ptime+O,
335 payload = Payload},
336 Format),
337 write_packets(Dev, DataSource2, P, F+1, L-1, O, Opts);
338 Other -> Other
339 end.
340
341write_packet(Dev, Time, P = #rtp_packet{}, Format) ->
342 Bin = make_rtp_packet(P),
343
344 write_packet_line(Dev, Time, P, Bin, Format).
345
346write_packet_pre(Dev, carray) ->
347 io:format(Dev,
348 "struct {float t; int len; char *data;} packets[] = {~n", []);
349
350write_packet_pre(_Dev, _) -> ok.
351
352write_packet_post(Dev, carray) ->
353 io:format(Dev, "};~n", []);
354
355write_packet_post(_Dev, _) -> ok.
356
357write_packet_line(Dev, Time, _P, Bin, state) ->
358 io:format(Dev, "~f ~s~n", [Time, bin_to_hex(Bin)]);
359
360write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, c) ->
361 ByteList = [ [ $0, $x | integer_to_list(Byte, 16) ] || <<Byte:8>> <= Bin ],
362 ByteStr = string:join(ByteList, ", "),
363 io:format(Dev, "/* time=~f, SeqNo=~B, TS=~B */ {~s}~n", [Time, N, TS, ByteStr]);
364
365write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, carray) ->
366 io:format(Dev, " /* RTP: SeqNo=~B, TS=~B */~n", [N, TS]),
367 io:format(Dev, " {~f, ~B, \"", [Time, size(Bin)]),
368 [ io:format(Dev, "\\x~2.16.0B", [Byte]) || <<Byte:8>> <= Bin ],
369 io:format(Dev, "\"},~n", []).
370
371%%% real reading work %%%
372
373read_packets(Dev, Opts) ->
374 Format = proplists:get_value(in_format, Opts, state),
375
376 read_packets(Dev, Opts, Format).
377
378read_packets(Dev, Opts, Format) ->
379 case read_packet(Dev, Format) of
380 eof -> [];
381 Tuple -> [Tuple | read_packets(Dev, Opts, Format)]
382 end.
383
384read_packet(Dev, Format) ->
385 case read_packet_line(Dev, Format) of
386 {Time, Bin} -> {Time, parse_rtp_packet(Bin)};
387 eof -> eof
388 end.
389
390read_packet_line(Dev, state) ->
391 case io:fread(Dev, "", "~f ~s") of
392 {ok, [Time, Hex]} -> {Time, hex_to_bin(Hex)};
393 eof -> eof
394 end.