libavformat/rtmpproto.c
9fd6b843
 /*
  * RTMP network protocol
de421b20
  * Copyright (c) 2009 Konstantin Shishkov
9fd6b843
  *
  * 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
9fd6b843
  * RTMP protocol
  */
 
 #include "libavcodec/bytestream.h"
 #include "libavutil/avstring.h"
08225d01
 #include "libavutil/base64.h"
6c87b866
 #include "libavutil/hmac.h"
3383a53e
 #include "libavutil/intfloat.h"
9fd6b843
 #include "libavutil/lfg.h"
08225d01
 #include "libavutil/md5.h"
6465562e
 #include "libavutil/opt.h"
e5f2731c
 #include "libavutil/random_seed.h"
9fd6b843
 #include "avformat.h"
e4a9e3cc
 #include "internal.h"
9fd6b843
 
 #include "network.h"
 
 #include "flv.h"
 #include "rtmp.h"
acd554c1
 #include "rtmpcrypt.h"
9fd6b843
 #include "rtmppkt.h"
0589da0a
 #include "url.h"
9fd6b843
 
93f257db
 #if CONFIG_ZLIB
 #include <zlib.h>
 #endif
 
da8ef5ac
 #define APP_MAX_LENGTH 1024
5165c600
 #define PLAYPATH_MAX_LENGTH 512
 #define TCURL_MAX_LENGTH 1024
e64673e4
 #define FLASHVER_MAX_LENGTH 64
e5f2731c
 #define RTMP_PKTDATA_DEFAULT_SIZE 4096
ba77757a
 #define RTMP_HEADER 11
6465562e
 
9fd6b843
 /** RTMP protocol handler state */
 typedef enum {
     STATE_START,      ///< client has not done anything yet
     STATE_HANDSHAKED, ///< client has performed handshake
6bf22e18
     STATE_FCPUBLISH,  ///< client FCPublishing stream (for output)
9fd6b843
     STATE_PLAYING,    ///< client has started receiving multimedia data from server
0d6fa397
     STATE_SEEKING,    ///< client has started the seek operation. Back on STATE_PLAYING when the time comes
6bf22e18
     STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output)
e5f2731c
     STATE_RECEIVING,  ///< received a publish command (for input)
ffb7669e
     STATE_SENDING,    ///< received a play command (for output)
72b870b9
     STATE_STOPPED,    ///< the broadcast has been stopped
9fd6b843
 } ClientState;
 
f89584ca
 typedef struct TrackedMethod {
     char *name;
     int id;
 } TrackedMethod;
 
9fd6b843
 /** protocol handler context */
 typedef struct RTMPContext {
6465562e
     const AVClass *class;
9fd6b843
     URLContext*   stream;                     ///< TCP stream used in interactions with RTMP server
84a125c4
     RTMPPacket    *prev_pkt[2];               ///< packet history used when reading and sending packets ([0] for reading, [1] for writing)
     int           nb_prev_pkt[2];             ///< number of elements in prev_pkt
f5ce90f2
     int           in_chunk_size;              ///< size of the chunks incoming RTMP packets are divided into
     int           out_chunk_size;             ///< size of the chunks outgoing RTMP packets are divided into
b316991b
     int           is_input;                   ///< input/output flag
b3b17512
     char          *playpath;                  ///< stream identifier to play (with possible "mp4:" prefix)
b2e495af
     int           live;                       ///< 0: recorded, -1: live, -2: both
6465562e
     char          *app;                       ///< name of application
8ee3e187
     char          *conn;                      ///< append arbitrary AMF data to the Connect message
9fd6b843
     ClientState   state;                      ///< current state
f8d1bb67
     int           stream_id;                  ///< ID assigned by the server for the stream
9fd6b843
     uint8_t*      flv_data;                   ///< buffer with data for demuxer
     int           flv_size;                   ///< current buffer size
     int           flv_off;                    ///< number of bytes read from current buffer
46743a85
     int           flv_nb_packets;             ///< number of flv packets published
6bf22e18
     RTMPPacket    out_pkt;                    ///< rtmp packet, created from flv a/v or metadata (for output)
bf7c1719
     uint32_t      client_report_size;         ///< number of bytes after which client should report to server
     uint32_t      bytes_read;                 ///< number of bytes read from server
     uint32_t      last_bytes_read;            ///< number of bytes read last reported to server
f4cd8b80
     uint32_t      last_timestamp;             ///< last timestamp received in a packet
3ffe32eb
     int           skip_bytes;                 ///< number of bytes to skip from the input FLV stream in the next write call
59cb5747
     int           has_audio;                  ///< presence of audio data
     int           has_video;                  ///< presence of video data
     int           received_metadata;          ///< Indicates if we have received metadata about the streams
ba77757a
     uint8_t       flv_header[RTMP_HEADER];    ///< partial incoming flv packet header
b14629e5
     int           flv_header_bytes;           ///< number of initialized bytes in flv_header
704af3e2
     int           nb_invokes;                 ///< keeps track of invoke messages
55c9320e
     char*         tcurl;                      ///< url of the target stream
e64673e4
     char*         flashver;                   ///< version of the flash plugin
635ac8e1
     char*         swfhash;                    ///< SHA256 hash of the decompressed SWF file (32 bytes)
     int           swfhash_len;                ///< length of the SHA256 hash
     int           swfsize;                    ///< size of the decompressed SWF file
05945db9
     char*         swfurl;                     ///< url of the swf player
93f257db
     char*         swfverify;                  ///< URL to player swf file, compute hash/size automatically
635ac8e1
     char          swfverification[42];        ///< hash of the SWF verification
758377a2
     char*         pageurl;                    ///< url of the web page
00cb52c6
     char*         subscribe;                  ///< name of live stream to subscribe
c2d38bea
     int           server_bw;                  ///< server bandwidth
9477c035
     int           client_buffer_time;         ///< client buffer time in ms
46743a85
     int           flush_interval;             ///< number of packets flushed in the same request (RTMPT only)
acd554c1
     int           encrypted;                  ///< use an encrypted connection (RTMPE only)
f89584ca
     TrackedMethod*tracked_methods;            ///< tracked methods buffer
     int           nb_tracked_methods;         ///< number of tracked methods
     int           tracked_methods_size;       ///< size of the tracked methods buffer
e5f2731c
     int           listen;                     ///< listen mode flag
     int           listen_timeout;             ///< listen timeout to wait for new connections
     int           nb_streamid;                ///< The next stream id to return on createStream calls
e65c776d
     double        duration;                   ///< Duration of the stream in seconds as returned by the server (only valid if non-zero)
08225d01
     char          username[50];
     char          password[50];
     char          auth_params[500];
     int           do_reconnect;
     int           auth_tried;
9fd6b843
 } RTMPContext;
 
 #define PLAYER_KEY_OPEN_PART_LEN 30   ///< length of partial key used for first client digest signing
 /** Client key used for digest signing */
 static const uint8_t rtmp_player_key[] = {
     'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
     'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1',
 
     0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
     0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
     0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
 };
 
 #define SERVER_KEY_OPEN_PART_LEN 36   ///< length of partial key used for first server digest signing
 /** Key used for RTMP server digest signing */
 static const uint8_t rtmp_server_key[] = {
     'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
     'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
     'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1',
 
     0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
     0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
     0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
 };
 
36b9c27d
 static int handle_chunk_size(URLContext *s, RTMPPacket *pkt);
 
f89584ca
 static int add_tracked_method(RTMPContext *rt, const char *name, int id)
 {
5626f994
     int err;
f89584ca
 
     if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) {
         rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2;
5626f994
         if ((err = av_reallocp(&rt->tracked_methods, rt->tracked_methods_size *
d872fb0f
                                sizeof(*rt->tracked_methods))) < 0) {
             rt->nb_tracked_methods = 0;
             rt->tracked_methods_size = 0;
5626f994
             return err;
d872fb0f
         }
f89584ca
     }
 
     rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name);
     if (!rt->tracked_methods[rt->nb_tracked_methods].name)
         return AVERROR(ENOMEM);
     rt->tracked_methods[rt->nb_tracked_methods].id = id;
     rt->nb_tracked_methods++;
 
     return 0;
 }
 
 static void del_tracked_method(RTMPContext *rt, int index)
 {
     memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1],
             sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1));
     rt->nb_tracked_methods--;
 }
 
a8103503
 static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset,
                                char **tracked_method)
 {
     RTMPContext *rt = s->priv_data;
     GetByteContext gbc;
     double pkt_id;
     int ret;
     int i;
 
ba5393a6
     bytestream2_init(&gbc, pkt->data + offset, pkt->size - offset);
a8103503
     if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
         return ret;
 
     for (i = 0; i < rt->nb_tracked_methods; i++) {
         if (rt->tracked_methods[i].id != pkt_id)
             continue;
 
         *tracked_method = rt->tracked_methods[i].name;
         del_tracked_method(rt, i);
         break;
     }
 
     return 0;
 }
 
f89584ca
 static void free_tracked_methods(RTMPContext *rt)
 {
     int i;
 
     for (i = 0; i < rt->nb_tracked_methods; i ++)
03b84f2f
         av_freep(&rt->tracked_methods[i].name);
     av_freep(&rt->tracked_methods);
08225d01
     rt->tracked_methods_size = 0;
     rt->nb_tracked_methods   = 0;
f89584ca
 }
 
 static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track)
 {
     int ret;
 
     if (pkt->type == RTMP_PT_INVOKE && track) {
         GetByteContext gbc;
         char name[128];
         double pkt_id;
         int len;
 
ba5393a6
         bytestream2_init(&gbc, pkt->data, pkt->size);
f89584ca
         if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0)
             goto fail;
 
         if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
             goto fail;
 
         if ((ret = add_tracked_method(rt, name, pkt_id)) < 0)
             goto fail;
     }
 
f5ce90f2
     ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
f89584ca
 fail:
     ff_rtmp_packet_destroy(pkt);
     return ret;
 }
 
8ee3e187
 static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p)
 {
05338686
     char *field, *value;
8ee3e187
     char type;
 
     /* The type must be B for Boolean, N for number, S for string, O for
      * object, or Z for null. For Booleans the data must be either 0 or 1 for
      * FALSE or TRUE, respectively. Likewise for Objects the data must be
      * 0 or 1 to end or begin an object, respectively. Data items in subobjects
      * may be named, by prefixing the type with 'N' and specifying the name
      * before the value (ie. NB:myFlag:1). This option may be used multiple times
      * to construct arbitrary AMF sequences. */
     if (param[0] && param[1] == ':') {
         type = param[0];
         value = param + 2;
     } else if (param[0] == 'N' && param[1] && param[2] == ':') {
         type = param[1];
05338686
         field = param + 3;
         value = strchr(field, ':');
         if (!value)
             goto fail;
         *value = '\0';
         value++;
8ee3e187
 
         ff_amf_write_field_name(p, field);
     } else {
         goto fail;
     }
 
     switch (type) {
     case 'B':
         ff_amf_write_bool(p, value[0] != '0');
         break;
     case 'S':
         ff_amf_write_string(p, value);
         break;
     case 'N':
         ff_amf_write_number(p, strtod(value, NULL));
         break;
     case 'Z':
         ff_amf_write_null(p);
         break;
     case 'O':
         if (value[0] != '0')
             ff_amf_write_object_start(p);
         else
             ff_amf_write_object_end(p);
         break;
     default:
         goto fail;
         break;
     }
 
     return 0;
 
 fail:
     av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param);
     return AVERROR(EINVAL);
 }
 
9fd6b843
 /**
49bd8e4b
  * Generate 'connect' call and send it to the server.
9fd6b843
  */
f645f1d6
 static int gen_connect(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
e64673e4
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
da8ef5ac
                                      0, 4096 + APP_MAX_LENGTH)) < 0)
f645f1d6
         return ret;
9fd6b843
 
     p = pkt.data;
 
     ff_amf_write_string(&p, "connect");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "app");
08225d01
     ff_amf_write_string2(&p, rt->app, rt->auth_params);
9fd6b843
 
e64673e4
     if (!rt->is_input) {
6bf22e18
         ff_amf_write_field_name(&p, "type");
         ff_amf_write_string(&p, "nonprivate");
     }
9fd6b843
     ff_amf_write_field_name(&p, "flashVer");
e64673e4
     ff_amf_write_string(&p, rt->flashver);
05945db9
 
     if (rt->swfurl) {
         ff_amf_write_field_name(&p, "swfUrl");
         ff_amf_write_string(&p, rt->swfurl);
     }
 
