yading@11: /* yading@11: * RTMP network protocol yading@11: * Copyright (c) 2009 Kostya Shishkov yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: /** yading@11: * @file yading@11: * RTMP protocol yading@11: */ yading@11: yading@11: #include "libavcodec/bytestream.h" yading@11: #include "libavutil/avstring.h" yading@11: #include "libavutil/base64.h" yading@11: #include "libavutil/intfloat.h" yading@11: #include "libavutil/lfg.h" yading@11: #include "libavutil/md5.h" yading@11: #include "libavutil/opt.h" yading@11: #include "libavutil/random_seed.h" yading@11: #include "libavutil/sha.h" yading@11: #include "avformat.h" yading@11: #include "internal.h" yading@11: yading@11: #include "network.h" yading@11: yading@11: #include "flv.h" yading@11: #include "rtmp.h" yading@11: #include "rtmpcrypt.h" yading@11: #include "rtmppkt.h" yading@11: #include "url.h" yading@11: yading@11: #if CONFIG_ZLIB yading@11: #include yading@11: #endif yading@11: yading@11: //#define DEBUG yading@11: yading@11: #define APP_MAX_LENGTH 1024 yading@11: #define PLAYPATH_MAX_LENGTH 256 yading@11: #define TCURL_MAX_LENGTH 512 yading@11: #define FLASHVER_MAX_LENGTH 64 yading@11: #define RTMP_PKTDATA_DEFAULT_SIZE 4096 yading@11: yading@11: /** RTMP protocol handler state */ yading@11: typedef enum { yading@11: STATE_START, ///< client has not done anything yet yading@11: STATE_HANDSHAKED, ///< client has performed handshake yading@11: STATE_FCPUBLISH, ///< client FCPublishing stream (for output) yading@11: STATE_PLAYING, ///< client has started receiving multimedia data from server yading@11: STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output) yading@11: STATE_RECEIVING, ///< received a publish command (for input) yading@11: STATE_STOPPED, ///< the broadcast has been stopped yading@11: } ClientState; yading@11: yading@11: typedef struct TrackedMethod { yading@11: char *name; yading@11: int id; yading@11: } TrackedMethod; yading@11: yading@11: /** protocol handler context */ yading@11: typedef struct RTMPContext { yading@11: const AVClass *class; yading@11: URLContext* stream; ///< TCP stream used in interactions with RTMP server yading@11: RTMPPacket prev_pkt[2][RTMP_CHANNELS]; ///< packet history used when reading and sending packets yading@11: int in_chunk_size; ///< size of the chunks incoming RTMP packets are divided into yading@11: int out_chunk_size; ///< size of the chunks outgoing RTMP packets are divided into yading@11: int is_input; ///< input/output flag yading@11: char *playpath; ///< stream identifier to play (with possible "mp4:" prefix) yading@11: int live; ///< 0: recorded, -1: live, -2: both yading@11: char *app; ///< name of application yading@11: char *conn; ///< append arbitrary AMF data to the Connect message yading@11: ClientState state; ///< current state yading@11: int main_channel_id; ///< an additional channel ID which is used for some invocations yading@11: uint8_t* flv_data; ///< buffer with data for demuxer yading@11: int flv_size; ///< current buffer size yading@11: int flv_off; ///< number of bytes read from current buffer yading@11: int flv_nb_packets; ///< number of flv packets published yading@11: RTMPPacket out_pkt; ///< rtmp packet, created from flv a/v or metadata (for output) yading@11: uint32_t client_report_size; ///< number of bytes after which client should report to server yading@11: uint32_t bytes_read; ///< number of bytes read from server yading@11: uint32_t last_bytes_read; ///< number of bytes read last reported to server yading@11: int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call yading@11: uint8_t flv_header[11]; ///< partial incoming flv packet header yading@11: int flv_header_bytes; ///< number of initialized bytes in flv_header yading@11: int nb_invokes; ///< keeps track of invoke messages yading@11: char* tcurl; ///< url of the target stream yading@11: char* flashver; ///< version of the flash plugin yading@11: char* swfhash; ///< SHA256 hash of the decompressed SWF file (32 bytes) yading@11: int swfhash_len; ///< length of the SHA256 hash yading@11: int swfsize; ///< size of the decompressed SWF file yading@11: char* swfurl; ///< url of the swf player yading@11: char* swfverify; ///< URL to player swf file, compute hash/size automatically yading@11: char swfverification[42]; ///< hash of the SWF verification yading@11: char* pageurl; ///< url of the web page yading@11: char* subscribe; ///< name of live stream to subscribe yading@11: int server_bw; ///< server bandwidth yading@11: int client_buffer_time; ///< client buffer time in ms yading@11: int flush_interval; ///< number of packets flushed in the same request (RTMPT only) yading@11: int encrypted; ///< use an encrypted connection (RTMPE only) yading@11: TrackedMethod*tracked_methods; ///< tracked methods buffer yading@11: int nb_tracked_methods; ///< number of tracked methods yading@11: int tracked_methods_size; ///< size of the tracked methods buffer yading@11: int listen; ///< listen mode flag yading@11: int listen_timeout; ///< listen timeout to wait for new connections yading@11: int nb_streamid; ///< The next stream id to return on createStream calls yading@11: char username[50]; yading@11: char password[50]; yading@11: char auth_params[500]; yading@11: int do_reconnect; yading@11: int auth_tried; yading@11: } RTMPContext; yading@11: yading@11: #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing yading@11: /** Client key used for digest signing */ yading@11: static const uint8_t rtmp_player_key[] = { yading@11: 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', yading@11: 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', yading@11: yading@11: 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, yading@11: 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, yading@11: 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE yading@11: }; yading@11: yading@11: #define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing yading@11: /** Key used for RTMP server digest signing */ yading@11: static const uint8_t rtmp_server_key[] = { yading@11: 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', yading@11: 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', yading@11: 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', yading@11: yading@11: 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, yading@11: 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, yading@11: 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE yading@11: }; yading@11: yading@11: static int add_tracked_method(RTMPContext *rt, const char *name, int id) yading@11: { yading@11: void *ptr; yading@11: yading@11: if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) { yading@11: rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2; yading@11: ptr = av_realloc(rt->tracked_methods, yading@11: rt->tracked_methods_size * sizeof(*rt->tracked_methods)); yading@11: if (!ptr) yading@11: return AVERROR(ENOMEM); yading@11: rt->tracked_methods = ptr; yading@11: } yading@11: yading@11: rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name); yading@11: if (!rt->tracked_methods[rt->nb_tracked_methods].name) yading@11: return AVERROR(ENOMEM); yading@11: rt->tracked_methods[rt->nb_tracked_methods].id = id; yading@11: rt->nb_tracked_methods++; yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static void del_tracked_method(RTMPContext *rt, int index) yading@11: { yading@11: memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1], yading@11: sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1)); yading@11: rt->nb_tracked_methods--; yading@11: } yading@11: yading@11: static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset, yading@11: char **tracked_method) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: GetByteContext gbc; yading@11: double pkt_id; yading@11: int ret; yading@11: int i; yading@11: yading@11: bytestream2_init(&gbc, pkt->data + offset, pkt->data_size - offset); yading@11: if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) yading@11: return ret; yading@11: yading@11: for (i = 0; i < rt->nb_tracked_methods; i++) { yading@11: if (rt->tracked_methods[i].id != pkt_id) yading@11: continue; yading@11: yading@11: *tracked_method = rt->tracked_methods[i].name; yading@11: del_tracked_method(rt, i); yading@11: break; yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static void free_tracked_methods(RTMPContext *rt) yading@11: { yading@11: int i; yading@11: yading@11: for (i = 0; i < rt->nb_tracked_methods; i ++) yading@11: av_free(rt->tracked_methods[i].name); yading@11: av_free(rt->tracked_methods); yading@11: rt->tracked_methods = NULL; yading@11: rt->tracked_methods_size = 0; yading@11: rt->nb_tracked_methods = 0; yading@11: } yading@11: yading@11: static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track) yading@11: { yading@11: int ret; yading@11: yading@11: if (pkt->type == RTMP_PT_INVOKE && track) { yading@11: GetByteContext gbc; yading@11: char name[128]; yading@11: double pkt_id; yading@11: int len; yading@11: yading@11: bytestream2_init(&gbc, pkt->data, pkt->data_size); yading@11: if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0) yading@11: goto fail; yading@11: yading@11: if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) yading@11: goto fail; yading@11: yading@11: if ((ret = add_tracked_method(rt, name, pkt_id)) < 0) yading@11: goto fail; yading@11: } yading@11: yading@11: ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: fail: yading@11: ff_rtmp_packet_destroy(pkt); yading@11: return ret; yading@11: } yading@11: yading@11: static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p) yading@11: { yading@11: char *field, *value; yading@11: char type; yading@11: yading@11: /* The type must be B for Boolean, N for number, S for string, O for yading@11: * object, or Z for null. For Booleans the data must be either 0 or 1 for yading@11: * FALSE or TRUE, respectively. Likewise for Objects the data must be yading@11: * 0 or 1 to end or begin an object, respectively. Data items in subobjects yading@11: * may be named, by prefixing the type with 'N' and specifying the name yading@11: * before the value (ie. NB:myFlag:1). This option may be used multiple times yading@11: * to construct arbitrary AMF sequences. */ yading@11: if (param[0] && param[1] == ':') { yading@11: type = param[0]; yading@11: value = param + 2; yading@11: } else if (param[0] == 'N' && param[1] && param[2] == ':') { yading@11: type = param[1]; yading@11: field = param + 3; yading@11: value = strchr(field, ':'); yading@11: if (!value) yading@11: goto fail; yading@11: *value = '\0'; yading@11: value++; yading@11: yading@11: ff_amf_write_field_name(p, field); yading@11: } else { yading@11: goto fail; yading@11: } yading@11: yading@11: switch (type) { yading@11: case 'B': yading@11: ff_amf_write_bool(p, value[0] != '0'); yading@11: break; yading@11: case 'S': yading@11: ff_amf_write_string(p, value); yading@11: break; yading@11: case 'N': yading@11: ff_amf_write_number(p, strtod(value, NULL)); yading@11: break; yading@11: case 'Z': yading@11: ff_amf_write_null(p); yading@11: break; yading@11: case 'O': yading@11: if (value[0] != '0') yading@11: ff_amf_write_object_start(p); yading@11: else yading@11: ff_amf_write_object_end(p); yading@11: break; yading@11: default: yading@11: goto fail; yading@11: break; yading@11: } yading@11: yading@11: return 0; yading@11: yading@11: fail: yading@11: av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'connect' call and send it to the server. yading@11: */ yading@11: static int gen_connect(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 4096 + APP_MAX_LENGTH)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: yading@11: ff_amf_write_string(&p, "connect"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_object_start(&p); yading@11: ff_amf_write_field_name(&p, "app"); yading@11: ff_amf_write_string2(&p, rt->app, rt->auth_params); yading@11: yading@11: if (!rt->is_input) { yading@11: ff_amf_write_field_name(&p, "type"); yading@11: ff_amf_write_string(&p, "nonprivate"); yading@11: } yading@11: ff_amf_write_field_name(&p, "flashVer"); yading@11: ff_amf_write_string(&p, rt->flashver); yading@11: yading@11: if (rt->swfurl) { yading@11: ff_amf_write_field_name(&p, "swfUrl"); yading@11: ff_amf_write_string(&p, rt->swfurl); yading@11: } yading@11: yading@11: ff_amf_write_field_name(&p, "tcUrl"); yading@11: ff_amf_write_string2(&p, rt->tcurl, rt->auth_params); yading@11: if (rt->is_input) { yading@11: ff_amf_write_field_name(&p, "fpad"); yading@11: ff_amf_write_bool(&p, 0); yading@11: ff_amf_write_field_name(&p, "capabilities"); yading@11: ff_amf_write_number(&p, 15.0); yading@11: yading@11: /* Tell the server we support all the audio codecs except yading@11: * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010) yading@11: * which are unused in the RTMP protocol implementation. */ yading@11: ff_amf_write_field_name(&p, "audioCodecs"); yading@11: ff_amf_write_number(&p, 4071.0); yading@11: ff_amf_write_field_name(&p, "videoCodecs"); yading@11: ff_amf_write_number(&p, 252.0); yading@11: ff_amf_write_field_name(&p, "videoFunction"); yading@11: ff_amf_write_number(&p, 1.0); yading@11: yading@11: if (rt->pageurl) { yading@11: ff_amf_write_field_name(&p, "pageUrl"); yading@11: ff_amf_write_string(&p, rt->pageurl); yading@11: } yading@11: } yading@11: ff_amf_write_object_end(&p); yading@11: yading@11: if (rt->conn) { yading@11: char *param = rt->conn; yading@11: yading@11: // Write arbitrary AMF data to the Connect message. yading@11: while (param != NULL) { yading@11: char *sep; yading@11: param += strspn(param, " "); yading@11: if (!*param) yading@11: break; yading@11: sep = strchr(param, ' '); yading@11: if (sep) yading@11: *sep = '\0'; yading@11: if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) { yading@11: // Invalid AMF parameter. yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: return ret; yading@11: } yading@11: yading@11: if (sep) yading@11: param = sep + 1; yading@11: else yading@11: break; yading@11: } yading@11: } yading@11: yading@11: pkt.data_size = p - pkt.data; yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: static int read_connect(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt = { 0 }; yading@11: uint8_t *p; yading@11: const uint8_t *cp; yading@11: int ret; yading@11: char command[64]; yading@11: int stringlen; yading@11: double seqnum; yading@11: uint8_t tmpstr[256]; yading@11: GetByteContext gbc; yading@11: yading@11: if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size, yading@11: rt->prev_pkt[1])) < 0) yading@11: return ret; yading@11: cp = pkt.data; yading@11: bytestream2_init(&gbc, cp, pkt.data_size); yading@11: if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to read command string\n"); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: if (strcmp(command, "connect")) { yading@11: av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: ret = ff_amf_read_number(&gbc, &seqnum); yading@11: if (ret) yading@11: av_log(s, AV_LOG_WARNING, "SeqNum not found\n"); yading@11: /* Here one could parse an AMF Object with data as flashVers and others. */ yading@11: ret = ff_amf_get_field_value(gbc.buffer, yading@11: gbc.buffer + bytestream2_get_bytes_left(&gbc), yading@11: "app", tmpstr, sizeof(tmpstr)); yading@11: if (ret) yading@11: av_log(s, AV_LOG_WARNING, "App field not found in connect\n"); yading@11: if (!ret && strcmp(tmpstr, rt->app)) yading@11: av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n", yading@11: tmpstr, rt->app); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: yading@11: // Send Window Acknowledgement Size (as defined in speficication) yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, yading@11: RTMP_PT_SERVER_BW, 0, 4)) < 0) yading@11: return ret; yading@11: p = pkt.data; yading@11: bytestream_put_be32(&p, rt->server_bw); yading@11: pkt.data_size = p - pkt.data; yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: // Send Peer Bandwidth yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, yading@11: RTMP_PT_CLIENT_BW, 0, 5)) < 0) yading@11: return ret; yading@11: p = pkt.data; yading@11: bytestream_put_be32(&p, rt->server_bw); yading@11: bytestream_put_byte(&p, 2); // dynamic yading@11: pkt.data_size = p - pkt.data; yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: // Ping request yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, yading@11: RTMP_PT_PING, 0, 6)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be16(&p, 0); // 0 -> Stream Begin yading@11: bytestream_put_be32(&p, 0); yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: // Chunk size yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_CHUNK_SIZE, 0, 4)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be32(&p, rt->out_chunk_size); yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: // Send result_ NetConnection.Connect.Success to connect yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_INVOKE, 0, yading@11: RTMP_PKTDATA_DEFAULT_SIZE)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "_result"); yading@11: ff_amf_write_number(&p, seqnum); yading@11: yading@11: ff_amf_write_object_start(&p); yading@11: ff_amf_write_field_name(&p, "fmsVer"); yading@11: ff_amf_write_string(&p, "FMS/3,0,1,123"); yading@11: ff_amf_write_field_name(&p, "capabilities"); yading@11: ff_amf_write_number(&p, 31); yading@11: ff_amf_write_object_end(&p); yading@11: yading@11: ff_amf_write_object_start(&p); yading@11: ff_amf_write_field_name(&p, "level"); yading@11: ff_amf_write_string(&p, "status"); yading@11: ff_amf_write_field_name(&p, "code"); yading@11: ff_amf_write_string(&p, "NetConnection.Connect.Success"); yading@11: ff_amf_write_field_name(&p, "description"); yading@11: ff_amf_write_string(&p, "Connection succeeded."); yading@11: ff_amf_write_field_name(&p, "objectEncoding"); yading@11: ff_amf_write_number(&p, 0); yading@11: ff_amf_write_object_end(&p); yading@11: yading@11: pkt.data_size = p - pkt.data; yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_INVOKE, 0, 30)) < 0) yading@11: return ret; yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "onBWDone"); yading@11: ff_amf_write_number(&p, 0); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_number(&p, 8192); yading@11: pkt.data_size = p - pkt.data; yading@11: ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&pkt); yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: /** yading@11: * Generate 'releaseStream' call and send it to the server. It should make yading@11: * the server release some channel for media streams. yading@11: */ yading@11: static int gen_release_stream(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 29 + strlen(rt->playpath))) < 0) yading@11: return ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Releasing stream...\n"); yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "releaseStream"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, rt->playpath); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'FCPublish' call and send it to the server. It should make yading@11: * the server preapare for receiving media streams. yading@11: */ yading@11: static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 25 + strlen(rt->playpath))) < 0) yading@11: return ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n"); yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "FCPublish"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, rt->playpath); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'FCUnpublish' call and send it to the server. It should make yading@11: * the server destroy stream. yading@11: */ yading@11: static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 27 + strlen(rt->playpath))) < 0) yading@11: return ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n"); yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "FCUnpublish"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, rt->playpath); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'createStream' call and send it to the server. It should make yading@11: * the server allocate some channel for media streams. yading@11: */ yading@11: static int gen_create_stream(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Creating stream...\n"); yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 25)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "createStream"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: yading@11: /** yading@11: * Generate 'deleteStream' call and send it to the server. It should make yading@11: * the server remove some channel for media streams. yading@11: */ yading@11: static int gen_delete_stream(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Deleting stream...\n"); yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 34)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "deleteStream"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_number(&p, rt->main_channel_id); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate client buffer time and send it to the server. yading@11: */ yading@11: static int gen_buffer_time(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, yading@11: 1, 10)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be16(&p, 3); yading@11: bytestream_put_be32(&p, rt->main_channel_id); yading@11: bytestream_put_be32(&p, rt->client_buffer_time); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'play' call and send it to the server, then ping the server yading@11: * to start actual playing. yading@11: */ yading@11: static int gen_play(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath); yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 29 + strlen(rt->playpath))) < 0) yading@11: return ret; yading@11: yading@11: pkt.extra = rt->main_channel_id; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "play"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, rt->playpath); yading@11: ff_amf_write_number(&p, rt->live); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: /** yading@11: * Generate 'publish' call and send it to the server. yading@11: */ yading@11: static int gen_publish(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath); yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 30 + strlen(rt->playpath))) < 0) yading@11: return ret; yading@11: yading@11: pkt.extra = rt->main_channel_id; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "publish"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, rt->playpath); yading@11: ff_amf_write_string(&p, "live"); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: /** yading@11: * Generate ping reply and send it to the server. yading@11: */ yading@11: static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if (ppkt->data_size < 6) { yading@11: av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", yading@11: ppkt->data_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, yading@11: ppkt->timestamp + 1, 6)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be16(&p, 7); yading@11: bytestream_put_be32(&p, AV_RB32(ppkt->data+2)); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate SWF verification message and send it to the server. yading@11: */ yading@11: static int gen_swf_verification(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n"); yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, yading@11: 0, 44)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be16(&p, 27); yading@11: memcpy(p, rt->swfverification, 42); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate server bandwidth message and send it to the server. yading@11: */ yading@11: static int gen_server_bw(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW, yading@11: 0, 4)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be32(&p, rt->server_bw); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: /** yading@11: * Generate check bandwidth message and send it to the server. yading@11: */ yading@11: static int gen_check_bw(URLContext *s, RTMPContext *rt) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 21)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "_checkbw"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: /** yading@11: * Generate report on bytes read so far and send it to the server. yading@11: */ yading@11: static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ, yading@11: ts, 4)) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: bytestream_put_be32(&p, rt->bytes_read); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 0); yading@11: } yading@11: yading@11: static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt, yading@11: const char *subscribe) yading@11: { yading@11: RTMPPacket pkt; yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, yading@11: 0, 27 + strlen(subscribe))) < 0) yading@11: return ret; yading@11: yading@11: p = pkt.data; yading@11: ff_amf_write_string(&p, "FCSubscribe"); yading@11: ff_amf_write_number(&p, ++rt->nb_invokes); yading@11: ff_amf_write_null(&p); yading@11: ff_amf_write_string(&p, subscribe); yading@11: yading@11: return rtmp_send_packet(rt, &pkt, 1); yading@11: } yading@11: yading@11: int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap, yading@11: const uint8_t *key, int keylen, uint8_t *dst) yading@11: { yading@11: struct AVSHA *sha; yading@11: uint8_t hmac_buf[64+32] = {0}; yading@11: int i; yading@11: yading@11: sha = av_sha_alloc(); yading@11: if (!sha) yading@11: return AVERROR(ENOMEM); yading@11: yading@11: if (keylen < 64) { yading@11: memcpy(hmac_buf, key, keylen); yading@11: } else { yading@11: av_sha_init(sha, 256); yading@11: av_sha_update(sha,key, keylen); yading@11: av_sha_final(sha, hmac_buf); yading@11: } yading@11: for (i = 0; i < 64; i++) yading@11: hmac_buf[i] ^= HMAC_IPAD_VAL; yading@11: yading@11: av_sha_init(sha, 256); yading@11: av_sha_update(sha, hmac_buf, 64); yading@11: if (gap <= 0) { yading@11: av_sha_update(sha, src, len); yading@11: } else { //skip 32 bytes used for storing digest yading@11: av_sha_update(sha, src, gap); yading@11: av_sha_update(sha, src + gap + 32, len - gap - 32); yading@11: } yading@11: av_sha_final(sha, hmac_buf + 64); yading@11: yading@11: for (i = 0; i < 64; i++) yading@11: hmac_buf[i] ^= HMAC_IPAD_VAL ^ HMAC_OPAD_VAL; //reuse XORed key for opad yading@11: av_sha_init(sha, 256); yading@11: av_sha_update(sha, hmac_buf, 64+32); yading@11: av_sha_final(sha, dst); yading@11: yading@11: av_free(sha); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val, yading@11: int add_val) yading@11: { yading@11: int i, digest_pos = 0; yading@11: yading@11: for (i = 0; i < 4; i++) yading@11: digest_pos += buf[i + off]; yading@11: digest_pos = digest_pos % mod_val + add_val; yading@11: yading@11: return digest_pos; yading@11: } yading@11: yading@11: /** yading@11: * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest yading@11: * will be stored) into that packet. yading@11: * yading@11: * @param buf handshake data (1536 bytes) yading@11: * @param encrypted use an encrypted connection (RTMPE) yading@11: * @return offset to the digest inside input data yading@11: */ yading@11: static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted) yading@11: { yading@11: int ret, digest_pos; yading@11: yading@11: if (encrypted) yading@11: digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776); yading@11: else yading@11: digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12); yading@11: yading@11: ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, yading@11: rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, yading@11: buf + digest_pos); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: return digest_pos; yading@11: } yading@11: yading@11: /** yading@11: * Verify that the received server response has the expected digest value. yading@11: * yading@11: * @param buf handshake data received from the server (1536 bytes) yading@11: * @param off position to search digest offset from yading@11: * @return 0 if digest is valid, digest position otherwise yading@11: */ yading@11: static int rtmp_validate_digest(uint8_t *buf, int off) yading@11: { yading@11: uint8_t digest[32]; yading@11: int ret, digest_pos; yading@11: yading@11: digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4); yading@11: yading@11: ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, yading@11: rtmp_server_key, SERVER_KEY_OPEN_PART_LEN, yading@11: digest); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: if (!memcmp(digest, buf + digest_pos, 32)) yading@11: return digest_pos; yading@11: return 0; yading@11: } yading@11: yading@11: static int rtmp_calc_swf_verification(URLContext *s, RTMPContext *rt, yading@11: uint8_t *buf) yading@11: { yading@11: uint8_t *p; yading@11: int ret; yading@11: yading@11: if (rt->swfhash_len != 32) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Hash of the decompressed SWF file is not 32 bytes long.\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: p = &rt->swfverification[0]; yading@11: bytestream_put_byte(&p, 1); yading@11: bytestream_put_byte(&p, 1); yading@11: bytestream_put_be32(&p, rt->swfsize); yading@11: bytestream_put_be32(&p, rt->swfsize); yading@11: yading@11: if ((ret = ff_rtmp_calc_digest(rt->swfhash, 32, 0, buf, 32, p)) < 0) yading@11: return ret; yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: #if CONFIG_ZLIB yading@11: static int rtmp_uncompress_swfplayer(uint8_t *in_data, int64_t in_size, yading@11: uint8_t **out_data, int64_t *out_size) yading@11: { yading@11: z_stream zs = { 0 }; yading@11: void *ptr; yading@11: int size; yading@11: int ret = 0; yading@11: yading@11: zs.avail_in = in_size; yading@11: zs.next_in = in_data; yading@11: ret = inflateInit(&zs); yading@11: if (ret != Z_OK) yading@11: return AVERROR_UNKNOWN; yading@11: yading@11: do { yading@11: uint8_t tmp_buf[16384]; yading@11: yading@11: zs.avail_out = sizeof(tmp_buf); yading@11: zs.next_out = tmp_buf; yading@11: yading@11: ret = inflate(&zs, Z_NO_FLUSH); yading@11: if (ret != Z_OK && ret != Z_STREAM_END) { yading@11: ret = AVERROR_UNKNOWN; yading@11: goto fail; yading@11: } yading@11: yading@11: size = sizeof(tmp_buf) - zs.avail_out; yading@11: if (!(ptr = av_realloc(*out_data, *out_size + size))) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: *out_data = ptr; yading@11: yading@11: memcpy(*out_data + *out_size, tmp_buf, size); yading@11: *out_size += size; yading@11: } while (zs.avail_out == 0); yading@11: yading@11: fail: yading@11: inflateEnd(&zs); yading@11: return ret; yading@11: } yading@11: #endif yading@11: yading@11: static int rtmp_calc_swfhash(URLContext *s) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: uint8_t *in_data = NULL, *out_data = NULL, *swfdata; yading@11: int64_t in_size, out_size; yading@11: URLContext *stream; yading@11: char swfhash[32]; yading@11: int swfsize; yading@11: int ret = 0; yading@11: yading@11: /* Get the SWF player file. */ yading@11: if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ, yading@11: &s->interrupt_callback, NULL)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify); yading@11: goto fail; yading@11: } yading@11: yading@11: if ((in_size = ffurl_seek(stream, 0, AVSEEK_SIZE)) < 0) { yading@11: ret = AVERROR(EIO); yading@11: goto fail; yading@11: } yading@11: yading@11: if (!(in_data = av_malloc(in_size))) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: yading@11: if ((ret = ffurl_read_complete(stream, in_data, in_size)) < 0) yading@11: goto fail; yading@11: yading@11: if (in_size < 3) { yading@11: ret = AVERROR_INVALIDDATA; yading@11: goto fail; yading@11: } yading@11: yading@11: if (!memcmp(in_data, "CWS", 3)) { yading@11: /* Decompress the SWF player file using Zlib. */ yading@11: if (!(out_data = av_malloc(8))) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: *in_data = 'F'; // magic stuff yading@11: memcpy(out_data, in_data, 8); yading@11: out_size = 8; yading@11: yading@11: #if CONFIG_ZLIB yading@11: if ((ret = rtmp_uncompress_swfplayer(in_data + 8, in_size - 8, yading@11: &out_data, &out_size)) < 0) yading@11: goto fail; yading@11: #else yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Zlib is required for decompressing the SWF player file.\n"); yading@11: ret = AVERROR(EINVAL); yading@11: goto fail; yading@11: #endif yading@11: swfsize = out_size; yading@11: swfdata = out_data; yading@11: } else { yading@11: swfsize = in_size; yading@11: swfdata = in_data; yading@11: } yading@11: yading@11: /* Compute the SHA256 hash of the SWF player file. */ yading@11: if ((ret = ff_rtmp_calc_digest(swfdata, swfsize, 0, yading@11: "Genuine Adobe Flash Player 001", 30, yading@11: swfhash)) < 0) yading@11: goto fail; yading@11: yading@11: /* Set SWFVerification parameters. */ yading@11: av_opt_set_bin(rt, "rtmp_swfhash", swfhash, 32, 0); yading@11: rt->swfsize = swfsize; yading@11: yading@11: fail: yading@11: av_freep(&in_data); yading@11: av_freep(&out_data); yading@11: ffurl_close(stream); yading@11: return ret; yading@11: } yading@11: yading@11: /** yading@11: * Perform handshake with the server by means of exchanging pseudorandom data yading@11: * signed with HMAC-SHA2 digest. yading@11: * yading@11: * @return 0 if handshake succeeds, negative value otherwise yading@11: */ yading@11: static int rtmp_handshake(URLContext *s, RTMPContext *rt) yading@11: { yading@11: AVLFG rnd; yading@11: uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { yading@11: 3, // unencrypted data yading@11: 0, 0, 0, 0, // client uptime yading@11: RTMP_CLIENT_VER1, yading@11: RTMP_CLIENT_VER2, yading@11: RTMP_CLIENT_VER3, yading@11: RTMP_CLIENT_VER4, yading@11: }; yading@11: uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; yading@11: uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; yading@11: int i; yading@11: int server_pos, client_pos; yading@11: uint8_t digest[32], signature[32]; yading@11: int ret, type = 0; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Handshaking...\n"); yading@11: yading@11: av_lfg_init(&rnd, 0xDEADC0DE); yading@11: // generate handshake packet - 1536 bytes of pseudorandom data yading@11: for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) yading@11: tosend[i] = av_lfg_get(&rnd) >> 24; yading@11: yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* When the client wants to use RTMPE, we have to change the command yading@11: * byte to 0x06 which means to use encrypted data and we have to set yading@11: * the flash version to at least 9.0.115.0. */ yading@11: tosend[0] = 6; yading@11: tosend[5] = 128; yading@11: tosend[6] = 0; yading@11: tosend[7] = 3; yading@11: tosend[8] = 2; yading@11: yading@11: /* Initialize the Diffie-Hellmann context and generate the public key yading@11: * to send to the server. */ yading@11: if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0) yading@11: return ret; yading@11: } yading@11: yading@11: client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted); yading@11: if (client_pos < 0) yading@11: return client_pos; yading@11: yading@11: if ((ret = ffurl_write(rt->stream, tosend, yading@11: RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n"); yading@11: return ret; yading@11: } yading@11: yading@11: if ((ret = ffurl_read_complete(rt->stream, serverdata, yading@11: RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); yading@11: return ret; yading@11: } yading@11: yading@11: if ((ret = ffurl_read_complete(rt->stream, clientdata, yading@11: RTMP_HANDSHAKE_PACKET_SIZE)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); yading@11: return ret; yading@11: } yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]); yading@11: av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", yading@11: serverdata[5], serverdata[6], serverdata[7], serverdata[8]); yading@11: yading@11: if (rt->is_input && serverdata[5] >= 3) { yading@11: server_pos = rtmp_validate_digest(serverdata + 1, 772); yading@11: if (server_pos < 0) yading@11: return server_pos; yading@11: yading@11: if (!server_pos) { yading@11: type = 1; yading@11: server_pos = rtmp_validate_digest(serverdata + 1, 8); yading@11: if (server_pos < 0) yading@11: return server_pos; yading@11: yading@11: if (!server_pos) { yading@11: av_log(s, AV_LOG_ERROR, "Server response validating failed\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: } yading@11: yading@11: /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, yading@11: * key are the last 32 bytes of the server handshake. */ yading@11: if (rt->swfsize) { yading@11: if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 + yading@11: RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0) yading@11: return ret; yading@11: } yading@11: yading@11: ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, yading@11: rtmp_server_key, sizeof(rtmp_server_key), yading@11: digest); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32, yading@11: 0, digest, 32, signature); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* Compute the shared secret key sent by the server and initialize yading@11: * the RC4 encryption. */ yading@11: if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, yading@11: tosend + 1, type)) < 0) yading@11: return ret; yading@11: yading@11: /* Encrypt the signature received by the server. */ yading@11: ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]); yading@11: } yading@11: yading@11: if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { yading@11: av_log(s, AV_LOG_ERROR, "Signature mismatch\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: yading@11: for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) yading@11: tosend[i] = av_lfg_get(&rnd) >> 24; yading@11: ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, yading@11: rtmp_player_key, sizeof(rtmp_player_key), yading@11: digest); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, yading@11: digest, 32, yading@11: tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* Encrypt the signature to be send to the server. */ yading@11: ff_rtmpe_encrypt_sig(rt->stream, tosend + yading@11: RTMP_HANDSHAKE_PACKET_SIZE - 32, digest, yading@11: serverdata[0]); yading@11: } yading@11: yading@11: // write reply back to the server yading@11: if ((ret = ffurl_write(rt->stream, tosend, yading@11: RTMP_HANDSHAKE_PACKET_SIZE)) < 0) yading@11: return ret; yading@11: yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* Set RC4 keys for encryption and update the keystreams. */ yading@11: if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) yading@11: return ret; yading@11: } yading@11: } else { yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* Compute the shared secret key sent by the server and initialize yading@11: * the RC4 encryption. */ yading@11: if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, yading@11: tosend + 1, 1)) < 0) yading@11: return ret; yading@11: yading@11: if (serverdata[0] == 9) { yading@11: /* Encrypt the signature received by the server. */ yading@11: ff_rtmpe_encrypt_sig(rt->stream, signature, digest, yading@11: serverdata[0]); yading@11: } yading@11: } yading@11: yading@11: if ((ret = ffurl_write(rt->stream, serverdata + 1, yading@11: RTMP_HANDSHAKE_PACKET_SIZE)) < 0) yading@11: return ret; yading@11: yading@11: if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { yading@11: /* Set RC4 keys for encryption and update the keystreams. */ yading@11: if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) yading@11: return ret; yading@11: } yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int, yading@11: uint32_t *second_int, char *arraydata, yading@11: int size) yading@11: { yading@11: int inoutsize; yading@11: yading@11: inoutsize = ffurl_read_complete(rt->stream, arraydata, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (inoutsize <= 0) yading@11: return AVERROR(EIO); yading@11: if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { yading@11: av_log(rt, AV_LOG_ERROR, "Erroneous Message size %d" yading@11: " not following standard\n", (int)inoutsize); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: *first_int = AV_RB32(arraydata); yading@11: *second_int = AV_RB32(arraydata + 4); yading@11: return 0; yading@11: } yading@11: yading@11: static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int, yading@11: uint32_t second_int, char *arraydata, int size) yading@11: { yading@11: int inoutsize; yading@11: yading@11: AV_WB32(arraydata, first_int); yading@11: AV_WB32(arraydata + 4, first_int); yading@11: inoutsize = ffurl_write(rt->stream, arraydata, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { yading@11: av_log(rt, AV_LOG_ERROR, "Unable to write answer\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: /** yading@11: * rtmp handshake server side yading@11: */ yading@11: static int rtmp_server_handshake(URLContext *s, RTMPContext *rt) yading@11: { yading@11: uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; yading@11: uint32_t hs_epoch; yading@11: uint32_t hs_my_epoch; yading@11: uint8_t hs_c1[RTMP_HANDSHAKE_PACKET_SIZE]; yading@11: uint8_t hs_s1[RTMP_HANDSHAKE_PACKET_SIZE]; yading@11: uint32_t zeroes; yading@11: uint32_t temp = 0; yading@11: int randomidx = 0; yading@11: int inoutsize = 0; yading@11: int ret; yading@11: yading@11: inoutsize = ffurl_read_complete(rt->stream, buffer, 1); // Receive C0 yading@11: if (inoutsize <= 0) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to read handshake\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: // Check Version yading@11: if (buffer[0] != 3) { yading@11: av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: if (ffurl_write(rt->stream, buffer, 1) <= 0) { // Send S0 yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Unable to write answer - RTMP S0\n"); yading@11: return AVERROR(EIO); yading@11: } yading@11: /* Receive C1 */ yading@11: ret = rtmp_receive_hs_packet(rt, &hs_epoch, &zeroes, hs_c1, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (ret) { yading@11: av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n"); yading@11: return ret; yading@11: } yading@11: if (zeroes) yading@11: av_log(s, AV_LOG_WARNING, "Erroneous C1 Message zero != 0\n"); yading@11: /* Send S1 */ yading@11: /* By now same epoch will be sent */ yading@11: hs_my_epoch = hs_epoch; yading@11: /* Generate random */ yading@11: for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE); yading@11: randomidx += 4) yading@11: AV_WB32(hs_s1 + randomidx, av_get_random_seed()); yading@11: yading@11: ret = rtmp_send_hs_packet(rt, hs_my_epoch, 0, hs_s1, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (ret) { yading@11: av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n"); yading@11: return ret; yading@11: } yading@11: /* Send S2 */ yading@11: ret = rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (ret) { yading@11: av_log(s, AV_LOG_ERROR, "RTMP Handshake S2 Error\n"); yading@11: return ret; yading@11: } yading@11: /* Receive C2 */ yading@11: ret = rtmp_receive_hs_packet(rt, &temp, &zeroes, buffer, yading@11: RTMP_HANDSHAKE_PACKET_SIZE); yading@11: if (ret) { yading@11: av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n"); yading@11: return ret; yading@11: } yading@11: if (temp != hs_my_epoch) yading@11: av_log(s, AV_LOG_WARNING, yading@11: "Erroneous C2 Message epoch does not match up with C1 epoch\n"); yading@11: if (memcmp(buffer + 8, hs_s1 + 8, yading@11: RTMP_HANDSHAKE_PACKET_SIZE - 8)) yading@11: av_log(s, AV_LOG_WARNING, yading@11: "Erroneous C2 Message random does not match up\n"); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_chunk_size(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int ret; yading@11: yading@11: if (pkt->data_size < 4) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Too short chunk size change packet (%d)\n", yading@11: pkt->data_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: if (!rt->is_input) { yading@11: /* Send the same chunk size change packet back to the server, yading@11: * setting the outgoing chunk size to the same as the incoming one. */ yading@11: if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1])) < 0) yading@11: return ret; yading@11: rt->out_chunk_size = AV_RB32(pkt->data); yading@11: } yading@11: yading@11: rt->in_chunk_size = AV_RB32(pkt->data); yading@11: if (rt->in_chunk_size <= 0) { yading@11: av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n", yading@11: rt->in_chunk_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n", yading@11: rt->in_chunk_size); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_ping(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int t, ret; yading@11: yading@11: if (pkt->data_size < 2) { yading@11: av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", yading@11: pkt->data_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: t = AV_RB16(pkt->data); yading@11: if (t == 6) { yading@11: if ((ret = gen_pong(s, rt, pkt)) < 0) yading@11: return ret; yading@11: } else if (t == 26) { yading@11: if (rt->swfsize) { yading@11: if ((ret = gen_swf_verification(s, rt)) < 0) yading@11: return ret; yading@11: } else { yading@11: av_log(s, AV_LOG_WARNING, "Ignoring SWFVerification request.\n"); yading@11: } yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_client_bw(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: yading@11: if (pkt->data_size < 4) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Client bandwidth report packet is less than 4 bytes long (%d)\n", yading@11: pkt->data_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: rt->client_report_size = AV_RB32(pkt->data); yading@11: if (rt->client_report_size <= 0) { yading@11: av_log(s, AV_LOG_ERROR, "Incorrect client bandwidth %d\n", yading@11: rt->client_report_size); yading@11: return AVERROR_INVALIDDATA; yading@11: yading@11: } yading@11: av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", rt->client_report_size); yading@11: rt->client_report_size >>= 1; yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_server_bw(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: yading@11: if (pkt->data_size < 4) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Too short server bandwidth report packet (%d)\n", yading@11: pkt->data_size); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: rt->server_bw = AV_RB32(pkt->data); yading@11: if (rt->server_bw <= 0) { yading@11: av_log(s, AV_LOG_ERROR, "Incorrect server bandwidth %d\n", yading@11: rt->server_bw); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt, yading@11: const char *opaque, const char *challenge) yading@11: { yading@11: uint8_t hash[16]; yading@11: char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10]; yading@11: struct AVMD5 *md5 = av_md5_alloc(); yading@11: if (!md5) yading@11: return AVERROR(ENOMEM); yading@11: yading@11: snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed()); yading@11: yading@11: av_md5_init(md5); yading@11: av_md5_update(md5, user, strlen(user)); yading@11: av_md5_update(md5, salt, strlen(salt)); yading@11: av_md5_update(md5, rt->password, strlen(rt->password)); yading@11: av_md5_final(md5, hash); yading@11: av_base64_encode(hashstr, sizeof(hashstr), hash, yading@11: sizeof(hash)); yading@11: av_md5_init(md5); yading@11: av_md5_update(md5, hashstr, strlen(hashstr)); yading@11: if (opaque) yading@11: av_md5_update(md5, opaque, strlen(opaque)); yading@11: else if (challenge) yading@11: av_md5_update(md5, challenge, strlen(challenge)); yading@11: av_md5_update(md5, challenge2, strlen(challenge2)); yading@11: av_md5_final(md5, hash); yading@11: av_base64_encode(hashstr, sizeof(hashstr), hash, yading@11: sizeof(hash)); yading@11: snprintf(rt->auth_params, sizeof(rt->auth_params), yading@11: "?authmod=%s&user=%s&challenge=%s&response=%s", yading@11: "adobe", user, challenge2, hashstr); yading@11: if (opaque) yading@11: av_strlcatf(rt->auth_params, sizeof(rt->auth_params), yading@11: "&opaque=%s", opaque); yading@11: yading@11: av_free(md5); yading@11: return 0; yading@11: } yading@11: yading@11: static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce) yading@11: { yading@11: uint8_t hash[16]; yading@11: char hashstr1[33], hashstr2[33]; yading@11: const char *realm = "live"; yading@11: const char *method = "publish"; yading@11: const char *qop = "auth"; yading@11: const char *nc = "00000001"; yading@11: char cnonce[10]; yading@11: struct AVMD5 *md5 = av_md5_alloc(); yading@11: if (!md5) yading@11: return AVERROR(ENOMEM); yading@11: yading@11: snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed()); yading@11: yading@11: av_md5_init(md5); yading@11: av_md5_update(md5, user, strlen(user)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, realm, strlen(realm)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, rt->password, strlen(rt->password)); yading@11: av_md5_final(md5, hash); yading@11: ff_data_to_hex(hashstr1, hash, 16, 1); yading@11: hashstr1[32] = '\0'; yading@11: yading@11: av_md5_init(md5); yading@11: av_md5_update(md5, method, strlen(method)); yading@11: av_md5_update(md5, ":/", 2); yading@11: av_md5_update(md5, rt->app, strlen(rt->app)); yading@11: av_md5_final(md5, hash); yading@11: ff_data_to_hex(hashstr2, hash, 16, 1); yading@11: hashstr2[32] = '\0'; yading@11: yading@11: av_md5_init(md5); yading@11: av_md5_update(md5, hashstr1, strlen(hashstr1)); yading@11: av_md5_update(md5, ":", 1); yading@11: if (nonce) yading@11: av_md5_update(md5, nonce, strlen(nonce)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, nc, strlen(nc)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, cnonce, strlen(cnonce)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, qop, strlen(qop)); yading@11: av_md5_update(md5, ":", 1); yading@11: av_md5_update(md5, hashstr2, strlen(hashstr2)); yading@11: av_md5_final(md5, hash); yading@11: ff_data_to_hex(hashstr1, hash, 16, 1); yading@11: yading@11: snprintf(rt->auth_params, sizeof(rt->auth_params), yading@11: "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s", yading@11: "llnw", user, nonce, cnonce, nc, hashstr1); yading@11: yading@11: av_free(md5); yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_connect_error(URLContext *s, const char *desc) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: char buf[300], *ptr, authmod[15]; yading@11: int i = 0, ret = 0; yading@11: const char *user = "", *salt = "", *opaque = NULL, yading@11: *challenge = NULL, *cptr = NULL, *nonce = NULL; yading@11: yading@11: if (!(cptr = strstr(desc, "authmod=adobe")) && yading@11: !(cptr = strstr(desc, "authmod=llnw"))) { yading@11: av_log(s, AV_LOG_ERROR, yading@11: "Unknown connect error (unsupported authentication method?)\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } yading@11: cptr += strlen("authmod="); yading@11: while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1) yading@11: authmod[i++] = *cptr++; yading@11: authmod[i] = '\0'; yading@11: yading@11: if (!rt->username[0] || !rt->password[0]) { yading@11: av_log(s, AV_LOG_ERROR, "No credentials set\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } yading@11: yading@11: if (strstr(desc, "?reason=authfailed")) { yading@11: av_log(s, AV_LOG_ERROR, "Incorrect username/password\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } else if (strstr(desc, "?reason=nosuchuser")) { yading@11: av_log(s, AV_LOG_ERROR, "Incorrect username\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } yading@11: yading@11: if (rt->auth_tried) { yading@11: av_log(s, AV_LOG_ERROR, "Authentication failed\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } yading@11: yading@11: rt->auth_params[0] = '\0'; yading@11: yading@11: if (strstr(desc, "code=403 need auth")) { yading@11: snprintf(rt->auth_params, sizeof(rt->auth_params), yading@11: "?authmod=%s&user=%s", authmod, rt->username); yading@11: return 0; yading@11: } yading@11: yading@11: if (!(cptr = strstr(desc, "?reason=needauth"))) { yading@11: av_log(s, AV_LOG_ERROR, "No auth parameters found\n"); yading@11: return AVERROR_UNKNOWN; yading@11: } yading@11: yading@11: av_strlcpy(buf, cptr + 1, sizeof(buf)); yading@11: ptr = buf; yading@11: yading@11: while (ptr) { yading@11: char *next = strchr(ptr, '&'); yading@11: char *value = strchr(ptr, '='); yading@11: if (next) yading@11: *next++ = '\0'; yading@11: if (value) yading@11: *value++ = '\0'; yading@11: if (!strcmp(ptr, "user")) { yading@11: user = value; yading@11: } else if (!strcmp(ptr, "salt")) { yading@11: salt = value; yading@11: } else if (!strcmp(ptr, "opaque")) { yading@11: opaque = value; yading@11: } else if (!strcmp(ptr, "challenge")) { yading@11: challenge = value; yading@11: } else if (!strcmp(ptr, "nonce")) { yading@11: nonce = value; yading@11: } yading@11: ptr = next; yading@11: } yading@11: yading@11: if (!strcmp(authmod, "adobe")) { yading@11: if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0) yading@11: return ret; yading@11: } else { yading@11: if ((ret = do_llnw_auth(rt, user, nonce)) < 0) yading@11: return ret; yading@11: } yading@11: yading@11: rt->auth_tried = 1; yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_invoke_error(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: const uint8_t *data_end = pkt->data + pkt->data_size; yading@11: char *tracked_method = NULL; yading@11: int level = AV_LOG_ERROR; yading@11: uint8_t tmpstr[256]; yading@11: int ret; yading@11: yading@11: if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0) yading@11: return ret; yading@11: yading@11: if (!ff_amf_get_field_value(pkt->data + 9, data_end, yading@11: "description", tmpstr, sizeof(tmpstr))) { yading@11: if (tracked_method && (!strcmp(tracked_method, "_checkbw") || yading@11: !strcmp(tracked_method, "releaseStream") || yading@11: !strcmp(tracked_method, "FCSubscribe") || yading@11: !strcmp(tracked_method, "FCPublish"))) { yading@11: /* Gracefully ignore Adobe-specific historical artifact errors. */ yading@11: level = AV_LOG_WARNING; yading@11: ret = 0; yading@11: } else if (tracked_method && !strcmp(tracked_method, "connect")) { yading@11: ret = handle_connect_error(s, tmpstr); yading@11: if (!ret) { yading@11: rt->do_reconnect = 1; yading@11: level = AV_LOG_VERBOSE; yading@11: } yading@11: } else yading@11: ret = AVERROR_UNKNOWN; yading@11: av_log(s, level, "Server error: %s\n", tmpstr); yading@11: } yading@11: yading@11: av_free(tracked_method); yading@11: return ret; yading@11: } yading@11: yading@11: static int send_invoke_response(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: double seqnum; yading@11: char filename[64]; yading@11: char command[64]; yading@11: char statusmsg[128]; yading@11: int stringlen; yading@11: char *pchar; yading@11: const uint8_t *p = pkt->data; yading@11: uint8_t *pp = NULL; yading@11: RTMPPacket spkt = { 0 }; yading@11: GetByteContext gbc; yading@11: int ret; yading@11: yading@11: bytestream2_init(&gbc, p, pkt->data_size); yading@11: if (ff_amf_read_string(&gbc, command, sizeof(command), yading@11: &stringlen)) { yading@11: av_log(s, AV_LOG_ERROR, "Error in PT_INVOKE\n"); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: ret = ff_amf_read_number(&gbc, &seqnum); yading@11: if (ret) yading@11: return ret; yading@11: ret = ff_amf_read_null(&gbc); yading@11: if (ret) yading@11: return ret; yading@11: if (!strcmp(command, "FCPublish") || yading@11: !strcmp(command, "publish")) { yading@11: ret = ff_amf_read_string(&gbc, filename, yading@11: sizeof(filename), &stringlen); yading@11: // check with url yading@11: if (s->filename) { yading@11: pchar = strrchr(s->filename, '/'); yading@11: if (!pchar) { yading@11: av_log(s, AV_LOG_WARNING, yading@11: "Unable to find / in url %s, bad format\n", yading@11: s->filename); yading@11: pchar = s->filename; yading@11: } yading@11: pchar++; yading@11: if (strcmp(pchar, filename)) yading@11: av_log(s, AV_LOG_WARNING, "Unexpected stream %s, expecting" yading@11: " %s\n", filename, pchar); yading@11: } yading@11: rt->state = STATE_RECEIVING; yading@11: } yading@11: yading@11: if (!strcmp(command, "FCPublish")) { yading@11: if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_INVOKE, 0, yading@11: RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); yading@11: return ret; yading@11: } yading@11: pp = spkt.data; yading@11: ff_amf_write_string(&pp, "onFCPublish"); yading@11: } else if (!strcmp(command, "publish")) { yading@11: PutByteContext pbc; yading@11: // Send Stream Begin 1 yading@11: if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL, yading@11: RTMP_PT_PING, 0, 6)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); yading@11: return ret; yading@11: } yading@11: pp = spkt.data; yading@11: bytestream2_init_writer(&pbc, pp, spkt.data_size); yading@11: bytestream2_put_be16(&pbc, 0); // 0 -> Stream Begin yading@11: bytestream2_put_be32(&pbc, rt->nb_streamid); yading@11: ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&spkt); yading@11: if (ret < 0) yading@11: return ret; yading@11: yading@11: // Send onStatus(NetStream.Publish.Start) yading@11: if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_INVOKE, 0, yading@11: RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); yading@11: return ret; yading@11: } yading@11: spkt.extra = pkt->extra; yading@11: pp = spkt.data; yading@11: ff_amf_write_string(&pp, "onStatus"); yading@11: ff_amf_write_number(&pp, 0); yading@11: ff_amf_write_null(&pp); yading@11: yading@11: ff_amf_write_object_start(&pp); yading@11: ff_amf_write_field_name(&pp, "level"); yading@11: ff_amf_write_string(&pp, "status"); yading@11: ff_amf_write_field_name(&pp, "code"); yading@11: ff_amf_write_string(&pp, "NetStream.Publish.Start"); yading@11: ff_amf_write_field_name(&pp, "description"); yading@11: snprintf(statusmsg, sizeof(statusmsg), yading@11: "%s is now published", filename); yading@11: ff_amf_write_string(&pp, statusmsg); yading@11: ff_amf_write_field_name(&pp, "details"); yading@11: ff_amf_write_string(&pp, filename); yading@11: ff_amf_write_field_name(&pp, "clientid"); yading@11: snprintf(statusmsg, sizeof(statusmsg), "%s", LIBAVFORMAT_IDENT); yading@11: ff_amf_write_string(&pp, statusmsg); yading@11: ff_amf_write_object_end(&pp); yading@11: yading@11: } else { yading@11: if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, yading@11: RTMP_PT_INVOKE, 0, yading@11: RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { yading@11: av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); yading@11: return ret; yading@11: } yading@11: pp = spkt.data; yading@11: ff_amf_write_string(&pp, "_result"); yading@11: ff_amf_write_number(&pp, seqnum); yading@11: ff_amf_write_null(&pp); yading@11: if (!strcmp(command, "createStream")) { yading@11: rt->nb_streamid++; yading@11: if (rt->nb_streamid == 0 || rt->nb_streamid == 2) yading@11: rt->nb_streamid++; /* Values 0 and 2 are reserved */ yading@11: ff_amf_write_number(&pp, rt->nb_streamid); yading@11: /* By now we don't control which streams are removed in yading@11: * deleteStream. There is no stream creation control yading@11: * if a client creates more than 2^32 - 2 streams. */ yading@11: } yading@11: } yading@11: spkt.data_size = pp - spkt.data; yading@11: ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, yading@11: rt->prev_pkt[1]); yading@11: ff_rtmp_packet_destroy(&spkt); yading@11: return ret; yading@11: } yading@11: yading@11: static int handle_invoke_result(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: char *tracked_method = NULL; yading@11: int ret = 0; yading@11: yading@11: if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0) yading@11: return ret; yading@11: yading@11: if (!tracked_method) { yading@11: /* Ignore this reply when the current method is not tracked. */ yading@11: return ret; yading@11: } yading@11: yading@11: if (!memcmp(tracked_method, "connect", 7)) { yading@11: if (!rt->is_input) { yading@11: if ((ret = gen_release_stream(s, rt)) < 0) yading@11: goto fail; yading@11: yading@11: if ((ret = gen_fcpublish_stream(s, rt)) < 0) yading@11: goto fail; yading@11: } else { yading@11: if ((ret = gen_server_bw(s, rt)) < 0) yading@11: goto fail; yading@11: } yading@11: yading@11: if ((ret = gen_create_stream(s, rt)) < 0) yading@11: goto fail; yading@11: yading@11: if (rt->is_input) { yading@11: /* Send the FCSubscribe command when the name of live yading@11: * stream is defined by the user or if it's a live stream. */ yading@11: if (rt->subscribe) { yading@11: if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0) yading@11: goto fail; yading@11: } else if (rt->live == -1) { yading@11: if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0) yading@11: goto fail; yading@11: } yading@11: } yading@11: } else if (!memcmp(tracked_method, "createStream", 12)) { yading@11: //extract a number from the result yading@11: if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) { yading@11: av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n"); yading@11: } else { yading@11: rt->main_channel_id = av_int2double(AV_RB64(pkt->data + 21)); yading@11: } yading@11: yading@11: if (!rt->is_input) { yading@11: if ((ret = gen_publish(s, rt)) < 0) yading@11: goto fail; yading@11: } else { yading@11: if ((ret = gen_play(s, rt)) < 0) yading@11: goto fail; yading@11: if ((ret = gen_buffer_time(s, rt)) < 0) yading@11: goto fail; yading@11: } yading@11: } yading@11: yading@11: fail: yading@11: av_free(tracked_method); yading@11: return ret; yading@11: } yading@11: yading@11: static int handle_invoke_status(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: const uint8_t *data_end = pkt->data + pkt->data_size; yading@11: const uint8_t *ptr = pkt->data + 11; yading@11: uint8_t tmpstr[256]; yading@11: int i, t; yading@11: yading@11: for (i = 0; i < 2; i++) { yading@11: t = ff_amf_tag_size(ptr, data_end); yading@11: if (t < 0) yading@11: return 1; yading@11: ptr += t; yading@11: } yading@11: yading@11: t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr)); yading@11: if (!t && !strcmp(tmpstr, "error")) { yading@11: if (!ff_amf_get_field_value(ptr, data_end, yading@11: "description", tmpstr, sizeof(tmpstr))) yading@11: av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr); yading@11: return -1; yading@11: } yading@11: yading@11: t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr)); yading@11: if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING; yading@11: if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED; yading@11: if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED; yading@11: if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING; yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int handle_invoke(URLContext *s, RTMPPacket *pkt) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int ret = 0; yading@11: yading@11: //TODO: check for the messages sent for wrong state? yading@11: if (!memcmp(pkt->data, "\002\000\006_error", 9)) { yading@11: if ((ret = handle_invoke_error(s, pkt)) < 0) yading@11: return ret; yading@11: } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) { yading@11: if ((ret = handle_invoke_result(s, pkt)) < 0) yading@11: return ret; yading@11: } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) { yading@11: if ((ret = handle_invoke_status(s, pkt)) < 0) yading@11: return ret; yading@11: } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) { yading@11: if ((ret = gen_check_bw(s, rt)) < 0) yading@11: return ret; yading@11: } else if (!memcmp(pkt->data, "\002\000\015releaseStream", 16) || yading@11: !memcmp(pkt->data, "\002\000\011FCPublish", 12) || yading@11: !memcmp(pkt->data, "\002\000\007publish", 10) || yading@11: !memcmp(pkt->data, "\002\000\010_checkbw", 11) || yading@11: !memcmp(pkt->data, "\002\000\014createStream", 15)) { yading@11: if ((ret = send_invoke_response(s, pkt)) < 0) yading@11: return ret; yading@11: } yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: static int handle_notify(URLContext *s, RTMPPacket *pkt) { yading@11: RTMPContext *rt = s->priv_data; yading@11: const uint8_t *p = NULL; yading@11: uint8_t *cp = NULL; yading@11: uint8_t commandbuffer[64]; yading@11: char statusmsg[128]; yading@11: int stringlen; yading@11: GetByteContext gbc; yading@11: PutByteContext pbc; yading@11: uint32_t ts; yading@11: int old_flv_size; yading@11: const uint8_t *datatowrite; yading@11: unsigned datatowritelength; yading@11: yading@11: p = pkt->data; yading@11: bytestream2_init(&gbc, p, pkt->data_size); yading@11: if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer), yading@11: &stringlen)) yading@11: return AVERROR_INVALIDDATA; yading@11: if (!strcmp(commandbuffer, "@setDataFrame")) { yading@11: datatowrite = gbc.buffer; yading@11: datatowritelength = bytestream2_get_bytes_left(&gbc); yading@11: if (ff_amf_read_string(&gbc, statusmsg, yading@11: sizeof(statusmsg), &stringlen)) yading@11: return AVERROR_INVALIDDATA; yading@11: if (strcmp(statusmsg, "onMetaData")) { yading@11: av_log(s, AV_LOG_INFO, "Expecting onMetadata but got %s\n", yading@11: statusmsg); yading@11: return 0; yading@11: } yading@11: yading@11: /* Provide ECMAArray to flv */ yading@11: ts = pkt->timestamp; yading@11: yading@11: // generate packet header and put data into buffer for FLV demuxer yading@11: if (rt->flv_off < rt->flv_size) { yading@11: old_flv_size = rt->flv_size; yading@11: rt->flv_size += datatowritelength + 15; yading@11: } else { yading@11: old_flv_size = 0; yading@11: rt->flv_size = datatowritelength + 15; yading@11: rt->flv_off = 0; yading@11: } yading@11: yading@11: cp = av_realloc(rt->flv_data, rt->flv_size); yading@11: if (!cp) yading@11: return AVERROR(ENOMEM); yading@11: rt->flv_data = cp; yading@11: bytestream2_init_writer(&pbc, cp, rt->flv_size); yading@11: bytestream2_skip_p(&pbc, old_flv_size); yading@11: bytestream2_put_byte(&pbc, pkt->type); yading@11: bytestream2_put_be24(&pbc, datatowritelength); yading@11: bytestream2_put_be24(&pbc, ts); yading@11: bytestream2_put_byte(&pbc, ts >> 24); yading@11: bytestream2_put_be24(&pbc, 0); yading@11: bytestream2_put_buffer(&pbc, datatowrite, datatowritelength); yading@11: bytestream2_put_be32(&pbc, 0); yading@11: } yading@11: return 0; yading@11: } yading@11: yading@11: /** yading@11: * Parse received packet and possibly perform some action depending on yading@11: * the packet contents. yading@11: * @return 0 for no errors, negative values for serious errors which prevent yading@11: * further communications, positive values for uncritical errors yading@11: */ yading@11: static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) yading@11: { yading@11: int ret; yading@11: yading@11: #ifdef DEBUG yading@11: ff_rtmp_packet_dump(s, pkt); yading@11: #endif yading@11: yading@11: switch (pkt->type) { yading@11: case RTMP_PT_BYTES_READ: yading@11: av_dlog(s, "received bytes read report\n"); yading@11: break; yading@11: case RTMP_PT_CHUNK_SIZE: yading@11: if ((ret = handle_chunk_size(s, pkt)) < 0) yading@11: return ret; yading@11: break; yading@11: case RTMP_PT_PING: yading@11: if ((ret = handle_ping(s, pkt)) < 0) yading@11: return ret; yading@11: break; yading@11: case RTMP_PT_CLIENT_BW: yading@11: if ((ret = handle_client_bw(s, pkt)) < 0) yading@11: return ret; yading@11: break; yading@11: case RTMP_PT_SERVER_BW: yading@11: if ((ret = handle_server_bw(s, pkt)) < 0) yading@11: return ret; yading@11: break; yading@11: case RTMP_PT_INVOKE: yading@11: if ((ret = handle_invoke(s, pkt)) < 0) yading@11: return ret; yading@11: break; yading@11: case RTMP_PT_VIDEO: yading@11: case RTMP_PT_AUDIO: yading@11: case RTMP_PT_METADATA: yading@11: case RTMP_PT_NOTIFY: yading@11: /* Audio, Video and Metadata packets are parsed in get_packet() */ yading@11: break; yading@11: default: yading@11: av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type); yading@11: break; yading@11: } yading@11: return 0; yading@11: } yading@11: yading@11: /** yading@11: * Interact with the server by receiving and sending RTMP packets until yading@11: * there is some significant data (media data or expected status notification). yading@11: * yading@11: * @param s reading context yading@11: * @param for_header non-zero value tells function to work until it yading@11: * gets notification from the server that playing has been started, yading@11: * otherwise function will work until some media data is received (or yading@11: * an error happens) yading@11: * @return 0 for successful operation, negative value in case of error yading@11: */ yading@11: static int get_packet(URLContext *s, int for_header) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int ret; yading@11: uint8_t *p; yading@11: const uint8_t *next; yading@11: uint32_t data_size; yading@11: uint32_t ts, cts, pts=0; yading@11: yading@11: if (rt->state == STATE_STOPPED) yading@11: return AVERROR_EOF; yading@11: yading@11: for (;;) { yading@11: RTMPPacket rpkt = { 0 }; yading@11: if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt, yading@11: rt->in_chunk_size, rt->prev_pkt[0])) <= 0) { yading@11: if (ret == 0) { yading@11: return AVERROR(EAGAIN); yading@11: } else { yading@11: return AVERROR(EIO); yading@11: } yading@11: } yading@11: rt->bytes_read += ret; yading@11: if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) { yading@11: av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n"); yading@11: if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0) yading@11: return ret; yading@11: rt->last_bytes_read = rt->bytes_read; yading@11: } yading@11: yading@11: ret = rtmp_parse_result(s, rt, &rpkt); yading@11: if (ret < 0) {//serious error in current packet yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return ret; yading@11: } yading@11: if (rt->do_reconnect && for_header) { yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return 0; yading@11: } yading@11: if (rt->state == STATE_STOPPED) { yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return AVERROR_EOF; yading@11: } yading@11: if (for_header && (rt->state == STATE_PLAYING || yading@11: rt->state == STATE_PUBLISHING || yading@11: rt->state == STATE_RECEIVING)) { yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return 0; yading@11: } yading@11: if (!rpkt.data_size || !rt->is_input) { yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: continue; yading@11: } yading@11: if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO || yading@11: (rpkt.type == RTMP_PT_NOTIFY && !memcmp("\002\000\012onMetaData", rpkt.data, 13))) { yading@11: ts = rpkt.timestamp; yading@11: yading@11: // generate packet header and put data into buffer for FLV demuxer yading@11: rt->flv_off = 0; yading@11: rt->flv_size = rpkt.data_size + 15; yading@11: rt->flv_data = p = av_realloc(rt->flv_data, rt->flv_size); yading@11: bytestream_put_byte(&p, rpkt.type); yading@11: bytestream_put_be24(&p, rpkt.data_size); yading@11: bytestream_put_be24(&p, ts); yading@11: bytestream_put_byte(&p, ts >> 24); yading@11: bytestream_put_be24(&p, 0); yading@11: bytestream_put_buffer(&p, rpkt.data, rpkt.data_size); yading@11: bytestream_put_be32(&p, 0); yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return 0; yading@11: } else if (rpkt.type == RTMP_PT_NOTIFY) { yading@11: ret = handle_notify(s, &rpkt); yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: if (ret) { yading@11: av_log(s, AV_LOG_ERROR, "Handle notify error\n"); yading@11: return ret; yading@11: } yading@11: return 0; yading@11: } else if (rpkt.type == RTMP_PT_METADATA) { yading@11: // we got raw FLV data, make it available for FLV demuxer yading@11: rt->flv_off = 0; yading@11: rt->flv_size = rpkt.data_size; yading@11: rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); yading@11: /* rewrite timestamps */ yading@11: next = rpkt.data; yading@11: ts = rpkt.timestamp; yading@11: while (next - rpkt.data < rpkt.data_size - 11) { yading@11: next++; yading@11: data_size = bytestream_get_be24(&next); yading@11: p=next; yading@11: cts = bytestream_get_be24(&next); yading@11: cts |= bytestream_get_byte(&next) << 24; yading@11: if (pts==0) yading@11: pts=cts; yading@11: ts += cts - pts; yading@11: pts = cts; yading@11: bytestream_put_be24(&p, ts); yading@11: bytestream_put_byte(&p, ts >> 24); yading@11: next += data_size + 3 + 4; yading@11: } yading@11: memcpy(rt->flv_data, rpkt.data, rpkt.data_size); yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: return 0; yading@11: } yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: } yading@11: } yading@11: yading@11: static int rtmp_close(URLContext *h) yading@11: { yading@11: RTMPContext *rt = h->priv_data; yading@11: int ret = 0; yading@11: yading@11: if (!rt->is_input) { yading@11: rt->flv_data = NULL; yading@11: if (rt->out_pkt.data_size) yading@11: ff_rtmp_packet_destroy(&rt->out_pkt); yading@11: if (rt->state > STATE_FCPUBLISH) yading@11: ret = gen_fcunpublish_stream(h, rt); yading@11: } yading@11: if (rt->state > STATE_HANDSHAKED) yading@11: ret = gen_delete_stream(h, rt); yading@11: yading@11: free_tracked_methods(rt); yading@11: av_freep(&rt->flv_data); yading@11: ffurl_close(rt->stream); yading@11: return ret; yading@11: } yading@11: yading@11: /** yading@11: * Open RTMP connection and verify that the stream can be played. yading@11: * yading@11: * URL syntax: rtmp://server[:port][/app][/playpath] yading@11: * where 'app' is first one or two directories in the path yading@11: * (e.g. /ondemand/, /flash/live/, etc.) yading@11: * and 'playpath' is a file name (the rest of the path, yading@11: * may be prefixed with "mp4:") yading@11: */ yading@11: static int rtmp_open(URLContext *s, const char *uri, int flags) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: char proto[8], hostname[256], path[1024], auth[100], *fname; yading@11: char *old_app; yading@11: uint8_t buf[2048]; yading@11: int port; yading@11: AVDictionary *opts = NULL; yading@11: int ret; yading@11: yading@11: if (rt->listen_timeout > 0) yading@11: rt->listen = 1; yading@11: yading@11: rt->is_input = !(flags & AVIO_FLAG_WRITE); yading@11: yading@11: av_url_split(proto, sizeof(proto), auth, sizeof(auth), yading@11: hostname, sizeof(hostname), &port, yading@11: path, sizeof(path), s->filename); yading@11: yading@11: if (auth[0]) { yading@11: char *ptr = strchr(auth, ':'); yading@11: if (ptr) { yading@11: *ptr = '\0'; yading@11: av_strlcpy(rt->username, auth, sizeof(rt->username)); yading@11: av_strlcpy(rt->password, ptr + 1, sizeof(rt->password)); yading@11: } yading@11: } yading@11: yading@11: if (rt->listen && strcmp(proto, "rtmp")) { yading@11: av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n", yading@11: proto); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) { yading@11: if (!strcmp(proto, "rtmpts")) yading@11: av_dict_set(&opts, "ffrtmphttp_tls", "1", 1); yading@11: yading@11: /* open the http tunneling connection */ yading@11: ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL); yading@11: } else if (!strcmp(proto, "rtmps")) { yading@11: /* open the tls connection */ yading@11: if (port < 0) yading@11: port = RTMPS_DEFAULT_PORT; yading@11: ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL); yading@11: } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) { yading@11: if (!strcmp(proto, "rtmpte")) yading@11: av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1); yading@11: yading@11: /* open the encrypted connection */ yading@11: ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL); yading@11: rt->encrypted = 1; yading@11: } else { yading@11: /* open the tcp connection */ yading@11: if (port < 0) yading@11: port = RTMP_DEFAULT_PORT; yading@11: if (rt->listen) yading@11: ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, yading@11: "?listen&listen_timeout=%d", yading@11: rt->listen_timeout * 1000); yading@11: else yading@11: ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL); yading@11: } yading@11: yading@11: reconnect: yading@11: if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE, yading@11: &s->interrupt_callback, &opts)) < 0) { yading@11: av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf); yading@11: goto fail; yading@11: } yading@11: yading@11: if (rt->swfverify) { yading@11: if ((ret = rtmp_calc_swfhash(s)) < 0) yading@11: goto fail; yading@11: } yading@11: yading@11: rt->state = STATE_START; yading@11: if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0) yading@11: goto fail; yading@11: if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0) yading@11: goto fail; yading@11: yading@11: rt->out_chunk_size = 128; yading@11: rt->in_chunk_size = 128; // Probably overwritten later yading@11: rt->state = STATE_HANDSHAKED; yading@11: yading@11: // Keep the application name when it has been defined by the user. yading@11: old_app = rt->app; yading@11: yading@11: rt->app = av_malloc(APP_MAX_LENGTH); yading@11: if (!rt->app) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: yading@11: //extract "app" part from path yading@11: if (!strncmp(path, "/ondemand/", 10)) { yading@11: fname = path + 10; yading@11: memcpy(rt->app, "ondemand", 9); yading@11: } else { yading@11: char *next = *path ? path + 1 : path; yading@11: char *p = strchr(next, '/'); yading@11: if (!p) { yading@11: fname = next; yading@11: rt->app[0] = '\0'; yading@11: } else { yading@11: // make sure we do not mismatch a playpath for an application instance yading@11: char *c = strchr(p + 1, ':'); yading@11: fname = strchr(p + 1, '/'); yading@11: if (!fname || (c && c < fname)) { yading@11: fname = p + 1; yading@11: av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH)); yading@11: } else { yading@11: fname++; yading@11: av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH)); yading@11: } yading@11: } yading@11: } yading@11: yading@11: if (old_app) { yading@11: // The name of application has been defined by the user, override it. yading@11: if (strlen(old_app) >= APP_MAX_LENGTH) { yading@11: ret = AVERROR(EINVAL); yading@11: goto fail; yading@11: } yading@11: av_free(rt->app); yading@11: rt->app = old_app; yading@11: } yading@11: yading@11: if (!rt->playpath) { yading@11: int len = strlen(fname); yading@11: yading@11: rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); yading@11: if (!rt->playpath) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: yading@11: if (!strchr(fname, ':') && len >= 4 && yading@11: (!strcmp(fname + len - 4, ".f4v") || yading@11: !strcmp(fname + len - 4, ".mp4"))) { yading@11: memcpy(rt->playpath, "mp4:", 5); yading@11: } else if (len >= 4 && !strcmp(fname + len - 4, ".flv")) { yading@11: fname[len - 4] = '\0'; yading@11: } else { yading@11: rt->playpath[0] = 0; yading@11: } yading@11: av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); yading@11: } yading@11: yading@11: if (!rt->tcurl) { yading@11: rt->tcurl = av_malloc(TCURL_MAX_LENGTH); yading@11: if (!rt->tcurl) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname, yading@11: port, "/%s", rt->app); yading@11: } yading@11: yading@11: if (!rt->flashver) { yading@11: rt->flashver = av_malloc(FLASHVER_MAX_LENGTH); yading@11: if (!rt->flashver) { yading@11: ret = AVERROR(ENOMEM); yading@11: goto fail; yading@11: } yading@11: if (rt->is_input) { yading@11: snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d", yading@11: RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2, yading@11: RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); yading@11: } else { yading@11: snprintf(rt->flashver, FLASHVER_MAX_LENGTH, yading@11: "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT); yading@11: } yading@11: } yading@11: yading@11: rt->client_report_size = 1048576; yading@11: rt->bytes_read = 0; yading@11: rt->last_bytes_read = 0; yading@11: rt->server_bw = 2500000; yading@11: yading@11: av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", yading@11: proto, path, rt->app, rt->playpath); yading@11: if (!rt->listen) { yading@11: if ((ret = gen_connect(s, rt)) < 0) yading@11: goto fail; yading@11: } else { yading@11: if (read_connect(s, s->priv_data) < 0) yading@11: goto fail; yading@11: rt->is_input = 1; yading@11: } yading@11: yading@11: do { yading@11: ret = get_packet(s, 1); yading@11: } while (ret == EAGAIN); yading@11: if (ret < 0) yading@11: goto fail; yading@11: yading@11: if (rt->do_reconnect) { yading@11: ffurl_close(rt->stream); yading@11: rt->stream = NULL; yading@11: rt->do_reconnect = 0; yading@11: rt->nb_invokes = 0; yading@11: memset(rt->prev_pkt, 0, sizeof(rt->prev_pkt)); yading@11: free_tracked_methods(rt); yading@11: goto reconnect; yading@11: } yading@11: yading@11: if (rt->is_input) { yading@11: // generate FLV header for demuxer yading@11: rt->flv_size = 13; yading@11: rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); yading@11: rt->flv_off = 0; yading@11: memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size); yading@11: } else { yading@11: rt->flv_size = 0; yading@11: rt->flv_data = NULL; yading@11: rt->flv_off = 0; yading@11: rt->skip_bytes = 13; yading@11: } yading@11: yading@11: s->max_packet_size = rt->stream->max_packet_size; yading@11: s->is_streamed = 1; yading@11: return 0; yading@11: yading@11: fail: yading@11: av_dict_free(&opts); yading@11: rtmp_close(s); yading@11: return ret; yading@11: } yading@11: yading@11: static int rtmp_read(URLContext *s, uint8_t *buf, int size) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int orig_size = size; yading@11: int ret; yading@11: yading@11: while (size > 0) { yading@11: int data_left = rt->flv_size - rt->flv_off; yading@11: yading@11: if (data_left >= size) { yading@11: memcpy(buf, rt->flv_data + rt->flv_off, size); yading@11: rt->flv_off += size; yading@11: return orig_size; yading@11: } yading@11: if (data_left > 0) { yading@11: memcpy(buf, rt->flv_data + rt->flv_off, data_left); yading@11: buf += data_left; yading@11: size -= data_left; yading@11: rt->flv_off = rt->flv_size; yading@11: return data_left; yading@11: } yading@11: if ((ret = get_packet(s, 0)) < 0) yading@11: return ret; yading@11: } yading@11: return orig_size; yading@11: } yading@11: yading@11: static int rtmp_write(URLContext *s, const uint8_t *buf, int size) yading@11: { yading@11: RTMPContext *rt = s->priv_data; yading@11: int size_temp = size; yading@11: int pktsize, pkttype; yading@11: uint32_t ts; yading@11: const uint8_t *buf_temp = buf; yading@11: uint8_t c; yading@11: int ret; yading@11: yading@11: do { yading@11: if (rt->skip_bytes) { yading@11: int skip = FFMIN(rt->skip_bytes, size_temp); yading@11: buf_temp += skip; yading@11: size_temp -= skip; yading@11: rt->skip_bytes -= skip; yading@11: continue; yading@11: } yading@11: yading@11: if (rt->flv_header_bytes < 11) { yading@11: const uint8_t *header = rt->flv_header; yading@11: int copy = FFMIN(11 - rt->flv_header_bytes, size_temp); yading@11: bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy); yading@11: rt->flv_header_bytes += copy; yading@11: size_temp -= copy; yading@11: if (rt->flv_header_bytes < 11) yading@11: break; yading@11: yading@11: pkttype = bytestream_get_byte(&header); yading@11: pktsize = bytestream_get_be24(&header); yading@11: ts = bytestream_get_be24(&header); yading@11: ts |= bytestream_get_byte(&header) << 24; yading@11: bytestream_get_be24(&header); yading@11: rt->flv_size = pktsize; yading@11: yading@11: //force 12bytes header yading@11: if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) || yading@11: pkttype == RTMP_PT_NOTIFY) { yading@11: if (pkttype == RTMP_PT_NOTIFY) yading@11: pktsize += 16; yading@11: rt->prev_pkt[1][RTMP_SOURCE_CHANNEL].channel_id = 0; yading@11: } yading@11: yading@11: //this can be a big packet, it's better to send it right here yading@11: if ((ret = ff_rtmp_packet_create(&rt->out_pkt, RTMP_SOURCE_CHANNEL, yading@11: pkttype, ts, pktsize)) < 0) yading@11: return ret; yading@11: yading@11: rt->out_pkt.extra = rt->main_channel_id; yading@11: rt->flv_data = rt->out_pkt.data; yading@11: yading@11: if (pkttype == RTMP_PT_NOTIFY) yading@11: ff_amf_write_string(&rt->flv_data, "@setDataFrame"); yading@11: } yading@11: yading@11: if (rt->flv_size - rt->flv_off > size_temp) { yading@11: bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, size_temp); yading@11: rt->flv_off += size_temp; yading@11: size_temp = 0; yading@11: } else { yading@11: bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, rt->flv_size - rt->flv_off); yading@11: size_temp -= rt->flv_size - rt->flv_off; yading@11: rt->flv_off += rt->flv_size - rt->flv_off; yading@11: } yading@11: yading@11: if (rt->flv_off == rt->flv_size) { yading@11: rt->skip_bytes = 4; yading@11: yading@11: if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0) yading@11: return ret; yading@11: rt->flv_size = 0; yading@11: rt->flv_off = 0; yading@11: rt->flv_header_bytes = 0; yading@11: rt->flv_nb_packets++; yading@11: } yading@11: } while (buf_temp - buf < size); yading@11: yading@11: if (rt->flv_nb_packets < rt->flush_interval) yading@11: return size; yading@11: rt->flv_nb_packets = 0; yading@11: yading@11: /* set stream into nonblocking mode */ yading@11: rt->stream->flags |= AVIO_FLAG_NONBLOCK; yading@11: yading@11: /* try to read one byte from the stream */ yading@11: ret = ffurl_read(rt->stream, &c, 1); yading@11: yading@11: /* switch the stream back into blocking mode */ yading@11: rt->stream->flags &= ~AVIO_FLAG_NONBLOCK; yading@11: yading@11: if (ret == AVERROR(EAGAIN)) { yading@11: /* no incoming data to handle */ yading@11: return size; yading@11: } else if (ret < 0) { yading@11: return ret; yading@11: } else if (ret == 1) { yading@11: RTMPPacket rpkt = { 0 }; yading@11: yading@11: if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt, yading@11: rt->in_chunk_size, yading@11: rt->prev_pkt[0], c)) <= 0) yading@11: return ret; yading@11: yading@11: if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0) yading@11: return ret; yading@11: yading@11: ff_rtmp_packet_destroy(&rpkt); yading@11: } yading@11: yading@11: return size; yading@11: } yading@11: yading@11: #define OFFSET(x) offsetof(RTMPContext, x) yading@11: #define DEC AV_OPT_FLAG_DECODING_PARAM yading@11: #define ENC AV_OPT_FLAG_ENCODING_PARAM yading@11: yading@11: static const AVOption rtmp_options[] = { yading@11: {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, yading@11: {"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}, yading@11: {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, yading@11: {"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}, yading@11: {"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}, yading@11: {"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"}, yading@11: {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"}, yading@11: {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"}, yading@11: {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"}, yading@11: {"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}, yading@11: {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, yading@11: {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, yading@11: {"rtmp_swfhash", "SHA256 hash of the decompressed SWF file (32 bytes).", OFFSET(swfhash), AV_OPT_TYPE_BINARY, .flags = DEC}, yading@11: {"rtmp_swfsize", "Size of the decompressed SWF file, required for SWFVerification.", OFFSET(swfsize), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC}, yading@11: {"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}, yading@11: {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically.", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, yading@11: {"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}, yading@11: {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, yading@11: {"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" }, yading@11: { NULL }, yading@11: }; yading@11: yading@11: #define RTMP_PROTOCOL(flavor) \ yading@11: static const AVClass flavor##_class = { \ yading@11: .class_name = #flavor, \ yading@11: .item_name = av_default_item_name, \ yading@11: .option = rtmp_options, \ yading@11: .version = LIBAVUTIL_VERSION_INT, \ yading@11: }; \ yading@11: \ yading@11: URLProtocol ff_##flavor##_protocol = { \ yading@11: .name = #flavor, \ yading@11: .url_open = rtmp_open, \ yading@11: .url_read = rtmp_read, \ yading@11: .url_write = rtmp_write, \ yading@11: .url_close = rtmp_close, \ yading@11: .priv_data_size = sizeof(RTMPContext), \ yading@11: .flags = URL_PROTOCOL_FLAG_NETWORK, \ yading@11: .priv_data_class= &flavor##_class, \ yading@11: }; yading@11: yading@11: yading@11: RTMP_PROTOCOL(rtmp) yading@11: RTMP_PROTOCOL(rtmpe) yading@11: RTMP_PROTOCOL(rtmps) yading@11: RTMP_PROTOCOL(rtmpt) yading@11: RTMP_PROTOCOL(rtmpte) yading@11: RTMP_PROTOCOL(rtmpts)