Coverage Report

Created: 2026-09-03 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/mp4/heif.c
Line
Count
Source
1
/*****************************************************************************
2
 * heif.c : ISO/IEC 23008-12 HEIF still picture demuxer
3
 *****************************************************************************
4
 * Copyright (C) 2018 Videolabs, VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
#ifdef HAVE_CONFIG_H
21
# include "config.h"
22
#endif
23
24
/*****************************************************************************
25
 * Preamble
26
 *****************************************************************************/
27
#include <vlc_common.h>
28
#include <vlc_arrays.h>
29
#include <vlc_demux.h>
30
#include <vlc_input.h>
31
#include <vlc_image.h>
32
#include <assert.h>
33
#include <limits.h>
34
#include <stdckdint.h>
35
36
#include "libmp4.h"
37
#include "heif.h"
38
#include "../../packetizer/iso_color_tables.h"
39
40
0
#define MAX_ILOC_INDIRECTION 2 // reference of reference should be enough
41
42
struct heif_private_t
43
{
44
    MP4_Box_t *p_root;
45
    es_out_id_t *id;
46
    vlc_tick_t i_pcr;
47
    vlc_tick_t i_end_display_time;
48
    vlc_tick_t i_image_duration;
49
    bool b_seekpoint_changed;
50
    uint32_t i_seekpoint;
51
    input_title_t *p_title;
52
53
    struct
54
    {
55
        MP4_Box_t *p_infe;
56
        es_format_t fmt;
57
        const MP4_Box_t *p_shared_header;
58
    } current;
59
};
60
61
static MP4_Box_t * NextAtom( MP4_Box_t *p_root,
62
                             vlc_fourcc_t i_type, const char *psz_path,
63
                             MP4_Box_t *p_infe )
64
397
{
65
397
    if( p_infe == NULL )
66
250
        p_infe = MP4_BoxGet( p_root, psz_path );
67
147
    else
68
147
        p_infe = p_infe->p_next;
69
397
    for( ; p_infe; p_infe = p_infe->p_next )
70
148
    {
71
148
        if( p_infe->i_type == i_type )
72
148
            return p_infe;
73
148
    }
74
249
    return NULL;
75
397
}
76
77
static MP4_Box_t * GetAtom( MP4_Box_t *p_root, MP4_Box_t *p_atom,
78
                            vlc_fourcc_t i_type, const char *psz_path,
79
                            bool(*pf_match)(const MP4_Box_t *, void *),
80
                            void *priv )
