rtsp_client.c 13.6 KB
Newer Older
1
2
3
4
5
6
7
/***
  This file is part of PulseAudio.

  Copyright 2008 Colin Guthrie

  PulseAudio is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
8
  by the Free Software Foundation; either version 2.1 of the License,
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  or (at your option) any later version.

  PulseAudio is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/ioctl.h>
33
#include <netinet/in.h>
34
35
36
37
38
39
40
41
42
43
44
45
46

#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#include <pulse/xmalloc.h>

#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/socket-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/strbuf.h>
47
#include <pulsecore/ioline.h>
48

Finn Thain's avatar
Finn Thain committed
49
50
51
52
53
54
#ifdef HAVE_POLL_H
#include <poll.h>
#else
#include <pulsecore/poll.h>
#endif

55
#include "rtsp_client.h"
56

57
struct pa_rtsp_client {
58
59
60
61
    pa_mainloop_api *mainloop;
    char *hostname;
    uint16_t port;

62
    pa_socket_client *sc;
63
    pa_ioline *ioline;
64

65
    pa_rtsp_cb_t callback;
66

67
68
    void *userdata;
    const char *useragent;
69

70
71
    pa_rtsp_state state;
    uint8_t waiting;
72

73
74
75
76
    pa_headerlist* headers;
    char *last_header;
    pa_strbuf *header_buffer;
    pa_headerlist* response_headers;
77

78
79
    char *localip;
    char *url;
80
    uint16_t rtp_port;
81
82
83
84
    uint32_t cseq;
    char *session;
    char *transport;
};
85

86
pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char* hostname, uint16_t port, const char* useragent) {
87
    pa_rtsp_client *c;
88

89
90
91
92
    pa_assert(mainloop);
    pa_assert(hostname);
    pa_assert(port > 0);

93
    c = pa_xnew0(pa_rtsp_client, 1);
94
95
96
    c->mainloop = mainloop;
    c->hostname = pa_xstrdup(hostname);
    c->port = port;
97
98
99
100
101
102
103
104
105
106
107
    c->headers = pa_headerlist_new();

    if (useragent)
        c->useragent = useragent;
    else
        c->useragent = "PulseAudio RTSP Client";

    return c;
}


108
void pa_rtsp_client_free(pa_rtsp_client* c) {
109
110
111
112
    pa_assert(c);

    if (c->sc)
        pa_socket_client_unref(c->sc);
113
114

    pa_rtsp_disconnect(c);
115
116
117
118
119
120
121
122
123
124
125
126
127

    pa_xfree(c->hostname);
    pa_xfree(c->url);
    pa_xfree(c->localip);
    pa_xfree(c->session);
    pa_xfree(c->transport);
    pa_xfree(c->last_header);
    if (c->header_buffer)
        pa_strbuf_free(c->header_buffer);
    if (c->response_headers)
        pa_headerlist_free(c->response_headers);
    pa_headerlist_free(c->headers);

128
129
130
131
    pa_xfree(c);
}


132
static void headers_read(pa_rtsp_client *c) {
133
    char* token;
134
    char delimiters[] = ";";
135

136
137
    pa_assert(c);
    pa_assert(c->response_headers);
138
    pa_assert(c->callback);
139

140
141
    /* Deal with a SETUP response */
    if (STATE_SETUP == c->state) {
142
143
        const char* token_state = NULL;
        const char* pc = NULL;
144
145
        c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session"));
        c->transport = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Transport"));
146
147

        if (!c->session || !c->transport) {
148
            pa_log("Invalid SETUP response.");
149
150
151
152
153
154
155
            return;
        }

        /* Now parse out the server port component of the response. */
        while ((token = pa_split(c->transport, delimiters, &token_state))) {
            if ((pc = strstr(token, "="))) {
                if (0 == strncmp(token, "server_port", 11)) {
156
                    pa_atou(pc+1, (uint32_t*)(&c->rtp_port));
157
158
159
160
161
162
                    pa_xfree(token);
                    break;
                }
            }
            pa_xfree(token);
        }
163
        if (0 == c->rtp_port) {
164
            /* Error no server_port in response */
165
            pa_log("Invalid SETUP response (no port number).");
166
167
168
169
170
            return;
        }
    }

    /* Call our callback */
