Switch to a new version of the Metacube protocol.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 18 Aug 2013 00:37:16 +0000 (02:37 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 18 Aug 2013 00:47:04 +0000 (02:47 +0200)
Compared to the old version, this fixes many small deficiencies:

 - The header is smaller; 16 instead of 24 bytes. With TS, Metacube actually
   yields noticeable overhead (13% or so), so reducing it by 1/3 is good.
 - The sync no longer overlaps with itself; both starting and ending with
   the same sounds bad if we should ever drop bytes in the middle.
   In fact, it no longer has any repeated characters.
 - The header is checksummed, to avoid cases where a corrupted header
   could cause us to pull in gigabytes of data. (The data is not.)

vlc-metacube.diff has been updated, although it includes another patch
(part of the WebM patch set) since that's what my VLC works from at the moment.

Makefile
httpinput.cpp
metacube.h [deleted file]
metacube2.cpp [new file with mode: 0644]
metacube2.h [new file with mode: 0644]
server.cpp
stream.cpp
vlc-metacube.diff

index 828c9fd..7269a4f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ PROTOC=protoc
 CXXFLAGS=-Wall -O2 -g -pthread
 LDLIBS=-lprotobuf -pthread -lrt
 
-OBJS=main.o client.o server.o stream.o udpstream.o serverpool.o mutexlock.o input.o input_stats.o httpinput.o udpinput.o parse.o config.o markpool.o acceptor.o stats.o accesslog.o thread.o util.o log.o state.pb.o
+OBJS=main.o client.o server.o stream.o udpstream.o serverpool.o mutexlock.o input.o input_stats.o httpinput.o udpinput.o parse.o config.o markpool.o acceptor.o stats.o accesslog.o thread.o util.o log.o metacube2.o state.pb.o
 
 all: cubemap
 
index 7f96970..80daa1a 100644 (file)
@@ -17,7 +17,7 @@
 
 #include "httpinput.h"
 #include "log.h"
-#include "metacube.h"
+#include "metacube2.h"
 #include "mutexlock.h"
 #include "parse.h"
 #include "serverpool.h"
@@ -505,7 +505,7 @@ void HTTPInput::process_data(char *ptr, size_t bytes)
 
        for ( ;; ) {
                // If we don't have enough data (yet) for even the Metacube header, just return.
-               if (pending_data.size() < sizeof(metacube_block_header)) {
+               if (pending_data.size() < sizeof(metacube2_block_header)) {
                        return;
                }
 
@@ -514,13 +514,13 @@ void HTTPInput::process_data(char *ptr, size_t bytes)
                if (!has_metacube_header) {
                        char *ptr = static_cast<char *>(
                                memmem(pending_data.data(), pending_data.size(),
-                                      METACUBE_SYNC, strlen(METACUBE_SYNC)));
+                                      METACUBE2_SYNC, strlen(METACUBE2_SYNC)));
                        if (ptr == NULL) {
                                // OK, so we didn't find the sync marker. We know then that
                                // we do not have the _full_ marker in the buffer, but we
                                // could have N-1 bytes. Drop everything before that,
                                // and then give up.
-                               drop_pending_data(pending_data.size() - (strlen(METACUBE_SYNC) - 1));
+                               drop_pending_data(pending_data.size() - (strlen(METACUBE2_SYNC) - 1));
                                return;
                        } else {
                                // Yay, we found the header. Drop everything (if anything) before it.
@@ -528,25 +528,37 @@ void HTTPInput::process_data(char *ptr, size_t bytes)
                                has_metacube_header = true;
 
                                // Re-check that we have the entire header; we could have dropped data.
-                               if (pending_data.size() < sizeof(metacube_block_header)) {
+                               if (pending_data.size() < sizeof(metacube2_block_header)) {
                                        return;
                                }
                        }
                }
 
                // Now it's safe to read the header.
-               metacube_block_header *hdr = reinterpret_cast<metacube_block_header *>(pending_data.data());    
-               assert(memcmp(hdr->sync, METACUBE_SYNC, sizeof(hdr->sync)) == 0);
+               metacube2_block_header *hdr = reinterpret_cast<metacube2_block_header *>(pending_data.data());  
+               assert(memcmp(hdr->sync, METACUBE2_SYNC, sizeof(hdr->sync)) == 0);
                uint32_t size = ntohl(hdr->size);
-               uint32_t flags = ntohl(hdr->flags);
+               uint16_t flags = ntohs(hdr->flags);
+               uint16_t expected_csum = metacube2_compute_crc(hdr);
 
+               if (expected_csum != ntohs(hdr->csum)) {
+                       log(WARNING, "[%s] Metacube checksum failed (expected 0x%x, got 0x%x), "
+                               "not reading block claiming to be %d bytes (flags=%x).",
+                               url.c_str(), expected_csum, ntohs(hdr->csum),
+                               size, flags);
+
+                       // Drop only the first byte, and let the rest of the code handle resync.
+                       pending_data.erase(pending_data.begin(), pending_data.begin() + 1);
+                       has_metacube_header = false;
+                       continue;
+               }
                if (size > 262144) {
                        log(WARNING, "[%s] Metacube block of %d bytes (flags=%x); corrupted header?",
                                url.c_str(), size, flags);
                }
 
                // See if we have the entire block. If not, wait for more data.
-               if (pending_data.size() < sizeof(metacube_block_header) + size) {
+               if (pending_data.size() < sizeof(metacube2_block_header) + size) {
                        return;
                }
 
@@ -555,7 +567,7 @@ void HTTPInput::process_data(char *ptr, size_t bytes)
                        MutexLock lock(&stats_mutex);
                        stats.data_bytes_received += size;
                }
-               char *inner_data = pending_data.data() + sizeof(metacube_block_header);
+               char *inner_data = pending_data.data() + sizeof(metacube2_block_header);
                if (flags & METACUBE_FLAGS_HEADER) {
                        stream_header = string(inner_data, inner_data + size);
                        for (size_t i = 0; i < stream_indices.size(); ++i) {
@@ -576,7 +588,7 @@ void HTTPInput::process_data(char *ptr, size_t bytes)
                // Consume the block. This isn't the most efficient way of dealing with things
                // should we have many blocks, but these routines don't need to be too efficient
                // anyway.
-               pending_data.erase(pending_data.begin(), pending_data.begin() + sizeof(metacube_block_header) + size);
+               pending_data.erase(pending_data.begin(), pending_data.begin() + sizeof(metacube2_block_header) + size);
                has_metacube_header = false;
        }
 }
@@ -586,7 +598,7 @@ void HTTPInput::drop_pending_data(size_t num_bytes)
        if (num_bytes == 0) {
                return;
        }
-       log(WARNING, "[%s] Dropping %lld junk bytes from stream, maybe it is not a Metacube stream?",
+       log(WARNING, "[%s] Dropping %lld junk bytes from stream, maybe it is not a Metacube2 stream?",
                url.c_str(), (long long)num_bytes);
        assert(pending_data.size() >= num_bytes);
        pending_data.erase(pending_data.begin(), pending_data.begin() + num_bytes);
diff --git a/metacube.h b/metacube.h
deleted file mode 100644 (file)
index 062325b..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef _METACUBE_H
-#define _METACUBE_H
-
-/* Definitions for the Metacube protocol, used to communicate with Cubemap. */
-
-#include <stdlib.h>
-
-#define METACUBE_SYNC "\\o/_metacube_\\o/"  /* 16 bytes long. */
-#define METACUBE_FLAGS_HEADER 0x1
-#define METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START 0x2
-
-struct metacube_block_header {
-       char sync[16];   /* METACUBE_SYNC */
-       uint32_t size;   /* Network byte order. Does not include header. */
-       uint32_t flags;  /* Network byte order. METACUBE_FLAGS_*. */
-};
-
-#endif  /* !defined(_METACUBE_H) */
diff --git a/metacube2.cpp b/metacube2.cpp
new file mode 100644 (file)
index 0000000..f7e4ce2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Implementation of Metacube2 utility functions.
+ *
+ * Note: This file is meant to compile as both C and C++, for easier inclusion
+ * in other projects.
+ */
+
+#include <stdlib.h>
+
+#include "metacube2.h"
+
+/*
+ * 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. */
+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;
+       int i, j;
+
+       for (i = 0; i < data_len; ++i) {        
+               uint8_t c = data[i];
+               for (j = 0; j < 8; j++) {
+                       int bit = crc & 0x8000;
+                       crc = (crc << 1) | ((c >> (7 - j)) & 0x01);
+                       if (bit) {
+                               crc ^= METACUBE2_CRC_POLYNOMIAL;
+                       }
+               }
+       }
+
+       /* Finalize. */
+       for (i = 0; i < 16; i++) {
+               int bit = crc & 0x8000;
+               crc = crc << 1;
+               if (bit) {
+                       crc ^= METACUBE2_CRC_POLYNOMIAL;
+               }
+       }
+
+       return crc;
+}
diff --git a/metacube2.h b/metacube2.h
new file mode 100644 (file)
index 0000000..43de8b7
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef _METACUBE2_H
+#define _METACUBE2_H
+
+/*
+ * Definitions for the Metacube2 protocol, used to communicate with Cubemap.
+ *
+ * Note: This file is meant to compile as both C and C++, for easier inclusion
+ * in other projects.
+ */
+
+#include <stdint.h>
+
+#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. */
+};
+
+uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr);
+
+#endif  /* !defined(_METACUBE_H) */
index 1637d94..4442b34 100644 (file)
@@ -20,7 +20,7 @@
 #include "accesslog.h"
 #include "log.h"
 #include "markpool.h"
