Coverage Report

Created: 2026-09-03 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/mjpeg.c
Line
Count
Source
1
/*****************************************************************************
2
 * mjpeg.c : demuxes mjpeg webcam http streams
3
 *****************************************************************************
4
 * Copyright (C) 2004 VLC authors and VideoLAN
5
 *
6
 * Authors: Henry Jen (slowhog) <henryjen@ztune.net>
7
 *          Derk-Jan Hartman (thedj)
8
 *          Sigmund Augdal (Dnumgis)
9
 *          Laurent Aimar <fenrir@via.ecp.fr>
10
 *
11
 * This program is free software; you can redistribute it and/or modify it
12
 * under the terms of the GNU Lesser General Public License as published by
13
 * the Free Software Foundation; either version 2.1 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Lesser General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Lesser General Public License
22
 * along with this program; if not, write to the Free Software Foundation,
23
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
 *****************************************************************************/
25
26
/*****************************************************************************
27
 * Preamble
28
 *****************************************************************************/
29
30
#ifdef HAVE_CONFIG_H
31
# include "config.h"
32
#endif
33
34
#include <vlc_common.h>
35
#include <vlc_plugin.h>
36
#include <vlc_demux.h>
37
#include "mxpeg_helper.h"
38
39
/*****************************************************************************
40
 * Module descriptor
41
 *****************************************************************************/
42
static int  Open ( vlc_object_t * );
43
44
#define FPS_TEXT N_("Frames per Second")
45
#define FPS_LONGTEXT N_("This is the desired frame rate when " \
46
    "playing MJPEG from a file. Use 0 (this is the default value) for a " \
47
    "live stream (from a camera).")
48
49
174
vlc_module_begin ()
50
87
    set_shortname( "MJPEG")
51
87
    set_description( N_("M-JPEG camera demuxer") )
52
87
    set_capability( "demux", 7 )
53
87
    set_callback( Open )
54
87
    set_subcategory( SUBCAT_INPUT_DEMUX )
55
87
    add_float( "mjpeg-fps", 0.0, FPS_TEXT, FPS_LONGTEXT )
56
87
vlc_module_end ()
57
58
/*****************************************************************************
59
 * Local prototypes
60
 *****************************************************************************/
61
static int MimeDemux( demux_t * );
62
static int MjpgDemux( demux_t * );
63
static int Control( demux_t *, int i_query, va_list args );
64
65
typedef struct
66
{
67
    es_format_t     fmt;
68
    es_out_id_t     *p_es;
69
70
    bool            b_still;
71
    vlc_tick_t      i_still_end;
72
    vlc_tick_t      i_time;
73
    vlc_tick_t      i_frame_length;
74
    char            *psz_separator;
75
    int             i_frame_size_estimate;
76
    const uint8_t   *p_peek;
77
    int             i_data_peeked;
78
    int             i_level;
79
} demux_sys_t;
80
81
/*****************************************************************************
82
 * Peek: Helper function to peek data with incremental size.
83
 * \return false if peek no more data, true otherwise.
84
 *****************************************************************************/
85
static bool Peek( demux_t *p_demux, bool b_first )
86
371
{
87
371
    int i_data;
88
371
    demux_sys_t *p_sys = p_demux->p_sys;
89
90
371
    if( b_first )
91
8
    {
92
8
        p_sys->i_data_peeked = 0;
93
8
    }
94
363
    else if( p_sys->i_data_peeked == p_sys->i_frame_size_estimate )
95
361
    {
96
361
        p_sys->i_frame_size_estimate += 5120;
97
361
    }
98
371
    i_data = vlc_stream_Peek( p_demux->s, &p_sys->p_peek,
99
371
                          p_sys->i_frame_size_estimate );
100
371
    if( i_data == p_sys->i_data_peeked )
101
2
    {
102
2
        msg_Warn( p_demux, "no more data" );
103
2
        return false;
104
2
    }
105
369
    p_sys->i_data_peeked = i_data;
106
369
    if( i_data <= 0 )
107
0
    {
108
0
        msg_Warn( p_demux, "cannot peek data" );
109
0
        return false;
110
0
    }
111
369
    return true;
112
369
}
113
114
/*****************************************************************************
115
 * GetLine: Internal function used to dup a line of string from the buffer
116
 *****************************************************************************/