9fd6b843
     ff_amf_write_field_name(&p, "tcUrl");
08225d01
     ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
6bf22e18
     if (rt->is_input) {
c7240611
         ff_amf_write_field_name(&p, "fpad");
         ff_amf_write_bool(&p, 0);
         ff_amf_write_field_name(&p, "capabilities");
         ff_amf_write_number(&p, 15.0);
faba4a9b
 
         /* Tell the server we support all the audio codecs except
          * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010)
          * which are unused in the RTMP protocol implementation. */
c7240611
         ff_amf_write_field_name(&p, "audioCodecs");
faba4a9b
         ff_amf_write_number(&p, 4071.0);
c7240611
         ff_amf_write_field_name(&p, "videoCodecs");
         ff_amf_write_number(&p, 252.0);
         ff_amf_write_field_name(&p, "videoFunction");
         ff_amf_write_number(&p, 1.0);
758377a2
 
         if (rt->pageurl) {
             ff_amf_write_field_name(&p, "pageUrl");
             ff_amf_write_string(&p, rt->pageurl);
         }
6bf22e18
     }
9fd6b843
     ff_amf_write_object_end(&p);
 
8ee3e187
     if (rt->conn) {
05338686
         char *param = rt->conn;
8ee3e187
 
         // Write arbitrary AMF data to the Connect message.
4b1f5e50
         while (param) {
05338686
             char *sep;
             param += strspn(param, " ");
             if (!*param)
                 break;
             sep = strchr(param, ' ');
             if (sep)
                 *sep = '\0';
8ee3e187
             if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) {
                 // Invalid AMF parameter.
                 ff_rtmp_packet_destroy(&pkt);
                 return ret;
             }
 
05338686
             if (sep)
                 param = sep + 1;
             else
                 break;
8ee3e187
         }
     }
 
ba5393a6
     pkt.size = p - pkt.data;
9fd6b843
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
9fd6b843
 }
 
e5f2731c
 static int read_connect(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt = { 0 };
     uint8_t *p;
     const uint8_t *cp;
     int ret;
     char command[64];
     int stringlen;
     double seqnum;
     uint8_t tmpstr[256];
     GetByteContext gbc;
 
     if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size,
84a125c4
                                    &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0)
e5f2731c
         return ret;
36b9c27d
 
     if (pkt.type == RTMP_PT_CHUNK_SIZE) {
         if ((ret = handle_chunk_size(s, &pkt)) < 0)
             return ret;
 
         ff_rtmp_packet_destroy(&pkt);
         if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size,
                                        &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0)
             return ret;
     }
 
e5f2731c
     cp = pkt.data;
ba5393a6
     bytestream2_init(&gbc, cp, pkt.size);
e5f2731c
     if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) {
         av_log(s, AV_LOG_ERROR, "Unable to read command string\n");
         ff_rtmp_packet_destroy(&pkt);
         return AVERROR_INVALIDDATA;
     }
     if (strcmp(command, "connect")) {
         av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command);
         ff_rtmp_packet_destroy(&pkt);
         return AVERROR_INVALIDDATA;
     }
     ret = ff_amf_read_number(&gbc, &seqnum);
     if (ret)
         av_log(s, AV_LOG_WARNING, "SeqNum not found\n");
     /* Here one could parse an AMF Object with data as flashVers and others. */
     ret = ff_amf_get_field_value(gbc.buffer,
                                  gbc.buffer + bytestream2_get_bytes_left(&gbc),
                                  "app", tmpstr, sizeof(tmpstr));
     if (ret)
         av_log(s, AV_LOG_WARNING, "App field not found in connect\n");
     if (!ret && strcmp(tmpstr, rt->app))
         av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n",
                tmpstr, rt->app);
     ff_rtmp_packet_destroy(&pkt);
 
     // Send Window Acknowledgement Size (as defined in speficication)
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_SERVER_BW, 0, 4)) < 0)
         return ret;
     p = pkt.data;
     bytestream_put_be32(&p, rt->server_bw);
ba5393a6
     pkt.size = p - pkt.data;
e5f2731c
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
     // Send Peer Bandwidth
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_CLIENT_BW, 0, 5)) < 0)
         return ret;
     p = pkt.data;
     bytestream_put_be32(&p, rt->server_bw);
     bytestream_put_byte(&p, 2); // dynamic
ba5393a6
     pkt.size = p - pkt.data;
e5f2731c
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     // Ping request
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_PING, 0, 6)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be16(&p, 0); // 0 -> Stream Begin
     bytestream_put_be32(&p, 0);
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     // Chunk size
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_CHUNK_SIZE, 0, 4)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be32(&p, rt->out_chunk_size);
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
79dd756e
     // Send _result NetConnection.Connect.Success to connect
e5f2731c
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_INVOKE, 0,
                                      RTMP_PKTDATA_DEFAULT_SIZE)) < 0)
         return ret;
 
     p = pkt.data;
     ff_amf_write_string(&p, "_result");
     ff_amf_write_number(&p, seqnum);
 
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "fmsVer");
     ff_amf_write_string(&p, "FMS/3,0,1,123");
     ff_amf_write_field_name(&p, "capabilities");
     ff_amf_write_number(&p, 31);
     ff_amf_write_object_end(&p);
 
     ff_amf_write_object_start(&p);
     ff_amf_write_field_name(&p, "level");
     ff_amf_write_string(&p, "status");
     ff_amf_write_field_name(&p, "code");
     ff_amf_write_string(&p, "NetConnection.Connect.Success");
     ff_amf_write_field_name(&p, "description");
     ff_amf_write_string(&p, "Connection succeeded.");
     ff_amf_write_field_name(&p, "objectEncoding");
     ff_amf_write_number(&p, 0);
     ff_amf_write_object_end(&p);
 
ba5393a6
     pkt.size = p - pkt.data;
e5f2731c
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
     if (ret < 0)
         return ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_INVOKE, 0, 30)) < 0)
         return ret;
     p = pkt.data;
     ff_amf_write_string(&p, "onBWDone");
     ff_amf_write_number(&p, 0);
     ff_amf_write_null(&p);
     ff_amf_write_number(&p, 8192);
ba5393a6
     pkt.size = p - pkt.data;
e5f2731c
     ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&pkt);
 
     return ret;
 }
 
9fd6b843
 /**
49bd8e4b
  * Generate 'releaseStream' call and send it to the server. It should make
6bf22e18
  * the server release some channel for media streams.
  */
f645f1d6
 static int gen_release_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 29 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Releasing stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "releaseStream");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
7011a42b
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'FCPublish' call and send it to the server. It should make
6bf22e18
  * the server preapare for receiving media streams.
  */
f645f1d6
 static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 25 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "FCPublish");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
7011a42b
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'FCUnpublish' call and send it to the server. It should make
6bf22e18
  * the server destroy stream.
  */
f645f1d6
 static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 27 + strlen(rt->playpath))) < 0)
         return ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n");
6bf22e18
     p = pkt.data;
     ff_amf_write_string(&p, "FCUnpublish");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate 'createStream' call and send it to the server. It should make
9fd6b843
  * the server allocate some channel for media streams.
  */
f645f1d6
 static int gen_create_stream(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Creating stream...\n");
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 25)) < 0)
         return ret;
9fd6b843
 
     p = pkt.data;
     ff_amf_write_string(&p, "createStream");
704af3e2
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 
 /**
49bd8e4b
  * Generate 'deleteStream' call and send it to the server. It should make
6bf22e18
  * the server remove some channel for media streams.
  */
f645f1d6
 static int gen_delete_stream(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Deleting stream...\n");
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 34)) < 0)
         return ret;
6bf22e18
 
     p = pkt.data;
     ff_amf_write_string(&p, "deleteStream");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_null(&p);
f8d1bb67
     ff_amf_write_number(&p, rt->stream_id);
9fd6b843
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9fd6b843
 }
 
 /**
e65c776d
  * Generate 'getStreamLength' call and send it to the server. If the server
  * knows the duration of the selected stream, it will reply with the duration
  * in seconds.
  */
 static int gen_get_stream_length(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
                                      0, 31 + strlen(rt->playpath))) < 0)
         return ret;
 
     p = pkt.data;
     ff_amf_write_string(&p, "getStreamLength");
     ff_amf_write_number(&p, ++rt->nb_invokes);
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
 
     return rtmp_send_packet(rt, &pkt, 1);
 }
 
 /**
9477c035
  * Generate client buffer time and send it to the server.
  */
 static int gen_buffer_time(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                      1, 10)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be16(&p, 3);
f8d1bb67
     bytestream_put_be32(&p, rt->stream_id);
9477c035
     bytestream_put_be32(&p, rt->client_buffer_time);
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9477c035
 }
 
 /**
49bd8e4b
  * Generate 'play' call and send it to the server, then ping the server
9fd6b843
  * to start actual playing.
  */
f645f1d6
 static int gen_play(URLContext *s, RTMPContext *rt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath);
f645f1d6
 
d4aef997
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
f645f1d6
                                      0, 29 + strlen(rt->playpath))) < 0)
         return ret;
 
f8d1bb67
     pkt.extra = rt->stream_id;
9fd6b843
 
     p = pkt.data;
     ff_amf_write_string(&p, "play");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
9fd6b843
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
4d6d7029
     ff_amf_write_number(&p, rt->live * 1000);
9fd6b843
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
9fd6b843
 }
 
0d6fa397
 static int gen_seek(URLContext *s, RTMPContext *rt, int64_t timestamp)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
4ebc7d65
     av_log(s, AV_LOG_DEBUG, "Sending seek command for timestamp %"PRId64"\n",
            timestamp);
0d6fa397
 
     if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 26)) < 0)
         return ret;
 
f8d1bb67
     pkt.extra = rt->stream_id;
0d6fa397
 
     p = pkt.data;
     ff_amf_write_string(&p, "seek");
     ff_amf_write_number(&p, 0); //no tracking back responses
     ff_amf_write_null(&p); //as usual, the first null param
     ff_amf_write_number(&p, timestamp); //where we want to jump
 
     return rtmp_send_packet(rt, &pkt, 1);
 }
 
9fd6b843
 /**
9bec3ca2
  * Generate a pause packet that either pauses or unpauses the current stream.
  */
 static int gen_pause(URLContext *s, RTMPContext *rt, int pause, uint32_t timestamp)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     av_log(s, AV_LOG_DEBUG, "Sending pause command for timestamp %d\n",
            timestamp);
 
     if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 29)) < 0)
         return ret;
 
     pkt.extra = rt->stream_id;
 
     p = pkt.data;
     ff_amf_write_string(&p, "pause");
     ff_amf_write_number(&p, 0); //no tracking back responses
     ff_amf_write_null(&p); //as usual, the first null param
     ff_amf_write_bool(&p, pause); // pause or unpause
     ff_amf_write_number(&p, timestamp); //where we pause the stream
 
     return rtmp_send_packet(rt, &pkt, 1);
 }
 
 /**
49bd8e4b
  * Generate 'publish' call and send it to the server.
6bf22e18
  */
f645f1d6
 static int gen_publish(URLContext *s, RTMPContext *rt)
6bf22e18
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
6bf22e18
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath);
f645f1d6
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
                                      0, 30 + strlen(rt->playpath))) < 0)
         return ret;
 
f8d1bb67
     pkt.extra = rt->stream_id;
6bf22e18
 
     p = pkt.data;
     ff_amf_write_string(&p, "publish");
1eef08f9
     ff_amf_write_number(&p, ++rt->nb_invokes);
6bf22e18
     ff_amf_write_null(&p);
     ff_amf_write_string(&p, rt->playpath);
     ff_amf_write_string(&p, "live");
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
6bf22e18
 }
 
 /**
49bd8e4b
  * Generate ping reply and send it to the server.
9fd6b843
  */
f645f1d6
 static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt)
9fd6b843
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
ba5393a6
     if (ppkt->size < 6) {
8ea1459b
         av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
ba5393a6
                ppkt->size);
8ea1459b
         return AVERROR_INVALIDDATA;
     }
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                      ppkt->timestamp + 1, 6)) < 0)
         return ret;
9fd6b843
 
     p = pkt.data;
     bytestream_put_be16(&p, 7);
4aaebf78
     bytestream_put_be32(&p, AV_RB32(ppkt->data+2));
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
9fd6b843
 }
 