-#include "metacube.h"
+#include "metacube2.h"
 #include "mutexlock.h"
 #include "parse.h"
 #include "server.h"
@@ -542,10 +542,11 @@ void Server::construct_header(Client *client)
                        "Content-encoding: metacube\r\n" +
                        "\r\n";
                if (!stream->stream_header.empty()) {
-                       metacube_block_header hdr;
-                       memcpy(hdr.sync, METACUBE_SYNC, sizeof(hdr.sync));
+                       metacube2_block_header hdr;
+                       memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
                        hdr.size = htonl(stream->stream_header.size());
-                       hdr.flags = htonl(METACUBE_FLAGS_HEADER);
+                       hdr.flags = htons(METACUBE_FLAGS_HEADER);
+                       hdr.csum = htons(metacube2_compute_crc(&hdr));
                        client->header_or_error.append(
                                string(reinterpret_cast<char *>(&hdr), sizeof(hdr)));
                }
index 6563b5b..e88ad77 100644 (file)
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "log.h"
-#include "metacube.h"
+#include "metacube2.h"
 #include "state.pb.h"
 #include "stream.h"
 #include "util.h"
@@ -219,12 +219,12 @@ void Stream::add_data_deferred(const char *data, size_t bytes, StreamStartSuitab
 
        if (encoding == Stream::STREAM_ENCODING_METACUBE) {
                // Add a Metacube block header before the data.
-               metacube_block_header hdr;
-               memcpy(hdr.sync, METACUBE_SYNC, sizeof(hdr.sync));
+               metacube2_block_header hdr;
+               memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
                hdr.size = htonl(bytes);
-               hdr.flags = htonl(0);
+               hdr.flags = htons(0);
                if (suitable_for_stream_start == NOT_SUITABLE_FOR_STREAM_START) {
-                       hdr.flags |= htonl(METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START);
+                       hdr.flags |= htons(METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START);
                }
 
                iovec iov;
index 0d965a1..fbde59d 100644 (file)
@@ -1,5 +1,38 @@
+diff --git a/include/metacube2.h b/include/metacube2.h
+new file mode 100644
+index 0000000..dfbfd24
+--- /dev/null
++++ b/include/metacube2.h
+@@ -0,0 +1,27 @@
++#ifndef _METACUBE2_H
++#define _METACUBE2_H
++
++/*
++ * Definitions for the Metacube2 protocol, used to communicate with Cubemap.
++ *
++ * Note: This file is meant to compile as both C and C++, for easier inclusion
++ * in other projects.
++ */
++
++#include <stdint.h>
++
++#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. */
++};
++
++/* This code is based on code generated by pycrc. */
++uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr);
++
++#endif  /* !defined(_METACUBE_H) */
 diff --git a/include/vlc_httpd.h b/include/vlc_httpd.h