117
static char* GetLine( demux_t *p_demux, int *p_pos )
118
1
{
119
1
    demux_sys_t *p_sys = p_demux->p_sys;
120
1
    const uint8_t *p_buf;
121
1
    int         i_size;
122
1
    int         i;
123
1
    char        *p_line;
124
125
1
    while( *p_pos >= p_sys->i_data_peeked )
126
0
    {
127
0
        if( ! Peek( p_demux, false ) )
128
0
        {
129
0
            return NULL;
130
0
        }
131
0
    }
132
1
    p_buf = p_sys->p_peek + *p_pos;
133
1
    i_size = p_sys->i_data_peeked - *p_pos;
134
1
    i = 0;
135
979k
    while( p_buf[i] != '\n' )
136
979k
    {
137
979k
        i++;
138
979k
        if( i == i_size )
139
190
        {
140
190
            if( ! Peek( p_demux, false ) )
141
1
            {
142
1
                return NULL;
143
1
            }
144
189
            p_buf = p_sys->p_peek + *p_pos;
145
189
            i_size = p_sys->i_data_peeked - *p_pos;
146
189
        }
147
979k
    }
148
0
    *p_pos += i + 1;
149
0
    if( i > 0 && p_buf[i - 1] == '\r' )
150
0
    {
151
0
        i--;
152
0
    }
153
0
    p_line = vlc_obj_malloc( VLC_OBJECT(p_demux), i + 1 );
154
0
    if( unlikely( p_line == NULL ) )
155
0
        return NULL;
156
0
    strncpy ( p_line, (char*)p_buf, i );
157
0
    p_line[i] = '\0';
158
0
    return p_line;
159
0
}
160
161
/*****************************************************************************
162
 * CheckMimeHeader: Internal function used to verify and skip mime header
163
 * \param p_header_size Return size of MIME header, 0 if no MIME header
164
 * detected, minus value if error
165
 * \return true if content type is image/jpeg, false otherwise
166
 *****************************************************************************/
