[FFmpeg-devel] Fix for poor bandwidth usage on Windows

Farhad Roueintan farhad at justin.tv
Fri Oct 5 22:23:11 CEST 2012


Hi Michael,

The 1-byte packets are for the markers that come between chunks 
(generated at the bottom of ff_rtmp_packet_write in rtmppkt.c). I didn't 
really look closely at what was generating the other small packets, but 
they all come from ff_rtmp_packet_write in the end. Even ignoring the 
small packets, calling send with 1K, 5K, etc. chunks of data is much 
less efficient than buffering them up and calling send() once. I've 
attached the patch file. Basically I just replaced all the calls to 
ff_urlwrite() in ff_rtmp_packet_write with calls to my function 
ff_buffered_urlwrite(). Here is the code for that function:

int ff_buffered_urlwrite(URLContext *h, const unsigned char *buf, int 
size, int pkt_type)
{
     int ret = 0;

     if (!rtmp_send_buffer)
     {
         return -1;
     }

     if (rtmp_send_buffer_data_size + size < RTMP_SEND_BUFFER_SIZE)
     {
         // Just add the data to the buffer
         memcpy(rtmp_send_buffer + rtmp_send_buffer_data_size, buf, size);
         rtmp_send_buffer_data_size += size;
         ret = size;
     }
     else
     {
         // Can't fit the new data in the buffer. First flush the buffer 
if it has some data
         if (rtmp_send_buffer_data_size > 0)
         {
             ret = ffurl_write(h, rtmp_send_buffer, 
rtmp_send_buffer_data_size);
             rtmp_send_buffer_data_size = 0;
             if (ret < 0)
                 return ret;
         }

         if (size > RTMP_SEND_BUFFER_SIZE)
         {
             // The size of the new data is greater than the buffer 
size, simply write out the data
             ret = ffurl_write(h, buf, size);
             rtmp_send_buffer_data_size = 0;
             if (ret < 0)
                 return ret;
         }
         else
         {
             // Put the new data in the buffer
             memcpy(rtmp_send_buffer, buf, size);
             rtmp_send_buffer_data_size = size;
         }

         ret = size;
     }

     if (pkt_type != RTMP_PT_AUDIO && pkt_type != RTMP_PT_VIDEO)
     {
         // If we've got a non-audio/video packets, if the buffer has 
data in it,
         // we must flush the buffer to send it immediately
         //
         if (rtmp_send_buffer_data_size > 0)
         {
             ret = ffurl_write(h, rtmp_send_buffer, 
rtmp_send_buffer_data_size);
             rtmp_send_buffer_data_size = 0;
             if (ret < 0)
                 return ret;
         }
     }

     return ret;
}


On 10/5/2012 12:45 PM, Michael Niedermayer wrote:
> Hi
>
> On Thu, Oct 04, 2012 at 06:47:12PM -0700, Farhad Roueintan wrote:
>> Hi,
>>
>> We had noticed that when we streamed a video using ffmpeg we'd get
>> much lower bitrates than what we expected based on our bandwidth. We
>> did some investigation and tracked down the problem to the way
>> ffmpeg's networking code calls send(). It was calling the function
>> many times with small chunks of data (including lots of calls with 1
>> byte, 8 bytes, etc.). This appears to be fine on Linux but on
>> Windows this causes a significant slowdown due to the way the send()
>> function behaves. To fix this we changed the ffmpeg RTMP code to
>> buffer the data and only call send() when the buffer fills up (we
>> use a 64K buffer). This improved the speed by over 10x. Our patch
>> was specific to RTMP since that's the only thing we were using;
>> however, you probably want to do this buffering in a more generic
>> way at the TCP level.
> Which line(s) of the rtmp code exactly cause these small packets ?
>
>
>> I've attached the affected files. The buffering happens in the
>> ff_buffered_urlwrite function in rtmppkt.c.
> please see:
>
> http://ffmpeg.org/developer.html#Submitting-patches-1
>
> posting a complete file is useless, noone can see from it
> what changes you made and noone can find these changes as noone
> knows upon which revission you based your changes.
>
> [...]
>

-------------- next part --------------
>From 02e437f7efca37f6124fda61e6b80415434507ec Mon Sep 17 00:00:00 2001
From: unknown <farhad at justin.tv>
Date: Fri, 5 Oct 2012 13:20:36 -0700
Subject: [PATCH] patch for rtmp bandwidth issue

