libavformat/rdt.c
e9dea59f
 /*
  * Realmedia RTSP protocol (RDT) support.
  * Copyright (c) 2007 Ronald S. Bultje
  *
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * FFmpeg 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
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with FFmpeg; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
 /**
ba87f080
  * @file
e9dea59f
  * @brief Realmedia RTSP protocol (RDT) support
  * @author Ronald S. Bultje <rbultje@ronald.bitfreak.net>
  */
 
 #include "avformat.h"
 #include "libavutil/avstring.h"
302879cb
 #include "rtpdec.h"
e9dea59f
 #include "rdt.h"
 #include "libavutil/base64.h"
 #include "libavutil/md5.h"
 #include "rm.h"
 #include "internal.h"
e731b8d8
 #include "avio_internal.h"
9106a698
 #include "libavcodec/get_bits.h"
e9dea59f
 
accc248f
 struct RDTDemuxContext {
63f412f9
     AVFormatContext *ic; /**< the containing (RTSP) demux context */
4f602856
     /** Each RDT stream-set (represented by one RTSPStream) can contain
      * multiple streams (of the same content, but with possibly different
      * codecs/bitrates). Each such stream is represented by one AVStream
      * in the AVFormatContext, and this variable points to the offset in
      * that array such that the first is the first stream of this set. */
     AVStream **streams;
     int n_streams; /**< streams with identifical content in this set */
accc248f
     void *dynamic_protocol_context;
     DynamicPayloadPacketHandlerProc parse_packet;
9168f7e6
     uint32_t prev_timestamp;
7960e18f
     int prev_set_id, prev_stream_id;
accc248f
 };
 
 RDTDemuxContext *
e0d1eabf
 ff_rdt_parse_open(AVFormatContext *ic, int first_stream_of_set_idx,
accc248f
                   void *priv_data, RTPDynamicProtocolHandler *handler)
 {
     RDTDemuxContext *s = av_mallocz(sizeof(RDTDemuxContext));
     if (!s)
         return NULL;
 
     s->ic = ic;
4f602856
     s->streams = &ic->streams[first_stream_of_set_idx];
     do {
         s->n_streams++;
     } while (first_stream_of_set_idx + s->n_streams < ic->nb_streams &&
b2dd842d
              s->streams[s->n_streams]->id == s->streams[0]->id);
239dec21
     s->prev_set_id    = -1;
7960e18f
     s->prev_stream_id = -1;
239dec21
     s->prev_timestamp = -1;
84f0aba1
     s->parse_packet = handler ? handler->parse_packet : NULL;
accc248f
     s->dynamic_protocol_context = priv_data;
 
     return s;
 }
 
 void
 ff_rdt_parse_close(RDTDemuxContext *s)
 {
     av_free(s);
 }
 
ed0aacc7
 struct PayloadContext {
ff13ba92
     AVFormatContext *rmctx;
dfdb353c
     int nb_rmst;
     RMStream **rmst;
ff13ba92
     uint8_t *mlti_data;
     unsigned int mlti_data_size;
059a9348
     char buffer[RTP_MAX_PACKET_LENGTH + AV_INPUT_BUFFER_PADDING_SIZE];
5d88c264
     int audio_pkt_cnt; /**< remaining audio packets in rmdec */
ed0aacc7
 };
ff13ba92
 
