|
|
|
@@ -96,6 +96,13 @@ typedef struct VTEncContext { |
|
|
|
bool warned_color_range; |
|
|
|
} VTEncContext; |
|
|
|
|
|
|
|
static int vtenc_populate_extradata(AVCodecContext *avctx, |
|
|
|
CMVideoCodecType codec_type, |
|
|
|
CFStringRef profile_level, |
|
|
|
CFNumberRef gamma_level, |
|
|
|
CFDictionaryRef enc_info, |
|
|
|
CFDictionaryRef pixel_buffer_info); |
|
|
|
|
|
|
|
/** |
|
|
|
* NULL-safe release of *refPtr, and sets value to NULL. |
|
|
|
*/ |
|
|
|
@@ -388,7 +395,7 @@ static int set_extradata(AVCodecContext *avctx, CMSampleBufferRef sample_buffer) |
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
avctx->extradata = av_malloc(total_size); |
|
|
|
avctx->extradata = av_mallocz(total_size + AV_INPUT_BUFFER_PADDING_SIZE); |
|
|
|
if (!avctx->extradata) { |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
} |
|
|
|
@@ -761,83 +768,28 @@ static int get_cv_ycbcr_matrix(AVCodecContext *avctx, CFStringRef *matrix) { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
static int vtenc_create_encoder(AVCodecContext *avctx, |
|
|
|
CMVideoCodecType codec_type, |
|
|
|
CFStringRef profile_level, |
|
|
|
CFNumberRef gamma_level, |
|
|
|
CFDictionaryRef enc_info, |
|
|
|
CFDictionaryRef pixel_buffer_info, |
|
|
|
VTCompressionSessionRef *session) |
|
|
|
{ |
|
|
|
CFMutableDictionaryRef enc_info; |
|
|
|
CFMutableDictionaryRef pixel_buffer_info; |
|
|
|
CMVideoCodecType codec_type; |
|
|
|
VTEncContext *vtctx = avctx->priv_data; |
|
|
|
CFStringRef profile_level; |
|
|
|
SInt32 bit_rate = avctx->bit_rate; |
|
|
|
CFNumberRef bit_rate_num; |
|
|
|
CFBooleanRef has_b_frames_cfbool; |
|
|
|
CFNumberRef gamma_level; |
|
|
|
int status; |
|
|
|
|
|
|
|
codec_type = get_cm_codec_type(avctx->codec_id); |
|
|
|
if (!codec_type) { |
|
|
|
av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
|
|
|
|
vtctx->has_b_frames = avctx->max_b_frames > 0; |
|
|
|
if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){ |
|
|
|
av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n"); |
|
|
|
vtctx->has_b_frames = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) { |
|
|
|
av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n"); |
|
|
|
vtctx->entropy = VT_ENTROPY_NOT_SET; |
|
|
|
} |
|
|
|
|
|
|
|
if (!get_vt_profile_level(avctx, &profile_level)) return AVERROR(EINVAL); |
|
|
|
|
|
|
|
vtctx->session = NULL; |
|
|
|
|
|
|
|
enc_info = CFDictionaryCreateMutable( |
|
|
|
kCFAllocatorDefault, |
|
|
|
20, |
|
|
|
&kCFCopyStringDictionaryKeyCallBacks, |
|
|
|
&kCFTypeDictionaryValueCallBacks |
|
|
|
); |
|
|
|
|
|
|
|
if (!enc_info) return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
#if !TARGET_OS_IPHONE |
|
|
|
if (!vtctx->allow_sw) { |
|
|
|
CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue); |
|
|
|
} else { |
|
|
|
CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder, kCFBooleanTrue); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
if (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) { |
|
|
|
status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info); |
|
|
|
if (status) { |
|
|
|
CFRelease(enc_info); |
|
|
|
return status; |
|
|
|
} |
|
|
|
} else { |
|
|
|
pixel_buffer_info = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
status = VTCompressionSessionCreate( |
|
|
|
kCFAllocatorDefault, |
|
|
|
avctx->width, |
|
|
|
avctx->height, |
|
|
|
codec_type, |
|
|
|
enc_info, |
|
|
|
pixel_buffer_info, |
|
|
|
kCFAllocatorDefault, |
|
|
|
vtenc_output_callback, |
|
|
|
avctx, |
|
|
|
&vtctx->session |
|
|
|
); |
|
|
|
|
|
|
|
if (pixel_buffer_info) CFRelease(pixel_buffer_info); |
|
|
|
CFRelease(enc_info); |
|
|
|
VTEncContext *vtctx = avctx->priv_data; |
|
|
|
SInt32 bit_rate = avctx->bit_rate; |
|
|
|
CFNumberRef bit_rate_num; |
|
|
|
|
|
|
|
int status = VTCompressionSessionCreate(kCFAllocatorDefault, |
|
|
|
avctx->width, |
|
|
|
avctx->height, |
|
|
|
codec_type, |
|
|
|
enc_info, |
|
|
|
pixel_buffer_info, |
|
|
|
kCFAllocatorDefault, |
|
|
|
vtenc_output_callback, |
|
|
|
avctx, |
|
|
|
session); |
|
|
|
|
|
|
|
if (status || !vtctx->session) { |
|
|
|
av_log(avctx, AV_LOG_ERROR, "Error: cannot create compression session: %d\n", status); |
|
|
|
@@ -982,8 +934,8 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
status = get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level); |
|
|
|
if (!status && vtctx->transfer_function) { |
|
|
|
|
|
|
|
if (vtctx->transfer_function) { |
|
|
|
status = VTSessionSetProperty(vtctx->session, |
|
|
|
kVTCompressionPropertyKey_TransferFunction, |
|
|
|
vtctx->transfer_function); |
|
|
|
@@ -993,8 +945,8 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
status = get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix); |
|
|
|
if (!status && vtctx->ycbcr_matrix) { |
|
|
|
|
|
|
|
if (vtctx->ycbcr_matrix) { |
|
|
|
status = VTSessionSetProperty(vtctx->session, |
|
|
|
kVTCompressionPropertyKey_YCbCrMatrix, |
|
|
|
vtctx->ycbcr_matrix); |
|
|
|
@@ -1004,8 +956,8 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
status = get_cv_color_primaries(avctx, &vtctx->color_primaries); |
|
|
|
if (!status && vtctx->color_primaries) { |
|
|
|
|
|
|
|
if (vtctx->color_primaries) { |
|
|
|
status = VTSessionSetProperty(vtctx->session, |
|
|
|
kVTCompressionPropertyKey_ColorPrimaries, |
|
|
|
vtctx->color_primaries); |
|
|
|
@@ -1015,7 +967,7 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!status && gamma_level) { |
|
|
|
if (gamma_level) { |
|
|
|
status = VTSessionSetProperty(vtctx->session, |
|
|
|
kCVImageBufferGammaLevelKey, |
|
|
|
gamma_level); |
|
|
|
@@ -1067,10 +1019,97 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
return AVERROR_EXTERNAL; |
|
|
|
} |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
{ |
|
|
|
CFMutableDictionaryRef enc_info; |
|
|
|
CFMutableDictionaryRef pixel_buffer_info; |
|
|
|
CMVideoCodecType codec_type; |
|
|
|
VTEncContext *vtctx = avctx->priv_data; |
|
|
|
CFStringRef profile_level; |
|
|
|
CFBooleanRef has_b_frames_cfbool; |
|
|
|
CFNumberRef gamma_level = NULL; |
|
|
|
int status; |
|
|
|
|
|
|
|
codec_type = get_cm_codec_type(avctx->codec_id); |
|
|
|
if (!codec_type) { |
|
|
|
av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
|
|
|
|
vtctx->has_b_frames = avctx->max_b_frames > 0; |
|
|
|
if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){ |
|
|
|
av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n"); |
|
|
|
vtctx->has_b_frames = false; |
|
|
|
} |
|
|
|
|
|
|
|
if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) { |
|
|
|
av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n"); |
|
|
|
vtctx->entropy = VT_ENTROPY_NOT_SET; |
|
|
|
} |
|
|
|
|
|
|
|
if (!get_vt_profile_level(avctx, &profile_level)) return AVERROR(EINVAL); |
|
|
|
|
|
|
|
vtctx->session = NULL; |
|
|
|
|
|
|
|
enc_info = CFDictionaryCreateMutable( |
|
|
|
kCFAllocatorDefault, |
|
|
|
20, |
|
|
|
&kCFCopyStringDictionaryKeyCallBacks, |
|
|
|
&kCFTypeDictionaryValueCallBacks |
|
|
|
); |
|
|
|
|
|
|
|
if (!enc_info) return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
#if !TARGET_OS_IPHONE |
|
|
|
if (!vtctx->allow_sw) { |
|
|
|
CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder, kCFBooleanTrue); |
|
|
|
} else { |
|
|
|
CFDictionarySetValue(enc_info, kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder, kCFBooleanTrue); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
if (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) { |
|
|
|
status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info); |
|
|
|
if (status) |
|
|
|
goto init_cleanup; |
|
|
|
} else { |
|
|
|
pixel_buffer_info = NULL; |
|
|
|
} |
|
|
|
|
|
|
|
pthread_mutex_init(&vtctx->lock, NULL); |
|
|
|
pthread_cond_init(&vtctx->cv_sample_sent, NULL); |
|
|
|
vtctx->dts_delta = vtctx->has_b_frames ? -1 : 0; |
|
|
|
|
|
|
|
get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level); |
|
|
|
get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix); |
|
|
|
get_cv_color_primaries(avctx, &vtctx->color_primaries); |
|
|
|
|
|
|
|
|
|
|
|
if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) { |
|
|
|
status = vtenc_populate_extradata(avctx, |
|
|
|
codec_type, |
|
|
|
profile_level, |
|
|
|
gamma_level, |
|
|
|
enc_info, |
|
|
|
pixel_buffer_info); |
|
|
|
if (status) |
|
|
|
goto init_cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
status = vtenc_create_encoder(avctx, |
|
|
|
codec_type, |
|
|
|
profile_level, |
|
|
|
gamma_level, |
|
|
|
enc_info, |
|
|
|
pixel_buffer_info, |
|
|
|
&vtctx->session); |
|
|
|
|
|
|
|
if (status < 0) |
|
|
|
goto init_cleanup; |
|
|
|
|
|
|
|
status = VTSessionCopyProperty(vtctx->session, |
|
|
|
kVTCompressionPropertyKey_AllowFrameReordering, |
|
|
|
kCFAllocatorDefault, |
|
|
|
@@ -1083,7 +1122,16 @@ static av_cold int vtenc_init(AVCodecContext *avctx) |
|
|
|
} |
|
|
|
avctx->has_b_frames = vtctx->has_b_frames; |
|
|
|
|
|
|
|
return 0; |
|
|
|
init_cleanup: |
|
|
|
if (gamma_level) |
|
|
|
CFRelease(gamma_level); |
|
|
|
|
|
|
|
if (pixel_buffer_info) |
|
|
|
CFRelease(pixel_buffer_info); |
|
|
|
|
|
|
|
CFRelease(enc_info); |
|
|
|
|
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
static void vtenc_get_frame_info(CMSampleBufferRef buffer, bool *is_key_frame) |
|
|
|
@@ -1753,6 +1801,114 @@ end_nopkt: |
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
static int vtenc_populate_extradata(AVCodecContext *avctx, |
|
|
|
CMVideoCodecType codec_type, |
|
|
|
CFStringRef profile_level, |
|
|
|
CFNumberRef gamma_level, |
|
|
|
CFDictionaryRef enc_info, |
|
|
|
CFDictionaryRef pixel_buffer_info) |
|
|
|
{ |
|
|
|
VTEncContext *vtctx = avctx->priv_data; |
|
|
|
AVFrame *frame = av_frame_alloc(); |
|
|
|
int y_size = avctx->width * avctx->height; |
|
|
|
int chroma_size = (avctx->width / 2) * (avctx->height / 2); |
|
|
|
CMSampleBufferRef buf = NULL; |
|
|
|
int status; |
|
|
|
|
|
|
|
if (!frame) |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
frame->buf[0] = av_buffer_alloc(y_size + 2 * chroma_size); |
|
|
|
|
|
|
|
if(!frame->buf[0]){ |
|
|
|
status = AVERROR(ENOMEM); |
|
|
|
goto pe_cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
status = vtenc_create_encoder(avctx, |
|
|
|
codec_type, |
|
|
|
profile_level, |
|
|
|
gamma_level, |
|
|
|
enc_info, |
|
|
|
pixel_buffer_info, |
|
|
|
&vtctx->session); |
|
|
|
if (status) |
|
|
|
goto pe_cleanup; |
|
|
|
|
|
|
|
frame->data[0] = frame->buf[0]->data; |
|
|
|
memset(frame->data[0], 0, y_size); |
|
|
|
|
|
|
|
frame->data[1] = frame->buf[0]->data + y_size; |
|
|
|
memset(frame->data[1], 128, chroma_size); |
|
|
|
|
|
|
|
|
|
|
|
if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) { |
|
|
|
frame->data[2] = frame->buf[0]->data + y_size + chroma_size; |
|
|
|
memset(frame->data[2], 128, chroma_size); |
|
|
|
} |
|
|
|
|
|
|
|
frame->linesize[0] = avctx->width; |
|
|
|
|
|
|
|
if (avctx->pix_fmt == AV_PIX_FMT_YUV420P) { |
|
|
|
frame->linesize[1] = |
|
|
|
frame->linesize[2] = (avctx->width + 1) / 2; |
|
|
|
} else { |
|
|
|
frame->linesize[1] = (avctx->width + 1) / 2; |
|
|
|
} |
|
|
|
|
|
|
|
frame->format = avctx->pix_fmt; |
|
|
|
frame->width = avctx->width; |
|
|
|
frame->height = avctx->height; |
|
|
|
av_frame_set_colorspace(frame, avctx->colorspace); |
|
|
|
av_frame_set_color_range(frame, avctx->color_range); |
|
|
|
frame->color_trc = avctx->color_trc; |
|
|
|
frame->color_primaries = avctx->color_primaries; |
|
|
|
|
|
|
|
frame->pts = 0; |
|
|
|
status = vtenc_send_frame(avctx, vtctx, frame); |
|
|
|
if (status) { |
|
|
|
av_log(avctx, AV_LOG_ERROR, "Error sending frame: %d\n", status); |
|
|
|
goto pe_cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
av_log(avctx, AV_LOG_INFO, "Completing\n"); |
|
|
|
|
|
|
|
//Populates extradata - output frames are flushed and param sets are available. |
|
|
|
status = VTCompressionSessionCompleteFrames(vtctx->session, |
|
|
|
kCMTimeIndefinite); |
|
|
|
|
|
|
|
|
|
|
|
av_log(avctx, AV_LOG_INFO, "Completed: %d\n", status); |
|
|
|
if (status) |
|
|
|
goto pe_cleanup; |
|
|
|
|
|
|
|
status = vtenc_q_pop(vtctx, 0, &buf); |
|
|
|
if (status) { |
|
|
|
av_log(avctx, AV_LOG_ERROR, "popping: %d\n", status); |
|
|
|
goto pe_cleanup; |
|
|
|
} |
|
|
|
|
|
|
|
CFRelease(buf); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pe_cleanup: |
|
|
|
if(vtctx->session) |
|
|
|
CFRelease(vtctx->session); |
|
|
|
|
|
|
|
vtctx->session = NULL; |
|
|
|
vtctx->frame_ct_out = 0; |
|
|
|
|
|
|
|
av_frame_unref(frame); |
|
|
|
av_frame_free(&frame); |
|
|
|
|
|
|
|
av_log(avctx, AV_LOG_INFO, "status %d ed %p size %d\n", status, avctx->extradata, avctx->extradata_size); |
|
|
|
|
|
|
|
av_assert0(status != 0 || (avctx->extradata && avctx->extradata_size > 0)); |
|
|
|
|
|
|
|
return status; |
|
|
|
} |
|
|
|
|
|
|
|
static av_cold int vtenc_close(AVCodecContext *avctx) |
|
|
|
{ |
|
|
|
VTEncContext *vtctx = avctx->priv_data; |
|
|
|
|