81
86
{
82
117
    while( (p_atom = NextAtom( p_root, i_type, psz_path, p_atom )) )
83
54
    {
84
54
        if( pf_match( p_atom, priv ) )
85
23
            return p_atom;
86
54
    }
87
63
    return NULL;
88
86
}
89
90
static bool MatchInfeID( const MP4_Box_t *p_infe, void *priv )
91
51
{
92
51
    return BOXDATA(p_infe)->i_item_id == *((uint32_t *) priv);
93
51
}
94
95
static bool MatchPureImage( const MP4_Box_t *p_infe, void *priv )
96
49
{
97
49
    MP4_Box_t *p_root = priv;
98
49
    const MP4_Box_t *p_iref = MP4_BoxGet( p_root, "meta/iref" );
99
49
    if( !p_iref )
100
40
        return true;
101
9
    for( const MP4_Box_t *p_refbox = p_iref->p_first;
102
12
                          p_refbox; p_refbox = p_refbox->p_next )
103
9
    {
104
9
        if( BOXDATA(p_refbox)->i_from_item_id == BOXDATA(p_infe)->i_item_id )
105
6
            return false;
106
9
    }
107
3
    return true;
108
9
}
109
110
static void SeekToPrevImageEnd( struct heif_private_t *p_sys, int i_picture )
111
0
{
112
0
    int i = 0;
113
0
    MP4_Box_t *p_infe = NULL;
114
0
    while( i < i_picture &&
115
0
          (p_infe = NextAtom( p_sys->p_root, ATOM_infe, "meta/iinf/infe", p_infe )) )
116
0
    {
117
0
        if( (BOXDATA(p_infe)->i_flags & 0x01) != 0x00 ||
118
0
                !MatchPureImage( p_infe, p_sys->p_root ) )
119
0
            continue;
120
0
        i++;
121
0
    }
122
0
    p_sys->current.p_infe = p_infe;
123
0
    p_sys->i_end_display_time = 0;
124
0
    p_sys->i_pcr = i * p_sys->i_image_duration;
125
0
}
126
127
static int ControlHEIF( demux_t *p_demux, int i_query, va_list args )
128
0
{
129
0
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
130
131
0
    switch( i_query )
132
0
    {
133
0
        case DEMUX_CAN_SEEK:
134
0
            *va_arg(args, bool *) = true;
135
0
            return VLC_SUCCESS;
136
0
        case DEMUX_GET_TITLE_INFO:
137
0
        {
138
0
            input_title_t ***ppp_title = va_arg( args, input_title_t *** );
139
0
            int *pi_int = va_arg( args, int* );
140
0
            int *pi_title_offset = va_arg( args, int* );
141
0
            int *pi_seekpoint_offset = va_arg( args, int* );
142
143
0
            if( !p_sys->p_title )
144
0
                return VLC_EGENERIC;
145
146
0
            *pi_int = 1;
147
0
            input_title_t **titles =  malloc(sizeof(*titles));
148
0
            if (titles == NULL)
149
0
                return VLC_ENOMEM;
150
151
0
            titles[0] = vlc_input_title_Duplicate(p_sys->p_title);
152
0
            if (titles[0] == NULL)
153
0
            {
154
0
                free(titles);
155
0
                return VLC_ENOMEM;
156
0
            }
157
158
0
            *ppp_title = titles;
159
0
            *pi_title_offset = 0;
160
0
            *pi_seekpoint_offset = 0;
161
0
            return VLC_SUCCESS;
162
0
        }
163
0
        case DEMUX_SET_TITLE:
164
0
        {
165
0
            const int i_title = va_arg( args, int );
166
0
            if( !p_sys->p_title || i_title != 0 )
167
0
                return VLC_EGENERIC;
168
0
            return VLC_SUCCESS;
169
0
        }
170
0
        case DEMUX_GET_SEEKPOINT:
171
0
            *va_arg( args, int * ) = p_sys->i_seekpoint;
172
0
            return VLC_SUCCESS;
173
0
        case DEMUX_SET_SEEKPOINT:
174
0
        {
175
0
            const int i_seekpoint = va_arg( args, int );
176
0
            if( !p_sys->p_title )
177
0
                return VLC_EGENERIC;
178
0
            SeekToPrevImageEnd( p_sys, i_seekpoint );
179
0
            return VLC_SUCCESS;
180
0
        }
181
0
        case DEMUX_TEST_AND_CLEAR_FLAGS:
182
0
        {
183
0
            unsigned *restrict flags = va_arg( args, unsigned * );
184
185
0
            if ((*flags & INPUT_UPDATE_SEEKPOINT) && p_sys->b_seekpoint_changed)
186
0
            {
187
0
                *flags = INPUT_UPDATE_SEEKPOINT;
188
0
                p_sys->b_seekpoint_changed = false;
189
0
            }
190
0
            else
191
0
                *flags = 0;
192
0
            return VLC_SUCCESS;
193
0
        }
194
0
        case DEMUX_GET_LENGTH:
195
0
            *(va_arg( args, vlc_tick_t * )) = p_sys->p_title->i_seekpoint *
196
0
                                              p_sys->i_image_duration;
197
0
            return VLC_SUCCESS;
198
0
        case DEMUX_GET_TIME:
199
0
            *(va_arg(args, vlc_tick_t *)) = p_sys->i_pcr;
200
0
            return VLC_SUCCESS;
201
0
        case DEMUX_SET_TIME:
202
0
        {
203
0
            SeekToPrevImageEnd( p_sys, va_arg(args, vlc_tick_t) /
204
0
                                p_sys->i_image_duration );
205
0
            return VLC_SUCCESS;
206
0
        }
207
0
        case DEMUX_GET_POSITION:
208
0
            if( !p_sys->p_title->i_seekpoint )
209
0
                return VLC_EGENERIC;
210
0
            *(va_arg(args, double *)) = (double) p_sys->i_pcr /
211
0
                    (p_sys->p_title->i_seekpoint * p_sys->i_image_duration);
212
0
            return VLC_SUCCESS;
213
0
        case DEMUX_SET_POSITION:
214
0
        {
215
0
            SeekToPrevImageEnd( p_sys,  va_arg(args, double) * p_sys->p_title->i_seekpoint );
216
0
            return VLC_SUCCESS;
217
0
        }
218
0
        case DEMUX_CAN_PAUSE:
219
0
        case DEMUX_SET_PAUSE_STATE:
220
0
        case DEMUX_CAN_CONTROL_PACE:
221
0
        case DEMUX_GET_PTS_DELAY:
222
0
            return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args );
223
224
0
        default:
225
0
            return VLC_EGENERIC;
226
227
0
    }
228
0
}
229
230
//static int DemuxCompositeImage( demux_t *p_demux )
231
//{
232
233
//}
234
235
static block_t *ReadItemExtents( demux_t *p_demux, uint32_t i_item_id,
236
                                 const MP4_Box_t *p_shared_header, int depth )