171
    c->callback(c, c->state, c->response_headers, c->userdata);
172
}
173
174


175
176
177
static void line_callback(pa_ioline *line, const char *s, void *userdata) {
    char *delimpos;
    char *s2, *s2p;
178

179
    pa_rtsp_client *c = userdata;
180
181
    pa_assert(line);
    pa_assert(c);
182
    pa_assert(c->callback);
183
184

    if (!s) {
Colin Guthrie's avatar
Colin Guthrie committed
185
186
        /* Keep the ioline/iochannel open as they will be freed automatically */
        c->ioline = NULL;
187
        c->callback(c, STATE_DISCONNECTED, NULL, c->userdata);
188
189
        return;
    }
190
191
192
193
194
195
196
197
198
199

    s2 = pa_xstrdup(s);
    /* Trim trailing carriage returns */
    s2p = s2 + strlen(s2) - 1;
    while (s2p >= s2 && '\r' == *s2p) {
        *s2p = '\0';
        s2p -= 1;
    }
    if (c->waiting && 0 == strcmp("RTSP/1.0 200 OK", s2)) {
        c->waiting = 0;
200
201
        if (c->response_headers)
            pa_headerlist_free(c->response_headers);
202
203
204
205
206
207
208
209
210
        c->response_headers = pa_headerlist_new();
        goto exit;
    }
    if (c->waiting) {
        pa_log_warn("Unexpected response: %s", s2);
        goto exit;;
    }
    if (!strlen(s2)) {
        /* End of headers */
Maarten Bosmans's avatar
Maarten Bosmans committed
211
        /* We will have a header left from our looping iteration, so add it in :) */
212
        if (c->last_header) {
Andy Shevchenko's avatar
Andy Shevchenko committed
213
            char *tmp = pa_strbuf_tostring_free(c->header_buffer);
214
            /* This is not a continuation header so let's dump it into our proplist */
Andy Shevchenko's avatar
Andy Shevchenko committed
215
216
            pa_headerlist_puts(c->response_headers, c->last_header, tmp);
            pa_xfree(tmp);
217
218
            pa_xfree(c->last_header);
            c->last_header = NULL;
Andy Shevchenko's avatar
Andy Shevchenko committed
219
            c->header_buffer = NULL;
220
221
        }

222
223
224
225
        pa_log_debug("Full response received. Dispatching");
        headers_read(c);
        c->waiting = 1;
        goto exit;
226
    }
227
228
229
230
231
232
233
234
235
236
237
238
239
240

    /* Read and parse a header (we know it's not empty) */
    /* TODO: Move header reading into the headerlist. */

    /* If the first character is a space, it's a continuation header */
    if (c->last_header && ' ' == s2[0]) {
        pa_assert(c->header_buffer);

        /* Add this line to the buffer (sans the space. */
        pa_strbuf_puts(c->header_buffer, &(s2[1]));
        goto exit;
    }

    if (c->last_header) {
Andy Shevchenko's avatar
Andy Shevchenko committed
241
        char *tmp = pa_strbuf_tostring_free(c->header_buffer);
242
243
        /* This is not a continuation header so let's dump the full
          header/value into our proplist */
Andy Shevchenko's avatar
Andy Shevchenko committed
244
245
        pa_headerlist_puts(c->response_headers, c->last_header, tmp);
        pa_xfree(tmp);
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
        pa_xfree(c->last_header);
        c->last_header = NULL;
        c->header_buffer = NULL;
    }

    delimpos = strstr(s2, ":");
    if (!delimpos) {
        pa_log_warn("Unexpected response when expecting header: %s", s);
        goto exit;
    }

    pa_assert(!c->header_buffer);
    pa_assert(!c->last_header);

    c->header_buffer = pa_strbuf_new();
    if (strlen(delimpos) > 1) {
        /* Cut our line off so we can copy the header name out */
        *delimpos++ = '\0';

        /* Trim the front of any spaces */
        while (' ' == *delimpos)
            ++delimpos;

        pa_strbuf_puts(c->header_buffer, delimpos);
    } else {
        /* Cut our line off so we can copy the header name out */
        *delimpos = '\0';
    }

    /* Save the header name */
    c->last_header = pa_xstrdup(s2);
  exit:
    pa_xfree(s2);
279
}
280