-index 6100dd0..46d557a 100644
+index 6100dd0..7a14515 100644
 --- a/include/vlc_httpd.h
 +++ b/include/vlc_httpd.h
 @@ -135,10 +135,10 @@ VLC_API void httpd_RedirectDelete( httpd_redirect_t * );
@@ -11,12 +44,12 @@ index 6100dd0..46d557a 100644
  VLC_API void httpd_StreamDelete( httpd_stream_t * );
  VLC_API int httpd_StreamHeader( httpd_stream_t *, uint8_t *p_data, int i_data );
 -VLC_API int httpd_StreamSend( httpd_stream_t *, uint8_t *p_data, int i_data );
-+VLC_API int httpd_StreamSend( httpd_stream_t *, uint8_t *p_data, int i_data, bool b_header );
++VLC_API int httpd_StreamSend( httpd_stream_t *, uint8_t *p_data, int i_data, bool b_header, bool b_keyframe );
  
  
  /* Msg functions facilities */
 diff --git a/modules/access_output/http.c b/modules/access_output/http.c
-index 61095f5..95dd705 100644
+index 364768c..f667e08 100644
 --- a/modules/access_output/http.c
 +++ b/modules/access_output/http.c
 @@ -72,6 +72,8 @@ vlc_module_begin ()