bf7c1719
 /**
635ac8e1
  * Generate SWF verification message and send it to the server.
  */
 static int gen_swf_verification(URLContext *s, RTMPContext *rt)
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n");
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
                                      0, 44)) < 0)
         return ret;
 
     p = pkt.data;
     bytestream_put_be16(&p, 27);
     memcpy(p, rt->swfverification, 42);
 
     return rtmp_send_packet(rt, &pkt, 0);
 }
 
 /**
34d908c0
  * Generate server bandwidth message and send it to the server.
  */
f645f1d6
 static int gen_server_bw(URLContext *s, RTMPContext *rt)
34d908c0
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW,
                                      0, 4)) < 0)
         return ret;
34d908c0
 
     p = pkt.data;
c2d38bea
     bytestream_put_be32(&p, rt->server_bw);
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
34d908c0
 }
 
 /**
d55961fa
  * Generate check bandwidth message and send it to the server.
  */
f645f1d6
 static int gen_check_bw(URLContext *s, RTMPContext *rt)
d55961fa
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
d55961fa
 
f645f1d6
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
                                      0, 21)) < 0)
         return ret;
d55961fa
 
     p = pkt.data;
     ff_amf_write_string(&p, "_checkbw");
8b6a5a79
     ff_amf_write_number(&p, ++rt->nb_invokes);
d55961fa
     ff_amf_write_null(&p);
 
fb7e7808
     return rtmp_send_packet(rt, &pkt, 1);
d55961fa
 }
 
 /**
49bd8e4b
  * Generate report on bytes read so far and send it to the server.
bf7c1719
  */
f645f1d6
 static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts)
bf7c1719
 {
     RTMPPacket pkt;
     uint8_t *p;
f645f1d6
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ,
                                      ts, 4)) < 0)
         return ret;
bf7c1719
 
     p = pkt.data;
     bytestream_put_be32(&p, rt->bytes_read);
f645f1d6
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 0);
bf7c1719
 }
 
00cb52c6
 static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt,
                                   const char *subscribe)
f9e77c17
 {
     RTMPPacket pkt;
     uint8_t *p;
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
00cb52c6
                                      0, 27 + strlen(subscribe))) < 0)
f9e77c17
         return ret;
 
     p = pkt.data;
     ff_amf_write_string(&p, "FCSubscribe");
     ff_amf_write_number(&p, ++rt->nb_invokes);
     ff_amf_write_null(&p);
00cb52c6
     ff_amf_write_string(&p, subscribe);
f9e77c17
 
f89584ca
     return rtmp_send_packet(rt, &pkt, 1);
f9e77c17
 }
 
3505d557
 int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap,
                         const uint8_t *key, int keylen, uint8_t *dst)
9fd6b843
 {
6c87b866
     AVHMAC *hmac;
9fd6b843
 
6c87b866
     hmac = av_hmac_alloc(AV_HMAC_SHA256);
     if (!hmac)
08e93f5b
         return AVERROR(ENOMEM);
9fd6b843
 
6c87b866
     av_hmac_init(hmac, key, keylen);
9fd6b843
     if (gap <= 0) {
6c87b866
         av_hmac_update(hmac, src, len);
9fd6b843
     } else { //skip 32 bytes used for storing digest
6c87b866
         av_hmac_update(hmac, src, gap);
         av_hmac_update(hmac, src + gap + 32, len - gap - 32);
9fd6b843
     }
6c87b866
     av_hmac_final(hmac, dst, 32);
9fd6b843
 
6ce3d9fc
     av_hmac_free(hmac);
08e93f5b
 
     return 0;
9fd6b843
 }
 
0e31088b
 int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val,
                             int add_val)
 {
     int i, digest_pos = 0;
 
     for (i = 0; i < 4; i++)
         digest_pos += buf[i + off];
     digest_pos = digest_pos % mod_val + add_val;
 
     return digest_pos;
 }
 
9fd6b843
 /**
49bd8e4b
  * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
9fd6b843
  * will be stored) into that packet.
  *
  * @param buf handshake data (1536 bytes)
acd554c1
  * @param encrypted use an encrypted connection (RTMPE)
9fd6b843
  * @return offset to the digest inside input data
  */
acd554c1
 static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)
9fd6b843
 {
0e31088b
     int ret, digest_pos;
9fd6b843
 
acd554c1
     if (encrypted)
         digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776);
     else
         digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12);
9fd6b843
 
3505d557
     ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                               rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN,
                               buf + digest_pos);
08e93f5b
     if (ret < 0)
         return ret;
 
9fd6b843
     return digest_pos;
 }
 
 /**
49bd8e4b
  * Verify that the received server response has the expected digest value.
9fd6b843
  *
  * @param buf handshake data received from the server (1536 bytes)
  * @param off position to search digest offset from
  * @return 0 if digest is valid, digest position otherwise
  */
 static int rtmp_validate_digest(uint8_t *buf, int off)
 {
     uint8_t digest[32];
0e31088b
     int ret, digest_pos;
9fd6b843
 
0e31088b
     digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4);
9fd6b843
 
3505d557
     ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
                               rtmp_server_key, SERVER_KEY_OPEN_PART_LEN,
                               digest);
08e93f5b
     if (ret < 0)
         return ret;
 
9fd6b843
     if (!memcmp(digest, buf + digest_pos, 32))
         return digest_pos;
     return 0;
 }
 
635ac8e1
 static int rtmp_calc_swf_verification(URLContext *s, RTMPContext *rt,
                                       uint8_t *buf)
 {
     uint8_t *p;
     int ret;
 
     if (rt->swfhash_len != 32) {
         av_log(s, AV_LOG_ERROR,
                "Hash of the decompressed SWF file is not 32 bytes long.\n");
         return AVERROR(EINVAL);
     }
 
     p = &rt->swfverification[0];
     bytestream_put_byte(&p, 1);
     bytestream_put_byte(&p, 1);
     bytestream_put_be32(&p, rt->swfsize);
     bytestream_put_be32(&p, rt->swfsize);
 
     if ((ret = ff_rtmp_calc_digest(rt->swfhash, 32, 0, buf, 32, p)) < 0)
         return ret;
 
     return 0;
 }
 
93f257db
 #if CONFIG_ZLIB
 static int rtmp_uncompress_swfplayer(uint8_t *in_data, int64_t in_size,
                                      uint8_t **out_data, int64_t *out_size)
 {
     z_stream zs = { 0 };
     void *ptr;
     int size;
     int ret = 0;
 
     zs.avail_in = in_size;
     zs.next_in  = in_data;
     ret = inflateInit(&zs);
     if (ret != Z_OK)
         return AVERROR_UNKNOWN;
 
     do {
         uint8_t tmp_buf[16384];
 
         zs.avail_out = sizeof(tmp_buf);
         zs.next_out  = tmp_buf;
 
         ret = inflate(&zs, Z_NO_FLUSH);
         if (ret != Z_OK && ret != Z_STREAM_END) {
             ret = AVERROR_UNKNOWN;
             goto fail;
         }
 
         size = sizeof(tmp_buf) - zs.avail_out;
         if (!(ptr = av_realloc(*out_data, *out_size + size))) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
         *out_data = ptr;
 
         memcpy(*out_data + *out_size, tmp_buf, size);
         *out_size += size;
     } while (zs.avail_out == 0);
 
 fail:
     inflateEnd(&zs);
     return ret;
 }
 #endif
 
 static int rtmp_calc_swfhash(URLContext *s)
 {
     RTMPContext *rt = s->priv_data;
     uint8_t *in_data = NULL, *out_data = NULL, *swfdata;
     int64_t in_size, out_size;
     URLContext *stream;
     char swfhash[32];
     int swfsize;
     int ret = 0;
 
     /* Get the SWF player file. */
     if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ,
                           &s->interrupt_callback, NULL)) < 0) {
         av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify);
         goto fail;
     }
 
     if ((in_size = ffurl_seek(stream, 0, AVSEEK_SIZE)) < 0) {
         ret = AVERROR(EIO);
         goto fail;
     }
 
     if (!(in_data = av_malloc(in_size))) {
         ret = AVERROR(ENOMEM);
         goto fail;
     }
 
     if ((ret = ffurl_read_complete(stream, in_data, in_size)) < 0)
         goto fail;
 
     if (in_size < 3) {
         ret = AVERROR_INVALIDDATA;
         goto fail;
     }
 
     if (!memcmp(in_data, "CWS", 3)) {
         /* Decompress the SWF player file using Zlib. */
         if (!(out_data = av_malloc(8))) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
         *in_data = 'F'; // magic stuff
         memcpy(out_data, in_data, 8);
         out_size = 8;
 
 #if CONFIG_ZLIB
         if ((ret = rtmp_uncompress_swfplayer(in_data + 8, in_size - 8,
                                              &out_data, &out_size)) < 0)
             goto fail;
 #else
         av_log(s, AV_LOG_ERROR,
                "Zlib is required for decompressing the SWF player file.\n");
         ret = AVERROR(EINVAL);
         goto fail;
 #endif
         swfsize = out_size;
         swfdata = out_data;
     } else {
         swfsize = in_size;
         swfdata = in_data;
     }
 
     /* Compute the SHA256 hash of the SWF player file. */
     if ((ret = ff_rtmp_calc_digest(swfdata, swfsize, 0,
                                    "Genuine Adobe Flash Player 001", 30,
                                    swfhash)) < 0)
         goto fail;
 
     /* Set SWFVerification parameters. */
     av_opt_set_bin(rt, "rtmp_swfhash", swfhash, 32, 0);
     rt->swfsize = swfsize;
 
 fail:
     av_freep(&in_data);
     av_freep(&out_data);
     ffurl_close(stream);
     return ret;
 }
 
9fd6b843
 /**
49bd8e4b
  * Perform handshake with the server by means of exchanging pseudorandom data
9fd6b843
  * signed with HMAC-SHA2 digest.
  *
  * @return 0 if handshake succeeds, negative value otherwise
  */
 static int rtmp_handshake(URLContext *s, RTMPContext *rt)
 {
     AVLFG rnd;
     uint8_t tosend    [RTMP_HANDSHAKE_PACKET_SIZE+1] = {
         3,                // unencrypted data
         0, 0, 0, 0,       // client uptime
         RTMP_CLIENT_VER1,
         RTMP_CLIENT_VER2,
         RTMP_CLIENT_VER3,
         RTMP_CLIENT_VER4,
     };
     uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE];
     uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1];
     int i;
     int server_pos, client_pos;
acd554c1
     uint8_t digest[32], signature[32];
     int ret, type = 0;
9fd6b843
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
9fd6b843
 
     av_lfg_init(&rnd, 0xDEADC0DE);
     // generate handshake packet - 1536 bytes of pseudorandom data
     for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++)
         tosend[i] = av_lfg_get(&rnd) >> 24;
acd554c1
 
23e9e5c7
     if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
         /* When the client wants to use RTMPE, we have to change the command
          * byte to 0x06 which means to use encrypted data and we have to set
          * the flash version to at least 9.0.115.0. */
         tosend[0] = 6;
         tosend[5] = 128;
         tosend[6] = 0;
         tosend[7] = 3;
         tosend[8] = 2;
 
         /* Initialize the Diffie-Hellmann context and generate the public key
          * to send to the server. */
         if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0)
             return ret;
     }
 
f7bfb126
     client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted);
08e93f5b
     if (client_pos < 0)
         return client_pos;
9fd6b843
 
bba287fd
     if ((ret = ffurl_write(rt->stream, tosend,
                            RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
         av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n");
         return ret;
     }
 
177bcc95
     if ((ret = ffurl_read_complete(rt->stream, serverdata,
                                    RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
7f804085
         av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
177bcc95
         return ret;
9fd6b843
     }
177bcc95
 
     if ((ret = ffurl_read_complete(rt->stream, clientdata,
                                    RTMP_HANDSHAKE_PACKET_SIZE)) < 0) {
7f804085
         av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
177bcc95
         return ret;
9fd6b843
     }
 
acd554c1
     av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]);
7f804085
     av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n",
9fd6b843
            serverdata[5], serverdata[6], serverdata[7], serverdata[8]);
 
e2ee11e8
     if (rt->is_input && serverdata[5] >= 3) {
c7240611
         server_pos = rtmp_validate_digest(serverdata + 1, 772);
08e93f5b
         if (server_pos < 0)
             return server_pos;
 
9fd6b843
         if (!server_pos) {
acd554c1
             type = 1;
c7240611
             server_pos = rtmp_validate_digest(serverdata + 1, 8);
08e93f5b
             if (server_pos < 0)
                 return server_pos;
 
c7240611
             if (!server_pos) {
7f804085
                 av_log(s, AV_LOG_ERROR, "Server response validating failed\n");
a4d3f358
                 return AVERROR(EIO);
c7240611
             }
9fd6b843
         }
 
635ac8e1
         /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF,
          * key are the last 32 bytes of the server handshake. */
         if (rt->swfsize) {
             if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 +
                                                   RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0)
                 return ret;
         }
 
3505d557
         ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0,
                                   rtmp_server_key, sizeof(rtmp_server_key),
                                   digest);
