int ff_rtsp_connect(AVFormatContext *s)

{

    RTSPState *rt = s->priv_data;

    char host[1024], path[1024], tcpname[1024], cmd[2048], auth[128];

    char *option_list, *option, *filename;

    int port, err, tcp_fd;

    RTSPMessageHeader reply1 = {}, *reply = &reply1;

    int lower_transport_mask = 0;

    char real_challenge[64];

    struct sockaddr_storage peer;

    socklen_t peer_len = sizeof(peer);



    if (!ff_network_init())

        return AVERROR(EIO);

redirect:

    rt->control_transport = RTSP_MODE_PLAIN;

    /* extract hostname and port */

    av_url_split(NULL, 0, auth, sizeof(auth),

                 host, sizeof(host), &port, path, sizeof(path), s->filename);

    if (*auth) {

        av_strlcpy(rt->auth, auth, sizeof(rt->auth));

    }

    if (port < 0)

        port = RTSP_DEFAULT_PORT;



    /* search for options */

    option_list = strrchr(path, '?');

    if (option_list) {

        /* Strip out the RTSP specific options, write out the rest of

         * the options back into the same string. */

        filename = option_list;

        while (option_list) {

            /* move the option pointer */

            option = ++option_list;

            option_list = strchr(option_list, '&');

            if (option_list)

                *option_list = 0;



            /* handle the options */

            if (!strcmp(option, "udp")) {

                lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP);

            } else if (!strcmp(option, "multicast")) {

                lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST);

            } else if (!strcmp(option, "tcp")) {

                lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP);

            } else if(!strcmp(option, "http")) {

                lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP);

                rt->control_transport = RTSP_MODE_TUNNEL;

            } else {

                /* Write options back into the buffer, using memmove instead

                 * of strcpy since the strings may overlap. */

                int len = strlen(option);

                memmove(++filename, option, len);

                filename += len;

                if (option_list) *filename = '&';

            }

        }

        *filename = 0;

    }



    if (!lower_transport_mask)

        lower_transport_mask = (1 << RTSP_LOWER_TRANSPORT_NB) - 1;



    if (s->oformat) {

        /* Only UDP or TCP - UDP multicast isn't supported. */

        lower_transport_mask &= (1 << RTSP_LOWER_TRANSPORT_UDP) |

                                (1 << RTSP_LOWER_TRANSPORT_TCP);

        if (!lower_transport_mask || rt->control_transport == RTSP_MODE_TUNNEL) {

            av_log(s, AV_LOG_ERROR, "Unsupported lower transport method, "

                                    "only UDP and TCP are supported for output.\n");

            err = AVERROR(EINVAL);

            goto fail;

        }

    }



    /* Construct the URI used in request; this is similar to s->filename,

     * but with authentication credentials removed and RTSP specific options

     * stripped out. */

    ff_url_join(rt->control_uri, sizeof(rt->control_uri), "rtsp", NULL,

                host, port, "%s", path);



    if (rt->control_transport == RTSP_MODE_TUNNEL) {

        /* set up initial handshake for tunneling */

        char httpname[1024];

        char sessioncookie[17];

        char headers[1024];



        ff_url_join(httpname, sizeof(httpname), "http", auth, host, port, "%s", path);

        snprintf(sessioncookie, sizeof(sessioncookie), "%08x%08x",

                 av_get_random_seed(), av_get_random_seed());



        /* GET requests */

        if (url_alloc(&rt->rtsp_hd, httpname, URL_RDONLY) < 0) {

            err = AVERROR(EIO);

            goto fail;

        }



        /* generate GET headers */

        snprintf(headers, sizeof(headers),

                 "x-sessioncookie: %s\r\n"

                 "Accept: application/x-rtsp-tunnelled\r\n"

                 "Pragma: no-cache\r\n"

                 "Cache-Control: no-cache\r\n",

                 sessioncookie);

        ff_http_set_headers(rt->rtsp_hd, headers);



        /* complete the connection */

        if (url_connect(rt->rtsp_hd)) {

            err = AVERROR(EIO);

            goto fail;

        }



        /* POST requests */

        if (url_alloc(&rt->rtsp_hd_out, httpname, URL_WRONLY) < 0 ) {

            err = AVERROR(EIO);

            goto fail;

        }



        /* generate POST headers */

        snprintf(headers, sizeof(headers),

                 "x-sessioncookie: %s\r\n"

                 "Content-Type: application/x-rtsp-tunnelled\r\n"

                 "Pragma: no-cache\r\n"

                 "Cache-Control: no-cache\r\n"

                 "Content-Length: 32767\r\n"

                 "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n",

                 sessioncookie);

        ff_http_set_headers(rt->rtsp_hd_out, headers);

        ff_http_set_chunked_transfer_encoding(rt->rtsp_hd_out, 0);



        /* Initialize the authentication state for the POST session. The HTTP

         * protocol implementation doesn't properly handle multi-pass

         * authentication for POST requests, since it would require one of

         * the following:

         * - implementing Expect: 100-continue, which many HTTP servers

         *   don't support anyway, even less the RTSP servers that do HTTP

         *   tunneling

         * - sending the whole POST data until getting a 401 reply specifying

         *   what authentication method to use, then resending all that data

         * - waiting for potential 401 replies directly after sending the

         *   POST header (waiting for some unspecified time)

         * Therefore, we copy the full auth state, which works for both basic

         * and digest. (For digest, we would have to synchronize the nonce

         * count variable between the two sessions, if we'd do more requests

         * with the original session, though.)

         */

        ff_http_init_auth_state(rt->rtsp_hd_out, rt->rtsp_hd);



        /* complete the connection */

        if (url_connect(rt->rtsp_hd_out)) {

            err = AVERROR(EIO);

            goto fail;

        }

    } else {

        /* open the tcp connection */

        ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, NULL);

        if (url_open(&rt->rtsp_hd, tcpname, URL_RDWR) < 0) {

            err = AVERROR(EIO);

            goto fail;

        }

        rt->rtsp_hd_out = rt->rtsp_hd;

    }

    rt->seq = 0;



    tcp_fd = url_get_file_handle(rt->rtsp_hd);

    if (!getpeername(tcp_fd, (struct sockaddr*) &peer, &peer_len)) {

        getnameinfo((struct sockaddr*) &peer, peer_len, host, sizeof(host),

                    NULL, 0, NI_NUMERICHOST);

    }



    /* request options supported by the server; this also detects server

     * type */

    for (rt->server_type = RTSP_SERVER_RTP;;) {

        cmd[0] = 0;

        if (rt->server_type == RTSP_SERVER_REAL)

            av_strlcat(cmd,

                       /**

                        * The following entries are required for proper

                        * streaming from a Realmedia server. They are

                        * interdependent in some way although we currently

                        * don't quite understand how. Values were copied

                        * from mplayer SVN r23589.

                        * @param CompanyID is a 16-byte ID in base64

                        * @param ClientChallenge is a 16-byte ID in hex

                        */

                       "ClientChallenge: 9e26d33f2984236010ef6253fb1887f7\r\n"

                       "PlayerStarttime: [28/03/2003:22:50:23 00:00]\r\n"

                       "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n"

                       "GUID: 00000000-0000-0000-0000-000000000000\r\n",

                       sizeof(cmd));

        ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL);

        if (reply->status_code != RTSP_STATUS_OK) {

            err = AVERROR_INVALIDDATA;

            goto fail;

        }



        /* detect server type if not standard-compliant RTP */

        if (rt->server_type != RTSP_SERVER_REAL && reply->real_challenge[0]) {

            rt->server_type = RTSP_SERVER_REAL;

            continue;

        } else if (!strncasecmp(reply->server, "WMServer/", 9)) {

            rt->server_type = RTSP_SERVER_WMS;

        } else if (rt->server_type == RTSP_SERVER_REAL)

            strcpy(real_challenge, reply->real_challenge);

        break;

    }



    if (s->iformat)

        err = rtsp_setup_input_streams(s, reply);

    else

        err = rtsp_setup_output_streams(s, host);

    if (err)

        goto fail;



    do {

        int lower_transport = ff_log2_tab[lower_transport_mask &

                                  ~(lower_transport_mask - 1)];



        err = make_setup_request(s, host, port, lower_transport,

                                 rt->server_type == RTSP_SERVER_REAL ?

                                     real_challenge : NULL);

        if (err < 0)

            goto fail;

        lower_transport_mask &= ~(1 << lower_transport);

        if (lower_transport_mask == 0 && err == 1) {

            err = FF_NETERROR(EPROTONOSUPPORT);

            goto fail;

        }

    } while (err);



    rt->state = RTSP_STATE_IDLE;

    rt->seek_timestamp = 0; /* default is to start stream at position zero */

    return 0;

 fail:

    ff_rtsp_close_streams(s);

    ff_rtsp_close_connections(s);

    if (reply->status_code >=300 && reply->status_code < 400 && s->iformat) {

        av_strlcpy(s->filename, reply->location, sizeof(s->filename));

        av_log(s, AV_LOG_INFO, "Status %d: Redirecting to %s\n",

               reply->status_code,

               s->filename);

        goto redirect;

    }

    ff_network_close();

    return err;

}
