* RTMP network protocol
* Copyright (c) 2009 Kostya Shishkov
*
- * This file is part of FFmpeg.
+ * This file is part of Libav.
*
- * FFmpeg is free software; you can redistribute it and/or
+ * 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.
*
- * FFmpeg is distributed in the hope that it will be useful,
+ * 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 FFmpeg; if not, write to the Free Software
+ * License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
- * @file libavformat/rtmpproto.c
+ * @file
* RTMP protocol
*/
#include "libavutil/lfg.h"
#include "libavutil/sha.h"
#include "avformat.h"
+#include "internal.h"
#include "network.h"
#include "flv.h"
#include "rtmp.h"
#include "rtmppkt.h"
+#include "url.h"
/* we can't use av_log() with URLContext yet... */
-#if LIBAVFORMAT_VERSION_MAJOR < 53
-#define LOG_CONTEXT NULL
-#else
+#if FF_API_URL_CLASS
#define LOG_CONTEXT s
+#else
+#define LOG_CONTEXT NULL
#endif
//#define DEBUG
int flv_size; ///< current buffer size
int flv_off; ///< number of bytes read from current buffer
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
} RTMPContext;
#define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing
};
/**
- * Generates 'connect' call and sends it to the server.
+ * Generate 'connect' call and send it to the server.
*/
static void gen_connect(URLContext *s, RTMPContext *rt, const char *proto,
const char *host, int port)
ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, 0, 4096);
p = pkt.data;
- snprintf(tcurl, sizeof(tcurl), "%s://%s:%d/%s", proto, host, port, rt->app);
+ ff_url_join(tcurl, sizeof(tcurl), proto, NULL, host, port, "/%s", rt->app);
ff_amf_write_string(&p, "connect");
ff_amf_write_number(&p, 1.0);
ff_amf_write_object_start(&p);
}
/**
- * Generates 'releaseStream' call and sends it to the server. It should make
+ * Generate 'releaseStream' call and send it to the server. It should make
* the server release some channel for media streams.
*/
static void gen_release_stream(URLContext *s, RTMPContext *rt)
}
/**
- * Generates 'FCPublish' call and sends it to the server. It should make
+ * Generate 'FCPublish' call and send it to the server. It should make
* the server preapare for receiving media streams.
*/
static void gen_fcpublish_stream(URLContext *s, RTMPContext *rt)
}
/**
- * Generates 'FCUnpublish' call and sends it to the server. It should make
+ * Generate 'FCUnpublish' call and send it to the server. It should make
* the server destroy stream.
*/
static void gen_fcunpublish_stream(URLContext *s, RTMPContext *rt)
}
/**
- * Generates 'createStream' call and sends it to the server. It should make
+ * Generate 'createStream' call and send it to the server. It should make
* the server allocate some channel for media streams.
*/
static void gen_create_stream(URLContext *s, RTMPContext *rt)
/**
- * Generates 'deleteStream' call and sends it to the server. It should make
+ * Generate 'deleteStream' call and send it to the server. It should make
* the server remove some channel for media streams.
*/
static void gen_delete_stream(URLContext *s, RTMPContext *rt)
}
/**
- * Generates 'play' call and sends it to the server, then pings the server
+ * Generate 'play' call and send it to the server, then ping the server
* to start actual playing.
*/
static void gen_play(URLContext *s, RTMPContext *rt)
}
/**
- * Generates 'publish' call and sends it to the server.
+ * Generate 'publish' call and send it to the server.
*/
static void gen_publish(URLContext *s, RTMPContext *rt)
{
}
/**
- * Generates ping reply and sends it to the server.
+ * Generate ping reply and send it to the server.
*/
static void gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt)
{
ff_rtmp_packet_destroy(&pkt);
}
+/**
+ * Generate report on bytes read so far and send it to the server.
+ */
+static void gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts)
+{
+ RTMPPacket pkt;
+ uint8_t *p;
+
+ ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ, ts, 4);
+ p = pkt.data;
+ bytestream_put_be32(&p, rt->bytes_read);
+ ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]);
+ ff_rtmp_packet_destroy(&pkt);
+}
+
//TODO: Move HMAC code somewhere. Eventually.
#define HMAC_IPAD_VAL 0x36
#define HMAC_OPAD_VAL 0x5C
/**
- * Calculates HMAC-SHA2 digest for RTMP handshake packets.
+ * Calculate HMAC-SHA2 digest for RTMP handshake packets.
*
* @param src input buffer
* @param len input buffer length (should be 1536)
}
/**
- * Puts HMAC-SHA2 digest of packet data (except for the bytes where this digest
+ * 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)
}
/**
- * Verifies that the received server response has the expected digest value.
+ * Verify that the received server response has the expected digest value.
*
* @param buf handshake data received from the server (1536 bytes)
* @param off position to search digest offset from
}
/**
- * Performs handshake with the server by means of exchanging pseudorandom data
+ * Perform handshake with the server by means of exchanging pseudorandom data
* signed with HMAC-SHA2 digest.
*
* @return 0 if handshake succeeds, negative value otherwise
tosend[i] = av_lfg_get(&rnd) >> 24;
client_pos = rtmp_handshake_imprint_with_digest(tosend + 1);
- url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE + 1);
- i = url_read_complete(rt->stream, serverdata, RTMP_HANDSHAKE_PACKET_SIZE + 1);
+ ffurl_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE + 1);
+ i = ffurl_read_complete(rt->stream, serverdata, RTMP_HANDSHAKE_PACKET_SIZE + 1);
if (i != RTMP_HANDSHAKE_PACKET_SIZE + 1) {
av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
return -1;
}
- i = url_read_complete(rt->stream, clientdata, RTMP_HANDSHAKE_PACKET_SIZE);
+ i = ffurl_read_complete(rt->stream, clientdata, RTMP_HANDSHAKE_PACKET_SIZE);
if (i != RTMP_HANDSHAKE_PACKET_SIZE) {
av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
return -1;
tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
// write reply back to the server
- url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE);
+ ffurl_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE);
} else {
- url_write(rt->stream, serverdata+1, RTMP_HANDSHAKE_PACKET_SIZE);
+ ffurl_write(rt->stream, serverdata+1, RTMP_HANDSHAKE_PACKET_SIZE);
}
return 0;
}
/**
- * Parses received packet and may perform some action depending on
+ * Parse received packet and possibly perform some action depending on
* the packet contents.
* @return 0 for no errors, negative values for serious errors which prevent
* further communications, positive values for uncritical errors
if (t == 6)
gen_pong(s, rt, pkt);
break;
+ case RTMP_PT_CLIENT_BW:
+ if (pkt->data_size < 4) {
+ av_log(LOG_CONTEXT, AV_LOG_ERROR,
+ "Client bandwidth report packet is less than 4 bytes long (%d)\n",
+ pkt->data_size);
+ return -1;
+ }
+ av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Client bandwidth = %d\n", AV_RB32(pkt->data));
+ rt->client_report_size = AV_RB32(pkt->data) >> 1;
+ break;
case RTMP_PT_INVOKE:
//TODO: check for the messages sent for wrong state?
if (!memcmp(pkt->data, "\002\000\006_error", 9)) {
}
/**
- * Interacts with the server by receiving and sending RTMP packets until
+ * Interact with the server by receiving and sending RTMP packets until
* there is some significant data (media data or expected status notification).
*
* @param s reading context
for (;;) {
RTMPPacket rpkt;
if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt,
- rt->chunk_size, rt->prev_pkt[0])) != 0) {
+ rt->chunk_size, rt->prev_pkt[0])) <= 0) {
if (ret == 0) {
return AVERROR(EAGAIN);
} else {
return AVERROR(EIO);
}
}
+ rt->bytes_read += ret;
+ if (rt->bytes_read > rt->last_bytes_read + rt->client_report_size) {
+ av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Sending bytes read report\n");
+ gen_bytes_read(s, rt, rpkt.timestamp + 1);
+ rt->last_bytes_read = rt->bytes_read;
+ }
ret = rtmp_parse_result(s, rt, &rpkt);
if (ret < 0) {//serious error in current packet
data_size = bytestream_get_be24(&next);
p=next;
cts = bytestream_get_be24(&next);
- cts |= bytestream_get_byte(&next);
+ cts |= bytestream_get_byte(&next) << 24;
if (pts==0)
pts=cts;
ts += cts - pts;
}
/**
- * Opens RTMP connection and verifies that the stream can be played.
+ * Open RTMP connection and verify that the stream can be played.
*
* URL syntax: rtmp://server[:port][/app][/playpath]
* where 'app' is first one or two directories in the path
s->priv_data = rt;
rt->is_input = !(flags & URL_WRONLY);
- url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
- path, sizeof(path), s->filename);
+ av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port,
+ path, sizeof(path), s->filename);
if (port < 0)
port = RTMP_DEFAULT_PORT;
- snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port);
+ ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
- if (url_open(&rt->stream, buf, URL_RDWR) < 0) {
+ if (ffurl_open(&rt->stream, buf, URL_RDWR) < 0) {
av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot open connection %s\n", buf);
goto fail;
}
}
strncat(rt->playpath, fname, sizeof(rt->playpath) - 5);
+ rt->client_report_size = 1048576;
+ rt->bytes_read = 0;
+ rt->last_bytes_read = 0;
+
av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
proto, path, rt->app, rt->playpath);
gen_connect(s, rt, proto, hostname, port);
buf += data_left;
size -= data_left;
rt->flv_off = rt->flv_size;
+ return data_left;
}
if ((ret = get_packet(s, 0)) < 0)
return ret;
return orig_size;
}
-static int rtmp_write(URLContext *h, uint8_t *buf, int size)
+static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
{
- RTMPContext *rt = h->priv_data;
+ RTMPContext *rt = s->priv_data;
int size_temp = size;
int pktsize, pkttype;
uint32_t ts;
return size;
}
-URLProtocol rtmp_protocol = {
+URLProtocol ff_rtmp_protocol = {
"rtmp",
rtmp_open,
rtmp_read,