08e93f5b
         if (ret < 0)
             return ret;
 
3505d557
         ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32,
acd554c1
                                   0, digest, 32, signature);
08e93f5b
         if (ret < 0)
             return ret;
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Compute the shared secret key sent by the server and initialize
              * the RC4 encryption. */
             if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                                                    tosend + 1, type)) < 0)
                 return ret;
 
             /* Encrypt the signature received by the server. */
             ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]);
         }
 
         if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
7f804085
             av_log(s, AV_LOG_ERROR, "Signature mismatch\n");
a4d3f358
             return AVERROR(EIO);
c7240611
         }
9fd6b843
 
c7240611
         for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++)
             tosend[i] = av_lfg_get(&rnd) >> 24;
3505d557
         ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0,
                                   rtmp_player_key, sizeof(rtmp_player_key),
                                   digest);
08e93f5b
         if (ret < 0)
             return ret;
 
3505d557
         ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0,
                                   digest, 32,
                                   tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
08e93f5b
         if (ret < 0)
             return ret;
c7240611
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Encrypt the signature to be send to the server. */
             ff_rtmpe_encrypt_sig(rt->stream, tosend +
                                  RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
                                  serverdata[0]);
         }
 
c7240611
         // write reply back to the server
bba287fd
         if ((ret = ffurl_write(rt->stream, tosend,
                                RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
             return ret;
acd554c1
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Set RC4 keys for encryption and update the keystreams. */
             if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                 return ret;
         }
6bf22e18
     } else {
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Compute the shared secret key sent by the server and initialize
              * the RC4 encryption. */
             if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1,
                             tosend + 1, 1)) < 0)
                 return ret;
 
             if (serverdata[0] == 9) {
                 /* Encrypt the signature received by the server. */
                 ff_rtmpe_encrypt_sig(rt->stream, signature, digest,
                                      serverdata[0]);
             }
         }
 
bba287fd
         if ((ret = ffurl_write(rt->stream, serverdata + 1,
                                RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
             return ret;
acd554c1
 
23e9e5c7
         if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) {
acd554c1
             /* Set RC4 keys for encryption and update the keystreams. */
             if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
                 return ret;
         }
6bf22e18
     }
 
9fd6b843
     return 0;
 }
 
e5f2731c
 static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int,
                                   uint32_t *second_int, char *arraydata,
                                   int size)
 {
cb5ab02a
     int inoutsize;
e5f2731c
 
     inoutsize = ffurl_read_complete(rt->stream, arraydata,
                                     RTMP_HANDSHAKE_PACKET_SIZE);
     if (inoutsize <= 0)
         return AVERROR(EIO);
     if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) {
         av_log(rt, AV_LOG_ERROR, "Erroneous Message size %d"
                " not following standard\n", (int)inoutsize);
         return AVERROR(EINVAL);
     }
 
     *first_int  = AV_RB32(arraydata);
     *second_int = AV_RB32(arraydata + 4);
     return 0;
 }
 
 static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int,
                                uint32_t second_int, char *arraydata, int size)
 {
cb5ab02a
     int inoutsize;
e5f2731c
 
     AV_WB32(arraydata, first_int);
fe0337e8
     AV_WB32(arraydata + 4, second_int);
e5f2731c
     inoutsize = ffurl_write(rt->stream, arraydata,
                             RTMP_HANDSHAKE_PACKET_SIZE);
     if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) {
         av_log(rt, AV_LOG_ERROR, "Unable to write answer\n");
         return AVERROR(EIO);
     }
 
     return 0;
 }
 
 /**
  * rtmp handshake server side
  */
 static int rtmp_server_handshake(URLContext *s, RTMPContext *rt)
 {
     uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE];
     uint32_t hs_epoch;
     uint32_t hs_my_epoch;
     uint8_t hs_c1[RTMP_HANDSHAKE_PACKET_SIZE];
     uint8_t hs_s1[RTMP_HANDSHAKE_PACKET_SIZE];
     uint32_t zeroes;
     uint32_t temp       = 0;
     int randomidx       = 0;
cb5ab02a
     int inoutsize       = 0;
e5f2731c
     int ret;
 
     inoutsize = ffurl_read_complete(rt->stream, buffer, 1);       // Receive C0
     if (inoutsize <= 0) {
         av_log(s, AV_LOG_ERROR, "Unable to read handshake\n");
         return AVERROR(EIO);
     }
     // Check Version
     if (buffer[0] != 3) {
         av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n");
         return AVERROR(EIO);
     }
     if (ffurl_write(rt->stream, buffer, 1) <= 0) {                 // Send S0
         av_log(s, AV_LOG_ERROR,
                "Unable to write answer - RTMP S0\n");
         return AVERROR(EIO);
     }
     /* Receive C1 */
     ret = rtmp_receive_hs_packet(rt, &hs_epoch, &zeroes, hs_c1,
                                  RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n");
         return ret;
     }
     /* Send S1 */
     /* By now same epoch will be sent */
     hs_my_epoch = hs_epoch;
     /* Generate random */
5a75924d
     for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE);
e5f2731c
          randomidx += 4)
5a75924d
         AV_WB32(hs_s1 + randomidx, av_get_random_seed());
e5f2731c
 
     ret = rtmp_send_hs_packet(rt, hs_my_epoch, 0, hs_s1,
                               RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n");
         return ret;
     }
     /* Send S2 */
     ret = rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1,
                               RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake S2 Error\n");
         return ret;
     }
     /* Receive C2 */
     ret = rtmp_receive_hs_packet(rt, &temp, &zeroes, buffer,
                                  RTMP_HANDSHAKE_PACKET_SIZE);
     if (ret) {
         av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n");
         return ret;
     }
     if (temp != hs_my_epoch)
         av_log(s, AV_LOG_WARNING,
                "Erroneous C2 Message epoch does not match up with C1 epoch\n");
     if (memcmp(buffer + 8, hs_s1 + 8,
                RTMP_HANDSHAKE_PACKET_SIZE - 8))
         av_log(s, AV_LOG_WARNING,
                "Erroneous C2 Message random does not match up\n");
 
     return 0;
 }
 
7be2a7d8
 static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     int ret;
 
ba5393a6
     if (pkt->size < 4) {
7be2a7d8
         av_log(s, AV_LOG_ERROR,
e49e6b64
                "Too short chunk size change packet (%d)\n",
ba5393a6
                pkt->size);
e7ea6883
         return AVERROR_INVALIDDATA;
7be2a7d8
     }
 
     if (!rt->is_input) {
f5ce90f2
         /* Send the same chunk size change packet back to the server,
          * setting the outgoing chunk size to the same as the incoming one. */
         if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
84a125c4
                                         &rt->prev_pkt[1], &rt->nb_prev_pkt[1])) < 0)
7be2a7d8
             return ret;
f5ce90f2
         rt->out_chunk_size = AV_RB32(pkt->data);
7be2a7d8
     }
 
f5ce90f2
     rt->in_chunk_size = AV_RB32(pkt->data);
     if (rt->in_chunk_size <= 0) {
         av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n",
                rt->in_chunk_size);
e7ea6883
         return AVERROR_INVALIDDATA;
7be2a7d8
     }
f5ce90f2
     av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n",
            rt->in_chunk_size);
7be2a7d8
 
     return 0;
 }
 
0ffd5161
 static int handle_ping(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     int t, ret;
 
ba5393a6
     if (pkt->size < 2) {
8ea1459b
         av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
ba5393a6
                pkt->size);
8ea1459b
         return AVERROR_INVALIDDATA;
     }
 
0ffd5161
     t = AV_RB16(pkt->data);
     if (t == 6) {
         if ((ret = gen_pong(s, rt, pkt)) < 0)
             return ret;
635ac8e1
     } else if (t == 26) {
         if (rt->swfsize) {
             if ((ret = gen_swf_verification(s, rt)) < 0)
                 return ret;
         } else {
             av_log(s, AV_LOG_WARNING, "Ignoring SWFVerification request.\n");
         }
0ffd5161
     }
 
     return 0;
 }
 
912ecc9a
 static int handle_client_bw(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
912ecc9a
     RTMPContext *rt = s->priv_data;
 
ba5393a6
     if (pkt->size < 4) {
912ecc9a
         av_log(s, AV_LOG_ERROR,
                "Client bandwidth report packet is less than 4 bytes long (%d)\n",
ba5393a6
                pkt->size);
088a82bb
         return AVERROR_INVALIDDATA;
912ecc9a
     }
abf77a24
 
     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;
912ecc9a
 
     return 0;
 }
 
9b498148
 static int handle_server_bw(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
 
ba5393a6
     if (pkt->size < 4) {
2357f606
         av_log(s, AV_LOG_ERROR,
                "Too short server bandwidth report packet (%d)\n",
ba5393a6
                pkt->size);
2357f606
         return AVERROR_INVALIDDATA;
     }
 
9b498148
     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);
be8f9492
         return AVERROR_INVALIDDATA;
9b498148
     }
     av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw);
 
     return 0;
 }
 
08225d01
 static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt,
                          const char *opaque, const char *challenge)
 {
     uint8_t hash[16];
     char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10];
     struct AVMD5 *md5 = av_md5_alloc();
     if (!md5)
         return AVERROR(ENOMEM);
 
     snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed());
 
     av_md5_init(md5);
     av_md5_update(md5, user, strlen(user));
     av_md5_update(md5, salt, strlen(salt));
     av_md5_update(md5, rt->password, strlen(rt->password));
     av_md5_final(md5, hash);
     av_base64_encode(hashstr, sizeof(hashstr), hash,
                      sizeof(hash));
     av_md5_init(md5);
     av_md5_update(md5, hashstr, strlen(hashstr));
     if (opaque)
         av_md5_update(md5, opaque, strlen(opaque));
     else if (challenge)
         av_md5_update(md5, challenge, strlen(challenge));
     av_md5_update(md5, challenge2, strlen(challenge2));
     av_md5_final(md5, hash);
     av_base64_encode(hashstr, sizeof(hashstr), hash,
                      sizeof(hash));
     snprintf(rt->auth_params, sizeof(rt->auth_params),
              "?authmod=%s&user=%s&challenge=%s&response=%s",
              "adobe", user, challenge2, hashstr);
     if (opaque)
         av_strlcatf(rt->auth_params, sizeof(rt->auth_params),
                     "&opaque=%s", opaque);
 
     av_free(md5);
     return 0;
 }
 
c1ea44c5
 static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce)
 {
     uint8_t hash[16];
     char hashstr1[33], hashstr2[33];
     const char *realm = "live";
     const char *method = "publish";
     const char *qop = "auth";
     const char *nc = "00000001";
     char cnonce[10];
     struct AVMD5 *md5 = av_md5_alloc();
     if (!md5)
         return AVERROR(ENOMEM);
 
     snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed());
 
     av_md5_init(md5);
     av_md5_update(md5, user, strlen(user));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, realm, strlen(realm));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, rt->password, strlen(rt->password));
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr1, hash, 16, 1);
     hashstr1[32] = '\0';
 
     av_md5_init(md5);
     av_md5_update(md5, method, strlen(method));
     av_md5_update(md5, ":/", 2);
     av_md5_update(md5, rt->app, strlen(rt->app));
6454c44f
     if (!strchr(rt->app, '/'))
         av_md5_update(md5, "/_definst_", strlen("/_definst_"));
c1ea44c5
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr2, hash, 16, 1);
     hashstr2[32] = '\0';
 
     av_md5_init(md5);
     av_md5_update(md5, hashstr1, strlen(hashstr1));
     av_md5_update(md5, ":", 1);
     if (nonce)
         av_md5_update(md5, nonce, strlen(nonce));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, nc, strlen(nc));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, cnonce, strlen(cnonce));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, qop, strlen(qop));
     av_md5_update(md5, ":", 1);
     av_md5_update(md5, hashstr2, strlen(hashstr2));
     av_md5_final(md5, hash);
     ff_data_to_hex(hashstr1, hash, 16, 1);
 
     snprintf(rt->auth_params, sizeof(rt->auth_params),
              "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s",
              "llnw", user, nonce, cnonce, nc, hashstr1);
 
     av_free(md5);
     return 0;
 }
 