@@ -46,13 +79,12 @@ index 61095f5..95dd705 100644
      if( !( p_sys = p_access->p_sys =
                  malloc( sizeof( sout_access_out_sys_t ) ) ) )
          return VLC_ENOMEM ;
-@@ -188,10 +192,12 @@ static int Open( vlc_object_t *p_this )
-     {
+@@ -189,9 +193,11 @@ static int Open( vlc_object_t *p_this )
          psz_mime = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "mime" );
      }
-+        
-+    b_metacube = var_GetBool( p_access, SOUT_CFG_PREFIX "metacube" );
  
++    b_metacube = var_GetBool( p_access, SOUT_CFG_PREFIX "metacube" );
++
      p_sys->p_httpd_stream =
          httpd_StreamNew( p_sys->p_httpd_host, path, psz_mime,
 -                         psz_user, psz_pwd );
@@ -60,39 +92,48 @@ index 61095f5..95dd705 100644
      free( psz_user );
      free( psz_pwd );
      free( psz_mime );
-@@ -263,8 +269,9 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
+@@ -263,8 +269,10 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
      while( p_buffer )
      {
          block_t *p_next;
--
--        if( p_buffer->i_flags & BLOCK_FLAG_HEADER )
 +        bool b_header_block = p_buffer->i_flags & BLOCK_FLAG_HEADER;
-+        
++        bool b_keyframe = p_buffer->i_flags & BLOCK_FLAG_TYPE_I;
+-        if( p_buffer->i_flags & BLOCK_FLAG_HEADER )
 +        if( b_header_block )
          {
              /* gather header */
              if( p_sys->b_header_complete )
-@@ -295,9 +302,10 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
-         }
+@@ -297,7 +305,7 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
          i_len += p_buffer->i_buffer;
-+
          /* send data */
          i_err = httpd_StreamSend( p_sys->p_httpd_stream, p_buffer->p_buffer,
 -                                  p_buffer->i_buffer );
-+                                  p_buffer->i_buffer, b_header_block );
++                                  p_buffer->i_buffer, b_header_block, b_keyframe );
  
          p_next = p_buffer->p_next;
          block_Release( p_buffer );