237
16
{
238
16
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
239
16
    block_t *p_block = NULL;
240
241
16
    MP4_Box_t *p_iloc = MP4_BoxGet( p_sys->p_root, "meta/iloc" );
242
16
    if( !p_iloc )
243
2
        return p_block;
244
245
14
    for( uint32_t i=0; i<BOXDATA(p_iloc)->i_item_count; i++ )
246
14
    {
247
14
        if( BOXDATA(p_iloc)->p_items[i].i_item_id != i_item_id )
248
0
            continue;
249
250
14
        block_t **pp_append = &p_block;
251
252
        /* Shared prefix data, ex: JPEG */
253
14
        if( p_shared_header )
254
0
        {
255
0
            *pp_append = block_Alloc( p_shared_header->data.p_binary->i_blob );
256
0
            if( *pp_append )
257
0
            {
258
0
                memcpy( (*pp_append)->p_buffer,
259
0
                        p_shared_header->data.p_binary->p_blob,
260
0
                        p_shared_header->data.p_binary->i_blob );
261
0
                pp_append = &((*pp_append)->p_next);
262
0
            }
263
0
        }
264
265
28
        for( uint16_t j=0; j<BOXDATA(p_iloc)->p_items[i].i_extent_count; j++ )
266
14
        {
267
14
            uint64_t i_offset = BOXDATA(p_iloc)->p_items[i].i_base_offset +
268
14
                                BOXDATA(p_iloc)->p_items[i].p_extents[j].i_extent_offset;
269
14
            uint64_t i_length = BOXDATA(p_iloc)->p_items[i].p_extents[j].i_extent_length;
270
271
14
            if( BOXDATA(p_iloc)->p_items[i].i_construction_method < 2 )
272
14
            {
273
                /* Extents are in 0:file, 1:idat */
274
14
                if( BOXDATA(p_iloc)->p_items[i].i_construction_method == 1 )
275
0
                {
276
0
                    MP4_Box_t *idat = MP4_BoxGet( p_sys->p_root, "meta/idat" );
277
0
                    if(!idat)
278
0
                        break;
279
0
                    i_offset += idat->i_pos + mp4_box_headersize(idat);
280
0
                    if( i_length == 0 ) /* Entire container */
281
0
                        i_length = idat->i_size - mp4_box_headersize(idat);
282
0
                }
283
14
                else
284
14
                {
285
14
                    if( i_length == 0 ) /* Entire container == file */
286
12
                    {
287
12
                        if( vlc_stream_GetSize( p_demux->s, &i_length )
288
12
                                == VLC_SUCCESS && i_length > i_offset )
289
0
                            i_length -= i_offset;
290
12
                        else
291
12
                            i_length = 0;
292
12
                    }
293
14
                }
294
14
                if( vlc_stream_Seek( p_demux->s, i_offset ) != VLC_SUCCESS )
295
0
                    break;
296
14
                *pp_append = vlc_stream_Block( p_demux->s, i_length );
297
14
            }
298
            /* Extents are 3:iloc reference */
299
0
            else if( BOXDATA(p_iloc)->p_items[i].i_construction_method == 2 &&
300
0
                     depth <= MAX_ILOC_INDIRECTION )
301
0
            {
302
                /* FIXME ? That's totally untested and really complicated */
303
0
                uint32_t i_extent_index = BOXDATA(p_iloc)->p_items[i].p_extents[j].i_extent_index;
304
0
                if(i_extent_index == 0)
305
0
                    i_extent_index = 1; /* Inferred. Indexes start 1 */
306
0
                const MP4_Box_t *p_iref = MP4_BoxGet( p_sys->p_root, "meta/iref" );
307
0
                if(!p_iref)
308
0
                    break;
309
0
                for( const MP4_Box_t *p_refbox = p_iref->p_first;
310
0
                                      p_refbox; p_refbox = p_refbox->p_next )
311
0
                {
312
0
                    if( p_refbox->i_type != VLC_FOURCC('i','l','o','c') ||
313
0
                        BOXDATA(p_refbox)->i_from_item_id == i_item_id )
314
0
                        continue;
315
316
0
                    for( uint16_t k=0; k< BOXDATA(p_refbox)->i_reference_count; k++ )
317
0
                    {
318
0
                        if( --i_extent_index > 0 )
319
0
                            continue;
320
0
                        if( BOXDATA(p_refbox)->p_references[k].i_to_item_id != i_item_id )
321
0
                        {
322
0
                            *pp_append = ReadItemExtents(p_demux,
323
0
                                            BOXDATA(p_refbox)->p_references[k].i_to_item_id,
324
0
                                            NULL, depth + 1);
325
0
                        }
326
0
                    }
327
328
0
                    break;
329
0
                }
330
0
            }
331
332
14
            while( *pp_append )
333
0
                pp_append = &((*pp_append)->p_next);
334
14
        }
335
14
        break;
336
14
    }
337
338
14
    if( p_block )
339
0
        p_block = block_ChainGather( p_block );
340
341
14
    return p_block;
342
16
}
343
344
static int SetPictureProperties( demux_t *p_demux, uint32_t i_item_id,
345
                                 es_format_t *fmt, const MP4_Box_t **p_header )
