Coverage Report

Created: 2025-10-12 06:51

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
108
vlc_module_begin ()
50
54
    set_shortname( "MJPEG")
51
54
    set_description( N_("M-JPEG camera demuxer") )
52
54
    set_capability( "demux", 7 )
53
54
    set_callback( Open )
54
54
    set_subcategory( SUBCAT_INPUT_DEMUX )
55
54
    add_float( "mjpeg-fps", 0.0, FPS_TEXT, FPS_LONGTEXT )
56
54
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
1.35k
{
87
1.35k
    int i_data;
88
1.35k
    demux_sys_t *p_sys = p_demux->p_sys;
89
90
1.35k
    if( b_first )
91
277
    {
92
277
        p_sys->i_data_peeked = 0;
93
277
    }
94
1.08k
    else if( p_sys->i_data_peeked == p_sys->i_frame_size_estimate )
95
1.06k
    {
96
1.06k
        p_sys->i_frame_size_estimate += 5120;
97
1.06k
    }
98
1.35k
    i_data = vlc_stream_Peek( p_demux->s, &p_sys->p_peek,
99
1.35k
                          p_sys->i_frame_size_estimate );
100
1.35k
    if( i_data == p_sys->i_data_peeked )
101
15
    {
102
15
        msg_Warn( p_demux, "no more data" );
103
15
        return false;
104
15
    }
105
1.34k
    p_sys->i_data_peeked = i_data;
106
1.34k
    if( i_data <= 0 )
107
0
    {
108
0
        msg_Warn( p_demux, "cannot peek data" );
109
0
        return false;
110
0
    }
111
1.34k
    return true;
112
1.34k
}
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
47.7k
{
119
47.7k
    demux_sys_t *p_sys = p_demux->p_sys;
120
47.7k
    const uint8_t *p_buf;
121
47.7k
    int         i_size;
122
47.7k
    int         i;
123
47.7k
    char        *p_line;
124
125
47.8k
    while( *p_pos >= p_sys->i_data_peeked )
126
17
    {
127
17
        if( ! Peek( p_demux, false ) )
128
0
        {
129
0
            return NULL;
130
0
        }
131
17
    }
132
47.7k
    p_buf = p_sys->p_peek + *p_pos;
133
47.7k
    i_size = p_sys->i_data_peeked - *p_pos;
134
47.7k
    i = 0;
135
2.69M
    while( p_buf[i] != '\n' )
136
2.65M
    {
137
2.65M
        i++;
138
2.65M
        if( i == i_size )
139
484
        {
140
484
            if( ! Peek( p_demux, false ) )
141
7
            {
142
7
                return NULL;
143
7
            }
144
477
            p_buf = p_sys->p_peek + *p_pos;
145
477
            i_size = p_sys->i_data_peeked - *p_pos;
146
477
        }
147
2.65M
    }
148
47.7k
    *p_pos += i + 1;
149
47.7k
    if( i > 0 && p_buf[i - 1] == '\r' )
150
194
    {
151
194
        i--;
152
194
    }
153
47.7k
    p_line = vlc_obj_malloc( VLC_OBJECT(p_demux), i + 1 );
154
47.7k
    if( unlikely( p_line == NULL ) )
155
0
        return NULL;
156
47.7k
    strncpy ( p_line, (char*)p_buf, i );
157
47.7k
    p_line[i] = '\0';
158
47.7k
    return p_line;
159
47.7k
}
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
265
{
169
265
    bool        b_jpeg = false;
170
265
    int         i_pos = 0;
171
265
    char        *psz_line;
172
265
    char        *p_ch;
173
265
    demux_sys_t *p_sys = p_demux->p_sys;
174
175
265
    *p_header_size = -1;
176
265
    if( !Peek( p_demux, true ) )
177
0
    {
178
0
        msg_Err( p_demux, "cannot peek" );
179
0
        return false;
180
0
    }
181
265
    if( p_sys->i_data_peeked < 5)
182
0
    {
183
0
        msg_Err( p_demux, "data shortage" );
184
0
        return false;
185
0
    }
186
265
    if( strncmp( (char *)p_sys->p_peek, "--", 2 ) != 0
187
250
        && strncmp( (char *)p_sys->p_peek, "\r\n--", 4 ) != 0 )
188
239
    {
189
239
        *p_header_size = 0;
190
239
        return false;
191
239
    }
192
26
    else
193
26
    {
194
26
        i_pos = *p_sys->p_peek == '-' ? 2 : 4;
195
26
        psz_line = GetLine( p_demux, &i_pos );
196
26
        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
25
        if( p_sys->psz_separator == NULL )
204
25
        {
205
25
            p_sys->psz_separator = psz_line;
206
25
            msg_Dbg( p_demux, "Multipart MIME detected, using separator: %s",
207
25
                     p_sys->psz_separator );
208
25
        }
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
25
    }
219
220
25
    psz_line = GetLine( p_demux, &i_pos );
221
47.7k
    while( psz_line && *psz_line )
222
47.7k
    {
223
47.7k
        if( !strncasecmp( psz_line, "Content-Type:", 13 ) )
224
13
        {
225
13
            p_ch = psz_line + 13;
226
15
            while( *p_ch != '\0' && ( *p_ch == ' ' || *p_ch == '\t' ) ) p_ch++;
227
13
            if( strncasecmp( p_ch, "image/jpeg", 10 ) )
228
8
            {
229
8
                msg_Warn( p_demux, "%s, image/jpeg is expected", psz_line );
230
8
                b_jpeg = false;
231
8
            }
232
5
            else
233
5
            {
234
5
                b_jpeg = true;
235
5
            }
236
13
        }
237
47.7k
        else
238
47.7k
        {
239
47.7k
            msg_Dbg( p_demux, "discard MIME header: %s", psz_line );
240
47.7k
        }
241
47.7k
        vlc_obj_free( VLC_OBJECT(p_demux), psz_line );
242
47.7k
        psz_line = GetLine( p_demux, &i_pos );
243
47.7k
    }
244
245
25
    if( NULL == psz_line )
246
6
    {
247
6
        msg_Err( p_demux, "no EOL" );
248
6
        return false;
249
6
    }
250
251
19
    vlc_obj_free( VLC_OBJECT(p_demux), psz_line );
252
253
19
    *p_header_size = i_pos;
254
19
    return b_jpeg;
255
25
}
256
257
static int SendBlock( demux_t *p_demux, int i )
258
3
{
259
3
    demux_sys_t *p_sys = p_demux->p_sys;
260
3
    block_t     *p_block;
261
262
3
    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
3
    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
3
    else
274
3
    {
275
3
        p_block->i_pts = vlc_tick_now();
276
3
    }
277
3
    p_block->i_dts = p_block->i_pts;
278
279
3
    es_out_SetPCR( p_demux->out, p_block->i_pts );
280
3
    es_out_Send( p_demux->out, p_sys->p_es, p_block );
281
282
3
    if( p_sys->b_still )
283
0
        p_sys->i_still_end = vlc_tick_now() + p_sys->i_frame_length;
284
285
3
    return VLC_DEMUXER_SUCCESS;
286
3
}
287
288
/*****************************************************************************
289
 * Open: check file and initializes structures
290
 *****************************************************************************/
