2 * RTP stream handler functions used by tshark and wireshark
4 * Copyright 2008, Ericsson AB
5 * By Balint Reczey <balint.reczey@ericsson.com>
7 * most functions are copied from ui/gtk/rtp_stream.c and ui/gtk/rtp_analysis.c
8 * Copyright 2003, Alcatel Business Systems
9 * By Lars Ruoff <lars.ruoff@gmx.net>
11 * Wireshark - Network traffic analyzer
12 * By Gerald Combs <gerald@wireshark.org>
13 * Copyright 1998 Gerald Combs
15 * SPDX-License-Identifier: GPL-2.0-or-later
26 #include <epan/rtp_pt.h>
27 #include <epan/addr_resolv.h>
28 #include <epan/proto_data.h>
29 #include <epan/dissectors/packet-rtp.h>
30 #include <wsutil/pint.h>
31 #include "rtp_stream.h"
32 #include "tap-rtp-common.h"
34 /* XXX: are changes needed to properly handle situations where
35 info_all_data_present == false ?
36 E.G., when captured frames are truncated.
39 /****************************************************************************/
40 /* Type for storing and writing rtpdump information */
41 typedef struct st_rtpdump_info
{
42 double rec_time
; /**< milliseconds since start of recording */
43 uint16_t num_samples
; /**< number of bytes in *frame */
44 const uint8_t *samples
; /**< data bytes */
47 /****************************************************************************/
48 /* init rtpstream_info_t structure */
49 void rtpstream_info_init(rtpstream_info_t
*info
)
51 memset(info
, 0, sizeof(rtpstream_info_t
));
54 /****************************************************************************/
55 /* malloc and init rtpstream_info_t structure */
56 rtpstream_info_t
*rtpstream_info_malloc_and_init(void)
58 rtpstream_info_t
*dest
;
60 dest
= g_new(rtpstream_info_t
, 1);
61 rtpstream_info_init(dest
);
66 /****************************************************************************/
67 /* deep copy of rtpstream_info_t */
68 void rtpstream_info_copy_deep(rtpstream_info_t
*dest
, const rtpstream_info_t
*src
)
70 /* Deep clone of contents */
71 *dest
= *src
; /* memberwise copy of struct */
72 copy_address(&(dest
->id
.src_addr
), &(src
->id
.src_addr
));
73 copy_address(&(dest
->id
.dst_addr
), &(src
->id
.dst_addr
));
74 dest
->all_payload_type_names
= g_strdup(src
->all_payload_type_names
);
77 /****************************************************************************/
78 /* malloc and deep copy rtpstream_info_t structure */
79 rtpstream_info_t
*rtpstream_info_malloc_and_copy_deep(const rtpstream_info_t
*src
)
81 rtpstream_info_t
*dest
;
83 dest
= g_new(rtpstream_info_t
, 1);
84 rtpstream_info_copy_deep(dest
, src
);
89 /****************************************************************************/
90 /* free rtpstream_info_t referenced values */
91 void rtpstream_info_free_data(rtpstream_info_t
*info
)
93 if (info
->all_payload_type_names
!= NULL
) {
94 g_free(info
->all_payload_type_names
);
97 rtpstream_id_free(&info
->id
);
100 /****************************************************************************/
101 /* free rtpstream_info_t referenced values and whole structure */
102 void rtpstream_info_free_all(rtpstream_info_t
*info
)
104 rtpstream_info_free_data(info
);
108 /****************************************************************************/
109 /* GCompareFunc style comparison function for rtpstream_info_t */
110 int rtpstream_info_cmp(const void *aa
, const void *bb
)
112 const rtpstream_info_t
*a
= (const rtpstream_info_t
*)aa
;
113 const rtpstream_info_t
*b
= (const rtpstream_info_t
*)bb
;
117 if (a
==NULL
|| b
==NULL
)
119 if (rtpstream_id_equal(&(a
->id
),&(b
->id
),RTPSTREAM_ID_EQUAL_SSRC
))
125 /****************************************************************************/
126 /* compare the endpoints of two RTP streams */
127 bool rtpstream_info_is_reverse(const rtpstream_info_t
*stream_a
, rtpstream_info_t
*stream_b
)
129 if (stream_a
== NULL
|| stream_b
== NULL
)
132 if ((addresses_equal(&(stream_a
->id
.src_addr
), &(stream_b
->id
.dst_addr
)))
133 && (stream_a
->id
.src_port
== stream_b
->id
.dst_port
)
134 && (addresses_equal(&(stream_a
->id
.dst_addr
), &(stream_b
->id
.src_addr
)))
135 && (stream_a
->id
.dst_port
== stream_b
->id
.src_port
))
141 /****************************************************************************/
142 /* when there is a [re]reading of packet's */
143 void rtpstream_reset(rtpstream_tapinfo_t
*tapinfo
)
146 rtpstream_info_t
*stream_info
;
148 if (tapinfo
->mode
== TAP_ANALYSE
) {
149 /* free the data items first */
150 if (tapinfo
->strinfo_hash
) {
151 g_hash_table_foreach(tapinfo
->strinfo_hash
, rtpstream_info_multihash_destroy_value
, NULL
);
152 g_hash_table_destroy(tapinfo
->strinfo_hash
);
154 list
= g_list_first(tapinfo
->strinfo_list
);
157 stream_info
= (rtpstream_info_t
*)(list
->data
);
158 rtpstream_info_free_data(stream_info
);
160 list
= g_list_next(list
);
162 g_list_free(tapinfo
->strinfo_list
);
163 tapinfo
->strinfo_list
= NULL
;
164 tapinfo
->strinfo_hash
= NULL
;
165 tapinfo
->nstreams
= 0;
166 tapinfo
->npackets
= 0;
172 void rtpstream_reset_cb(void *arg
)
174 rtpstream_tapinfo_t
*ti
=(rtpstream_tapinfo_t
*)arg
;
176 /* Give listeners a chance to cleanup references. */
182 /****************************************************************************/
184 /****************************************************************************/
186 /****************************************************************************/
187 /* redraw the output */
188 static void rtpstream_draw_cb(void *ti_ptr
)
190 rtpstream_tapinfo_t
*tapinfo
= (rtpstream_tapinfo_t
*)ti_ptr
;
191 /* XXX: see rtpstream_on_update in rtp_streams_dlg.c for comments
192 g_signal_emit_by_name(top_level, "signal_rtpstream_update");
194 if (tapinfo
&& tapinfo
->tap_draw
) {
195 /* RTP_STREAM_DEBUG("streams: %d packets: %d", tapinfo->nstreams, tapinfo->npackets); */
196 tapinfo
->tap_draw(tapinfo
);
203 /****************************************************************************/
205 remove_tap_listener_rtpstream(rtpstream_tapinfo_t
*tapinfo
)
207 if (tapinfo
&& tapinfo
->is_registered
) {
208 remove_tap_listener(tapinfo
);
209 tapinfo
->is_registered
= false;
213 /****************************************************************************/
215 register_tap_listener_rtpstream(rtpstream_tapinfo_t
*tapinfo
, const char *fstring
, rtpstream_tap_error_cb tap_error
)
217 GString
*error_string
;
223 if (!tapinfo
->is_registered
) {
224 error_string
= register_tap_listener("rtp", tapinfo
,
225 fstring
, 0, rtpstream_reset_cb
, rtpstream_packet_cb
,
226 rtpstream_draw_cb
, NULL
);
228 if (error_string
!= NULL
) {
230 tap_error(error_string
);
232 g_string_free(error_string
, TRUE
);
236 tapinfo
->is_registered
= true;
241 * rtpdump file format
243 * The file starts with the tool to be used for playing this file,
244 * the multicast/unicast receive address and the port.
246 * #!rtpplay1.0 224.2.0.1/3456\n
248 * This is followed by one binary header (RD_hdr_t) and one RD_packet_t
249 * structure for each received packet. All fields are in network byte
250 * order. We don't need the source IP address since we can do mapping
251 * based on SSRC. This saves (a little) space, avoids non-IPv4
252 * problems and privacy/security concerns. The header is followed by
253 * the RTP/RTCP header and (optionally) the actual payload.
256 static const char *PAYLOAD_UNKNOWN_STR
= "Unknown";
258 static void update_payload_names(rtpstream_info_t
*stream_info
, const struct _rtp_info
*rtpinfo
)
260 GString
*payload_type_names
;
261 const char *new_payload_type_str
;
263 /* Ensure that we have non empty payload_type_str */
264 if (rtpinfo
->info_payload_type_str
!= NULL
) {
265 new_payload_type_str
= rtpinfo
->info_payload_type_str
;
268 /* String is created from const strings only */
269 new_payload_type_str
= val_to_str_ext_const(rtpinfo
->info_payload_type
,
270 &rtp_payload_type_short_vals_ext
,
274 stream_info
->payload_type_names
[rtpinfo
->info_payload_type
] = new_payload_type_str
;
276 /* Join all existing payload names to one string */
277 payload_type_names
= g_string_sized_new(40); /* Preallocate memory */
278 for(int i
=0; i
<256; i
++) {
279 if (stream_info
->payload_type_names
[i
] != NULL
) {
280 if (payload_type_names
->len
> 0) {
281 g_string_append(payload_type_names
, ", ");
283 g_string_append(payload_type_names
, stream_info
->payload_type_names
[i
]);
286 if (stream_info
->all_payload_type_names
!= NULL
) {
287 g_free(stream_info
->all_payload_type_names
);
289 stream_info
->all_payload_type_names
= g_string_free(payload_type_names
, FALSE
);
292 bool rtpstream_is_payload_used(const rtpstream_info_t
*stream_info
, const uint8_t payload_type
)
294 return stream_info
->payload_type_names
[payload_type
] != NULL
;
297 #define RTPFILE_VERSION "1.0"
300 * Write a header to the current output file.
301 * The header consists of an identifying string, followed
302 * by a binary structure.
304 void rtp_write_header(rtpstream_info_t
*strinfo
, FILE *file
)
306 uint32_t start_sec
; /* start of recording (GMT) (seconds) */
307 uint32_t start_usec
; /* start of recording (GMT) (microseconds)*/
308 uint32_t source
; /* network source (multicast address) */
310 uint16_t port
; /* UDP port */
311 uint16_t padding
; /* 2 padding bytes */
312 char* addr_str
= address_to_display(NULL
, &(strinfo
->id
.dst_addr
));
314 fprintf(file
, "#!rtpplay%s %s/%u\n", RTPFILE_VERSION
,
316 strinfo
->id
.dst_port
);
317 wmem_free(NULL
, addr_str
);
319 start_sec
= g_htonl(strinfo
->start_fd
->abs_ts
.secs
);
320 start_usec
= g_htonl(strinfo
->start_fd
->abs_ts
.nsecs
/ 1000);
321 /* rtpdump only accepts uint32_t as source, will be fake for IPv6 */
322 memset(&source
, 0, sizeof source
);
323 sourcelen
= strinfo
->id
.src_addr
.len
;
324 if (sourcelen
> sizeof source
)
325 sourcelen
= sizeof source
;
326 memcpy(&source
, strinfo
->id
.src_addr
.data
, sourcelen
);
327 port
= g_htons(strinfo
->id
.src_port
);
330 if (fwrite(&start_sec
, 4, 1, file
) == 0)
332 if (fwrite(&start_usec
, 4, 1, file
) == 0)
334 if (fwrite(&source
, 4, 1, file
) == 0)
336 if (fwrite(&port
, 2, 1, file
) == 0)
338 if (fwrite(&padding
, 2, 1, file
) == 0)
342 /* utility function for writing a sample to file in rtpdump -F dump format (.rtp)*/
343 static void rtp_write_sample(rtpdump_info_t
* rtpdump_info
, FILE* file
)
345 uint16_t length
; /* length of packet, including this header (may
346 be smaller than plen if not whole packet recorded) */
347 uint16_t plen
; /* actual header+payload length for RTP, 0 for RTCP */
348 uint32_t offset
; /* milliseconds since the start of recording */
350 length
= g_htons(rtpdump_info
->num_samples
+ 8);
351 plen
= g_htons(rtpdump_info
->num_samples
);
352 offset
= g_htonl(rtpdump_info
->rec_time
);
354 if (fwrite(&length
, 2, 1, file
) == 0)
356 if (fwrite(&plen
, 2, 1, file
) == 0)
358 if (fwrite(&offset
, 4, 1, file
) == 0)
360 if (fwrite(rtpdump_info
->samples
, rtpdump_info
->num_samples
, 1, file
) == 0)
365 /****************************************************************************/
366 /* whenever a RTP packet is seen by the tap listener */
367 tap_packet_status
rtpstream_packet_cb(void *arg
, packet_info
*pinfo
, epan_dissect_t
*edt _U_
, const void *arg2
, tap_flags_t flags _U_
)
369 rtpstream_tapinfo_t
*tapinfo
= (rtpstream_tapinfo_t
*)arg
;
370 const struct _rtp_info
*rtpinfo
= (const struct _rtp_info
*)arg2
;
371 rtpstream_id_t new_stream_id
;
372 rtpstream_info_t
*stream_info
= NULL
;
373 rtpdump_info_t rtpdump_info
;
375 /* gather infos on the stream this packet is part of.
376 * Shallow copy addresses as this is just for examination. */
377 rtpstream_id_copy_pinfo_shallow(pinfo
,&new_stream_id
,false);
378 new_stream_id
.ssrc
= rtpinfo
->info_sync_src
;
380 if (tapinfo
->mode
== TAP_ANALYSE
) {
381 /* if display filtering activated and packet do not match, ignore it */
382 if (tapinfo
->apply_display_filter
&& (pinfo
->fd
->passed_dfilter
== 0)) {
383 return TAP_PACKET_DONT_REDRAW
;
386 /* check whether we already have a stream with these parameters in the list */
387 if (tapinfo
->strinfo_hash
) {
388 stream_info
= rtpstream_info_multihash_lookup(tapinfo
->strinfo_hash
, &new_stream_id
);
391 /* not in the list? then create a new entry */
393 /* init info and collect id */
394 stream_info
= rtpstream_info_malloc_and_init();
395 /* Deep copy addresses for the new entry. */
396 rtpstream_id_copy_pinfo(pinfo
,&(stream_info
->id
),false);
397 stream_info
->id
.ssrc
= rtpinfo
->info_sync_src
;
399 /* init counters for first packet */
400 rtpstream_info_analyse_init(stream_info
, pinfo
, rtpinfo
);
403 tapinfo
->strinfo_list
= g_list_prepend(tapinfo
->strinfo_list
, stream_info
);
404 if (!tapinfo
->strinfo_hash
) {
405 tapinfo
->strinfo_hash
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
407 rtpstream_info_multihash_insert(tapinfo
->strinfo_hash
, stream_info
);
410 /* update analysis counters */
411 rtpstream_info_analyse_process(stream_info
, pinfo
, rtpinfo
);
413 /* increment the packets counter of all streams */
414 ++(tapinfo
->npackets
);
416 return TAP_PACKET_REDRAW
; /* refresh output */
418 else if (tapinfo
->mode
== TAP_SAVE
) {
419 if (rtpstream_id_equal(&new_stream_id
, &(tapinfo
->filter_stream_fwd
->id
), RTPSTREAM_ID_EQUAL_SSRC
)) {
420 /* XXX - what if rtpinfo->info_all_data_present is
421 false, so that we don't *have* all the data? */
422 rtpdump_info
.rec_time
= nstime_to_msec(&pinfo
->abs_ts
) -
423 nstime_to_msec(&tapinfo
->filter_stream_fwd
->start_fd
->abs_ts
);
424 rtpdump_info
.num_samples
= rtpinfo
->info_data_len
;
425 rtpdump_info
.samples
= rtpinfo
->info_data
;
426 rtp_write_sample(&rtpdump_info
, tapinfo
->save_file
);
429 else if (tapinfo
->mode
== TAP_MARK
&& tapinfo
->tap_mark_packet
) {
430 if (rtpstream_id_equal(&new_stream_id
, &(tapinfo
->filter_stream_fwd
->id
), RTPSTREAM_ID_EQUAL_SSRC
)
431 || rtpstream_id_equal(&new_stream_id
, &(tapinfo
->filter_stream_rev
->id
), RTPSTREAM_ID_EQUAL_SSRC
))
433 tapinfo
->tap_mark_packet(tapinfo
, pinfo
->fd
);
436 return TAP_PACKET_DONT_REDRAW
;
439 /****************************************************************************/
440 /* evaluate rtpstream_info_t calculations */
441 /* - code is gathered from existing GTK/Qt/tui sources related to RTP statistics calculation
442 * - one place for calculations ensures that all wireshark tools shows same output for same input and avoids code duplication
444 void rtpstream_info_calculate(const rtpstream_info_t
*strinfo
, rtpstream_info_calc_t
*calc
)
450 double clock_drift_x
;
451 uint32_t clock_rate_x
;
454 calc
->src_addr_str
= address_to_display(NULL
, &(strinfo
->id
.src_addr
));
455 calc
->src_port
= strinfo
->id
.src_port
;
456 calc
->dst_addr_str
= address_to_display(NULL
, &(strinfo
->id
.dst_addr
));
457 calc
->dst_port
= strinfo
->id
.dst_port
;
458 calc
->ssrc
= strinfo
->id
.ssrc
;
460 calc
->all_payload_type_names
= wmem_strdup(NULL
, strinfo
->all_payload_type_names
);
462 calc
->packet_count
= strinfo
->packet_count
;
463 /* packet count, lost packets */
464 calc
->packet_expected
= strinfo
->rtp_stats
.stop_seq_nr
- strinfo
->rtp_stats
.start_seq_nr
+ 1;
465 calc
->total_nr
= strinfo
->rtp_stats
.total_nr
;
466 calc
->lost_num
= calc
->packet_expected
- strinfo
->rtp_stats
.total_nr
;
467 if (calc
->packet_expected
) {
468 calc
->lost_perc
= (double)(calc
->lost_num
*100)/(double)calc
->packet_expected
;
473 calc
->max_delta
= strinfo
->rtp_stats
.max_delta
;
474 calc
->min_delta
= strinfo
->rtp_stats
.min_delta
;
475 calc
->mean_delta
= strinfo
->rtp_stats
.mean_delta
;
476 calc
->min_jitter
= strinfo
->rtp_stats
.min_jitter
;
477 calc
->max_jitter
= strinfo
->rtp_stats
.max_jitter
;
478 calc
->mean_jitter
= strinfo
->rtp_stats
.mean_jitter
;
479 calc
->max_skew
= strinfo
->rtp_stats
.max_skew
;
480 calc
->problem
= strinfo
->problem
;
481 sumt
= strinfo
->rtp_stats
.sumt
;
482 sumTS
= strinfo
->rtp_stats
.sumTS
;
483 sumt2
= strinfo
->rtp_stats
.sumt2
;
484 sumtTS
= strinfo
->rtp_stats
.sumtTS
;
485 duration_x
= strinfo
->rtp_stats
.time
- strinfo
->rtp_stats
.start_time
;
487 if ((calc
->packet_count
>0) && (sumt2
> 0)) {
488 clock_drift_x
= (calc
->packet_count
* sumtTS
- sumt
* sumTS
) / (calc
->packet_count
* sumt2
- sumt
* sumt
);
489 calc
->clock_drift_ms
= duration_x
* (clock_drift_x
- 1.0);
490 clock_rate_x
= (uint32_t)(strinfo
->rtp_stats
.clock_rate
* clock_drift_x
);
491 calc
->freq_drift_hz
= clock_drift_x
* clock_rate_x
;
492 calc
->freq_drift_perc
= 100.0 * (clock_drift_x
- 1.0);
494 calc
->clock_drift_ms
= 0.0;
495 calc
->freq_drift_hz
= 0.0;
496 calc
->freq_drift_perc
= 0.0;
498 calc
->duration_ms
= duration_x
/ 1000.0;
499 calc
->sequence_err
= strinfo
->rtp_stats
.sequence
;
500 calc
->start_time_ms
= strinfo
->rtp_stats
.start_time
/ 1000.0;
501 calc
->first_packet_num
= strinfo
->rtp_stats
.first_packet_num
;
502 calc
->last_packet_num
= strinfo
->rtp_stats
.max_nr
;
505 /****************************************************************************/
506 /* free rtpstream_info_calc_t structure (internal items) */
507 void rtpstream_info_calc_free(rtpstream_info_calc_t
*calc
)
509 wmem_free(NULL
, calc
->src_addr_str
);
510 wmem_free(NULL
, calc
->dst_addr_str
);
511 wmem_free(NULL
, calc
->all_payload_type_names
);
514 /****************************************************************************/
515 /* Init analyse counters in rtpstream_info_t from pinfo */
516 void rtpstream_info_analyse_init(rtpstream_info_t
*stream_info
, const packet_info
*pinfo
, const struct _rtp_info
*rtpinfo
)
518 struct _rtp_packet_info
*p_packet_data
= NULL
;
520 /* reset stream stats */
521 stream_info
->first_payload_type
= rtpinfo
->info_payload_type
;
522 stream_info
->first_payload_type_name
= rtpinfo
->info_payload_type_str
;
523 stream_info
->start_fd
= pinfo
->fd
;
524 stream_info
->start_rel_time
= pinfo
->rel_ts
;
525 stream_info
->start_abs_time
= pinfo
->abs_ts
;
527 /* reset RTP stats */
528 stream_info
->rtp_stats
.first_packet
= true;
529 stream_info
->rtp_stats
.reg_pt
= PT_UNDEFINED
;
531 /* Get the Setup frame number who set this RTP stream */
532 p_packet_data
= (struct _rtp_packet_info
*)p_get_proto_data(wmem_file_scope(), (packet_info
*)pinfo
, proto_get_id_by_filter_name("rtp"), RTP_CONVERSATION_PROTO_DATA
);
534 stream_info
->setup_frame_number
= p_packet_data
->frame_number
;
536 stream_info
->setup_frame_number
= 0xFFFFFFFF;
539 /****************************************************************************/
540 /* Update analyse counters in rtpstream_info_t from pinfo */
541 void rtpstream_info_analyse_process(rtpstream_info_t
*stream_info
, const packet_info
*pinfo
, const struct _rtp_info
*rtpinfo
)
543 /* get RTP stats for the packet */
544 rtppacket_analyse(&(stream_info
->rtp_stats
), pinfo
, rtpinfo
);
545 if (stream_info
->payload_type_names
[rtpinfo
->info_payload_type
] == NULL
) {
546 update_payload_names(stream_info
, rtpinfo
);
549 if (stream_info
->rtp_stats
.flags
& STAT_FLAG_WRONG_TIMESTAMP
550 || stream_info
->rtp_stats
.flags
& STAT_FLAG_WRONG_SEQ
)
551 stream_info
->problem
= true;
553 /* increment the packets counter for this stream */
554 ++(stream_info
->packet_count
);
555 stream_info
->stop_rel_time
= pinfo
->rel_ts
;
558 /****************************************************************************/
559 /* Get hash for rtpstream_info_t */
560 unsigned rtpstream_to_hash(const void *key
)
563 return rtpstream_id_to_hash(&((rtpstream_info_t
*)key
)->id
);
569 /****************************************************************************/
570 /* Inserts new_stream_info to multihash if its not there */
572 void rtpstream_info_multihash_insert(GHashTable
*multihash
, rtpstream_info_t
*new_stream_info
)
574 GList
*hlist
= (GList
*)g_hash_table_lookup(multihash
, GINT_TO_POINTER(rtpstream_to_hash(new_stream_info
)));
577 // Key exists in hash
578 GList
*list
= g_list_first(hlist
);
581 if (rtpstream_id_equal(&(new_stream_info
->id
), &((rtpstream_info_t
*)(list
->data
))->id
, RTPSTREAM_ID_EQUAL_SSRC
)) {
585 list
= g_list_next(list
);
588 // stream_info is not in list yet, add it
589 hlist
= g_list_prepend(hlist
, new_stream_info
);
592 // No key in hash, init new list
593 hlist
= g_list_prepend(hlist
, new_stream_info
);
595 g_hash_table_insert(multihash
, GINT_TO_POINTER(rtpstream_to_hash(new_stream_info
)), hlist
);
598 /****************************************************************************/
599 /* Lookup stream_info in multihash */
601 rtpstream_info_t
*rtpstream_info_multihash_lookup(GHashTable
*multihash
, rtpstream_id_t
*stream_id
)
603 GList
*hlist
= (GList
*)g_hash_table_lookup(multihash
, GINT_TO_POINTER(rtpstream_to_hash(stream_id
)));
605 // Key exists in hash
606 GList
*list
= g_list_first(hlist
);
609 if (rtpstream_id_equal(stream_id
, &((rtpstream_info_t
*)(list
->data
))->id
, RTPSTREAM_ID_EQUAL_SSRC
)) {
610 return (rtpstream_info_t
*)(list
->data
);
612 list
= g_list_next(list
);
616 // No stream_info in hash or was not found in existing list
620 /****************************************************************************/
621 /* Destroys GList used in multihash */
623 void rtpstream_info_multihash_destroy_value(void *key _U_
, void *value
, void *user_data _U_
)
625 g_list_free((GList
*)value
);