e9dea59f
 void
 ff_rdt_calc_response_and_checksum(char response[41], char chksum[9],
                                   const char *challenge)
 {
     int ch_len = strlen (challenge), i;
     unsigned char zres[16],
         buf[64] = { 0xa1, 0xe9, 0x14, 0x9d, 0x0e, 0x6b, 0x3b, 0x59 };
 #define XOR_TABLE_SIZE 37
c4f9a4cd
     static const unsigned char xor_table[XOR_TABLE_SIZE] = {
e9dea59f
         0x05, 0x18, 0x74, 0xd0, 0x0d, 0x09, 0x02, 0x53,
         0xc0, 0x01, 0x05, 0x05, 0x67, 0x03, 0x19, 0x70,
         0x08, 0x27, 0x66, 0x10, 0x10, 0x72, 0x08, 0x09,
         0x63, 0x11, 0x03, 0x71, 0x08, 0x08, 0x70, 0x02,
         0x10, 0x57, 0x05, 0x18, 0x54 };
 
     /* some (length) checks */
     if (ch_len == 40) /* what a hack... */
         ch_len = 32;
     else if (ch_len > 56)
         ch_len = 56;
     memcpy(buf + 8, challenge, ch_len);
 
     /* xor challenge bytewise with xor_table */
     for (i = 0; i < XOR_TABLE_SIZE; i++)
         buf[8 + i] ^= xor_table[i];
 
     av_md5_sum(zres, buf, 64);
ddbeb954
     ff_data_to_hex(response, zres, 16, 1);
e9dea59f
 
     /* add tail */
     strcpy (response + 32, "01d0a8e3");
 
     /* calculate checksum */
     for (i = 0; i < 8; i++)
         chksum[i] = response[i * 4];
     chksum[8] = 0;
 }
ff13ba92
 
 static int
ed0aacc7
 rdt_load_mdpr (PayloadContext *rdt, AVStream *st, int rule_nr)
ff13ba92
 {
ae628ec1
     AVIOContext pb;
1c77ead1
     unsigned int size;
ff13ba92
     uint32_t tag;
 
     /**
      * Layout of the MLTI chunk:
0baf34d8
      * 4: MLTI
      * 2: number of streams
ff13ba92
      * Then for each stream ([number_of_streams] times):
0baf34d8
      *     2: mdpr index
      * 2: number of mdpr chunks
ff13ba92
      * Then for each mdpr chunk ([number_of_mdpr_chunks] times):
0baf34d8
      *     4: size
      *     [size]: data
ff13ba92
      * we skip MDPR chunks until we reach the one of the stream
      * we're interested in, and forward that ([size]+[data]) to
      * the RM demuxer to parse the stream-specific header data.
      */
     if (!rdt->mlti_data)
         return -1;
e731b8d8
     ffio_init_context(&pb, rdt->mlti_data, rdt->mlti_data_size, 0,
a4b8cb3c
                   NULL, NULL, NULL, NULL);
b7effd4e
     tag = avio_rl32(&pb);
ff13ba92
     if (tag == MKTAG('M', 'L', 'T', 'I')) {
         int num, chunk_nr;
 
         /* read index of MDPR chunk numbers */
b7effd4e
         num = avio_rb16(&pb);
ff13ba92
         if (rule_nr < 0 || rule_nr >= num)
             return -1;
45a8a02a
         avio_skip(&pb, rule_nr * 2);
b7effd4e
         chunk_nr = avio_rb16(&pb);
45a8a02a
         avio_skip(&pb, (num - 1 - rule_nr) * 2);
ff13ba92
 
         /* read MDPR chunks */
b7effd4e
         num = avio_rb16(&pb);
ff13ba92
         if (chunk_nr >= num)
             return -1;
         while (chunk_nr--)
45a8a02a
             avio_skip(&pb, avio_rb32(&pb));
b7effd4e
         size = avio_rb32(&pb);
ff13ba92
     } else {
         size = rdt->mlti_data_size;
6b4aa5da
         avio_seek(&pb, 0, SEEK_SET);
ff13ba92
     }
19a61bf3
     if (ff_rm_read_mdpr_codecdata(rdt->rmctx, &pb, st, rdt->rmst[st->index], size, NULL) < 0)
ff13ba92
         return -1;
 
     return 0;
 }
 
4fce284c
 /**
  * Actual data handling.
  */
 
985b05d3
 int
 ff_rdt_parse_header(const uint8_t *buf, int len,
e269ab79
                     int *pset_id, int *pseq_no, int *pstream_id,
                     int *pis_keyframe, uint32_t *ptimestamp)