+diff --git a/src/Makefile.am b/src/Makefile.am
+index 639d157..89f78bc 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -480,6 +480,7 @@ SOURCES_libvlc_common = \
+ SOURCES_libvlc_httpd = \
+       network/httpd.c \
++      network/metacube2.c \
+       $(NULL)
+ SOURCES_libvlc_sout = \
 diff --git a/src/network/httpd.c b/src/network/httpd.c
-index f76c47c..dcda968 100644
+index bbfbd18..8e0b130 100644
 --- a/src/network/httpd.c
 +++ b/src/network/httpd.c
 @@ -39,6 +39,7 @@
  #include <vlc_charset.h>
  #include <vlc_url.h>
  #include <vlc_mime.h>
-+#include <metacube.h>
++#include <metacube2.h>
  #include "../libvlc.h"
  
  #include <string.h>
@@ -104,7 +145,21 @@ index f76c47c..dcda968 100644
  
  /* each host run in his own thread */
  struct httpd_host_t
-@@ -621,6 +623,7 @@ struct httpd_stream_t
+@@ -159,6 +161,13 @@ struct httpd_client_t
+     int     i_buffer;
+     uint8_t *p_buffer;
++    /*
++     * If waiting for a keyframe, this is the position (in bytes) of the
++     * last keyframe the stream saw before this client connected.
++     * Otherwise, -1.
++     */
++    int64_t i_keyframe_wait_to_pass;
++
+     /* */
+     httpd_message_t query;  /* client -> httpd */
+     httpd_message_t answer; /* httpd -> client */
+@@ -621,11 +630,20 @@ struct httpd_stream_t
      httpd_url_t *url;
  
      char    *psz_mime;
@@ -112,7 +167,37 @@ index f76c47c..dcda968 100644
  
      /* Header to send as first packet */
      uint8_t *p_header;
-@@ -712,9 +715,24 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
+     int     i_header;
++    /* Some muxes, in particular the avformat mux, can mark given blocks
++     * as keyframes, to ensure that the stream starts with one.
++     * (This is particularly important for WebM streaming to certain
++     * browsers.) Store if we've ever seen any such keyframe blocks,
++     * and if so, the byte position of the start of the last one. */
++    bool        b_has_keyframes;
++    int64_t     i_last_keyframe_seen_pos;
++
+     /* circular buffer */
+     int         i_buffer_size;      /* buffer size, can't be reallocated smaller */
+     uint8_t     *p_buffer;          /* buffer */
+@@ -659,6 +677,16 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
+             /* fprintf( stderr, "httpd_StreamCallBack: no data\n" ); */
+             return VLC_EGENERIC;    /* wait, no data available */
+         }
++        if( cl->i_keyframe_wait_to_pass >= 0 )
++        {
++            if( stream->i_last_keyframe_seen_pos <= cl->i_keyframe_wait_to_pass )
++                /* still waiting for the next keyframe */
++                return VLC_EGENERIC;
++
++            /* seek to the new keyframe */
++            answer->i_body_offset = stream->i_last_keyframe_seen_pos;
++            cl->i_keyframe_wait_to_pass = -1;
++        }
+         if( answer->i_body_offset + stream->i_buffer_size <
+             stream->i_buffer_pos )
+         {
+@@ -712,11 +740,31 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
              /* Send the header */
              if( stream->i_header > 0 )
              {
@@ -121,10 +206,11 @@ index f76c47c..dcda968 100644
 -                memcpy( answer->p_body, stream->p_header, stream->i_header );
 +                if ( stream->b_metacube )
 +                {
-+                    struct metacube_block_header hdr;
-+                    memcpy( hdr.sync, METACUBE_SYNC, sizeof(METACUBE_SYNC) );
++                    struct metacube2_block_header hdr;
++                    memcpy( hdr.sync, METACUBE2_SYNC, sizeof(METACUBE2_SYNC) );
 +                    hdr.size = htonl( stream->i_header );
-+                    hdr.flags = htonl( METACUBE_FLAGS_HEADER );
++                    hdr.flags = htons( METACUBE_FLAGS_HEADER );
++                    hdr.csum = htons( metacube2_compute_crc( &hdr ));
 +
 +                    answer->i_body = stream->i_header + sizeof( hdr );
 +                    answer->p_body = xmalloc( answer->i_body );
@@ -139,15 +225,19 @@ index f76c47c..dcda968 100644
 +                }
              }
              answer->i_body_offset = stream->i_buffer_last_pos;
++            if( stream->b_has_keyframes )
++                cl->i_keyframe_wait_to_pass = stream->i_last_keyframe_seen_pos;
++            else
++                cl->i_keyframe_wait_to_pass = -1;
              vlc_mutex_unlock( &stream->lock );
-@@ -758,13 +776,18 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
+         }
+         else
+@@ -758,13 +806,16 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
              httpd_MsgAdd( answer, "Content-type",  "%s", stream->psz_mime );
          }
          httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
