Newer
Older
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
bytestream2_init(&gbc, pkt->data, pkt->size);
// Value 1/4: "_result" as AMF_STRING
if (ff_amf_read_string(&gbc, strbuffer, sizeof(strbuffer), &stringlen))
return AVERROR_INVALIDDATA;
if (strcmp(strbuffer, "_result"))
return AVERROR_INVALIDDATA;
// Value 2/4: The callee reference number
if (ff_amf_read_number(&gbc, &numbuffer))
return AVERROR_INVALIDDATA;
// Value 3/4: Null
if (ff_amf_read_null(&gbc))
return AVERROR_INVALIDDATA;
// Value 4/4: The resonse as AMF_NUMBER
if (ff_amf_read_number(&gbc, &numbuffer))
return AVERROR_INVALIDDATA;
else
*number = numbuffer;
return 0;
}
static int handle_invoke_result(URLContext *s, RTMPPacket *pkt)
{
RTMPContext *rt = s->priv_data;
char *tracked_method = NULL;
int ret = 0;
if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0)
if (!tracked_method) {
/* Ignore this reply when the current method is not tracked. */
return ret;
}
if (!rt->is_input) {
if ((ret = gen_release_stream(s, rt)) < 0)
goto fail;
if ((ret = gen_fcpublish_stream(s, rt)) < 0)
goto fail;
} else {
if ((ret = gen_server_bw(s, rt)) < 0)
goto fail;
if ((ret = gen_create_stream(s, rt)) < 0)
goto fail;
if (rt->is_input) {
/* Send the FCSubscribe command when the name of live
* stream is defined by the user or if it's a live stream. */
if (rt->subscribe) {
if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0)
goto fail;
} else if (rt->live == -1) {
if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0)
goto fail;
} else if (!strcmp(tracked_method, "createStream")) {
double stream_id;
if (read_number_result(pkt, &stream_id)) {
av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n");
} else {
rt->stream_id = stream_id;
if (!rt->is_input) {
if ((ret = gen_publish(s, rt)) < 0)
goto fail;
} else {
if (rt->live != -1) {
if ((ret = gen_get_stream_length(s, rt)) < 0)
goto fail;
}
if ((ret = gen_play(s, rt)) < 0)
goto fail;
if ((ret = gen_buffer_time(s, rt)) < 0)
goto fail;
} else if (!strcmp(tracked_method, "getStreamLength")) {
if (read_number_result(pkt, &rt->duration)) {
av_log(s, AV_LOG_WARNING, "Unexpected reply on getStreamLength()\n");
}
}
fail:
av_free(tracked_method);
return ret;
}
static int handle_invoke_status(URLContext *s, RTMPPacket *pkt)
RTMPContext *rt = s->priv_data;
const uint8_t *data_end = pkt->data + pkt->size;
const uint8_t *ptr = pkt->data + RTMP_HEADER;
uint8_t tmpstr[256];
for (i = 0; i < 2; i++) {
t = ff_amf_tag_size(ptr, data_end);
if (t < 0)
return 1;
ptr += t;
}
t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr));
if (!t && !strcmp(tmpstr, "error")) {
t = ff_amf_get_field_value(ptr, data_end,
"description", tmpstr, sizeof(tmpstr));
if (t || !tmpstr[0])
t = ff_amf_get_field_value(ptr, data_end, "code",
tmpstr, sizeof(tmpstr));
if (!t)
av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr);
return -1;
}
t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr));
if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING;
if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED;
if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED;
if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING;
if (!t && !strcmp(tmpstr, "NetStream.Seek.Notify")) rt->state = STATE_PLAYING;
return 0;
}
static int handle_invoke(URLContext *s, RTMPPacket *pkt)
{
RTMPContext *rt = s->priv_data;
//TODO: check for the messages sent for wrong state?
if (ff_amf_match_string(pkt->data, pkt->size, "_error")) {
if ((ret = handle_invoke_error(s, pkt)) < 0)
return ret;
} else if (ff_amf_match_string(pkt->data, pkt->size, "_result")) {
if ((ret = handle_invoke_result(s, pkt)) < 0)
} else if (ff_amf_match_string(pkt->data, pkt->size, "onStatus")) {
if ((ret = handle_invoke_status(s, pkt)) < 0)
return ret;
} else if (ff_amf_match_string(pkt->data, pkt->size, "onBWDone")) {
if ((ret = gen_check_bw(s, rt)) < 0)
return ret;
} else if (ff_amf_match_string(pkt->data, pkt->size, "releaseStream") ||
ff_amf_match_string(pkt->data, pkt->size, "FCPublish") ||
ff_amf_match_string(pkt->data, pkt->size, "publish") ||
ff_amf_match_string(pkt->data, pkt->size, "play") ||
ff_amf_match_string(pkt->data, pkt->size, "_checkbw") ||
ff_amf_match_string(pkt->data, pkt->size, "createStream")) {
if ((ret = send_invoke_response(s, pkt)) < 0)
static int update_offset(RTMPContext *rt, int size)
{
int old_flv_size;
// generate packet header and put data into buffer for FLV demuxer
if (rt->flv_off < rt->flv_size) {
// There is old unread data in the buffer, thus append at the end
rt->flv_size += size;
// All data has been read, write the new data at the start of the buffer
rt->flv_size = size;
return old_flv_size;
}
static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip)
{
int old_flv_size, ret;
PutByteContext pbc;
const uint8_t *data = pkt->data + skip;
const int size = pkt->size - skip;
uint32_t ts = pkt->timestamp;
if (pkt->type == RTMP_PT_AUDIO) {
rt->has_audio = 1;
} else if (pkt->type == RTMP_PT_VIDEO) {
rt->has_video = 1;
}
old_flv_size = update_offset(rt, size + 15);
if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) {
rt->flv_size = rt->flv_off = 0;
bytestream2_init_writer(&pbc, rt->flv_data, rt->flv_size);
bytestream2_skip_p(&pbc, old_flv_size);
bytestream2_put_byte(&pbc, pkt->type);
bytestream2_put_be24(&pbc, ts);
bytestream2_put_byte(&pbc, ts >> 24);
bytestream2_put_be24(&pbc, 0);
static int handle_notify(URLContext *s, RTMPPacket *pkt)
{
RTMPContext *rt = s->priv_data;
uint8_t commandbuffer[64];
char statusmsg[128];
int stringlen, ret, skip = 0;
GetByteContext gbc;
bytestream2_init(&gbc, pkt->data, pkt->size);
if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer),
&stringlen))
return AVERROR_INVALIDDATA;
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
if (!strcmp(commandbuffer, "onMetaData")) {
// metadata properties should be stored in a mixed array
if (bytestream2_get_byte(&gbc) == AMF_DATA_TYPE_MIXEDARRAY) {
// We have found a metaData Array so flv can determine the streams
// from this.
rt->received_metadata = 1;
// skip 32-bit max array index
bytestream2_skip(&gbc, 4);
while (bytestream2_get_bytes_left(&gbc) > 3) {
if (ff_amf_get_string(&gbc, statusmsg, sizeof(statusmsg),
&stringlen))
return AVERROR_INVALIDDATA;
// We do not care about the content of the property (yet).
stringlen = ff_amf_tag_size(gbc.buffer, gbc.buffer_end);
if (stringlen < 0)
return AVERROR_INVALIDDATA;
bytestream2_skip(&gbc, stringlen);
// The presence of the following properties indicates that the
// respective streams are present.
if (!strcmp(statusmsg, "videocodecid")) {
rt->has_video = 1;
}
if (!strcmp(statusmsg, "audiocodecid")) {
rt->has_audio = 1;
}
}
if (bytestream2_get_be24(&gbc) != AMF_END_OF_OBJECT)
return AVERROR_INVALIDDATA;
}
}
// Skip the @setDataFrame string and validate it is a notification
if (!strcmp(commandbuffer, "@setDataFrame")) {
skip = gbc.buffer - pkt->data;
ret = ff_amf_read_string(&gbc, statusmsg,
sizeof(statusmsg), &stringlen);
if (ret < 0)
return AVERROR_INVALIDDATA;
}
return append_flv_data(rt, pkt, skip);
}
/**
* Parse received packet and possibly perform some action depending on
* the packet contents.
* @return 0 for no errors, negative values for serious errors which prevent
* further communications, positive values for uncritical errors
*/
static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt)
{
int ret;
#ifdef DEBUG
ff_rtmp_packet_dump(s, pkt);
#endif
switch (pkt->type) {
case RTMP_PT_BYTES_READ:
av_dlog(s, "received bytes read report\n");
break;
case RTMP_PT_CHUNK_SIZE:
if ((ret = handle_chunk_size(s, pkt)) < 0)
return ret;
break;
case RTMP_PT_PING:
if ((ret = handle_ping(s, pkt)) < 0)
return ret;
break;
case RTMP_PT_CLIENT_BW:
if ((ret = handle_client_bw(s, pkt)) < 0)
return ret;
break;
case RTMP_PT_SERVER_BW:
if ((ret = handle_server_bw(s, pkt)) < 0)
return ret;
break;
case RTMP_PT_INVOKE:
if ((ret = handle_invoke(s, pkt)) < 0)
return ret;
Jordi Ortiz
committed
case RTMP_PT_VIDEO:
case RTMP_PT_AUDIO:
case RTMP_PT_METADATA:
/* Audio, Video and Metadata packets are parsed in get_packet() */
Jordi Ortiz
committed
break;
default:
av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type);
break;
static int handle_metadata(RTMPContext *rt, RTMPPacket *pkt)
{
int ret, old_flv_size, type;
const uint8_t *next;
uint8_t *p;
uint32_t size;
uint32_t ts, cts, pts = 0;
old_flv_size = update_offset(rt, pkt->size);
if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) {
rt->flv_size = rt->flv_off = 0;
next = pkt->data;
p = rt->flv_data + old_flv_size;
/* copy data while rewriting timestamps */
ts = pkt->timestamp;
while (next - pkt->data < pkt->size - RTMP_HEADER) {
type = bytestream_get_byte(&next);
size = bytestream_get_be24(&next);
cts = bytestream_get_be24(&next);
cts |= bytestream_get_byte(&next) << 24;
if (!pts)
pts = cts;
ts += cts - pts;
pts = cts;
if (size + 3 + 4 > pkt->data + pkt->size - next)
break;
bytestream_put_byte(&p, type);
bytestream_put_be24(&p, size);
bytestream_put_be24(&p, ts);
bytestream_put_byte(&p, ts >> 24);
memcpy(p, next, size + 3 + 4);
next += size + 3 + 4;
p += size + 3 + 4;
}
Martin Storsjö
committed
if (p != rt->flv_data + rt->flv_size) {
av_log(NULL, AV_LOG_WARNING, "Incomplete flv packets in "
"RTMP_PT_METADATA packet\n");
rt->flv_size = p - rt->flv_data;
}
* 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
* @param for_header non-zero value tells function to work until it
* gets notification from the server that playing has been started,
* otherwise function will work until some media data is received (or
* an error happens)
* @return 0 for successful operation, negative value in case of error
*/
static int get_packet(URLContext *s, int for_header)
{
RTMPContext *rt = s->priv_data;
int ret;
if (rt->state == STATE_STOPPED)
return AVERROR_EOF;
for (;;) {
if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt,
rt->in_chunk_size, &rt->prev_pkt[0],
&rt->nb_prev_pkt[0])) <= 0) {
Kostya Shishkov
committed
if (ret == 0) {
return AVERROR(EAGAIN);
} else {
return AVERROR(EIO);
}
}
if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) {
av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n");
if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0)
return ret;
rt->last_bytes_read = rt->bytes_read;
}
ret = rtmp_parse_result(s, rt, &rpkt);
// At this point we must check if we are in the seek state and continue
// with the next packet. handle_invoke will get us out of this state
// when the right message is encountered
if (rt->state == STATE_SEEKING) {
ff_rtmp_packet_destroy(&rpkt);
// We continue, let the natural flow of things happen:
// AVERROR(EAGAIN) or handle_invoke gets us out of here
continue;
}
if (ret < 0) {//serious error in current packet
ff_rtmp_packet_destroy(&rpkt);
if (rt->do_reconnect && for_header) {
ff_rtmp_packet_destroy(&rpkt);
return 0;
}
if (rt->state == STATE_STOPPED) {
ff_rtmp_packet_destroy(&rpkt);
return AVERROR_EOF;
}
if (for_header && (rt->state == STATE_PLAYING ||
rt->state == STATE_PUBLISHING ||
rt->state == STATE_RECEIVING)) {
ff_rtmp_packet_destroy(&rpkt);
return 0;
}
ff_rtmp_packet_destroy(&rpkt);
continue;
}
if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO) {
ret = append_flv_data(rt, &rpkt, 0);
} else if (rpkt.type == RTMP_PT_NOTIFY) {
ret = handle_notify(s, &rpkt);
ff_rtmp_packet_destroy(&rpkt);
} else if (rpkt.type == RTMP_PT_METADATA) {
ff_rtmp_packet_destroy(&rpkt);
return 0;
}
ff_rtmp_packet_destroy(&rpkt);
}
}
static int rtmp_close(URLContext *h)
{
RTMPContext *rt = h->priv_data;
if (!rt->is_input) {
rt->flv_data = NULL;
ff_rtmp_packet_destroy(&rt->out_pkt);
if (rt->state > STATE_FCPUBLISH)
ret = gen_fcunpublish_stream(h, rt);
if (rt->state > STATE_HANDSHAKED)
ret = gen_delete_stream(h, rt);
for (i = 0; i < 2; i++) {
for (j = 0; j < rt->nb_prev_pkt[i]; j++)
ff_rtmp_packet_destroy(&rt->prev_pkt[i][j]);
av_freep(&rt->prev_pkt[i]);
}
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
/**
* Insert a fake onMetadata packet into the FLV stream to notify the FLV
* demuxer about the duration of the stream.
*
* This should only be done if there was no real onMetadata packet sent by the
* server at the start of the stream and if we were able to retrieve a valid
* duration via a getStreamLength call.
*
* @return 0 for successful operation, negative value in case of error
*/
static int inject_fake_duration_metadata(RTMPContext *rt)
{
// We need to insert the metdata packet directly after the FLV
// header, i.e. we need to move all other already read data by the
// size of our fake metadata packet.
uint8_t* p;
// Keep old flv_data pointer
uint8_t* old_flv_data = rt->flv_data;
// Allocate a new flv_data pointer with enough space for the additional package
if (!(rt->flv_data = av_malloc(rt->flv_size + 55))) {
rt->flv_data = old_flv_data;
return AVERROR(ENOMEM);
}
// Copy FLV header
memcpy(rt->flv_data, old_flv_data, 13);
// Copy remaining packets
memcpy(rt->flv_data + 13 + 55, old_flv_data + 13, rt->flv_size - 13);
// Increase the size by the injected packet
rt->flv_size += 55;
// Delete the old FLV data
av_free(old_flv_data);
p = rt->flv_data + 13;
bytestream_put_byte(&p, FLV_TAG_TYPE_META);
bytestream_put_be24(&p, 40); // size of data part (sum of all parts below)
bytestream_put_be24(&p, 0); // timestamp
bytestream_put_be32(&p, 0); // reserved
// first event name as a string
bytestream_put_byte(&p, AMF_DATA_TYPE_STRING);
// "onMetaData" as AMF string
bytestream_put_be16(&p, 10);
bytestream_put_buffer(&p, "onMetaData", 10);
// mixed array (hash) with size and string/type/data tuples
bytestream_put_byte(&p, AMF_DATA_TYPE_MIXEDARRAY);
bytestream_put_be32(&p, 1); // metadata_count
// "duration" as AMF string
bytestream_put_be16(&p, 8);
bytestream_put_buffer(&p, "duration", 8);
bytestream_put_byte(&p, AMF_DATA_TYPE_NUMBER);
bytestream_put_be64(&p, av_double2int(rt->duration));
// Finalise object
bytestream_put_be16(&p, 0); // Empty string
bytestream_put_byte(&p, AMF_END_OF_OBJECT);
bytestream_put_be32(&p, 40); // size of data part (sum of all parts below)
return 0;
}
* 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
* (e.g. /ondemand/, /flash/live/, etc.)
* and 'playpath' is a file name (the rest of the path,
* may be prefixed with "mp4:")
*/
static int rtmp_open(URLContext *s, const char *uri, int flags)
{
RTMPContext *rt = s->priv_data;
char proto[8], hostname[256], path[1024], auth[100], *fname;
char *old_app, *qmark, fname_buffer[1024];
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,
if (strchr(path, ' ')) {
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");
}
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);
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);
if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
if (rt->swfverify) {
if ((ret = rtmp_calc_swfhash(s)) < 0)
goto fail;
}
if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0)
goto fail;
if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0)
rt->out_chunk_size = 128;
rt->in_chunk_size = 128; // Probably overwritten later
Samuel Pitoiset
committed
// 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;
Samuel Pitoiset
committed
}
qmark = strchr(path, '?');
if (qmark && strstr(qmark, "slist=")) {
char* amp;
// After slist we have the playpath, before the params, the app
av_strlcpy(rt->app, path + 1, FFMIN(qmark - path, 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, '/');
fname = next;
// make sure we do not mismatch a playpath for an application instance
char *c = strchr(p + 1, ':');
fname = strchr(p + 1, '/');
av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH));
av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH));
Samuel Pitoiset
committed
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;
}
Samuel Pitoiset
committed
av_free(rt->app);
rt->app = old_app;
}
Samuel Pitoiset
committed
if (!rt->playpath) {
int len = strlen(fname);
Samuel Pitoiset
committed
rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
if (!rt->playpath) {
ret = AVERROR(ENOMEM);
goto fail;
Samuel Pitoiset
committed
}
Samuel Pitoiset
committed
if (!strchr(fname, ':') && len >= 4 &&
(!strcmp(fname + len - 4, ".f4v") ||
!strcmp(fname + len - 4, ".mp4"))) {
Samuel Pitoiset
committed
memcpy(rt->playpath, "mp4:", 5);
} else {
if (len >= 4 && !strcmp(fname + len - 4, ".flv"))
fname[len - 4] = '\0';
Samuel Pitoiset
committed
rt->playpath[0] = 0;
}
av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
Samuel Pitoiset
committed
if (!rt->tcurl) {
rt->tcurl = av_malloc(TCURL_MAX_LENGTH);
if (!rt->tcurl) {
ret = AVERROR(ENOMEM);
goto fail;
}
Samuel Pitoiset
committed
ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname,
port, "/%s", rt->app);
}
Samuel Pitoiset
committed
if (!rt->flashver) {
rt->flashver = av_malloc(FLASHVER_MAX_LENGTH);
if (!rt->flashver) {
ret = AVERROR(ENOMEM);
goto fail;
}
Samuel Pitoiset
committed
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;
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)
do {
ret = get_packet(s, 1);
} while (ret == AVERROR(EAGAIN));
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 ((err = av_reallocp(&rt->flv_data, rt->flv_size)) < 0)
return err;
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)
}
// 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:
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
}
static int rtmp_read(URLContext *s, uint8_t *buf, int size)
{
RTMPContext *rt = s->priv_data;
int orig_size = size;
int ret;
while (size > 0) {
int data_left = rt->flv_size - rt->flv_off;
if (data_left >= size) {
memcpy(buf, rt->flv_data + rt->flv_off, size);
rt->flv_off += size;
return orig_size;
}
if (data_left > 0) {
memcpy(buf, rt->flv_data + rt->flv_off, data_left);
buf += data_left;
size -= data_left;
rt->flv_off = rt->flv_size;
return data_left;
}
if ((ret = get_packet(s, 0)) < 0)
return ret;
}
return orig_size;
}
static int64_t rtmp_seek(URLContext *s, int stream_index, int64_t timestamp,
int flags)
{
RTMPContext *rt = s->priv_data;
int ret;
av_log(s, AV_LOG_DEBUG,
"Seek on stream index %d at timestamp %"PRId64" with flags %08x\n",
stream_index, timestamp, flags);
if ((ret = gen_seek(s, rt, timestamp)) < 0) {
av_log(s, AV_LOG_ERROR,
"Unable to send seek command on stream index %d at timestamp "
"%"PRId64" with flags %08x\n",
stream_index, timestamp, flags);
return ret;
}
rt->flv_off = rt->flv_size;
rt->state = STATE_SEEKING;
return timestamp;
}
static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
RTMPContext *rt = s->priv_data;
int size_temp = size;
int pktsize, pkttype;
uint32_t ts;
const uint8_t *buf_temp = buf;
if (rt->skip_bytes) {
int skip = FFMIN(rt->skip_bytes, size_temp);
buf_temp += skip;
size_temp -= skip;
rt->skip_bytes -= skip;
continue;
}
if (rt->flv_header_bytes < RTMP_HEADER) {
const uint8_t *header = rt->flv_header;
int copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp);
int channel = RTMP_AUDIO_CHANNEL;
bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy);
rt->flv_header_bytes += copy;
size_temp -= copy;
if (rt->flv_header_bytes < RTMP_HEADER)
pkttype = bytestream_get_byte(&header);
pktsize = bytestream_get_be24(&header);
ts = bytestream_get_be24(&header);
ts |= bytestream_get_byte(&header) << 24;
bytestream_get_be24(&header);
rt->flv_size = pktsize;
if (pkttype == RTMP_PT_VIDEO)
channel = RTMP_VIDEO_CHANNEL;
//force 12bytes header
if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) ||
pkttype == RTMP_PT_NOTIFY) {
if (pkttype == RTMP_PT_NOTIFY)
pktsize += 16;
if ((ret = ff_rtmp_check_alloc_array(&rt->prev_pkt[1],
&rt->nb_prev_pkt[1],
channel)) < 0)
return ret;
rt->prev_pkt[1][channel].channel_id = 0;
}
//this can be a big packet, it's better to send it right here
if ((ret = ff_rtmp_packet_create(&rt->out_pkt, channel,
pkttype, ts, pktsize)) < 0)
return ret;
rt->out_pkt.extra = rt->stream_id;
rt->flv_data = rt->out_pkt.data;
if (pkttype == RTMP_PT_NOTIFY)
ff_amf_write_string(&rt->flv_data, "@setDataFrame");
}
if (rt->flv_size - rt->flv_off > size_temp) {
bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, size_temp);
rt->flv_off += size_temp;
} else {
bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, rt->flv_size - rt->flv_off);
size_temp -= rt->flv_size - rt->flv_off;
rt->flv_off += rt->flv_size - rt->flv_off;
}
if (rt->flv_off == rt->flv_size) {
rt->skip_bytes = 4;
if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0)
rt->flv_size = 0;
rt->flv_off = 0;
rt->flv_header_bytes = 0;
rt->flv_nb_packets++;
} while (buf_temp - buf < size);
if (rt->flv_nb_packets < rt->flush_interval)
return size;
rt->flv_nb_packets = 0;
/* set stream into nonblocking mode */
rt->stream->flags |= AVIO_FLAG_NONBLOCK;
/* try to read one byte from the stream */
ret = ffurl_read(rt->stream, &c, 1);
/* switch the stream back into blocking mode */
rt->stream->flags &= ~AVIO_FLAG_NONBLOCK;
if (ret == AVERROR(EAGAIN)) {
/* no incoming data to handle */
return size;
} else if (ret < 0) {
return ret;
} else if (ret == 1) {
RTMPPacket rpkt = { 0 };
if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt,