Coverage Report

Created: 2026-07-25 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavformat/rdt.c
Line
Count
Source
1
/*
2
 * Realmedia RTSP protocol (RDT) support.
3
 * Copyright (c) 2007 Ronald S. Bultje
4
 *
5
 * This file is part of FFmpeg.
6
 *
7
 * FFmpeg is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with FFmpeg; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
 */
21
22
/**
23
 * @file
24
 * @brief Realmedia RTSP protocol (RDT) support
25
 * @author Ronald S. Bultje <rbultje@ronald.bitfreak.net>
26
 */
27
28
#include "avformat.h"
29
#include "libavutil/avstring.h"
30
#include "libavutil/mem.h"
31
#include "demux.h"
32
#include "rtpdec.h"
33
#include "rdt.h"
34
#include "libavutil/base64.h"
35
#include "libavutil/md5.h"
36
#include "rm.h"
37
#include "internal.h"
38
#include "avio_internal.h"
39
#include "libavcodec/get_bits.h"
40
41
struct RDTDemuxContext {
42
    AVFormatContext *ic; /**< the containing (RTSP) demux context */
43
    /** Each RDT stream-set (represented by one RTSPStream) can contain
44
     * multiple streams (of the same content, but with possibly different
45
     * codecs/bitrates). Each such stream is represented by one AVStream
46
     * in the AVFormatContext, and this variable points to the offset in
47
     * that array such that the first is the first stream of this set. */
48
    AVStream **streams;
49
    int n_streams; /**< streams with identical content in this set */
50
    void *dynamic_protocol_context;
51
    DynamicPayloadPacketHandlerProc parse_packet;
52
    uint32_t prev_timestamp;
53
    int prev_set_id, prev_stream_id;
54
};
55
56
RDTDemuxContext *
57
ff_rdt_parse_open(AVFormatContext *ic, int first_stream_of_set_idx,
58
                  void *priv_data, const RTPDynamicProtocolHandler *handler)
59
0
{
60
0
    RDTDemuxContext *s = av_mallocz(sizeof(RDTDemuxContext));
61
0
    if (!s)
62
0
        return NULL;
63
64
0
    s->ic = ic;
65
0
    s->streams = &ic->streams[first_stream_of_set_idx];
66
0
    do {
67
0
        s->n_streams++;
68
0
    } while (first_stream_of_set_idx + s->n_streams < ic->nb_streams &&
69
0
             s->streams[s->n_streams]->id == s->streams[0]->id);
70
0
    s->prev_set_id    = -1;
71
0
    s->prev_stream_id = -1;
72
0
    s->prev_timestamp = -1;
73
0
    s->parse_packet = handler ? handler->parse_packet : NULL;
74
0
    s->dynamic_protocol_context = priv_data;
75
76
0
    return s;
77
0
}
78
79
void
80
ff_rdt_parse_close(RDTDemuxContext *s)
81
0
{
82
0
    av_free(s);
83
0
}
84
85
struct PayloadContext {
86
    AVFormatContext *rmctx;
87
    int nb_rmst;
88
    RMStream **rmst;
89
    uint8_t *mlti_data;
90
    unsigned int mlti_data_size;
91
    char buffer[RTP_MAX_PACKET_LENGTH + AV_INPUT_BUFFER_PADDING_SIZE];
92
    int audio_pkt_cnt; /**< remaining audio packets in rmdec */
93
};
94
95
void
96
ff_rdt_calc_response_and_checksum(char response[41], char chksum[9],
97
                                  const char *challenge)
