fbde59d1f0e3bbe2bd38b7b453b2c74c2f5aa967
[cubemap] / vlc-metacube.diff
1 diff --git a/include/metacube2.h b/include/metacube2.h
2 new file mode 100644
3 index 0000000..dfbfd24
4 --- /dev/null
5 +++ b/include/metacube2.h
6 @@ -0,0 +1,27 @@
7 +#ifndef _METACUBE2_H
8 +#define _METACUBE2_H
9 +
10 +/*
11 + * Definitions for the Metacube2 protocol, used to communicate with Cubemap.
12 + *
13 + * Note: This file is meant to compile as both C and C++, for easier inclusion
14 + * in other projects.
15 + */
16 +
17 +#include <stdint.h>
18 +
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
22 +
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. */
28 +};
29 +
30 +/* This code is based on code generated by pycrc. */
31 +uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr);
32 +
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 * );
39  
40  
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 );
48  
49  
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 )
62  vlc_module_end ()
63  
64 @@ -80,7 +82,7 @@ vlc_module_end ()
65   * Exported prototypes
66   *****************************************************************************/
67  static const char *const ppsz_sout_options[] = {
68 -    "user", "pwd", "mime", NULL
69 +    "user", "pwd", "mime", "metacube", NULL
70  };
71  
72  static ssize_t Write( sout_access_out_t *, block_t * );
73 @@ -114,6 +116,8 @@ static int Open( vlc_object_t *p_this )
74      char                *psz_pwd;
75      char                *psz_mime;
76  
77 +    bool                    b_metacube;
78 +
79      if( !( p_sys = p_access->p_sys =
80                  malloc( sizeof( sout_access_out_sys_t ) ) ) )
81          return VLC_ENOMEM ;
82 @@ -189,9 +193,11 @@ static int Open( vlc_object_t *p_this )
83          psz_mime = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "mime" );
84      }
85  
86 +    b_metacube = var_GetBool( p_access, SOUT_CFG_PREFIX "metacube" );
87 +
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 );
92      free( psz_user );
93      free( psz_pwd );
94      free( psz_mime );
95 @@ -263,8 +269,10 @@ static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
96      while( p_buffer )
97      {
98          block_t *p_next;
99 +        bool b_header_block = p_buffer->i_flags & BLOCK_FLAG_HEADER;
100 +        bool b_keyframe = p_buffer->i_flags & BLOCK_FLAG_TYPE_I;
101  
102 -        if( p_buffer->i_flags & BLOCK_FLAG_HEADER )
103 +        if( b_header_block )
104          {
105              /* gather header */
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;
109          /* send data */
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 );
113  
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 = \
121  
122  SOURCES_libvlc_httpd = \
123         network/httpd.c \
124 +       network/metacube2.c \
125         $(NULL)
126  
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
132 @@ -39,6 +39,7 @@
133  #include <vlc_charset.h>
134  #include <vlc_url.h>
135  #include <vlc_mime.h>
136 +#include <metacube2.h>
137  #include "../libvlc.h"
138  
139  #include <string.h>
140 @@ -66,6 +67,7 @@
141  #endif
142  
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 );
145  
146  /* each host run in his own thread */
147  struct httpd_host_t
148 @@ -159,6 +161,13 @@ struct httpd_client_t
149      int     i_buffer;
150      uint8_t *p_buffer;
151  
152 +    /*
153 +     * If waiting for a keyframe, this is the position (in bytes) of the
154 +     * last keyframe the stream saw before this client connected.
155 +     * Otherwise, -1.
156 +     */
157 +    int64_t i_keyframe_wait_to_pass;
158 +
159      /* */
160      httpd_message_t query;  /* client -> httpd */
161      httpd_message_t answer; /* httpd -> client */
162 @@ -621,11 +630,20 @@ struct httpd_stream_t
163      httpd_url_t *url;
164  
165      char    *psz_mime;
166 +    bool    b_metacube;
167  
168      /* Header to send as first packet */
169      uint8_t *p_header;
170      int     i_header;
171  
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;
179 +
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 */
186          }
187 +        if( cl->i_keyframe_wait_to_pass >= 0 )
188 +        {
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;
192 +
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;
196 +        }
197          if( answer->i_body_offset + stream->i_buffer_size <
198              stream->i_buffer_pos )
199          {
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 )
203              {
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 )
208 +                {
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 ));
214 +
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 );
219 +                }
220 +                else
221 +                {
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 );
225 +                }
226              }
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;
230 +            else
231 +                cl->i_keyframe_wait_to_pass = -1;
232              vlc_mutex_unlock( &stream->lock );
233          }
234          else
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 );
237          }
238          httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
239 +        if( stream->b_metacube )
240 +            httpd_MsgAdd( answer, "Content-encoding",  "metacube");
241          return VLC_SUCCESS;
242      }
243  }
244  
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,
249 +                                 bool b_metacube )
250  {
251      httpd_stream_t *stream = xmalloc( sizeof( httpd_stream_t ) );
252  
253 @@ -783,6 +834,7 @@ httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
254      {
255          stream->psz_mime = strdup( vlc_mime_Ext2Mime( psz_url ) );
256      }
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;
267  
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 )
271      return VLC_SUCCESS;
272  }
273  
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 )
276  {
277 -    int i_count;
278 -    int i_pos;
279 -
280 -    if( i_data < 0 || p_data == NULL )
281 -    {
282 -        return VLC_SUCCESS;
283 -    }
284 -    vlc_mutex_lock( &stream->lock );
285 -
286 -    /* save this pointer (to be used by new connection) */
287 -    stream->i_buffer_last_pos = stream->i_buffer_pos;
288 -
289 -    i_pos = stream->i_buffer_pos % stream->i_buffer_size;
290 -    i_count = i_data;
291 +    int i_pos = stream->i_buffer_pos % stream->i_buffer_size;
292 +    int i_count = i_data;
293      while( i_count > 0)
294      {
295          int i_copy;
296 @@ -850,6 +892,40 @@ int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
297      }
298  
299      stream->i_buffer_pos += i_data;
300 +}
301 +
302 +int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data, bool b_header, bool b_keyframe )
303 +{
304 +    if( i_data < 0 || p_data == NULL )
305 +    {
306 +        return VLC_SUCCESS;
307 +    }
308 +    vlc_mutex_lock( &stream->lock );
309 +
310 +    /* save this pointer (to be used by new connection) */
311 +    stream->i_buffer_last_pos = stream->i_buffer_pos;
312 +
313 +    if( b_keyframe )
314 +    {
315 +        stream->b_has_keyframes = true;
316 +        stream->i_last_keyframe_seen_pos = stream->i_buffer_pos;
317 +    }
318 +
319 +    if( stream->b_metacube )
320 +    {
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 );
325 +        if( b_header )
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) );
331 +    }
332 +
333 +    httpd_AppendData( stream, p_data, i_data );
334  
335      vlc_mutex_unlock( &stream->lock );
336      return VLC_SUCCESS;
337 @@ -1273,6 +1349,7 @@ static void httpd_ClientInit( httpd_client_t *cl, mtime_t now )
338      cl->i_buffer_size = HTTPD_CL_BUFSIZE;
339      cl->i_buffer = 0;
340      cl->p_buffer = xmalloc( cl->i_buffer_size );
341 +    cl->i_keyframe_wait_to_pass = -1;
342      cl->b_stream_mode = false;
343  
344      httpd_MsgInit( &cl->query );
345 diff --git a/src/network/metacube2.c b/src/network/metacube2.c
346 new file mode 100644
347 index 0000000..7b2dacf
348 --- /dev/null
349 +++ b/src/network/metacube2.c
350 @@ -0,0 +1,49 @@
351 +/*
352 + * Implementation of Metacube2 utility functions.
353 + *
354 + * Note: This file is meant to compile as both C and C++, for easier inclusion
355 + * in other projects.
356 + */
357 +
358 +#include <stdlib.h>
359 +
360 +#include "metacube2.h"
361 +
362 +/*
363 + * https://www.ece.cmu.edu/~koopman/pubs/KoopmanCRCWebinar9May2012.pdf
364 + * recommends this for messages as short as ours (see table at page 34).
365 + */
366 +#define METACUBE2_CRC_POLYNOMIAL 0x8FDB
367 +
368 +/* Semi-random starting value to make sure all-zero won't pass. */
369 +#define METACUBE2_CRC_START 0x1234
370 +
371 +uint16_t metacube2_compute_crc(const struct metacube2_block_header *hdr)
372 +{
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;
376 +       int i, j;
377 +
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);
383 +                       if (bit) {
384 +                               crc ^= METACUBE2_CRC_POLYNOMIAL;
385 +                       }
386 +               }
387 +       }
388 +
389 +       /* Finalize. */
390 +       for (i = 0; i < 16; i++) {
391 +               int bit = crc & 0x8000;
392 +               crc = crc << 1;
393 +               if (bit) {
394 +                       crc ^= METACUBE2_CRC_POLYNOMIAL;
395 +               }
396 +       }
397 +
398 +       return crc;
399 +}