/* hbavfilter.c Copyright (c) 2003-2015 HandBrake Team This file is part of the HandBrake source code Homepage: . It may be used under the terms of the GNU General Public License v2. For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html */ #include "handbrake/handbrake.h" #include "handbrake/hbffmpeg.h" #include "libavfilter/avfilter.h" #include "libavfilter/buffersrc.h" #include "libavfilter/buffersink.h" #include "handbrake/hbavfilter.h" #include "handbrake/avfilter_priv.h" #if HB_PROJECT_FEATURE_QSV #include "handbrake/qsv_common.h" #endif struct hb_avfilter_graph_s { AVFilterGraph * avgraph; AVFilterContext * last; AVFilterContext * input; AVFilterContext * output; char * settings; AVFrame * frame; AVRational out_time_base; hb_job_t * job; }; static AVFilterContext * append_filter( hb_avfilter_graph_t * graph, const char * name, const char * args, AVBufferSrcParameters *par) { AVFilterContext * filter; int result; result = avfilter_graph_create_filter(&filter, avfilter_get_by_name(name), name, args, NULL, graph->avgraph); if (result < 0) { return NULL; } if (par) { result = av_buffersrc_parameters_set(filter, par); if (result < 0) { return NULL; } } if (graph->last != NULL) { result = avfilter_link(graph->last, 0, filter, 0); if (result < 0) { avfilter_free(filter); return NULL; } } graph->last = filter; return filter; } hb_avfilter_graph_t * hb_avfilter_graph_init(hb_value_t * settings, hb_filter_init_t * init) { hb_avfilter_graph_t * graph; AVFilterInOut * in = NULL, * out = NULL; AVBufferSrcParameters * par = NULL; AVFilterContext * avfilter; char * settings_str; int result; char * filter_args; graph = calloc(1, sizeof(hb_avfilter_graph_t)); if (graph == NULL) { return NULL; } settings_str = hb_filter_settings_string(HB_FILTER_AVFILTER, settings); if (settings_str == NULL) { hb_error("hb_avfilter_graph_init: no filter settings specified"); goto fail; } graph->job = init->job; graph->settings = settings_str; graph->avgraph = avfilter_graph_alloc(); if (graph->avgraph == NULL) { hb_error("hb_avfilter_graph_init: avfilter_graph_alloc failed"); goto fail; } #if HB_PROJECT_FEATURE_QSV if (!hb_qsv_hw_filters_are_enabled(graph->job)) #endif { av_opt_set(graph->avgraph, "scale_sws_opts", "lanczos+accurate_rnd", 0); } result = avfilter_graph_parse2(graph->avgraph, settings_str, &in, &out); if (result < 0 || in == NULL || out == NULL) { hb_error("hb_avfilter_graph_init: avfilter_graph_parse2 failed (%s)", settings_str); goto fail; } // Build filter input #if HB_PROJECT_FEATURE_QSV if (hb_qsv_hw_filters_are_enabled(graph->job)) { par = av_buffersrc_parameters_alloc(); filter_args = hb_strdup_printf( "video_size=%dx%d:pix_fmt=%d:sar=%d/%d:" "time_base=%d/%d:frame_rate=%d/%d", init->geometry.width, init->geometry.height, AV_PIX_FMT_QSV, init->geometry.par.num, init->geometry.par.den, init->time_base.num, init->time_base.den, init->vrate.num, init->vrate.den); AVBufferRef *hb_hw_frames_ctx = NULL; result = hb_create_ffmpeg_pool(graph->job, init->geometry.width, init->geometry.height, init->pix_fmt, 32, 0, &hb_hw_frames_ctx); if (result < 0) { hb_error("hb_create_ffmpeg_pool failed"); goto fail; } par->hw_frames_ctx = hb_hw_frames_ctx; // TODO: need to pass correct alignment for the scale_qsv output frame int out_alignment = 0; switch (graph->job->vcodec) { case HB_VCODEC_QSV_H264: out_alignment = 16; break; case HB_VCODEC_QSV_H265_10BIT: case HB_VCODEC_QSV_H265: out_alignment = 32; break; default: out_alignment = 0; break; } graph->avgraph->opaque = (void*)(intptr_t)out_alignment; } else #endif { filter_args = hb_strdup_printf( "width=%d:height=%d:pix_fmt=%d:sar=%d/%d:" "time_base=%d/%d:frame_rate=%d/%d", init->geometry.width, init->geometry.height, init->pix_fmt, init->geometry.par.num, init->geometry.par.den, init->time_base.num, init->time_base.den, init->vrate.num, init->vrate.den); } avfilter = append_filter(graph, "buffer", filter_args, par); av_free(par); free(filter_args); if (avfilter == NULL) { hb_error("hb_avfilter_graph_init: failed to create buffer source filter"); goto fail; } graph->input = avfilter; // Link input to filter chain created by avfilter_graph_parse2 result = avfilter_link(graph->last, 0, in->filter_ctx, 0); if (result < 0) { goto fail; } graph->last = out->filter_ctx; // Build filter output avfilter = append_filter(graph, "buffersink", NULL, 0); if (avfilter == NULL) { hb_error("hb_avfilter_graph_init: failed to create buffer output filter"); goto fail; } #if 0 // Set output pix fmt to YUV420P enum AVPixelFormat pix_fmts[2] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE}; if (av_opt_set_int_list(avfilter, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { hb_error("hb_avfilter_graph_init: failed to set buffersink pix_fmt"); goto fail; } #endif graph->output = avfilter; result = avfilter_graph_config(graph->avgraph, NULL); if (result < 0) { hb_error("hb_avfilter_graph_init: failed to configure filter graph"); goto fail; } graph->frame = av_frame_alloc(); if (graph->frame == NULL) { hb_error("hb_avfilter_graph_init: failed to allocate frame filter"); goto fail; } graph->out_time_base = graph->output->inputs[0]->time_base; avfilter_inout_free(&in); avfilter_inout_free(&out); return graph; fail: av_free(par); avfilter_inout_free(&in); avfilter_inout_free(&out); hb_avfilter_graph_close(&graph); return NULL; } const char * hb_avfilter_graph_settings(hb_avfilter_graph_t * graph) { return graph->settings; } void hb_avfilter_graph_close(hb_avfilter_graph_t ** _g) { hb_avfilter_graph_t * graph = *_g; if (graph == NULL) { return; } if (graph->avgraph != NULL) { avfilter_graph_free(&graph->avgraph); } free(graph->settings); av_frame_free(&graph->frame); free(graph); *_g = NULL; } void hb_avfilter_graph_update_init(hb_avfilter_graph_t * graph, hb_filter_init_t * init) { // Retrieve the parameters of the output filter AVFilterLink *link = graph->output->inputs[0]; init->geometry.width = link->w; init->geometry.height = link->h; init->geometry.par.num = link->sample_aspect_ratio.num; init->geometry.par.den = link->sample_aspect_ratio.den; init->pix_fmt = link->format; // avfilter can generate "unknown" framerates. If this happens // just pass along the source framerate. if (link->frame_rate.num > 0 && link->frame_rate.den > 0) { init->vrate.num = link->frame_rate.num; init->vrate.den = link->frame_rate.den; } } int hb_avfilter_add_frame(hb_avfilter_graph_t * graph, AVFrame * frame) { return av_buffersrc_add_frame(graph->input, frame); } int hb_avfilter_get_frame(hb_avfilter_graph_t * graph, AVFrame * frame) { return av_buffersink_get_frame(graph->output, frame); } int hb_avfilter_add_buf(hb_avfilter_graph_t * graph, hb_buffer_t * in) { if (in != NULL) { #if HB_PROJECT_FEATURE_QSV if (hb_qsv_hw_filters_are_enabled(graph->job)) { hb_video_buffer_to_avframe(in->qsv_details.frame, in); return hb_avfilter_add_frame(graph, in->qsv_details.frame); } else #endif { hb_video_buffer_to_avframe(graph->frame, in); return av_buffersrc_add_frame(graph->input, graph->frame); } } else { return av_buffersrc_add_frame(graph->input, NULL); } } hb_buffer_t * hb_avfilter_get_buf(hb_avfilter_graph_t * graph) { int result; result = av_buffersink_get_frame(graph->output, graph->frame); if (result >= 0) { hb_buffer_t * buf; #if HB_PROJECT_FEATURE_QSV if (hb_qsv_hw_filters_are_enabled(graph->job)) { buf = hb_qsv_copy_frame(graph->job, graph->frame, 1); hb_avframe_set_video_buffer_flags(buf, graph->frame, graph->out_time_base); } else #endif { buf = hb_avframe_to_video_buffer(graph->frame, graph->out_time_base); } av_frame_unref(graph->frame); return buf; } return NULL; } void hb_avfilter_combine( hb_list_t * list) { hb_filter_object_t * avfilter = NULL; hb_value_t * settings = NULL; int ii; for (ii = 0; ii < hb_list_count(list); ii++) { hb_filter_object_t * filter = hb_list_item(list, ii); hb_filter_private_t * pv = filter->private_data; switch (filter->id) { case HB_FILTER_AVFILTER: case HB_FILTER_DEINTERLACE: case HB_FILTER_DEBLOCK: case HB_FILTER_CROP_SCALE: case HB_FILTER_PAD: case HB_FILTER_ROTATE: case HB_FILTER_COLORSPACE: { settings = pv->avfilters; } break; default: { settings = NULL; avfilter = NULL; } break; } if (settings != NULL) { if (avfilter == NULL) { hb_filter_private_t * avpv = NULL; avfilter = hb_filter_init(HB_FILTER_AVFILTER); avfilter->aliased = 1; avpv = calloc(1, sizeof(struct hb_filter_private_s)); avfilter->private_data = avpv; avpv->input = pv->input; avfilter->settings = hb_value_array_init(); hb_list_insert(list, ii, avfilter); ii++; } hb_value_array_concat(avfilter->settings, settings); } } } void hb_avfilter_append_dict(hb_value_array_t * filters, const char * name, hb_value_t * settings) { hb_dict_t * filter_dict = hb_dict_init(); hb_dict_set(filter_dict, name, settings); hb_value_array_append(filters, filter_dict); }