167
static bool CheckMimeHeader( demux_t *p_demux, int *p_header_size )
168
6
{
169
6
    bool        b_jpeg = false;
170
6
    int         i_pos = 0;
171
6
    char        *psz_line;
172
6
    char        *p_ch;
173
6
    demux_sys_t *p_sys = p_demux->p_sys;
174
175
6
    *p_header_size = -1;
176
6
    if( !Peek( p_demux, true ) )
177
0
    {
178
0
        msg_Err( p_demux, "cannot peek" );
179
0
        return false;
180
0
    }
181
6
    if( p_sys->i_data_peeked < 5)
182
0
    {
183
0
        msg_Err( p_demux, "data shortage" );
184
0
        return false;
185
0
    }
186
6
    if( strncmp( (char *)p_sys->p_peek, "--", 2 ) != 0
187
5
        && strncmp( (char *)p_sys->p_peek, "\r\n--", 4 ) != 0 )
188
5
    {
189
5
        *p_header_size = 0;
190
5
        return false;
191
5
    }
192
1
    else
193
1
    {
194
1
        i_pos = *p_sys->p_peek == '-' ? 2 : 4;
195
1
        psz_line = GetLine( p_demux, &i_pos );
196
1
        if( NULL == psz_line )
197
1
        {
198
1
            msg_Err( p_demux, "no EOL" );
199
1
            return false;
200
1
        }
201
202
        /* Read the separator and remember it if not yet stored */
203
0
        if( p_sys->psz_separator == NULL )
204
0
        {
205
0
            p_sys->psz_separator = psz_line;
206
0
            msg_Dbg( p_demux, "Multipart MIME detected, using separator: %s",
207
0
                     p_sys->psz_separator );
208
0
        }
209
0
        else
210
0
        {
211
0
            if( strcmp( psz_line, p_sys->psz_separator ) )
212
0
            {
213
0
                msg_Warn( p_demux, "separator %s does not match %s", psz_line,
214
0
                          p_sys->psz_separator );
215
0
            }
216
0
            vlc_obj_free( VLC_OBJECT(p_demux), psz_line );
217
0
        }
218
0
    }
219
220
0
    psz_line = GetLine( p_demux, &i_pos );
221
0
    while( psz_line && *psz_line )
222
0
    {
223
0
        if( !strncasecmp( psz_line, "Content-Type:", 13 ) )
224
0
        {
225
0
            p_ch = psz_line + 13;
226
0
            while( *p_ch != '\0' && ( *p_ch == ' ' || *p_ch == '\t' ) ) p_ch++;
227
0
            if( strncasecmp( p_ch, "image/jpeg", 10 ) )
228
0
            {
229
0
                msg_Warn( p_demux, "%s, image/jpeg is expected", psz_line );
230
0
                b_jpeg = false;
231
0
            }
232
0
            else
233
0
            {
234
0
                b_jpeg = true;
235
0
            }
236
0
        }
237
0
        else
238
0
        {
239
0
            msg_Dbg( p_demux, "discard MIME header: %s", psz_line );
240
0
        }
241
0
        vlc_obj_free( VLC_OBJECT(p_demux), psz_line );
242
0
        psz_line = GetLine( p_demux, &i_pos );
243
0
    }
244
245
0
    if( NULL == psz_line )
246
0
    {
247
0
        msg_Err( p_demux, "no EOL" );
248
0
        return false;
249
0
    }
250
251
0
    vlc_obj_free( VLC_OBJECT(p_demux), psz_line );
252
253
0
    *p_header_size = i_pos;
254
0
    return b_jpeg;
255
0
}
256
257
static int SendBlock( demux_t *p_demux, int i )
258
1
{
259
1
    demux_sys_t *p_sys = p_demux->p_sys;
260
1
    block_t     *p_block;
261
262
1
    if( ( p_block = vlc_stream_Block( p_demux->s, i ) ) == NULL )
263
0
    {
264
0
        msg_Warn( p_demux, "cannot read data" );
265
0
        return VLC_DEMUXER_EOF;
266
0
    }
267
268
1
    if( p_sys->i_frame_length != VLC_TICK_INVALID )
269
0
    {
270
0
        p_block->i_pts = p_sys->i_time;
271
0
        p_sys->i_time += p_sys->i_frame_length;
272
0
    }
273
1
    else
274
1
    {
275
1
        p_block->i_pts = vlc_tick_now();
276
1
    }
277
1
    p_block->i_dts = p_block->i_pts;
278
279
1
    es_out_SetPCR( p_demux->out, p_block->i_pts );
280
1
    es_out_Send( p_demux->out, p_sys->p_es, p_block );
281
282
1
    if( p_sys->b_still )
283
0
        p_sys->i_still_end = vlc_tick_now() + p_sys->i_frame_length;
284
285
1
    return VLC_DEMUXER_SUCCESS;
286
1
}
287
288
/*****************************************************************************
289
 * Open: check file and initializes structures
290
 *****************************************************************************/