08225d01
 static int handle_connect_error(URLContext *s, const char *desc)
 {
     RTMPContext *rt = s->priv_data;
c1ea44c5
     char buf[300], *ptr, authmod[15];
08225d01
     int i = 0, ret = 0;
     const char *user = "", *salt = "", *opaque = NULL,
c1ea44c5
                *challenge = NULL, *cptr = NULL, *nonce = NULL;
08225d01
 
c1ea44c5
     if (!(cptr = strstr(desc, "authmod=adobe")) &&
         !(cptr = strstr(desc, "authmod=llnw"))) {
08225d01
         av_log(s, AV_LOG_ERROR,
                "Unknown connect error (unsupported authentication method?)\n");
         return AVERROR_UNKNOWN;
     }
c1ea44c5
     cptr += strlen("authmod=");
     while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1)
         authmod[i++] = *cptr++;
     authmod[i] = '\0';
08225d01
 
     if (!rt->username[0] || !rt->password[0]) {
         av_log(s, AV_LOG_ERROR, "No credentials set\n");
         return AVERROR_UNKNOWN;
     }
 
     if (strstr(desc, "?reason=authfailed")) {
         av_log(s, AV_LOG_ERROR, "Incorrect username/password\n");
         return AVERROR_UNKNOWN;
     } else if (strstr(desc, "?reason=nosuchuser")) {
         av_log(s, AV_LOG_ERROR, "Incorrect username\n");
         return AVERROR_UNKNOWN;
     }
 
     if (rt->auth_tried) {
         av_log(s, AV_LOG_ERROR, "Authentication failed\n");
         return AVERROR_UNKNOWN;
     }
 
     rt->auth_params[0] = '\0';
 
     if (strstr(desc, "code=403 need auth")) {
         snprintf(rt->auth_params, sizeof(rt->auth_params),
c1ea44c5
                  "?authmod=%s&user=%s", authmod, rt->username);
08225d01
         return 0;
     }
 
     if (!(cptr = strstr(desc, "?reason=needauth"))) {
         av_log(s, AV_LOG_ERROR, "No auth parameters found\n");
         return AVERROR_UNKNOWN;
     }
 
     av_strlcpy(buf, cptr + 1, sizeof(buf));
     ptr = buf;
 
     while (ptr) {
         char *next  = strchr(ptr, '&');
         char *value = strchr(ptr, '=');
         if (next)
             *next++ = '\0';
e83aae28
         if (value) {
08225d01
             *value++ = '\0';
e83aae28
             if (!strcmp(ptr, "user")) {
                 user = value;
             } else if (!strcmp(ptr, "salt")) {
                 salt = value;
             } else if (!strcmp(ptr, "opaque")) {
                 opaque = value;
             } else if (!strcmp(ptr, "challenge")) {
                 challenge = value;
             } else if (!strcmp(ptr, "nonce")) {
                 nonce = value;
93cade48
             } else {
                 av_log(s, AV_LOG_INFO, "Ignoring unsupported var %s\n", ptr);
e83aae28
             }
         } else {
             av_log(s, AV_LOG_WARNING, "Variable %s has NULL value\n", ptr);
08225d01
         }
         ptr = next;
     }
 
c1ea44c5
     if (!strcmp(authmod, "adobe")) {
a5e6080a
         if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0)
c1ea44c5
             return ret;
     } else {
         if ((ret = do_llnw_auth(rt, user, nonce)) < 0)
             return ret;
     }
08225d01
 
     rt->auth_tried = 1;
     return 0;
 }
 
3eebc1e1
 static int handle_invoke_error(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
08225d01
     RTMPContext *rt = s->priv_data;
ba5393a6
     const uint8_t *data_end = pkt->data + pkt->size;
fb7e7808
     char *tracked_method = NULL;
     int level = AV_LOG_ERROR;
3eebc1e1
     uint8_t tmpstr[256];
fb7e7808
     int ret;
 
     if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0)
         return ret;
3eebc1e1
 
     if (!ff_amf_get_field_value(pkt->data + 9, data_end,
                                 "description", tmpstr, sizeof(tmpstr))) {
7011a42b
         if (tracked_method && (!strcmp(tracked_method, "_checkbw")      ||
                                !strcmp(tracked_method, "releaseStream") ||
                                !strcmp(tracked_method, "FCSubscribe")   ||
                                !strcmp(tracked_method, "FCPublish"))) {
             /* Gracefully ignore Adobe-specific historical artifact errors. */
fb7e7808
             level = AV_LOG_WARNING;
             ret = 0;
a4903911
         } else if (tracked_method && !strcmp(tracked_method, "getStreamLength")) {
             level = rt->live ? AV_LOG_DEBUG : AV_LOG_WARNING;
09711545
             ret = 0;
08225d01
         } else if (tracked_method && !strcmp(tracked_method, "connect")) {
             ret = handle_connect_error(s, tmpstr);
             if (!ret) {
                 rt->do_reconnect = 1;
                 level = AV_LOG_VERBOSE;
             }
fb7e7808
         } else
c76daa89
             ret = AVERROR_UNKNOWN;
fb7e7808
         av_log(s, level, "Server error: %s\n", tmpstr);
3eebc1e1
     }
 
fb7e7808
     av_free(tracked_method);
     return ret;
3eebc1e1
 }
 
97d35fa8
 static int write_begin(URLContext *s)
 {
     RTMPContext *rt = s->priv_data;
     PutByteContext pbc;
     RTMPPacket spkt = { 0 };
     int ret;
 
     // Send Stream Begin 1
     if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL,
                                      RTMP_PT_PING, 0, 6)) < 0) {
         av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
         return ret;
     }
 
     bytestream2_init_writer(&pbc, spkt.data, spkt.size);
     bytestream2_put_be16(&pbc, 0);          // 0 -> Stream Begin
     bytestream2_put_be32(&pbc, rt->nb_streamid);
 
     ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
97d35fa8
 
     ff_rtmp_packet_destroy(&spkt);
 
     return ret;
 }
 
 static int write_status(URLContext *s, RTMPPacket *pkt,
                         const char *status, const char *filename)
 {
     RTMPContext *rt = s->priv_data;
     RTMPPacket spkt = { 0 };
     char statusmsg[128];
     uint8_t *pp;
     int ret;
 
     if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                      RTMP_PT_INVOKE, 0,
                                      RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
         av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
         return ret;
     }
 
     pp = spkt.data;
     spkt.extra = pkt->extra;
     ff_amf_write_string(&pp, "onStatus");
     ff_amf_write_number(&pp, 0);
     ff_amf_write_null(&pp);
 
     ff_amf_write_object_start(&pp);
     ff_amf_write_field_name(&pp, "level");
     ff_amf_write_string(&pp, "status");
     ff_amf_write_field_name(&pp, "code");
     ff_amf_write_string(&pp, status);
     ff_amf_write_field_name(&pp, "description");
     snprintf(statusmsg, sizeof(statusmsg),
              "%s is now published", filename);
     ff_amf_write_string(&pp, statusmsg);
     ff_amf_write_field_name(&pp, "details");
     ff_amf_write_string(&pp, filename);
     ff_amf_write_field_name(&pp, "clientid");
     snprintf(statusmsg, sizeof(statusmsg), "%s", LIBAVFORMAT_IDENT);
     ff_amf_write_string(&pp, statusmsg);
     ff_amf_write_object_end(&pp);
 
     spkt.size = pp - spkt.data;
     ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
97d35fa8
     ff_rtmp_packet_destroy(&spkt);
 
     return ret;
 }
 
e5f2731c
 static int send_invoke_response(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
     double seqnum;
     char filename[64];
     char command[64];
     int stringlen;
     char *pchar;
     const uint8_t *p = pkt->data;
     uint8_t *pp      = NULL;
     RTMPPacket spkt  = { 0 };
     GetByteContext gbc;
     int ret;
 
ba5393a6
     bytestream2_init(&gbc, p, pkt->size);
e5f2731c
     if (ff_amf_read_string(&gbc, command, sizeof(command),
                            &stringlen)) {
         av_log(s, AV_LOG_ERROR, "Error in PT_INVOKE\n");
         return AVERROR_INVALIDDATA;
     }
 
     ret = ff_amf_read_number(&gbc, &seqnum);
     if (ret)
         return ret;
     ret = ff_amf_read_null(&gbc);
     if (ret)
         return ret;
     if (!strcmp(command, "FCPublish") ||
         !strcmp(command, "publish")) {
         ret = ff_amf_read_string(&gbc, filename,
                                  sizeof(filename), &stringlen);
         // check with url
         if (s->filename) {
             pchar = strrchr(s->filename, '/');
             if (!pchar) {
                 av_log(s, AV_LOG_WARNING,
                        "Unable to find / in url %s, bad format\n",
                        s->filename);
                 pchar = s->filename;
             }
             pchar++;
             if (strcmp(pchar, filename))
                 av_log(s, AV_LOG_WARNING, "Unexpected stream %s, expecting"
                        " %s\n", filename, pchar);
         }
         rt->state = STATE_RECEIVING;
     }
 
     if (!strcmp(command, "FCPublish")) {
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                          RTMP_PT_INVOKE, 0,
                                          RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         pp = spkt.data;
         ff_amf_write_string(&pp, "onFCPublish");
     } else if (!strcmp(command, "publish")) {
97d35fa8
         ret = write_begin(s);
e5f2731c
         if (ret < 0)
             return ret;
 
         // Send onStatus(NetStream.Publish.Start)
97d35fa8
         return write_status(s, pkt, "NetStream.Publish.Start",
                            filename);
ffb7669e
     } else if (!strcmp(command, "play")) {
         ret = write_begin(s);
         if (ret < 0)
             return ret;
         rt->state = STATE_SENDING;
         return write_status(s, pkt, "NetStream.Play.Start",
                             filename);
e5f2731c
     } else {
         if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL,
                                          RTMP_PT_INVOKE, 0,
                                          RTMP_PKTDATA_DEFAULT_SIZE)) < 0) {
             av_log(s, AV_LOG_ERROR, "Unable to create response packet\n");
             return ret;
         }
         pp = spkt.data;
         ff_amf_write_string(&pp, "_result");
         ff_amf_write_number(&pp, seqnum);
         ff_amf_write_null(&pp);
         if (!strcmp(command, "createStream")) {
             rt->nb_streamid++;
             if (rt->nb_streamid == 0 || rt->nb_streamid == 2)
                 rt->nb_streamid++; /* Values 0 and 2 are reserved */
             ff_amf_write_number(&pp, rt->nb_streamid);
             /* By now we don't control which streams are removed in
              * deleteStream. There is no stream creation control
              * if a client creates more than 2^32 - 2 streams. */
         }
     }
ba5393a6
     spkt.size = pp - spkt.data;
e5f2731c
     ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size,
84a125c4
                                &rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
e5f2731c
     ff_rtmp_packet_destroy(&spkt);
     return ret;
 }
 
324b23dd
 /**
  * Read the AMF_NUMBER response ("_result") to a function call
  * (e.g. createStream()). This response should be made up of the AMF_STRING
  * "result", a NULL object and then the response encoded as AMF_NUMBER. On a
  * successful response, we will return set the value to number (otherwise number
  * will not be changed).
  *
  * @return 0 if reading the value succeeds, negative value otherwiss
  */
 static int read_number_result(RTMPPacket *pkt, double *number)
 {
     // We only need to fit "_result" in this.
     uint8_t strbuffer[8];
     int stringlen;
     double numbuffer;
     GetByteContext gbc;
 
     bytestream2_init(&gbc, pkt->data, pkt->size);
 
     // Value 1/4: "_result" as AMF_STRING
     if (ff_amf_read_string(&gbc, strbuffer, sizeof(strbuffer), &stringlen))
         return AVERROR_INVALIDDATA;
     if (strcmp(strbuffer, "_result"))
         return AVERROR_INVALIDDATA;
     // Value 2/4: The callee reference number
     if (ff_amf_read_number(&gbc, &numbuffer))
         return AVERROR_INVALIDDATA;
     // Value 3/4: Null
     if (ff_amf_read_null(&gbc))
         return AVERROR_INVALIDDATA;
     // Value 4/4: The resonse as AMF_NUMBER
     if (ff_amf_read_number(&gbc, &numbuffer))
         return AVERROR_INVALIDDATA;
     else
         *number = numbuffer;
 
     return 0;
 }
 
