xref: /third_party/ffmpeg/libavformat/sapdec.c (revision cabdff1a)
1/*
2 * Session Announcement Protocol (RFC 2974) demuxer
3 * Copyright (c) 2010 Martin Storsjo
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#include "avformat.h"
23#include "libavutil/avstring.h"
24#include "libavutil/intreadwrite.h"
25#include "network.h"
26#include "os_support.h"
27#include "internal.h"
28#include "avio_internal.h"
29#include "url.h"
30#include "rtpdec.h"
31#if HAVE_POLL_H
32#include <poll.h>
33#endif
34
35struct SAPState {
36    URLContext *ann_fd;
37    AVFormatContext *sdp_ctx;
38    FFIOContext sdp_pb;
39    uint16_t hash;
40    char *sdp;
41    int eof;
42};
43
44static int sap_probe(const AVProbeData *p)
45{
46    if (av_strstart(p->filename, "sap:", NULL))
47        return AVPROBE_SCORE_MAX;
48    return 0;
49}
50
51static int sap_read_close(AVFormatContext *s)
52{
53    struct SAPState *sap = s->priv_data;
54    if (sap->sdp_ctx)
55        avformat_close_input(&sap->sdp_ctx);
56    ffurl_closep(&sap->ann_fd);
57    av_freep(&sap->sdp);
58    ff_network_close();
59    return 0;
60}
61
62static int sap_read_header(AVFormatContext *s)
63{
64    struct SAPState *sap = s->priv_data;
65    char host[1024], path[1024], url[1024];
66    uint8_t recvbuf[RTP_MAX_PACKET_LENGTH];
67    const AVInputFormat *infmt;
68    int port;
69    int ret, i;
70
71    if (!ff_network_init())
72        return AVERROR(EIO);
73
74    av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port,
75                 path, sizeof(path), s->url);
76    if (port < 0)
77        port = 9875;
78
79    if (!host[0]) {
80        /* Listen for announcements on sap.mcast.net if no host was specified */
81        av_strlcpy(host, "224.2.127.254", sizeof(host));
82    }
83
84    ff_url_join(url, sizeof(url), "udp", NULL, host, port, "?localport=%d",
85                port);
86    ret = ffurl_open_whitelist(&sap->ann_fd, url, AVIO_FLAG_READ,
87                               &s->interrupt_callback, NULL,
88                               s->protocol_whitelist, s->protocol_blacklist, NULL);
89    if (ret)
90        goto fail;
91
92    while (1) {
93        int addr_type, auth_len;
94        int pos;
95
96        ret = ffurl_read(sap->ann_fd, recvbuf, sizeof(recvbuf) - 1);
97        if (ret == AVERROR(EAGAIN))
98            continue;
99        if (ret < 0)
100            goto fail;
101        recvbuf[ret] = '\0'; /* Null terminate for easier parsing */
102        if (ret < 8) {
103            av_log(s, AV_LOG_WARNING, "Received too short packet\n");
104            continue;
105        }
106
107        if ((recvbuf[0] & 0xe0) != 0x20) {
108            av_log(s, AV_LOG_WARNING, "Unsupported SAP version packet "
109                                      "received\n");
110            continue;
111        }
112
113        if (recvbuf[0] & 0x04) {
114            av_log(s, AV_LOG_WARNING, "Received stream deletion "
115                                      "announcement\n");
116            continue;
117        }
118        addr_type = recvbuf[0] & 0x10;
119        auth_len  = recvbuf[1];
120        sap->hash = AV_RB16(&recvbuf[2]);
121        pos = 4;
122        if (addr_type)
123            pos += 16; /* IPv6 */
124        else
125            pos += 4; /* IPv4 */
126        pos += auth_len * 4;
127        if (pos + 4 >= ret) {
128            av_log(s, AV_LOG_WARNING, "Received too short packet\n");
129            continue;
130        }
131#define MIME "application/sdp"
132        if (strcmp(&recvbuf[pos], MIME) == 0) {
133            pos += strlen(MIME) + 1;
134        } else if (strncmp(&recvbuf[pos], "v=0\r\n", 5) == 0) {
135            // Direct SDP without a mime type
136        } else {
137            av_log(s, AV_LOG_WARNING, "Unsupported mime type %s\n",
138                                      &recvbuf[pos]);
139            continue;
140        }
141
142        sap->sdp = av_strdup(&recvbuf[pos]);
143        if (!sap->sdp) {
144            ret = AVERROR(ENOMEM);
145            goto fail;
146        }
147        break;
148    }
149
150    av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", sap->sdp);
151    ffio_init_context(&sap->sdp_pb, sap->sdp, strlen(sap->sdp), 0, NULL, NULL,
152                  NULL, NULL);
153
154    infmt = av_find_input_format("sdp");
155    if (!infmt)
156        goto fail;
157    sap->sdp_ctx = avformat_alloc_context();
158    if (!sap->sdp_ctx) {
159        ret = AVERROR(ENOMEM);
160        goto fail;
161    }
162    sap->sdp_ctx->max_delay = s->max_delay;
163    sap->sdp_ctx->pb        = &sap->sdp_pb.pub;
164    sap->sdp_ctx->interrupt_callback = s->interrupt_callback;
165
166    if ((ret = ff_copy_whiteblacklists(sap->sdp_ctx, s)) < 0)
167        goto fail;
168
169    ret = avformat_open_input(&sap->sdp_ctx, "temp.sdp", infmt, NULL);
170    if (ret < 0)
171        goto fail;
172    if (sap->sdp_ctx->ctx_flags & AVFMTCTX_NOHEADER)
173        s->ctx_flags |= AVFMTCTX_NOHEADER;
174    for (i = 0; i < sap->sdp_ctx->nb_streams; i++) {
175        AVStream *st = avformat_new_stream(s, NULL);
176        if (!st) {
177            ret = AVERROR(ENOMEM);
178            goto fail;
179        }
180        st->id = i;
181        avcodec_parameters_copy(st->codecpar, sap->sdp_ctx->streams[i]->codecpar);
182        st->time_base = sap->sdp_ctx->streams[i]->time_base;
183    }
184
185    return 0;
186
187fail:
188    sap_read_close(s);
189    return ret;
190}
191
192static int sap_fetch_packet(AVFormatContext *s, AVPacket *pkt)
193{
194    struct SAPState *sap = s->priv_data;
195    int fd = ffurl_get_file_handle(sap->ann_fd);
196    int n, ret;
197    struct pollfd p = {fd, POLLIN, 0};
198    uint8_t recvbuf[RTP_MAX_PACKET_LENGTH];
199
200    if (sap->eof)
201        return AVERROR_EOF;
202
203    while (1) {
204        n = poll(&p, 1, 0);
205        if (n <= 0 || !(p.revents & POLLIN))
206            break;
207        ret = ffurl_read(sap->ann_fd, recvbuf, sizeof(recvbuf));
208        if (ret >= 8) {
209            uint16_t hash = AV_RB16(&recvbuf[2]);
210            /* Should ideally check the source IP address, too */
211            if (recvbuf[0] & 0x04 && hash == sap->hash) {
212                /* Stream deletion */
213                sap->eof = 1;
214                return AVERROR_EOF;
215            }
216        }
217    }
218    ret = av_read_frame(sap->sdp_ctx, pkt);
219    if (ret < 0)
220        return ret;
221    if (s->ctx_flags & AVFMTCTX_NOHEADER) {
222        while (sap->sdp_ctx->nb_streams > s->nb_streams) {
223            int i = s->nb_streams;
224            AVStream *st = avformat_new_stream(s, NULL);
225            if (!st) {
226                return AVERROR(ENOMEM);
227            }
228            st->id = i;
229            avcodec_parameters_copy(st->codecpar, sap->sdp_ctx->streams[i]->codecpar);
230            st->time_base = sap->sdp_ctx->streams[i]->time_base;
231        }
232    }
233    return ret;
234}
235
236const AVInputFormat ff_sap_demuxer = {
237    .name           = "sap",
238    .long_name      = NULL_IF_CONFIG_SMALL("SAP input"),
239    .priv_data_size = sizeof(struct SAPState),
240    .read_probe     = sap_probe,
241    .read_header    = sap_read_header,
242    .read_packet    = sap_fetch_packet,
243    .read_close     = sap_read_close,
244    .flags          = AVFMT_NOFILE,
245};
246