-+        if ( stream->b_metacube )
-+        {
++        if( stream->b_metacube )
 +            httpd_MsgAdd( answer, "Content-encoding",  "metacube");
-+        }
          return VLC_SUCCESS;
      }
  }
@@ -160,7 +250,7 @@ index f76c47c..dcda968 100644
  {
      httpd_stream_t *stream = xmalloc( sizeof( httpd_stream_t ) );
  
-@@ -783,6 +806,7 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
+@@ -783,6 +834,7 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
      {
          stream->psz_mime = strdup( vlc_mime_Ext2Mime( psz_url ) );
      }
@@ -168,7 +258,16 @@ index f76c47c..dcda968 100644
      stream->i_header = 0;
      stream->p_header = NULL;
      stream->i_buffer_size = 5000000;    /* 5 Mo per stream */
-@@ -819,22 +843,10 @@ int httpd_StreamHeader( httpd_stream_t *stream, uint8_t *p_data, int i_data )
+@@ -791,6 +843,8 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
+      * (this way i_body_offset can never be 0) */
+     stream->i_buffer_pos = 1;
+     stream->i_buffer_last_pos = 1;
++    stream->b_has_keyframes = false;
++    stream->i_last_keyframe_seen_pos = 0;
+     httpd_UrlCatch( stream->url, HTTPD_MSG_HEAD, httpd_StreamCallBack,
+                     (httpd_callback_sys_t*)stream );
+@@ -819,22 +873,10 @@ int httpd_StreamHeader( httpd_stream_t *stream, uint8_t *p_data, int i_data )
      return VLC_SUCCESS;
  }
  
@@ -194,13 +293,13 @@ index f76c47c..dcda968 100644
      while( i_count > 0)
      {
          int i_copy;
-@@ -850,6 +862,31 @@ int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
+@@ -850,6 +892,40 @@ int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
      }
  
      stream->i_buffer_pos += i_data;
 +}
 +