346
16
{
347
16
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
348
349
16
    const MP4_Box_t *p_ipma = MP4_BoxGet( p_sys->p_root, "meta/iprp/ipma" );
350
16
    if( !p_ipma )
351
0
        return VLC_EGENERIC;
352
353
    /* Load properties */
354
34
    for( uint32_t i=0; i<BOXDATA(p_ipma)->i_entry_count; i++ )
355
18
    {
356
18
        if( BOXDATA(p_ipma)->p_entries[i].i_item_id != i_item_id )
357
2
            continue;
358
359
104
        for( uint8_t j=0; j<BOXDATA(p_ipma)->p_entries[i].i_association_count; j++ )
360
88
        {
361
88
            if( !BOXDATA(p_ipma)->p_entries[i].p_assocs[j].i_property_index )
362
12
                continue;
363
364
76
            const MP4_Box_t *p_prop = MP4_BoxGetVa( p_sys->p_root, "meta/iprp/ipco/[%u]",
365
76
                BOXDATA(p_ipma)->p_entries[i].p_assocs[j].i_property_index - 1 );
366
76
            if( !p_prop )
367
0
                continue;
368
369
76
            switch( p_prop->i_type )
370
76
            {
371
0
                case ATOM_hvcC:
372
0
                case ATOM_avcC:
373
0
                    if( !fmt->p_extra && p_prop->data.p_binary &&
374
0
                       ((fmt->i_codec == VLC_CODEC_HEVC && p_prop->i_type == ATOM_hvcC) ||
375
0
                        (fmt->i_codec == VLC_CODEC_H264 && p_prop->i_type == ATOM_avcC) ))
376
0
                    {
377
0
                        fmt->p_extra = malloc( p_prop->data.p_binary->i_blob );
378
0
                        if( fmt->p_extra )
379
0
                        {
380
0
                            fmt->i_extra = p_prop->data.p_binary->i_blob;
381
0
                            memcpy( fmt->p_extra, p_prop->data.p_binary->p_blob, fmt->i_extra );
382
0
                        }
383
0
                    }
384
0
                    break;
385
13
                case ATOM_av1C:
386
13
                    if( fmt->i_codec == VLC_CODEC_AV1 && !fmt->i_extra &&
387
10
                        p_prop->data.p_av1C->i_av1C >= 4 )
388
10
                    {
389
10
                        fmt->p_extra = malloc( p_prop->data.p_av1C->i_av1C );
390
10
                        if( fmt->p_extra )
391
10
                        {
392
10
                            fmt->i_extra = p_prop->data.p_av1C->i_av1C ;
393
10
                            memcpy( fmt->p_extra, p_prop->data.p_av1C->p_av1C, fmt->i_extra );
394
10
                        }
395
10
                    }
396
13
                    break;
397
0
                case ATOM_jpeC:
398
0
                    if( fmt->i_codec == VLC_CODEC_JPEG )
399
0
                        *p_header = p_prop;
400
0
                    break;
401
9
                case ATOM_ispe:
402
9
                    if (p_prop->data.p_ispe->i_width <= fmt->video.i_width &&
403
0
                        p_prop->data.p_ispe->i_height <= fmt->video.i_height)
404
0
                    {
405
0
                        fmt->video.i_visible_width = p_prop->data.p_ispe->i_width;
406
0
                        fmt->video.i_visible_height = p_prop->data.p_ispe->i_height;
407
0
                    }
408
9
                    break;
409
0
                case ATOM_clap:
410
0
                    if(p_prop->data.p_clap->i_width + p_prop->data.p_clap->i_x_offset <= fmt->video.i_width &&
411
0
                       p_prop->data.p_clap->i_height + p_prop->data.p_clap->i_y_offset <= fmt->video.i_height)
412
0
                    {
413
0
                        fmt->video.i_visible_width = p_prop->data.p_clap->i_width;
414
0
                        fmt->video.i_visible_height = p_prop->data.p_clap->i_height;
415
0
                        fmt->video.i_x_offset = p_prop->data.p_clap->i_x_offset;
416
0
                        fmt->video.i_y_offset = p_prop->data.p_clap->i_y_offset;
417
0
                    }
418
0
                    break;
419
8
                case ATOM_pasp:
420
8
                    if( p_prop->data.p_pasp->i_horizontal_spacing &&
421
8
                        p_prop->data.p_pasp->i_vertical_spacing )
422
8
                    {
423
8
                        fmt->video.i_sar_num = p_prop->data.p_pasp->i_horizontal_spacing;
424
8
                        fmt->video.i_sar_den = p_prop->data.p_pasp->i_vertical_spacing;
425
8
                    }
426
8
                    break;
427
0
                case ATOM_irot:
428
0
                    switch( p_prop->data.p_irot->i_ccw_degrees % 360 )
429
0
                    {
430
0
                        default:
431
0
                        case 0:   fmt->video.orientation = ORIENT_NORMAL ; break;
432
0
                        case 90:  fmt->video.orientation = ORIENT_ROTATED_270; break;
433
0
                        case 180: fmt->video.orientation = ORIENT_ROTATED_180 ; break;
434
0
                        case 270: fmt->video.orientation = ORIENT_ROTATED_90 ; break;
435
0
                    }
436
0
                    break;
437
0
                case ATOM_colr:
438
0
                    fmt->video.primaries = iso_23001_8_cp_to_vlc_primaries(
439
0
                                            p_prop->data.p_colr->nclc.i_primary_idx );
440
0
                    fmt->video.transfer = iso_23001_8_tc_to_vlc_xfer(
441
0
                                            p_prop->data.p_colr->nclc.i_transfer_function_idx );
442
0
                    fmt->video.space = iso_23001_8_mc_to_vlc_coeffs(
443
0
                                        p_prop->data.p_colr->nclc.i_matrix_idx );
444
0
                    fmt->video.color_range = p_prop->data.p_colr->nclc.i_full_range ?
445
0
                                COLOR_RANGE_FULL : COLOR_RANGE_LIMITED;
446
0
                    break;
447
0
                case ATOM_clli:
448
0
                    fmt->video.lighting.MaxCLL = p_prop->data.p_CoLL->i_maxCLL;
449
0
                    fmt->video.lighting.MaxFALL = p_prop->data.p_CoLL->i_maxFALL;
450
0
                    break;
451
0
                case ATOM_mdcv:
452
0
                    memcpy( fmt->video.mastering.primaries,
453
0
                            p_prop->data.p_SmDm->primaries, sizeof(fmt->video.mastering.primaries) );
454
0
                    memcpy( fmt->video.mastering.white_point,
455
0
                            p_prop->data.p_SmDm->white_point, sizeof(fmt->video.mastering.white_point) );
456
0
                    fmt->video.mastering.max_luminance = p_prop->data.p_SmDm->i_luminanceMax;
457
0
                    fmt->video.mastering.min_luminance = p_prop->data.p_SmDm->i_luminanceMin;
458
0
                    break;
459
76
            }
460
76
        }
461
16
    }
462
463
16
    fmt->video.i_frame_rate      = 1000;
464
16
    fmt->video.i_frame_rate_base = p_sys->i_image_duration / 1000;
465
466
16
    return VLC_SUCCESS;
467
16
}
468
469
static int SetupPicture( demux_t *p_demux, const MP4_Box_t *p_infe,
470
                         es_format_t *fmt, const MP4_Box_t **p_header )
