This patch adds optional parameters to pass the state file, the destination address (default 127.0.0.1), the destination port (default 4000), the source port (default 0). So it is called as follows:
gst rtp_replay.st -a [FILE [HOST [SOURCEPORT [DESTPORT]]]]
In addition, nonexistant FILEs are no longer created but opened read-only instead.
Sponsored-by: On-Waves ehf --- openbsc/contrib/rtp/rtp_replay.st | 10 +++++++--- openbsc/contrib/rtp/rtp_replay_shared.st | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/openbsc/contrib/rtp/rtp_replay.st b/openbsc/contrib/rtp/rtp_replay.st index 7fa9a4c..e26d073 100644 --- a/openbsc/contrib/rtp/rtp_replay.st +++ b/openbsc/contrib/rtp/rtp_replay.st @@ -7,11 +7,15 @@ FileStream fileIn: 'rtp_replay_shared.st'.
Eval [ - | replay | + | replay file host dport |
+ file := Smalltalk arguments at: 1 ifAbsent: [ 'rtpstream.state' ]. + host := Smalltalk arguments at: 2 ifAbsent: [ '127.0.0.1' ]. + dport := (Smalltalk arguments at: 3 ifAbsent: [ '4000' ]) asInteger. + sport := (Smalltalk arguments at: 4 ifAbsent: [ '0' ]) asInteger.
- replay := RTPReplay on: Smalltalk arguments first. + replay := RTPReplay on: file fromPort: sport.
Transcript nextPutAll: 'Going to stream now'; nl. - replay streamAudio: '127.0.0.1' port: 4000. + replay streamAudio: host port: dport. ] diff --git a/openbsc/contrib/rtp/rtp_replay_shared.st b/openbsc/contrib/rtp/rtp_replay_shared.st index dd32aed..7b68c0f 100644 --- a/openbsc/contrib/rtp/rtp_replay_shared.st +++ b/openbsc/contrib/rtp/rtp_replay_shared.st @@ -42,8 +42,18 @@ Object subclass: RTPReplay [ file: aFile; yourself ]
+ RTPReplay class >> on: aFile fromPort: aPort [ + ^ self new + initialize: aPort; + file: aFile; yourself + ] + initialize [ - socket := Sockets.DatagramSocket new. + self initialize: 0. + ] + + initialize: aPort [ + socket := Sockets.DatagramSocket local: '0.0.0.0' port: aPort. ]
file: aFile [ @@ -59,7 +69,7 @@ Object subclass: RTPReplay [
last_time := nil. last_image := nil. - file := FileStream open: filename. + file := FileStream open: filename mode: #read.
"Send the payload" dest := Sockets.SocketAddress byName: aHost.
This tool provides the following features: - Output formats: state, C arrays - Optionally take RTP payload from existing state files - Generate streams with RTP timestamp jumps and/or delays - Set/change SSRC or payload type
Requires erlang to be installed.
Example: Generate 300 packets, set playout time offset to 1s, set RTP timestamp offset to 8000 (1s), generate another 100 packets, the RTP payload is copied from rtp.state:
./gen_rtp_header.erl --type=98 --file=rtp.state -- 0 300 0 --delay=1.0 100 8000
Sponsored-by: On-Waves ehf --- openbsc/contrib/rtp/gen_rtp_header.erl | 392 ++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100755 openbsc/contrib/rtp/gen_rtp_header.erl
diff --git a/openbsc/contrib/rtp/gen_rtp_header.erl b/openbsc/contrib/rtp/gen_rtp_header.erl new file mode 100755 index 0000000..9f980b0 --- /dev/null +++ b/openbsc/contrib/rtp/gen_rtp_header.erl @@ -0,0 +1,392 @@ +#!/usr/bin/env escript +%% -*- erlang -*- +%%! -smp disable +-module(gen_rtp_header). + +% -mode(compile). + +-define(VERSION, "0.1"). + +-export([main/1]). + +-record(rtp_packet, + { + version = 2, + padding = 0, + marker = 0, + payload_type = 0, + seqno = 0, + timestamp = 0, + ssrc = 0, + csrcs = [], + extension = <<>>, + payload = <<>>, + realtime + }). + + +main(Args) -> + DefaultOpts = [{format, state}, + {ssrc, 16#11223344}, + {pt, 98}], + {PosArgs, Opts} = getopts_checked(Args, DefaultOpts), + log(debug, fun (Dev) -> + io:format(Dev, "Initial options:~n", []), + dump_opts(Dev, Opts), + io:format(Dev, "~s: ~p~n", ["Args", PosArgs]) + end, [], Opts), + main(PosArgs, Opts). + +main([First | RemArgs], Opts) -> + try + F = list_to_integer(First), + Format = proplists:get_value(format, Opts, state), + PayloadData = proplists:get_value(payload, Opts, undef), + InFile = proplists:get_value(file, Opts, undef), + + Payload = case {PayloadData, InFile} of + {undef, undef} -> #rtp_packet.payload; + {P, undef} -> P; + {_, File} -> + log(info, "Loading file '~s'~n", [File], Opts), + {ok, InDev} = file:open(File, [read]), + DS = [ Pl#rtp_packet.payload || {_T, Pl} <- read_packets(InDev, Opts)], + file:close(InDev), + log(debug, "File '~s' closed, ~w packets read.~n", [File, length(DS)], Opts), + DS + end, + Dev = standard_io, + write_packet_pre(Dev, Format), + do_groups(Dev, Payload, F, RemArgs, Opts), + write_packet_post(Dev, Format), + 0 + catch + _:_ -> + log(debug, "~p~n", [hd(erlang:get_stacktrace())], Opts), + usage(), + halt(1) + end + ; + +main(_, _Opts) -> + usage(), + halt(1). + +%%% group (count + offset) handling %%% + +do_groups(_Dev, _Pl, _F, [], _Opts) -> + ok; + +do_groups(Dev, Pl, F, [L], Opts) -> + do_groups(Dev, Pl, F, [L, 0], Opts); + +do_groups(Dev, Pl, First, [L, O | Args], Opts) -> + Ssrc = proplists:get_value(ssrc, Opts, #rtp_packet.ssrc), + PT = proplists:get_value(pt, Opts, #rtp_packet.payload_type), + Len = list_to_num(L), + Offs = list_to_num(O), + log(info, "Starting group: Ssrc=~.16B, PT=~B, First=~B, Len=~B, Offs=~B~n", + [Ssrc, PT, First, Len, Offs], Opts), + Pkg = #rtp_packet{ssrc = Ssrc, payload_type = PT}, + Pl2 = write_packets(Dev, Pl, Pkg, First, Len, Offs, Opts), + {Args2, Opts2} = getopts_checked(Args, Opts), + log(debug, fun (Io) -> + io:format(Io, "Changed options:~n", []), + dump_opts(Io, Opts2 -- Opts) + end, [], Opts), + do_groups(Dev, Pl2, First+Len, Args2, Opts2). + +%%% error handling helpers %%% + +getopts_checked(Args, Opts) -> + try + getopts(Args, Opts) + catch + C:R -> + log(error, "~s~n", + [explain_error(C, R, erlang:get_stacktrace(), Opts)], Opts), + usage(), + halt(1) + end. + +explain_error(error, badarg, [{erlang,list_to_integer,[S,B]} | _ ], _Opts) -> + io_lib:format("Invalid number '~s' (base ~B)", [S, B]); +explain_error(error, badarg, [{erlang,list_to_integer,[S]} | _ ], _Opts) -> + io_lib:format("Invalid decimal number '~s'", [S]); +explain_error(C, R, [Hd | _ ], _Opts) -> + io_lib:format("~p, ~p:~p", [Hd, C, R]); +explain_error(_, _, [], _Opts) -> + "". + +%%% usage and options %%% + +myname() -> + filename:basename(escript:script_name()). + +usage(Text) -> + io:format(standard_error, "~s: ~s~n", [myname(), Text]), + usage(). + +usage() -> + io:format(standard_error, + "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n", + [myname()]). + +show_version() -> + io:format(standard_io, + "~s ~s~n", [myname(), ?VERSION]). + +show_help() -> + io:format(standard_io, + "Usage: ~s [Options] Start Count1 Offs1 [[Options] Count2 Offs2 ...]~n~n" ++ + "Options:~n" ++ + " -h, --help this text~n" ++ + " --version show version info~n" ++ + " -i, --file=FILE reads payload from state file~n" ++ + " -p, --payload=HEX set constant payload~n" ++ + " --verbose=N set verbosity~n" ++ + " -v increase verbosity~n" ++ + " --format=state use state format for output (default)~n" ++ + " -C, --format=c use simple C lines for output~n" ++ + " --format=carray use a C array for output~n" ++ + " -s, --ssrc=SSRC set the SSRC~n" ++ + " -t, --type=N set the payload type~n" ++ + " -d, --delay=FLOAT add offset to playout timestamp~n" ++ + "~n" ++ + "Arguments:~n" ++ + " Start initial packet (sequence) number~n" ++ + " Count number of packets~n" ++ + " Offs timestamp offset (in RTP units)~n" ++ + "", [myname()]). + +getopts([ "--file=" ++ File | R], Opts) -> + getopts(R, [{file, File} | Opts]); +getopts([ "-i" ++ T | R], Opts) -> + getopts_alias_arg("--file", T, R, Opts); +getopts([ "--version" | _], _Opts) -> + show_version(), + halt(0); +getopts([ "--help" | _], _Opts) -> + show_help(), + halt(0); +getopts([ "-h" ++ T | R], Opts) -> + getopts_alias_no_arg("--help", T, R, Opts); +getopts([ "--verbose=" ++ V | R], Opts) -> + Verbose = list_to_integer(V), + getopts(R, [{verbose, Verbose} | Opts]); +getopts([ "-v" ++ T | R], Opts) -> + Verbose = proplists:get_value(verbose, Opts, 0), + getopts_short_no_arg(T, R, [ {verbose, Verbose+1} | Opts]); +getopts([ "--format=state" | R], Opts) -> + getopts(R, [{format, state} | Opts]); +getopts([ "--format=c" | R], Opts) -> + getopts(R, [{format, c} | Opts]); +getopts([ "-C" ++ T | R], Opts) -> + getopts_alias_no_arg("--format=c", T, R, Opts); +getopts([ "--format=carray" | R], Opts) -> + getopts(R, [{format, carray} | Opts]); +getopts([ "--payload=" ++ Hex | R], Opts) -> + getopts(R, [{payload, hex_to_bin(Hex)} | Opts]); +getopts([ "--ssrc=" ++ Num | R], Opts) -> + getopts(R, [{ssrc, list_to_num(Num)} | Opts]); +getopts([ "-s" ++ T | R], Opts) -> + getopts_alias_arg("--ssrc", T, R, Opts); +getopts([ "--type=" ++ Num | R], Opts) -> + getopts(R, [{pt, list_to_num(Num)} | Opts]); +getopts([ "-t" ++ T | R], Opts) -> + getopts_alias_arg("--type", T, R, Opts); +getopts([ "--delay=" ++ Num | R], Opts) -> + getopts(R, [{delay, list_to_float(Num)} | Opts]); +getopts([ "-d" ++ T | R], Opts) -> + getopts_alias_arg("--delay", T, R, Opts); + +% parsing helpers +getopts([ "--" | R], Opts) -> + {R, normalize_opts(Opts)}; +getopts([ O = "--" ++ _ | _], _Opts) -> + usage("Invalid option: " ++ O), + halt(1); +getopts([ [ $-, C | _] | _], _Opts) when C < $0; C > $9 -> + usage("Invalid option: -" ++ [C]), + halt(1); + +getopts(R, Opts) -> + {R, normalize_opts(Opts)}. + +getopts_short_no_arg([], R, Opts) -> getopts(R, Opts); +getopts_short_no_arg(T, R, Opts) -> getopts([ "-" ++ T | R], Opts). + +getopts_alias_no_arg(A, [], R, Opts) -> getopts([A | R], Opts); +getopts_alias_no_arg(A, T, R, Opts) -> getopts([A, "-" ++ T | R], Opts). + +getopts_alias_arg(A, [], [T | R], Opts) -> getopts([A ++ "=" ++ T | R], Opts); +getopts_alias_arg(A, T, R, Opts) -> getopts([A ++ "=" ++ T | R], Opts). + +normalize_opts(Opts) -> + [ proplists:lookup(E, Opts) || E <- proplists:get_keys(Opts) ]. + +%%% conversions %%% + +bin_to_hex(Bin) -> [hd(integer_to_list(N,16)) || <<N:4>> <= Bin]. +hex_to_bin(Hex) -> << <<(list_to_integer([Nib],16)):4>> || Nib <- Hex>>. + +list_to_num("-" ++ Str) -> -list_to_num(Str); +list_to_num("0x" ++ Str) -> list_to_integer(Str, 16); +list_to_num("0b" ++ Str) -> list_to_integer(Str, 2); +list_to_num(Str = [ $0 | _ ]) -> list_to_integer(Str, 8); +list_to_num(Str) -> list_to_integer(Str, 10). + +%%% dumping data %%% + +dump_opts(Dev, Opts) -> + dump_opts2(Dev, Opts, proplists:get_keys(Opts)). + +dump_opts2(Dev, Opts, [OptName | R]) -> + io:format(Dev, " ~-10s: ~p~n", + [OptName, proplists:get_value(OptName, Opts)]), + dump_opts2(Dev, Opts, R); +dump_opts2(_Dev, _Opts, []) -> ok. + +%%% logging %%% + +log(L, Fmt, Args, Opts) when is_list(Opts) -> + log(L, Fmt, Args, proplists:get_value(verbose, Opts, 0), Opts). + +log(debug, Fmt, Args, V, Opts) when V > 2 -> log2("DEBUG", Fmt, Args, Opts); +log(info, Fmt, Args, V, Opts) when V > 1 -> log2("INFO", Fmt, Args, Opts); +log(notice, Fmt, Args, V, Opts) when V > 0 -> log2("NOTICE", Fmt, Args, Opts); +log(warn, Fmt, Args, _V, Opts) -> log2("WARNING", Fmt, Args, Opts); +log(error, Fmt, Args, _V, Opts) -> log2("ERROR", Fmt, Args, Opts); + +log(Lvl, Fmt, Args, V, Opts) when V >= Lvl -> log2("", Fmt, Args, Opts); + +log(_, _, _, _i, _) -> ok. + +log2(Type, Fmt, Args, _Opts) when is_list(Fmt) -> + io:format(standard_error, "~s: " ++ Fmt, [Type | Args]); +log2("", Fmt, Args, _Opts) when is_list(Fmt) -> + io:format(standard_error, Fmt, Args); +log2(_Type, Fun, _Args, _Opts) when is_function(Fun, 1) -> + Fun(standard_error). + +%%% RTP packets %%% + +make_rtp_packet(P = #rtp_packet{version = 2}) -> + << (P#rtp_packet.version):2, + 0:1, % P + 0:1, % X + 0:4, % CC + (P#rtp_packet.marker):1, + (P#rtp_packet.payload_type):7, + (P#rtp_packet.seqno):16, + (P#rtp_packet.timestamp):32, + (P#rtp_packet.ssrc):32, + (P#rtp_packet.payload)/bytes + >>. + +parse_rtp_packet( + << 2:2, % Version 2 + 0:1, % P (not supported yet) + 0:1, % X (not supported yet) + 0:4, % CC (not supported yet) + M:1, + PT:7, + SeqNo: 16, + TS:32, + Ssrc:32, + Payload/bytes >>) -> + #rtp_packet{ + version = 0, + marker = M, + payload_type = PT, + seqno = SeqNo, + timestamp = TS, + ssrc = Ssrc, + payload = Payload}. + +%%% payload generation %%% + +next_payload(F) when is_function(F) -> + {F(), F}; +next_payload({F, D}) when is_function(F) -> + {P, D2} = F(D), + {P, {F, D2}}; +next_payload([P | R]) -> + {P, R}; +next_payload([]) -> + undef; +next_payload(Bin = <<_/bytes>>) -> + {Bin, Bin}. + +%%% real writing work %%% + +write_packets(_Dev, DS, _P, _F, 0, _O, _Opts) -> + DS; +write_packets(Dev, DataSource, P = #rtp_packet{}, F, L, O, Opts) -> + Format = proplists:get_value(format, Opts, state), + Ptime = proplists:get_value(duration, Opts, 160), + Delay = proplists:get_value(delay, Opts, 0), + case next_payload(DataSource) of + {Payload, DataSource2} -> + write_packet(Dev, 0.020 * F + Delay, + P#rtp_packet{seqno = F, timestamp = F*Ptime+O, + payload = Payload}, + Format), + write_packets(Dev, DataSource2, P, F+1, L-1, O, Opts); + Other -> Other + end. + +write_packet(Dev, Time, P = #rtp_packet{}, Format) -> + Bin = make_rtp_packet(P), + + write_packet_line(Dev, Time, P, Bin, Format). + +write_packet_pre(Dev, carray) -> + io:format(Dev, + "struct {float t; int len; char *data;} packets[] = {~n", []); + +write_packet_pre(_Dev, _) -> ok. + +write_packet_post(Dev, carray) -> + io:format(Dev, "};~n", []); + +write_packet_post(_Dev, _) -> ok. + +write_packet_line(Dev, Time, _P, Bin, state) -> + io:format(Dev, "~f ~s~n", [Time, bin_to_hex(Bin)]); + +write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, c) -> + ByteList = [ [ $0, $x | integer_to_list(Byte, 16) ] || <Byte:8> <= Bin ], + ByteStr = string:join(ByteList, ", "), + io:format(Dev, "/* time=~f, SeqNo=~B, TS=~B */ {~s}~n", [Time, N, TS, ByteStr]); + +write_packet_line(Dev, Time, #rtp_packet{seqno = N, timestamp = TS}, Bin, carray) -> + io:format(Dev, " /* RTP: SeqNo=~B, TS=~B */~n", [N, TS]), + io:format(Dev, " {~f, ~B, "", [Time, size(Bin)]), + [ io:format(Dev, "\x~2.16.0B", [Byte]) || <Byte:8> <= Bin ], + io:format(Dev, ""},~n", []). + +%%% real reading work %%% + +read_packets(Dev, Opts) -> + Format = proplists:get_value(in_format, Opts, state), + + read_packets(Dev, Opts, Format). + +read_packets(Dev, Opts, Format) -> + case read_packet(Dev, Format) of + eof -> []; + Tuple -> [Tuple | read_packets(Dev, Opts, Format)] + end. + +read_packet(Dev, Format) -> + case read_packet_line(Dev, Format) of + {Time, Bin} -> {Time, parse_rtp_packet(Bin)}; + eof -> eof + end. + +read_packet_line(Dev, state) -> + case io:fread(Dev, "", "~f ~s") of + {ok, [Time, Hex]} -> {Time, hex_to_bin(Hex)}; + eof -> eof + end.