X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Frtmpproto.c;h=427655c27e19766c87ce9c26adcb9b3402367826;hb=fad729fa505c0450482028c4a55c29fdc503dad7;hp=37c3d95acad862dad937863bea6ce4e5950c4132;hpb=704af3e29c3ddbc22ac5c8f40e5a0f860d53ac4c;p=ffmpeg diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c index 37c3d95acad..427655c27e1 100644 --- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -26,8 +26,9 @@ #include "libavcodec/bytestream.h" #include "libavutil/avstring.h" -#include "libavutil/intfloat_readwrite.h" +#include "libavutil/intfloat.h" #include "libavutil/lfg.h" +#include "libavutil/opt.h" #include "libavutil/sha.h" #include "avformat.h" #include "internal.h" @@ -41,6 +42,11 @@ //#define DEBUG +#define APP_MAX_LENGTH 128 +#define PLAYPATH_MAX_LENGTH 256 +#define TCURL_MAX_LENGTH 512 +#define FLASHVER_MAX_LENGTH 64 + /** RTMP protocol handler state */ typedef enum { STATE_START, ///< client has not done anything yet @@ -56,12 +62,14 @@ typedef enum { /** protocol handler context */ typedef struct RTMPContext { + const AVClass *class; URLContext* stream; ///< TCP stream used in interactions with RTMP server RTMPPacket prev_pkt[2][RTMP_CHANNELS]; ///< packet history used when reading and sending packets int chunk_size; ///< size of the chunks RTMP packets are divided into int is_input; ///< input/output flag - char playpath[256]; ///< path to filename to play (with possible "mp4:" prefix) - char app[128]; ///< application + char *playpath; ///< stream identifier to play (with possible "mp4:" prefix) + int live; ///< 0: recorded, -1: live, -2: both + char *app; ///< name of application ClientState state; ///< current state int main_channel_id; ///< an additional channel ID which is used for some invocations uint8_t* flv_data; ///< buffer with data for demuxer @@ -75,6 +83,10 @@ typedef struct RTMPContext { uint8_t flv_header[11]; ///< partial incoming flv packet header int flv_header_bytes; ///< number of initialized bytes in flv_header int nb_invokes; ///< keeps track of invoke messages + int create_stream_invoke; ///< invoke id for the create stream command + char* tcurl; ///< url of the target stream + char* flashver; ///< version of the flash plugin + char* swfurl; ///< url of the swf player } RTMPContext; #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing @@ -103,42 +115,45 @@ static const uint8_t rtmp_server_key[] = { /** * 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) +static void gen_connect(URLContext *s, RTMPContext *rt) { RTMPPacket pkt; - uint8_t ver[64], *p; - char tcurl[512]; + uint8_t *p; ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, 0, 4096); p = pkt.data; - 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_number(&p, ++rt->nb_invokes); ff_amf_write_object_start(&p); ff_amf_write_field_name(&p, "app"); ff_amf_write_string(&p, rt->app); - if (rt->is_input) { - snprintf(ver, sizeof(ver), "%s %d,%d,%d,%d", RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, - RTMP_CLIENT_VER2, RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); - } else { - snprintf(ver, sizeof(ver), "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT); + 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, ver); + 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_string(&p, tcurl); + ff_amf_write_string(&p, rt->tcurl); 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, 1639.0); + 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"); @@ -237,6 +252,7 @@ static void gen_create_stream(URLContext *s, RTMPContext *rt) ff_amf_write_string(&p, "createStream"); ff_amf_write_number(&p, ++rt->nb_invokes); ff_amf_write_null(&p); + rt->create_stream_invoke = rt->nb_invokes; ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); ff_rtmp_packet_destroy(&pkt); @@ -257,7 +273,7 @@ static void gen_delete_stream(URLContext *s, RTMPContext *rt) p = pkt.data; ff_amf_write_string(&p, "deleteStream"); - ff_amf_write_number(&p, 0.0); + ff_amf_write_number(&p, ++rt->nb_invokes); ff_amf_write_null(&p); ff_amf_write_number(&p, rt->main_channel_id); @@ -276,14 +292,15 @@ static void gen_play(URLContext *s, RTMPContext *rt) av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath); ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, - 20 + strlen(rt->playpath)); + 29 + strlen(rt->playpath)); pkt.extra = rt->main_channel_id; p = pkt.data; ff_amf_write_string(&p, "play"); - ff_amf_write_number(&p, 0.0); + 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); ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); ff_rtmp_packet_destroy(&pkt); @@ -315,7 +332,7 @@ static void gen_publish(URLContext *s, RTMPContext *rt) p = pkt.data; ff_amf_write_string(&p, "publish"); - ff_amf_write_number(&p, 0.0); + 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"); @@ -340,6 +357,40 @@ static void gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) ff_rtmp_packet_destroy(&pkt); } +/** + * Generate server bandwidth message and send it to the server. + */ +static void gen_server_bw(URLContext *s, RTMPContext *rt) +{ + RTMPPacket pkt; + uint8_t *p; + + ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW, 0, 4); + p = pkt.data; + bytestream_put_be32(&p, 2500000); + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + ff_rtmp_packet_destroy(&pkt); +} + +/** + * Generate check bandwidth message and send it to the server. + */ +static void gen_check_bw(URLContext *s, RTMPContext *rt) +{ + RTMPPacket pkt; + uint8_t *p; + + ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, 0, 21); + + p = pkt.data; + ff_amf_write_string(&p, "_checkbw"); + ff_amf_write_number(&p, ++rt->nb_invokes); + ff_amf_write_null(&p); + + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + ff_rtmp_packet_destroy(&pkt); +} + /** * Generate report on bytes read so far and send it to the server. */ @@ -601,6 +652,7 @@ static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) gen_fcpublish_stream(s, rt); rt->state = STATE_RELEASING; } else { + gen_server_bw(s, rt); rt->state = STATE_CONNECTING; } gen_create_stream(s, rt); @@ -613,8 +665,8 @@ static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) /* hack for Wowza Media Server, it does not send result for * releaseStream and FCPublish calls */ if (!pkt->data[10]) { - int pkt_id = (int) av_int2dbl(AV_RB64(pkt->data + 11)); - if (pkt_id == 4) + int pkt_id = av_int2double(AV_RB64(pkt->data + 11)); + if (pkt_id == rt->create_stream_invoke) rt->state = STATE_CONNECTING; } if (rt->state != STATE_CONNECTING) @@ -624,7 +676,7 @@ static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) { av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n"); } else { - rt->main_channel_id = (int) av_int2dbl(AV_RB64(pkt->data + 21)); + rt->main_channel_id = av_int2double(AV_RB64(pkt->data + 21)); } if (rt->is_input) { gen_play(s, rt); @@ -658,6 +710,8 @@ static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) 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; + } else if (!memcmp(pkt->data, "\002\000\010onBWDone", 11)) { + gen_check_bw(s, rt); } break; } @@ -784,7 +838,6 @@ static int rtmp_close(URLContext *h) av_freep(&rt->flv_data); ffurl_close(rt->stream); - av_free(rt); return 0; } @@ -799,16 +852,13 @@ static int rtmp_close(URLContext *h) */ static int rtmp_open(URLContext *s, const char *uri, int flags) { - RTMPContext *rt; + RTMPContext *rt = s->priv_data; char proto[8], hostname[256], path[1024], *fname; + char *old_app; uint8_t buf[2048]; int port; int ret; - rt = av_mallocz(sizeof(RTMPContext)); - if (!rt) - return AVERROR(ENOMEM); - s->priv_data = rt; rt->is_input = !(flags & AVIO_FLAG_WRITE); av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, @@ -826,10 +876,20 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) rt->state = STATE_START; if (rtmp_handshake(s, rt)) - return -1; + goto fail; rt->chunk_size = 128; 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) { + rtmp_close(s); + return AVERROR(ENOMEM); + } + //extract "app" part from path if (!strncmp(path, "/ondemand/", 10)) { fname = path + 10; @@ -851,14 +911,47 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) } } } - if (!strchr(fname, ':') && - (!strcmp(fname + strlen(fname) - 4, ".f4v") || - !strcmp(fname + strlen(fname) - 4, ".mp4"))) { - memcpy(rt->playpath, "mp4:", 5); - } else { - rt->playpath[0] = 0; + + if (old_app) { + // The name of application has been defined by the user, override it. + av_free(rt->app); + rt->app = old_app; + } + + if (!rt->playpath) { + rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); + if (!rt->playpath) { + rtmp_close(s); + return AVERROR(ENOMEM); + } + + if (!strchr(fname, ':') && + (!strcmp(fname + strlen(fname) - 4, ".f4v") || + !strcmp(fname + strlen(fname) - 4, ".mp4"))) { + memcpy(rt->playpath, "mp4:", 5); + } else { + rt->playpath[0] = 0; + } + strncat(rt->playpath, fname, PLAYPATH_MAX_LENGTH - 5); + } + + if (!rt->tcurl) { + rt->tcurl = av_malloc(TCURL_MAX_LENGTH); + 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->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); + } } - strncat(rt->playpath, fname, sizeof(rt->playpath) - 5); rt->client_report_size = 1048576; rt->bytes_read = 0; @@ -866,7 +959,7 @@ static int rtmp_open(URLContext *s, const char *uri, int flags) av_log(s, 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); + gen_connect(s, rt); do { ret = get_packet(s, 1); @@ -996,10 +1089,37 @@ static int rtmp_write(URLContext *s, const uint8_t *buf, int size) return size; } +#define OFFSET(x) offsetof(RTMPContext, x) +#define DEC AV_OPT_FLAG_DECODING_PARAM +#define ENC AV_OPT_FLAG_ENCODING_PARAM + +static const AVOption rtmp_options[] = { + {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"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}, + {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {-2}, INT_MIN, INT_MAX, DEC, "rtmp_live"}, + {"any", "both", 0, AV_OPT_TYPE_CONST, {-2}, 0, 0, DEC, "rtmp_live"}, + {"live", "live stream", 0, AV_OPT_TYPE_CONST, {-1}, 0, 0, DEC, "rtmp_live"}, + {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {0}, 0, 0, DEC, "rtmp_live"}, + {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + {"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}, + {"rtmp_tcurl", "URL of the target stream. Defaults to rtmp://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, + { NULL }, +}; + +static const AVClass rtmp_class = { + .class_name = "rtmp", + .item_name = av_default_item_name, + .option = rtmp_options, + .version = LIBAVUTIL_VERSION_INT, +}; + URLProtocol ff_rtmp_protocol = { - .name = "rtmp", - .url_open = rtmp_open, - .url_read = rtmp_read, - .url_write = rtmp_write, - .url_close = rtmp_close, + .name = "rtmp", + .url_open = rtmp_open, + .url_read = rtmp_read, + .url_write = rtmp_write, + .url_close = rtmp_close, + .priv_data_size = sizeof(RTMPContext), + .flags = URL_PROTOCOL_FLAG_NETWORK, + .priv_data_class= &rtmp_class, };