5e6001db
 static int handle_invoke_result(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
f89584ca
     char *tracked_method = NULL;
     int ret = 0;
9fd6b843
 
a8103503
     if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0)
5e6001db
         return ret;
9fd6b843
 
5e6001db
     if (!tracked_method) {
         /* Ignore this reply when the current method is not tracked. */
         return ret;
     }
f89584ca
 
5718e348
     if (!strcmp(tracked_method, "connect")) {
5e6001db
         if (!rt->is_input) {
             if ((ret = gen_release_stream(s, rt)) < 0)
                 goto fail;
f89584ca
 
5e6001db
             if ((ret = gen_fcpublish_stream(s, rt)) < 0)
                 goto fail;
         } else {
             if ((ret = gen_server_bw(s, rt)) < 0)
                 goto fail;
f89584ca
         }
 
5e6001db
         if ((ret = gen_create_stream(s, rt)) < 0)
             goto fail;
f89584ca
 
5e6001db
         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;
f89584ca
             }
9fd6b843
         }
5718e348
     } else if (!strcmp(tracked_method, "createStream")) {
324b23dd
         double stream_id;
         if (read_number_result(pkt, &stream_id)) {
5e6001db
             av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n");
         } else {
324b23dd
             rt->stream_id = stream_id;
6d1c9945
         }
5e6001db
 
         if (!rt->is_input) {
             if ((ret = gen_publish(s, rt)) < 0)
                 goto fail;
         } else {
e65c776d
             if (rt->live != -1) {
                 if ((ret = gen_get_stream_length(s, rt)) < 0)
                     goto fail;
             }
5e6001db
             if ((ret = gen_play(s, rt)) < 0)
                 goto fail;
             if ((ret = gen_buffer_time(s, rt)) < 0)
                 goto fail;
6d1c9945
         }
e65c776d
     } else if (!strcmp(tracked_method, "getStreamLength")) {
         if (read_number_result(pkt, &rt->duration)) {
             av_log(s, AV_LOG_WARNING, "Unexpected reply on getStreamLength()\n");
         }
5e6001db
     }
 
 fail:
     av_free(tracked_method);
     return ret;
 }
 
71036a3a
 static int handle_invoke_status(URLContext *s, RTMPPacket *pkt)
9fd6b843
 {
6d1c9945
     RTMPContext *rt = s->priv_data;
ba5393a6
     const uint8_t *data_end = pkt->data + pkt->size;
ba77757a
     const uint8_t *ptr = pkt->data + RTMP_HEADER;
71036a3a
     uint8_t tmpstr[256];
9fd6b843
     int i, t;
71036a3a
 
     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")) {
a6b36132
         t = ff_amf_get_field_value(ptr, data_end,
                                    "description", tmpstr, sizeof(tmpstr));
         if (t || !tmpstr[0])
             t = ff_amf_get_field_value(ptr, data_end, "code",
                                        tmpstr, sizeof(tmpstr));
         if (!t)
71036a3a
             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;
0d6fa397
     if (!t && !strcmp(tmpstr, "NetStream.Seek.Notify")) rt->state = STATE_PLAYING;
71036a3a
 
     return 0;
 }
 
 static int handle_invoke(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt = s->priv_data;
f89584ca
     int ret = 0;
9fd6b843
 
6d1c9945
     //TODO: check for the messages sent for wrong state?
5718e348
     if (ff_amf_match_string(pkt->data, pkt->size, "_error")) {
3eebc1e1
         if ((ret = handle_invoke_error(s, pkt)) < 0)
             return ret;
5718e348
     } else if (ff_amf_match_string(pkt->data, pkt->size, "_result")) {
5e6001db
         if ((ret = handle_invoke_result(s, pkt)) < 0)
f89584ca
             return ret;
5718e348
     } else if (ff_amf_match_string(pkt->data, pkt->size, "onStatus")) {
71036a3a
         if ((ret = handle_invoke_status(s, pkt)) < 0)
             return ret;
5718e348
     } else if (ff_amf_match_string(pkt->data, pkt->size, "onBWDone")) {
6d1c9945
         if ((ret = gen_check_bw(s, rt)) < 0)
             return ret;
5718e348
     } else if (ff_amf_match_string(pkt->data, pkt->size, "releaseStream") ||
                ff_amf_match_string(pkt->data, pkt->size, "FCPublish")     ||
                ff_amf_match_string(pkt->data, pkt->size, "publish")       ||
ffb7669e
                ff_amf_match_string(pkt->data, pkt->size, "play")          ||
5718e348
                ff_amf_match_string(pkt->data, pkt->size, "_checkbw")      ||
                ff_amf_match_string(pkt->data, pkt->size, "createStream")) {
a601eb95
         if ((ret = send_invoke_response(s, pkt)) < 0)
e5f2731c
             return ret;
6d1c9945
     }
 
f89584ca
     return ret;
6d1c9945
 }
 
e40a0e82
 static int update_offset(RTMPContext *rt, int size)
 {
     int old_flv_size;
e5f2731c
 
92ed83e3
     // generate packet header and put data into buffer for FLV demuxer
     if (rt->flv_off < rt->flv_size) {
b97b1adb
         // There is old unread data in the buffer, thus append at the end
92ed83e3
         old_flv_size  = rt->flv_size;
24fee953
         rt->flv_size += size;
92ed83e3
     } else {
b97b1adb
         // All data has been read, write the new data at the start of the buffer
92ed83e3
         old_flv_size = 0;
24fee953
         rt->flv_size = size;
92ed83e3
         rt->flv_off  = 0;
e5f2731c
     }
fbd54827
 
e40a0e82
     return old_flv_size;
 }
 
 static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip)
 {
     int old_flv_size, ret;
     PutByteContext pbc;
     const uint8_t *data = pkt->data + skip;
     const int size      = pkt->size - skip;
     uint32_t ts         = pkt->timestamp;
 
59cb5747
     if (pkt->type == RTMP_PT_AUDIO) {
         rt->has_audio = 1;
     } else if (pkt->type == RTMP_PT_VIDEO) {
         rt->has_video = 1;
     }
 
24fee953
     old_flv_size = update_offset(rt, size + 15);
e40a0e82
 
72540e51
     if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) {
         rt->flv_size = rt->flv_off = 0;
e40a0e82
         return ret;
72540e51
     }
fbd54827
     bytestream2_init_writer(&pbc, rt->flv_data, rt->flv_size);
     bytestream2_skip_p(&pbc, old_flv_size);
     bytestream2_put_byte(&pbc, pkt->type);
e40a0e82
     bytestream2_put_be24(&pbc, size);
fbd54827
     bytestream2_put_be24(&pbc, ts);
     bytestream2_put_byte(&pbc, ts >> 24);
     bytestream2_put_be24(&pbc, 0);
e40a0e82
     bytestream2_put_buffer(&pbc, data, size);
fbd54827
     bytestream2_put_be32(&pbc, 0);
 
e5f2731c
     return 0;
 }
 
e40a0e82
 static int handle_notify(URLContext *s, RTMPPacket *pkt)
 {
     RTMPContext *rt  = s->priv_data;
     uint8_t commandbuffer[64];
     char statusmsg[128];
     int stringlen, ret, skip = 0;
     GetByteContext gbc;
 
     bytestream2_init(&gbc, pkt->data, pkt->size);
     if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer),
                            &stringlen))
         return AVERROR_INVALIDDATA;
 
59cb5747
     if (!strcmp(commandbuffer, "onMetaData")) {
         // metadata properties should be stored in a mixed array
         if (bytestream2_get_byte(&gbc) == AMF_DATA_TYPE_MIXEDARRAY) {
             // We have found a metaData Array so flv can determine the streams
             // from this.
             rt->received_metadata = 1;
             // skip 32-bit max array index
             bytestream2_skip(&gbc, 4);
             while (bytestream2_get_bytes_left(&gbc) > 3) {
                 if (ff_amf_get_string(&gbc, statusmsg, sizeof(statusmsg),
                                       &stringlen))
                     return AVERROR_INVALIDDATA;
                 // We do not care about the content of the property (yet).
                 stringlen = ff_amf_tag_size(gbc.buffer, gbc.buffer_end);
                 if (stringlen < 0)
                     return AVERROR_INVALIDDATA;
                 bytestream2_skip(&gbc, stringlen);
 
                 // The presence of the following properties indicates that the
                 // respective streams are present.
                 if (!strcmp(statusmsg, "videocodecid")) {
                     rt->has_video = 1;
                 }
                 if (!strcmp(statusmsg, "audiocodecid")) {
                     rt->has_audio = 1;
                 }
             }
             if (bytestream2_get_be24(&gbc) != AMF_END_OF_OBJECT)
                 return AVERROR_INVALIDDATA;
         }
     }
 
e40a0e82
     // Skip the @setDataFrame string and validate it is a notification
     if (!strcmp(commandbuffer, "@setDataFrame")) {
         skip = gbc.buffer - pkt->data;
         ret = ff_amf_read_string(&gbc, statusmsg,
                                  sizeof(statusmsg), &stringlen);
         if (ret < 0)
             return AVERROR_INVALIDDATA;
     }
 
     return append_flv_data(rt, pkt, skip);
 }
 
6d1c9945
 /**
  * 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) {
fb96c1c5
     case RTMP_PT_BYTES_READ:
1a3eb042
         av_log(s, AV_LOG_TRACE, "received bytes read report\n");
fb96c1c5
         break;
6d1c9945
     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;
9fd6b843
         break;
08e087cc
     case RTMP_PT_VIDEO:
     case RTMP_PT_AUDIO:
9c9c21ea
     case RTMP_PT_METADATA:
e5f2731c
     case RTMP_PT_NOTIFY:
9c9c21ea
         /* Audio, Video and Metadata packets are parsed in get_packet() */
08e087cc
         break;
9ff930aa
     default:
         av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type);
         break;
9fd6b843
     }
     return 0;
 }
 
58404738
 static int handle_metadata(RTMPContext *rt, RTMPPacket *pkt)
 {
     int ret, old_flv_size, type;
     const uint8_t *next;
     uint8_t *p;
     uint32_t size;
     uint32_t ts, cts, pts = 0;
 
     old_flv_size = update_offset(rt, pkt->size);
 
72540e51
     if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) {
         rt->flv_size = rt->flv_off = 0;
58404738
         return ret;
72540e51
     }
58404738
 
     next = pkt->data;
     p    = rt->flv_data + old_flv_size;
 
     /* copy data while rewriting timestamps */
     ts = pkt->timestamp;
 
ba77757a
     while (next - pkt->data < pkt->size - RTMP_HEADER) {
58404738
         type = bytestream_get_byte(&next);
         size = bytestream_get_be24(&next);
         cts  = bytestream_get_be24(&next);
         cts |= bytestream_get_byte(&next) << 24;
         if (!pts)
             pts = cts;
         ts += cts - pts;
         pts = cts;
cd818b3a
         if (size + 3 + 4 > pkt->data + pkt->size - next)
             break;
58404738
         bytestream_put_byte(&p, type);
         bytestream_put_be24(&p, size);
         bytestream_put_be24(&p, ts);
         bytestream_put_byte(&p, ts >> 24);
         memcpy(p, next, size + 3 + 4);
         next += size + 3 + 4;
         p    += size + 3 + 4;
     }
8921e32f
     if (p != rt->flv_data + rt->flv_size) {
         av_log(NULL, AV_LOG_WARNING, "Incomplete flv packets in "
                                      "RTMP_PT_METADATA packet\n");
         rt->flv_size = p - rt->flv_data;
     }
58404738
 
     return 0;
 }
 