281

282
static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
283
    pa_rtsp_client *c = userdata;
284
285
286
287
288
289
290
291
    union {
        struct sockaddr sa;
        struct sockaddr_in in;
        struct sockaddr_in6 in6;
    } sa;
    socklen_t sa_len = sizeof(sa);

    pa_assert(sc);
292
    pa_assert(c);
293
    pa_assert(STATE_CONNECT == c->state);
294
295
296
    pa_assert(c->sc == sc);
    pa_socket_client_unref(c->sc);
    c->sc = NULL;
297

298
299
300
301
    if (!io) {
        pa_log("Connection failed: %s", pa_cstrerror(errno));
        return;
    }
302
    pa_assert(!c->ioline);
303
304
305

    c->ioline = pa_ioline_new(io);
    pa_ioline_set_callback(c->ioline, line_callback, c);
306
307
308
309
310
311
312

    /* Get the local IP address for use externally */
    if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) {
        char buf[INET6_ADDRSTRLEN];
        const char *res = NULL;

        if (AF_INET == sa.sa.sa_family) {
313
314
315
            if ((res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf)))) {
                c->localip = pa_xstrdup(res);
            }
316
        } else if (AF_INET6 == sa.sa.sa_family) {
317
318
319
            if ((res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)))) {
                c->localip = pa_sprintf_malloc("[%s]", res);
            }
320
321
        }
    }
322
    pa_log_debug("Established RTSP connection from local ip %s", c->localip);
323
324
325

    if (c->callback)
        c->callback(c, c->state, NULL, c->userdata);
326
}
327

328
int pa_rtsp_connect(pa_rtsp_client *c) {
329
    pa_assert(c);
330
331
332
333
    pa_assert(!c->sc);

    pa_xfree(c->session);
    c->session = NULL;
334

335
    if (!(c->sc = pa_socket_client_new_string(c->mainloop, TRUE, c->hostname, c->port))) {
336
        pa_log("failed to connect to server '%s:%d'", c->hostname, c->port);
337
        return -1;
338
339
    }

340
    pa_socket_client_set_callback(c->sc, on_connection, c);
341
    c->waiting = 1;
342
    c->state = STATE_CONNECT;
343
    return 0;
344
345
}

346
void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata) {
347
348
349
350
351
    pa_assert(c);

    c->callback = callback;
    c->userdata = userdata;
}
352

353
void pa_rtsp_disconnect(pa_rtsp_client *c) {
354
355
    pa_assert(c);

356
357
358
    if (c->ioline)
        pa_ioline_close(c->ioline);
    c->ioline = NULL;
359
360
361
}


362
const char* pa_rtsp_localip(pa_rtsp_client* c) {
363
364
    pa_assert(c);

365
    return c->localip;
366
367
}

368
uint32_t pa_rtsp_serverport(pa_rtsp_client* c) {
369
370
    pa_assert(c);

371
    return c->rtp_port;
372
}
373

374
void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) {
375
376
377
378
379
    pa_assert(c);

    c->url = pa_xstrdup(url);
}

380
void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value)
381
382
383
384
385
386
387
388
{
    pa_assert(c);
    pa_assert(key);
    pa_assert(value);

    pa_headerlist_puts(c->headers, key, value);
}

389
void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key)
390
391
392
393
394
395
396
{
    pa_assert(c);
    pa_assert(key);

    pa_headerlist_remove(c->headers, key);
}

