FFMPEG版本爲3.2 release。git
libavformat/rtmpproto.c服務器
調用關係:app
avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts) -> init_input(s, filename, &tmp) -> s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options),實際回調函數io_open_default() -> ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist); -> ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL); ffurl_connect(*puc, options) -> uc->prot->url_open(uc, uc->filename, uc->flags)實際回調函數rtmp_open()
即dom
Breakpoint 3, rtmp_open (s=0x2768fc0, uri=0x2769018 "rtmp://223.203.1.34:1936/live?vhost=cc.com/stream1_1", flags=1) at libavformat/rtmpproto.c:2608 2608 { (gdb) bt #0 rtmp_open (s=0x2768fc0, uri=0x2769018 "rtmp://223.203.1.34:1936/live?vhost=cc.com/stream1_1", flags=1) at libavformat/rtmpproto.c:2608 #1 0x00000000005f35dd in ffurl_connect (uc=0x2768fc0, options=0x7fffffffdaf0) at libavformat/avio.c:210 #2 0x00000000005f3b53 in ffurl_open_whitelist (puc=0x7fffffffda68, filename=<value optimized out>, flags=1, int_cb=<value optimized out>, options=0x7fffffffdaf0, whitelist=0x0, blacklist=0x0, parent=0x0) at libavformat/avio.c:347 #3 0x00000000005f8b55 in ffio_open_whitelist (s=0x2768700, filename=<value optimized out>, flags=<value optimized out>, int_cb=<value optimized out>, options=<value optimized out>, whitelist=<value optimized out>, blacklist=0x0) at libavformat/aviobuf.c:1046 #4 0x00000000006ad1e1 in io_open_default (s=<value optimized out>, pb=<value optimized out>, url=<value optimized out>, flags=<value optimized out>, options=<value optimized out>) at libavformat/options.c:112 #5 0x00000000007044bd in init_input (ps=0x7fffffffdbd8, filename=0x7fffffffe758 "rtmp://223.203.1.34:1936/live?vhost=cc.com/stream1_1", fmt=<value optimized out>, options=0x2768428) at libavformat/utils.c:415 #6 avformat_open_input (ps=0x7fffffffdbd8, filename=0x7fffffffe758 "rtmp://223.203.1.34:1936/live?vhost=cc.com/stream1_1", fmt=<value optimized out>, options=0x2768428) at libavformat/utils.c:529 #7 0x000000000047e65a in open_input_file (o=0x7fffffffdc80, filename=<value optimized out>) at ffmpeg_opt.c:997 #8 0x000000000047bb76 in open_files (l=0x2767998, inout=0x18b68a6 "input", open_file=0x47e220 <open_input_file>) at ffmpeg_opt.c:3135 #9 0x000000000047bde7 in ffmpeg_parse_options (argc=<value optimized out>, argv=<value optimized out>) at ffmpeg_opt.c:3175 #10 0x00000000004927c4 in main (argc=12, argv=0x7fffffffe488) at ffmpeg.c:4564
static int rtmp_open(URLContext *s, const char *uri, int flags) { RTMPContext *rt = s->priv_data; char proto[8], hostname[256], path[1024], auth[100], *fname; char *old_app, *qmark, *n, fname_buffer[1024]; uint8_t buf[2048]; int port; AVDictionary *opts = NULL; int ret; if (rt->listen_timeout > 0) rt->listen = 1; rt->is_input = !(flags & AVIO_FLAG_WRITE); av_url_split(proto, sizeof(proto), auth, sizeof(auth), hostname, sizeof(hostname), &port, path, sizeof(path), s->filename); n = strchr(path, ' '); if (n) { av_log(s, AV_LOG_WARNING, "Detected librtmp style URL parameters, these aren't supported " "by the libavformat internal RTMP handler currently enabled. " "See the documentation for the correct way to pass parameters.\n"); *n = '\0'; // Trim not supported part } if (auth[0]) { char *ptr = strchr(auth, ':'); if (ptr) { *ptr = '\0'; av_strlcpy(rt->username, auth, sizeof(rt->username)); av_strlcpy(rt->password, ptr + 1, sizeof(rt->password)); } } if (rt->listen && strcmp(proto, "rtmp")) { av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n", proto); return AVERROR(EINVAL); } if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) { if (!strcmp(proto, "rtmpts")) av_dict_set(&opts, "ffrtmphttp_tls", "1", 1); /* open the http tunneling connection */ ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL); } else if (!strcmp(proto, "rtmps")) { /* open the tls connection */ if (port < 0) port = RTMPS_DEFAULT_PORT; ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL); } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) { if (!strcmp(proto, "rtmpte")) av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1); /* open the encrypted connection */ ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL); rt->encrypted = 1; } else { /* open the tcp connection */ if (port < 0) port = RTMP_DEFAULT_PORT; if (rt->listen) ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, "?listen&listen_timeout=%d", rt->listen_timeout * 1000); else ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL); } reconnect: if ((ret = ffurl_open_whitelist(&rt->stream, buf, AVIO_FLAG_READ_WRITE, &s->interrupt_callback, &opts, s->protocol_whitelist, s->protocol_blacklist, s)) < 0) { av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf); goto fail; } if (rt->swfverify) { if ((ret = rtmp_calc_swfhash(s)) < 0) goto fail; } rt->state = STATE_START; if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0) goto fail; if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0) goto fail; rt->out_chunk_size = 128; rt->in_chunk_size = 128; // Probably overwritten later rt->state = STATE_HANDSHAKED; // Keep the application name when it has been defined by the user. old_app = rt->app; rt->app = av_malloc(APP_MAX_LENGTH); if (!rt->app) { ret = AVERROR(ENOMEM); goto fail; } //extract "app" part from path qmark = strchr(path, '?'); if (qmark && strstr(qmark, "slist=")) { char* amp; // After slist we have the playpath, the full path is used as app av_strlcpy(rt->app, path + 1, APP_MAX_LENGTH); fname = strstr(path, "slist=") + 6; // Strip any further query parameters from fname amp = strchr(fname, '&'); if (amp) { av_strlcpy(fname_buffer, fname, FFMIN(amp - fname + 1, sizeof(fname_buffer))); fname = fname_buffer; } } else if (!strncmp(path, "/ondemand/", 10)) { fname = path + 10; memcpy(rt->app, "ondemand", 9); } else { char *next = *path ? path + 1 : path; char *p = strchr(next, '/'); if (!p) { if (old_app) { // If name of application has been defined by the user, assume that // playpath is provided in the URL fname = next; } else { fname = NULL; av_strlcpy(rt->app, next, APP_MAX_LENGTH); } } else { // make sure we do not mismatch a playpath for an application instance char *c = strchr(p + 1, ':'); fname = strchr(p + 1, '/'); if (!fname || (c && c < fname)) { fname = p + 1; av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH)); } else { fname++; av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH)); } } } if (old_app) { // The name of application has been defined by the user, override it. if (strlen(old_app) >= APP_MAX_LENGTH) { ret = AVERROR(EINVAL); goto fail; } av_free(rt->app); rt->app = old_app; } if (!rt->playpath) { rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); if (!rt->playpath) { ret = AVERROR(ENOMEM); goto fail; } if (fname) { int len = strlen(fname); if (!strchr(fname, ':') && len >= 4 && (!strcmp(fname + len - 4, ".f4v") || !strcmp(fname + len - 4, ".mp4"))) { memcpy(rt->playpath, "mp4:", 5); } else { if (len >= 4 && !strcmp(fname + len - 4, ".flv")) fname[len - 4] = '\0'; rt->playpath[0] = 0; } av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); } else { rt->playpath[0] = '\0'; } } if (!rt->tcurl) { rt->tcurl = av_malloc(TCURL_MAX_LENGTH); if (!rt->tcurl) { ret = AVERROR(ENOMEM); goto fail; } ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname, port, "/%s", rt->app); } if (!rt->flashver) { rt->flashver = av_malloc(FLASHVER_MAX_LENGTH); if (!rt->flashver) { ret = AVERROR(ENOMEM); goto fail; } if (rt->is_input) { snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d", RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2, RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); } else { snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT); } } rt->client_report_size = 1048576; rt->bytes_read = 0; rt->has_audio = 0; rt->has_video = 0; rt->received_metadata = 0; rt->last_bytes_read = 0; rt->server_bw = 2500000; rt->duration = 0; av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", proto, path, rt->app, rt->playpath); if (!rt->listen) { if ((ret = gen_connect(s, rt)) < 0) goto fail; } else { if ((ret = read_connect(s, s->priv_data)) < 0) goto fail; } do { ret = get_packet(s, 1); } while (ret == AVERROR(EAGAIN)); if (ret < 0) goto fail; if (rt->do_reconnect) { int i; ffurl_close(rt->stream); rt->stream = NULL; rt->do_reconnect = 0; rt->nb_invokes = 0; for (i = 0; i < 2; i++) memset(rt->prev_pkt[i], 0, sizeof(**rt->prev_pkt) * rt->nb_prev_pkt[i]); free_tracked_methods(rt); goto reconnect; } if (rt->is_input) { // generate FLV header for demuxer rt->flv_size = 13; if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) goto fail; rt->flv_off = 0; memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size); // Read packets until we reach the first A/V packet or read metadata. // If there was a metadata package in front of the A/V packets, we can // build the FLV header from this. If we do not receive any metadata, // the FLV decoder will allocate the needed streams when their first // audio or video packet arrives. while (!rt->has_audio && !rt->has_video && !rt->received_metadata) { if ((ret = get_packet(s, 0)) < 0) goto fail; } // Either after we have read the metadata or (if there is none) the // first packet of an A/V stream, we have a better knowledge about the // streams, so set the FLV header accordingly. if (rt->has_audio) { rt->flv_data[4] |= FLV_HEADER_FLAG_HASAUDIO; } if (rt->has_video) { rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO; } // If we received the first packet of an A/V stream and no metadata but // the server returned a valid duration, create a fake metadata packet // to inform the FLV decoder about the duration. if (!rt->received_metadata && rt->duration > 0) { if ((ret = inject_fake_duration_metadata(rt)) < 0) goto fail; } } else { rt->flv_size = 0; rt->flv_data = NULL; rt->flv_off = 0; rt->skip_bytes = 13; } s->max_packet_size = rt->stream->max_packet_size; s->is_streamed = 1; return 0; fail: av_dict_free(&opts); rtmp_close(s); return ret; }
該函數主要包括四個功能:curl
1.解析urlsocket
2.打開鏈接tcp
3. 三次握手ide
4. Generate 'connect' call and send it to the server函數
void av_url_split(char *proto, int proto_size, char *authorization, int authorization_size, char *hostname, int hostname_size, int *port_ptr, char *path, int path_size, const char *url) { const char *p, *ls, *ls2, *at, *at2, *col, *brk; if (port_ptr) *port_ptr = -1; if (proto_size > 0) proto[0] = 0; if (authorization_size > 0) authorization[0] = 0; if (hostname_size > 0) hostname[0] = 0; if (path_size > 0) path[0] = 0; /* parse protocol */ if ((p = strchr(url, ':'))) { av_strlcpy(proto, url, FFMIN(proto_size, p + 1 - url)); p++; /* skip ':' */ if (*p == '/') p++; if (*p == '/') p++; } else { /* no protocol means plain filename */ av_strlcpy(path, url, path_size); return; } /* separate path from hostname */ ls = strchr(p, '/'); ls2 = strchr(p, '?'); if (!ls) ls = ls2; else if (ls && ls2) ls = FFMIN(ls, ls2); if (ls) av_strlcpy(path, ls, path_size); else ls = &p[strlen(p)]; // XXX /* the rest is hostname, use that to parse auth/port */ if (ls != p) { /* authorization (user[:pass]@hostname) */ at2 = p; while ((at = strchr(p, '@')) && at < ls) { av_strlcpy(authorization, at2, FFMIN(authorization_size, at + 1 - at2)); p = at + 1; /* skip '@' */ } if (*p == '[' && (brk = strchr(p, ']')) && brk < ls) { /* [host]:port */ av_strlcpy(hostname, p + 1, FFMIN(hostname_size, brk - p)); if (brk[1] == ':' && port_ptr) *port_ptr = atoi(brk + 2); } else if ((col = strchr(p, ':')) && col < ls) { av_strlcpy(hostname, p, FFMIN(col + 1 - p, hostname_size)); if (port_ptr) *port_ptr = atoi(col + 1); } else av_strlcpy(hostname, p, FFMIN(ls + 1 - p, hostname_size)); } }
根據輸入url獲取hostname,端口號,路徑參數。ui
int ff_url_join(char *str, int size, const char *proto, const char *authorization, const char *hostname, int port, const char *fmt, ...) { #if CONFIG_NETWORK struct addrinfo hints = { 0 }, *ai; #endif str[0] = '\0'; if (proto) av_strlcatf(str, size, "%s://", proto); if (authorization && authorization[0]) av_strlcatf(str, size, "%s@", authorization); #if CONFIG_NETWORK && defined(AF_INET6) /* Determine if hostname is a numerical IPv6 address, * properly escape it within [] in that case. */ hints.ai_flags = AI_NUMERICHOST; if (!getaddrinfo(hostname, NULL, &hints, &ai)) { if (ai->ai_family == AF_INET6) { av_strlcat(str, "[", size); av_strlcat(str, hostname, size); av_strlcat(str, "]", size); } else { av_strlcat(str, hostname, size); } freeaddrinfo(ai); } else #endif /* Not an IPv6 address, just output the plain string. */ av_strlcat(str, hostname, size); if (port >= 0) av_strlcatf(str, size, ":%d", port); if (fmt) { va_list vl; size_t len = strlen(str); va_start(vl, fmt); vsnprintf(str + len, size > len ? size - len : 0, fmt, vl); va_end(vl); } return strlen(str); }
例如,輸入url爲 rtmp://IP:1936/live?vhost=cc.com/stream_1,
則調用函數ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);由於rtmp是基於tcp協議。因此buf的內容爲 "tcp://IP:1936"
打開URL,即調用tcp協議
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char* blacklist, URLContext *parent) { AVDictionary *tmp_opts = NULL; AVDictionaryEntry *e; int ret = ffurl_alloc(puc, filename, flags, int_cb); if (ret < 0) return ret; if (parent) av_opt_copy(*puc, parent); if (options && (ret = av_opt_set_dict(*puc, options)) < 0) goto fail; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; if (!options) options = &tmp_opts; av_assert0(!whitelist || !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) || !strcmp(whitelist, e->value)); av_assert0(!blacklist || !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) || !strcmp(blacklist, e->value)); if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0) goto fail; if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0) goto fail; if ((ret = av_opt_set_dict(*puc, options)) < 0) goto fail; ret = ffurl_connect(*puc, options); if (!ret) return 0; fail: ffurl_close(*puc); *puc = NULL; return ret; }
即函數uc->prot->url_open(uc, uc->filename, uc->flags)回調tcp_open()
/* return non zero if error */ static int tcp_open(URLContext *h, const char *uri, int flags) { struct addrinfo hints = { 0 }, *ai, *cur_ai; int port, fd = -1; TCPContext *s = h->priv_data; const char *p; char buf[256]; int ret; char hostname[1024],proto[1024],path[1024]; char portstr[10]; s->open_timeout = 5000000; av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, path, sizeof(path), uri); if (strcmp(proto, "tcp")) return AVERROR(EINVAL); if (port <= 0 || port >= 65536) { av_log(h, AV_LOG_ERROR, "Port missing in uri\n"); return AVERROR(EINVAL); } p = strchr(uri, '?'); if (p) { if (av_find_info_tag(buf, sizeof(buf), "listen", p)) { char *endptr = NULL; s->listen = strtol(buf, &endptr, 10); /* assume if no digits were found it is a request to enable it */ if (buf == endptr) s->listen = 1; } if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) { s->rw_timeout = strtol(buf, NULL, 10); } if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) { s->listen_timeout = strtol(buf, NULL, 10); } } if (s->rw_timeout >= 0) { s->open_timeout = h->rw_timeout = s->rw_timeout; } hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; snprintf(portstr, sizeof(portstr), "%d", port); if (s->listen) hints.ai_flags |= AI_PASSIVE; if (!hostname[0]) ret = getaddrinfo(NULL, portstr, &hints, &ai); else ret = getaddrinfo(hostname, portstr, &hints, &ai); if (ret) { av_log(h, AV_LOG_ERROR, "Failed to resolve hostname %s: %s\n", hostname, gai_strerror(ret)); return AVERROR(EIO); } cur_ai = ai; restart: fd = ff_socket(cur_ai->ai_family, cur_ai->ai_socktype, cur_ai->ai_protocol); if (fd < 0) { ret = ff_neterrno(); goto fail; } if (s->listen == 2) { // multi-client if ((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen)) < 0) goto fail1; } else if (s->listen == 1) { // single client if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, s->listen_timeout, h)) < 0) goto fail1; // Socket descriptor already closed here. Safe to overwrite to client one. fd = ret; } else { if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) { if (ret == AVERROR_EXIT) goto fail1; else goto fail; } } h->is_streamed = 1; s->fd = fd; /* Set the socket's send or receive buffer sizes, if specified. If unspecified or setting fails, system default is used. */ if (s->recv_buffer_size > 0) { setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &s->recv_buffer_size, sizeof (s->recv_buffer_size)); } if (s->send_buffer_size > 0) { setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &s->send_buffer_size, sizeof (s->send_buffer_size)); } freeaddrinfo(ai); return 0; fail: if (cur_ai->ai_next) { /* Retry with the next sockaddr */ cur_ai = cur_ai->ai_next; if (fd >= 0) closesocket(fd); ret = 0; goto restart; } fail1: if (fd >= 0) closesocket(fd); freeaddrinfo(ai); return ret; }
/** * Perform handshake with the server by means of exchanging pseudorandom data * signed with HMAC-SHA2 digest. * * @return 0 if handshake succeeds, negative value otherwise */ static int rtmp_handshake(URLContext *s, RTMPContext *rt) { AVLFG rnd; uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { 3, // unencrypted data 0, 0, 0, 0, // client uptime RTMP_CLIENT_VER1, RTMP_CLIENT_VER2, RTMP_CLIENT_VER3, RTMP_CLIENT_VER4, }; uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; int i; int server_pos, client_pos; uint8_t digest[32], signature[32]; int ret, type = 0; av_log(s, AV_LOG_DEBUG, "Handshaking...\n"); av_lfg_init(&rnd, 0xDEADC0DE); // generate handshake packet - 1536 bytes of pseudorandom data for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) tosend[i] = av_lfg_get(&rnd) >> 24; if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* When the client wants to use RTMPE, we have to change the command * byte to 0x06 which means to use encrypted data and we have to set * the flash version to at least 9.0.115.0. */ tosend[0] = 6; tosend[5] = 128; tosend[6] = 0; tosend[7] = 3; tosend[8] = 2; /* Initialize the Diffie-Hellmann context and generate the public key * to send to the server. */ if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0) return ret; } client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted); if (client_pos < 0) return client_pos; if ((ret = ffurl_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n"); return ret; } if ((ret = ffurl_read_complete(rt->stream, serverdata, RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); return ret; } if ((ret = ffurl_read_complete(rt->stream, clientdata, RTMP_HANDSHAKE_PACKET_SIZE)) < 0) { av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); return ret; } av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]); av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", serverdata[5], serverdata[6], serverdata[7], serverdata[8]); if (rt->is_input && serverdata[5] >= 3) { server_pos = rtmp_validate_digest(serverdata + 1, 772); if (server_pos < 0) return server_pos; if (!server_pos) { type = 1; server_pos = rtmp_validate_digest(serverdata + 1, 8); if (server_pos < 0) return server_pos; if (!server_pos) { av_log(s, AV_LOG_ERROR, "Server response validating failed\n"); return AVERROR(EIO); } } /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, * key are the last 32 bytes of the server handshake. */ if (rt->swfsize) { if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 + RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0) return ret; } ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, rtmp_server_key, sizeof(rtmp_server_key), digest); if (ret < 0) return ret; ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, digest, 32, signature); if (ret < 0) return ret; if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* Compute the shared secret key sent by the server and initialize * the RC4 encryption. */ if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, tosend + 1, type)) < 0) return ret; /* Encrypt the signature received by the server. */ ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]); } if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { av_log(s, AV_LOG_ERROR, "Signature mismatch\n"); return AVERROR(EIO); } for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) tosend[i] = av_lfg_get(&rnd) >> 24; ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, rtmp_player_key, sizeof(rtmp_player_key), digest); if (ret < 0) return ret; ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, digest, 32, tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); if (ret < 0) return ret; if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* Encrypt the signature to be send to the server. */ ff_rtmpe_encrypt_sig(rt->stream, tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32, digest, serverdata[0]); } // write reply back to the server if ((ret = ffurl_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE)) < 0) return ret; if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* Set RC4 keys for encryption and update the keystreams. */ if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) return ret; } } else { if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* Compute the shared secret key sent by the server and initialize * the RC4 encryption. */ if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, tosend + 1, 1)) < 0) return ret; if (serverdata[0] == 9) { /* Encrypt the signature received by the server. */ ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]); } } if ((ret = ffurl_write(rt->stream, serverdata + 1, RTMP_HANDSHAKE_PACKET_SIZE)) < 0) return ret; if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { /* Set RC4 keys for encryption and update the keystreams. */ if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) return ret; } } return 0; }
其中:
(ret = ffurl_write(rt->stream, tosend,
RTMP_HANDSHAKE_PACKET_SIZE + 1))發送C0和C1到服務器,共1537個字節。寫操做調用函數tcp_write()。
(ret = ffurl_read_complete(rt->stream, serverdata,
RTMP_HANDSHAKE_PACKET_SIZE + 1))接受服務器的響應S0和S1的數據,共1537個字節。讀操做調用函數tcp_read()。
(ret = ffurl_read_complete(rt->stream, clientdata,
RTMP_HANDSHAKE_PACKET_SIZE))接受服務器的響應S2的數據,共1536個字節。讀操做調用函數tcp_read()。
(ret = ffurl_write(rt->stream, serverdata + 1,
RTMP_HANDSHAKE_PACKET_SIZE))發送C2(內容爲S1)到服務器,共1536個字節。寫操做調用函數tcp_write()。
/** * Generate 'connect' call and send it to the server. */ static int gen_connect(URLContext *s, RTMPContext *rt) { RTMPPacket pkt; uint8_t *p; int ret; if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, 0, 4096 + APP_MAX_LENGTH)) < 0) return ret; p = pkt.data; ff_amf_write_string(&p, "connect"); ff_amf_write_number(&p, ++rt->nb_invokes); ff_amf_write_object_start(&p); ff_amf_write_field_name(&p, "app"); ff_amf_write_string2(&p, rt->app, rt->auth_params); if (!rt->is_input) { ff_amf_write_field_name(&p, "type"); ff_amf_write_string(&p, "nonprivate"); } ff_amf_write_field_name(&p, "flashVer"); ff_amf_write_string(&p, rt->flashver); if (rt->swfurl) { ff_amf_write_field_name(&p, "swfUrl"); ff_amf_write_string(&p, rt->swfurl); } ff_amf_write_field_name(&p, "tcUrl"); ff_amf_write_string2(&p, rt->tcurl, rt->auth_params); if (rt->is_input) { ff_amf_write_field_name(&p, "fpad"); ff_amf_write_bool(&p, 0); ff_amf_write_field_name(&p, "capabilities"); ff_amf_write_number(&p, 15.0); /* Tell the server we support all the audio codecs except * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010) * which are unused in the RTMP protocol implementation. */ ff_amf_write_field_name(&p, "audioCodecs"); ff_amf_write_number(&p, 4071.0); ff_amf_write_field_name(&p, "videoCodecs"); ff_amf_write_number(&p, 252.0); ff_amf_write_field_name(&p, "videoFunction"); ff_amf_write_number(&p, 1.0); if (rt->pageurl) { ff_amf_write_field_name(&p, "pageUrl"); ff_amf_write_string(&p, rt->pageurl); } } ff_amf_write_object_end(&p); if (rt->conn) { char *param = rt->conn; // Write arbitrary AMF data to the Connect message. while (param) { char *sep; param += strspn(param, " "); if (!*param) break; sep = strchr(param, ' '); if (sep) *sep = '\0'; if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) { // Invalid AMF parameter. ff_rtmp_packet_destroy(&pkt); return ret; } if (sep) param = sep + 1; else break; } } pkt.size = p - pkt.data; return rtmp_send_packet(rt, &pkt, 1); }