Commit | Line | Data |
---|---|---|
210d172f RK |
1 | /* |
2 | * This file is part of DisOrder. | |
3 | * Copyright (C) 2009 Richard Kettlewell | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation, either version 3 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
17 | */ | |
18 | /** @file clients/rtpmon.c | |
19 | * @brief RTP monitor | |
20 | * | |
21 | * This progam monitors the rate at which data arrives by RTP and | |
22 | * constantly display it. It is intended for debugging only. | |
23 | * | |
24 | * TODO de-dupe with playrtp. | |
25 | */ | |
26 | #include "common.h" | |
27 | ||
28 | #include <getopt.h> | |
29 | #include <sys/socket.h> | |
30 | #include <sys/types.h> | |
31 | #include <netdb.h> | |
32 | #include <netinet/in.h> | |
33 | #include <sys/time.h> | |
34 | #include <unistd.h> | |
35 | #include <locale.h> | |
36 | #include <errno.h> | |
37 | #include <stdlib.h> | |
033ede99 | 38 | #include <sys/uio.h> |
210d172f RK |
39 | |
40 | #include "syscalls.h" | |
41 | #include "timeval.h" | |
42 | #include "mem.h" | |
43 | #include "log.h" | |
44 | #include "version.h" | |
45 | #include "addr.h" | |
46 | #include "configuration.h" | |
47 | #include "rtp.h" | |
48 | ||
49 | /** @brief Record of one packet */ | |
50 | struct entry { | |
51 | /** @brief When packet arrived */ | |
52 | struct timeval when; | |
53 | ||
54 | /** @brief Serial number of first sample */ | |
55 | uint32_t serial; | |
56 | }; | |
57 | ||
58 | /** @brief Bytes per frame */ | |
59 | static unsigned bpf = 4; | |
60 | ||
61 | /** @brief Frame serial number */ | |
62 | static uint32_t serial; | |
63 | ||
64 | /** @brief Size of ring buffer */ | |
211af11e | 65 | #define RINGSIZE 131072 |
210d172f RK |
66 | |
67 | /** @brief Ring buffer */ | |
68 | static struct entry ring[RINGSIZE]; | |
69 | ||
70 | /** @brief Where new packets join the ring */ | |
71 | static unsigned ringtail; | |
72 | ||
73 | static const struct option options[] = { | |
74 | { "help", no_argument, 0, 'h' }, | |
75 | { "version", no_argument, 0, 'V' }, | |
76 | { "bpf", required_argument, 0, 'b' }, | |
77 | { 0, 0, 0, 0 } | |
78 | }; | |
79 | ||
16bf32dc | 80 | static void attribute((noreturn)) help(void) { |
210d172f RK |
81 | xprintf("Usage:\n" |
82 | " rtpmon [OPTIONS] [ADDRESS] PORT\n" | |
83 | "Options:\n" | |
84 | " --bpf, -b Bytes/frame (default 4)\n" | |
85 | " --help, -h Display usage message\n" | |
86 | " --version, -V Display version number\n" | |
87 | ); | |
88 | xfclose(stdout); | |
89 | exit(0); | |
90 | } | |
91 | ||
92 | /** @brief Compute the rate by sampling at two points in the ring buffer */ | |
93 | static double rate(unsigned earlier, unsigned later) { | |
94 | const uint32_t frames = ring[later].serial - ring[earlier].serial; | |
95 | const int64_t us = tvsub_us(ring[later].when, ring[earlier].when); | |
96 | ||
97 | if(us) | |
98 | return 1000000.0 * frames / us; | |
99 | else | |
100 | return 0.0; | |
101 | } | |
102 | ||
103 | /** @brief Called to say we received some bytes | |
104 | * @param when When we received them | |
105 | * @param n How many frames of audio data we received | |
106 | */ | |
107 | static void frames(const struct timeval *when, size_t n) { | |
92dcf839 RK |
108 | const time_t prev = ring[(ringtail - 1) % RINGSIZE].when.tv_sec; |
109 | ||
210d172f RK |
110 | ring[ringtail].when = *when; |
111 | ring[ringtail].serial = serial; | |
112 | serial += n; | |
113 | ringtail = (ringtail + 1) % RINGSIZE; | |
92dcf839 RK |
114 | // Report once a second |
115 | if(prev != when->tv_sec) { | |
211af11e RK |
116 | if(printf("%8.2f %8.2f %8.2f %8.2f %8.2f %8.2f %8.2f\n", |
117 | rate((ringtail - RINGSIZE / 128) % RINGSIZE, | |
118 | (ringtail - 1) % RINGSIZE), | |
119 | rate((ringtail - RINGSIZE / 64) % RINGSIZE, | |
120 | (ringtail - 1) % RINGSIZE), | |
121 | rate((ringtail - RINGSIZE / 32) % RINGSIZE, | |
122 | (ringtail - 1) % RINGSIZE), | |
123 | rate((ringtail - RINGSIZE / 16) % RINGSIZE, | |
124 | (ringtail - 1) % RINGSIZE), | |
210d172f RK |
125 | rate((ringtail - RINGSIZE / 8) % RINGSIZE, |
126 | (ringtail - 1) % RINGSIZE), | |
127 | rate((ringtail - RINGSIZE / 4) % RINGSIZE, | |
128 | (ringtail - 1) % RINGSIZE), | |
129 | rate((ringtail - RINGSIZE / 2) % RINGSIZE, | |
130 | (ringtail - 1) % RINGSIZE)) < 0 | |
131 | || fflush(stdout) < 0) | |
2e9ba080 | 132 | disorder_fatal(errno, "stdout"); |
210d172f RK |
133 | } |
134 | } | |
135 | ||
136 | int main(int argc, char **argv) { | |
137 | int n; | |
138 | struct addrinfo *res; | |
139 | struct stringlist sl; | |
140 | struct ip_mreq mreq; | |
141 | struct ipv6_mreq mreq6; | |
142 | int rtpfd; | |
143 | char *sockname; | |
144 | int is_multicast; | |
145 | union any_sockaddr { | |
146 | struct sockaddr sa; | |
147 | struct sockaddr_in in; | |
148 | struct sockaddr_in6 in6; | |
149 | }; | |
150 | union any_sockaddr mgroup; | |
151 | ||
152 | static const struct addrinfo prefs = { | |
153 | .ai_flags = AI_PASSIVE, | |
154 | .ai_family = PF_INET, | |
155 | .ai_socktype = SOCK_DGRAM, | |
156 | .ai_protocol = IPPROTO_UDP | |
157 | }; | |
158 | static const int one = 1; | |
159 | ||
160 | mem_init(); | |
161 | if(!setlocale(LC_CTYPE, "")) | |
2e9ba080 | 162 | disorder_fatal(errno, "error calling setlocale"); |
210d172f RK |
163 | while((n = getopt_long(argc, argv, "hVb:", options, 0)) >= 0) { |
164 | switch(n) { | |
165 | case 'h': help(); | |
166 | case 'V': version("rtpmon"); | |
167 | case 'b': bpf = atoi(optarg); break; | |
2e9ba080 | 168 | default: disorder_fatal(0, "invalid option"); |
210d172f RK |
169 | } |
170 | } | |
171 | argc -= optind; | |
172 | argv += optind; | |
173 | switch(argc) { | |
174 | case 1: | |
175 | case 2: | |
176 | /* Use command-line ADDRESS+PORT or just PORT */ | |
177 | sl.n = argc; | |
178 | sl.s = argv; | |
179 | break; | |
180 | default: | |
2e9ba080 | 181 | disorder_fatal(0, "usage: rtpmon [OPTIONS] [ADDRESS] PORT"); |
210d172f RK |
182 | } |
183 | if(!(res = get_address(&sl, &prefs, &sockname))) | |
184 | exit(1); | |
185 | /* Create the socket */ | |
186 | if((rtpfd = socket(res->ai_family, | |
187 | res->ai_socktype, | |
188 | res->ai_protocol)) < 0) | |
2e9ba080 | 189 | disorder_fatal(errno, "error creating socket"); |
210d172f RK |
190 | /* Allow multiple listeners */ |
191 | xsetsockopt(rtpfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); | |
192 | is_multicast = multicast(res->ai_addr); | |
193 | /* The multicast and unicast/broadcast cases are different enough that they | |
194 | * are totally split. Trying to find commonality between them causes more | |
195 | * trouble that it's worth. */ | |
196 | if(is_multicast) { | |
197 | /* Stash the multicast group address */ | |
198 | memcpy(&mgroup, res->ai_addr, res->ai_addrlen); | |
199 | switch(res->ai_addr->sa_family) { | |
200 | case AF_INET: | |
201 | mgroup.in.sin_port = 0; | |
202 | break; | |
203 | case AF_INET6: | |
204 | mgroup.in6.sin6_port = 0; | |
205 | break; | |
206 | default: | |
2e9ba080 | 207 | disorder_fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family); |
210d172f RK |
208 | } |
209 | /* Bind to to the multicast group address */ | |
210 | if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0) | |
2e9ba080 RK |
211 | disorder_fatal(errno, "error binding socket to %s", |
212 | format_sockaddr(res->ai_addr)); | |
210d172f RK |
213 | /* Add multicast group membership */ |
214 | switch(mgroup.sa.sa_family) { | |
215 | case PF_INET: | |
216 | mreq.imr_multiaddr = mgroup.in.sin_addr; | |
217 | mreq.imr_interface.s_addr = 0; /* use primary interface */ | |
218 | if(setsockopt(rtpfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, | |
219 | &mreq, sizeof mreq) < 0) | |
2e9ba080 | 220 | disorder_fatal(errno, "error calling setsockopt IP_ADD_MEMBERSHIP"); |
210d172f RK |
221 | break; |
222 | case PF_INET6: | |
223 | mreq6.ipv6mr_multiaddr = mgroup.in6.sin6_addr; | |
224 | memset(&mreq6.ipv6mr_interface, 0, sizeof mreq6.ipv6mr_interface); | |
225 | if(setsockopt(rtpfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, | |
226 | &mreq6, sizeof mreq6) < 0) | |
2e9ba080 | 227 | disorder_fatal(errno, "error calling setsockopt IPV6_JOIN_GROUP"); |
210d172f RK |
228 | break; |
229 | default: | |
2e9ba080 | 230 | disorder_fatal(0, "unsupported address family %d", res->ai_family); |
210d172f RK |
231 | } |
232 | /* Report what we did */ | |
2e9ba080 RK |
233 | disorder_info("listening on %s multicast group %s", |
234 | format_sockaddr(res->ai_addr), format_sockaddr(&mgroup.sa)); | |
210d172f RK |
235 | } else { |
236 | /* Bind to 0/port */ | |
237 | switch(res->ai_addr->sa_family) { | |
238 | case AF_INET: { | |
239 | struct sockaddr_in *in = (struct sockaddr_in *)res->ai_addr; | |
240 | ||
241 | memset(&in->sin_addr, 0, sizeof (struct in_addr)); | |
242 | if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0) | |
2e9ba080 RK |
243 | disorder_fatal(errno, "error binding socket to 0.0.0.0 port %d", |
244 | ntohs(in->sin_port)); | |
210d172f RK |
245 | break; |
246 | } | |
247 | case AF_INET6: { | |
248 | struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)res->ai_addr; | |
249 | ||
250 | memset(&in6->sin6_addr, 0, sizeof (struct in6_addr)); | |
251 | break; | |
252 | } | |
253 | default: | |
2e9ba080 | 254 | disorder_fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family); |
210d172f RK |
255 | } |
256 | if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0) | |
2e9ba080 RK |
257 | disorder_fatal(errno, "error binding socket to %s", |
258 | format_sockaddr(res->ai_addr)); | |
210d172f | 259 | /* Report what we did */ |
2e9ba080 | 260 | disorder_info("listening on %s", format_sockaddr(res->ai_addr)); |
210d172f RK |
261 | } |
262 | for(;;) { | |
263 | struct rtp_header header; | |
264 | char buffer[4096]; | |
265 | struct iovec iov[2]; | |
266 | struct timeval when; | |
267 | ||
268 | iov[0].iov_base = &header; | |
269 | iov[0].iov_len = sizeof header; | |
270 | iov[1].iov_base = buffer; | |
271 | iov[1].iov_len = sizeof buffer; | |
272 | n = readv(rtpfd, iov, 2); | |
273 | gettimeofday(&when, 0); | |
274 | if(n < 0) { | |
275 | switch(errno) { | |
276 | case EINTR: | |
277 | continue; | |
278 | default: | |
2e9ba080 | 279 | disorder_fatal(errno, "error reading from socket"); |
210d172f RK |
280 | } |
281 | } | |
282 | if((size_t)n <= sizeof (struct rtp_header)) { | |
2e9ba080 | 283 | disorder_info("ignored a short packet"); |
210d172f RK |
284 | continue; |
285 | } | |
286 | frames(&when, (n - sizeof header) / bpf); | |
287 | } | |
288 | } | |
289 | ||
290 | /* | |
291 | Local Variables: | |
292 | c-basic-offset:2 | |
293 | comment-column:40 | |
294 | fill-column:79 | |
295 | indent-tabs-mode:nil | |
296 | End: | |
297 | */ |