98
0
{
99
0
    int ch_len = strlen (challenge), i;
100
0
    unsigned char zres[16],
101
0
        buf[64] = { 0xa1, 0xe9, 0x14, 0x9d, 0x0e, 0x6b, 0x3b, 0x59 };
102
0
#define XOR_TABLE_SIZE 37
103
0
    static const unsigned char xor_table[XOR_TABLE_SIZE] = {
104
0
        0x05, 0x18, 0x74, 0xd0, 0x0d, 0x09, 0x02, 0x53,
105
0
        0xc0, 0x01, 0x05, 0x05, 0x67, 0x03, 0x19, 0x70,
106
0
        0x08, 0x27, 0x66, 0x10, 0x10, 0x72, 0x08, 0x09,
107
0
        0x63, 0x11, 0x03, 0x71, 0x08, 0x08, 0x70, 0x02,
108
0
        0x10, 0x57, 0x05, 0x18, 0x54 };
109
110
    /* some (length) checks */
111
0
    if (ch_len == 40) /* what a hack... */
112
0
        ch_len = 32;
113
0
    else if (ch_len > 56)
114
0
        ch_len = 56;
115
0
    memcpy(buf + 8, challenge, ch_len);
116
117
    /* xor challenge bytewise with xor_table */
118
0
    for (i = 0; i < XOR_TABLE_SIZE; i++)
119
0
        buf[8 + i] ^= xor_table[i];
120
121
0
    av_md5_sum(zres, buf, 64);
122
0
    ff_data_to_hex(response, zres, 16, 1);
123
124
    /* add tail */
125
0
    strcpy (response + 32, "01d0a8e3");
126
127
    /* calculate checksum */
128
0
    for (i = 0; i < 8; i++)
129
0
        chksum[i] = response[i * 4];
130
0
    chksum[8] = 0;
131
0
}
132
133
static int
134
rdt_load_mdpr (PayloadContext *rdt, AVStream *st, int rule_nr)
135
0
{
136
0
    FFIOContext pb0;
137
0
    AVIOContext *const pb = &pb0.pub;
138
0
    unsigned int size;
139
0
    uint32_t tag;
140
141
    /**
142
     * Layout of the MLTI chunk:
143
     * 4: MLTI
144
     * 2: number of streams
145
     * Then for each stream ([number_of_streams] times):
146
     *     2: mdpr index
147
     * 2: number of mdpr chunks
148
     * Then for each mdpr chunk ([number_of_mdpr_chunks] times):
149
     *     4: size
150
     *     [size]: data
151
     * we skip MDPR chunks until we reach the one of the stream
152
     * we're interested in, and forward that ([size]+[data]) to
153
     * the RM demuxer to parse the stream-specific header data.
154
     */
155
0
    if (!rdt->mlti_data)
156
0
        return -1;
157
0
    ffio_init_read_context(&pb0, rdt->mlti_data, rdt->mlti_data_size);
158
0
    tag = avio_rl32(pb);
159
0
    if (tag == MKTAG('M', 'L', 'T', 'I')) {
160
0
        int num, chunk_nr;
161
162
        /* read index of MDPR chunk numbers */
163
0
        num = avio_rb16(pb);
164
0
        if (rule_nr < 0 || rule_nr >= num)
165
0
            return -1;
166
0
        avio_skip(pb, rule_nr * 2);
167
0
        chunk_nr = avio_rb16(pb);
168
0
        avio_skip(pb, (num - 1 - rule_nr) * 2);
169
170
        /* read MDPR chunks */
171
0
        num = avio_rb16(pb);
172
0
        if (chunk_nr >= num)
173
0
            return -1;
174
0
        while (chunk_nr--)
175
0
            avio_skip(pb, avio_rb32(pb));
176
0
        size = avio_rb32(pb);
177
0
    } else {
178
0
        size = rdt->mlti_data_size;
179
0
        avio_seek(pb, 0, SEEK_SET);
180
0
    }
181
0
    if (ff_rm_read_mdpr_codecdata(rdt->rmctx, pb, st, rdt->rmst[st->index], size, NULL) < 0)
182
0
        return -1;
183
184
0
    return 0;
185
0
}
186
187
/**
188
 * Actual data handling.
189
 */
190
191
int
192
ff_rdt_parse_header(const uint8_t *buf, int len,
193
                    int *pset_id, int *pseq_no, int *pstream_id,
194
                    int *pis_keyframe, uint32_t *ptimestamp)
