#include "httpinput.h"
#include "log.h"
-#include "metacube.h"
+#include "metacube2.h"
#include "mutexlock.h"
#include "parse.h"
#include "serverpool.h"
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;
}
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.
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;
}
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) {
// 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;
}
}
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/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 * );
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 ()
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 );
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>
/* 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;
/* 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 )
{
- 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 );
+ }
}
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;
}
}
{
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 ) );
}
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;
}
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 )
+ {
+ /* 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;
++}