---
 libavformat/rtmppkt.c   | 92 ++++++++++++++++++++++++++++++++++++++++++++++---
 libavformat/rtmppkt.h   | 22 ++++++++++++
 libavformat/rtmpproto.c | 39 +++++++++++++++++----
 3 files changed, 143 insertions(+), 10 deletions(-)

diff --git a/libavformat/rtmppkt.c b/libavformat/rtmppkt.c
index b607d5b..05cda34 100644
--- a/libavformat/rtmppkt.c
+++ b/libavformat/rtmppkt.c
@@ -218,6 +218,90 @@ int ff_rtmp_packet_read_internal(URLContext *h, RTMPPacket *p, int chunk_size,
     return size;
 }
 
+#define RTMP_SEND_BUFFER_SIZE 65536
+static unsigned char* rtmp_send_buffer = 0;
+static unsigned int rtmp_send_buffer_data_size = 0;
+
+int ff_create_rtmp_send_buffer(void)
+{	
+	if (!rtmp_send_buffer)
+	{
+		rtmp_send_buffer = (unsigned char*)av_malloc(RTMP_SEND_BUFFER_SIZE);
+		rtmp_send_buffer_data_size = 0;
+	}
+
+	return 0;
+}
+
+int ff_destroy_rtmp_send_buffer(void)
+{
+	av_freep(&rtmp_send_buffer);
+	rtmp_send_buffer_data_size = 0;
+	return 0;
+}
+
+int ff_buffered_urlwrite(URLContext *h, const unsigned char *buf, int size, int pkt_type)
+{
+	int ret = 0;
+
+	if (!rtmp_send_buffer)
+	{
+		return -1;		
+	}
+
+	if (rtmp_send_buffer_data_size + size < RTMP_SEND_BUFFER_SIZE)
+	{			
+		// Just add the data to the buffer
+		memcpy(rtmp_send_buffer + rtmp_send_buffer_data_size, buf, size);
+		rtmp_send_buffer_data_size += size;
+		ret = size;
+	}
+	else
+	{	
+		// Can't fit the new data in the buffer. First flush the buffer if it has some data		
+		if (rtmp_send_buffer_data_size > 0)
+		{
+			ret = ffurl_write(h, rtmp_send_buffer, rtmp_send_buffer_data_size);
+			rtmp_send_buffer_data_size = 0;
+			if (ret < 0)
+				return ret;
+		}
+			
+		if (size > RTMP_SEND_BUFFER_SIZE)
+		{
+			// The size of the new data is greater than the buffer size, simply write out the data
+			ret = ffurl_write(h, buf, size);
+			rtmp_send_buffer_data_size = 0;
+			if (ret < 0)
+				return ret;
+		}
+		else
+		{				
+			// Put the new data in the buffer
+			memcpy(rtmp_send_buffer, buf, size);
+			rtmp_send_buffer_data_size = size;
+		}
+
+		ret = size;		
+	}
+
+	if (pkt_type != RTMP_PT_AUDIO && pkt_type != RTMP_PT_VIDEO)
+	{
+		// If we've got a non-audio/video packets, if the buffer has data in it, 
+		// we must flush the buffer to send it immediately
+		//
+		if (rtmp_send_buffer_data_size > 0)
+		{
+			ret = ffurl_write(h, rtmp_send_buffer, rtmp_send_buffer_data_size);
+			rtmp_send_buffer_data_size = 0;
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return ret;
+}
+
 int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
                          int chunk_size, RTMPPacket *prev_pkt)
 {
@@ -277,17 +361,17 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
     }
     prev_pkt[pkt->channel_id].extra      = pkt->extra;
 
-    if ((ret = ffurl_write(h, pkt_hdr, p - pkt_hdr)) < 0)
+    if ((ret = ff_buffered_urlwrite(h, pkt_hdr, p - pkt_hdr, pkt->type)) < 0)
         return ret;
     size = p - pkt_hdr + pkt->data_size;
     while (off < pkt->data_size) {
         int towrite = FFMIN(chunk_size, pkt->data_size - off);
-        if ((ret = ffurl_write(h, pkt->data + off, towrite)) < 0)
+        if ((ret = ff_buffered_urlwrite(h, pkt->data + off, towrite, pkt->type)) < 0)
             return ret;
         off += towrite;
         if (off < pkt->data_size) {
-            uint8_t marker = 0xC0 | pkt->channel_id;
-            if ((ret = ffurl_write(h, &marker, 1)) < 0)
+            uint8_t marker = 0xC0 | pkt->channel_id;			
+            if ((ret = ff_buffered_urlwrite(h, &marker, 1, pkt->type)) < 0)
                 return ret;
             size++;
         }
diff --git a/libavformat/rtmppkt.h b/libavformat/rtmppkt.h
index 7ed3113..5e90460 100644
--- a/libavformat/rtmppkt.h
+++ b/libavformat/rtmppkt.h
@@ -131,6 +131,28 @@ int ff_rtmp_packet_read_internal(URLContext *h, RTMPPacket *p, int chunk_size,
                                  RTMPPacket *prev_pkt, uint8_t c);
 
 /**
+ * Create the send buffer used to buffer the incoming RTMP packets
+ */
+int ff_create_rtmp_send_buffer(void);
+
+/**
+ * Destroy the send buffer used to buffer the incoming RTMP packets
+ */
+int ff_destroy_rtmp_send_buffer(void);
+
+/**
+ * Buffer the incoming RTMP packet data before calling ffurl_write
+ *
+ * @param h          reader context
+ * @param buf        buffer to send
+ * @param size		 size of the data in the buffer
+ * @param pkt_type   type of the RTMP packet data
+ * @return number of bytes written on success, negative value otherwise
+ */
+int ff_buffered_urlwrite(URLContext *h, const unsigned char *buf, 
+						 int size, int pkt_type);
+
+/**
  * Send RTMP packet to the server.
  *
  * @param h          reader context
diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c
index 518e7b2..7f96e88 100644
--- a/libavformat/rtmpproto.c
+++ b/libavformat/rtmpproto.c
@@ -53,6 +53,7 @@
 #define TCURL_MAX_LENGTH 512
 #define FLASHVER_MAX_LENGTH 64
 #define RTMP_PKTDATA_DEFAULT_SIZE 4096
+#define RTMP_OUTGOING_CHUNK_SIZE 128
 
 /** RTMP protocol handler state */
 typedef enum {
@@ -774,6 +775,25 @@ static int gen_swf_verification(URLContext *s, RTMPContext *rt)
 }
 
 /**
+ * Generate outgoing chunk size message and send it to the server
+ */
+static int gen_outgoing_chunk_size(URLContext *s, RTMPContext *rt)
+{
+    RTMPPacket pkt;
+    uint8_t *p;
+    int ret;
+
+    if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_CHUNK_SIZE,
+                                     0, 4)) < 0)
+        return ret;
+
+    p = pkt.data;
+    bytestream_put_be32(&p, rt->out_chunk_size);
+
+    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)
@@ -1419,12 +1439,13 @@ static int handle_chunk_size(URLContext *s, RTMPPacket *pkt)
     }
 
     if (!rt->is_input) {
-        /* Send the same chunk size change packet back to the server,
-         * setting the outgoing chunk size to the same as the incoming one. */
-        if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size,
-                                        rt->prev_pkt[1])) < 0)
-            return ret;
-        rt->out_chunk_size = AV_RB32(pkt->data);
+
+		// Set the outgoing chunk size
+		rt->out_chunk_size = RTMP_OUTGOING_CHUNK_SIZE;
+
+        // Send the outgoing chunk size to the server
+		if ((ret = gen_outgoing_chunk_size(s, rt)) < 0)
+	        return ret;        
     }
 
     rt->in_chunk_size = AV_RB32(pkt->data);
@@ -2045,6 +2066,9 @@ static int rtmp_close(URLContext *h)
     free_tracked_methods(rt);
     av_freep(&rt->flv_data);
     ffurl_close(rt->stream);
+
+	ff_destroy_rtmp_send_buffer();
+
     return ret;
 }
 
@@ -2059,6 +2083,9 @@ static int rtmp_close(URLContext *h)
  */
 static int rtmp_open(URLContext *s, const char *uri, int flags)
 {
+	if (ff_create_rtmp_send_buffer() < 0)
+		return AVERROR(ENOMEM);
+
     RTMPContext *rt = s->priv_data;
     char proto[8], hostname[256], path[1024], *fname;
     char *old_app;
-- 
1.7.11.msysgit.1



More information about the ffmpeg-devel mailing list