195
0
{
196
0
    GetBitContext gb;
197
0
    int consumed = 0, set_id, seq_no, stream_id, is_keyframe,
198
0
        len_included, need_reliable;
199
0
    uint32_t timestamp;
200
201
    /* skip status packets */
202
0
    while (len >= 5 && buf[1] == 0xFF /* status packet */) {
203
0
        int pkt_len;
204
205
0
        if (!(buf[0] & 0x80))
206
0
            return -1; /* not followed by a data packet */
207
208
0
        pkt_len = AV_RB16(buf+3);
209
0
        if (pkt_len > len)
210
0
            return AVERROR_INVALIDDATA;
211
0
        buf += pkt_len;
212
0
        len -= pkt_len;
213
0
        consumed += pkt_len;
214
0
    }
215
0
    if (len < 16)
216
0
        return -1;
217
    /**
218
     * Layout of the header (in bits):
219
     * 1:  len_included
220
     *     Flag indicating whether this header includes a length field;
221
     *     this can be used to concatenate multiple RDT packets in a
222
     *     single UDP/TCP data frame and is used to precede RDT data
223
     *     by stream status packets
224
     * 1:  need_reliable
225
     *     Flag indicating whether this header includes a "reliable
226
     *     sequence number"; these are apparently sequence numbers of
227
     *     data packets alone. For data packets, this flag is always
228
     *     set, according to the Real documentation [1]
229
     * 5:  set_id
230
     *     ID of a set of streams of identical content, possibly with
231
     *     different codecs or bitrates
232
     * 1:  is_reliable
233
     *     Flag set for certain streams deemed less tolerable for packet
234
     *     loss
235
     * 16: seq_no
236
     *     Packet sequence number; if >=0xFF00, this is a non-data packet
237
     *     containing stream status info, the second byte indicates the
238
     *     type of status packet (see wireshark docs / source code [2])
239
     * if (len_included) {
240
     *     16: packet_len
241
     * } else {
242
     *     packet_len = remainder of UDP/TCP frame
243
     * }
244
     * 1:  is_back_to_back
245
     *     Back-to-Back flag; used for timing, set for one in every 10
246
     *     packets, according to the Real documentation [1]
247
     * 1:  is_slow_data
248
     *     Slow-data flag; currently unused, according to Real docs [1]
249
     * 5:  stream_id
250
     *     ID of the stream within this particular set of streams
251
     * 1:  is_no_keyframe
252
     *     Non-keyframe flag (unset if packet belongs to a keyframe)
253
     * 32: timestamp (PTS)
254
     * if (set_id == 0x1F) {
255
     *     16: set_id (extended set-of-streams ID; see set_id)
256
     * }
257
     * if (need_reliable) {
258
     *     16: reliable_seq_no
259
     *         Reliable sequence number (see need_reliable)
260
     * }
261
     * if (stream_id == 0x3F) {
262
     *     16: stream_id (extended stream ID; see stream_id)
263
     * }
264
     * [1] https://protocol.helixcommunity.org/files/2005/devdocs/RDT_Feature_Level_20.txt
265
     * [2] http://www.wireshark.org/docs/dfref/r/rdt.html and
266
     *     http://anonsvn.wireshark.org/viewvc/trunk/epan/dissectors/packet-rdt.c
267
     */
268
0
    init_get_bits(&gb, buf, len << 3);
269
0
    len_included  = get_bits1(&gb);
270
0
    need_reliable = get_bits1(&gb);
271
0
    set_id        = get_bits(&gb, 5);
272
0
    skip_bits(&gb, 1);
273
0
    seq_no        = get_bits(&gb, 16);
274
0
    if (len_included)
275
0
        skip_bits(&gb, 16);
276
0
    skip_bits(&gb, 2);
277
0
    stream_id     = get_bits(&gb, 5);
278
0
    is_keyframe   = !get_bits1(&gb);
279
0
    timestamp     = get_bits_long(&gb, 32);
280
0
    if (set_id == 0x1f)
281
0
        set_id    = get_bits(&gb, 16);
282
0
    if (need_reliable)
283
0
        skip_bits(&gb, 16);
284
0
    if (stream_id == 0x1f)
285
0
        stream_id = get_bits(&gb, 16);
286
287
0
    if (pset_id)      *pset_id      = set_id;
288
0
    if (pseq_no)      *pseq_no      = seq_no;
289
0
    if (pstream_id)   *pstream_id   = stream_id;
290
0
    if (pis_keyframe) *pis_keyframe = is_keyframe;
291
0
    if (ptimestamp)   *ptimestamp   = timestamp;
292
293
0
    return consumed + (get_bits_count(&gb) >> 3);
294
0
}
295
296
/** return 0 on packet, no more left, 1 on packet, 1 on partial packet... */
297
static int
298
rdt_parse_packet (AVFormatContext *ctx, PayloadContext *rdt, AVStream *st,
299
                  AVPacket *pkt, uint32_t *timestamp,
300
                  const uint8_t *buf, int len, uint16_t rtp_seq, int flags)
