1 /*****************************************************************************
2 * heif.c : ISO/IEC 23008-12 HEIF still picture demuxer
3 *****************************************************************************
4 * Copyright (C) 2018 Videolabs, VLC authors and VideoLAN
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
27 #include <vlc_common.h>
28 #include <vlc_demux.h>
29 #include <vlc_input.h>
35 #include "../../packetizer/iso_color_tables.h"
42 vlc_tick_t i_end_display_time
;
43 vlc_tick_t i_image_duration
;
44 bool b_seekpoint_changed
;
46 input_title_t
*p_title
;
52 const MP4_Box_t
*p_shared_header
;
56 static MP4_Box_t
* NextAtom( MP4_Box_t
*p_root
,
57 vlc_fourcc_t i_type
, const char *psz_path
,
61 p_infe
= MP4_BoxGet( p_root
, psz_path
);
63 p_infe
= p_infe
->p_next
;
64 for( ; p_infe
; p_infe
= p_infe
->p_next
)
66 if( p_infe
->i_type
== i_type
)
72 static MP4_Box_t
* GetAtom( MP4_Box_t
*p_root
, MP4_Box_t
*p_atom
,
73 vlc_fourcc_t i_type
, const char *psz_path
,
74 bool(*pf_match
)(const MP4_Box_t
*, void *),
77 while( (p_atom
= NextAtom( p_root
, i_type
, psz_path
, p_atom
)) )
79 if( pf_match( p_atom
, priv
) )
85 static bool MatchInfeID( const MP4_Box_t
*p_infe
, void *priv
)
87 return BOXDATA(p_infe
)->i_item_id
== *((uint32_t *) priv
);
90 static bool MatchPureImage( const MP4_Box_t
*p_infe
, void *priv
)
92 MP4_Box_t
*p_root
= priv
;
93 const MP4_Box_t
*p_iref
= MP4_BoxGet( p_root
, "meta/iref" );
96 for( const MP4_Box_t
*p_refbox
= p_iref
->p_first
;
97 p_refbox
; p_refbox
= p_refbox
->p_next
)
99 if( BOXDATA(p_refbox
)->i_from_item_id
== BOXDATA(p_infe
)->i_item_id
)
105 static void SeekToPrevImageEnd( struct heif_private_t
*p_sys
, int i_picture
)
108 MP4_Box_t
*p_infe
= NULL
;
109 while( i
< i_picture
&&
110 (p_infe
= NextAtom( p_sys
->p_root
, ATOM_infe
, "meta/iinf/infe", p_infe
)) )
112 if( (BOXDATA(p_infe
)->i_flags
& 0x01) != 0x00 ||
113 !MatchPureImage( p_infe
, p_sys
->p_root
) )
117 p_sys
->current
.p_infe
= p_infe
;
118 p_sys
->i_end_display_time
= 0;
119 p_sys
->i_pcr
= i
* p_sys
->i_image_duration
;
122 static int ControlHEIF( demux_t
*p_demux
, int i_query
, va_list args
)
124 struct heif_private_t
*p_sys
= (void *) p_demux
->p_sys
;
129 *va_arg(args
, bool *) = true;
131 case DEMUX_GET_TITLE_INFO
:
133 input_title_t
***ppp_title
= va_arg( args
, input_title_t
*** );
134 int *pi_int
= va_arg( args
, int* );
135 int *pi_title_offset
= va_arg( args
, int* );
136 int *pi_seekpoint_offset
= va_arg( args
, int* );
138 if( !p_sys
->p_title
)
142 *ppp_title
= malloc( sizeof( input_title_t
*) );
143 (*ppp_title
)[0] = vlc_input_title_Duplicate( p_sys
->p_title
);
144 *pi_title_offset
= 0;
145 *pi_seekpoint_offset
= 0;
148 case DEMUX_SET_TITLE
:
150 const int i_title
= va_arg( args
, int );
151 if( !p_sys
->p_title
|| i_title
!= 0 )
155 case DEMUX_GET_SEEKPOINT
:
156 *va_arg( args
, int * ) = p_sys
->i_seekpoint
;
158 case DEMUX_SET_SEEKPOINT
:
160 const int i_seekpoint
= va_arg( args
, int );
161 if( !p_sys
->p_title
)
163 SeekToPrevImageEnd( p_sys
, i_seekpoint
);
166 case DEMUX_TEST_AND_CLEAR_FLAGS
:
168 unsigned *restrict flags
= va_arg( args
, unsigned * );
170 if ((*flags
& INPUT_UPDATE_SEEKPOINT
) && p_sys
->b_seekpoint_changed
)
172 *flags
= INPUT_UPDATE_SEEKPOINT
;
173 p_sys
->b_seekpoint_changed
= false;
179 case DEMUX_GET_LENGTH
:
180 *(va_arg( args
, vlc_tick_t
* )) = p_sys
->p_title
->i_seekpoint
*
181 p_sys
->i_image_duration
;
184 *(va_arg(args
, vlc_tick_t
*)) = p_sys
->i_pcr
;
188 SeekToPrevImageEnd( p_sys
, va_arg(args
, vlc_tick_t
) /
189 p_sys
->i_image_duration
);
192 case DEMUX_GET_POSITION
:
193 if( !p_sys
->p_title
->i_seekpoint
)
195 *(va_arg(args
, double *)) = (double) p_sys
->i_pcr
/
196 (p_sys
->p_title
->i_seekpoint
* p_sys
->i_image_duration
);
198 case DEMUX_SET_POSITION
:
200 SeekToPrevImageEnd( p_sys
, va_arg(args
, double) * p_sys
->p_title
->i_seekpoint
);
203 case DEMUX_CAN_PAUSE
:
204 case DEMUX_SET_PAUSE_STATE
:
205 case DEMUX_CAN_CONTROL_PACE
:
206 case DEMUX_GET_PTS_DELAY
:
207 return demux_vaControlHelper( p_demux
->s
, 0, -1, 0, 1, i_query
, args
);
215 //static int DemuxCompositeImage( demux_t *p_demux )
220 static block_t
*ReadItemExtents( demux_t
*p_demux
, uint32_t i_item_id
,
221 const MP4_Box_t
*p_shared_header
)
223 struct heif_private_t
*p_sys
= (void *) p_demux
->p_sys
;
224 block_t
*p_block
= NULL
;
226 MP4_Box_t
*p_iloc
= MP4_BoxGet( p_sys
->p_root
, "meta/iloc" );
230 for( uint32_t i
=0; i
<BOXDATA(p_iloc
)->i_item_count
; i
++ )
232 if( BOXDATA(p_iloc
)->p_items
[i
].i_item_id
!= i_item_id
)
235 block_t
**pp_append
= &p_block
;
237 /* Shared prefix data, ex: JPEG */
238 if( p_shared_header
)
240 *pp_append
= block_Alloc( p_shared_header
->data
.p_binary
->i_blob
);
243 memcpy( (*pp_append
)->p_buffer
,
244 p_shared_header
->data
.p_binary
->p_blob
,
245 p_shared_header
->data
.p_binary
->i_blob
);
246 pp_append
= &((*pp_append
)->p_next
);
250 for( uint16_t j
=0; j
<BOXDATA(p_iloc
)->p_items
[i
].i_extent_count
; j
++ )
252 uint64_t i_offset
= BOXDATA(p_iloc
)->p_items
[i
].i_base_offset
+
253 BOXDATA(p_iloc
)->p_items
[i
].p_extents
[j
].i_extent_offset
;
254 uint64_t i_length
= BOXDATA(p_iloc
)->p_items
[i
].p_extents
[j
].i_extent_length
;
255 if( vlc_stream_Seek( p_demux
->s
, i_offset
) != VLC_SUCCESS
)
257 *pp_append
= vlc_stream_Block( p_demux
->s
, i_length
);
259 pp_append
= &((*pp_append
)->p_next
);
265 p_block
= block_ChainGather( p_block
);
270 static int DemuxHEIF( demux_t
*p_demux
)
272 struct heif_private_t
*p_sys
= (void *) p_demux
->p_sys
;
274 /* Displaying a picture */
275 if( p_sys
->i_end_display_time
> 0 )
278 es_out_Control( p_demux
->out
, ES_OUT_GET_EMPTY
, &b_empty
);
279 if( !b_empty
|| vlc_tick_now() <= p_sys
->i_end_display_time
)
281 vlc_tick_sleep( VLC_TICK_FROM_MS(40) );
282 return VLC_DEMUXER_SUCCESS
;
284 p_sys
->i_end_display_time
= 0;
287 /* Reset prev pic params */
288 p_sys
->current
.p_shared_header
= NULL
;
290 /* First or next picture */
291 if( !p_sys
->current
.p_infe
)
293 MP4_Box_t
*p_pitm
= MP4_BoxGet( p_sys
->p_root
, "meta/pitm" );
295 return VLC_DEMUXER_EOF
;
297 p_sys
->current
.p_infe
= GetAtom( p_sys
->p_root
, NULL
,
298 ATOM_infe
, "meta/iinf/infe",
299 MatchInfeID
, &BOXDATA(p_pitm
)->i_item_id
);
303 p_sys
->current
.p_infe
= GetAtom( p_sys
->p_root
, p_sys
->current
.p_infe
,
304 ATOM_infe
, "meta/iinf/infe",
305 MatchPureImage
, p_sys
->p_root
);
308 if( !p_sys
->current
.p_infe
)
309 return VLC_DEMUXER_EOF
;
311 const uint32_t i_current_item_id
= p_sys
->current
.BOXDATA(p_infe
)->i_item_id
;
312 const MP4_Box_t
*p_ipco
= MP4_BoxGet( p_sys
->p_root
, "meta/iprp/ipco" );
313 const MP4_Box_t
*p_ipma
= MP4_BoxGet( p_sys
->p_root
, "meta/iprp/ipma" );
314 if( !p_ipma
|| !p_ipco
)
315 return VLC_DEMUXER_EOF
;
318 const char *psz_mime
= p_sys
->current
.BOXDATA(p_infe
)->psz_content_type
;
319 switch( p_sys
->current
.BOXDATA(p_infe
)->item_type
)
321 case VLC_FOURCC('h','v','c','1'):
322 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_HEVC
);
324 case VLC_FOURCC('a','v','c','1'):
325 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_H264
);
328 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_AV1
);
330 case VLC_FOURCC('j','p','e','g'):
331 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_JPEG
);
336 if( !strcasecmp( "image/jpeg", psz_mime
) )
338 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_JPEG
);
341 else if( !strcasecmp( "image/avif", psz_mime
) )
343 es_format_Init( &fmt
, VIDEO_ES
, VLC_CODEC_AV1
);
347 return VLC_DEMUXER_SUCCESS
; /* Unsupported picture, goto next */
350 /* Load properties */
351 for( uint32_t i
=0; i
<BOXDATA(p_ipma
)->i_entry_count
; i
++ )
353 if( BOXDATA(p_ipma
)->p_entries
[i
].i_item_id
!= i_current_item_id
)
355 for( uint8_t j
=0; j
<BOXDATA(p_ipma
)->p_entries
[i
].i_association_count
; j
++ )
357 if( !BOXDATA(p_ipma
)->p_entries
[i
].p_assocs
[j
].i_property_index
)
360 const MP4_Box_t
*p_prop
= MP4_BoxGet( p_ipco
, "./[%u]",
361 BOXDATA(p_ipma
)->p_entries
[i
].p_assocs
[j
].i_property_index
- 1 );
365 switch( p_prop
->i_type
)
371 ((fmt
.i_codec
== VLC_CODEC_HEVC
&& p_prop
->i_type
== ATOM_hvcC
) ||
372 (fmt
.i_codec
== VLC_CODEC_H264
&& p_prop
->i_type
== ATOM_avcC
) ||
373 (fmt
.i_codec
== VLC_CODEC_AV1
&& p_prop
->i_type
== ATOM_av1C
)) )
375 fmt
.p_extra
= malloc( p_prop
->data
.p_binary
->i_blob
);
378 fmt
.i_extra
= p_prop
->data
.p_binary
->i_blob
;
379 memcpy( fmt
.p_extra
, p_prop
->data
.p_binary
->p_blob
, fmt
.i_extra
);
384 if( fmt
.i_codec
== VLC_CODEC_JPEG
)
385 p_sys
->current
.p_shared_header
= p_prop
;
388 fmt
.video
.i_visible_width
= p_prop
->data
.p_ispe
->i_width
;
389 fmt
.video
.i_visible_height
= p_prop
->data
.p_ispe
->i_height
;
392 if( p_prop
->data
.p_pasp
->i_horizontal_spacing
&&
393 p_prop
->data
.p_pasp
->i_vertical_spacing
)
395 fmt
.video
.i_sar_num
= p_prop
->data
.p_pasp
->i_horizontal_spacing
;
396 fmt
.video
.i_sar_den
= p_prop
->data
.p_pasp
->i_vertical_spacing
;
400 switch( p_prop
->data
.p_irot
->i_ccw_degrees
% 360 )
403 case 0: fmt
.video
.orientation
= ORIENT_NORMAL
; break;
404 case 90: fmt
.video
.orientation
= ORIENT_ROTATED_90
; break;
405 case 180: fmt
.video
.orientation
= ORIENT_ROTATED_180
; break;
406 case 270: fmt
.video
.orientation
= ORIENT_ROTATED_270
; break;
410 fmt
.video
.primaries
= iso_23001_8_cp_to_vlc_primaries(
411 p_prop
->data
.p_colr
->nclc
.i_primary_idx
);
412 fmt
.video
.transfer
= iso_23001_8_tc_to_vlc_xfer(
413 p_prop
->data
.p_colr
->nclc
.i_transfer_function_idx
);
414 fmt
.video
.space
= iso_23001_8_mc_to_vlc_coeffs(
415 p_prop
->data
.p_colr
->nclc
.i_matrix_idx
);
416 fmt
.video
.b_color_range_full
= p_prop
->data
.p_colr
->nclc
.i_full_range
;
419 fmt
.video
.lighting
.MaxCLL
= p_prop
->data
.p_CoLL
->i_maxCLL
;
420 fmt
.video
.lighting
.MaxFALL
= p_prop
->data
.p_CoLL
->i_maxFALL
;
423 memcpy( fmt
.video
.mastering
.primaries
,
424 p_prop
->data
.p_SmDm
->primaries
, sizeof(uint16_t) * 6 );
425 memcpy( fmt
.video
.mastering
.white_point
,
426 p_prop
->data
.p_SmDm
->white_point
, sizeof(uint16_t) * 2 );
427 fmt
.video
.mastering
.max_luminance
= p_prop
->data
.p_SmDm
->i_luminanceMax
;
428 fmt
.video
.mastering
.min_luminance
= p_prop
->data
.p_SmDm
->i_luminanceMin
;
434 fmt
.video
.i_frame_rate
= 1000;
435 fmt
.video
.i_frame_rate_base
= p_sys
->i_image_duration
/ 1000;
437 es_format_Clean( &p_sys
->current
.fmt
);
438 es_format_Copy( &p_sys
->current
.fmt
, &fmt
);
439 es_format_Clean( &fmt
);
441 p_sys
->id
= es_out_Add( p_demux
->out
, &p_sys
->current
.fmt
);
443 es_out_Control( p_demux
->out
, ES_OUT_RESTART_ES
, p_sys
->id
);
447 p_sys
->current
.p_infe
= NULL
; /* Goto next picture */
448 return VLC_DEMUXER_SUCCESS
;
451 block_t
*p_block
= ReadItemExtents( p_demux
, i_current_item_id
,
452 p_sys
->current
.p_shared_header
);
454 return VLC_DEMUXER_SUCCESS
; /* Goto next picture */
456 if( p_sys
->i_pcr
== VLC_TICK_INVALID
)
458 p_sys
->i_pcr
= VLC_TICK_0
;
459 es_out_SetPCR( p_demux
->out
, p_sys
->i_pcr
);
462 p_block
->i_dts
= p_block
->i_pts
= p_sys
->i_pcr
;
463 p_block
->i_length
= p_sys
->i_image_duration
;
465 p_block
->i_flags
|= BLOCK_FLAG_END_OF_SEQUENCE
;
467 p_sys
->i_end_display_time
= vlc_tick_now() + p_block
->i_length
;
468 p_sys
->b_seekpoint_changed
= true;
470 p_sys
->i_pcr
= p_block
->i_dts
+ p_block
->i_length
;
471 es_out_Send( p_demux
->out
, p_sys
->id
, p_block
);
472 es_out_SetPCR( p_demux
->out
, p_sys
->i_pcr
);
474 return VLC_DEMUXER_SUCCESS
;
477 int OpenHEIF( vlc_object_t
* p_this
)
479 demux_t
*p_demux
= (demux_t
*)p_this
;
480 const uint8_t *p_peek
;
482 if( vlc_stream_Peek( p_demux
->s
, &p_peek
, 12 ) < 12 )
485 if( VLC_FOURCC( p_peek
[4], p_peek
[5], p_peek
[6], p_peek
[7] ) != ATOM_ftyp
)
488 switch( VLC_FOURCC( p_peek
[8], p_peek
[9], p_peek
[10], p_peek
[11] ) )
506 MP4_Box_t
*p_root
= MP4_BoxGetRoot( p_demux
->s
);
510 MP4_BoxDumpStructure( p_demux
->s
, p_root
);
512 struct heif_private_t
*p_sys
= calloc( 1, sizeof(*p_sys
) );
513 p_demux
->p_sys
= (void *) p_sys
;
514 p_sys
->p_root
= p_root
;
515 p_sys
->p_title
= vlc_input_title_New();
516 if( !p_sys
->p_title
)
522 p_sys
->i_image_duration
= vlc_tick_from_sec(var_InheritFloat( p_demux
, "heif-image-duration" ));
523 if( p_sys
->i_image_duration
<= 0 )
524 p_sys
->i_image_duration
= VLC_TICK_FROM_SEC(HEIF_DEFAULT_DURATION
);
526 MP4_Box_t
*p_infe
= NULL
;
527 while( (p_infe
= NextAtom( p_root
, ATOM_infe
, "meta/iinf/infe", p_infe
)) )
529 if( (BOXDATA(p_infe
)->i_flags
& 0x01) != 0x00 ||
530 !MatchPureImage( p_infe
, p_root
) )
532 seekpoint_t
*s
= vlc_seekpoint_New();
535 s
->i_time_offset
= p_sys
->p_title
->i_seekpoint
* p_sys
->i_image_duration
;
536 if( BOXDATA(p_infe
)->psz_item_name
)
537 s
->psz_name
= strdup( BOXDATA(p_infe
)->psz_item_name
);
538 TAB_APPEND( p_sys
->p_title
->i_seekpoint
, p_sys
->p_title
->seekpoint
, s
);
542 es_format_Init( &p_sys
->current
.fmt
, UNKNOWN_ES
, 0 );
544 p_demux
->pf_demux
= DemuxHEIF
;
545 p_demux
->pf_control
= ControlHEIF
;
550 void CloseHEIF ( vlc_object_t
* p_this
)
552 demux_t
*p_demux
= (demux_t
*)p_this
;
553 struct heif_private_t
*p_sys
= (void *) p_demux
->p_sys
;
554 MP4_BoxFree( p_sys
->p_root
);
556 es_out_Del( p_demux
->out
, p_sys
->id
);
557 es_format_Clean( &p_sys
->current
.fmt
);
558 vlc_input_title_Delete( p_sys
->p_title
);