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