From 979a284b4039b0ea74525b700b9f1089b8c4248d Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 18 Aug 2013 02:37:16 +0200 Subject: [PATCH] Switch to a new version of the Metacube protocol. 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 | 2 +- httpinput.cpp | 36 ++++--- metacube.h | 18 ---- metacube2.cpp | 50 +++++++++ metacube2.h | 26 +++++ server.cpp | 9 +- stream.cpp | 10 +- vlc-metacube.diff | 258 ++++++++++++++++++++++++++++++++++++---------- 8 files changed, 314 insertions(+), 95 deletions(-) delete mode 100644 metacube.h create mode 100644 metacube2.cpp create mode 100644 metacube2.h diff --git a/Makefile b/Makefile index 828c9fd..7269a4f 100644 --- 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 diff --git a/httpinput.cpp b/httpinput.cpp index 7f96970..80daa1a 100644 --- a/httpinput.cpp +++ b/httpinput.cpp @@ -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( 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(pending_data.data()); - assert(memcmp(hdr->sync, METACUBE_SYNC, sizeof(hdr->sync)) == 0); + metacube2_block_header *hdr = reinterpret_cast(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 index 062325b..0000000 --- a/metacube.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _METACUBE_H -#define _METACUBE_H - -/* Definitions for the Metacube protocol, used to communicate with Cubemap. */ - -#include - -#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 index 0000000..f7e4ce2 --- /dev/null +++ b/metacube2.cpp @@ -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 + +#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 index 0000000..43de8b7 --- /dev/null +++ b/metacube2.h @@ -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 + +#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) */ diff --git a/server.cpp b/server.cpp index 1637d94..4442b34 100644 --- a/server.cpp +++ b/server.cpp @@ -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(&hdr), sizeof(hdr))); } diff --git a/stream.cpp b/stream.cpp index 6563b5b..e88ad77 100644 --- a/stream.cpp +++ b/stream.cpp @@ -9,7 +9,7 @@ #include #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; diff --git a/vlc-metacube.diff b/vlc-metacube.diff index 0d965a1..fbde59d 100644 --- a/vlc-metacube.diff +++ b/vlc-metacube.diff @@ -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 ++ ++#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 #include #include -+#include ++#include #include "../libvlc.h" #include @@ -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 + -+#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; ++} -- 2.39.2