301
0
{
302
0
    int seq = 1, res;
303
304
0
    if (rdt->audio_pkt_cnt == 0) {
305
0
        FFIOContext pb;
306
0
        int pos, rmflags;
307
308
0
        ffio_init_read_context(&pb, buf, len);
309
0
        rmflags = (flags & RTP_FLAG_KEY) ? 2 : 0;
310
0
        res = ff_rm_parse_packet(rdt->rmctx, &pb.pub, st, rdt->rmst[st->index],
311
0
                                 len, pkt, &seq, rmflags, *timestamp);
312
0
        pos = avio_tell(&pb.pub);
313
0
        if (res < 0)
314
0
            return res;
315
0
        if (res > 0) {
316
0
            if (st->codecpar->codec_id == AV_CODEC_ID_AAC) {
317
0
                memcpy (rdt->buffer, buf + pos, len - pos);
318
0
                rdt->rmctx->pb = avio_alloc_context (rdt->buffer, len - pos, 0,
319
0
                                                    NULL, NULL, NULL, NULL);
320
0
            }
321
0
            goto get_cache;
322
0
        }
323
0
    } else {
324
0
get_cache:
325
0
        rdt->audio_pkt_cnt =
326
0
            ff_rm_retrieve_cache (rdt->rmctx, rdt->rmctx->pb,
327
0
                                  st, rdt->rmst[st->index], pkt);
328
0
        if (rdt->audio_pkt_cnt == 0 &&
329
0
            st->codecpar->codec_id == AV_CODEC_ID_AAC)
330
0
            avio_context_free(&rdt->rmctx->pb);
331
0
    }
332
0
    pkt->stream_index = st->index;
333
0
    pkt->pts = *timestamp;
334
335
0
    return rdt->audio_pkt_cnt > 0;
336
0
}
337
338
int
339
ff_rdt_parse_packet(RDTDemuxContext *s, AVPacket *pkt,
340
                    uint8_t **bufptr, int len)
341
0
{
342
0
    uint8_t *buf = bufptr ? *bufptr : NULL;
343
0
    int seq_no, flags = 0, stream_id, set_id, is_keyframe;
344
0
    uint32_t timestamp;
345
0
    int rv= 0;
346
347
0
    if (!s->parse_packet)
348
0
        return -1;
349
350
0
    if (!buf && s->prev_stream_id != -1) {
351
        /* return the next packets, if any */
352
0
        timestamp= 0; ///< Should not be used if buf is NULL, but should be set to the timestamp of the packet returned....
353
0
        rv= s->parse_packet(s->ic, s->dynamic_protocol_context,
354
0
                            s->streams[s->prev_stream_id],
355
0
                            pkt, &timestamp, NULL, 0, 0, flags);
356
0
        return rv;
357
0
    }
358
359
0
    if (len < 12)
360
0
        return -1;
361
0
    rv = ff_rdt_parse_header(buf, len, &set_id, &seq_no, &stream_id, &is_keyframe, &timestamp);
362
0
    if (rv < 0)
363
0
        return rv;
364
0
    if (is_keyframe &&
365
0
        (set_id != s->prev_set_id || timestamp != s->prev_timestamp ||
366
0
         stream_id != s->prev_stream_id)) {
367
0
        flags |= RTP_FLAG_KEY;
368
0
        s->prev_set_id    = set_id;
369
0
        s->prev_timestamp = timestamp;
370
0
    }
371
0
    s->prev_stream_id = stream_id;
372
0
    buf += rv;
373
0
    len -= rv;
374
375
0
     if (s->prev_stream_id >= s->n_streams) {
376
0
         s->prev_stream_id = -1;
377
0
         return -1;
378
0
     }
379
380
0
    rv = s->parse_packet(s->ic, s->dynamic_protocol_context,
381
0
                         s->streams[s->prev_stream_id],
382
0
                         pkt, &timestamp, buf, len, 0, flags);
383
384
0
    return rv;
385
0
}
386
387
void
388
ff_rdt_subscribe_rule (char *cmd, int size,
389
                       int stream_nr, int rule_nr)
