Skip to content
Snippets Groups Projects
rtmpproto.c 55.5 KiB
Newer Older
  • Learn to ignore specific revisions
  •     if (t == 6) {
            if ((ret = gen_pong(s, rt, pkt)) < 0)
                return ret;
        }
    
        return 0;
    }
    
    
    static int handle_client_bw(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
    
        if (pkt->data_size < 4) {
            av_log(s, AV_LOG_ERROR,
                   "Client bandwidth report packet is less than 4 bytes long (%d)\n",
                   pkt->data_size);
    
            return AVERROR_INVALIDDATA;
    
    
        rt->client_report_size = AV_RB32(pkt->data);
        if (rt->client_report_size <= 0) {
            av_log(s, AV_LOG_ERROR, "Incorrect client bandwidth %d\n",
                    rt->client_report_size);
            return AVERROR_INVALIDDATA;
    
        }
        av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", rt->client_report_size);
        rt->client_report_size >>= 1;
    
    static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
    
    
        if (pkt->data_size < 4) {
            av_log(s, AV_LOG_ERROR,
                   "Too short server bandwidth report packet (%d)\n",
                   pkt->data_size);
            return AVERROR_INVALIDDATA;
        }
    
    
        rt->server_bw = AV_RB32(pkt->data);
        if (rt->server_bw <= 0) {
            av_log(s, AV_LOG_ERROR, "Incorrect server bandwidth %d\n",
                   rt->server_bw);
    
            return AVERROR_INVALIDDATA;
    
        }
        av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw);
    
        return 0;
    }
    
    
    static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
    {
        const uint8_t *data_end = pkt->data + pkt->data_size;
        uint8_t tmpstr[256];
    
        if (!ff_amf_get_field_value(pkt->data + 9, data_end,
                                    "description", tmpstr, sizeof(tmpstr))) {
            av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr);
            return -1;
        }
    
        return 0;
    }
    
    
    static int handle_invoke_result(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
        char *tracked_method = NULL;
        int ret = 0;
    
    
        if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0)
    
            return ret;
    
        if (!tracked_method) {
            /* Ignore this reply when the current method is not tracked. */
            return ret;
        }
    
        if (!memcmp(tracked_method, "connect", 7)) {
            if (!rt->is_input) {
                if ((ret = gen_release_stream(s, rt)) < 0)
                    goto fail;
    
                if ((ret = gen_fcpublish_stream(s, rt)) < 0)
                    goto fail;
            } else {
                if ((ret = gen_server_bw(s, rt)) < 0)
                    goto fail;
            }
    
            if ((ret = gen_create_stream(s, rt)) < 0)
                goto fail;
    
            if (rt->is_input) {
                /* Send the FCSubscribe command when the name of live
                 * stream is defined by the user or if it's a live stream. */
                if (rt->subscribe) {
                    if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0)
                        goto fail;
                } else if (rt->live == -1) {
                    if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0)
                        goto fail;
                }
            }
        } else if (!memcmp(tracked_method, "createStream", 12)) {
            //extract a number from the result
            if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) {
                av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n");
            } else {
                rt->main_channel_id = av_int2double(AV_RB64(pkt->data + 21));
            }
    
            if (!rt->is_input) {
                if ((ret = gen_publish(s, rt)) < 0)
                    goto fail;
            } else {
                if ((ret = gen_play(s, rt)) < 0)
                    goto fail;
                if ((ret = gen_buffer_time(s, rt)) < 0)
                    goto fail;
            }
        }
    
    fail:
        av_free(tracked_method);
        return ret;
    }
    
    
    static int handle_invoke_status(URLContext *s, RTMPPacket *pkt)
    
        RTMPContext *rt = s->priv_data;
    
        const uint8_t *data_end = pkt->data + pkt->data_size;
        const uint8_t *ptr = pkt->data + 11;
        uint8_t tmpstr[256];
    
    
        for (i = 0; i < 2; i++) {
            t = ff_amf_tag_size(ptr, data_end);
            if (t < 0)
                return 1;
            ptr += t;
        }
    
        t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr));
        if (!t && !strcmp(tmpstr, "error")) {
            if (!ff_amf_get_field_value(ptr, data_end,
                                        "description", tmpstr, sizeof(tmpstr)))
                av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr);
            return -1;
        }
    
        t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr));
        if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING;
        if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED;
        if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED;
        if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING;
    
        return 0;
    }
    
    static int handle_invoke(URLContext *s, RTMPPacket *pkt)
    {
        RTMPContext *rt = s->priv_data;
    
        int ret = 0;
    
        //TODO: check for the messages sent for wrong state?
        if (!memcmp(pkt->data, "\002\000\006_error", 9)) {
    
            if ((ret = handle_invoke_error(s, pkt)) < 0)
                return ret;
    
        } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) {
    
            if ((ret = handle_invoke_result(s, pkt)) < 0)
    
                return ret;
    
        } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) {
    
            if ((ret = handle_invoke_status(s, pkt)) < 0)
                return ret;
    
        } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) {
            if ((ret = gen_check_bw(s, rt)) < 0)
                return ret;
        }
    
    
        return ret;
    
    }
    
    /**
     * Parse received packet and possibly perform some action depending on
     * the packet contents.
     * @return 0 for no errors, negative values for serious errors which prevent
     *         further communications, positive values for uncritical errors
     */
    static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt)
    {
        int ret;
    
    #ifdef DEBUG
        ff_rtmp_packet_dump(s, pkt);
    #endif
    
        switch (pkt->type) {
    
        case RTMP_PT_BYTES_READ:
            av_dlog(s, "received bytes read report\n");
            break;
    
        case RTMP_PT_CHUNK_SIZE:
            if ((ret = handle_chunk_size(s, pkt)) < 0)
                return ret;
            break;
        case RTMP_PT_PING:
            if ((ret = handle_ping(s, pkt)) < 0)
                return ret;
            break;
        case RTMP_PT_CLIENT_BW:
            if ((ret = handle_client_bw(s, pkt)) < 0)
                return ret;
            break;
        case RTMP_PT_SERVER_BW:
            if ((ret = handle_server_bw(s, pkt)) < 0)
                return ret;
            break;
        case RTMP_PT_INVOKE:
            if ((ret = handle_invoke(s, pkt)) < 0)
                return ret;
    
        case RTMP_PT_METADATA:
            /* Audio, Video and Metadata packets are parsed in get_packet() */
    
        default:
            av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type);
            break;
    
     * Interact with the server by receiving and sending RTMP packets until
    
     * there is some significant data (media data or expected status notification).
     *
     * @param s          reading context
    
     * @param for_header non-zero value tells function to work until it
     * gets notification from the server that playing has been started,
     * otherwise function will work until some media data is received (or
     * an error happens)
    
     * @return 0 for successful operation, negative value in case of error
     */
    static int get_packet(URLContext *s, int for_header)
    {
        RTMPContext *rt = s->priv_data;
        int ret;
    
        uint8_t *p;
        const uint8_t *next;
        uint32_t data_size;
        uint32_t ts, cts, pts=0;
    
        if (rt->state == STATE_STOPPED)
            return AVERROR_EOF;
    
    
            RTMPPacket rpkt = { 0 };
    
            if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt,
    
                                           rt->in_chunk_size, rt->prev_pkt[0])) <= 0) {
    
                    return AVERROR(EAGAIN);
                } else {
                    return AVERROR(EIO);
                }
            }
    
            rt->bytes_read += ret;
            if (rt->bytes_read > rt->last_bytes_read + rt->client_report_size) {
    
                av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n");
    
                if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0)
                    return ret;
    
                rt->last_bytes_read = rt->bytes_read;
            }
    
    
            ret = rtmp_parse_result(s, rt, &rpkt);
            if (ret < 0) {//serious error in current packet
                ff_rtmp_packet_destroy(&rpkt);
    
            if (rt->state == STATE_STOPPED) {
                ff_rtmp_packet_destroy(&rpkt);
                return AVERROR_EOF;
            }
    
            if (for_header && (rt->state == STATE_PLAYING || rt->state == STATE_PUBLISHING)) {
    
                ff_rtmp_packet_destroy(&rpkt);
                return 0;
            }
    
            if (!rpkt.data_size || !rt->is_input) {
    
                ff_rtmp_packet_destroy(&rpkt);
                continue;
            }
            if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO ||
    
               (rpkt.type == RTMP_PT_NOTIFY && !memcmp("\002\000\012onMetaData", rpkt.data, 13))) {
    
                ts = rpkt.timestamp;
    
    
                // generate packet header and put data into buffer for FLV demuxer
                rt->flv_off  = 0;
                rt->flv_size = rpkt.data_size + 15;
                rt->flv_data = p = av_realloc(rt->flv_data, rt->flv_size);
                bytestream_put_byte(&p, rpkt.type);
                bytestream_put_be24(&p, rpkt.data_size);
                bytestream_put_be24(&p, ts);
                bytestream_put_byte(&p, ts >> 24);
                bytestream_put_be24(&p, 0);
                bytestream_put_buffer(&p, rpkt.data, rpkt.data_size);
                bytestream_put_be32(&p, 0);
                ff_rtmp_packet_destroy(&rpkt);
                return 0;
            } else if (rpkt.type == RTMP_PT_METADATA) {
                // we got raw FLV data, make it available for FLV demuxer
                rt->flv_off  = 0;
                rt->flv_size = rpkt.data_size;
                rt->flv_data = av_realloc(rt->flv_data, rt->flv_size);
    
                /* rewrite timestamps */
                next = rpkt.data;
                ts = rpkt.timestamp;
                while (next - rpkt.data < rpkt.data_size - 11) {
                    next++;
                    data_size = bytestream_get_be24(&next);
                    p=next;
                    cts = bytestream_get_be24(&next);
    
                    cts |= bytestream_get_byte(&next) << 24;
    
                    if (pts==0)
                        pts=cts;
                    ts += cts - pts;
                    pts = cts;
                    bytestream_put_be24(&p, ts);
                    bytestream_put_byte(&p, ts >> 24);
                    next += data_size + 3 + 4;
                }
    
                memcpy(rt->flv_data, rpkt.data, rpkt.data_size);
                ff_rtmp_packet_destroy(&rpkt);
                return 0;
            }
            ff_rtmp_packet_destroy(&rpkt);
        }
    }
    
    static int rtmp_close(URLContext *h)
    {
        RTMPContext *rt = h->priv_data;
    
            rt->flv_data = NULL;
            if (rt->out_pkt.data_size)
                ff_rtmp_packet_destroy(&rt->out_pkt);
    
            if (rt->state > STATE_FCPUBLISH)
    
                ret = gen_fcunpublish_stream(h, rt);
    
        if (rt->state > STATE_HANDSHAKED)
    
            ret = gen_delete_stream(h, rt);
    
        free_tracked_methods(rt);
    
        av_freep(&rt->flv_data);
    
        ffurl_close(rt->stream);
    
     * Open RTMP connection and verify that the stream can be played.
    
     *
     * URL syntax: rtmp://server[:port][/app][/playpath]
     *             where 'app' is first one or two directories in the path
     *             (e.g. /ondemand/, /flash/live/, etc.)
     *             and 'playpath' is a file name (the rest of the path,
     *             may be prefixed with "mp4:")
     */
    static int rtmp_open(URLContext *s, const char *uri, int flags)
    {
    
        RTMPContext *rt = s->priv_data;
    
        char proto[8], hostname[256], path[1024], *fname;
    
        uint8_t buf[2048];
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        AVDictionary *opts = NULL;
    
        rt->is_input = !(flags & AVIO_FLAG_WRITE);
    
        av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
    
    Martin Storsjö's avatar
    Martin Storsjö committed
                     path, sizeof(path), s->filename);
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) {
            if (!strcmp(proto, "rtmpts"))
                av_dict_set(&opts, "ffrtmphttp_tls", "1", 1);
    
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
            /* open the http tunneling connection */
    
            ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL);
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        } else if (!strcmp(proto, "rtmps")) {
            /* open the tls connection */
            if (port < 0)
                port = RTMPS_DEFAULT_PORT;
            ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL);
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) {
            if (!strcmp(proto, "rtmpte"))
                av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1);
    
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
            /* open the encrypted connection */
            ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
            rt->encrypted = 1;
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        } else {
            /* open the tcp connection */
            if (port < 0)
                port = RTMP_DEFAULT_PORT;
            ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
        }
    
        if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
                              &s->interrupt_callback, &opts)) < 0) {
    
            av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
    
        rt->state = STATE_START;
    
        if ((ret = rtmp_handshake(s, rt)) < 0)
    
        rt->out_chunk_size = 128;
        rt->in_chunk_size  = 128; // Probably overwritten later
    
        rt->state = STATE_HANDSHAKED;
    
    
        // Keep the application name when it has been defined by the user.
        old_app = rt->app;
    
        rt->app = av_malloc(APP_MAX_LENGTH);
        if (!rt->app) {
    
            ret = AVERROR(ENOMEM);
            goto fail;
    
        //extract "app" part from path
        if (!strncmp(path, "/ondemand/", 10)) {
            fname = path + 10;
            memcpy(rt->app, "ondemand", 9);
        } else {
    
            char *next = *path ? path + 1 : path;
            char *p = strchr(next, '/');
    
                rt->app[0] = '\0';
    
    Luca Barbato's avatar
    Luca Barbato committed
                // make sure we do not mismatch a playpath for an application instance
    
                char *c = strchr(p + 1, ':');
                fname = strchr(p + 1, '/');
    
    Luca Barbato's avatar
    Luca Barbato committed
                if (!fname || (c && c < fname)) {
    
                    fname = p + 1;
                    av_strlcpy(rt->app, path + 1, p - path);
    
                    fname++;
                    av_strlcpy(rt->app, path + 1, fname - path - 1);
    
    
        if (old_app) {
            // The name of application has been defined by the user, override it.
            av_free(rt->app);
            rt->app = old_app;
        }
    
    
            int len = strlen(fname);
    
    
            rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
            if (!rt->playpath) {
    
                ret = AVERROR(ENOMEM);
                goto fail;
    
                (!strcmp(fname + len - 4, ".f4v") ||
                 !strcmp(fname + len - 4, ".mp4"))) {
    
            } else if (len >= 4 && !strcmp(fname + len - 4, ".flv")) {
    
                fname[len - 4] = '\0';
    
            } else {
                rt->playpath[0] = 0;
            }
            strncat(rt->playpath, fname, PLAYPATH_MAX_LENGTH - 5);
    
        if (!rt->tcurl) {
            rt->tcurl = av_malloc(TCURL_MAX_LENGTH);
    
            if (!rt->tcurl) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
    
            ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname,
                        port, "/%s", rt->app);
        }
    
    
        if (!rt->flashver) {
            rt->flashver = av_malloc(FLASHVER_MAX_LENGTH);
    
            if (!rt->flashver) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
    
            if (rt->is_input) {
                snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d",
                        RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2,
                        RTMP_CLIENT_VER3, RTMP_CLIENT_VER4);
            } else {
                snprintf(rt->flashver, FLASHVER_MAX_LENGTH,
                        "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT);
            }
        }
    
    
        rt->client_report_size = 1048576;
        rt->bytes_read = 0;
        rt->last_bytes_read = 0;
    
        rt->server_bw = 2500000;
    
        av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
    
               proto, path, rt->app, rt->playpath);
    
        if ((ret = gen_connect(s, rt)) < 0)
            goto fail;
    
        do {
            ret = get_packet(s, 1);
        } while (ret == EAGAIN);
        if (ret < 0)
            goto fail;
    
            // generate FLV header for demuxer
            rt->flv_size = 13;
            rt->flv_data = av_realloc(rt->flv_data, rt->flv_size);
            rt->flv_off  = 0;
            memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size);
    
        } else {
            rt->flv_size = 0;
            rt->flv_data = NULL;
            rt->flv_off  = 0;
    
        s->max_packet_size = rt->stream->max_packet_size;
    
        s->is_streamed     = 1;
        return 0;
    
    fail:
    
    Samuel Pitoiset's avatar
    Samuel Pitoiset committed
        av_dict_free(&opts);
    
        rtmp_close(s);
    
    }
    
    static int rtmp_read(URLContext *s, uint8_t *buf, int size)
    {
        RTMPContext *rt = s->priv_data;
        int orig_size = size;
        int ret;
    
        while (size > 0) {
            int data_left = rt->flv_size - rt->flv_off;
    
            if (data_left >= size) {
                memcpy(buf, rt->flv_data + rt->flv_off, size);
                rt->flv_off += size;
                return orig_size;
            }
            if (data_left > 0) {
                memcpy(buf, rt->flv_data + rt->flv_off, data_left);
                buf  += data_left;
                size -= data_left;
                rt->flv_off = rt->flv_size;
    
            }
            if ((ret = get_packet(s, 0)) < 0)
               return ret;
        }
        return orig_size;
    }
    
    
    static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
    
        RTMPContext *rt = s->priv_data;
    
        int size_temp = size;
        int pktsize, pkttype;
        uint32_t ts;
        const uint8_t *buf_temp = buf;
    
            if (rt->skip_bytes) {
                int skip = FFMIN(rt->skip_bytes, size_temp);
                buf_temp       += skip;
                size_temp      -= skip;
                rt->skip_bytes -= skip;
                continue;
            }
    
            if (rt->flv_header_bytes < 11) {
                const uint8_t *header = rt->flv_header;
                int copy = FFMIN(11 - rt->flv_header_bytes, size_temp);
                bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy);
                rt->flv_header_bytes += copy;
                size_temp            -= copy;
                if (rt->flv_header_bytes < 11)
                    break;
    
                pkttype = bytestream_get_byte(&header);
                pktsize = bytestream_get_be24(&header);
                ts = bytestream_get_be24(&header);
                ts |= bytestream_get_byte(&header) << 24;
                bytestream_get_be24(&header);
    
                rt->flv_size = pktsize;
    
                //force 12bytes header
                if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) ||
                    pkttype == RTMP_PT_NOTIFY) {
                    if (pkttype == RTMP_PT_NOTIFY)
                        pktsize += 16;
                    rt->prev_pkt[1][RTMP_SOURCE_CHANNEL].channel_id = 0;
                }
    
                //this can be a big packet, it's better to send it right here
    
                if ((ret = ff_rtmp_packet_create(&rt->out_pkt, RTMP_SOURCE_CHANNEL,
                                                 pkttype, ts, pktsize)) < 0)
                    return ret;
    
    
                rt->out_pkt.extra = rt->main_channel_id;
                rt->flv_data = rt->out_pkt.data;
    
                if (pkttype == RTMP_PT_NOTIFY)
                    ff_amf_write_string(&rt->flv_data, "@setDataFrame");
            }
    
            if (rt->flv_size - rt->flv_off > size_temp) {
                bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, size_temp);
                rt->flv_off += size_temp;
    
            } else {
                bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, rt->flv_size - rt->flv_off);
    
                size_temp   -= rt->flv_size - rt->flv_off;
    
                rt->flv_off += rt->flv_size - rt->flv_off;
            }
    
            if (rt->flv_off == rt->flv_size) {
    
                if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0)
    
                rt->flv_size = 0;
                rt->flv_off = 0;
    
        } while (buf_temp - buf < size);
    
        if (rt->flv_nb_packets < rt->flush_interval)
            return size;
        rt->flv_nb_packets = 0;
    
    
        /* set stream into nonblocking mode */
        rt->stream->flags |= AVIO_FLAG_NONBLOCK;
    
        /* try to read one byte from the stream */
        ret = ffurl_read(rt->stream, &c, 1);
    
        /* switch the stream back into blocking mode */
        rt->stream->flags &= ~AVIO_FLAG_NONBLOCK;
    
        if (ret == AVERROR(EAGAIN)) {
            /* no incoming data to handle */
            return size;
        } else if (ret < 0) {
            return ret;
        } else if (ret == 1) {
            RTMPPacket rpkt = { 0 };
    
            if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt,
    
                                                    rt->prev_pkt[0], c)) <= 0)
                 return ret;
    
            if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0)
                return ret;
    
            ff_rtmp_packet_destroy(&rpkt);
        }
    
    
    #define OFFSET(x) offsetof(RTMPContext, x)
    #define DEC AV_OPT_FLAG_DECODING_PARAM
    #define ENC AV_OPT_FLAG_ENCODING_PARAM
    
    static const AVOption rtmp_options[] = {
        {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
        {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_INT, {3000}, 0, INT_MAX, DEC|ENC},
    
        {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
        {"rtmp_flashver", "Version of the Flash plugin used to run the SWF player.", OFFSET(flashver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
        {"rtmp_flush_interval", "Number of packets flushed in the same request (RTMPT only).", OFFSET(flush_interval), AV_OPT_TYPE_INT, {10}, 0, INT_MAX, ENC},
    
        {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {-2}, INT_MIN, INT_MAX, DEC, "rtmp_live"},
        {"any", "both", 0, AV_OPT_TYPE_CONST, {-2}, 0, 0, DEC, "rtmp_live"},
        {"live", "live stream", 0, AV_OPT_TYPE_CONST, {-1}, 0, 0, DEC, "rtmp_live"},
        {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {0}, 0, 0, DEC, "rtmp_live"},
    
        {"rtmp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
    
        {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
        {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
    
        {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
        {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
    
    #define RTMP_PROTOCOL(flavor)                    \
    static const AVClass flavor##_class = {          \
        .class_name = #flavor,                       \
        .item_name  = av_default_item_name,          \
        .option     = rtmp_options,                  \
        .version    = LIBAVUTIL_VERSION_INT,         \
    };                                               \
                                                     \
    URLProtocol ff_##flavor##_protocol = {           \
        .name           = #flavor,                   \
        .url_open       = rtmp_open,                 \
        .url_read       = rtmp_read,                 \
        .url_write      = rtmp_write,                \
        .url_close      = rtmp_close,                \
        .priv_data_size = sizeof(RTMPContext),       \
        .flags          = URL_PROTOCOL_FLAG_NETWORK, \
        .priv_data_class= &flavor##_class,           \
    
    RTMP_PROTOCOL(rtmp)
    RTMP_PROTOCOL(rtmpe)
    RTMP_PROTOCOL(rtmps)
    RTMP_PROTOCOL(rtmpt)
    RTMP_PROTOCOL(rtmpte)
    RTMP_PROTOCOL(rtmpts)