471
22
{
472
22
    fmt->i_codec = 0;
473
22
    *p_header = NULL;
474
475
22
    const uint32_t i_item_id = BOXDATA(p_infe)->i_item_id;
476
22
    const char *psz_mime = BOXDATA(p_infe)->psz_content_type;
477
22
    switch( BOXDATA(p_infe)->item_type )
478
22
    {
479
0
        case VLC_FOURCC('h','v','c','1'):
480
0
            es_format_Init( fmt, VIDEO_ES, VLC_CODEC_HEVC );
481
0
            break;
482
0
        case VLC_FOURCC('a','v','c','1'):
483
0
            es_format_Init( fmt, VIDEO_ES, VLC_CODEC_H264 );
484
0
            break;
485
16
        case ATOM_av01:
486
16
            es_format_Init( fmt, VIDEO_ES, VLC_CODEC_AV1 );
487
16
            break;
488
0
        case VLC_FOURCC('j','p','e','g'):
489
0
            es_format_Init( fmt, VIDEO_ES, VLC_CODEC_JPEG );
490
0
            break;
491
6
        default:
492
6
            if( psz_mime )
493
0
            {
494
0
                if( !strcasecmp( "image/jpeg", psz_mime ) )
495
0
                    es_format_Init( fmt, VIDEO_ES, VLC_CODEC_JPEG );
496
0
                else if( !strcasecmp( "image/avif", psz_mime ) )
497
0
                    es_format_Init( fmt, VIDEO_ES, VLC_CODEC_AV1 );
498
0
            }
499
6
            break;
500
22
    }
501
502
22
    if( fmt->i_codec == 0 )
503
6
        return VLC_EGENERIC;
504
505
16
    return SetPictureProperties( p_demux, i_item_id, fmt, p_header );
506
22
}
507
508
union heif_derivation_data
509
{
510
    struct
511
    {
512
        uint8_t rows_minus_one;
513
        uint8_t columns_minus_one;
514
        uint32_t output_width;
515
        uint32_t output_height;
516
    } ImageGrid;
517
};
518
519
static int ReadDerivationData_Grid( const uint8_t *p_data, size_t i_data,
520
                                    union heif_derivation_data *d )
521
0
{
522
0
    if( i_data < 8 || p_data[0] != 0x00 )
523
0
        return VLC_EGENERIC;
524
525
0
    uint8_t i_fieldlength = ((p_data[1] & 0x01) + 1) << 1;
526
    /* length is either 2 or 4 bytes */
527
0
    d->ImageGrid.rows_minus_one = p_data[2];
528
0
    d->ImageGrid.columns_minus_one = p_data[3];
529
0
    if(i_fieldlength == 2)
530
0
    {
531
0
        d->ImageGrid.output_width = GetWBE(&p_data[4]);
532
0
        d->ImageGrid.output_height = GetWBE(&p_data[6]);
533
0
    }
534
0
    else
535
0
    {
536
0
        if(i_data < 12)
537
0
            return VLC_EGENERIC;
538
0
        d->ImageGrid.output_width = GetDWBE(&p_data[4]);
539
0
        d->ImageGrid.output_height = GetDWBE(&p_data[8]);
540
0
    }
541
0
    return VLC_SUCCESS;
542
0
}
543
544
static int ReadDerivationData( demux_t *p_demux, vlc_fourcc_t type,
545
                               uint32_t i_item_id,
546
                               union heif_derivation_data *d )
547
0
{
548
0
    int i_ret = VLC_EGENERIC;
549
0
    block_t *p_data = ReadItemExtents( p_demux, i_item_id, NULL, 0 );
550
0
    if( p_data )
551
0
    {
552
0
        switch( type )
553
0
        {
554
0
            case VLC_FOURCC('g','r','i','d'):
555
0
                i_ret = ReadDerivationData_Grid( p_data->p_buffer,
556
0
                                                 p_data->i_buffer, d );
557
                /* fallthrough */
558
0
            default:
559
0
                break;
560
0
        }
561
0
        block_Release( p_data );
562
0
    }
563
0
    return i_ret;
564
0
}
565
566
static int LoadGridImage( demux_t *p_demux,
567
                          image_handler_t *handler,
568
                          uint32_t i_pic_item_id,
569
                               uint8_t *p_buffer, size_t i_buffer,
570
                               unsigned tile, unsigned gridcols,
571
                               unsigned imagewidth, unsigned imageheight )
