Support the Metacube protocol when streaming over HTTP.
authorSteinar H. Gunderson <steinar+vlc@gunderson.no>
Sun, 16 Feb 2014 10:53:41 +0000 (11:53 +0100)
committerDenis Charmet <typx@dinauz.org>
Mon, 24 Feb 2014 22:29:08 +0000 (23:29 +0100)
This is an inclusion of a patch that has been living out-of-tree for some time,
but now as Cubemap 1.0.0 has been released and the protocol has been frozen,
it would sound reasonable to get it into mainline.

Essentially it makes the VLC HTTP server support the Metacube protocol, which
is a framing format to let an external reflector know where the start and end
of the blocks are without having to understand the format itself (similar to
how the VLC HTTP server itself gets to know this information). This lets it
serve the correct header block to new clients joining in the middle of the
stream, and makes sure new clients don't start in the middle of a block,
which is a problem for non-self-synchronizing formats (such as FLV and WebM).
It includes self-synchronization and header checksumming to ensure smooth
recovery from overruns in the server-to-reflector TCP stream.

The only consumer of this protocol currently, and for the foreseeable future,
is Cubemap (http://git.sesse.net/?p=cubemap), a scalable video reflector
designed to be used with VLC.

Signed-off-by: Denis Charmet <typx@dinauz.org>
modules/access_output/http.c

index bf75130..f217679 100644 (file)
@@ -30,6 +30,9 @@
 # include "config.h"
 #endif
 
+#include <arpa/inet.h>
+#include <stdint.h>
+
 #include <vlc_common.h>
 #include <vlc_plugin.h>
 #include <vlc_sout.h>
@@ -57,6 +60,9 @@ static void Close( vlc_object_t * );
 #define MIME_TEXT N_("Mime")
 #define MIME_LONGTEXT N_("MIME returned by the server (autodetected " \
                         "if not specified)." )
+#define METACUBE_TEXT N_("Metacube")
+#define METACUBE_LONGTEXT N_("Use the Metacube protocol. Needed for streaming " \
+                             "to the Cubemap reflector.")
 
 
 vlc_module_begin ()
@@ -72,6 +78,8 @@ vlc_module_begin ()
                   PASS_TEXT, PASS_LONGTEXT, true )
     add_string( SOUT_CFG_PREFIX "mime", "",
                 MIME_TEXT, MIME_LONGTEXT, true )
+    add_bool( SOUT_CFG_PREFIX "metacube", false,
+              METACUBE_TEXT, METACUBE_LONGTEXT, true )
     set_callbacks( Open, Close )
 vlc_module_end ()
 
@@ -80,7 +88,7 @@ vlc_module_end ()
  * Exported prototypes
  *****************************************************************************/
 static const char *const ppsz_sout_options[] = {
-    "user", "pwd", "mime", NULL
+    "user", "pwd", "mime", "metacube", NULL
 };
 
 static ssize_t Write( sout_access_out_t *, block_t * );
@@ -100,8 +108,69 @@ struct sout_access_out_sys_t
     int                 i_header_size;
     uint8_t             *p_header;
     bool          b_header_complete;
+    bool                b_metacube;
+    bool                b_has_keyframes;
+};
+
+/* Definitions for the Metacube2 protocol, used to communicate with Cubemap. */
+
+#define METACUBE2_SYNC "cube!map"  /* 8 bytes long. */
+#define METACUBE_FLAGS_HEADER 0x1
+#define METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START 0x2
+
+struct metacube2_block_header
+{
+    char sync[8];    /* METACUBE2_SYNC */
+    uint32_t size;   /* Network byte order. Does not include header. */
+    uint16_t flags;  /* Network byte order. METACUBE_FLAGS_*. */
+    uint16_t csum;   /* Network byte order. CRC16 of size and flags. */
 };
 
+/*
+ * Implementation of Metacube2 utility functions. Taken from the Cubemap
+ * distribution and then relicensed by the author to LGPL2.1+ for inclusion
+ * into VLC.
+ */
+
+/*
+ * https://www.ece.cmu.edu/~koopman/pubs/KoopmanCRCWebinar9May2012.pdf
+ * recommends this for messages as short as ours (see table at page 34).
+ */
+#define METACUBE2_CRC_POLYNOMIAL 0x8FDB
+
+/* Semi-random starting value to make sure all-zero won't pass. */
+#define METACUBE2_CRC_START 0x1234
+
+/* This code is based on code generated by pycrc. */
+static uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr)
+{
+    static const int data_len = sizeof(hdr->size) + sizeof(hdr->flags);
+    const uint8_t *data = (uint8_t *)&hdr->size;
+    uint16_t crc = METACUBE2_CRC_START;
+
+    for (int i = 0; i < data_len; ++i) {
+        uint8_t c = data[i];
+        for (int j = 0; j < 8; j++) {
+            int bit = crc & 0x8000;
+            crc = (crc << 1) | ((c >> (7 - j)) & 0x01);
+            if (bit) {
+                crc ^= METACUBE2_CRC_POLYNOMIAL;
+            }
+        }
+    }
+
+    /* Finalize. */
+    for (int i = 0; i < 16; i++) {
+        int bit = crc & 0x8000;
+        crc = crc << 1;
+        if (bit) {
+            crc ^= METACUBE2_CRC_POLYNOMIAL;
+        }
+    }
+
+    return crc;
+}
+
 /*****************************************************************************
  * Open: open the file
  *****************************************************************************/