4fce284c
 {
6bafd6f5
     GetBitContext gb;
43af8b2b
     int consumed = 0, set_id, seq_no, stream_id, is_keyframe,
         len_included, need_reliable;
6bafd6f5
     uint32_t timestamp;
4fce284c
 
e3b7216b
     /* skip status packets */
     while (len >= 5 && buf[1] == 0xFF /* status packet */) {
         int pkt_len;
 
         if (!(buf[0] & 0x80))
             return -1; /* not followed by a data packet */
 
         pkt_len = AV_RB16(buf+3);
         buf += pkt_len;
         len -= pkt_len;
         consumed += pkt_len;
4fce284c
     }
43af8b2b
     if (len < 16)
985b05d3
         return -1;
9e164392
     /**
      * Layout of the header (in bits):
      * 1:  len_included
      *     Flag indicating whether this header includes a length field;
      *     this can be used to concatenate multiple RDT packets in a
      *     single UDP/TCP data frame and is used to precede RDT data
      *     by stream status packets
      * 1:  need_reliable
      *     Flag indicating whether this header includes a "reliable
      *     sequence number"; these are apparently sequence numbers of
      *     data packets alone. For data packets, this flag is always
      *     set, according to the Real documentation [1]
      * 5:  set_id
      *     ID of a set of streams of identical content, possibly with
      *     different codecs or bitrates
      * 1:  is_reliable
      *     Flag set for certain streams deemed less tolerable for packet
      *     loss
      * 16: seq_no
      *     Packet sequence number; if >=0xFF00, this is a non-data packet
      *     containing stream status info, the second byte indicates the
      *     type of status packet (see wireshark docs / source code [2])
      * if (len_included) {
      *     16: packet_len
      * } else {
      *     packet_len = remainder of UDP/TCP frame
      * }
      * 1:  is_back_to_back
      *     Back-to-Back flag; used for timing, set for one in every 10
      *     packets, according to the Real documentation [1]
      * 1:  is_slow_data
      *     Slow-data flag; currently unused, according to Real docs [1]
      * 5:  stream_id
      *     ID of the stream within this particular set of streams
      * 1:  is_no_keyframe
      *     Non-keyframe flag (unset if packet belongs to a keyframe)
      * 32: timestamp (PTS)
      * if (set_id == 0x1F) {
      *     16: set_id (extended set-of-streams ID; see set_id)
      * }
      * if (need_reliable) {
      *     16: reliable_seq_no
      *         Reliable sequence number (see need_reliable)
      * }
      * if (stream_id == 0x3F) {
      *     16: stream_id (extended stream ID; see stream_id)
      * }
      * [1] https://protocol.helixcommunity.org/files/2005/devdocs/RDT_Feature_Level_20.txt
      * [2] http://www.wireshark.org/docs/dfref/r/rdt.html and
      *     http://anonsvn.wireshark.org/viewvc/trunk/epan/dissectors/packet-rdt.c
      */
6bafd6f5
     init_get_bits(&gb, buf, len << 3);
43af8b2b
     len_included  = get_bits1(&gb);
     need_reliable = get_bits1(&gb);
6bafd6f5
     set_id        = get_bits(&gb, 5);
     skip_bits(&gb, 1);
     seq_no        = get_bits(&gb, 16);
43af8b2b
     if (len_included)
         skip_bits(&gb, 16);
6bafd6f5
     skip_bits(&gb, 2);
     stream_id     = get_bits(&gb, 5);
     is_keyframe   = !get_bits1(&gb);
     timestamp     = get_bits_long(&gb, 32);
43af8b2b
     if (set_id == 0x1f)
         set_id    = get_bits(&gb, 16);
     if (need_reliable)
90e0450f
         skip_bits(&gb, 16);
43af8b2b
     if (stream_id == 0x1f)
         stream_id = get_bits(&gb, 16);
4fce284c
 
6bafd6f5
     if (pset_id)      *pset_id      = set_id;
     if (pseq_no)      *pseq_no      = seq_no;
     if (pstream_id)   *pstream_id   = stream_id;
     if (pis_keyframe) *pis_keyframe = is_keyframe;
     if (ptimestamp)   *ptimestamp   = timestamp;
 
     return consumed + (get_bits_count(&gb) >> 3);
4fce284c
 }
 
 /**< return 0 on packet, no more left, 1 on packet, 1 on partial packet... */
 static int
1a45a9f4
 rdt_parse_packet (AVFormatContext *ctx, PayloadContext *rdt, AVStream *st,
9b932b8a
                   AVPacket *pkt, uint32_t *timestamp,
90c784cc
                   const uint8_t *buf, int len, uint16_t rtp_seq, int flags)
