]> git.sesse.net Git - vlc/blob - modules/demux/rtpsession.c
Minimalistic RTP demux
[vlc] / modules / demux / rtpsession.c
1 /**
2  * @file session.c
3  * @brief RTP session handling
4  */
5 /*****************************************************************************
6  * Copyright © 2008 Rémi Denis-Courmont
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2.0
11  * of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  ****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <stdlib.h>
28 #include <assert.h>
29 #include <errno.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc_demux.h>
33
34 #include "rtp.h"
35
36 typedef struct rtp_source_t rtp_source_t;
37
38 /** State for a RTP session: */
39 struct rtp_session_t
40 {
41     rtp_source_t **srcv;
42     unsigned       srcc;
43     uint8_t        ptc;
44     rtp_pt_t      *ptv;
45 };
46
47 static rtp_source_t *
48 rtp_source_create (demux_t *, const rtp_session_t *, uint32_t, uint16_t);
49 static void
50 rtp_source_destroy (demux_t *, const rtp_session_t *, rtp_source_t *);
51
52 static void rtp_decode (demux_t *, const rtp_session_t *, rtp_source_t *);
53
54 /**
55  * Creates a new RTP session.
56  */
57 rtp_session_t *
58 rtp_session_create (demux_t *demux)
59 {
60     rtp_session_t *session = malloc (sizeof (*session));
61     if (session == NULL)
62         return NULL;
63
64     session->srcv = NULL;
65     session->srcc = 0;
66     session->ptc = 0;
67     session->ptv = NULL;
68
69     (void)demux;
70     return session;
71 }
72
73
74 /**
75  * Destroys an RTP session.
76  */
77 void rtp_session_destroy (demux_t *demux, rtp_session_t *session)
78 {
79     for (unsigned i = 0; i < session->srcc; i++)
80         rtp_source_destroy (demux, session, session->srcv[i]);
81
82     free (session->srcv);
83     free (session->ptv);
84     free (session);
85     (void)demux;
86 }
87
88 static void *no_init (demux_t *demux)
89 {
90     return demux;
91 }
92
93 static void no_destroy (demux_t *demux, void *opaque)
94 {
95     (void)demux; (void)opaque;
96 }
97
98 static void no_decode (demux_t *demux, void *opaque, block_t *block)
99 {
100     (void)demux; (void)opaque;
101     block_Release (block);
102 }
103
104 /**
105  * Adds a payload type to an RTP session.
106  */
107 int rtp_add_type (demux_t *demux, rtp_session_t *ses, const rtp_pt_t *pt)
108 {
109     if (ses->srcc > 0)
110     {
111         msg_Err (demux, "cannot change RTP payload formats during session");
112         return EINVAL;
113     }
114
115     rtp_pt_t *ppt = realloc (ses->ptv, (ses->ptc + 1) * sizeof (rtp_pt_t));
116     if (ppt == NULL)
117         return ENOMEM;
118
119     ses->ptv = ppt;
120     ppt += ses->ptc++;
121
122     ppt->init = pt->init ? pt->init : no_init;
123     ppt->destroy = pt->destroy ? pt->destroy : no_destroy;
124     ppt->decode = pt->decode ? pt->decode : no_decode;
125     ppt->frequency = pt->frequency;
126     ppt->number = pt->number;
127
128     assert (ppt->frequency > 0); /* SIGFPE! */
129     (void)demux;
130     return 0;
131 }
132
133 /** State for an RTP source */
134 struct rtp_source_t
135 {
136     mtime_t  expiry;  /* inactivation date */
137     uint32_t ssrc;
138     uint16_t bad_seq; /* tentatively next expected sequence for resync */
139     uint16_t max_seq; /* next expected sequence */
140
141     block_t *blocks; /* re-ordered blocks queue */
142     void    *opaque[0]; /* Per-source prviate payload data */
143 };
144
145 /**
146  * Initializes a new RTP source within an RTP session.
147  */
148 static rtp_source_t *
149 rtp_source_create (demux_t *demux, const rtp_session_t *session,
150                    uint32_t ssrc, uint16_t init_seq)
151 {
152     rtp_source_t *source;
153
154     source = malloc (sizeof (*source) + (sizeof (void *) * session->ptc));
155     if (source == NULL)
156         return NULL;
157
158     source->ssrc = ssrc;
159     source->max_seq = source->bad_seq = init_seq;
160     source->blocks = NULL;
161
162     /* Initializes all payload */
163     for (unsigned i = 0; i < session->ptc; i++)
164         source->opaque[i] = session->ptv[i].init (demux);
165
166     msg_Dbg (demux, "added RTP source (%08x)", ssrc);
167     return source;
168 }
169
170
171 /**
172  * Destroys an RTP source and its associated streams.
173  */
174 static void
175 rtp_source_destroy (demux_t *demux, const rtp_session_t *session,
176                     rtp_source_t *source)
177 {
178     msg_Dbg (demux, "removing RTP source (%08x)", source->ssrc);
179
180     for (unsigned i = 0; i < session->ptc; i++)
181         session->ptv[i].destroy (demux, source->opaque[i]);
182     block_ChainRelease (source->blocks);
183     free (source);
184 }
185
186
187 static inline uint16_t rtp_seq (const block_t *block)
188 {
189     assert (block->i_buffer >= 4);
190     return GetWBE (block->p_buffer + 2);
191 }
192
193 /**
194  * Receives an RTP packet and queues it.
195  * @param demux VLC demux object
196  * @param session RTP session receiving the packet
197  * @param block RTP packet including the RTP header
198  */
199 void
200 rtp_receive (demux_t *demux, rtp_session_t *session, block_t *block)
201 {
202     demux_sys_t *p_sys = demux->p_sys;
203
204     /* RTP header sanity checks (see RFC 3550) */
205     if (block->i_buffer < 12)
206         goto drop;
207     if ((block->p_buffer[0] >> 6 ) != 2) /* RTP version number */
208         goto drop;
209
210     /* Remove padding if present */
211     if (block->p_buffer[0] & 0x20)
212     {
213         uint8_t padding = block->p_buffer[block->i_buffer - 1];
214         if ((padding == 0) || (block->i_buffer < (12u + padding)))
215             goto drop; /* illegal value */
216
217         block->i_buffer -= padding;
218     }
219
220     mtime_t        now = mdate ();
221     rtp_source_t  *src  = NULL;
222     const uint16_t seq  = GetWBE (block->p_buffer + 2);
223     const uint32_t ssrc = GetDWBE (block->p_buffer + 8);
224
225     /* In most case, we know this source already */
226     for (unsigned i = 0, max = session->srcc; i < max; i++)
227     {
228         rtp_source_t *tmp = session->srcv[i];
229         if (tmp->ssrc == ssrc)
230         {
231             src = tmp;
232             break;
233         }
234
235         /* RTP source garbage collection */
236         if (tmp->expiry < now)
237         {
238             rtp_source_destroy (demux, session, tmp);
239             if (--session->srcc > 0)
240                 session->srcv[i] = session->srcv[session->srcc - 1];
241         }
242     }
243
244     if (src == NULL)
245     {
246         /* New source */
247         if (session->srcc >= p_sys->max_src)
248         {
249             msg_Warn (demux, "too many RTP sessions");
250             goto drop;
251         }
252
253         rtp_source_t **tab;
254         tab = realloc (session->srcv, (session->srcc + 1) * sizeof (*tab));
255         if (tab == NULL)
256             goto drop;
257         session->srcv = tab;
258
259         src = rtp_source_create (demux, session, ssrc, seq);
260         if (src == NULL)
261             goto drop;
262
263         tab[session->srcc++] = src;
264     }
265
266     /* Check sequence number */
267     /* NOTE: the sequence number is per-source,
268      * but is independent from the payload type. */
269     uint16_t delta_seq = seq - (src->max_seq + 1);
270     if ((delta_seq < 0x8000) ? (delta_seq > p_sys->max_dropout)
271                              : ((65535 - delta_seq) > p_sys->max_misorder))
272     {
273         msg_Dbg (demux, "sequence discontinuity (got: %u, expected: %u)",
274                  seq, (src->max_seq + 1) & 0xffff);
275         if (seq == ((src->bad_seq + 1) & 0xffff))
276         {
277             src->max_seq = src->bad_seq = seq;
278             msg_Warn (demux, "sequence resynchronized");
279             block_ChainRelease (src->blocks);
280             src->blocks = NULL;
281         }
282         else
283         {
284             src->bad_seq = seq;
285             goto drop;
286         }
287     }
288
289     /* Queues the block in sequence order,
290      * hence there is a single queue for all payload types. */
291     block_t **pp = &src->blocks;
292     for (block_t *prev = *pp; prev != NULL; prev = *pp)
293     {
294         if ((int16_t)(seq - rtp_seq (*pp)) >= 0)
295             break;
296         pp = &prev->p_next;
297     }
298     block->p_next = *pp;
299     *pp = block;
300
301     rtp_decode (demux, session, src);
302     return;
303
304 drop:
305     block_Release (block);
306 }
307
308
309 static void
310 rtp_decode (demux_t *demux, const rtp_session_t *session, rtp_source_t *src)
311 {
312     block_t *block = src->blocks;
313
314     /* Buffer underflow? */
315     if (!block || !block->p_next || !block->p_next->p_next)
316         return;
317     /* TODO: use time rather than packet counts for buffer measurement */
318     src->blocks = block->p_next;
319     block->p_next = NULL;
320
321     /* Match the payload type */
322     const rtp_pt_t *pt = NULL;
323     void *pt_data = NULL;
324     const uint8_t   ptype = block->p_buffer[1] & 0x7F;
325
326     for (unsigned i = 0; i < session->ptc; i++)
327     {
328         if (session->ptv[i].number == ptype)
329         {
330             pt = &session->ptv[i];
331             pt_data = src->opaque[i];
332             break;
333         }
334     }
335
336     if (pt == NULL)
337     {
338         msg_Dbg (demux, "ignoring unknown payload (%"PRIu8")", ptype);
339         goto drop;
340     }
341
342     /* Computes the PTS from the RTP timestamp and payload RTP frequency.
343      * DTS is unknown. Also, while the clock frequency depends on the payload
344      * format, a single source MUST only use payloads of a chosen frequency.
345      * Otherwise it would be impossible to compute consistent timestamps. */
346     /* FIXME: handle timestamp wrap properly */
347     /* TODO: sync multiple sources sanely... */
348     const uint32_t timestamp = GetDWBE (block->p_buffer + 4);
349     block->i_pts = UINT64_C(1) * CLOCK_FREQ * timestamp / pt->frequency;
350     //msg_Dbg (demux, "pts = %"PRIu64, block->i_pts);
351
352     /* CSRC count */
353     size_t skip = 12u + (block->p_buffer[0] & 0x0F) * 4;
354
355     /* Extension header (ignored for now) */
356     if (block->p_buffer[0] & 0x10)
357     {
358         skip += 4;
359         if (block->i_buffer < skip)
360             goto drop;
361
362         skip += 4 * GetWBE (block->p_buffer + skip - 2);
363     }
364
365     if (block->i_buffer < skip)
366         goto drop;
367
368     block->p_buffer += skip;
369     block->i_buffer -= skip;
370
371     pt->decode (demux, pt_data, block);
372     return;
373
374 drop:
375     block_Release (block);
376 }