390
0
{
391
0
    av_strlcatf(cmd, size, "stream=%d;rule=%d,stream=%d;rule=%d",
392
0
                stream_nr, rule_nr * 2, stream_nr, rule_nr * 2 + 1);
393
0
}
394
395
static unsigned char *
396
rdt_parse_b64buf (unsigned int *target_len, const char *p)
397
0
{
398
0
    unsigned char *target;
399
0
    int len = strlen(p);
400
0
    if (*p == '\"') {
401
0
        p++;
402
0
        len -= 2; /* skip embracing " at start/end */
403
0
    }
404
0
    *target_len = len * 3 / 4;
405
0
    target = av_mallocz(*target_len + AV_INPUT_BUFFER_PADDING_SIZE);
406
0
    if (!target)
407
0
        return NULL;
408
0
    av_base64_decode(target, p, *target_len);
409
0
    return target;
410
0
}
411
412
static int
413
rdt_parse_sdp_line (AVFormatContext *s, int st_index,
414
                    PayloadContext *rdt, const char *line)
415
0
{
416
0
    AVStream *stream = s->streams[st_index];
417
0
    const char *p = line;
418
419
0
    if (av_strstart(p, "OpaqueData:buffer;", &p)) {
420
0
        rdt->mlti_data = rdt_parse_b64buf(&rdt->mlti_data_size, p);
421
0
    } else if (av_strstart(p, "StartTime:integer;", &p))
422
0
        ffstream(stream)->first_dts = atoi(p);
423
0
    else if (av_strstart(p, "ASMRuleBook:string;", &p)) {
424
0
        int n, first = -1;
425
426
0
        for (n = 0; n < s->nb_streams; n++)
427
0
            if (s->streams[n]->id == stream->id) {
428
0
                int count = s->streams[n]->index + 1, err;
429
0
                if (first == -1) first = n;
430
0
                if (rdt->nb_rmst < count) {
431
0
                    if ((err = av_reallocp(&rdt->rmst,
432
0
                                           count * sizeof(*rdt->rmst))) < 0) {
433
0
                        rdt->nb_rmst = 0;
434
0
                        return err;
435
0
                    }
436
0
                    memset(rdt->rmst + rdt->nb_rmst, 0,
437
0
                           (count - rdt->nb_rmst) * sizeof(*rdt->rmst));
438
0
                    rdt->nb_rmst = count;
439
0
                }
440
0
                rdt->rmst[s->streams[n]->index] = ff_rm_alloc_rmstream();
441
0
                if (!rdt->rmst[s->streams[n]->index])
442
0
                    return AVERROR(ENOMEM);
443
0
                rdt_load_mdpr(rdt, s->streams[n], (n - first) * 2);
444
0
           }
445
0
    }
446
447
0
    return 0;
448
0
}
449
450
static void
451
real_parse_asm_rule(AVStream *st, const char *p, const char *end)
452
0
{
453
0
    do {
454
        /* can be either averagebandwidth= or AverageBandwidth= */
455
0
        if (sscanf(p, " %*1[Aa]verage%*1[Bb]andwidth=%"SCNd64, &st->codecpar->bit_rate) == 1)
456
0
            break;
457
0
        if (!(p = strchr(p, ',')) || p > end)
458
0
            p = end;
459
0
        p++;
460
0
    } while (p < end);
461
0
}
462
463
static AVStream *
464
add_dstream(AVFormatContext *s, AVStream *orig_st)
465
0
{
466
0
    AVStream *st;
467
468
0
    if (!(st = avformat_new_stream(s, NULL)))
469
0
        return NULL;
470
0
    st->id = orig_st->id;
471
0
    st->codecpar->codec_type = orig_st->codecpar->codec_type;
472
0
    ffstream(st)->first_dts  = ffstream(orig_st)->first_dts;
473
474
0
    return st;
475
0
}
476
477
static void
478
real_parse_asm_rulebook(AVFormatContext *s, AVStream *orig_st,
479
                        const char *p)
