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