4fce284c
 {
     int seq = 1, res;
ae628ec1
     AVIOContext pb;
4fce284c
 
a15ebf34
     if (rdt->audio_pkt_cnt == 0) {
b72b212a
         int pos, rmflags;
4fce284c
 
35debfc3
         ffio_init_context(&pb, (uint8_t *)buf, len, 0, NULL, NULL, NULL, NULL);
b72b212a
         rmflags = (flags & RTP_FLAG_KEY) ? 2 : 0;
7c68a177
         res = ff_rm_parse_packet (rdt->rmctx, &pb, st, rdt->rmst[st->index], len, pkt,
b72b212a
                                   &seq, rmflags, *timestamp);
a2704c97
         pos = avio_tell(&pb);
4fce284c
         if (res < 0)
             return res;
c5efef7b
         if (res > 0) {
36ef5369
             if (st->codec->codec_id == AV_CODEC_ID_AAC) {
c8829279
                 memcpy (rdt->buffer, buf + pos, len - pos);
8d9ac969
                 rdt->rmctx->pb = avio_alloc_context (rdt->buffer, len - pos, 0,
c8829279
                                                     NULL, NULL, NULL, NULL);
c5efef7b
             }
             goto get_cache;
4fce284c
         }
     } else {
c5efef7b
 get_cache:
a9f84821
         rdt->audio_pkt_cnt =
ade8fb4d
             ff_rm_retrieve_cache (rdt->rmctx, rdt->rmctx->pb,
7c68a177
                                   st, rdt->rmst[st->index], pkt);
5d88c264
         if (rdt->audio_pkt_cnt == 0 &&
36ef5369
             st->codec->codec_id == AV_CODEC_ID_AAC)
a4b8cb3c
             av_freep(&rdt->rmctx->pb);
4fce284c
     }
     pkt->stream_index = st->index;
     pkt->pts = *timestamp;
 
5d88c264
     return rdt->audio_pkt_cnt > 0;
4fce284c
 }
 
 int
accc248f
 ff_rdt_parse_packet(RDTDemuxContext *s, AVPacket *pkt,
ad4ad27f
                     uint8_t **bufptr, int len)
4fce284c
 {
ad4ad27f
     uint8_t *buf = bufptr ? *bufptr : NULL;
114732f4
     int seq_no, flags = 0, stream_id, set_id, is_keyframe;
4fce284c
     uint32_t timestamp;
     int rv= 0;
 
3ff2a062
     if (!s->parse_packet)
         return -1;
 
7960e18f
     if (!buf && s->prev_stream_id != -1) {
4fce284c
         /* return the next packets, if any */
         timestamp= 0; ///< Should not be used if buf is NULL, but should be set to the timestamp of the packet returned....
1a45a9f4
         rv= s->parse_packet(s->ic, s->dynamic_protocol_context,
7960e18f
                             s->streams[s->prev_stream_id],
90c784cc
                             pkt, &timestamp, NULL, 0, 0, flags);
4fce284c
         return rv;
     }
 
     if (len < 12)
         return -1;
114732f4
     rv = ff_rdt_parse_header(buf, len, &set_id, &seq_no, &stream_id, &is_keyframe, &timestamp);
4fce284c
     if (rv < 0)
         return rv;
7960e18f
     if (is_keyframe &&
         (set_id != s->prev_set_id || timestamp != s->prev_timestamp ||
          stream_id != s->prev_stream_id)) {
a68d44ed
         flags |= RTP_FLAG_KEY;
239dec21
         s->prev_set_id    = set_id;
         s->prev_timestamp = timestamp;
985b05d3
     }
7960e18f
     s->prev_stream_id = stream_id;
4fce284c
     buf += rv;
     len -= rv;
 
7960e18f
      if (s->prev_stream_id >= s->n_streams) {
          s->prev_stream_id = -1;
          return -1;
      }
 
1a45a9f4
     rv = s->parse_packet(s->ic, s->dynamic_protocol_context,
7960e18f
                          s->streams[s->prev_stream_id],
90c784cc
                          pkt, &timestamp, buf, len, 0, flags);
4fce284c
 
     return rv;
 }
 
1256d16b
 void