-+int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data, bool b_header )
++int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data, bool b_header, bool b_keyframe )
 +{
 +    if( i_data < 0 || p_data == NULL )
 +    {
@@ -211,41 +310,90 @@ index f76c47c..dcda968 100644
 +    /* save this pointer (to be used by new connection) */
 +    stream->i_buffer_last_pos = stream->i_buffer_pos;
 +
-+    if ( stream->b_metacube ) {
-+        struct metacube_block_header hdr;
-+        memcpy( hdr.sync, METACUBE_SYNC, sizeof(METACUBE_SYNC) );
++    if( b_keyframe )
++    {
++        stream->b_has_keyframes = true;
++        stream->i_last_keyframe_seen_pos = stream->i_buffer_pos;
++    }
++
++    if( stream->b_metacube )
++    {
++        struct metacube2_block_header hdr;
++        memcpy( hdr.sync, METACUBE2_SYNC, sizeof(METACUBE2_SYNC) );
 +        hdr.size = htonl( i_data );
-+        if ( b_header ) {
-+            hdr.flags = htonl( METACUBE_FLAGS_HEADER );
-+        } else {
-+            hdr.flags = htonl( 0 );
-+        }
++        hdr.flags = htons( 0 );
++        if( b_header )
++            hdr.flags |= htons( METACUBE_FLAGS_HEADER );
++        if( stream->b_has_keyframes && !b_keyframe )
++            hdr.flags |= htons( METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START );
++        hdr.csum = htons( metacube2_compute_crc( &hdr ));
 +        httpd_AppendData( stream, (uint8_t *)&hdr, sizeof(hdr) );
 +    }
++
 +    httpd_AppendData( stream, p_data, i_data );
  
      vlc_mutex_unlock( &stream->lock );
      return VLC_SUCCESS;
-diff --git a/include/metacube.h b/include/metacube.h
+@@ -1273,6 +1349,7 @@ static void httpd_ClientInit( httpd_client_t *cl, mtime_t now )
+     cl->i_buffer_size = HTTPD_CL_BUFSIZE;
+     cl->i_buffer = 0;
+     cl->p_buffer = xmalloc( cl->i_buffer_size );
++    cl->i_keyframe_wait_to_pass = -1;
+     cl->b_stream_mode = false;
+     httpd_MsgInit( &cl->query );
+diff --git a/src/network/metacube2.c b/src/network/metacube2.c
 new file mode 100644
-index 0000000..b40a42e
+index 0000000..7b2dacf
 --- /dev/null
-+++ b/include/metacube.h
-@@ -0,0 +1,17 @@
-+#ifndef _METACUBE_H
-+#define _METACUBE_H
-+
-+/* Definitions for the Metacube protocol, used to communicate with Cubemap. */
++++ b/src/network/metacube2.c
+@@ -0,0 +1,49 @@
++/*
++ * Implementation of Metacube2 utility functions.
++ *
++ * Note: This file is meant to compile as both C and C++, for easier inclusion
++ * in other projects.
++ */
 +
 +#include <stdlib.h>
 +
-+#define METACUBE_SYNC "\\o/_metacube_\\o/"  /* 16 bytes long. */
-+#define METACUBE_FLAGS_HEADER 0x1
++#include "metacube2.h"
 +
-+struct metacube_block_header {
-+      char sync[16];   /* METACUBE_SYNC */
-+      uint32_t size;   /* Network byte order. Does not include header. */
-+      uint32_t flags;  /* Network byte order. METACUBE_FLAGS_*. */
-+};
++/*
++ * 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
 +
-+#endif  /* !defined(_METACUBE_H) */
++/* Semi-random starting value to make sure all-zero won't pass. */
++#define METACUBE2_CRC_START 0x1234
++
++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;
++      int i, j;
++
++      for (i = 0; i < data_len; ++i) {
++              uint8_t c = data[i];
++              for (j = 0; j < 8; j++) {
++                      int bit = crc & 0x8000;
++                      crc = (crc << 1) | ((c >> (7 - j)) & 0x01);
++                      if (bit) {
++                              crc ^= METACUBE2_CRC_POLYNOMIAL;
++                      }
++              }
++      }
++
++      /* Finalize. */
++      for (i = 0; i < 16; i++) {
++              int bit = crc & 0x8000;
++              crc = crc << 1;
++              if (bit) {
++                      crc ^= METACUBE2_CRC_POLYNOMIAL;
++              }
++      }
++
++      return crc;
++}