9fd6b843
 /**
49bd8e4b
  * Interact with the server by receiving and sending RTMP packets until
9fd6b843
  * there is some significant data (media data or expected status notification).
  *
  * @param s          reading context
1d8041b3
  * @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)
9fd6b843
  * @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;
 
72b870b9
     if (rt->state == STATE_STOPPED)
         return AVERROR_EOF;
 
e07c92e4
     for (;;) {
271c869c
         RTMPPacket rpkt = { 0 };
9fd6b843
         if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt,
84a125c4
                                        rt->in_chunk_size, &rt->prev_pkt[0],
                                        &rt->nb_prev_pkt[0])) <= 0) {
b381a823
             if (ret == 0) {
9fd6b843
                 return AVERROR(EAGAIN);
             } else {
                 return AVERROR(EIO);
             }
         }
f4cd8b80
 
         // Track timestamp for later use
         rt->last_timestamp = rpkt.timestamp;
 
bf7c1719
         rt->bytes_read += ret;
0849a0eb
         if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) {
7f804085
             av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n");
f645f1d6
             if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0)
                 return ret;
bf7c1719
             rt->last_bytes_read = rt->bytes_read;
         }
9fd6b843
 
         ret = rtmp_parse_result(s, rt, &rpkt);
0d6fa397
 
         // At this point we must check if we are in the seek state and continue
         // with the next packet. handle_invoke will get us out of this state
         // when the right message is encountered
         if (rt->state == STATE_SEEKING) {
             ff_rtmp_packet_destroy(&rpkt);
             // We continue, let the natural flow of things happen:
             // AVERROR(EAGAIN) or handle_invoke gets us out of here
             continue;
         }
 
9fd6b843
         if (ret < 0) {//serious error in current packet
             ff_rtmp_packet_destroy(&rpkt);
f645f1d6
             return ret;
9fd6b843
         }
08225d01
         if (rt->do_reconnect && for_header) {
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
72b870b9
         if (rt->state == STATE_STOPPED) {
             ff_rtmp_packet_destroy(&rpkt);
             return AVERROR_EOF;
         }
e5f2731c
         if (for_header && (rt->state == STATE_PLAYING    ||
                            rt->state == STATE_PUBLISHING ||
ffb7669e
                            rt->state == STATE_SENDING    ||
e5f2731c
                            rt->state == STATE_RECEIVING)) {
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
ba5393a6
         if (!rpkt.size || !rt->is_input) {
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
             continue;
         }
e40a0e82
         if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO) {
             ret = append_flv_data(rt, &rpkt, 0);
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
e40a0e82
             return ret;
e5f2731c
         } else if (rpkt.type == RTMP_PT_NOTIFY) {
             ret = handle_notify(s, &rpkt);
             ff_rtmp_packet_destroy(&rpkt);
e40a0e82
             return ret;
9fd6b843
         } else if (rpkt.type == RTMP_PT_METADATA) {
58404738
             ret = handle_metadata(rt, &rpkt);
9fd6b843
             ff_rtmp_packet_destroy(&rpkt);
             return 0;
         }
         ff_rtmp_packet_destroy(&rpkt);
     }
 }
 
 static int rtmp_close(URLContext *h)
 {
     RTMPContext *rt = h->priv_data;
8583b142
     int ret = 0, i, j;
9fd6b843
 
e07c92e4
     if (!rt->is_input) {
6bf22e18
         rt->flv_data = NULL;
ba5393a6
         if (rt->out_pkt.size)
6bf22e18
             ff_rtmp_packet_destroy(&rt->out_pkt);
615c2879
         if (rt->state > STATE_FCPUBLISH)
f645f1d6
             ret = gen_fcunpublish_stream(h, rt);
6bf22e18
     }
615c2879
     if (rt->state > STATE_HANDSHAKED)
f645f1d6
         ret = gen_delete_stream(h, rt);
84a125c4
     for (i = 0; i < 2; i++) {
         for (j = 0; j < rt->nb_prev_pkt[i]; j++)
8583b142
             ff_rtmp_packet_destroy(&rt->prev_pkt[i][j]);
84a125c4
         av_freep(&rt->prev_pkt[i]);
     }
6bf22e18
 
f89584ca
     free_tracked_methods(rt);
9fd6b843
     av_freep(&rt->flv_data);
e52a9145
     ffurl_close(rt->stream);
f645f1d6
     return ret;
9fd6b843
 }
 
 /**
e65c776d
  * Insert a fake onMetadata packet into the FLV stream to notify the FLV
  * demuxer about the duration of the stream.
  *
  * This should only be done if there was no real onMetadata packet sent by the
  * server at the start of the stream and if we were able to retrieve a valid
  * duration via a getStreamLength call.
  *
  * @return 0 for successful operation, negative value in case of error
  */
 static int inject_fake_duration_metadata(RTMPContext *rt)
 {
     // We need to insert the metdata packet directly after the FLV
     // header, i.e. we need to move all other already read data by the
     // size of our fake metadata packet.
 
     uint8_t* p;
     // Keep old flv_data pointer
     uint8_t* old_flv_data = rt->flv_data;
     // Allocate a new flv_data pointer with enough space for the additional package
     if (!(rt->flv_data = av_malloc(rt->flv_size + 55))) {
         rt->flv_data = old_flv_data;
         return AVERROR(ENOMEM);
     }
 
     // Copy FLV header
     memcpy(rt->flv_data, old_flv_data, 13);
     // Copy remaining packets
     memcpy(rt->flv_data + 13 + 55, old_flv_data + 13, rt->flv_size - 13);
     // Increase the size by the injected packet
     rt->flv_size += 55;
     // Delete the old FLV data
03b84f2f
     av_freep(&old_flv_data);
e65c776d
 
     p = rt->flv_data + 13;
     bytestream_put_byte(&p, FLV_TAG_TYPE_META);
     bytestream_put_be24(&p, 40); // size of data part (sum of all parts below)
     bytestream_put_be24(&p, 0);  // timestamp
     bytestream_put_be32(&p, 0);  // reserved
 
     // first event name as a string
     bytestream_put_byte(&p, AMF_DATA_TYPE_STRING);
     // "onMetaData" as AMF string
     bytestream_put_be16(&p, 10);
     bytestream_put_buffer(&p, "onMetaData", 10);
 
     // mixed array (hash) with size and string/type/data tuples
     bytestream_put_byte(&p, AMF_DATA_TYPE_MIXEDARRAY);
     bytestream_put_be32(&p, 1); // metadata_count
 
     // "duration" as AMF string
     bytestream_put_be16(&p, 8);
     bytestream_put_buffer(&p, "duration", 8);
     bytestream_put_byte(&p, AMF_DATA_TYPE_NUMBER);
     bytestream_put_be64(&p, av_double2int(rt->duration));
 
     // Finalise object
     bytestream_put_be16(&p, 0); // Empty string
     bytestream_put_byte(&p, AMF_END_OF_OBJECT);
     bytestream_put_be32(&p, 40); // size of data part (sum of all parts below)
 
     return 0;
 }
 
 /**
49bd8e4b
  * Open RTMP connection and verify that the stream can be played.
9fd6b843
  *
  * 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)
 {
7e580505
     RTMPContext *rt = s->priv_data;
08225d01
     char proto[8], hostname[256], path[1024], auth[100], *fname;
f22cf88f
     char *old_app, *qmark, *n, fname_buffer[1024];
9fd6b843
     uint8_t buf[2048];
b316991b
     int port;
86991ce2
     AVDictionary *opts = NULL;
9fd6b843
     int ret;
 
e5f2731c
     if (rt->listen_timeout > 0)
         rt->listen = 1;
 
59d96941
     rt->is_input = !(flags & AVIO_FLAG_WRITE);
9fd6b843
 
08225d01
     av_url_split(proto, sizeof(proto), auth, sizeof(auth),
                  hostname, sizeof(hostname), &port,
f984dcf6
                  path, sizeof(path), s->filename);
9fd6b843
 
f22cf88f
     n = strchr(path, ' ');
     if (n) {
8e1fe345
         av_log(s, AV_LOG_WARNING,
                "Detected librtmp style URL parameters, these aren't supported "
                "by the libavformat internal RTMP handler currently enabled. "
                "See the documentation for the correct way to pass parameters.\n");
f22cf88f
         *n = '\0'; // Trim not supported part
8e1fe345
     }
 
08225d01
     if (auth[0]) {
         char *ptr = strchr(auth, ':');
         if (ptr) {
             *ptr = '\0';
             av_strlcpy(rt->username, auth, sizeof(rt->username));
             av_strlcpy(rt->password, ptr + 1, sizeof(rt->password));
         }
     }
 
e5f2731c
     if (rt->listen && strcmp(proto, "rtmp")) {
         av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
                proto);
         return AVERROR(EINVAL);
     }
86991ce2
     if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) {
         if (!strcmp(proto, "rtmpts"))
             av_dict_set(&opts, "ffrtmphttp_tls", "1", 1);
 
8e50c57d
         /* open the http tunneling connection */
775c4d36
         ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL);
6aedabc9
     } 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);
08cd95e8
     } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) {
         if (!strcmp(proto, "rtmpte"))
             av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1);
 
acd554c1
         /* open the encrypted connection */
         ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
         rt->encrypted = 1;
8e50c57d
     } else {
         /* open the tcp connection */
         if (port < 0)
             port = RTMP_DEFAULT_PORT;
e5f2731c
         if (rt->listen)
             ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port,
                         "?listen&listen_timeout=%d",
                         rt->listen_timeout * 1000);
         else
             ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
8e50c57d
     }
9fd6b843
 
08225d01
 reconnect:
f645f1d6
     if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
86991ce2
                           &s->interrupt_callback, &opts)) < 0) {
59d96941
         av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
9fd6b843
         goto fail;
fe523958
     }
9fd6b843
 
93f257db
     if (rt->swfverify) {
         if ((ret = rtmp_calc_swfhash(s)) < 0)
             goto fail;
     }
 
c7240611
     rt->state = STATE_START;
e5f2731c
     if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0)
         goto fail;
     if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0)
02490bf3
         goto fail;
9fd6b843
 
f5ce90f2
     rt->out_chunk_size = 128;
     rt->in_chunk_size  = 128; // Probably overwritten later
c7240611
     rt->state = STATE_HANDSHAKED;
6465562e
 
     // 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) {
f645f1d6
         ret = AVERROR(ENOMEM);
         goto fail;
6465562e
     }
 
c7240611
     //extract "app" part from path
7ce3bd96
     qmark = strchr(path, '?');
     if (qmark && strstr(qmark, "slist=")) {
         char* amp;
         // After slist we have the playpath, before the params, the app
0c2a6dab
         av_strlcpy(rt->app, path + 1, FFMIN(qmark - path, APP_MAX_LENGTH));
7ce3bd96
         fname = strstr(path, "slist=") + 6;
         // Strip any further query parameters from fname
         amp = strchr(fname, '&');
         if (amp) {
0bacfa8d
             av_strlcpy(fname_buffer, fname, FFMIN(amp - fname + 1,
                                                   sizeof(fname_buffer)));
7ce3bd96
             fname = fname_buffer;
         }
     } else if (!strncmp(path, "/ondemand/", 10)) {
c7240611
         fname = path + 10;
         memcpy(rt->app, "ondemand", 9);
     } else {
4b7304e8
         char *next = *path ? path + 1 : path;
         char *p = strchr(next, '/');
c7240611
         if (!p) {
f22cf88f
             if (old_app) {
                 // If name of application has been defined by the user, assume that
                 // playpath is provided in the URL
                 fname = next;
             } else {
                 fname = NULL;
                 av_strlcpy(rt->app, next, APP_MAX_LENGTH);
             }
9fd6b843
         } else {
c6eeb9b7
             // make sure we do not mismatch a playpath for an application instance
c7240611
             char *c = strchr(p + 1, ':');
             fname = strchr(p + 1, '/');
c6eeb9b7
             if (!fname || (c && c < fname)) {
c7240611
                 fname = p + 1;
02ac3398
                 av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH));
9fd6b843
             } else {
c7240611
                 fname++;
02ac3398
                 av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH));
9fd6b843
             }
         }
c7240611
     }
6465562e
 
     if (old_app) {
         // The name of application has been defined by the user, override it.
02ac3398
         if (strlen(old_app) >= APP_MAX_LENGTH) {
             ret = AVERROR(EINVAL);
             goto fail;
         }
6465562e
         av_free(rt->app);
         rt->app = old_app;
     }
 
b3b17512
     if (!rt->playpath) {
         rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
         if (!rt->playpath) {
f645f1d6
             ret = AVERROR(ENOMEM);
             goto fail;
b3b17512
         }
 
f22cf88f
         if (fname) {
             int len = strlen(fname);
             if (!strchr(fname, ':') && len >= 4 &&
                 (!strcmp(fname + len - 4, ".f4v") ||
                  !strcmp(fname + len - 4, ".mp4"))) {
                 memcpy(rt->playpath, "mp4:", 5);
             } else {
                 if (len >= 4 && !strcmp(fname + len - 4, ".flv"))
                     fname[len - 4] = '\0';
                 rt->playpath[0] = 0;
             }
             av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
b3b17512
         } else {
f22cf88f
             rt->playpath[0] = '\0';
b3b17512
         }
c7240611
     }