@@ -189,6 +258,9 @@ static int Open( vlc_object_t *p_this )
         psz_mime = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "mime" );
     }
 
+    p_sys->b_metacube = var_GetBool( p_access, SOUT_CFG_PREFIX "metacube" );
+    p_sys->b_has_keyframes = false;
+
     p_sys->p_httpd_stream =
         httpd_StreamNew( p_sys->p_httpd_host, path, psz_mime,
                          psz_user, psz_pwd );
@@ -205,6 +277,17 @@ static int Open( vlc_object_t *p_this )
         return VLC_EGENERIC;
     }
 
+    if( p_sys->b_metacube )
+    {
+        httpd_header headers[] = {{ "Content-encoding", "metacube" }};
+        int err = httpd_StreamSetHTTPHeaders( p_sys->p_httpd_stream, headers, sizeof( headers ) / sizeof( httpd_header ) );
+        if( err != VLC_SUCCESS )
+        {
+            free( p_sys );
+            return err;
+        }
+    }
+
     p_sys->i_header_allocated = 1024;
     p_sys->i_header_size      = 0;
     p_sys->p_header           = xmalloc( p_sys->i_header_allocated );
@@ -290,15 +373,63 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
         {
             p_sys->b_header_complete = true;
 
-            httpd_StreamHeader( p_sys->p_httpd_stream, p_sys->p_header,
-                                p_sys->i_header_size );
+            if ( p_sys->b_metacube )
+            {
+                struct metacube2_block_header hdr;
+                memcpy( hdr.sync, METACUBE2_SYNC, sizeof( METACUBE2_SYNC ) );
+                hdr.size = htonl( p_sys->i_header_size );
+                hdr.flags = htons( METACUBE_FLAGS_HEADER );
+                hdr.csum = htons( metacube2_compute_crc( &hdr ) );
+
+                int i_header_size = p_sys->i_header_size + sizeof( hdr );
+                uint8_t *p_hdr_block = xmalloc( i_header_size );
+                memcpy( p_hdr_block, &hdr, sizeof( hdr ) );
+                memcpy( p_hdr_block + sizeof( hdr ), p_sys->p_header, p_sys->i_header_size );
+
+                httpd_StreamHeader( p_sys->p_httpd_stream, p_hdr_block, i_header_size );
+
+                free( p_hdr_block );
+            }
+            else
+            {
+                httpd_StreamHeader( p_sys->p_httpd_stream, p_sys->p_header,
+                                    p_sys->i_header_size );
+            }
         }
 
         i_len += p_buffer->i_buffer;
+
+        if( p_buffer->i_flags & BLOCK_FLAG_TYPE_I )
+        {
+            p_sys->b_has_keyframes = true;
+        }
+
+        p_next = p_buffer->p_next;
+
+        if( p_sys->b_metacube )
+        {
+            /* prepend Metacube header */
+            struct metacube2_block_header hdr;
+            memcpy( hdr.sync, METACUBE2_SYNC, sizeof( METACUBE2_SYNC ) );
+            hdr.size = htonl( p_buffer->i_buffer );
+            hdr.flags = htons( 0 );
+            if( p_buffer->i_flags & BLOCK_FLAG_HEADER )
+                hdr.flags |= htons( METACUBE_FLAGS_HEADER );
+            if( p_sys->b_has_keyframes && !( p_buffer->i_flags & BLOCK_FLAG_TYPE_I ) )
+                hdr.flags |= htons( METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START );
+            hdr.csum = htons( metacube2_compute_crc( &hdr ) );
+
+            p_buffer = block_Realloc( p_buffer, sizeof( hdr ), p_buffer->i_buffer );
+            if( p_buffer == NULL ) {
+                block_ChainRelease( p_next );
+                return VLC_ENOMEM;
+            }
+            memcpy( p_buffer->p_buffer, &hdr, sizeof( hdr ) );
+        }
+
         /* send data */
         i_err = httpd_StreamSend( p_sys->p_httpd_stream, p_buffer );
 
-        p_next = p_buffer->p_next;
         block_Release( p_buffer );
         p_buffer = p_next;