Hi. This patch implements rtmp-output for libavformat. It was tested with red5/wowza/fms. Also it adds extraction/injection of extended timestamps, and header compression for ff_rtmp_packet_write
On Mon, Nov 30, 2009 at 06:18:18PM +0200, Sergiy wrote:
This patch implements rtmp-output for libavformat. It was tested with red5/wowza/fms.
--- a/libavformat/rtmppkt.c +++ b/libavformat/rtmppkt.c @@ -111,6 +111,13 @@ int ff_rtmp_packet_read(URLContext *h, RTMPPacket *p, + //extract ts extension + if(timestamp == 0xffffff) { + if (url_read_complete(h, buf, 4) != 4) + return AVERROR(EIO); + timestamp = AV_RB32(buf); + } + if (ff_rtmp_packet_create(p, channel_id, type, timestamp, data_size)) return -1;
Please respect the surrounding (K&R) style and be consistent, you mix styles even in the code you add: Place spaces after if/for/while/switch.
--- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -94,42 +98,53 @@ static const uint8_t rtmp_server_key[] = { + + if (rt->is_input){
.. and space before {
2009/11/30 Diego Biurrun <diego at biurrun.de>:
Please respect the surrounding (K&R) style and be consistent, you mix styles even in the code you add: Place spaces after if/for/while/switch.
.. and space before {
Added. I also fix code for sending channel_id >= 64 in ff_rtmp_packet_write().
On date Monday 2009-11-30 23:21:46 +0200, Sergiy encoded:
2009/11/30 Diego Biurrun <diego at biurrun.de>:
Please respect the surrounding (K&R) style and be consistent, you mix styles even in the code you add: Place spaces after if/for/while/switch.
.. and space before {
Added. I also fix code for sending channel_id >= 64 in ff_rtmp_packet_write().
diff --git a/libavformat/rtmppkt.c b/libavformat/rtmppkt.c index e1f0647..a07e996 100644 --- a/libavformat/rtmppkt.c +++ b/libavformat/rtmppkt.c @@ -111,6 +111,13 @@ int ff_rtmp_packet_read(URLContext *h, RTMPPacket *p, } } } + //extract ts extension + if (timestamp == 0xffffff) { + if (url_read_complete(h, buf, 4) != 4) + return AVERROR(EIO); + timestamp = AV_RB32(buf); + } +
This belongs to a separate patch.
if (ff_rtmp_packet_create(p, channel_id, type, timestamp, data_size)) return -1; p->extra = extra; @@ -144,17 +151,47 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt, int mode = RTMP_PS_TWELVEBYTES; int off = 0;
- //TODO: header compression
Don't remove the todo if you didn't fixed it yet.
- bytestream_put_byte(&p, pkt->channel_id | (mode << 6)); + //if channel_id = 0, this is first presentation of prev_pkt, send full hdr. + if (prev_pkt[pkt->channel_id].channel_id && + pkt->extra == prev_pkt[pkt->channel_id].extra) { + if (pkt->type == prev_pkt[pkt->channel_id].type && + pkt->data_size == prev_pkt[pkt->channel_id].data_size) { + mode = RTMP_PS_FOURBYTES; + if (pkt->timestamp == prev_pkt[pkt->channel_id].timestamp) + mode = RTMP_PS_ONEBYTE; + } else + mode = RTMP_PS_EIGHTBYTES; + } + + if (pkt->channel_id < 64) + bytestream_put_byte(&p, pkt->channel_id | (mode << 6)); + else if (pkt->channel_id - 64 < 256) { + bytestream_put_byte(&p, 0 | (mode << 6)); + bytestream_put_byte(&p, pkt->channel_id-64); + } else { + bytestream_put_byte(&p, 1 | (mode << 6)); + bytestream_put_le16(&p, pkt->channel_id-64); + }
This belongs to a separate patch.
if (mode != RTMP_PS_ONEBYTE) { - bytestream_put_be24(&p, pkt->timestamp); + bytestream_put_be24(&p, pkt->timestamp < 0xffffff ? pkt->timestamp : 0xffffff); if (mode != RTMP_PS_FOURBYTES) { bytestream_put_be24(&p, pkt->data_size); bytestream_put_byte(&p, pkt->type); if (mode == RTMP_PS_TWELVEBYTES) bytestream_put_le32(&p, pkt->extra); } + //send ts extension + if (pkt->timestamp >= 0xffffff) + bytestream_put_be32(&p, pkt->timestamp); } + // save history + prev_pkt[pkt->channel_id].channel_id = pkt->channel_id; + prev_pkt[pkt->channel_id].type = pkt->type; + prev_pkt[pkt->channel_id].data_size = pkt->data_size; + prev_pkt[pkt->channel_id].timestamp = pkt->timestamp; + prev_pkt[pkt->channel_id].extra = pkt->extra; + url_write(h, pkt_hdr, p-pkt_hdr); while (off < pkt->data_size) { int towrite = FFMIN(chunk_size, pkt->data_size - off); diff --git a/libavformat/rtmppkt.h b/libavformat/rtmppkt.h index f50996f..2c4f92b 100644 --- a/libavformat/rtmppkt.h +++ b/libavformat/rtmppkt.h @@ -34,6 +34,7 @@ enum RTMPChannel { RTMP_NETWORK_CHANNEL = 2, ///< channel for network-related messages (bandwidth report, ping, etc) RTMP_SYSTEM_CHANNEL, ///< channel for sending server control messages + RTMP_SOURCE_CHANNEL, ///< channel for sending a/v to server RTMP_VIDEO_CHANNEL = 8, ///< channel for video data RTMP_AUDIO_CHANNEL, ///< channel for audio data }; diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c index 6d52214..4640da3 100644 --- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -57,12 +57,16 @@ typedef struct RTMPContext { 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 + double pkt_id; ///< each packet sends number, that increased by 1 + int is_input; ///< input/output flag char playpath[256]; ///< path to filename to play (with possible "mp4:" prefix)
+ char app[128]; ///< application
This belongs to a separate patch.
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 int flv_size; ///< current buffer size int flv_off; ///< number of bytes read from current buffer + RTMPPacket out_pkt; ///< type of flv packet (a/v or metadata) uint32_t video_ts; ///< current video timestamp in milliseconds uint32_t audio_ts; ///< current audio timestamp in milliseconds } RTMPContext; @@ -94,42 +98,53 @@ static const uint8_t rtmp_server_key[] = { * Generates 'connect' call and sends it to the server. */ static void gen_connect(URLContext *s, RTMPContext *rt, const char *proto, - const char *host, int port, const char *app) + const char *host, int port) { RTMPPacket pkt; - uint8_t ver[32], *p; + uint8_t ver[64], *p; char tcurl[512];
- ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, 4096); + 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, app); + snprintf(tcurl, sizeof(tcurl), "%s://%s:%d/%s", proto, host, port, rt->app); ff_amf_write_string(&p, "connect"); - ff_amf_write_number(&p, 1.0); + ff_amf_write_number(&p, rt->pkt_id); + rt->pkt_id++; ff_amf_write_object_start(&p); ff_amf_write_field_name(&p, "app"); - ff_amf_write_string(&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); + ff_amf_write_field_name(&p, "type"); + ff_amf_write_string(&p, "nonprivate"); + }
- snprintf(ver, sizeof(ver), "%s %d,%d,%d,%d", RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, - RTMP_CLIENT_VER2, RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); ff_amf_write_field_name(&p, "flashVer"); ff_amf_write_string(&p, ver); ff_amf_write_field_name(&p, "tcUrl"); ff_amf_write_string(&p, tcurl); - 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); - ff_amf_write_field_name(&p, "audioCodecs"); - ff_amf_write_number(&p, 1639.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); + ff_amf_write_field_name(&p, "swfUrl"); //justin.tv want this + ff_amf_write_string(&p, 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); + ff_amf_write_field_name(&p, "audioCodecs"); + ff_amf_write_number(&p, 1639.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); + }
Leave the reindent for a separate cosmetic patch, this way the patch is easier to read.
ff_amf_write_object_end(&p);
pkt.data_size = p - pkt.data; - ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]);
Cosmetics.
}
@@ -142,13 +157,37 @@ static void gen_create_stream(URLContext *s, RTMPContext *rt) RTMPPacket pkt; uint8_t *p;
- av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Creating stream...\n"); - ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, 25); + ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, 0, 4096); + + if (!rt->is_input) { + av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Releasing stream...\n"); + p = pkt.data; + ff_amf_write_string(&p, "releaseStream"); + ff_amf_write_number(&p, rt->pkt_id); + rt->pkt_id++; + ff_amf_write_null(&p); + ff_amf_write_string(&p, rt->playpath); + pkt.data_size = p - pkt.data; + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + + av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Publishing stream...\n");
Add some trace for debugging is fine but again it belongs to a separate patch.
+ p = pkt.data; + ff_amf_write_string(&p, "FCPublish"); + ff_amf_write_number(&p, rt->pkt_id); + rt->pkt_id++; + ff_amf_write_null(&p); + ff_amf_write_string(&p, rt->playpath); + pkt.data_size = p - pkt.data; + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + }
+ av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Creating stream...\n"); p = pkt.data; ff_amf_write_string(&p, "createStream"); - ff_amf_write_number(&p, 3.0); + ff_amf_write_number(&p, rt->pkt_id); + rt->pkt_id++; ff_amf_write_null(&p); + pkt.data_size = p - pkt.data;
ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); ff_rtmp_packet_destroy(&pkt); @@ -163,30 +202,40 @@ static void gen_play(URLContext *s, RTMPContext *rt) RTMPPacket pkt; uint8_t *p;
- av_log(LOG_CONTEXT, 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)); + av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Sending %s command for '%s'\n", rt->is_input ? "play" : "publish", + rt->playpath); + ff_rtmp_packet_create(&pkt, rt->is_input ? RTMP_VIDEO_CHANNEL : RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, 0, + 4096); pkt.extra = rt->main_channel_id;
p = pkt.data; - ff_amf_write_string(&p, "play"); + if (rt->is_input) + ff_amf_write_string(&p, "play");
Again cosmetics.
+ else + ff_amf_write_string(&p, "publish"); + ff_amf_write_number(&p, 0.0); ff_amf_write_null(&p); ff_amf_write_string(&p, rt->playpath); + if (!rt->is_input) + ff_amf_write_string(&p, "live"); + pkt.data_size = p - pkt.data;
ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); ff_rtmp_packet_destroy(&pkt);
- // set client buffer time disguised in ping packet - ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, 1, 10); + if (rt->is_input) { + // set client buffer time disguised in ping packet + ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, 1, 10);
- p = pkt.data; - bytestream_put_be16(&p, 3); - bytestream_put_be32(&p, 1); - bytestream_put_be32(&p, 256); //TODO: what is a good value here? + p = pkt.data; + bytestream_put_be16(&p, 3); + bytestream_put_be32(&p, 1); + bytestream_put_be32(&p, 256); //TODO: what is a good value here?
- ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); - ff_rtmp_packet_destroy(&pkt); + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + ff_rtmp_packet_destroy(&pkt); + } }
/** @@ -205,6 +254,25 @@ static void gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) ff_rtmp_packet_destroy(&pkt); }
+/** + * Generates client/server bandwidth reply and sends it to the server. + */ +static void gen_bw(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) +{ + RTMPPacket pkt; + uint8_t *p; + + if ((rt->is_input && ppkt->type == RTMP_PT_CLIENT_BW) || + (!rt->is_input && ppkt->type == RTMP_PT_SERVER_BW)) + return; + ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, rt->is_input ? RTMP_PT_CLIENT_BW : RTMP_PT_SERVER_BW, + 0, 4); + p = pkt.data; + bytestream_put_be32(&p, AV_RB32(ppkt->data)); + ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); + ff_rtmp_packet_destroy(&pkt); +}
Also this seems to belong to a separate patch. [...] This feature is definitively welcome, but please try to split the patch in separate minimal changes. Regards. -- FFmpeg = Fiendish and Foolish Multimedia Peaceless Eccentric Gadget
On Mon, Nov 30, 2009 at 11:21:46PM +0200, Sergiy wrote:
2009/11/30 Diego Biurrun <diego at biurrun.de>:
Please respect the surrounding (K&R) style and be consistent, you mix styles even in the code you add: Place spaces after if/for/while/switch.
.. and space before {
Added. I also fix code for sending channel_id >= 64 in ff_rtmp_packet_write().
--- a/libavformat/rtmppkt.c +++ b/libavformat/rtmppkt.c @@ -144,17 +151,47 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
+ if (prev_pkt[pkt->channel_id].channel_id && + pkt->extra == prev_pkt[pkt->channel_id].extra) { + if (pkt->type == prev_pkt[pkt->channel_id].type && + pkt->data_size == prev_pkt[pkt->channel_id].data_size) { + mode = RTMP_PS_FOURBYTES; + if (pkt->timestamp == prev_pkt[pkt->channel_id].timestamp) + mode = RTMP_PS_ONEBYTE;
weird and inconsistent indentation
--- a/libavformat/rtmpproto.c +++ b/libavformat/rtmpproto.c @@ -163,30 +202,40 @@ static void gen_play(URLContext *s, RTMPContext *rt)
- av_log(LOG_CONTEXT, 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)); + av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Sending %s command for '%s'\n", rt->is_input ? "play" : "publish", + rt->playpath); + ff_rtmp_packet_create(&pkt, rt->is_input ? RTMP_VIDEO_CHANNEL : RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, 0, + 4096);
again Align the second line with the first character after the '('. .. more below ..
@@ -593,60 +671,51 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
+ rt->pkt_id = 1.0; + rt->chunk_size = 128; + rt->state = STATE_HANDSHAKED;
align the = Diego
On Mon, Nov 30, 2009 at 06:18:18PM +0200, Sergiy wrote:
Hi. This patch implements rtmp-output for libavformat. It was tested with red5/wowza/fms.
Also it adds extraction/injection of extended timestamps, and header compression for ff_rtmp_packet_write
I've ripped out and committed more useful chunks, now you can concentrate on writing only RTMP output ;)
On Tue, Dec 01, 2009 at 06:37:05PM +0200, Kostya wrote:
On Mon, Nov 30, 2009 at 06:18:18PM +0200, Sergiy wrote:
This patch implements rtmp-output for libavformat. It was tested with red5/wowza/fms.
Also it adds extraction/injection of extended timestamps, and header compression for ff_rtmp_packet_write
I've ripped out and committed more useful chunks, now you can concentrate on writing only RTMP output ;)
Maybe you should credit some of those chunks then.. Diego
On Tue, Dec 01, 2009 at 05:40:45PM +0100, Diego Biurrun wrote:
On Tue, Dec 01, 2009 at 06:37:05PM +0200, Kostya wrote:
On Mon, Nov 30, 2009 at 06:18:18PM +0200, Sergiy wrote:
This patch implements rtmp-output for libavformat. It was tested with red5/wowza/fms.
Also it adds extraction/injection of extended timestamps, and header compression for ff_rtmp_packet_write
I've ripped out and committed more useful chunks, now you can concentrate on writing only RTMP output ;)
Maybe you should credit some of those chunks then..
I gave for the ones I copied almost verbatim. The rest differs a bit.
Diego
splitted into 10 parts.
On Wed, Dec 02, 2009 at 12:58:22PM +0200, Sergiy wrote:
splitted into 10 parts.
Part 0 - TS extension - already in SVN Part 1 - support for channel id > 64 - slightly another version is in SVN Part 2 - header compression - looks ok but IIRC there were servers that did not understand it - does the client still works with it? Part 3 - looks ok, will apply soon Part 4 - does rt->pkt_id really needs to be incremented? I suspect setting it from parameter would be better. Also it may go wrong when calls are interleaved like gen_connect(); gen_connect(); gen_create_stream(); Part 5 - moving application path to context is fine, but replacing parsing code chunk is not Part 6 - seems wrong (I heard that bandwidth packets should contain number of bytes read by server/client) Part 7 - several things mixed - I'm not against changing invoke channel but why? For swfUrl and a lot of other just we should have options, not hardcoded URL. \0xx are octal numbers, so \008 is wrong. And av_freep() checks for pointer, no need to have if() before it. Part 8 - adding new channel ID is fine (should be in separate patch though), I'll commit that part. Please resend the rest in separate threads (like [PATCH x/N] RTMP output: do something). It's easier to review that instead of stack of patches.
participants (4)
-
diego@biurrun.de -
kostya.shishkov@gmail.com -
piratfm@gmail.com -
stefano.sabatini-lala@poste.it