291
static int Open( vlc_object_t * p_this )
292
6
{
293
6
    demux_t     *p_demux = (demux_t*)p_this;
294
6
    int         i_size;
295
6
    bool        b_matched = false;
296
297
6
    if( IsMxpeg( p_demux->s ) && !p_demux->obj.force )
298
        // let avformat handle this case
299
0
        return VLC_EGENERIC;
300
301
6
    demux_sys_t *p_sys = vlc_obj_malloc( p_this, sizeof (*p_sys) );
302
6
    if( unlikely(p_sys == NULL) )
303
0
        return VLC_ENOMEM;
304
305
6
    p_demux->p_sys      = p_sys;
306
6
    p_sys->p_es         = NULL;
307
6
    p_sys->i_time       = VLC_TICK_0;
308
6
    p_sys->i_level      = 0;
309
310
6
    p_sys->psz_separator = NULL;
311
6
    p_sys->i_frame_size_estimate = 15 * 1024;
312
313
6
    char *content_type = stream_ContentType( p_demux->s );
314
6
    if ( content_type )
315
0
    {
316
        //FIXME: this is not fully match to RFC
317
0
        char* boundary = strstr( content_type, "boundary=" );
318
0
        if( boundary )
319
0
        {
320
0
            boundary += strlen( "boundary=" );
321
0
            size_t len = strlen( boundary );
322
0
            if( len > 2 && boundary[0] == '"'
323
0
                && boundary[len-1] == '"' )
324
0
            {
325
0
                boundary[len-1] = '\0';
326
0
                boundary++;
327
0
            }
328
0
            p_sys->psz_separator = vlc_obj_strdup( p_this, boundary );
329
0
            if( !p_sys->psz_separator )
330
0
            {
331
0
                free( content_type );
332
0
                return VLC_ENOMEM;
333
0
            }
334
0
        }
335
0
        free( content_type );
336
0
    }
337
338
6
    b_matched = CheckMimeHeader( p_demux, &i_size);
339
6
    if( b_matched )
340
0
    {
341
0
        p_demux->pf_demux = MimeDemux;
342
0
        if( vlc_stream_Read( p_demux->s, NULL, i_size ) != i_size )
343
0
            return VLC_EGENERIC;
344
0
    }
345
6
    else if( i_size == 0 )
346
5
    {
347
        /* 0xffd8 identify a JPEG SOI */
348
5
        if( p_sys->p_peek[0] == 0xFF && p_sys->p_peek[1] == 0xD8 )
349
1
        {
350
1
            msg_Dbg( p_demux, "JPEG SOI marker detected" );
351
1
            p_demux->pf_demux = MjpgDemux;
352
1
            p_sys->i_level++;
353
1
        }
354
4
        else
355
4
        {
356
4
            return VLC_EGENERIC;
357
4
        }
358
5
    }
359
1
    else
360
1
    {
361
1
        return VLC_EGENERIC;
362
1
    }
363
364
    /* Frame rate */
365
1
    float f_fps = var_InheritFloat( p_demux, "mjpeg-fps" );
366
367
1
    p_sys->i_still_end = VLC_TICK_INVALID;
368
1
    if( demux_IsPathExtension( p_demux, ".jpeg" ) ||
369
1
        demux_IsPathExtension( p_demux, ".jpg" ) )
370
0
    {
371
        /* Plain JPEG file = single still picture */
372
0
        p_sys->b_still = true;
373
0
        if( f_fps == 0.f )
374
            /* Defaults to 1fps */
375
0
            f_fps = 1.f;
376
0
    }
377
1
    else
378
1
        p_sys->b_still = false;
379
1
    p_sys->i_frame_length = f_fps ? vlc_tick_rate_duration(f_fps) : VLC_TICK_INVALID;
380
381
1
    es_format_Init( &p_sys->fmt, VIDEO_ES, VLC_CODEC_MJPG );
382
383
1
    p_sys->fmt.i_id = 0;
384
1
    p_sys->p_es = es_out_Add( p_demux->out, &p_sys->fmt );
385
1
    if( unlikely(p_sys->p_es == NULL) )
386
0
        return VLC_ENOMEM;
387
388
1
    p_demux->pf_control = Control;
389
1
    return VLC_SUCCESS;
390
1
}
391
392
/*****************************************************************************
393
 * Demux: read packet and send them to decoders
394
 *****************************************************************************
395
 * Returns -1 in case of error, 0 in case of EOF, 1 otherwise
396
 *****************************************************************************/