ab63fb03
 ff_rdt_subscribe_rule (char *cmd, int size,
1256d16b
                        int stream_nr, int rule_nr)
 {
     av_strlcatf(cmd, size, "stream=%d;rule=%d,stream=%d;rule=%d",
ab63fb03
                 stream_nr, rule_nr * 2, stream_nr, rule_nr * 2 + 1);
 }
 
ff13ba92
 static unsigned char *
 rdt_parse_b64buf (unsigned int *target_len, const char *p)
 {
     unsigned char *target;
     int len = strlen(p);
     if (*p == '\"') {
         p++;
         len -= 2; /* skip embracing " at start/end */
     }
     *target_len = len * 3 / 4;
059a9348
     target = av_mallocz(*target_len + AV_INPUT_BUFFER_PADDING_SIZE);
8692e628
     if (!target)
         return NULL;
ff13ba92
     av_base64_decode(target, p, *target_len);
     return target;
 }
 
 static int
7b2a0708
 rdt_parse_sdp_line (AVFormatContext *s, int st_index,
                     PayloadContext *rdt, const char *line)
ff13ba92
 {
7b2a0708
     AVStream *stream = s->streams[st_index];
ff13ba92
     const char *p = line;
 
     if (av_strstart(p, "OpaqueData:buffer;", &p)) {
         rdt->mlti_data = rdt_parse_b64buf(&rdt->mlti_data_size, p);
     } else if (av_strstart(p, "StartTime:integer;", &p))
         stream->first_dts = atoi(p);
7c68a177
     else if (av_strstart(p, "ASMRuleBook:string;", &p)) {
0b9535b9
         int n, first = -1;
7c68a177
 
         for (n = 0; n < s->nb_streams; n++)
b2dd842d
             if (s->streams[n]->id == stream->id) {
5626f994
                 int count = s->streams[n]->index + 1, err;
7c68a177
                 if (first == -1) first = n;
dfdb353c
                 if (rdt->nb_rmst < count) {
5626f994
                     if ((err = av_reallocp(&rdt->rmst,
d872fb0f
                                            count * sizeof(*rdt->rmst))) < 0) {
                         rdt->nb_rmst = 0;
5626f994
                         return err;
d872fb0f
                     }
5626f994
                     memset(rdt->rmst + rdt->nb_rmst, 0,
                            (count - rdt->nb_rmst) * sizeof(*rdt->rmst));
dfdb353c
                     rdt->nb_rmst = count;
                 }
7c68a177
                 rdt->rmst[s->streams[n]->index] = ff_rm_alloc_rmstream();
da7e31a2
                 if (!rdt->rmst[s->streams[n]->index])
                     return AVERROR(ENOMEM);
7c68a177
                 rdt_load_mdpr(rdt, s->streams[n], (n - first) * 2);
            }
     }
ff13ba92
 
     return 0;
 }
 
530bca94
 static void
 real_parse_asm_rule(AVStream *st, const char *p, const char *end)
 {
     do {
         /* can be either averagebandwidth= or AverageBandwidth= */
         if (sscanf(p, " %*1[Aa]verage%*1[Bb]andwidth=%d", &st->codec->bit_rate) == 1)
             break;
         if (!(p = strchr(p, ',')) || p > end)
             p = end;
         p++;
     } while (p < end);
 }
 