291
static int Open( vlc_object_t * p_this )
292
262
{
293
262
    demux_t     *p_demux = (demux_t*)p_this;
294
262
    int         i_size;
295
262
    bool        b_matched = false;
296
297
262
    if( IsMxpeg( p_demux->s ) && !p_demux->obj.force )
298
        // let avformat handle this case
299
0
        return VLC_EGENERIC;
300
301
262
    demux_sys_t *p_sys = vlc_obj_malloc( p_this, sizeof (*p_sys) );
302
262
    if( unlikely(p_sys == NULL) )
303
0
        return VLC_ENOMEM;
304
305
262
    p_demux->p_sys      = p_sys;
306
262
    p_sys->p_es         = NULL;
307
262
    p_sys->i_time       = VLC_TICK_0;
308
262
    p_sys->i_level      = 0;
309
310
262
    p_sys->psz_separator = NULL;
311
262
    p_sys->i_frame_size_estimate = 15 * 1024;
312
313
262
    char *content_type = stream_ContentType( p_demux->s );
314
262
    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
262
    b_matched = CheckMimeHeader( p_demux, &i_size);
339
262
    if( b_matched )
340
3
    {
341
3
        p_demux->pf_demux = MimeDemux;
342
3
        if( vlc_stream_Read( p_demux->s, NULL, i_size ) != i_size )
343
0
            return VLC_EGENERIC;
344
3
    }
345
259
    else if( i_size == 0 )
346
236
    {
347
        /* 0xffd8 identify a JPEG SOI */
348
236
        if( p_sys->p_peek[0] == 0xFF && p_sys->p_peek[1] == 0xD8 )
349
6
        {
350
6
            msg_Dbg( p_demux, "JPEG SOI marker detected" );
351
6
            p_demux->pf_demux = MjpgDemux;
352
6
            p_sys->i_level++;
353
6
        }
354
230
        else
355
230
        {
356
230
            return VLC_EGENERIC;
357
230
        }
358
236
    }
359
23
    else
360
23
    {
361
23
        return VLC_EGENERIC;
362
23
    }
363
364
    /* Frame rate */
365
9
    float f_fps = var_InheritFloat( p_demux, "mjpeg-fps" );
366
367
9
    p_sys->i_still_end = VLC_TICK_INVALID;
368
9
    if( demux_IsPathExtension( p_demux, ".jpeg" ) ||
369
9
        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
9
    else
378
9
        p_sys->b_still = false;
379
9
    p_sys->i_frame_length = f_fps ? vlc_tick_rate_duration(f_fps) : VLC_TICK_INVALID;
380
381
9
    es_format_Init( &p_sys->fmt, VIDEO_ES, VLC_CODEC_MJPG );
382
383
9
    p_sys->fmt.i_id = 0;
384
9
    p_sys->p_es = es_out_Add( p_demux->out, &p_sys->fmt );
385
9
    if( unlikely(p_sys->p_es == NULL) )
386
0
        return VLC_ENOMEM;
387
388
9
    p_demux->pf_control = Control;
389
9
    return VLC_SUCCESS;
390
9
}
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
9
{
399
9
    demux_sys_t *p_sys = p_demux->p_sys;
400
9
    int i;
401
402
9
    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
9
    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
9
    if( p_sys->i_data_peeked < 4 )
416
1
    {
417
1
        msg_Warn( p_demux, "data shortage" );
418
1
        return VLC_DEMUXER_EOF;
419
1
    }
420
8
    i = 3;
421
8
FIND_NEXT_EOI:
422
2.67M
    while( !( p_sys->p_peek[i-1] == 0xFF && p_sys->p_peek[i] == 0xD9 ) )
423
2.67M
    {
424
2.67M
        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
2.67M
        i++;
430
2.67M
        if( i >= p_sys->i_data_peeked )
431
512
        {
432
512
            msg_Dbg( p_demux, "did not find JPEG EOI in %d bytes",
433
512
                     p_sys->i_data_peeked );
434
512
            if( !Peek( p_demux, false ) )
435
5
            {
436
5
                msg_Warn( p_demux, "no more data is available at the moment" );
437
5
                return VLC_DEMUXER_EOF;
438
5
            }
439
512
        }
440
2.67M
    }
441
3
    i++;
442
443
3
    msg_Dbg( p_demux, "JPEG EOI detected at %d", i );
444
3
    p_sys->i_level--;
445
446
3
    if( p_sys->i_level > 0 )
447
0
        goto FIND_NEXT_EOI;
448
3
    return SendBlock( p_demux, i );
449
3
}
450
451
static int MimeDemux( demux_t *p_demux )
452
3
{
453
3
    demux_sys_t *p_sys = p_demux->p_sys;
454
3
    int         i_size, i;
455
456
3
    bool  b_match = CheckMimeHeader( p_demux, &i_size );
457
458
3
    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
3
    else if( i_size < 0 )
464
0
    {
465
0
        return VLC_DEMUXER_EOF;
466
0
    }
467
3
    else
468
3
    {
469
        // No MIME header, assume OK
470
3
        b_match = true;
471
3
    }
472
473
3
    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
3
    i = 0;
480
3
    size_t sep_len = strlen(p_sys->psz_separator);
481
3
    i_size = sep_len + 2;
482
3
    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
3
    for( ;; )
489
954
    {
490
374k
        while( !( p_sys->p_peek[i] == '-' && p_sys->p_peek[i+1] == '-' ) )
491
373k
        {
492
373k
            i++;
493
373k
            i_size++;
494
373k
            if( i_size >= p_sys->i_data_peeked )
495
69
            {
496
69
                msg_Dbg( p_demux, "MIME boundary not found in %d bytes of "
497
69
                         "data", p_sys->i_data_peeked );
498
499
69
                if( !Peek( p_demux, false ) )
500
3
                {
501
3
                    msg_Warn( p_demux, "no more data is available at the "
502
3
                              "moment" );
503
3
                    return VLC_DEMUXER_EOF;
504
3
                }
505
69
            }
506
373k
        }
507
508
        /* peek is located now at "--", check if we have enough data for the
509
         * separator */
510
951
        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
951
        if (!strncmp(p_sys->psz_separator, (char *)(p_sys->p_peek + i + 2), sep_len)
521
951
         || (sep_len > 4
522
951
          && !strncmp(p_sys->psz_separator, "--", 2)
523
906
          && !strncmp(p_sys->psz_separator, (char *)(p_sys->p_peek + i), sep_len)))
524
0
        {
525
0
            break;
526
0
        }
527
528
951
        i++;
529
951
        i_size++;
530
951
    }
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
}