572
0
{
573
0
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
574
575
0
    MP4_Box_t *p_infe = GetAtom( p_sys->p_root, NULL,
576
0
                                 ATOM_infe, "meta/iinf/infe",
577
0
                                 MatchInfeID, &i_pic_item_id );
578
0
    if( !p_infe )
579
0
        return VLC_EGENERIC;
580
581
0
    es_format_t fmt;
582
0
    es_format_Init(&fmt, UNKNOWN_ES, 0);
583
584
0
    const MP4_Box_t *p_shared_header = NULL;
585
0
    if( SetupPicture( p_demux, p_infe, &fmt, &p_shared_header ) != VLC_SUCCESS )
586
0
    {
587
0
        es_format_Clean( &fmt );
588
0
        return VLC_EGENERIC; /* Unsupported picture, goto next */
589
0
    }
590
591
0
    block_t *p_sample = ReadItemExtents( p_demux, i_pic_item_id,
592
0
                                         p_shared_header, 0 );
593
0
    if(!p_sample)
594
0
    {
595
0
        es_format_Clean( &fmt );
596
0
        return VLC_EGENERIC;
597
0
    }
598
599
0
    video_format_t decoded;
600
0
    video_format_Init( &decoded, VLC_CODEC_RGBA );
601
602
0
    fmt.video.i_chroma = fmt.i_codec;
603
604
0
    picture_t *p_picture = image_Read( handler, p_sample, &fmt, &decoded );
605
606
0
    es_format_Clean( &fmt );
607
608
0
    if ( !p_picture )
609
0
        return VLC_EGENERIC;
610
611
0
    const unsigned tilewidth = p_picture->format.i_visible_width;
612
0
    const unsigned tileheight = p_picture->format.i_visible_height;
613
0
    const unsigned offsetpxw = (tile % gridcols) * tilewidth;
614
0
    const unsigned offsetpxh = (tile / gridcols) * tileheight;
615
0
    if (i_buffer < offsetpxh * imagewidth * 4)
616
0
    {
617
0
        picture_Release( p_picture );
618
0
        return VLC_EINVAL;
619
0
    }
620
621
0
    size_t dst_size = i_buffer - offsetpxh * imagewidth * 4;
622
0
    uint8_t *dstline = &p_buffer[offsetpxh * imagewidth * 4];
623
0
    if( offsetpxw <= imagewidth )
624
0
    {
625
0
        const uint8_t *srcline = p_picture->p[0].p_pixels +
626
0
                                 p_picture->format.i_y_offset * p_picture->p[0].i_pitch +
627
0
                                 p_picture->format.i_x_offset * 4;
628
0
        unsigned tocopylines = p_picture->p[0].i_visible_lines;
629
0
        if(offsetpxh <= imageheight  && offsetpxh + tocopylines >= imageheight)
630
0
            tocopylines = imageheight - offsetpxh;
631
0
        for(unsigned i=0; i<tocopylines; i++)
632
0
        {
633
0
            size_t tocopypx = tilewidth;
634
0
            if( offsetpxw + tilewidth > imagewidth )
635
0
                tocopypx = imagewidth - offsetpxw;
636
0
            if (dst_size < (offsetpxw * 4 + tocopypx * 4))
637
0
                break;
638
0
            memcpy( &dstline[offsetpxw * 4], srcline, tocopypx * 4 );
639
0
            dstline += imagewidth * 4;
640
0
            dst_size -= imagewidth * 4;
641
0
            srcline += p_picture->p[0].i_pitch;
642
0
        }
643
0
    }
644
645
0
    picture_Release( p_picture );
646
647
0
    return VLC_SUCCESS;
648
0
}
649
650
static int DerivedImageAssembleGrid( demux_t *p_demux, uint32_t i_grid_item_id,
651
                                     es_format_t *fmt, block_t **pp_block )