397
398
399
400
401
402
403
404
405
static int rtsp_exec(pa_rtsp_client* c, const char* cmd,
                        const char* content_type, const char* content,
                        int expect_response,
                        pa_headerlist* headers) {
    pa_strbuf* buf;
    char* hdrs;

    pa_assert(c);
    pa_assert(c->url);
406
407
    pa_assert(cmd);
    pa_assert(c->ioline);
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445

    pa_log_debug("Sending command: %s", cmd);

    buf = pa_strbuf_new();
    pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq);
    if (c->session)
        pa_strbuf_printf(buf, "Session: %s\r\n", c->session);

    /* Add the headers */
    if (headers) {
        hdrs = pa_headerlist_to_string(headers);
        pa_strbuf_puts(buf, hdrs);
        pa_xfree(hdrs);
    }

    if (content_type && content) {
        pa_strbuf_printf(buf, "Content-Type: %s\r\nContent-Length: %d\r\n",
          content_type, (int)strlen(content));
    }

    pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent);

    if (c->headers) {
        hdrs = pa_headerlist_to_string(c->headers);
        pa_strbuf_puts(buf, hdrs);
        pa_xfree(hdrs);
    }

    pa_strbuf_puts(buf, "\r\n");

    if (content_type && content) {
        pa_strbuf_puts(buf, content);
    }

    /* Our packet is created... now we can send it :) */
    hdrs = pa_strbuf_tostring_free(buf);
    /*pa_log_debug("Submitting request:");
    pa_log_debug(hdrs);*/
446
    pa_ioline_puts(c->ioline, hdrs);
447
448
449
450
451
452
    pa_xfree(hdrs);

    return 0;
}


453
int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) {
454
455
    pa_assert(c);
    if (!sdp)
456
        return -1;
457

458
    c->state = STATE_ANNOUNCE;
459
    return rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL);
460
461
462
}


463
int pa_rtsp_setup(pa_rtsp_client* c) {
464
    pa_headerlist* headers;
465
    int rv;
466
467
468
469
470
471

    pa_assert(c);

    headers = pa_headerlist_new();
    pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");

472
    c->state = STATE_SETUP;
473
    rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers);
474
    pa_headerlist_free(headers);
475
    return rv;
476
477
478
}


479
int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime) {
480
481
    pa_headerlist* headers;
    int rv;
482
    char *info;
483
484
485

    pa_assert(c);
    if (!c->session) {
Maarten Bosmans's avatar
Maarten Bosmans committed
486
        /* No session in progress */
487
        return -1;
488
489
    }

490
491
492
    /* Todo: Generate these values randomly as per spec */
    *seq = *rtptime = 0;

493
494
    headers = pa_headerlist_new();
    pa_headerlist_puts(headers, "Range", "npt=0-");
495
496
497
    info = pa_sprintf_malloc("seq=%u;rtptime=%u", *seq, *rtptime);
    pa_headerlist_puts(headers, "RTP-Info", info);
    pa_xfree(info);
498

499
    c->state = STATE_RECORD;
500
    rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers);
501
502
503
504
505
    pa_headerlist_free(headers);
    return rv;
}


506
int pa_rtsp_teardown(pa_rtsp_client *c) {
507
508
    pa_assert(c);

509
    c->state = STATE_TEARDOWN;
510
    return rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL);
511
512
513
}


514
int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) {
515
516
    pa_assert(c);
    if (!param)
517
        return -1;
518

519
    c->state = STATE_SET_PARAMETER;
520
    return rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL);
521
522
523
}


524
int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) {
525
526
    pa_headerlist* headers;
    int rv;
527
    char *info;
528
529
530
531

    pa_assert(c);

    headers = pa_headerlist_new();
532
533
534
    info = pa_sprintf_malloc("seq=%u;rtptime=%u", seq, rtptime);
    pa_headerlist_puts(headers, "RTP-Info", info);
    pa_xfree(info);
535

536
    c->state = STATE_FLUSH;
537
    rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers);
538
539
540
    pa_headerlist_free(headers);
    return rv;
}