[FFmpeg-devel] [PATCH 2/2] lavc/videotoolboxenc: set extradata when opening codec
Richard Kern
kernrj at gmail.com
Wed Jun 8 02:56:58 CEST 2016
Apple confirmed there’s no way to get the parameter sets earlier through VideoToolbox. If no one reviews, I’ll push in about 24 h.
> On Jun 2, 2016, at 2:43 AM, Rick Kern <kernrj at gmail.com> wrote:
>
> VideoToolbox doesn't supply parameter sets until the first frame is done
> encoding. This spins up a temporary encoder and encodes a single frame to
> get this data.
>
> Signed-off-by: Rick Kern <kernrj at gmail.com>
> ---
> libavcodec/videotoolboxenc.c | 326 ++++++++++++++++++++++++++++++++-----------
> 1 file changed, 241 insertions(+), 85 deletions(-)
>
> diff --git a/libavcodec/videotoolboxenc.c b/libavcodec/videotoolboxenc.c
> index f4f0d8e..895924a 100644
> --- a/libavcodec/videotoolboxenc.c
> +++ b/libavcodec/videotoolboxenc.c
> @@ -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;
> + frame->colorspace = avctx->colorspace;
> + frame->color_trc = avctx->color_trc;
> + frame->color_range = avctx->color_range;
> + 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;
> --
> 2.7.4
>
More information about the ffmpeg-devel
mailing list