Newer
Older
* Copyright (c) 2009 Konstantin Shishkov
* This file is part of Libav.
* Libav is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Libav is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* RTMP protocol
*/
#include "libavcodec/bytestream.h"
#include "libavutil/avstring.h"
#include "libavutil/base64.h"
#include "libavutil/hmac.h"
#include "libavutil/intfloat.h"
Samuel Pitoiset
committed
#include "libavutil/opt.h"
#include "libavutil/random_seed.h"
#include "internal.h"
#include "network.h"
#include "flv.h"
#include "rtmp.h"
#if CONFIG_ZLIB
#include <zlib.h>
#endif
Samuel Pitoiset
committed
#define APP_MAX_LENGTH 128
Samuel Pitoiset
committed
#define PLAYPATH_MAX_LENGTH 256
Samuel Pitoiset
committed
#define TCURL_MAX_LENGTH 512
Samuel Pitoiset
committed
#define FLASHVER_MAX_LENGTH 64
#define RTMP_PKTDATA_DEFAULT_SIZE 4096
Samuel Pitoiset
committed
/** RTMP protocol handler state */
typedef enum {
STATE_START, ///< client has not done anything yet
STATE_HANDSHAKED, ///< client has performed handshake
STATE_FCPUBLISH, ///< client FCPublishing stream (for output)
STATE_PLAYING, ///< client has started receiving multimedia data from server
STATE_SEEKING, ///< client has started the seek operation. Back on STATE_PLAYING when the time comes
STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output)
STATE_RECEIVING, ///< received a publish command (for input)
STATE_SENDING, ///< received a play command (for output)
STATE_STOPPED, ///< the broadcast has been stopped
typedef struct TrackedMethod {
char *name;
int id;
} TrackedMethod;
/** protocol handler context */
typedef struct RTMPContext {
Samuel Pitoiset
committed
const AVClass *class;
URLContext* stream; ///< TCP stream used in interactions with RTMP server
RTMPPacket *prev_pkt[2]; ///< packet history used when reading and sending packets ([0] for reading, [1] for writing)
int nb_prev_pkt[2]; ///< number of elements in prev_pkt
int in_chunk_size; ///< size of the chunks incoming RTMP packets are divided into
int out_chunk_size; ///< size of the chunks outgoing RTMP packets are divided into
int is_input; ///< input/output flag
Samuel Pitoiset
committed
char *playpath; ///< stream identifier to play (with possible "mp4:" prefix)
Samuel Pitoiset
committed
int live; ///< 0: recorded, -1: live, -2: both
Samuel Pitoiset
committed
char *app; ///< name of application
char *conn; ///< append arbitrary AMF data to the Connect message
ClientState state; ///< current state
int stream_id; ///< ID assigned by the server for the stream
uint8_t* flv_data; ///< buffer with data for demuxer
int flv_size; ///< current buffer size
int flv_off; ///< number of bytes read from current buffer
int flv_nb_packets; ///< number of flv packets published
RTMPPacket out_pkt; ///< rtmp packet, created from flv a/v or metadata (for output)
uint32_t client_report_size; ///< number of bytes after which client should report to server
uint32_t bytes_read; ///< number of bytes read from server
uint32_t last_bytes_read; ///< number of bytes read last reported to server
uint32_t last_timestamp; ///< last timestamp received in a packet
int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call
int has_audio; ///< presence of audio data
int has_video; ///< presence of video data
int received_metadata; ///< Indicates if we have received metadata about the streams
uint8_t flv_header[RTMP_HEADER]; ///< partial incoming flv packet header
int flv_header_bytes; ///< number of initialized bytes in flv_header
int nb_invokes; ///< keeps track of invoke messages
Samuel Pitoiset
committed
char* tcurl; ///< url of the target stream
Samuel Pitoiset
committed
char* flashver; ///< version of the flash plugin
char* swfhash; ///< SHA256 hash of the decompressed SWF file (32 bytes)
int swfhash_len; ///< length of the SHA256 hash
int swfsize; ///< size of the decompressed SWF file
Samuel Pitoiset
committed
char* swfurl; ///< url of the swf player
char* swfverify; ///< URL to player swf file, compute hash/size automatically
char swfverification[42]; ///< hash of the SWF verification
char* pageurl; ///< url of the web page
char* subscribe; ///< name of live stream to subscribe
int server_bw; ///< server bandwidth
int client_buffer_time; ///< client buffer time in ms
int flush_interval; ///< number of packets flushed in the same request (RTMPT only)
int encrypted; ///< use an encrypted connection (RTMPE only)
TrackedMethod*tracked_methods; ///< tracked methods buffer
int nb_tracked_methods; ///< number of tracked methods
int tracked_methods_size; ///< size of the tracked methods buffer
int listen; ///< listen mode flag
int listen_timeout; ///< listen timeout to wait for new connections
int nb_streamid; ///< The next stream id to return on createStream calls
double duration; ///< Duration of the stream in seconds as returned by the server (only valid if non-zero)
char username[50];
char password[50];
char auth_params[500];
int do_reconnect;
int auth_tried;
} RTMPContext;
#define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
/** Client key used for digest signing */
static const uint8_t rtmp_player_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
#define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing
/** Key used for RTMP server digest signing */
static const uint8_t rtmp_server_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02,
0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8,
0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
static int handle_chunk_size(URLContext *s, RTMPPacket *pkt);
static int add_tracked_method(RTMPContext *rt, const char *name, int id)
{
if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) {
rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2;
if ((err = av_reallocp(&rt->tracked_methods, rt->tracked_methods_size *
Martin Storsjö
committed
sizeof(*rt->tracked_methods))) < 0) {
rt->nb_tracked_methods = 0;
rt->tracked_methods_size = 0;
Martin Storsjö
committed
}
}
rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name);
if (!rt->tracked_methods[rt->nb_tracked_methods].name)
return AVERROR(ENOMEM);
rt->tracked_methods[rt->nb_tracked_methods].id = id;
rt->nb_tracked_methods++;
return 0;
}
static void del_tracked_method(RTMPContext *rt, int index)
{
memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1],
sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1));
rt->nb_tracked_methods--;
}
static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset,
char **tracked_method)
{
RTMPContext *rt = s->priv_data;
GetByteContext gbc;
double pkt_id;
int ret;
int i;
bytestream2_init(&gbc, pkt->data + offset, pkt->size - offset);
if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
return ret;
for (i = 0; i < rt->nb_tracked_methods; i++) {
if (rt->tracked_methods[i].id != pkt_id)
continue;
*tracked_method = rt->tracked_methods[i].name;
del_tracked_method(rt, i);
break;
}
return 0;
}
static void free_tracked_methods(RTMPContext *rt)
{
int i;
for (i = 0; i < rt->nb_tracked_methods; i ++)
av_free(rt->tracked_methods[i].name);
av_free(rt->tracked_methods);
rt->tracked_methods = NULL;
rt->tracked_methods_size = 0;
rt->nb_tracked_methods = 0;
}
static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track)
{
int ret;
if (pkt->type == RTMP_PT_INVOKE && track) {
GetByteContext gbc;
char name[128];
double pkt_id;
int len;
bytestream2_init(&gbc, pkt->data, pkt->size);
if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0)
goto fail;
if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0)
goto fail;
if ((ret = add_tracked_method(rt, name, pkt_id)) < 0)
goto fail;
}
ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
fail:
ff_rtmp_packet_destroy(pkt);
return ret;
}
static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p)
{
Martin Storsjö
committed
char *field, *value;
char type;
/* The type must be B for Boolean, N for number, S for string, O for
* object, or Z for null. For Booleans the data must be either 0 or 1 for
* FALSE or TRUE, respectively. Likewise for Objects the data must be
* 0 or 1 to end or begin an object, respectively. Data items in subobjects
* may be named, by prefixing the type with 'N' and specifying the name
* before the value (ie. NB:myFlag:1). This option may be used multiple times
* to construct arbitrary AMF sequences. */
if (param[0] && param[1] == ':') {
type = param[0];
value = param + 2;
} else if (param[0] == 'N' && param[1] && param[2] == ':') {
type = param[1];
Martin Storsjö
committed
field = param + 3;
value = strchr(field, ':');
if (!value)
goto fail;
*value = '\0';
value++;
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
ff_amf_write_field_name(p, field);
} else {
goto fail;
}
switch (type) {
case 'B':
ff_amf_write_bool(p, value[0] != '0');
break;
case 'S':
ff_amf_write_string(p, value);
break;
case 'N':
ff_amf_write_number(p, strtod(value, NULL));
break;
case 'Z':
ff_amf_write_null(p);
break;
case 'O':
if (value[0] != '0')
ff_amf_write_object_start(p);
else
ff_amf_write_object_end(p);
break;
default:
goto fail;
break;
}
return 0;
fail:
av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param);
return AVERROR(EINVAL);
}
* Generate 'connect' call and send it to the server.
static int gen_connect(URLContext *s, RTMPContext *rt)
Samuel Pitoiset
committed
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 4096)) < 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);
Samuel Pitoiset
committed
if (!rt->is_input) {
ff_amf_write_field_name(&p, "type");
ff_amf_write_string(&p, "nonprivate");
}
ff_amf_write_field_name(&p, "flashVer");
Samuel Pitoiset
committed
ff_amf_write_string(&p, rt->flashver);
Samuel Pitoiset
committed
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);
}
Martin Storsjö
committed
char *param = rt->conn;
// Write arbitrary AMF data to the Connect message.
while (param) {
Martin Storsjö
committed
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;
}
Martin Storsjö
committed
if (sep)
param = sep + 1;
else
break;
static int read_connect(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt = { 0 };
uint8_t *p;
const uint8_t *cp;
int ret;
char command[64];
int stringlen;
double seqnum;
uint8_t tmpstr[256];
GetByteContext gbc;
if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size,
&rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0)
if (pkt.type == RTMP_PT_CHUNK_SIZE) {
if ((ret = handle_chunk_size(s, &pkt)) < 0)
return ret;
ff_rtmp_packet_destroy(&pkt);
if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size,
&rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0)
return ret;
}
if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) {
av_log(s, AV_LOG_ERROR, "Unable to read command string\n");
ff_rtmp_packet_destroy(&pkt);
return AVERROR_INVALIDDATA;
}
if (strcmp(command, "connect")) {
av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command);
ff_rtmp_packet_destroy(&pkt);
return AVERROR_INVALIDDATA;
}
ret = ff_amf_read_number(&gbc, &seqnum);
if (ret)
av_log(s, AV_LOG_WARNING, "SeqNum not found\n");
/* Here one could parse an AMF Object with data as flashVers and others. */
ret = ff_amf_get_field_value(gbc.buffer,
gbc.buffer + bytestream2_get_bytes_left(&gbc),
"app", tmpstr, sizeof(tmpstr));
if (ret)
av_log(s, AV_LOG_WARNING, "App field not found in connect\n");
if (!ret && strcmp(tmpstr, rt->app))
av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n",
tmpstr, rt->app);
ff_rtmp_packet_destroy(&pkt);
// Send Window Acknowledgement Size (as defined in specification)
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
RTMP_PT_SERVER_BW, 0, 4)) < 0)
return ret;
p = pkt.data;
bytestream_put_be32(&p, rt->server_bw);
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
if (ret < 0)
return ret;
// Send Peer Bandwidth
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
RTMP_PT_CLIENT_BW, 0, 5)) < 0)
return ret;
p = pkt.data;
bytestream_put_be32(&p, rt->server_bw);
bytestream_put_byte(&p, 2); // dynamic
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
if (ret < 0)
return ret;
// Ping request
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL,
RTMP_PT_PING, 0, 6)) < 0)
return ret;
p = pkt.data;
bytestream_put_be16(&p, 0); // 0 -> Stream Begin
bytestream_put_be32(&p, 0);
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
if (ret < 0)
return ret;
// Chunk size
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
RTMP_PT_CHUNK_SIZE, 0, 4)) < 0)
return ret;
p = pkt.data;
bytestream_put_be32(&p, rt->out_chunk_size);
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
if (ret < 0)
return ret;
// Send _result NetConnection.Connect.Success to connect
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
RTMP_PT_INVOKE, 0,
RTMP_PKTDATA_DEFAULT_SIZE)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "_result");
ff_amf_write_number(&p, seqnum);
ff_amf_write_object_start(&p);
ff_amf_write_field_name(&p, "fmsVer");
ff_amf_write_string(&p, "FMS/3,0,1,123");
ff_amf_write_field_name(&p, "capabilities");
ff_amf_write_number(&p, 31);
ff_amf_write_object_end(&p);
ff_amf_write_object_start(&p);
ff_amf_write_field_name(&p, "level");
ff_amf_write_string(&p, "status");
ff_amf_write_field_name(&p, "code");
ff_amf_write_string(&p, "NetConnection.Connect.Success");
ff_amf_write_field_name(&p, "description");
ff_amf_write_string(&p, "Connection succeeded.");
ff_amf_write_field_name(&p, "objectEncoding");
ff_amf_write_number(&p, 0);
ff_amf_write_object_end(&p);
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
if (ret < 0)
return ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL,
RTMP_PT_INVOKE, 0, 30)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "onBWDone");
ff_amf_write_number(&p, 0);
ff_amf_write_null(&p);
ff_amf_write_number(&p, 8192);
ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size,
&rt->prev_pkt[1], &rt->nb_prev_pkt[1]);
ff_rtmp_packet_destroy(&pkt);
return ret;
}
* Generate 'releaseStream' call and send it to the server. It should make
* the server release some channel for media streams.
*/
static int gen_release_stream(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 29 + strlen(rt->playpath))) < 0)
return ret;
av_log(s, AV_LOG_DEBUG, "Releasing stream...\n");
p = pkt.data;
ff_amf_write_string(&p, "releaseStream");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
return rtmp_send_packet(rt, &pkt, 1);
* Generate 'FCPublish' call and send it to the server. It should make
* the server prepare for receiving media streams.
static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 25 + strlen(rt->playpath))) < 0)
return ret;
av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n");
p = pkt.data;
ff_amf_write_string(&p, "FCPublish");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
return rtmp_send_packet(rt, &pkt, 1);
* Generate 'FCUnpublish' call and send it to the server. It should make
* the server destroy stream.
*/
static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 27 + strlen(rt->playpath))) < 0)
return ret;
av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n");
p = pkt.data;
ff_amf_write_string(&p, "FCUnpublish");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
* Generate 'createStream' call and send it to the server. It should make
* the server allocate some channel for media streams.
*/
static int gen_create_stream(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
av_log(s, AV_LOG_DEBUG, "Creating stream...\n");
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 25)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "createStream");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
}
/**
* Generate 'deleteStream' call and send it to the server. It should make
* the server remove some channel for media streams.
*/
static int gen_delete_stream(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
av_log(s, AV_LOG_DEBUG, "Deleting stream...\n");
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 34)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "deleteStream");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_number(&p, rt->stream_id);
/**
* Generate 'getStreamLength' call and send it to the server. If the server
* knows the duration of the selected stream, it will reply with the duration
* in seconds.
*/
static int gen_get_stream_length(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
0, 31 + strlen(rt->playpath))) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "getStreamLength");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
return rtmp_send_packet(rt, &pkt, 1);
}
/**
* Generate client buffer time and send it to the server.
*/
static int gen_buffer_time(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
1, 10)) < 0)
return ret;
p = pkt.data;
bytestream_put_be16(&p, 3);
bytestream_put_be32(&p, rt->stream_id);
bytestream_put_be32(&p, rt->client_buffer_time);
* Generate 'play' call and send it to the server, then ping the server
static int gen_play(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath);
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
0, 29 + strlen(rt->playpath))) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "play");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
ff_amf_write_number(&p, rt->live * 1000);
static int gen_seek(URLContext *s, RTMPContext *rt, int64_t timestamp)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
av_log(s, AV_LOG_DEBUG, "Sending seek command for timestamp %"PRId64"\n",
timestamp);
if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 26)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "seek");
ff_amf_write_number(&p, 0); //no tracking back responses
ff_amf_write_null(&p); //as usual, the first null param
ff_amf_write_number(&p, timestamp); //where we want to jump
return rtmp_send_packet(rt, &pkt, 1);
}
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
/**
* Generate a pause packet that either pauses or unpauses the current stream.
*/
static int gen_pause(URLContext *s, RTMPContext *rt, int pause, uint32_t timestamp)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
av_log(s, AV_LOG_DEBUG, "Sending pause command for timestamp %d\n",
timestamp);
if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 29)) < 0)
return ret;
pkt.extra = rt->stream_id;
p = pkt.data;
ff_amf_write_string(&p, "pause");
ff_amf_write_number(&p, 0); //no tracking back responses
ff_amf_write_null(&p); //as usual, the first null param
ff_amf_write_bool(&p, pause); // pause or unpause
ff_amf_write_number(&p, timestamp); //where we pause the stream
return rtmp_send_packet(rt, &pkt, 1);
}
* Generate 'publish' call and send it to the server.
static int gen_publish(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath);
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
0, 30 + strlen(rt->playpath))) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "publish");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, rt->playpath);
ff_amf_write_string(&p, "live");
* Generate ping reply and send it to the server.
static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt)
{
RTMPPacket pkt;
uint8_t *p;
av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n",
return AVERROR_INVALIDDATA;
}
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
ppkt->timestamp + 1, 6)) < 0)
return ret;
p = pkt.data;
bytestream_put_be16(&p, 7);
bytestream_put_be32(&p, AV_RB32(ppkt->data+2));
/**
* Generate SWF verification message and send it to the server.
*/
static int gen_swf_verification(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n");
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING,
0, 44)) < 0)
return ret;
p = pkt.data;
bytestream_put_be16(&p, 27);
memcpy(p, rt->swfverification, 42);
return rtmp_send_packet(rt, &pkt, 0);
}
/**
* Generate server bandwidth message and send it to the server.
*/
static int gen_server_bw(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW,
0, 4)) < 0)
return ret;
bytestream_put_be32(&p, rt->server_bw);
/**
* Generate check bandwidth message and send it to the server.
*/
static int gen_check_bw(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 21)) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "_checkbw");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
return rtmp_send_packet(rt, &pkt, 1);
* Generate report on bytes read so far and send it to the server.
static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ,
ts, 4)) < 0)
return ret;
p = pkt.data;
bytestream_put_be32(&p, rt->bytes_read);
static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt,
const char *subscribe)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 27 + strlen(subscribe))) < 0)
return ret;
p = pkt.data;
ff_amf_write_string(&p, "FCSubscribe");
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_null(&p);
ff_amf_write_string(&p, subscribe);
int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap,
const uint8_t *key, int keylen, uint8_t *dst)
hmac = av_hmac_alloc(AV_HMAC_SHA256);
if (!hmac)
av_hmac_init(hmac, key, keylen);
av_hmac_update(hmac, src, len);
} else { //skip 32 bytes used for storing digest
av_hmac_update(hmac, src, gap);
av_hmac_update(hmac, src + gap + 32, len - gap - 32);
av_hmac_final(hmac, dst, 32);
int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val,
int add_val)
{
int i, digest_pos = 0;
for (i = 0; i < 4; i++)
digest_pos += buf[i + off];
digest_pos = digest_pos % mod_val + add_val;
return digest_pos;
}
* Put HMAC-SHA2 digest of packet data (except for the bytes where this digest
* will be stored) into that packet.
*
* @param buf handshake data (1536 bytes)
* @param encrypted use an encrypted connection (RTMPE)
* @return offset to the digest inside input data
*/
static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)