1 diff --git a/include/metacube2.h b/include/metacube2.h
5 +++ b/include/metacube2.h
11 + * Definitions for the Metacube2 protocol, used to communicate with Cubemap.
13 + * Note: This file is meant to compile as both C and C++, for easier inclusion
14 + * in other projects.
19 +#define METACUBE2_SYNC "cube!map" /* 8 bytes long. */
20 +#define METACUBE_FLAGS_HEADER 0x1
21 +#define METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START 0x2
23 +struct metacube2_block_header {
24 + char sync[8]; /* METACUBE2_SYNC */
25 + uint32_t size; /* Network byte order. Does not include header. */
26 + uint16_t flags; /* Network byte order. METACUBE_FLAGS_*. */
27 + uint16_t csum; /* Network byte order. CRC16 of size and flags. */
30 +/* This code is based on code generated by pycrc. */
31 +uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr);
33 +#endif /* !defined(_METACUBE_H) */
34 diff --git a/include/vlc_httpd.h b/include/vlc_httpd.h
35 index 6100dd0..7a14515 100644
36 --- a/include/vlc_httpd.h
37 +++ b/include/vlc_httpd.h
38 @@ -135,10 +135,10 @@ VLC_API void httpd_RedirectDelete( httpd_redirect_t * );
41 typedef struct httpd_stream_t httpd_stream_t;
42 -VLC_API httpd_stream_t * httpd_StreamNew( httpd_host_t *, const char *psz_url, const char *psz_mime, const char *psz_user, const char *psz_password ) VLC_USED;
43 +VLC_API httpd_stream_t * httpd_StreamNew( httpd_host_t *, const char *psz_url, const char *psz_mime, const char *psz_user, const char *psz_password, bool b_metacube ) VLC_USED;
44 VLC_API void httpd_StreamDelete( httpd_stream_t * );
45 VLC_API int httpd_StreamHeader( httpd_stream_t *, uint8_t *p_data, int i_data );
46 -VLC_API int httpd_StreamSend( httpd_stream_t *, uint8_t *p_data, int i_data );
47 +VLC_API int httpd_StreamSend( httpd_stream_t *, uint8_t *p_data, int i_data, bool b_header, bool b_keyframe );
50 /* Msg functions facilities */
51 diff --git a/modules/access_output/http.c b/modules/access_output/http.c
52 index 364768c..f667e08 100644
53 --- a/modules/access_output/http.c
54 +++ b/modules/access_output/http.c
55 @@ -72,6 +72,8 @@ vlc_module_begin ()
56 PASS_TEXT, PASS_LONGTEXT, true )
57 add_string( SOUT_CFG_PREFIX "mime", "",
58 MIME_TEXT, MIME_LONGTEXT, true )
59 + add_bool( SOUT_CFG_PREFIX "metacube", false,
60 + "Use the metacube protocol", "Use the metacube protocol", true )
61 set_callbacks( Open, Close )
64 @@ -80,7 +82,7 @@ vlc_module_end ()
66 *****************************************************************************/
67 static const char *const ppsz_sout_options[] = {
68 - "user", "pwd", "mime", NULL
69 + "user", "pwd", "mime", "metacube", NULL
72 static ssize_t Write( sout_access_out_t *, block_t * );
73 @@ -114,6 +116,8 @@ static int Open( vlc_object_t *p_this )
79 if( !( p_sys = p_access->p_sys =
80 malloc( sizeof( sout_access_out_sys_t ) ) ) )
82 @@ -189,9 +193,11 @@ static int Open( vlc_object_t *p_this )
83 psz_mime = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "mime" );
86 + b_metacube = var_GetBool( p_access, SOUT_CFG_PREFIX "metacube" );
88 p_sys->p_httpd_stream =
89 httpd_StreamNew( p_sys->p_httpd_host, path, psz_mime,
90 - psz_user, psz_pwd );
91 + psz_user, psz_pwd, b_metacube );
95 @@ -263,8 +269,10 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
99 + bool b_header_block = p_buffer->i_flags & BLOCK_FLAG_HEADER;
100 + bool b_keyframe = p_buffer->i_flags & BLOCK_FLAG_TYPE_I;
102 - if( p_buffer->i_flags & BLOCK_FLAG_HEADER )
103 + if( b_header_block )
106 if( p_sys->b_header_complete )
107 @@ -297,7 +305,7 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
108 i_len += p_buffer->i_buffer;
110 i_err = httpd_StreamSend( p_sys->p_httpd_stream, p_buffer->p_buffer,
111 - p_buffer->i_buffer );
112 + p_buffer->i_buffer, b_header_block, b_keyframe );
114 p_next = p_buffer->p_next;
115 block_Release( p_buffer );
116 diff --git a/src/Makefile.am b/src/Makefile.am
117 index 639d157..89f78bc 100644
118 --- a/src/Makefile.am
119 +++ b/src/Makefile.am
120 @@ -480,6 +480,7 @@ SOURCES_libvlc_common = \
122 SOURCES_libvlc_httpd = \
124 + network/metacube2.c \
127 SOURCES_libvlc_sout = \
128 diff --git a/src/network/httpd.c b/src/network/httpd.c
129 index bbfbd18..8e0b130 100644
130 --- a/src/network/httpd.c
131 +++ b/src/network/httpd.c
133 #include <vlc_charset.h>
135 #include <vlc_mime.h>
136 +#include <metacube2.h>
137 #include "../libvlc.h"
143 static void httpd_ClientClean( httpd_client_t *cl );
144 +static void httpd_AppendData( httpd_stream_t *stream, uint8_t *p_data, int i_data );
146 /* each host run in his own thread */
148 @@ -159,6 +161,13 @@ struct httpd_client_t
153 + * If waiting for a keyframe, this is the position (in bytes) of the
154 + * last keyframe the stream saw before this client connected.
157 + int64_t i_keyframe_wait_to_pass;
160 httpd_message_t query; /* client -> httpd */
161 httpd_message_t answer; /* httpd -> client */
162 @@ -621,11 +630,20 @@ struct httpd_stream_t
168 /* Header to send as first packet */
172 + /* Some muxes, in particular the avformat mux, can mark given blocks
173 + * as keyframes, to ensure that the stream starts with one.
174 + * (This is particularly important for WebM streaming to certain
175 + * browsers.) Store if we've ever seen any such keyframe blocks,
176 + * and if so, the byte position of the start of the last one. */
177 + bool b_has_keyframes;
178 + int64_t i_last_keyframe_seen_pos;
180 /* circular buffer */
181 int i_buffer_size; /* buffer size, can't be reallocated smaller */
182 uint8_t *p_buffer; /* buffer */
183 @@ -659,6 +677,16 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
184 /* fprintf( stderr, "httpd_StreamCallBack: no data\n" ); */
185 return VLC_EGENERIC; /* wait, no data available */
187 + if( cl->i_keyframe_wait_to_pass >= 0 )
189 + if( stream->i_last_keyframe_seen_pos <= cl->i_keyframe_wait_to_pass )
190 + /* still waiting for the next keyframe */
191 + return VLC_EGENERIC;
193 + /* seek to the new keyframe */
194 + answer->i_body_offset = stream->i_last_keyframe_seen_pos;
195 + cl->i_keyframe_wait_to_pass = -1;
197 if( answer->i_body_offset + stream->i_buffer_size <
198 stream->i_buffer_pos )
200 @@ -712,11 +740,31 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
201 /* Send the header */
202 if( stream->i_header > 0 )
204 - answer->i_body = stream->i_header;
205 - answer->p_body = xmalloc( stream->i_header );
206 - memcpy( answer->p_body, stream->p_header, stream->i_header );
207 + if ( stream->b_metacube )
209 + struct metacube2_block_header hdr;
210 + memcpy( hdr.sync, METACUBE2_SYNC, sizeof(METACUBE2_SYNC) );
211 + hdr.size = htonl( stream->i_header );
212 + hdr.flags = htons( METACUBE_FLAGS_HEADER );
213 + hdr.csum = htons( metacube2_compute_crc( &hdr ));
215 + answer->i_body = stream->i_header + sizeof( hdr );
216 + answer->p_body = xmalloc( answer->i_body );
217 + memcpy( answer->p_body, &hdr, sizeof( hdr ) );
218 + memcpy( answer->p_body + sizeof( hdr ), stream->p_header, stream->i_header );
222 + answer->i_body = stream->i_header;
223 + answer->p_body = xmalloc( stream->i_header );
224 + memcpy( answer->p_body, stream->p_header, stream->i_header );
227 answer->i_body_offset = stream->i_buffer_last_pos;
228 + if( stream->b_has_keyframes )
229 + cl->i_keyframe_wait_to_pass = stream->i_last_keyframe_seen_pos;
231 + cl->i_keyframe_wait_to_pass = -1;
232 vlc_mutex_unlock( &stream->lock );
235 @@ -758,13 +806,16 @@ static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
236 httpd_MsgAdd( answer, "Content-type", "%s", stream->psz_mime );
238 httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
239 + if( stream->b_metacube )
240 + httpd_MsgAdd( answer, "Content-encoding", "metacube");
245 httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
246 const char *psz_url, const char *psz_mime,
247 - const char *psz_user, const char *psz_password )
248 + const char *psz_user, const char *psz_password,
251 httpd_stream_t *stream = xmalloc( sizeof( httpd_stream_t ) );
253 @@ -783,6 +834,7 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
255 stream->psz_mime = strdup( vlc_mime_Ext2Mime( psz_url ) );
257 + stream->b_metacube = b_metacube;
258 stream->i_header = 0;
259 stream->p_header = NULL;
260 stream->i_buffer_size = 5000000; /* 5 Mo per stream */
261 @@ -791,6 +843,8 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
262 * (this way i_body_offset can never be 0) */
263 stream->i_buffer_pos = 1;
264 stream->i_buffer_last_pos = 1;
265 + stream->b_has_keyframes = false;
266 + stream->i_last_keyframe_seen_pos = 0;
268 httpd_UrlCatch( stream->url, HTTPD_MSG_HEAD, httpd_StreamCallBack,
269 (httpd_callback_sys_t*)stream );
270 @@ -819,22 +873,10 @@ int httpd_StreamHeader( httpd_stream_t *stream, uint8_t *p_data, int i_data )
274 -int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
275 +static void httpd_AppendData( httpd_stream_t *stream, uint8_t *p_data, int i_data )
280 - if( i_data < 0 || p_data == NULL )
282 - return VLC_SUCCESS;
284 - vlc_mutex_lock( &stream->lock );
286 - /* save this pointer (to be used by new connection) */
287 - stream->i_buffer_last_pos = stream->i_buffer_pos;
289 - i_pos = stream->i_buffer_pos % stream->i_buffer_size;
291 + int i_pos = stream->i_buffer_pos % stream->i_buffer_size;
292 + int i_count = i_data;
296 @@ -850,6 +892,40 @@ int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
299 stream->i_buffer_pos += i_data;
302 +int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data, bool b_header, bool b_keyframe )
304 + if( i_data < 0 || p_data == NULL )
306 + return VLC_SUCCESS;
308 + vlc_mutex_lock( &stream->lock );
310 + /* save this pointer (to be used by new connection) */
311 + stream->i_buffer_last_pos = stream->i_buffer_pos;
315 + stream->b_has_keyframes = true;
316 + stream->i_last_keyframe_seen_pos = stream->i_buffer_pos;
319 + if( stream->b_metacube )
321 + struct metacube2_block_header hdr;
322 + memcpy( hdr.sync, METACUBE2_SYNC, sizeof(METACUBE2_SYNC) );
323 + hdr.size = htonl( i_data );
324 + hdr.flags = htons( 0 );
326 + hdr.flags |= htons( METACUBE_FLAGS_HEADER );
327 + if( stream->b_has_keyframes && !b_keyframe )
328 + hdr.flags |= htons( METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START );
329 + hdr.csum = htons( metacube2_compute_crc( &hdr ));
330 + httpd_AppendData( stream, (uint8_t *)&hdr, sizeof(hdr) );
333 + httpd_AppendData( stream, p_data, i_data );
335 vlc_mutex_unlock( &stream->lock );
337 @@ -1273,6 +1349,7 @@ static void httpd_ClientInit( httpd_client_t *cl, mtime_t now )
338 cl->i_buffer_size = HTTPD_CL_BUFSIZE;
340 cl->p_buffer = xmalloc( cl->i_buffer_size );
341 + cl->i_keyframe_wait_to_pass = -1;
342 cl->b_stream_mode = false;
344 httpd_MsgInit( &cl->query );
345 diff --git a/src/network/metacube2.c b/src/network/metacube2.c
347 index 0000000..7b2dacf
349 +++ b/src/network/metacube2.c
352 + * Implementation of Metacube2 utility functions.
354 + * Note: This file is meant to compile as both C and C++, for easier inclusion
355 + * in other projects.
360 +#include "metacube2.h"
363 + * https://www.ece.cmu.edu/~koopman/pubs/KoopmanCRCWebinar9May2012.pdf
364 + * recommends this for messages as short as ours (see table at page 34).
366 +#define METACUBE2_CRC_POLYNOMIAL 0x8FDB
368 +/* Semi-random starting value to make sure all-zero won't pass. */
369 +#define METACUBE2_CRC_START 0x1234
371 +uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr)
373 + static const int data_len = sizeof(hdr->size) + sizeof(hdr->flags);
374 + const uint8_t *data = (uint8_t *)&hdr->size;
375 + uint16_t crc = METACUBE2_CRC_START;
378 + for (i = 0; i < data_len; ++i) {
379 + uint8_t c = data[i];
380 + for (j = 0; j < 8; j++) {
381 + int bit = crc & 0x8000;
382 + crc = (crc << 1) | ((c >> (7 - j)) & 0x01);
384 + crc ^= METACUBE2_CRC_POLYNOMIAL;
390 + for (i = 0; i < 16; i++) {
391 + int bit = crc & 0x8000;
394 + crc ^= METACUBE2_CRC_POLYNOMIAL;