9fd6b843
 
55c9320e
     if (!rt->tcurl) {
         rt->tcurl = av_malloc(TCURL_MAX_LENGTH);
08e93f5b
         if (!rt->tcurl) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
55c9320e
         ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname,
                     port, "/%s", rt->app);
     }
 
e64673e4
     if (!rt->flashver) {
         rt->flashver = av_malloc(FLASHVER_MAX_LENGTH);
08e93f5b
         if (!rt->flashver) {
             ret = AVERROR(ENOMEM);
             goto fail;
         }
e64673e4
         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);
         }
     }
 
bf7c1719
     rt->client_report_size = 1048576;
     rt->bytes_read = 0;
59cb5747
     rt->has_audio = 0;
     rt->has_video = 0;
     rt->received_metadata = 0;
bf7c1719
     rt->last_bytes_read = 0;
c2d38bea
     rt->server_bw = 2500000;
e65c776d
     rt->duration = 0;
bf7c1719
 
7f804085
     av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
c7240611
            proto, path, rt->app, rt->playpath);
e5f2731c
     if (!rt->listen) {
         if ((ret = gen_connect(s, rt)) < 0)
             goto fail;
     } else {
64771397
         if ((ret = read_connect(s, s->priv_data)) < 0)
e5f2731c
             goto fail;
     }
9fd6b843
 
c7240611
     do {
         ret = get_packet(s, 1);
0d378439
     } while (ret == AVERROR(EAGAIN));
c7240611
     if (ret < 0)
         goto fail;
6bf22e18
 
08225d01
     if (rt->do_reconnect) {
84a125c4
         int i;
08225d01
         ffurl_close(rt->stream);
         rt->stream       = NULL;
         rt->do_reconnect = 0;
         rt->nb_invokes   = 0;
84a125c4
         for (i = 0; i < 2; i++)
             memset(rt->prev_pkt[i], 0,
                    sizeof(**rt->prev_pkt) * rt->nb_prev_pkt[i]);
08225d01
         free_tracked_methods(rt);
         goto reconnect;
     }
 
6bf22e18
     if (rt->is_input) {
9fd6b843
         // generate FLV header for demuxer
         rt->flv_size = 13;
0034314a
         if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0)
             goto fail;
9fd6b843
         rt->flv_off  = 0;
59cb5747
         memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size);
 
         // Read packets until we reach the first A/V packet or read metadata.
         // If there was a metadata package in front of the A/V packets, we can
         // build the FLV header from this. If we do not receive any metadata,
         // the FLV decoder will allocate the needed streams when their first
         // audio or video packet arrives.
         while (!rt->has_audio && !rt->has_video && !rt->received_metadata) {
             if ((ret = get_packet(s, 0)) < 0)
08ccc474
                goto fail;
59cb5747
         }
 
         // Either after we have read the metadata or (if there is none) the
         // first packet of an A/V stream, we have a better knowledge about the
         // streams, so set the FLV header accordingly.
         if (rt->has_audio) {
             rt->flv_data[4] |= FLV_HEADER_FLAG_HASAUDIO;
         }
         if (rt->has_video) {
             rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO;
         }
e65c776d
 
         // If we received the first packet of an A/V stream and no metadata but
         // the server returned a valid duration, create a fake metadata packet
         // to inform the FLV decoder about the duration.
         if (!rt->received_metadata && rt->duration > 0) {
             if ((ret = inject_fake_duration_metadata(rt)) < 0)
                 goto fail;
         }
6bf22e18
     } else {
         rt->flv_size = 0;
         rt->flv_data = NULL;
         rt->flv_off  = 0;
b14629e5
         rt->skip_bytes = 13;
9fd6b843
     }
 
5958df34
     s->max_packet_size = rt->stream->max_packet_size;
9fd6b843
     s->is_streamed     = 1;
     return 0;
 
 fail:
86991ce2
     av_dict_free(&opts);
9fd6b843
     rtmp_close(s);
f645f1d6
     return ret;
9fd6b843
 }
 
 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;
e8ccf245
             return data_left;
9fd6b843
         }
         if ((ret = get_packet(s, 0)) < 0)
            return ret;
     }
     return orig_size;
 }
 
0d6fa397
 static int64_t rtmp_seek(URLContext *s, int stream_index, int64_t timestamp,
                          int flags)
 {
     RTMPContext *rt = s->priv_data;
     int ret;
     av_log(s, AV_LOG_DEBUG,
4ebc7d65
            "Seek on stream index %d at timestamp %"PRId64" with flags %08x\n",
0d6fa397
            stream_index, timestamp, flags);
     if ((ret = gen_seek(s, rt, timestamp)) < 0) {
         av_log(s, AV_LOG_ERROR,
4ebc7d65
                "Unable to send seek command on stream index %d at timestamp "
                "%"PRId64" with flags %08x\n",
0d6fa397
                stream_index, timestamp, flags);
         return ret;
     }
     rt->flv_off = rt->flv_size;
     rt->state = STATE_SEEKING;
     return timestamp;
 }
 
9bec3ca2
 static int rtmp_pause(URLContext *s, int pause)
 {
     RTMPContext *rt = s->priv_data;
     int ret;
     av_log(s, AV_LOG_DEBUG, "Pause at timestamp %d\n",
            rt->last_timestamp);
     if ((ret = gen_pause(s, rt, pause, rt->last_timestamp)) < 0) {
         av_log(s, AV_LOG_ERROR, "Unable to send pause command at timestamp %d\n",
                rt->last_timestamp);
         return ret;
     }
     return 0;
 }
 
ea468763
 static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
9fd6b843
 {
ea468763
     RTMPContext *rt = s->priv_data;
6bf22e18
     int size_temp = size;
3c3b8003
     int pktsize, pkttype, copy;
6bf22e18
     uint32_t ts;
     const uint8_t *buf_temp = buf;
7dc747f5
     uint8_t c;
f645f1d6
     int ret;
6bf22e18
 
     do {
b14629e5
         if (rt->skip_bytes) {
             int skip = FFMIN(rt->skip_bytes, size_temp);
             buf_temp       += skip;
             size_temp      -= skip;
             rt->skip_bytes -= skip;
             continue;
         }
 
ba77757a
         if (rt->flv_header_bytes < RTMP_HEADER) {
b14629e5
             const uint8_t *header = rt->flv_header;
d4aef997
             int channel = RTMP_AUDIO_CHANNEL;
60fd790f
 
3c3b8003
             copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp);
b14629e5
             bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy);
             rt->flv_header_bytes += copy;
             size_temp            -= copy;
ba77757a
             if (rt->flv_header_bytes < RTMP_HEADER)
b14629e5
                 break;
6bf22e18
 
b14629e5
             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);
6bf22e18
             rt->flv_size = pktsize;
 
120af23c
             if (pkttype == RTMP_PT_VIDEO)
                 channel = RTMP_VIDEO_CHANNEL;
 
6bf22e18
             if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) ||
                 pkttype == RTMP_PT_NOTIFY) {
84a125c4
                 if ((ret = ff_rtmp_check_alloc_array(&rt->prev_pkt[1],
                                                      &rt->nb_prev_pkt[1],
                                                      channel)) < 0)
                     return ret;
49180563
                 // Force sending a full 12 bytes header by clearing the
857e6667
                 // channel id, to make it not match a potential earlier
                 // packet in the same channel.
120af23c
                 rt->prev_pkt[1][channel].channel_id = 0;
6bf22e18
             }
 
             //this can be a big packet, it's better to send it right here
120af23c
             if ((ret = ff_rtmp_packet_create(&rt->out_pkt, channel,
f645f1d6
                                              pkttype, ts, pktsize)) < 0)
                 return ret;
 
f8d1bb67
             rt->out_pkt.extra = rt->stream_id;
6bf22e18
             rt->flv_data = rt->out_pkt.data;
         }
 
3c3b8003
         copy = FFMIN(rt->flv_size - rt->flv_off, size_temp);
         bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, copy);
         rt->flv_off += copy;
         size_temp   -= copy;
6bf22e18
 
         if (rt->flv_off == rt->flv_size) {
b14629e5
             rt->skip_bytes = 4;
 
01eac895
             if (rt->out_pkt.type == RTMP_PT_NOTIFY) {
                 // For onMetaData and |RtmpSampleAccess packets, we want
                 // @setDataFrame prepended to the packet before it gets sent.
                 // However, not all RTMP_PT_NOTIFY packets (e.g., onTextData
                 // and onCuePoint).
                 uint8_t commandbuffer[64];
                 int stringlen = 0;
                 GetByteContext gbc;
 
                 bytestream2_init(&gbc, rt->flv_data, rt->flv_size);
                 if (!ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer),
                                         &stringlen)) {
                     if (!strcmp(commandbuffer, "onMetaData") ||
                         !strcmp(commandbuffer, "|RtmpSampleAccess")) {
                         uint8_t *ptr;
                         if ((ret = av_reallocp(&rt->out_pkt.data, rt->out_pkt.size + 16)) < 0) {
                             rt->flv_size = rt->flv_off = rt->flv_header_bytes = 0;
                             return ret;
                         }
                         memmove(rt->out_pkt.data + 16, rt->out_pkt.data, rt->out_pkt.size);
                         rt->out_pkt.size += 16;
                         ptr = rt->out_pkt.data;
                         ff_amf_write_string(&ptr, "@setDataFrame");
                     }
                 }
             }
 
f89584ca
             if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0)
bba287fd
                 return ret;
6bf22e18
             rt->flv_size = 0;
             rt->flv_off = 0;
b14629e5
             rt->flv_header_bytes = 0;
46743a85
             rt->flv_nb_packets++;
6bf22e18
         }
07631dee
     } while (buf_temp - buf < size);
7dc747f5
 
46743a85
     if (rt->flv_nb_packets < rt->flush_interval)
         return size;
     rt->flv_nb_packets = 0;
 
7dc747f5
     /* 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,
f5ce90f2
                                                 rt->in_chunk_size,
84a125c4
                                                 &rt->prev_pkt[0],
                                                 &rt->nb_prev_pkt[0], c)) <= 0)
7dc747f5
              return ret;
 
         if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0)
             return ret;
 
         ff_rtmp_packet_destroy(&rpkt);
     }
 
6bf22e18
     return size;
9fd6b843
 }
 
6465562e
 #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},
e6153f17
     {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_INT, {.i64 = 3000}, 0, INT_MAX, DEC|ENC},
8ee3e187
     {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
e64673e4
     {"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},
e6153f17
     {"rtmp_flush_interval", "Number of packets flushed in the same request (RTMPT only).", OFFSET(flush_interval), AV_OPT_TYPE_INT, {.i64 = 10}, 0, INT_MAX, ENC},
     {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {.i64 = -2}, INT_MIN, INT_MAX, DEC, "rtmp_live"},
124134e4
     {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"},
     {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"},
     {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"},
758377a2
     {"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},
b3b17512
     {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC},
00cb52c6
     {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
635ac8e1
     {"rtmp_swfhash", "SHA256 hash of the decompressed SWF file (32 bytes).", OFFSET(swfhash), AV_OPT_TYPE_BINARY, .flags = DEC},
e6153f17
     {"rtmp_swfsize", "Size of the decompressed SWF file, required for SWFVerification.", OFFSET(swfsize), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC},
05945db9
     {"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},
93f257db
     {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically.", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC},
63ffa154
     {"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},
e6153f17
     {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
628a17d7
     {"listen",      "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
e6153f17
     {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1",  OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" },
6465562e
     { NULL },
 };
 
12127b65
 #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,                 \
0d6fa397
     .url_read_seek  = rtmp_seek,                 \
9bec3ca2
     .url_read_pause = rtmp_pause,                \
12127b65
     .url_write      = rtmp_write,                \
     .url_close      = rtmp_close,                \
     .priv_data_size = sizeof(RTMPContext),       \
     .flags          = URL_PROTOCOL_FLAG_NETWORK, \
     .priv_data_class= &flavor##_class,           \
9fd6b843
 };
8e50c57d
 
acd554c1
 
12127b65
 RTMP_PROTOCOL(rtmp)
 RTMP_PROTOCOL(rtmpe)
 RTMP_PROTOCOL(rtmps)
 RTMP_PROTOCOL(rtmpt)
 RTMP_PROTOCOL(rtmpte)
 RTMP_PROTOCOL(rtmpts)