480
0
{
481
0
    const char *end;
482
0
    int n_rules = 0, odd = 0;
483
0
    AVStream *st;
484
485
    /**
486
     * The ASMRuleBook contains a list of comma-separated strings per rule,
487
     * and each rule is separated by a ;. The last one also has a ; at the
488
     * end so we can use it as delimiter.
489
     * Every rule occurs twice, once for when the RTSP packet header marker
490
     * is set and once for if it isn't. We only read the first because we
491
     * don't care much (that's what the "odd" variable is for).
492
     * Each rule contains a set of one or more statements, optionally
493
     * preceded by a single condition. If there's a condition, the rule
494
     * starts with a '#'. Multiple conditions are merged between brackets,
495
     * so there are never multiple conditions spread out over separate
496
     * statements. Generally, these conditions are bitrate limits (min/max)
497
     * for multi-bitrate streams.
498
     */
499
0
    if (*p == '\"') p++;
500
0
    while (1) {
501
0
        if (!(end = strchr(p, ';')))
502
0
            break;
503
0
        if (!odd && end != p) {
504
0
            if (n_rules > 0)
505
0
                st = add_dstream(s, orig_st);
506
0
            else
507
0
                st = orig_st;
508
0
            if (!st)
509
0
                break;
510
0
            real_parse_asm_rule(st, p, end);
511
0
            n_rules++;
512
0
        }
513
0
        p = end + 1;
514
0
        odd ^= 1;
515
0
    }
516
0
}
517
518
void
519
ff_real_parse_sdp_a_line (AVFormatContext *s, int stream_index,
520
                          const char *line)
521
0
{
522
0
    const char *p = line;
523
524
0
    if (av_strstart(p, "ASMRuleBook:string;", &p))
525
0
        real_parse_asm_rulebook(s, s->streams[stream_index], p);
526
0
}
527
528
529
530
static av_cold int rdt_init(AVFormatContext *s, int st_index, PayloadContext *rdt)
531
0
{
532
0
    int ret;
533
534
0
    rdt->rmctx = avformat_alloc_context();
535
0
    if (!rdt->rmctx)
536
0
        return AVERROR(ENOMEM);
537
538
0
    if ((ret = ff_copy_whiteblacklists(rdt->rmctx, s)) < 0)
539
0
        return ret;
540
541
0
    return avformat_open_input(&rdt->rmctx, "", &ff_rdt_demuxer.p, NULL);
542
0
}
543
544
static void
545
rdt_close_context (PayloadContext *rdt)
546
0
{
547
0
    int i;
548
549
0
    for (i = 0; i < rdt->nb_rmst; i++)
550
0
        if (rdt->rmst[i]) {
551
0
            ff_rm_free_rmstream(rdt->rmst[i]);
552
0
            av_freep(&rdt->rmst[i]);
553
0
        }
554
0
    if (rdt->rmctx)
555
0
        avformat_close_input(&rdt->rmctx);
556
0
    av_freep(&rdt->mlti_data);
557
0
    av_freep(&rdt->rmst);
558
0
}
559
560
#define RDT_HANDLER(n, s, t) \
561
const RTPDynamicProtocolHandler ff_rdt_ ## n ## _handler = { \
562
    .enc_name         = s, \
563
    .codec_type       = t, \
564
    .codec_id         = AV_CODEC_ID_NONE, \
565
    .priv_data_size   = sizeof(PayloadContext), \
566
    .init             = rdt_init, \
567
    .parse_sdp_a_line = rdt_parse_sdp_line, \
568
    .close            = rdt_close_context, \
569
    .parse_packet     = rdt_parse_packet \
570
}
571
572
RDT_HANDLER(live_video, "x-pn-multirate-realvideo-live", AVMEDIA_TYPE_VIDEO);
573
RDT_HANDLER(live_audio, "x-pn-multirate-realaudio-live", AVMEDIA_TYPE_AUDIO);
574
RDT_HANDLER(video,      "x-pn-realvideo",                AVMEDIA_TYPE_VIDEO);
575
RDT_HANDLER(audio,      "x-pn-realaudio",                AVMEDIA_TYPE_AUDIO);