You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2280 lines
76 KiB
2280 lines
76 KiB
/*****************************************************************************
|
|
* videotoolbox.m: Video Toolbox decoder
|
|
*****************************************************************************
|
|
* Copyright © 2014-2015 VideoLabs SAS
|
|
*
|
|
* Authors: Felix Paul Kühne <fkuehne # videolan.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#pragma mark preamble
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# import "config.h"
|
|
#endif
|
|
|
|
#import <vlc_common.h>
|
|
#import <vlc_plugin.h>
|
|
#import <vlc_codec.h>
|
|
#import "hxxx_helper.h"
|
|
#import <vlc_bits.h>
|
|
#import <vlc_boxes.h>
|
|
#import "vt_utils.h"
|
|
#import "../packetizer/h264_nal.h"
|
|
#import "../packetizer/h264_slice.h"
|
|
#import "../packetizer/hxxx_nal.h"
|
|
#import "../packetizer/hxxx_sei.h"
|
|
|
|
#import <VideoToolbox/VideoToolbox.h>
|
|
#import <VideoToolbox/VTErrors.h>
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <TargetConditionals.h>
|
|
|
|
#import <sys/types.h>
|
|
#import <sys/sysctl.h>
|
|
#import <mach/machine.h>
|
|
|
|
#define ALIGN_16( x ) ( ( ( x ) + 15 ) / 16 * 16 )
|
|
#define VT_RESTART_MAX 1
|
|
|
|
#if TARGET_OS_IPHONE
|
|
#import <UIKit/UIKit.h>
|
|
|
|
/* support iOS SDKs < v9.1 */
|
|
#ifndef CPUFAMILY_ARM_TWISTER
|
|
#define CPUFAMILY_ARM_TWISTER 0x92fb37c8
|
|
#endif
|
|
|
|
#endif
|
|
|
|
// Define stuff for older SDKs
|
|
#if (TARGET_OS_OSX && MAC_OS_X_VERSION_MAX_ALLOWED < 101100) || \
|
|
(TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < 90000) || \
|
|
(TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED < 90000)
|
|
enum { kCMVideoCodecType_HEVC = 'hvc1' };
|
|
#endif
|
|
|
|
#if (!TARGET_OS_OSX || MAC_OS_X_VERSION_MAX_ALLOWED < 1090)
|
|
const CFStringRef kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder = CFSTR("EnableHardwareAcceleratedVideoDecoder");
|
|
const CFStringRef kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder = CFSTR("RequireHardwareAcceleratedVideoDecoder");
|
|
#endif
|
|
|
|
#pragma mark - module descriptor
|
|
|
|
static int OpenDecoder(vlc_object_t *);
|
|
static void CloseDecoder(vlc_object_t *);
|
|
|
|
#define VT_ENABLE_TEXT N_("Enable hardware acceleration")
|
|
#define VT_REQUIRE_HW_DEC N_("Use Hardware decoders only")
|
|
#define VT_FORCE_CVPX_CHROMA "Force the VT decoder CVPX chroma"
|
|
#define VT_FORCE_CVPX_CHROMA_LONG "Values can be 'BGRA', 'y420', '420f', '420v', '2vuy'. \
|
|
By Default, the best chroma is choosen by the VT decoder."
|
|
|
|
vlc_module_begin()
|
|
set_category(CAT_INPUT)
|
|
set_subcategory(SUBCAT_INPUT_VCODEC)
|
|
set_description(N_("VideoToolbox video decoder"))
|
|
set_capability("video decoder",800)
|
|
set_callbacks(OpenDecoder, CloseDecoder)
|
|
|
|
add_obsolete_bool("videotoolbox-temporal-deinterlacing")
|
|
add_bool("videotoolbox", true, VT_ENABLE_TEXT, NULL, false)
|
|
add_bool("videotoolbox-hw-decoder-only", true, VT_REQUIRE_HW_DEC, VT_REQUIRE_HW_DEC, false)
|
|
add_string("videotoolbox-cvpx-chroma", "", VT_FORCE_CVPX_CHROMA, VT_FORCE_CVPX_CHROMA_LONG, true);
|
|
vlc_module_end()
|
|
|
|
#pragma mark - local prototypes
|
|
|
|
enum vtsession_status
|
|
{
|
|
VTSESSION_STATUS_OK,
|
|
VTSESSION_STATUS_RESTART,
|
|
VTSESSION_STATUS_RESTART_CHROMA,
|
|
VTSESSION_STATUS_ABORT,
|
|
};
|
|
|
|
static int ConfigureVout(decoder_t *);
|
|
static CFMutableDictionaryRef ESDSExtradataInfoCreate(decoder_t *, uint8_t *, uint32_t);
|
|
static CFMutableDictionaryRef ExtradataInfoCreate(CFStringRef, void *, size_t);
|
|
static CFMutableDictionaryRef CreateSessionDescriptionFormat(decoder_t *, unsigned, unsigned);
|
|
static int HandleVTStatus(decoder_t *, OSStatus, enum vtsession_status *);
|
|
static int DecodeBlock(decoder_t *, block_t *);
|
|
static void RequestFlush(decoder_t *);
|
|
static void Drain(decoder_t *p_dec, bool flush);
|
|
static void DecoderCallback(void *, void *, OSStatus, VTDecodeInfoFlags,
|
|
CVPixelBufferRef, CMTime, CMTime);
|
|
static BOOL deviceSupportsHEVC();
|
|
static BOOL deviceSupportsAdvancedProfiles();
|
|
static BOOL deviceSupportsAdvancedLevels();
|
|
|
|
typedef struct frame_info_t frame_info_t;
|
|
|
|
struct frame_info_t
|
|
{
|
|
picture_t *p_picture;
|
|
int i_poc;
|
|
int i_foc;
|
|
bool b_forced;
|
|
bool b_flush;
|
|
bool b_keyframe;
|
|
bool b_field;
|
|
bool b_progressive;
|
|
bool b_top_field_first;
|
|
uint8_t i_num_ts;
|
|
unsigned i_length;
|
|
frame_info_t *p_next;
|
|
};
|
|
|
|
#pragma mark - decoder structure
|
|
|
|
#define H264_MAX_DPB 16
|
|
#define VT_MAX_SEI_COUNT 16
|
|
|
|
typedef struct decoder_sys_t
|
|
{
|
|
CMVideoCodecType codec;
|
|
struct hxxx_helper hh;
|
|
|
|
/* Codec specific callbacks */
|
|
bool (*pf_codec_init)(decoder_t *);
|
|
void (*pf_codec_clean)(decoder_t *);
|
|
bool (*pf_codec_supported)(decoder_t *);
|
|
bool (*pf_late_start)(decoder_t *);
|
|
block_t* (*pf_process_block)(decoder_t *,
|
|
block_t *, bool *);
|
|
bool (*pf_need_restart)(decoder_t *,
|
|
VTDecompressionSessionRef);
|
|
bool (*pf_configure_vout)(decoder_t *);
|
|
CFMutableDictionaryRef (*pf_get_extradata)(decoder_t *);
|
|
bool (*pf_fill_reorder_info)(decoder_t *, const block_t *,
|
|
frame_info_t *);
|
|
/* !Codec specific callbacks */
|
|
|
|
bool b_vt_feed;
|
|
bool b_vt_flush;
|
|
bool b_vt_need_keyframe;
|
|
VTDecompressionSessionRef session;
|
|
CMVideoFormatDescriptionRef videoFormatDescription;
|
|
|
|
vlc_mutex_t lock;
|
|
frame_info_t *p_pic_reorder;
|
|
uint8_t i_pic_reorder;
|
|
uint8_t i_pic_reorder_max;
|
|
bool b_invalid_pic_reorder_max;
|
|
bool b_poc_based_reorder;
|
|
|
|
bool b_format_propagated;
|
|
|
|
enum vtsession_status vtsession_status;
|
|
unsigned i_restart_count;
|
|
|
|
int i_cvpx_format;
|
|
bool b_cvpx_format_forced;
|
|
|
|
h264_poc_context_t h264_pocctx;
|
|
hevc_poc_ctx_t hevc_pocctx;
|
|
bool b_drop_blocks;
|
|
date_t pts;
|
|
|
|
struct pic_holder *pic_holder;
|
|
} decoder_sys_t;
|
|
|
|
struct pic_holder
|
|
{
|
|
bool closed;
|
|
vlc_mutex_t lock;
|
|
vlc_cond_t wait;
|
|
uint8_t nb_field_out;
|
|
uint8_t field_reorder_max;
|
|
};
|
|
|
|
static void pic_holder_update_reorder_max(struct pic_holder *, uint8_t, uint8_t);
|
|
|
|
#pragma mark - start & stop
|
|
|
|
/* Codec Specific */
|
|
|
|
static void HXXXGetBestChroma(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
if (p_sys->i_cvpx_format != 0 || p_sys->b_cvpx_format_forced)
|
|
return;
|
|
|
|
uint8_t i_chroma_format, i_depth_luma, i_depth_chroma;
|
|
if (hxxx_helper_get_chroma_chroma(&p_sys->hh, &i_chroma_format, &i_depth_luma,
|
|
&i_depth_chroma) != VLC_SUCCESS)
|
|
return;
|
|
|
|
if (i_chroma_format == 1 /* YUV 4:2:0 */)
|
|
{
|
|
if (i_depth_luma == 8 && i_depth_chroma == 8)
|
|
p_sys->i_cvpx_format = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
|
#if !TARGET_OS_IPHONE
|
|
/* Not for iOS since there is no 10bits textures with the old iOS
|
|
* openGLES version, and therefore no P010 shaders */
|
|
else if (i_depth_luma == 10 && i_depth_chroma == 10 && deviceSupportsHEVC())
|
|
p_sys->i_cvpx_format = 'x420'; /* kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange */
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void GetxPSH264(uint8_t i_pps_id, void *priv,
|
|
const h264_sequence_parameter_set_t **pp_sps,
|
|
const h264_picture_parameter_set_t **pp_pps)
|
|
{
|
|
decoder_sys_t *p_sys = priv;
|
|
|
|
*pp_pps = p_sys->hh.h264.pps_list[i_pps_id].h264_pps;
|
|
if(*pp_pps == NULL)
|
|
*pp_sps = NULL;
|
|
else
|
|
*pp_sps = p_sys->hh.h264.sps_list[(*pp_pps)->i_sps_id].h264_sps;
|
|
}
|
|
|
|
struct sei_callback_h264_s
|
|
{
|
|
uint8_t i_pic_struct;
|
|
const h264_sequence_parameter_set_t *p_sps;
|
|
};
|
|
|
|
static bool ParseH264SEI(const hxxx_sei_data_t *p_sei_data, void *priv)
|
|
{
|
|
if(p_sei_data->i_type == HXXX_SEI_PIC_TIMING)
|
|
{
|
|
struct sei_callback_h264_s *s = priv;
|
|
if(s->p_sps && s->p_sps->vui.b_valid)
|
|
{
|
|
if(s->p_sps->vui.b_hrd_parameters_present_flag)
|
|
{
|
|
bs_read(p_sei_data->p_bs, s->p_sps->vui.i_cpb_removal_delay_length_minus1 + 1);
|
|
bs_read(p_sei_data->p_bs, s->p_sps->vui.i_dpb_output_delay_length_minus1 + 1);
|
|
}
|
|
|
|
if(s->p_sps->vui.b_pic_struct_present_flag)
|
|
s->i_pic_struct = bs_read( p_sei_data->p_bs, 4);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool FillReorderInfoH264(decoder_t *p_dec, const block_t *p_block,
|
|
frame_info_t *p_info)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
hxxx_iterator_ctx_t itctx;
|
|
hxxx_iterator_init(&itctx, p_block->p_buffer, p_block->i_buffer,
|
|
p_sys->hh.i_nal_length_size);
|
|
|
|
const uint8_t *p_nal; size_t i_nal;
|
|
struct
|
|
{
|
|
const uint8_t *p_nal;
|
|
size_t i_nal;
|
|
} sei_array[VT_MAX_SEI_COUNT];
|
|
size_t i_sei_count = 0;
|
|
while(hxxx_iterate_next(&itctx, &p_nal, &i_nal))
|
|
{
|
|
if(i_nal < 2)
|
|
continue;
|
|
|
|
const enum h264_nal_unit_type_e i_nal_type = p_nal[0] & 0x1F;
|
|
|
|
if (i_nal_type <= H264_NAL_SLICE_IDR && i_nal_type != H264_NAL_UNKNOWN)
|
|
{
|
|
h264_slice_t slice;
|
|
if(!h264_decode_slice(p_nal, i_nal, GetxPSH264, p_sys, &slice))
|
|
return false;
|
|
|
|
const h264_sequence_parameter_set_t *p_sps;
|
|
const h264_picture_parameter_set_t *p_pps;
|
|
GetxPSH264(slice.i_pic_parameter_set_id, p_sys, &p_sps, &p_pps);
|
|
if(p_sps)
|
|
{
|
|
int bFOC;
|
|
h264_compute_poc(p_sps, &slice, &p_sys->h264_pocctx,
|
|
&p_info->i_poc, &p_info->i_foc, &bFOC);
|
|
|
|
p_info->b_keyframe = slice.type == H264_SLICE_TYPE_I;
|
|
p_info->b_flush = (slice.type == H264_SLICE_TYPE_I) || slice.has_mmco5;
|
|
p_info->b_field = slice.i_field_pic_flag;
|
|
p_info->b_progressive = !p_sps->mb_adaptive_frame_field_flag &&
|
|
!slice.i_field_pic_flag;
|
|
|
|
struct sei_callback_h264_s sei;
|
|
sei.p_sps = p_sps;
|
|
sei.i_pic_struct = UINT8_MAX;
|
|
|
|
for(size_t i=0; i<i_sei_count; i++)
|
|
HxxxParseSEI(sei_array[i].p_nal, sei_array[i].i_nal, 1,
|
|
ParseH264SEI, &sei);
|
|
|
|
p_info->i_num_ts = h264_get_num_ts(p_sps, &slice, sei.i_pic_struct,
|
|
p_info->i_foc, bFOC);
|
|
|
|
if(!p_info->b_progressive)
|
|
p_info->b_top_field_first = (sei.i_pic_struct % 2 == 1);
|
|
|
|
/* Set frame rate for timings in case of missing rate */
|
|
if( (!p_dec->fmt_in.video.i_frame_rate_base ||
|
|
!p_dec->fmt_in.video.i_frame_rate) &&
|
|
p_sps->vui.i_time_scale && p_sps->vui.i_num_units_in_tick )
|
|
{
|
|
date_Change( &p_sys->pts, p_sps->vui.i_time_scale,
|
|
p_sps->vui.i_num_units_in_tick );
|
|
}
|
|
|
|
if(!p_sys->b_invalid_pic_reorder_max && i_nal_type == H264_NAL_SLICE_IDR)
|
|
{
|
|
unsigned dummy;
|
|
uint8_t i_reorder;
|
|
h264_get_dpb_values(p_sps, &i_reorder, &dummy);
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->i_pic_reorder_max = i_reorder;
|
|
pic_holder_update_reorder_max(p_sys->pic_holder,
|
|
p_sys->i_pic_reorder_max,
|
|
p_info->i_num_ts);
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
|
|
}
|
|
|
|
return true; /* No need to parse further NAL */
|
|
}
|
|
else if(i_nal_type == H264_NAL_SEI)
|
|
{
|
|
if(i_sei_count < VT_MAX_SEI_COUNT)
|
|
{
|
|
sei_array[i_sei_count].p_nal = p_nal;
|
|
sei_array[i_sei_count++].i_nal = i_nal;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static block_t *ProcessBlockH264(decoder_t *p_dec, block_t *p_block, bool *pb_config_changed)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
return p_sys->hh.pf_process_block(&p_sys->hh, p_block, pb_config_changed);
|
|
}
|
|
|
|
|
|
static bool InitH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
h264_poc_context_init(&p_sys->h264_pocctx);
|
|
hxxx_helper_init(&p_sys->hh, VLC_OBJECT(p_dec),
|
|
p_dec->fmt_in.i_codec, true);
|
|
return hxxx_helper_set_extra(&p_sys->hh, p_dec->fmt_in.p_extra,
|
|
p_dec->fmt_in.i_extra) == VLC_SUCCESS;
|
|
}
|
|
|
|
static void CleanH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
hxxx_helper_clean(&p_sys->hh);
|
|
}
|
|
|
|
static CFMutableDictionaryRef GetDecoderExtradataH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
CFMutableDictionaryRef extradata = nil;
|
|
if (p_dec->fmt_in.i_extra && p_sys->hh.b_is_xvcC)
|
|
{
|
|
/* copy DecoderConfiguration */
|
|
extradata = ExtradataInfoCreate(CFSTR("avcC"),
|
|
p_dec->fmt_in.p_extra,
|
|
p_dec->fmt_in.i_extra);
|
|
}
|
|
else if (p_sys->hh.h264.i_pps_count && p_sys->hh.h264.i_sps_count)
|
|
{
|
|
/* build DecoderConfiguration from gathered */
|
|
block_t *p_avcC = h264_helper_get_avcc_config(&p_sys->hh);
|
|
if (p_avcC)
|
|
{
|
|
extradata = ExtradataInfoCreate(CFSTR("avcC"),
|
|
p_avcC->p_buffer,
|
|
p_avcC->i_buffer);
|
|
block_Release(p_avcC);
|
|
}
|
|
}
|
|
return extradata;
|
|
}
|
|
|
|
static bool CodecSupportedH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
uint8_t i_profile, i_level;
|
|
if (hxxx_helper_get_current_profile_level(&p_sys->hh, &i_profile, &i_level))
|
|
return true;
|
|
|
|
switch (i_profile) {
|
|
case PROFILE_H264_BASELINE:
|
|
case PROFILE_H264_MAIN:
|
|
case PROFILE_H264_HIGH:
|
|
break;
|
|
|
|
case PROFILE_H264_HIGH_10:
|
|
{
|
|
if (deviceSupportsAdvancedProfiles())
|
|
break;
|
|
else
|
|
{
|
|
msg_Err(p_dec, "current device doesn't support H264 10bits");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
default:
|
|
{
|
|
msg_Warn(p_dec, "unknown H264 profile %" PRIx8, i_profile);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* A level higher than 5.2 was not tested, so don't dare to try to decode
|
|
* it. On SoC A8, 4.2 is the highest specified profile. on Twister, we can
|
|
* do up to 5.2 */
|
|
if (i_level > 52 || (i_level > 42 && !deviceSupportsAdvancedLevels()))
|
|
{
|
|
msg_Err(p_dec, "current device doesn't support this H264 level: %"
|
|
PRIx8, i_level);
|
|
return false;
|
|
}
|
|
|
|
HXXXGetBestChroma(p_dec);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LateStartH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
return (p_dec->fmt_in.i_extra == 0 &&
|
|
(!p_sys->hh.h264.i_pps_count || !p_sys->hh.h264.i_sps_count) );
|
|
}
|
|
|
|
static bool ConfigureVoutH264(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
if(p_dec->fmt_in.video.primaries == COLOR_PRIMARIES_UNDEF)
|
|
{
|
|
video_color_primaries_t primaries;
|
|
video_transfer_func_t transfer;
|
|
video_color_space_t colorspace;
|
|
bool full_range;
|
|
if (hxxx_helper_get_colorimetry(&p_sys->hh,
|
|
&primaries,
|
|
&transfer,
|
|
&colorspace,
|
|
&full_range) == VLC_SUCCESS)
|
|
{
|
|
p_dec->fmt_out.video.primaries = primaries;
|
|
p_dec->fmt_out.video.transfer = transfer;
|
|
p_dec->fmt_out.video.space = colorspace;
|
|
p_dec->fmt_out.video.b_color_range_full = full_range;
|
|
}
|
|
}
|
|
|
|
if (!p_dec->fmt_in.video.i_visible_width || !p_dec->fmt_in.video.i_visible_height)
|
|
{
|
|
unsigned i_width, i_height, i_vis_width, i_vis_height;
|
|
if(VLC_SUCCESS ==
|
|
hxxx_helper_get_current_picture_size(&p_sys->hh,
|
|
&i_width, &i_height,
|
|
&i_vis_width, &i_vis_height))
|
|
{
|
|
p_dec->fmt_out.video.i_visible_width = i_vis_width;
|
|
p_dec->fmt_out.video.i_width = ALIGN_16( i_vis_width );
|
|
p_dec->fmt_out.video.i_visible_height = i_vis_height;
|
|
p_dec->fmt_out.video.i_height = ALIGN_16( i_vis_height );
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
if(!p_dec->fmt_in.video.i_sar_num || !p_dec->fmt_in.video.i_sar_den)
|
|
{
|
|
int i_sar_num, i_sar_den;
|
|
if (VLC_SUCCESS ==
|
|
hxxx_helper_get_current_sar(&p_sys->hh, &i_sar_num, &i_sar_den))
|
|
{
|
|
p_dec->fmt_out.video.i_sar_num = i_sar_num;
|
|
p_dec->fmt_out.video.i_sar_den = i_sar_den;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool VideoToolboxNeedsToRestartH264(decoder_t *p_dec,
|
|
VTDecompressionSessionRef session)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
const struct hxxx_helper *hh = &p_sys->hh;
|
|
|
|
unsigned w, h, vw, vh;
|
|
int sarn, sard;
|
|
|
|
if (hxxx_helper_get_current_picture_size(hh, &w, &h, &vw, &vh) != VLC_SUCCESS)
|
|
return true;
|
|
|
|
if (hxxx_helper_get_current_sar(hh, &sarn, &sard) != VLC_SUCCESS)
|
|
return true;
|
|
|
|
bool b_ret = true;
|
|
|
|
CFMutableDictionaryRef decoderConfiguration =
|
|
CreateSessionDescriptionFormat(p_dec, sarn, sard);
|
|
if (decoderConfiguration != nil)
|
|
{
|
|
CMFormatDescriptionRef newvideoFormatDesc;
|
|
/* create new video format description */
|
|
OSStatus status = CMVideoFormatDescriptionCreate(kCFAllocatorDefault,
|
|
p_sys->codec,
|
|
vw, vh,
|
|
decoderConfiguration,
|
|
&newvideoFormatDesc);
|
|
if (!status)
|
|
{
|
|
b_ret = !VTDecompressionSessionCanAcceptFormatDescription(session,
|
|
newvideoFormatDesc);
|
|
CFRelease(newvideoFormatDesc);
|
|
}
|
|
CFRelease(decoderConfiguration);
|
|
}
|
|
|
|
return b_ret;
|
|
}
|
|
|
|
static bool InitHEVC(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
hevc_poc_cxt_init(&p_sys->hevc_pocctx);
|
|
hxxx_helper_init(&p_sys->hh, VLC_OBJECT(p_dec),
|
|
p_dec->fmt_in.i_codec, true);
|
|
return hxxx_helper_set_extra(&p_sys->hh, p_dec->fmt_in.p_extra,
|
|
p_dec->fmt_in.i_extra) == VLC_SUCCESS;
|
|
}
|
|
|
|
#define CleanHEVC CleanH264
|
|
|
|
static void GetxPSHEVC(uint8_t i_id, void *priv,
|
|
hevc_picture_parameter_set_t **pp_pps,
|
|
hevc_sequence_parameter_set_t **pp_sps,
|
|
hevc_video_parameter_set_t **pp_vps)
|
|
{
|
|
decoder_sys_t *p_sys = priv;
|
|
|
|
*pp_pps = p_sys->hh.hevc.pps_list[i_id].hevc_pps;
|
|
if (*pp_pps == NULL)
|
|
{
|
|
*pp_vps = NULL;
|
|
*pp_sps = NULL;
|
|
}
|
|
else
|
|
{
|
|
uint8_t i_sps_id = hevc_get_pps_sps_id(*pp_pps);
|
|
*pp_sps = p_sys->hh.hevc.sps_list[i_sps_id].hevc_sps;
|
|
if (*pp_sps == NULL)
|
|
*pp_vps = NULL;
|
|
else
|
|
{
|
|
uint8_t i_vps_id = hevc_get_sps_vps_id(*pp_sps);
|
|
*pp_vps = p_sys->hh.hevc.vps_list[i_vps_id].hevc_vps;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct hevc_sei_callback_s
|
|
{
|
|
hevc_sei_pic_timing_t *p_timing;
|
|
const hevc_sequence_parameter_set_t *p_sps;
|
|
};
|
|
|
|
static bool ParseHEVCSEI(const hxxx_sei_data_t *p_sei_data, void *priv)
|
|
{
|
|
if(p_sei_data->i_type == HXXX_SEI_PIC_TIMING)
|
|
{
|
|
struct hevc_sei_callback_s *s = priv;
|
|
if(likely(s->p_sps))
|
|
s->p_timing = hevc_decode_sei_pic_timing(p_sei_data->p_bs, s->p_sps);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool FillReorderInfoHEVC(decoder_t *p_dec, const block_t *p_block,
|
|
frame_info_t *p_info)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
hxxx_iterator_ctx_t itctx;
|
|
hxxx_iterator_init(&itctx, p_block->p_buffer, p_block->i_buffer,
|
|
p_sys->hh.i_nal_length_size);
|
|
|
|
const uint8_t *p_nal; size_t i_nal;
|
|
struct
|
|
{
|
|
const uint8_t *p_nal;
|
|
size_t i_nal;
|
|
} sei_array[VT_MAX_SEI_COUNT];
|
|
size_t i_sei_count = 0;
|
|
|
|
while(hxxx_iterate_next(&itctx, &p_nal, &i_nal))
|
|
{
|
|
if(i_nal < 2 || hevc_getNALLayer(p_nal) > 0)
|
|
continue;
|
|
|
|
const enum hevc_nal_unit_type_e i_nal_type = hevc_getNALType(p_nal);
|
|
if (i_nal_type <= HEVC_NAL_IRAP_VCL23)
|
|
{
|
|
hevc_slice_segment_header_t *p_sli =
|
|
hevc_decode_slice_header(p_nal, i_nal, true, GetxPSHEVC, p_sys);
|
|
if(!p_sli)
|
|
return false;
|
|
|
|
/* XXX: Work-around a VT bug on recent devices (iPhone X, MacBook
|
|
* Pro 2017). The VT session will report a BadDataErr if you send a
|
|
* RASL frame just after a CRA one. Indeed, RASL frames are
|
|
* corrupted if the decoding start at an IRAP frame (IDR/CRA), VT
|
|
* is likely failing to handle this case. */
|
|
if (!p_sys->b_vt_feed && (i_nal_type != HEVC_NAL_IDR_W_RADL &&
|
|
i_nal_type != HEVC_NAL_IDR_N_LP))
|
|
p_sys->b_drop_blocks = true;
|
|
else if (p_sys->b_drop_blocks)
|
|
{
|
|
if (i_nal_type == HEVC_NAL_RASL_N || i_nal_type == HEVC_NAL_RASL_R)
|
|
{
|
|
hevc_rbsp_release_slice_header(p_sli);
|
|
return false;
|
|
}
|
|
else
|
|
p_sys->b_drop_blocks = false;
|
|
}
|
|
|
|
p_info->b_keyframe = i_nal_type >= HEVC_NAL_BLA_W_LP;
|
|
|
|
hevc_sequence_parameter_set_t *p_sps;
|
|
hevc_picture_parameter_set_t *p_pps;
|
|
hevc_video_parameter_set_t *p_vps;
|
|
GetxPSHEVC(hevc_get_slice_pps_id(p_sli), p_sys, &p_pps, &p_sps, &p_vps);
|
|
if(p_sps)
|
|
{
|
|
struct hevc_sei_callback_s sei;
|
|
sei.p_sps = p_sps;
|
|
sei.p_timing = NULL;
|
|
|
|
const int POC = hevc_compute_picture_order_count(p_sps, p_sli,
|
|
&p_sys->hevc_pocctx);
|
|
|
|
for(size_t i=0; i<i_sei_count; i++)
|
|
HxxxParseSEI(sei_array[i].p_nal, sei_array[i].i_nal,
|
|
2, ParseHEVCSEI, &sei);
|
|
|
|
p_info->i_poc = POC;
|
|
p_info->i_foc = POC; /* clearly looks wrong :/ */
|
|
p_info->i_num_ts = hevc_get_num_clock_ts(p_sps, sei.p_timing);
|
|
p_info->b_flush = (POC == 0);
|
|
p_info->b_field = (p_info->i_num_ts == 1);
|
|
p_info->b_progressive = hevc_frame_is_progressive(p_sps, sei.p_timing);
|
|
|
|
/* Set frame rate for timings in case of missing rate */
|
|
if( (!p_dec->fmt_in.video.i_frame_rate_base ||
|
|
!p_dec->fmt_in.video.i_frame_rate) )
|
|
{
|
|
unsigned num, den;
|
|
if(hevc_get_frame_rate(p_sps, p_vps, &num, &den))
|
|
date_Change(&p_sys->pts, num, den);
|
|
}
|
|
|
|
if(sei.p_timing)
|
|
hevc_release_sei_pic_timing(sei.p_timing);
|
|
|
|
if(!p_sys->b_invalid_pic_reorder_max && p_vps)
|
|
{
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->i_pic_reorder_max = hevc_get_max_num_reorder(p_vps);
|
|
pic_holder_update_reorder_max(p_sys->pic_holder,
|
|
p_sys->i_pic_reorder_max,
|
|
p_info->i_num_ts);
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
|
|
}
|
|
|
|
hevc_rbsp_release_slice_header(p_sli);
|
|
return true; /* No need to parse further NAL */
|
|
}
|
|
else if(i_nal_type == HEVC_NAL_PREF_SEI)
|
|
{
|
|
if(i_sei_count < VT_MAX_SEI_COUNT)
|
|
{
|
|
sei_array[i_sei_count].p_nal = p_nal;
|
|
sei_array[i_sei_count++].i_nal = i_nal;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static CFMutableDictionaryRef GetDecoderExtradataHEVC(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
CFMutableDictionaryRef extradata = nil;
|
|
if (p_dec->fmt_in.i_extra && p_sys->hh.b_is_xvcC)
|
|
{
|
|
/* copy DecoderConfiguration */
|
|
extradata = ExtradataInfoCreate(CFSTR("hvcC"),
|
|
p_dec->fmt_in.p_extra,
|
|
p_dec->fmt_in.i_extra);
|
|
}
|
|
else if (p_sys->hh.hevc.i_pps_count &&
|
|
p_sys->hh.hevc.i_sps_count &&
|
|
p_sys->hh.hevc.i_vps_count)
|
|
{
|
|
/* build DecoderConfiguration from gathered */
|
|
block_t *p_hvcC = hevc_helper_get_hvcc_config(&p_sys->hh);
|
|
if (p_hvcC)
|
|
{
|
|
extradata = ExtradataInfoCreate(CFSTR("hvcC"),
|
|
p_hvcC->p_buffer,
|
|
p_hvcC->i_buffer);
|
|
block_Release(p_hvcC);
|
|
}
|
|
}
|
|
return extradata;
|
|
}
|
|
|
|
static bool LateStartHEVC(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
return (p_dec->fmt_in.i_extra == 0 &&
|
|
(!p_sys->hh.hevc.i_pps_count ||
|
|
!p_sys->hh.hevc.i_sps_count ||
|
|
!p_sys->hh.hevc.i_vps_count) );
|
|
}
|
|
|
|
static bool CodecSupportedHEVC(decoder_t *p_dec)
|
|
{
|
|
HXXXGetBestChroma(p_dec);
|
|
|
|
return true;
|
|
}
|
|
|
|
#define ConfigureVoutHEVC ConfigureVoutH264
|
|
#define ProcessBlockHEVC ProcessBlockH264
|
|
#define VideoToolboxNeedsToRestartHEVC VideoToolboxNeedsToRestartH264
|
|
|
|
static CFMutableDictionaryRef GetDecoderExtradataMPEG4(decoder_t *p_dec)
|
|
{
|
|
if (p_dec->fmt_in.i_extra)
|
|
return ESDSExtradataInfoCreate(p_dec, p_dec->fmt_in.p_extra,
|
|
p_dec->fmt_in.i_extra);
|
|
else
|
|
return nil; /* MPEG4 without esds ? */
|
|
}
|
|
|
|
static CFMutableDictionaryRef GetDecoderExtradataDefault(decoder_t *p_dec)
|
|
{
|
|
return ExtradataInfoCreate(NULL, NULL, 0); /* Empty Needed ? */
|
|
}
|
|
|
|
/* !Codec Specific */
|
|
|
|
static void InsertIntoDPB(decoder_sys_t *p_sys, frame_info_t *p_info)
|
|
{
|
|
frame_info_t **pp_lead_in = &p_sys->p_pic_reorder;
|
|
|
|
for( ;; pp_lead_in = & ((*pp_lead_in)->p_next))
|
|
{
|
|
bool b_insert;
|
|
if(*pp_lead_in == NULL)
|
|
b_insert = true;
|
|
else if(p_sys->b_poc_based_reorder)
|
|
b_insert = ((*pp_lead_in)->i_foc > p_info->i_foc);
|
|
else
|
|
b_insert = ((*pp_lead_in)->p_picture->date >= p_info->p_picture->date);
|
|
|
|
if(b_insert)
|
|
{
|
|
p_info->p_next = *pp_lead_in;
|
|
*pp_lead_in = p_info;
|
|
p_sys->i_pic_reorder += (p_info->b_field) ? 1 : 2;
|
|
break;
|
|
}
|
|
}
|
|
#if 0
|
|
for(frame_info_t *p_in=p_sys->p_pic_reorder; p_in; p_in = p_in->p_next)
|
|
printf(" %d", p_in->i_foc);
|
|
printf("\n");
|
|
#endif
|
|
}
|
|
|
|
static picture_t * RemoveOneFrameFromDPB(decoder_sys_t *p_sys)
|
|
{
|
|
frame_info_t *p_info = p_sys->p_pic_reorder;
|
|
if(p_info == NULL)
|
|
return NULL;
|
|
|
|
const int i_framepoc = p_info->i_poc;
|
|
|
|
picture_t *p_ret = NULL;
|
|
picture_t **pp_ret_last = &p_ret;
|
|
bool b_dequeue;
|
|
|
|
do
|
|
{
|
|
picture_t *p_field = p_info->p_picture;
|
|
|
|
/* Compute time if missing */
|
|
if (p_field->date == VLC_TS_INVALID)
|
|
p_field->date = date_Get(&p_sys->pts);
|
|
else
|
|
date_Set(&p_sys->pts, p_field->date);
|
|
|
|
/* Set next picture time, in case it is missing */
|
|
if (p_info->i_length)
|
|
date_Set(&p_sys->pts, p_field->date + p_info->i_length);
|
|
else
|
|
date_Increment(&p_sys->pts, p_info->i_num_ts);
|
|
|
|
*pp_ret_last = p_field;
|
|
pp_ret_last = &p_field->p_next;
|
|
|
|
p_sys->i_pic_reorder -= (p_info->b_field) ? 1 : 2;
|
|
|
|
p_sys->p_pic_reorder = p_info->p_next;
|
|
free(p_info);
|
|
p_info = p_sys->p_pic_reorder;
|
|
|
|
if (p_info)
|
|
{
|
|
if (p_sys->b_poc_based_reorder)
|
|
b_dequeue = (p_info->i_poc == i_framepoc);
|
|
else
|
|
b_dequeue = (p_field->date == p_info->p_picture->date);
|
|
}
|
|
else b_dequeue = false;
|
|
|
|
} while(b_dequeue);
|
|
|
|
return p_ret;
|
|
}
|
|
|
|
static void DrainDPBLocked(decoder_t *p_dec, bool flush)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
for( ;; )
|
|
{
|
|
picture_t *p_fields = RemoveOneFrameFromDPB(p_sys);
|
|
if (p_fields == NULL)
|
|
break;
|
|
do
|
|
{
|
|
picture_t *p_next = p_fields->p_next;
|
|
p_fields->p_next = NULL;
|
|
if (flush)
|
|
picture_Release(p_fields);
|
|
else
|
|
decoder_QueueVideo(p_dec, p_fields);
|
|
p_fields = p_next;
|
|
} while(p_fields != NULL);
|
|
}
|
|
}
|
|
|
|
static frame_info_t * CreateReorderInfo(decoder_t *p_dec, const block_t *p_block)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
frame_info_t *p_info = calloc(1, sizeof(*p_info));
|
|
if (!p_info)
|
|
return NULL;
|
|
|
|
if (p_sys->pf_fill_reorder_info)
|
|
{
|
|
if(!p_sys->pf_fill_reorder_info(p_dec, p_block, p_info))
|
|
{
|
|
free(p_info);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p_info->i_num_ts = 2;
|
|
p_info->b_progressive = true;
|
|
p_info->b_field = false;
|
|
p_info->b_keyframe = true;
|
|
}
|
|
|
|
p_info->i_length = p_block->i_length;
|
|
|
|
/* required for still pictures/menus */
|
|
p_info->b_forced = (p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE);
|
|
|
|
if (date_Get(&p_sys->pts) == VLC_TS_INVALID)
|
|
date_Set(&p_sys->pts, p_block->i_dts);
|
|
|
|
return p_info;
|
|
}
|
|
|
|
static void OnDecodedFrame(decoder_t *p_dec, frame_info_t *p_info)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
assert(p_info->p_picture);
|
|
while(p_info->b_flush || p_sys->i_pic_reorder >= (p_sys->i_pic_reorder_max * 2))
|
|
{
|
|
/* First check if DPB sizing was correct before removing one frame */
|
|
if (p_sys->p_pic_reorder && !p_info->b_flush &&
|
|
p_sys->i_pic_reorder_max < H264_MAX_DPB)
|
|
{
|
|
if(p_sys->b_poc_based_reorder && p_sys->p_pic_reorder->i_foc > p_info->i_foc)
|
|
{
|
|
p_sys->b_invalid_pic_reorder_max = true;
|
|
p_sys->i_pic_reorder_max++;
|
|
pic_holder_update_reorder_max(p_sys->pic_holder,
|
|
p_sys->i_pic_reorder_max, p_info->i_num_ts);
|
|
msg_Info(p_dec, "Raising max DPB to %"PRIu8, p_sys->i_pic_reorder_max);
|
|
break;
|
|
}
|
|
else if (!p_sys->b_poc_based_reorder &&
|
|
p_info->p_picture->date > VLC_TS_INVALID &&
|
|
p_sys->p_pic_reorder->p_picture->date > p_info->p_picture->date)
|
|
{
|
|
p_sys->b_invalid_pic_reorder_max = true;
|
|
p_sys->i_pic_reorder_max++;
|
|
pic_holder_update_reorder_max(p_sys->pic_holder,
|
|
p_sys->i_pic_reorder_max, p_info->i_num_ts);
|
|
msg_Info(p_dec, "Raising max DPB to %"PRIu8, p_sys->i_pic_reorder_max);
|
|
break;
|
|
}
|
|
}
|
|
|
|
picture_t *p_fields = RemoveOneFrameFromDPB(p_sys);
|
|
if (p_fields == NULL)
|
|
break;
|
|
do
|
|
{
|
|
picture_t *p_next = p_fields->p_next;
|
|
p_fields->p_next = NULL;
|
|
decoder_QueueVideo(p_dec, p_fields);
|
|
p_fields = p_next;
|
|
} while(p_fields != NULL);
|
|
}
|
|
|
|
InsertIntoDPB(p_sys, p_info);
|
|
}
|
|
|
|
static CMVideoCodecType CodecPrecheck(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
/* check for the codec we can and want to decode */
|
|
switch (p_dec->fmt_in.i_codec) {
|
|
case VLC_CODEC_H264:
|
|
return kCMVideoCodecType_H264;
|
|
|
|
case VLC_CODEC_HEVC:
|
|
if (!deviceSupportsHEVC())
|
|
{
|
|
msg_Warn(p_dec, "device doesn't support HEVC");
|
|
return -1;
|
|
}
|
|
return kCMVideoCodecType_HEVC;
|
|
|
|
case VLC_CODEC_MP4V:
|
|
{
|
|
if (p_dec->fmt_in.i_original_fourcc == VLC_FOURCC( 'X','V','I','D' )) {
|
|
msg_Warn(p_dec, "XVID decoding not implemented, fallback on software");
|
|
return -1;
|
|
}
|
|
|
|
msg_Dbg(p_dec, "Will decode MP4V with original FourCC '%4.4s'", (char *)&p_dec->fmt_in.i_original_fourcc);
|
|
return kCMVideoCodecType_MPEG4Video;
|
|
}
|
|
#if !TARGET_OS_IPHONE
|
|
case VLC_CODEC_H263:
|
|
return kCMVideoCodecType_H263;
|
|
|
|
/* there are no DV or ProRes decoders on iOS, so bailout early */
|
|
case VLC_CODEC_PRORES:
|
|
/* the VT decoder can't differenciate between the ProRes flavors, so we do it */
|
|
switch (p_dec->fmt_in.i_original_fourcc) {
|
|
case VLC_FOURCC( 'a','p','4','c' ):
|
|
case VLC_FOURCC( 'a','p','4','h' ):
|
|
case VLC_FOURCC( 'a','p','4','x' ):
|
|
return kCMVideoCodecType_AppleProRes4444;
|
|
|
|
case VLC_FOURCC( 'a','p','c','h' ):
|
|
return kCMVideoCodecType_AppleProRes422HQ;
|
|
|
|
case VLC_FOURCC( 'a','p','c','s' ):
|
|
return kCMVideoCodecType_AppleProRes422LT;
|
|
|
|
case VLC_FOURCC( 'a','p','c','o' ):
|
|
return kCMVideoCodecType_AppleProRes422Proxy;
|
|
|
|
default:
|
|
return kCMVideoCodecType_AppleProRes422;
|
|
}
|
|
|
|
case VLC_CODEC_DV:
|
|
/* the VT decoder can't differenciate between PAL and NTSC, so we need to do it */
|
|
switch (p_dec->fmt_in.i_original_fourcc) {
|
|
case VLC_FOURCC( 'd', 'v', 'c', ' '):
|
|
case VLC_FOURCC( 'd', 'v', ' ', ' '):
|
|
msg_Dbg(p_dec, "Decoding DV NTSC");
|
|
return kCMVideoCodecType_DVCNTSC;
|
|
|
|
case VLC_FOURCC( 'd', 'v', 's', 'd'):
|
|
case VLC_FOURCC( 'd', 'v', 'c', 'p'):
|
|
case VLC_FOURCC( 'D', 'V', 'S', 'D'):
|
|
msg_Dbg(p_dec, "Decoding DV PAL");
|
|
return kCMVideoCodecType_DVCPAL;
|
|
default:
|
|
return -1;
|
|
}
|
|
#endif
|
|
/* mpgv / mp2v needs fixing, so disable it for now */
|
|
#if 0
|
|
case VLC_CODEC_MPGV:
|
|
return kCMVideoCodecType_MPEG1Video;
|
|
case VLC_CODEC_MP2V:
|
|
return kCMVideoCodecType_MPEG2Video;
|
|
#endif
|
|
|
|
default:
|
|
#ifndef NDEBUG
|
|
msg_Err(p_dec, "'%4.4s' is not supported", (char *)&p_dec->fmt_in.i_codec);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
vlc_assert_unreachable();
|
|
}
|
|
|
|
static CFMutableDictionaryRef CreateSessionDescriptionFormat(decoder_t *p_dec,
|
|
unsigned i_sar_num,
|
|
unsigned i_sar_den)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
CFMutableDictionaryRef decoderConfiguration = cfdict_create(0);
|
|
if (decoderConfiguration == NULL)
|
|
return nil;
|
|
|
|
CFMutableDictionaryRef extradata = p_sys->pf_get_extradata
|
|
? p_sys->pf_get_extradata(p_dec) : nil;
|
|
if(extradata)
|
|
{
|
|
/* then decoder will also fail if required, no need to handle it */
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms,
|
|
extradata);
|
|
CFRelease(extradata);
|
|
}
|
|
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kCVImageBufferChromaLocationBottomFieldKey,
|
|
kCVImageBufferChromaLocation_Left);
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kCVImageBufferChromaLocationTopFieldKey,
|
|
kCVImageBufferChromaLocation_Left);
|
|
|
|
/* pixel aspect ratio */
|
|
if(i_sar_num && i_sar_den)
|
|
{
|
|
CFMutableDictionaryRef pixelaspectratio = cfdict_create(2);
|
|
if(pixelaspectratio == NULL)
|
|
{
|
|
CFRelease(decoderConfiguration);
|
|
return nil;
|
|
}
|
|
|
|
cfdict_set_int32(pixelaspectratio,
|
|
kCVImageBufferPixelAspectRatioHorizontalSpacingKey,
|
|
i_sar_num);
|
|
cfdict_set_int32(pixelaspectratio,
|
|
kCVImageBufferPixelAspectRatioVerticalSpacingKey,
|
|
i_sar_den);
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kCVImageBufferPixelAspectRatioKey,
|
|
pixelaspectratio);
|
|
CFRelease(pixelaspectratio);
|
|
}
|
|
|
|
/* Setup YUV->RGB matrix since VT can output BGRA directly. Don't setup
|
|
* transfer and primaries since the transformation is done via the GL
|
|
* fragment shader. */
|
|
CFStringRef yuvmatrix;
|
|
switch (p_dec->fmt_out.video.space)
|
|
{
|
|
case COLOR_SPACE_BT601:
|
|
yuvmatrix = kCVImageBufferYCbCrMatrix_ITU_R_601_4;
|
|
break;
|
|
case COLOR_SPACE_BT2020:
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wpartial-availability"
|
|
if (&kCVImageBufferColorPrimaries_ITU_R_2020 != nil)
|
|
{
|
|
yuvmatrix = kCVImageBufferColorPrimaries_ITU_R_2020;
|
|
break;
|
|
}
|
|
#pragma clang diagnostic pop
|
|
/* fall through */
|
|
case COLOR_SPACE_BT709:
|
|
default:
|
|
yuvmatrix = kCVImageBufferColorPrimaries_ITU_R_709_2;
|
|
break;
|
|
}
|
|
CFDictionarySetValue(decoderConfiguration, kCVImageBufferYCbCrMatrixKey,
|
|
yuvmatrix);
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wpartial-availability"
|
|
|
|
/* enable HW accelerated playback, since this is optional on OS X
|
|
* note that the backend may still fallback on software mode if no
|
|
* suitable hardware is available */
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
|
|
kCFBooleanTrue);
|
|
|
|
/* on OS X, we can force VT to fail if no suitable HW decoder is available,
|
|
* preventing the aforementioned SW fallback */
|
|
if (var_InheritBool(p_dec, "videotoolbox-hw-decoder-only"))
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder,
|
|
kCFBooleanTrue);
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kVTDecompressionPropertyKey_FieldMode,
|
|
kVTDecompressionProperty_FieldMode_DeinterlaceFields);
|
|
CFDictionarySetValue(decoderConfiguration,
|
|
kVTDecompressionPropertyKey_DeinterlaceMode,
|
|
kVTDecompressionProperty_DeinterlaceMode_Temporal);
|
|
|
|
return decoderConfiguration;
|
|
}
|
|
|
|
static void PtsInit(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
if( p_dec->fmt_in.video.i_frame_rate_base && p_dec->fmt_in.video.i_frame_rate )
|
|
{
|
|
date_Init( &p_sys->pts, p_dec->fmt_in.video.i_frame_rate * 2,
|
|
p_dec->fmt_in.video.i_frame_rate_base );
|
|
}
|
|
else date_Init( &p_sys->pts, 2 * 30000, 1001 );
|
|
}
|
|
|
|
static int StartVideoToolbox(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
/* Late starts */
|
|
if(p_sys->pf_late_start && p_sys->pf_late_start(p_dec))
|
|
{
|
|
assert(p_sys->session == NULL);
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/* Fills fmt_out (from extradata if any) */
|
|
if(ConfigureVout(p_dec) != VLC_SUCCESS)
|
|
return VLC_EGENERIC;
|
|
|
|
/* destination pixel buffer attributes */
|
|
CFMutableDictionaryRef destinationPixelBufferAttributes = cfdict_create(0);
|
|
if(destinationPixelBufferAttributes == nil)
|
|
return VLC_EGENERIC;
|
|
|
|
CFMutableDictionaryRef decoderConfiguration =
|
|
CreateSessionDescriptionFormat(p_dec,
|
|
p_dec->fmt_out.video.i_sar_num,
|
|
p_dec->fmt_out.video.i_sar_den);
|
|
if(decoderConfiguration == nil)
|
|
{
|
|
CFRelease(destinationPixelBufferAttributes);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/* create video format description */
|
|
OSStatus status = CMVideoFormatDescriptionCreate(
|
|
kCFAllocatorDefault,
|
|
p_sys->codec,
|
|
p_dec->fmt_out.video.i_visible_width,
|
|
p_dec->fmt_out.video.i_visible_height,
|
|
decoderConfiguration,
|
|
&p_sys->videoFormatDescription);
|
|
if (status)
|
|
{
|
|
CFRelease(destinationPixelBufferAttributes);
|
|
CFRelease(decoderConfiguration);
|
|
msg_Err(p_dec, "video format description creation failed (%i)", (int)status);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
#if !TARGET_OS_IPHONE
|
|
CFDictionarySetValue(destinationPixelBufferAttributes,
|
|
kCVPixelBufferIOSurfaceOpenGLTextureCompatibilityKey,
|
|
kCFBooleanTrue);
|
|
#else
|
|
CFDictionarySetValue(destinationPixelBufferAttributes,
|
|
kCVPixelBufferOpenGLESCompatibilityKey,
|
|
kCFBooleanTrue);
|
|
#endif
|
|
|
|
cfdict_set_int32(destinationPixelBufferAttributes,
|
|
kCVPixelBufferWidthKey, p_dec->fmt_out.video.i_visible_width);
|
|
cfdict_set_int32(destinationPixelBufferAttributes,
|
|
kCVPixelBufferHeightKey, p_dec->fmt_out.video.i_visible_height);
|
|
|
|
if (p_sys->i_cvpx_format != 0)
|
|
{
|
|
int chroma = htonl(p_sys->i_cvpx_format);
|
|
msg_Warn(p_dec, "forcing CVPX format: %4.4s", (const char *) &chroma);
|
|
cfdict_set_int32(destinationPixelBufferAttributes,
|
|
kCVPixelBufferPixelFormatTypeKey,
|
|
p_sys->i_cvpx_format);
|
|
}
|
|
|
|
cfdict_set_int32(destinationPixelBufferAttributes,
|
|
kCVPixelBufferBytesPerRowAlignmentKey, 16);
|
|
|
|
/* setup decoder callback record */
|
|
VTDecompressionOutputCallbackRecord decoderCallbackRecord;
|
|
decoderCallbackRecord.decompressionOutputCallback = DecoderCallback;
|
|
decoderCallbackRecord.decompressionOutputRefCon = p_dec;
|
|
|
|
/* create decompression session */
|
|
status = VTDecompressionSessionCreate(kCFAllocatorDefault,
|
|
p_sys->videoFormatDescription,
|
|
decoderConfiguration,
|
|
destinationPixelBufferAttributes,
|
|
&decoderCallbackRecord, &p_sys->session);
|
|
CFRelease(decoderConfiguration);
|
|
CFRelease(destinationPixelBufferAttributes);
|
|
|
|
if (HandleVTStatus(p_dec, status, NULL) != VLC_SUCCESS)
|
|
return VLC_EGENERIC;
|
|
|
|
PtsInit(p_dec);
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void StopVideoToolbox(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
if (p_sys->session != nil)
|
|
{
|
|
Drain(p_dec, true);
|
|
|
|
VTDecompressionSessionInvalidate(p_sys->session);
|
|
CFRelease(p_sys->session);
|
|
p_sys->session = nil;
|
|
|
|
#if TARGET_OS_IPHONE
|
|
/* In case of 4K 10bits (BGRA), we can easily reach the device max
|
|
* memory when flushing. Indeed, we'll create a new VT session that
|
|
* will reallocate frames while previous frames are still used by the
|
|
* vout (and not released). To work-around this issue, we force a vout
|
|
* change. */
|
|
if (p_dec->fmt_out.i_codec == VLC_CODEC_CVPX_BGRA
|
|
&& p_dec->fmt_out.video.i_width * p_dec->fmt_out.video.i_height >= 8000000)
|
|
{
|
|
const video_format_t orig = p_dec->fmt_out.video;
|
|
p_dec->fmt_out.video.i_width = p_dec->fmt_out.video.i_height =
|
|
p_dec->fmt_out.video.i_visible_width = p_dec->fmt_out.video.i_visible_height = 64;
|
|
(void) decoder_UpdateVideoFormat(p_dec);
|
|
p_dec->fmt_out.video = orig;
|
|
}
|
|
#endif
|
|
|
|
p_sys->b_format_propagated = false;
|
|
p_dec->fmt_out.i_codec = 0;
|
|
}
|
|
|
|
if (p_sys->videoFormatDescription != nil) {
|
|
CFRelease(p_sys->videoFormatDescription);
|
|
p_sys->videoFormatDescription = nil;
|
|
}
|
|
p_sys->b_vt_feed = false;
|
|
p_sys->b_drop_blocks = false;
|
|
}
|
|
|
|
#pragma mark - module open and close
|
|
|
|
|
|
static int OpenDecoder(vlc_object_t *p_this)
|
|
{
|
|
decoder_t *p_dec = (decoder_t *)p_this;
|
|
|
|
if (!var_InheritBool(p_dec, "videotoolbox"))
|
|
return VLC_EGENERIC;
|
|
|
|
#if TARGET_OS_IPHONE
|
|
if (unlikely([[UIDevice currentDevice].systemVersion floatValue] < 8.0)) {
|
|
msg_Warn(p_dec, "decoder skipped as OS is too old");
|
|
return VLC_EGENERIC;
|
|
}
|
|
#endif
|
|
|
|
/* Fail if this module already failed to decode this ES */
|
|
if (var_Type(p_dec, "videotoolbox-failed") != 0)
|
|
return VLC_EGENERIC;
|
|
|
|
/* check quickly if we can digest the offered data */
|
|
CMVideoCodecType codec;
|
|
|
|
codec = CodecPrecheck(p_dec);
|
|
if (codec == -1)
|
|
return VLC_EGENERIC;
|
|
|
|
/* now that we see a chance to decode anything, allocate the
|
|
* internals and start the decoding session */
|
|
decoder_sys_t *p_sys;
|
|
p_sys = calloc(1, sizeof(*p_sys));
|
|
if (!p_sys)
|
|
return VLC_ENOMEM;
|
|
p_dec->p_sys = p_sys;
|
|
p_sys->session = nil;
|
|
p_sys->codec = codec;
|
|
p_sys->videoFormatDescription = nil;
|
|
p_sys->i_pic_reorder_max = 4;
|
|
p_sys->vtsession_status = VTSESSION_STATUS_OK;
|
|
p_sys->b_cvpx_format_forced = false;
|
|
|
|
char *cvpx_chroma = var_InheritString(p_dec, "videotoolbox-cvpx-chroma");
|
|
if (cvpx_chroma != NULL)
|
|
{
|
|
if (strlen(cvpx_chroma) != 4)
|
|
{
|
|
msg_Err(p_dec, "invalid videotoolbox-cvpx-chroma option");
|
|
free(cvpx_chroma);
|
|
free(p_sys);
|
|
return VLC_EGENERIC;
|
|
}
|
|
memcpy(&p_sys->i_cvpx_format, cvpx_chroma, 4);
|
|
p_sys->i_cvpx_format = ntohl(p_sys->i_cvpx_format);
|
|
p_sys->b_cvpx_format_forced = true;
|
|
free(cvpx_chroma);
|
|
}
|
|
|
|
p_sys->pic_holder = malloc(sizeof(struct pic_holder));
|
|
if (!p_sys->pic_holder)
|
|
{
|
|
free(p_sys);
|
|
return VLC_ENOMEM;
|
|
}
|
|
|
|
vlc_mutex_init(&p_sys->pic_holder->lock);
|
|
vlc_cond_init(&p_sys->pic_holder->wait);
|
|
p_sys->pic_holder->nb_field_out = 0;
|
|
p_sys->pic_holder->closed = false;
|
|
p_sys->pic_holder->field_reorder_max = p_sys->i_pic_reorder_max * 2;
|
|
p_sys->b_vt_need_keyframe = false;
|
|
|
|
vlc_mutex_init(&p_sys->lock);
|
|
|
|
p_dec->pf_decode = DecodeBlock;
|
|
p_dec->pf_flush = RequestFlush;
|
|
|
|
switch(codec)
|
|
{
|
|
case kCMVideoCodecType_H264:
|
|
p_sys->pf_codec_init = InitH264;
|
|
p_sys->pf_codec_clean = CleanH264;
|
|
p_sys->pf_codec_supported = CodecSupportedH264;
|
|
p_sys->pf_late_start = LateStartH264;
|
|
p_sys->pf_process_block = ProcessBlockH264;
|
|
p_sys->pf_need_restart = VideoToolboxNeedsToRestartH264;
|
|
p_sys->pf_configure_vout = ConfigureVoutH264;
|
|
p_sys->pf_get_extradata = GetDecoderExtradataH264;
|
|
p_sys->pf_fill_reorder_info = FillReorderInfoH264;
|
|
p_sys->b_poc_based_reorder = true;
|
|
break;
|
|
|
|
case kCMVideoCodecType_HEVC:
|
|
p_sys->pf_codec_init = InitHEVC;
|
|
p_sys->pf_codec_clean = CleanHEVC;
|
|
p_sys->pf_codec_supported = CodecSupportedHEVC;
|
|
p_sys->pf_late_start = LateStartHEVC;
|
|
p_sys->pf_process_block = ProcessBlockHEVC;
|
|
p_sys->pf_need_restart = VideoToolboxNeedsToRestartHEVC;
|
|
p_sys->pf_configure_vout = ConfigureVoutHEVC;
|
|
p_sys->pf_get_extradata = GetDecoderExtradataHEVC;
|
|
p_sys->pf_fill_reorder_info = FillReorderInfoHEVC;
|
|
p_sys->b_poc_based_reorder = true;
|
|
p_sys->b_vt_need_keyframe = true;
|
|
break;
|
|
|
|
case kCMVideoCodecType_MPEG4Video:
|
|
p_sys->pf_get_extradata = GetDecoderExtradataMPEG4;
|
|
break;
|
|
|
|
default:
|
|
p_sys->pf_get_extradata = GetDecoderExtradataDefault;
|
|
break;
|
|
}
|
|
|
|
if (p_sys->pf_codec_init && !p_sys->pf_codec_init(p_dec))
|
|
{
|
|
CloseDecoder(p_this);
|
|
return VLC_EGENERIC;
|
|
}
|
|
if (p_sys->pf_codec_supported && !p_sys->pf_codec_supported(p_dec))
|
|
{
|
|
CloseDecoder(p_this);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
int i_ret = StartVideoToolbox(p_dec);
|
|
if (i_ret == VLC_SUCCESS)
|
|
msg_Info(p_dec, "Using Video Toolbox to decode '%4.4s'",
|
|
(char *)&p_dec->fmt_in.i_codec);
|
|
else
|
|
CloseDecoder(p_this);
|
|
return i_ret;
|
|
}
|
|
|
|
static void pic_holder_clean(struct pic_holder *pic_holder)
|
|
{
|
|
vlc_mutex_destroy(&pic_holder->lock);
|
|
vlc_cond_destroy(&pic_holder->wait);
|
|
free(pic_holder);
|
|
}
|
|
|
|
static void CloseDecoder(vlc_object_t *p_this)
|
|
{
|
|
decoder_t *p_dec = (decoder_t *)p_this;
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
StopVideoToolbox(p_dec);
|
|
|
|
if(p_sys->pf_codec_clean)
|
|
p_sys->pf_codec_clean(p_dec);
|
|
|
|
vlc_mutex_destroy(&p_sys->lock);
|
|
|
|
vlc_mutex_lock(&p_sys->pic_holder->lock);
|
|
if (p_sys->pic_holder->nb_field_out == 0)
|
|
{
|
|
vlc_mutex_unlock(&p_sys->pic_holder->lock);
|
|
pic_holder_clean(p_sys->pic_holder);
|
|
}
|
|
else
|
|
{
|
|
p_sys->pic_holder->closed = true;
|
|
vlc_mutex_unlock(&p_sys->pic_holder->lock);
|
|
}
|
|
free(p_sys);
|
|
}
|
|
|
|
#pragma mark - helpers
|
|
|
|
static BOOL deviceSupportsHEVC()
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wpartial-availability"
|
|
|
|
#if (TARGET_OS_OSX && MAC_OS_X_VERSION_MAX_ALLOWED >= 101300) || \
|
|
(TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || \
|
|
(TARGET_OS_TV && __TV_OS_VERSION_MAX_ALLOWED >= 110000)
|
|
if (VTIsHardwareDecodeSupported != nil)
|
|
return VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);
|
|
else
|
|
#endif
|
|
#pragma clang diagnostic pop
|
|
return NO;
|
|
}
|
|
|
|
static BOOL deviceSupportsAdvancedProfiles()
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
size_t size;
|
|
cpu_type_t type;
|
|
|
|
size = sizeof(type);
|
|
sysctlbyname("hw.cputype", &type, &size, NULL, 0);
|
|
|
|
/* Support for H264 profile HIGH 10 was introduced with the first 64bit Apple ARM SoC, the A7 */
|
|
if (type == CPU_TYPE_ARM64)
|
|
return YES;
|
|
|
|
#endif
|
|
return NO;
|
|
}
|
|
|
|
static BOOL deviceSupportsAdvancedLevels()
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
#ifdef __LP64__
|
|
size_t size;
|
|
int32_t cpufamily;
|
|
size = sizeof(cpufamily);
|
|
sysctlbyname("hw.cpufamily", &cpufamily, &size, NULL, 0);
|
|
|
|
/* Proper 4K decoding requires a Twister SoC
|
|
* Everything below will kill the decoder daemon */
|
|
if (cpufamily == CPUFAMILY_ARM_CYCLONE || cpufamily == CPUFAMILY_ARM_TYPHOON) {
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
#else
|
|
/* we need a 64bit SoC for advanced levels */
|
|
return NO;
|
|
#endif
|
|
#else
|
|
return YES;
|
|
#endif
|
|
}
|
|
|
|
static inline void bo_add_mp4_tag_descr(bo_t *p_bo, uint8_t tag, uint32_t size)
|
|
{
|
|
bo_add_8(p_bo, tag);
|
|
for (int i = 3; i>0; i--)
|
|
bo_add_8(p_bo, (size>>(7*i)) | 0x80);
|
|
bo_add_8(p_bo, size & 0x7F);
|
|
}
|
|
|
|
static CFMutableDictionaryRef ESDSExtradataInfoCreate(decoder_t *p_dec,
|
|
uint8_t *p_buf,
|
|
uint32_t i_buf_size)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
int full_size = 3 + 5 +13 + 5 + i_buf_size + 3;
|
|
int config_size = 13 + 5 + i_buf_size;
|
|
int padding = 12;
|
|
|
|
bo_t bo;
|
|
bool status = bo_init(&bo, 1024);
|
|
if (status != true)
|
|
return nil;
|
|
|
|
bo_add_8(&bo, 0); // Version
|
|
bo_add_24be(&bo, 0); // Flags
|
|
|
|
// elementary stream description tag
|
|
bo_add_mp4_tag_descr(&bo, 0x03, full_size);
|
|
bo_add_16be(&bo, 0); // esid
|
|
bo_add_8(&bo, 0); // stream priority (0-3)
|
|
|
|
// decoder configuration description tag
|
|
bo_add_mp4_tag_descr(&bo, 0x04, config_size);
|
|
bo_add_8(&bo, 32); // object type identification (32 == MPEG4)
|
|
bo_add_8(&bo, 0x11); // stream type
|
|
bo_add_24be(&bo, 0); // buffer size
|
|
bo_add_32be(&bo, 0); // max bitrate
|
|
bo_add_32be(&bo, 0); // avg bitrate
|
|
|
|
// decoder specific description tag
|
|
bo_add_mp4_tag_descr(&bo, 0x05, i_buf_size);
|
|
bo_add_mem(&bo, i_buf_size, p_buf);
|
|
|
|
// sync layer configuration description tag
|
|
bo_add_8(&bo, 0x06); // tag
|
|
bo_add_8(&bo, 0x01); // length
|
|
bo_add_8(&bo, 0x02); // no SL
|
|
|
|
CFMutableDictionaryRef extradataInfo =
|
|
ExtradataInfoCreate(CFSTR("esds"), bo.b->p_buffer, bo.b->i_buffer);
|
|
bo_deinit(&bo);
|
|
return extradataInfo;
|
|
}
|
|
|
|
static int ConfigureVout(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
/* return our proper VLC internal state */
|
|
p_dec->fmt_out.video = p_dec->fmt_in.video;
|
|
p_dec->fmt_out.video.p_palette = NULL;
|
|
p_dec->fmt_out.i_codec = 0;
|
|
|
|
if(p_sys->pf_configure_vout &&
|
|
!p_sys->pf_configure_vout(p_dec))
|
|
return VLC_EGENERIC;
|
|
|
|
if (!p_dec->fmt_out.video.i_sar_num || !p_dec->fmt_out.video.i_sar_den)
|
|
{
|
|
p_dec->fmt_out.video.i_sar_num = 1;
|
|
p_dec->fmt_out.video.i_sar_den = 1;
|
|
}
|
|
|
|
if (!p_dec->fmt_out.video.i_visible_width || !p_dec->fmt_out.video.i_visible_height)
|
|
{
|
|
p_dec->fmt_out.video.i_visible_width = p_dec->fmt_out.video.i_width;
|
|
p_dec->fmt_out.video.i_visible_height = p_dec->fmt_out.video.i_height;
|
|
}
|
|
|
|
p_dec->fmt_out.video.i_width = ALIGN_16( p_dec->fmt_out.video.i_visible_width );
|
|
p_dec->fmt_out.video.i_height = ALIGN_16( p_dec->fmt_out.video.i_visible_height );
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static CFMutableDictionaryRef ExtradataInfoCreate(CFStringRef name,
|
|
void *p_data, size_t i_data)
|
|
{
|
|
CFMutableDictionaryRef extradataInfo = cfdict_create(1);
|
|
if (extradataInfo == nil)
|
|
return nil;
|
|
|
|
if (p_data == NULL)
|
|
return nil;
|
|
|
|
CFDataRef extradata = CFDataCreate(kCFAllocatorDefault, p_data, i_data);
|
|
if (extradata == nil)
|
|
{
|
|
CFRelease(extradataInfo);
|
|
return nil;
|
|
}
|
|
CFDictionarySetValue(extradataInfo, name, extradata);
|
|
CFRelease(extradata);
|
|
return extradataInfo;
|
|
}
|
|
|
|
static CMSampleBufferRef VTSampleBufferCreate(decoder_t *p_dec,
|
|
CMFormatDescriptionRef fmt_desc,
|
|
block_t *p_block)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
OSStatus status;
|
|
CMBlockBufferRef block_buf = NULL;
|
|
CMSampleBufferRef sample_buf = NULL;
|
|
CMTime pts;
|
|
if(!p_sys->b_poc_based_reorder && p_block->i_pts == VLC_TS_INVALID)
|
|
pts = CMTimeMake(p_block->i_dts, CLOCK_FREQ);
|
|
else
|
|
pts = CMTimeMake(p_block->i_pts, CLOCK_FREQ);
|
|
|
|
CMSampleTimingInfo timeInfoArray[1] = { {
|
|
.duration = CMTimeMake(p_block->i_length, 1),
|
|
.presentationTimeStamp = pts,
|
|
.decodeTimeStamp = CMTimeMake(p_block->i_dts, CLOCK_FREQ),
|
|
} };
|
|
|
|
status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,// structureAllocator
|
|
p_block->p_buffer, // memoryBlock
|
|
p_block->i_buffer, // blockLength
|
|
kCFAllocatorNull, // blockAllocator
|
|
NULL, // customBlockSource
|
|
0, // offsetToData
|
|
p_block->i_buffer, // dataLength
|
|
false, // flags
|
|
&block_buf);
|
|
|
|
if (!status) {
|
|
status = CMSampleBufferCreate(kCFAllocatorDefault, // allocator
|
|
block_buf, // dataBuffer
|
|
TRUE, // dataReady
|
|
0, // makeDataReadyCallback
|
|
0, // makeDataReadyRefcon
|
|
fmt_desc, // formatDescription
|
|
1, // numSamples
|
|
1, // numSampleTimingEntries
|
|
timeInfoArray, // sampleTimingArray
|
|
0, // numSampleSizeEntries
|
|
NULL, // sampleSizeArray
|
|
&sample_buf);
|
|
if (status != noErr)
|
|
msg_Warn(p_dec, "sample buffer creation failure %i", (int)status);
|
|
} else
|
|
msg_Warn(p_dec, "cm block buffer creation failure %i", (int)status);
|
|
|
|
if (block_buf != nil)
|
|
CFRelease(block_buf);
|
|
block_buf = nil;
|
|
|
|
return sample_buf;
|
|
}
|
|
|
|
static int HandleVTStatus(decoder_t *p_dec, OSStatus status,
|
|
enum vtsession_status * p_vtsession_status)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
#define VTERRCASE(x) \
|
|
case x: msg_Warn(p_dec, "vt session error: '" #x "'"); break;
|
|
|
|
switch (status)
|
|
{
|
|
case noErr:
|
|
return VLC_SUCCESS;
|
|
|
|
VTERRCASE(kVTPropertyNotSupportedErr)
|
|
VTERRCASE(kVTPropertyReadOnlyErr)
|
|
VTERRCASE(kVTParameterErr)
|
|
VTERRCASE(kVTInvalidSessionErr)
|
|
VTERRCASE(kVTAllocationFailedErr)
|
|
VTERRCASE(kVTPixelTransferNotSupportedErr)
|
|
VTERRCASE(kVTCouldNotFindVideoDecoderErr)
|
|
VTERRCASE(kVTCouldNotCreateInstanceErr)
|
|
VTERRCASE(kVTCouldNotFindVideoEncoderErr)
|
|
VTERRCASE(kVTVideoDecoderBadDataErr)
|
|
VTERRCASE(kVTVideoDecoderUnsupportedDataFormatErr)
|
|
VTERRCASE(kVTVideoDecoderMalfunctionErr)
|
|
VTERRCASE(kVTVideoEncoderMalfunctionErr)
|
|
VTERRCASE(kVTVideoDecoderNotAvailableNowErr)
|
|
VTERRCASE(kVTImageRotationNotSupportedErr)
|
|
VTERRCASE(kVTVideoEncoderNotAvailableNowErr)
|
|
VTERRCASE(kVTFormatDescriptionChangeNotSupportedErr)
|
|
VTERRCASE(kVTInsufficientSourceColorDataErr)
|
|
VTERRCASE(kVTCouldNotCreateColorCorrectionDataErr)
|
|
VTERRCASE(kVTColorSyncTransformConvertFailedErr)
|
|
VTERRCASE(kVTVideoDecoderAuthorizationErr)
|
|
VTERRCASE(kVTVideoEncoderAuthorizationErr)
|
|
VTERRCASE(kVTColorCorrectionPixelTransferFailedErr)
|
|
VTERRCASE(kVTMultiPassStorageIdentifierMismatchErr)
|
|
VTERRCASE(kVTMultiPassStorageInvalidErr)
|
|
VTERRCASE(kVTFrameSiloInvalidTimeStampErr)
|
|
VTERRCASE(kVTFrameSiloInvalidTimeRangeErr)
|
|
VTERRCASE(kVTCouldNotFindTemporalFilterErr)
|
|
VTERRCASE(kVTPixelTransferNotPermittedErr)
|
|
case -12219:
|
|
msg_Warn(p_dec, "vt session error: "
|
|
"'kVTColorCorrectionImageRotationFailedErr'");
|
|
break;
|
|
default:
|
|
msg_Warn(p_dec, "unknown vt session error (%i)", (int)status);
|
|
}
|
|
#undef VTERRCASE
|
|
|
|
if (p_vtsession_status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case kVTPixelTransferNotSupportedErr:
|
|
case kVTPixelTransferNotPermittedErr:
|
|
*p_vtsession_status = VTSESSION_STATUS_RESTART_CHROMA;
|
|
break;
|
|
case -8960 /* codecErr */:
|
|
case kVTVideoDecoderMalfunctionErr:
|
|
case kVTInvalidSessionErr:
|
|
*p_vtsession_status = VTSESSION_STATUS_RESTART;
|
|
break;
|
|
case -8969 /* codecBadDataErr */:
|
|
case kVTVideoDecoderBadDataErr:
|
|
default:
|
|
*p_vtsession_status = VTSESSION_STATUS_ABORT;
|
|
break;
|
|
}
|
|
}
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
#pragma mark - actual decoding
|
|
|
|
static void RequestFlush(decoder_t *p_dec)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->b_vt_flush = true;
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
|
|
static void Drain(decoder_t *p_dec, bool flush)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
/* draining: return last pictures of the reordered queue */
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->b_vt_flush = true;
|
|
DrainDPBLocked(p_dec, flush);
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
if (p_sys->session && p_sys->b_vt_feed)
|
|
VTDecompressionSessionWaitForAsynchronousFrames(p_sys->session);
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
assert(RemoveOneFrameFromDPB(p_sys) == NULL);
|
|
p_sys->b_vt_flush = false;
|
|
p_sys->b_vt_feed = false;
|
|
p_sys->b_drop_blocks = false;
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
|
|
static int DecodeBlock(decoder_t *p_dec, block_t *p_block)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
|
|
if (p_sys->b_vt_flush)
|
|
{
|
|
Drain(p_dec, true);
|
|
PtsInit(p_dec);
|
|
}
|
|
|
|
if (p_block == NULL)
|
|
{
|
|
Drain(p_dec, false);
|
|
return VLCDEC_SUCCESS;
|
|
}
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
|
|
if (p_block->i_flags & BLOCK_FLAG_INTERLACED_MASK)
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
msg_Warn(p_dec, "VT decoder doesn't handle deinterlacing on iOS, "
|
|
"aborting...");
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
#else
|
|
if (!p_sys->b_cvpx_format_forced
|
|
&& p_sys->i_cvpx_format == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)
|
|
{
|
|
/* In case of interlaced content, force VT to output I420 since our
|
|
* SW deinterlacer handle this chroma natively. This avoids having
|
|
* 2 extra conversions (CVPX->I420 then I420->CVPX). */
|
|
|
|
p_sys->i_cvpx_format = kCVPixelFormatType_420YpCbCr8Planar;
|
|
msg_Warn(p_dec, "Interlaced content: forcing VT to output I420");
|
|
if (p_sys->session != nil && p_sys->vtsession_status == VTSESSION_STATUS_OK)
|
|
{
|
|
msg_Warn(p_dec, "restarting vt session (color changed)");
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
/* Drain before stopping */
|
|
Drain(p_dec, false);
|
|
StopVideoToolbox(p_dec);
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (p_sys->vtsession_status == VTSESSION_STATUS_RESTART ||
|
|
p_sys->vtsession_status == VTSESSION_STATUS_RESTART_CHROMA)
|
|
{
|
|
bool do_restart;
|
|
if (p_sys->vtsession_status == VTSESSION_STATUS_RESTART_CHROMA)
|
|
{
|
|
if (p_sys->i_cvpx_format == 0 && p_sys->b_cvpx_format_forced)
|
|
{
|
|
/* Already tried to fallback to the original chroma, aborting... */
|
|
do_restart = false;
|
|
}
|
|
else
|
|
{
|
|
p_sys->i_cvpx_format = 0;
|
|
p_sys->b_cvpx_format_forced = true;
|
|
do_restart = true;
|
|
}
|
|
}
|
|
else
|
|
do_restart = p_sys->i_restart_count <= VT_RESTART_MAX;
|
|
|
|
if (do_restart)
|
|
{
|
|
msg_Warn(p_dec, "restarting vt session (dec callback failed)");
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
/* Session will be started by Late Start code block */
|
|
StopVideoToolbox(p_dec);
|
|
if (p_dec->fmt_in.i_extra == 0)
|
|
{
|
|
/* Clean old parameter sets since they may be corrupt */
|
|
hxxx_helper_clean(&p_sys->hh);
|
|
}
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->vtsession_status = VTSESSION_STATUS_OK;
|
|
}
|
|
else
|
|
{
|
|
msg_Warn(p_dec, "too many vt failure...");
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
}
|
|
}
|
|
|
|
if (p_sys->vtsession_status == VTSESSION_STATUS_ABORT)
|
|
{
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
msg_Err(p_dec, "decoder failure, Abort.");
|
|
/* Add an empty variable so that videotoolbox won't be loaded again for
|
|
* this ES */
|
|
var_Create(p_dec, "videotoolbox-failed", VLC_VAR_VOID);
|
|
return VLCDEC_RELOAD;
|
|
}
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
if (unlikely(p_block->i_flags&(BLOCK_FLAG_CORRUPTED)))
|
|
{
|
|
if (p_sys->b_vt_feed)
|
|
{
|
|
Drain(p_dec, false);
|
|
PtsInit(p_dec);
|
|
}
|
|
goto skip;
|
|
}
|
|
|
|
bool b_config_changed = false;
|
|
if(p_sys->pf_process_block)
|
|
{
|
|
p_block = p_sys->pf_process_block(p_dec, p_block, &b_config_changed);
|
|
if (!p_block)
|
|
return VLCDEC_SUCCESS;
|
|
}
|
|
|
|
frame_info_t *p_info = CreateReorderInfo(p_dec, p_block);
|
|
if(unlikely(!p_info))
|
|
goto skip;
|
|
|
|
if (!p_sys->session /* Late Start */||
|
|
(b_config_changed && p_info->b_flush))
|
|
{
|
|
if (p_sys->session &&
|
|
p_sys->pf_need_restart &&
|
|
p_sys->pf_need_restart(p_dec,p_sys->session))
|
|
{
|
|
msg_Dbg(p_dec, "parameters sets changed: draining decoder");
|
|
Drain(p_dec, false);
|
|
msg_Dbg(p_dec, "parameters sets changed: restarting decoder");
|
|
StopVideoToolbox(p_dec);
|
|
}
|
|
|
|
if(!p_sys->session)
|
|
{
|
|
if ((p_sys->pf_codec_supported && !p_sys->pf_codec_supported(p_dec))
|
|
|| StartVideoToolbox(p_dec) != VLC_SUCCESS)
|
|
{
|
|
/* The current device doesn't handle the profile/level, abort */
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
}
|
|
|
|
if (!p_sys->session) /* Start Failed */
|
|
{
|
|
free(p_info);
|
|
goto skip;
|
|
}
|
|
}
|
|
|
|
if (!p_sys->b_vt_feed && p_sys->b_vt_need_keyframe && !p_info->b_keyframe)
|
|
{
|
|
free(p_info);
|
|
goto skip;
|
|
}
|
|
|
|
CMSampleBufferRef sampleBuffer =
|
|
VTSampleBufferCreate(p_dec, p_sys->videoFormatDescription, p_block);
|
|
if (unlikely(!sampleBuffer))
|
|
{
|
|
free(p_info);
|
|
goto skip;
|
|
}
|
|
|
|
VTDecodeInfoFlags flagOut;
|
|
VTDecodeFrameFlags decoderFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
|
|
|
|
OSStatus status =
|
|
VTDecompressionSessionDecodeFrame(p_sys->session, sampleBuffer,
|
|
decoderFlags, p_info, &flagOut);
|
|
|
|
enum vtsession_status vtsession_status;
|
|
if (HandleVTStatus(p_dec, status, &vtsession_status) == VLC_SUCCESS)
|
|
{
|
|
p_sys->b_vt_feed = true;
|
|
if( p_block->i_flags & BLOCK_FLAG_END_OF_SEQUENCE )
|
|
Drain( p_dec, false );
|
|
}
|
|
else
|
|
{
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
if (vtsession_status == VTSESSION_STATUS_RESTART)
|
|
p_sys->i_restart_count++;
|
|
p_sys->vtsession_status = vtsession_status;
|
|
/* In case of abort, the decoder module will be reloaded next time
|
|
* since we already modified the input block */
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
}
|
|
CFRelease(sampleBuffer);
|
|
|
|
skip:
|
|
block_Release(p_block);
|
|
return VLCDEC_SUCCESS;
|
|
}
|
|
|
|
static int UpdateVideoFormat(decoder_t *p_dec, CVPixelBufferRef imageBuffer)
|
|
{
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
NSDictionary *attachmentDict =
|
|
(__bridge NSDictionary *)CVBufferGetAttachments(imageBuffer, kCVAttachmentMode_ShouldPropagate);
|
|
|
|
if (attachmentDict != nil && attachmentDict.count > 0
|
|
&& p_dec->fmt_out.video.chroma_location == CHROMA_LOCATION_UNDEF)
|
|
{
|
|
NSString *chromaLocation = attachmentDict[(NSString *)kCVImageBufferChromaLocationTopFieldKey];
|
|
if (chromaLocation != nil) {
|
|
if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_Left] ||
|
|
[chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_DV420])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_LEFT;
|
|
else if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_Center])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_CENTER;
|
|
else if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_TopLeft])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_TOP_LEFT;
|
|
else if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_Top])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_TOP_CENTER;
|
|
}
|
|
if (p_dec->fmt_out.video.chroma_location == CHROMA_LOCATION_UNDEF)
|
|
{
|
|
chromaLocation = attachmentDict[(NSString *)kCVImageBufferChromaLocationBottomFieldKey];
|
|
if (chromaLocation != nil) {
|
|
if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_BottomLeft])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_BOTTOM_LEFT;
|
|
else if ([chromaLocation isEqualToString:(NSString *)kCVImageBufferChromaLocation_Bottom])
|
|
p_dec->fmt_out.video.chroma_location = CHROMA_LOCATION_BOTTOM_CENTER;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t cvfmt = CVPixelBufferGetPixelFormatType(imageBuffer);
|
|
msg_Info(p_dec, "vt cvpx chroma: %4.4s",
|
|
(const char *)&(uint32_t) { htonl(cvfmt) });
|
|
switch (cvfmt)
|
|
{
|
|
case kCVPixelFormatType_422YpCbCr8:
|
|
case 'yuv2':
|
|
p_dec->fmt_out.i_codec = VLC_CODEC_CVPX_UYVY;
|
|
assert(CVPixelBufferIsPlanar(imageBuffer) == false);
|
|
break;
|
|
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
|
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
|
p_dec->fmt_out.i_codec = VLC_CODEC_CVPX_NV12;
|
|
assert(CVPixelBufferIsPlanar(imageBuffer) == true);
|
|
break;
|
|
case 'xf20': /* kCVPixelFormatType_420YpCbCr10BiPlanarFullRange */
|
|
case 'x420': /* kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange */
|
|
p_dec->fmt_out.i_codec = VLC_CODEC_CVPX_P010;
|
|
assert(CVPixelBufferIsPlanar(imageBuffer) == true);
|
|
break;
|
|
case kCVPixelFormatType_420YpCbCr8Planar:
|
|
p_dec->fmt_out.i_codec = VLC_CODEC_CVPX_I420;
|
|
assert(CVPixelBufferIsPlanar(imageBuffer) == true);
|
|
break;
|
|
case kCVPixelFormatType_32BGRA:
|
|
p_dec->fmt_out.i_codec = VLC_CODEC_CVPX_BGRA;
|
|
assert(CVPixelBufferIsPlanar(imageBuffer) == false);
|
|
break;
|
|
default:
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
return -1;
|
|
}
|
|
p_dec->fmt_out.video.i_chroma = p_dec->fmt_out.i_codec;
|
|
if (decoder_UpdateVideoFormat(p_dec) != 0)
|
|
{
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
pic_holder_on_cvpx_released(CVPixelBufferRef cvpx, void *data, unsigned nb_fields)
|
|
{
|
|
struct pic_holder *pic_holder = data;
|
|
|
|
vlc_mutex_lock(&pic_holder->lock);
|
|
assert((int) pic_holder->nb_field_out - nb_fields >= 0);
|
|
pic_holder->nb_field_out -= nb_fields;
|
|
if (pic_holder->nb_field_out == 0 && pic_holder->closed)
|
|
{
|
|
vlc_mutex_unlock(&pic_holder->lock);
|
|
pic_holder_clean(pic_holder);
|
|
}
|
|
else
|
|
{
|
|
vlc_cond_broadcast(&pic_holder->wait);
|
|
vlc_mutex_unlock(&pic_holder->lock);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pic_holder_update_reorder_max(struct pic_holder *pic_holder, uint8_t pic_reorder_max,
|
|
uint8_t nb_field)
|
|
{
|
|
vlc_mutex_lock(&pic_holder->lock);
|
|
|
|
pic_holder->field_reorder_max = pic_reorder_max * (nb_field < 2 ? 2 : nb_field);
|
|
vlc_cond_signal(&pic_holder->wait);
|
|
|
|
vlc_mutex_unlock(&pic_holder->lock);
|
|
}
|
|
|
|
static int pic_holder_wait(struct pic_holder *pic_holder, const picture_t *pic)
|
|
{
|
|
const uint8_t reserved_fields = 2 * (pic->i_nb_fields < 2 ? 2 : pic->i_nb_fields);
|
|
|
|
vlc_mutex_lock(&pic_holder->lock);
|
|
|
|
/* Wait 200 ms max. We can't really know what the video output will do with
|
|
* output pictures (will they be rendered immediately ?), so don't wait
|
|
* infinitely. The output will be paced anyway by the vlc_cond_timedwait()
|
|
* call. */
|
|
mtime_t deadline = mdate() + INT64_C(200000);
|
|
int ret = 0;
|
|
while (ret == 0 && pic_holder->field_reorder_max != 0
|
|
&& pic_holder->nb_field_out >= pic_holder->field_reorder_max + reserved_fields)
|
|
ret = vlc_cond_timedwait(&pic_holder->wait, &pic_holder->lock, deadline);
|
|
pic_holder->nb_field_out += pic->i_nb_fields;
|
|
|
|
vlc_mutex_unlock(&pic_holder->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void DecoderCallback(void *decompressionOutputRefCon,
|
|
void *sourceFrameRefCon,
|
|
OSStatus status,
|
|
VTDecodeInfoFlags infoFlags,
|
|
CVPixelBufferRef imageBuffer,
|
|
CMTime pts,
|
|
CMTime duration)
|
|
{
|
|
VLC_UNUSED(duration);
|
|
decoder_t *p_dec = (decoder_t *)decompressionOutputRefCon;
|
|
decoder_sys_t *p_sys = p_dec->p_sys;
|
|
frame_info_t *p_info = (frame_info_t *) sourceFrameRefCon;
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
if (p_sys->b_vt_flush)
|
|
goto end;
|
|
|
|
enum vtsession_status vtsession_status;
|
|
if (HandleVTStatus(p_dec, status, &vtsession_status) != VLC_SUCCESS)
|
|
{
|
|
if (p_sys->vtsession_status != VTSESSION_STATUS_ABORT)
|
|
{
|
|
p_sys->vtsession_status = vtsession_status;
|
|
if (vtsession_status == VTSESSION_STATUS_RESTART)
|
|
p_sys->i_restart_count++;
|
|
}
|
|
goto end;
|
|
}
|
|
if (unlikely(!imageBuffer))
|
|
{
|
|
msg_Err(p_dec, "critical: null imageBuffer with a valid status");
|
|
p_sys->vtsession_status = VTSESSION_STATUS_ABORT;
|
|
goto end;
|
|
}
|
|
|
|
if (p_sys->vtsession_status == VTSESSION_STATUS_ABORT)
|
|
goto end;
|
|
|
|
if (unlikely(!p_sys->b_format_propagated)) {
|
|
p_sys->b_format_propagated =
|
|
UpdateVideoFormat(p_dec, imageBuffer) == VLC_SUCCESS;
|
|
|
|
if (!p_sys->b_format_propagated)
|
|
goto end;
|
|
assert(p_dec->fmt_out.i_codec != 0);
|
|
}
|
|
|
|
if (infoFlags & kVTDecodeInfo_FrameDropped)
|
|
{
|
|
/* We can't trust VT, some decoded frames can be marked as dropped */
|
|
msg_Dbg(p_dec, "decoder dropped frame");
|
|
}
|
|
|
|
if (!CMTIME_IS_VALID(pts))
|
|
goto end;
|
|
|
|
if (CVPixelBufferGetDataSize(imageBuffer) == 0)
|
|
goto end;
|
|
|
|
if(likely(p_info))
|
|
{
|
|
/* Unlock the mutex because decoder_NewPicture() is blocking. Indeed,
|
|
* it can wait indefinitely when the input is paused. */
|
|
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
|
|
picture_t *p_pic = decoder_NewPicture(p_dec);
|
|
if (!p_pic)
|
|
{
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
goto end;
|
|
}
|
|
|
|
p_info->p_picture = p_pic;
|
|
|
|
p_pic->date = pts.value;
|
|
p_pic->b_force = p_info->b_forced;
|
|
p_pic->b_progressive = p_info->b_progressive;
|
|
if(!p_pic->b_progressive)
|
|
{
|
|
p_pic->i_nb_fields = p_info->i_num_ts;
|
|
p_pic->b_top_field_first = p_info->b_top_field_first;
|
|
}
|
|
|
|
if (cvpxpic_attach_with_cb(p_pic, imageBuffer, pic_holder_on_cvpx_released,
|
|
p_sys->pic_holder) != VLC_SUCCESS)
|
|
{
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
goto end;
|
|
}
|
|
|
|
/* VT is not pacing frame allocation. If we are not fast enough to
|
|
* render (release) the output pictures, the VT session can end up
|
|
* allocating way too many frames. This can be problematic for 4K
|
|
* 10bits. To fix this issue, we ensure that we don't have too many
|
|
* output frames allocated by waiting for the vout to release them.
|
|
*
|
|
* FIXME: A proper way to fix this issue is to allow decoder modules to
|
|
* specify the dpb and having the vout re-allocating output frames when
|
|
* this number changes. */
|
|
if (pic_holder_wait(p_sys->pic_holder, p_pic))
|
|
msg_Warn(p_dec, "pic_holder_wait timed out");
|
|
|
|
|
|
vlc_mutex_lock(&p_sys->lock);
|
|
|
|
if (p_sys->b_vt_flush)
|
|
{
|
|
picture_Release(p_pic);
|
|
goto end;
|
|
}
|
|
|
|
p_sys->i_restart_count = 0;
|
|
|
|
OnDecodedFrame( p_dec, p_info );
|
|
p_info = NULL;
|
|
}
|
|
|
|
end:
|
|
free(p_info);
|
|
vlc_mutex_unlock(&p_sys->lock);
|
|
return;
|
|
}
|
|
|