3ca45429
 static AVStream *
 add_dstream(AVFormatContext *s, AVStream *orig_st)
 {
     AVStream *st;
 
84ad31ff
     if (!(st = avformat_new_stream(s, NULL)))
3ca45429
         return NULL;
84ad31ff
     st->id = orig_st->id;
3ca45429
     st->codec->codec_type = orig_st->codec->codec_type;
     st->first_dts         = orig_st->first_dts;
 
     return st;
 }
 
 static void
 real_parse_asm_rulebook(AVFormatContext *s, AVStream *orig_st,
                         const char *p)
 {
     const char *end;
dfdb353c
     int n_rules = 0, odd = 0;
3ca45429
     AVStream *st;
 
     /**
      * The ASMRuleBook contains a list of comma-separated strings per rule,
      * and each rule is separated by a ;. The last one also has a ; at the
      * end so we can use it as delimiter.
      * Every rule occurs twice, once for when the RTSP packet header marker
      * is set and once for if it isn't. We only read the first because we
      * don't care much (that's what the "odd" variable is for).
      * Each rule contains a set of one or more statements, optionally
da9cea77
      * preceded by a single condition. If there's a condition, the rule
3ca45429
      * starts with a '#'. Multiple conditions are merged between brackets,
      * so there are never multiple conditions spread out over separate
      * statements. Generally, these conditions are bitrate limits (min/max)
      * for multi-bitrate streams.
      */
     if (*p == '\"') p++;
dfdb353c
     while (1) {
3ca45429
         if (!(end = strchr(p, ';')))
             break;
         if (!odd && end != p) {
             if (n_rules > 0)
                 st = add_dstream(s, orig_st);
             else
                 st = orig_st;
dfdb353c
             if (!st)
                 break;
530bca94
             real_parse_asm_rule(st, p, end);
3ca45429
             n_rules++;
         }
         p = end + 1;
         odd ^= 1;
     }
 }
 
 void
 ff_real_parse_sdp_a_line (AVFormatContext *s, int stream_index,
                           const char *line)
 {
     const char *p = line;
 
     if (av_strstart(p, "ASMRuleBook:string;", &p))
         real_parse_asm_rulebook(s, s->streams[stream_index], p);
 }
 
ff13ba92
 
 
78791c08
 static av_cold int rdt_init(AVFormatContext *s, int st_index, PayloadContext *rdt)
13ee94a4
 {
     int ret;
 
d52bf89d
     rdt->rmctx = avformat_alloc_context();
     if (!rdt->rmctx)
         return AVERROR(ENOMEM);
 
13ee94a4
     if ((ret = ff_copy_whitelists(rdt->rmctx, s)) < 0)
         return ret;
 
     return avformat_open_input(&rdt->rmctx, "", &ff_rdt_demuxer, NULL);
 }
 
ff13ba92
 static void
d594dbec
 rdt_close_context (PayloadContext *rdt)
ff13ba92
 {
7c68a177
     int i;
 
dfdb353c
     for (i = 0; i < rdt->nb_rmst; i++)
7c68a177
         if (rdt->rmst[i]) {
             ff_rm_free_rmstream(rdt->rmst[i]);
             av_freep(&rdt->rmst[i]);
         }
ff13ba92
     if (rdt->rmctx)
cd3716b9
         avformat_close_input(&rdt->rmctx);
ff13ba92
     av_freep(&rdt->mlti_data);
dfdb353c
     av_freep(&rdt->rmst);
ff13ba92
 }
 
 #define RDT_HANDLER(n, s, t) \
e926b5ce
 static RTPDynamicProtocolHandler rdt_ ## n ## _handler = { \
202a6697
     .enc_name         = s, \
     .codec_type       = t, \
36ef5369
     .codec_id         = AV_CODEC_ID_NONE, \
78791c08
     .priv_data_size   = sizeof(PayloadContext), \
     .init             = rdt_init, \
202a6697
     .parse_sdp_a_line = rdt_parse_sdp_line, \
d594dbec
     .close            = rdt_close_context, \
202a6697
     .parse_packet     = rdt_parse_packet \
44adbebe
 }
ff13ba92
 
72415b2a
 RDT_HANDLER(live_video, "x-pn-multirate-realvideo-live", AVMEDIA_TYPE_VIDEO);
 RDT_HANDLER(live_audio, "x-pn-multirate-realaudio-live", AVMEDIA_TYPE_AUDIO);
 RDT_HANDLER(video,      "x-pn-realvideo",                AVMEDIA_TYPE_VIDEO);
 RDT_HANDLER(audio,      "x-pn-realaudio",                AVMEDIA_TYPE_AUDIO);
ff13ba92
 
feeafb4a
 void ff_register_rdt_dynamic_payload_handlers(void)
ff13ba92
 {
e926b5ce
     ff_register_dynamic_payload_handler(&rdt_video_handler);
     ff_register_dynamic_payload_handler(&rdt_audio_handler);
     ff_register_dynamic_payload_handler(&rdt_live_video_handler);
     ff_register_dynamic_payload_handler(&rdt_live_audio_handler);
ff13ba92
 }