397
static int MjpgDemux( demux_t *p_demux )
398
2
{
399
2
    demux_sys_t *p_sys = p_demux->p_sys;
400
2
    int i;
401
402
2
    if( p_sys->b_still && p_sys->i_still_end != VLC_TICK_INVALID )
403
0
    {
404
        /* Still frame, wait until the pause delay is gone */
405
0
        vlc_tick_wait( p_sys->i_still_end );
406
0
        p_sys->i_still_end = VLC_TICK_INVALID;
407
0
        return VLC_DEMUXER_SUCCESS;
408
0
    }
409
410
2
    if( !Peek( p_demux, true ) )
411
0
    {
412
0
        msg_Warn( p_demux, "cannot peek data" );
413
0
        return VLC_DEMUXER_EOF;
414
0
    }
415
2
    if( p_sys->i_data_peeked < 4 )
416
0
    {
417
0
        msg_Warn( p_demux, "data shortage" );
418
0
        return VLC_DEMUXER_EOF;
419
0
    }
420
2
    i = 3;
421
2
FIND_NEXT_EOI:
422
917k
    while( !( p_sys->p_peek[i-1] == 0xFF && p_sys->p_peek[i] == 0xD9 ) )
423
917k
    {
424
917k
        if( p_sys->p_peek[i-1] == 0xFF && p_sys->p_peek[i] == 0xD9  )
425
0
        {
426
0
            p_sys->i_level++;
427
0
            msg_Dbg( p_demux, "we found another JPEG SOI at %d", i );
428
0
        }
429
917k
        i++;
430
917k
        if( i >= p_sys->i_data_peeked )
431
173
        {
432
173
            msg_Dbg( p_demux, "did not find JPEG EOI in %d bytes",
433
173
                     p_sys->i_data_peeked );
434
173
            if( !Peek( p_demux, false ) )
435
1
            {
436
1
                msg_Warn( p_demux, "no more data is available at the moment" );
437
1
                return VLC_DEMUXER_EOF;
438
1
            }
439
173
        }
440
917k
    }
441
1
    i++;
442
443
1
    msg_Dbg( p_demux, "JPEG EOI detected at %d", i );
444
1
    p_sys->i_level--;
445
446
1
    if( p_sys->i_level > 0 )
447
0
        goto FIND_NEXT_EOI;
448
1
    return SendBlock( p_demux, i );
449
1
}
450
451
static int MimeDemux( demux_t *p_demux )
452
0
{
453
0
    demux_sys_t *p_sys = p_demux->p_sys;
454
0
    int         i_size, i;
455
456
0
    bool  b_match = CheckMimeHeader( p_demux, &i_size );
457
458
0
    if( i_size > 0 )
459
0
    {
460
0
        if( vlc_stream_Read( p_demux->s, NULL, i_size ) != i_size )
461
0
            return VLC_DEMUXER_EOF;
462
0
    }
463
0
    else if( i_size < 0 )
464
0
    {
465
0
        return VLC_DEMUXER_EOF;
466
0
    }
467
0
    else
468
0
    {
469
        // No MIME header, assume OK
470
0
        b_match = true;
471
0
    }
472
473
0
    if( !Peek( p_demux, true ) )
474
0
    {
475
0
        msg_Warn( p_demux, "cannot peek data" );
476
0
        return VLC_DEMUXER_EOF;
477
0
    }
478
479
0
    i = 0;
480
0
    size_t sep_len = strlen(p_sys->psz_separator);
481
0
    i_size = sep_len + 2;
482
0
    if( p_sys->i_data_peeked < i_size )
483
0
    {
484
0
        msg_Warn( p_demux, "data shortage" );
485
0
        return VLC_DEMUXER_EOF;
486
0
    }
487
488
0
    for( ;; )
489
0
    {
490
0
        while( !( p_sys->p_peek[i] == '-' && p_sys->p_peek[i+1] == '-' ) )
491
0
        {
492
0
            i++;
493
0
            i_size++;
494
0
            if( i_size >= p_sys->i_data_peeked )
495
0
            {
496
0
                msg_Dbg( p_demux, "MIME boundary not found in %d bytes of "
497
0
                         "data", p_sys->i_data_peeked );
498
499
0
                if( !Peek( p_demux, false ) )
500
0
                {
501
0
                    msg_Warn( p_demux, "no more data is available at the "
502
0
                              "moment" );
503
0
                    return VLC_DEMUXER_EOF;
504
0
                }
505
0
            }
506
0
        }
507
508
        /* peek is located now at "--", check if we have enough data for the
509
         * separator */
510
0
        while (i + 2 + sep_len > (size_t)p_sys->i_data_peeked)
511
0
        {
512
0
            if (!Peek(p_demux, false))
513
0
            {
514
0
                msg_Warn( p_demux, "data shortage" );
515
0
                return VLC_DEMUXER_EOF;
516
0
            }
517
0
        }
518
519
        /* Handle old and new style of separators */
520
0
        if (!strncmp(p_sys->psz_separator, (char *)(p_sys->p_peek + i + 2), sep_len)
521
0
         || (sep_len > 4
522
0
          && !strncmp(p_sys->psz_separator, "--", 2)
523
0
          && !strncmp(p_sys->psz_separator, (char *)(p_sys->p_peek + i), sep_len)))
524
0
        {
525
0
            break;
526
0
        }
527
528
0
        i++;
529
0
        i_size++;
530
0
    }
531
532
0
    if( !b_match )
533
0
    {
534
0
        msg_Err( p_demux, "discard non-JPEG part" );
535
0
        return VLC_DEMUXER_EOF;
536
0
    }
537
538
0
    return SendBlock( p_demux, i );
539
0
}
540
541
/*****************************************************************************
542
 * Control:
543
 *****************************************************************************/
544
static int Control( demux_t *p_demux, int i_query, va_list args )
545
0
{
546
0
    return demux_vaControlHelper( p_demux->s, 0, 0, 0, 0, i_query, args );
547
0
}