652
0
{
653
0
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
654
655
0
    const MP4_Box_t *p_iref = MP4_BoxGet( p_sys->p_root, "meta/iref" );
656
0
    if(!p_iref)
657
0
        return VLC_EGENERIC;
658
659
0
    const MP4_Box_t *p_refbox;
660
0
    for( p_refbox = p_iref->p_first; p_refbox; p_refbox = p_refbox->p_next )
661
0
    {
662
0
        if( p_refbox->i_type == VLC_FOURCC('d','i','m','g') &&
663
0
            BOXDATA(p_refbox)->i_from_item_id == i_grid_item_id )
664
0
            break;
665
0
    }
666
667
0
    if(!p_refbox)
668
0
        return VLC_EGENERIC;
669
670
0
    union heif_derivation_data derivation_data;
671
0
    if( ReadDerivationData( p_demux,
672
0
                            p_sys->current.BOXDATA(p_infe)->item_type,
673
0
                            i_grid_item_id, &derivation_data ) != VLC_SUCCESS )
674
0
        return VLC_EGENERIC;
675
676
0
    msg_Dbg(p_demux,"%ux%upx image %ux%u tiles composition",
677
0
            derivation_data.ImageGrid.output_width,
678
0
            derivation_data.ImageGrid.output_height,
679
0
            derivation_data.ImageGrid.columns_minus_one + 1,
680
0
            derivation_data.ImageGrid.columns_minus_one + 1);
681
682
0
    image_handler_t *handler = image_HandlerCreate( p_demux );
683
0
    if( !handler )
684
0
        return VLC_EGENERIC;
685
686
0
    size_t alloc_size;
687
0
    if( ckd_mul( &alloc_size, derivation_data.ImageGrid.output_width,
688
0
                   derivation_data.ImageGrid.output_height ) ||
689
0
        ckd_mul( &alloc_size, alloc_size, 4 ) )
690
0
    {
691
0
        image_HandlerDelete( handler );
692
0
        return VLC_EGENERIC;
693
0
    }
694
695
0
    block_t *p_block = block_Alloc( alloc_size );
696
0
    if( !p_block )
697
0
    {
698
0
        image_HandlerDelete( handler );
699
0
        return VLC_EGENERIC;
700
0
    }
701
0
    *pp_block = p_block;
702
703
0
    es_format_Init( fmt, VIDEO_ES, VLC_CODEC_RGBA );
704
0
    fmt->video.i_width =
705
0
    fmt->video.i_visible_width = derivation_data.ImageGrid.output_width;
706
0
    fmt->video.i_height =
707
0
    fmt->video.i_visible_height = derivation_data.ImageGrid.output_height;
708
709
0
    unsigned total_tiles = ((unsigned)derivation_data.ImageGrid.rows_minus_one + 1) *
710
0
                           ((unsigned)derivation_data.ImageGrid.columns_minus_one + 1);
711
712
0
    for( uint16_t i=0; i<BOXDATA(p_refbox)->i_reference_count && i < total_tiles; i++ )
713
0
    {
714
0
        msg_Dbg( p_demux, "Loading tile %"PRIu16"/%u", i, total_tiles );
715
0
        LoadGridImage( p_demux, handler,
716
0
                       BOXDATA(p_refbox)->p_references[i].i_to_item_id,
717
0
                       p_block->p_buffer, p_block->i_buffer, i,
718
0
                       (unsigned)derivation_data.ImageGrid.columns_minus_one + 1,
719
0
                       derivation_data.ImageGrid.output_width,
720
0
                       derivation_data.ImageGrid.output_height );
721
0
    }
722
723
0
    SetPictureProperties( p_demux, i_grid_item_id, fmt, NULL );
724
725
0
    image_HandlerDelete( handler );
726
727
0
    return VLC_SUCCESS;
728
0
}
729
730
static int DemuxHEIF( demux_t *p_demux )
731
208
{
732
208
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
733
734
    /* Displaying a picture */
735
208
    if( p_sys->i_end_display_time > 0 )
736
0
    {
737
0
        bool b_empty = true;
738
0
        int ret = es_out_Control( p_demux->out, ES_OUT_DRAIN );
739
0
        if( ret == VLC_SUCCESS )
740
0
            es_out_Control( p_demux->out, ES_OUT_IS_EMPTY, &b_empty );
741
0
        if( !b_empty || vlc_tick_now() <= p_sys->i_end_display_time )
742
0
        {
743
0
            vlc_tick_sleep( VLC_TICK_FROM_MS(40) );
744
0
            return VLC_DEMUXER_SUCCESS;
745
0
        }
746
0
        p_sys->i_end_display_time = 0;
747
0
    }
748
749
    /* Reset prev pic params */
750
208
    p_sys->current.p_shared_header = NULL;
751
752
    /* First or next picture */
753
208
    if( !p_sys->current.p_infe )
754
186
    {
755
186
        MP4_Box_t *p_pitm = MP4_BoxGet( p_sys->p_root, "meta/pitm" );
756
186
        if( !p_pitm )
757
122
            return VLC_DEMUXER_EOF;
758
759
64
        p_sys->current.p_infe = GetAtom( p_sys->p_root, NULL,
760
64
                                         ATOM_infe, "meta/iinf/infe",
761
64
                                         MatchInfeID, &BOXDATA(p_pitm)->i_item_id );
762
64
    }
763
22
    else
764
22
    {
765
22
        p_sys->current.p_infe = GetAtom( p_sys->p_root, p_sys->current.p_infe,
766
22
                                         ATOM_infe, "meta/iinf/infe",
767
22
                                         MatchPureImage, p_sys->p_root );
768
22
    }
769
770
86
    if( !p_sys->current.p_infe )
771
63
        return VLC_DEMUXER_EOF;
772
773
23
    const uint32_t i_current_item_id = p_sys->current.BOXDATA(p_infe)->i_item_id;
774
23
    const MP4_Box_t *p_ipco = MP4_BoxGet( p_sys->p_root, "meta/iprp/ipco" );
775
23
    if( !p_ipco )
776
1
        return VLC_DEMUXER_EOF;
777
778
22
    es_format_t fmt;
779
22
    es_format_Init(&fmt, UNKNOWN_ES, 0);
780
781
22
    block_t *p_block = NULL;
782
22
    if( p_sys->current.BOXDATA(p_infe)->item_type == VLC_FOURCC('g','r','i','d') )
783
0
    {
784
0
        if( DerivedImageAssembleGrid( p_demux, i_current_item_id,
785
0
                                      &fmt, &p_block ) != VLC_SUCCESS )
786
0
        {
787
0
            es_format_Clean( &fmt );
788
0
            return VLC_DEMUXER_SUCCESS;
789
0
        }
790
0
    }
791
22
    else
792
22
    {
793
22
        if( SetupPicture( p_demux, p_sys->current.p_infe,
794
22
                          &fmt, &p_sys->current.p_shared_header ) != VLC_SUCCESS )
795
6
        {
796
6
            es_format_Clean( &fmt );
797
6
            return VLC_DEMUXER_SUCCESS;
798
6
        }
799
800
16
        p_block = ReadItemExtents( p_demux, i_current_item_id,
801
16
                                   p_sys->current.p_shared_header, 0 );
802
16
        if( !p_block )
803
16
        {
804
16
            es_format_Clean( &fmt );
805
16
            return VLC_DEMUXER_SUCCESS; /* Goto next picture */
806
16
        }
807
16
    }
808
809
0
    es_format_Clean( &p_sys->current.fmt );
810
0
    es_format_Copy( &p_sys->current.fmt, &fmt );
811
0
    es_format_Clean( &fmt );
812
0
    if( p_sys->id )
813
0
        es_out_Del( p_demux->out, p_sys->id );
814
0
    p_sys->id = es_out_Add( p_demux->out, &p_sys->current.fmt );
815
816
0
    if( !p_sys->id )
817
0
    {
818
0
        block_Release(p_block);
819
0
        p_sys->current.p_infe = NULL; /* Goto next picture */
820
0
        return VLC_DEMUXER_SUCCESS;
821
0
    }
822
823
0
    if( p_sys->i_pcr == VLC_TICK_INVALID )
824
0
    {
825
0
        p_sys->i_pcr = VLC_TICK_0;
826
0
        es_out_SetPCR( p_demux->out, p_sys->i_pcr );
827
0
    }
828
829
0
    p_block->i_dts = p_block->i_pts = p_sys->i_pcr;
830
0
    p_block->i_length = p_sys->i_image_duration;
831
832
0
    p_block->i_flags |= BLOCK_FLAG_END_OF_SEQUENCE;
833
834
0
    p_sys->i_end_display_time = vlc_tick_now() + p_block->i_length;
835
0
    p_sys->b_seekpoint_changed = true;
836
837
0
    p_sys->i_pcr = p_block->i_dts + p_block->i_length;
838
0
    es_out_Send( p_demux->out, p_sys->id, p_block );
839
0
    es_out_SetPCR( p_demux->out, p_sys->i_pcr );
840
841
0
    return VLC_DEMUXER_SUCCESS;
842
0
}
843
844
int OpenHEIF( vlc_object_t * p_this )
845
401
{
846
401
    demux_t  *p_demux = (demux_t *)p_this;
847
401
    const uint8_t *p_peek;
848
849
401
    if( vlc_stream_Peek( p_demux->s, &p_peek, 12 ) < 12 )
850
8
        return VLC_EGENERIC;
851
852
393
    if( VLC_FOURCC( p_peek[4], p_peek[5], p_peek[6], p_peek[7] ) != ATOM_ftyp )
853
64
        return VLC_EGENERIC;
854
855
329
    switch( VLC_FOURCC( p_peek[8], p_peek[9], p_peek[10], p_peek[11] ) )
856
329
    {
857
1
        case BRAND_mif1:
858
2
        case BRAND_heic:
859
2
        case BRAND_heix:
860
2
        case BRAND_jpeg:
861
2
        case BRAND_avci:
862
186
        case BRAND_avif:
863
186
            break;
864
1
        case BRAND_msf1:
865
1
        case BRAND_hevc:
866
1
        case BRAND_hevx:
867
1
        case BRAND_avcs:
868
2
        case BRAND_avis:
869
143
        default:
870
143
            return VLC_EGENERIC;
871
329
    }
872
873
186
    MP4_Box_t *p_root = MP4_BoxGetRoot( p_demux->s );
874
186
    if( !p_root )
875
0
        return VLC_EGENERIC;
876
877
186
    MP4_BoxDumpStructure( VLC_OBJECT(p_demux), p_root );
878
879
186
    struct heif_private_t *p_sys = calloc( 1, sizeof(*p_sys) );
880
186
    p_demux->p_sys = (void *) p_sys;
881
186
    p_sys->p_root = p_root;
882
186
    p_sys->p_title = vlc_input_title_New();
883
186
    if( !p_sys->p_title )
884
0
    {
885
0
        free( p_sys );
886
0
        return VLC_ENOMEM;
887
0
    }
888
889
186
    p_sys->i_image_duration = vlc_tick_from_sec(var_InheritFloat( p_demux, "heif-image-duration" ));
890
186
    if( p_sys->i_image_duration <= 0 )
891
0
        p_sys->i_image_duration = VLC_TICK_FROM_SEC(HEIF_DEFAULT_DURATION);
892
893
186
    MP4_Box_t *p_infe = NULL;
894
280
    while( (p_infe = NextAtom( p_root, ATOM_infe, "meta/iinf/infe", p_infe )) )
895
94
    {
896
94
        if( (BOXDATA(p_infe)->i_flags & 0x01) != 0x00 ||
897
46
                !MatchPureImage( p_infe, p_root ) )
898
51
            continue;
899
43
        seekpoint_t *s = vlc_seekpoint_New();
900
43
        if( s )
901
43
        {
902
43
            s->i_time_offset = p_sys->p_title->i_seekpoint * p_sys->i_image_duration;
903
43
            if( BOXDATA(p_infe)->psz_item_name )
904
23
                s->psz_name = strdup( BOXDATA(p_infe)->psz_item_name );
905
43
            TAB_APPEND( p_sys->p_title->i_seekpoint, p_sys->p_title->seekpoint, s );
906
43
        }
907
43
    }
908
909
186
    es_format_Init( &p_sys->current.fmt, UNKNOWN_ES, 0 );
910
911
186
    p_demux->pf_demux = DemuxHEIF;
912
186
    p_demux->pf_control = ControlHEIF;
913
914
186
    return VLC_SUCCESS;
915
186
}
916
917
void CloseHEIF ( vlc_object_t * p_this )
918
186
{
919
186
    demux_t *p_demux = (demux_t *)p_this;
920
186
    struct heif_private_t *p_sys = (void *) p_demux->p_sys;
921
186
    MP4_BoxFree( p_sys->p_root );
922
186
    if( p_sys->id )
923
0
        es_out_Del( p_demux->out, p_sys->id );
924
186
    es_format_Clean( &p_sys->current.fmt );
925
186
    vlc_input_title_Delete( p_sys->p_title );
926
186
    free( p_sys );
927
186
}