diff options
Diffstat (limited to '')
-rw-r--r-- | filters/f_autoconvert.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/filters/f_autoconvert.c b/filters/f_autoconvert.c new file mode 100644 index 0000000..dcd5ea2 --- /dev/null +++ b/filters/f_autoconvert.c @@ -0,0 +1,576 @@ +#include "audio/aframe.h" +#include "audio/chmap_sel.h" +#include "audio/format.h" +#include "common/common.h" +#include "common/msg.h" +#include "video/hwdec.h" +#include "video/mp_image.h" +#include "video/mp_image_pool.h" + +#include "f_autoconvert.h" +#include "f_hwtransfer.h" +#include "f_swresample.h" +#include "f_swscale.h" +#include "f_utils.h" +#include "filter.h" +#include "filter_internal.h" + +struct priv { + struct mp_log *log; + + struct mp_subfilter sub; + + bool force_update; + + int *imgfmts; + int *subfmts; + int num_imgfmts; + struct mp_image_params imgparams; + bool imgparams_set; + + // Enable special conversion for the final stage before the VO. + bool vo_convert; + + // sws state + int in_imgfmt, in_subfmt; + + int *afmts; + int num_afmts; + int *srates; + int num_srates; + struct mp_chmap_sel chmaps; + + int in_afmt, in_srate; + struct mp_chmap in_chmap; + + double audio_speed; + bool resampling_forced; + + bool format_change_blocked; + bool format_change_cont; + + struct mp_autoconvert public; +}; + +// Dummy filter for bundling sub-conversion filters. +static const struct mp_filter_info convert_filter = { + .name = "convert", +}; + +void mp_autoconvert_clear(struct mp_autoconvert *c) +{ + struct priv *p = c->f->priv; + + p->num_imgfmts = 0; + p->imgparams_set = false; + p->num_afmts = 0; + p->num_srates = 0; + p->chmaps = (struct mp_chmap_sel){0}; + p->force_update = true; +} + +void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt) +{ + struct priv *p = c->f->priv; + + MP_TARRAY_GROW(p, p->imgfmts, p->num_imgfmts); + MP_TARRAY_GROW(p, p->subfmts, p->num_imgfmts); + + p->imgfmts[p->num_imgfmts] = imgfmt; + p->subfmts[p->num_imgfmts] = subfmt; + + p->num_imgfmts += 1; + p->force_update = true; +} + +void mp_autoconvert_set_target_image_params(struct mp_autoconvert *c, + struct mp_image_params *par) +{ + struct priv *p = c->f->priv; + + if (p->imgparams_set && mp_image_params_equal(&p->imgparams, par) && + p->num_imgfmts == 1 && p->imgfmts[0] == par->imgfmt && + p->subfmts[0] == par->hw_subfmt) + return; + + p->imgparams = *par; + p->imgparams_set = true; + + p->num_imgfmts = 0; + mp_autoconvert_add_imgfmt(c, par->imgfmt, par->hw_subfmt); +} + +void mp_autoconvert_add_all_sw_imgfmts(struct mp_autoconvert *c) +{ + for (int n = IMGFMT_START; n < IMGFMT_END; n++) { + if (!IMGFMT_IS_HWACCEL(n)) + mp_autoconvert_add_imgfmt(c, n, 0); + } +} + +void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt) +{ + struct priv *p = c->f->priv; + + MP_TARRAY_APPEND(p, p->afmts, p->num_afmts, afmt); + p->force_update = true; +} + +void mp_autoconvert_add_chmap(struct mp_autoconvert *c, struct mp_chmap *chmap) +{ + struct priv *p = c->f->priv; + + mp_chmap_sel_add_map(&p->chmaps, chmap); + p->force_update = true; +} + +void mp_autoconvert_add_srate(struct mp_autoconvert *c, int rate) +{ + struct priv *p = c->f->priv; + + MP_TARRAY_APPEND(p, p->srates, p->num_srates, rate); + // Some other API we call expects a 0-terminated sample rates array. + MP_TARRAY_GROW(p, p->srates, p->num_srates); + p->srates[p->num_srates] = 0; + p->force_update = true; +} + +// If this returns true, and *out==NULL, no conversion is necessary. +static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log, + struct mp_image *img, struct mp_filter **f_out) +{ + struct mp_filter *f = c->f; + struct priv *p = f->priv; + + *f_out = NULL; + + if (!p->num_imgfmts) + return true; + + for (int n = 0; n < p->num_imgfmts; n++) { + bool samefmt = img->params.imgfmt == p->imgfmts[n]; + bool samesubffmt = img->params.hw_subfmt == p->subfmts[n]; + /* + * In practice, `p->subfmts` is not usually populated today, in which + * case we must actively probe formats below to establish if the VO can + * accept the subfmt being used by the hwdec. + */ + if (samefmt && samesubffmt) { + if (p->imgparams_set) { + if (!mp_image_params_equal(&p->imgparams, &img->params)) + break; + } + return true; + } + } + + struct mp_filter *conv = mp_filter_create(f, &convert_filter); + if (!conv) + return false; + + mp_filter_add_pin(conv, MP_PIN_IN, "in"); + mp_filter_add_pin(conv, MP_PIN_OUT, "out"); + + // 0: hw->sw download + // 1: swscale + // 2: sw->hw upload + struct mp_filter *filters[3] = {0}; + bool need_sws = true; + bool force_sws_params = false; + struct mp_image_params imgpar = img->params; + + int *fmts = p->imgfmts; + int num_fmts = p->num_imgfmts; + int hwupload_fmt = 0; + + bool imgfmt_is_sw = !IMGFMT_IS_HWACCEL(img->imgfmt); + + // This should not happen. But not enough guarantee to make it an assert(). + if (imgfmt_is_sw != !img->hwctx) + mp_warn(log, "Unexpected AVFrame/imgfmt hardware context mismatch.\n"); + + bool dst_all_hw = true; + bool dst_have_sw = false; + bool has_src_hw_fmt = false; + for (int n = 0; n < num_fmts; n++) { + bool is_hw = IMGFMT_IS_HWACCEL(fmts[n]); + dst_all_hw &= is_hw; + dst_have_sw |= !is_hw; + has_src_hw_fmt |= is_hw && fmts[n] == imgpar.imgfmt; + } + + // Source is hw, some targets are sw -> try to download. + bool hw_to_sw = !imgfmt_is_sw && dst_have_sw; + + if (has_src_hw_fmt) { + int src_fmt = img->params.hw_subfmt; + /* + * If the source format is a hardware format, and our output supports + * that hardware format, we prioritize preserving the use of that + * hardware format. In most cases, the sub format will also be supported + * and no conversion will be required, but in some cases, the hwdec + * may be able to output formats that the VO cannot display, and + * hardware format conversion becomes necessary. + */ + struct mp_hwupload upload = mp_hwupload_create(conv, imgpar.imgfmt, + src_fmt, + true); + if (upload.successful_init) { + if (upload.f) { + mp_info(log, "Converting %s[%s] -> %s[%s]\n", + mp_imgfmt_to_name(imgpar.imgfmt), + mp_imgfmt_to_name(src_fmt), + mp_imgfmt_to_name(imgpar.imgfmt), + mp_imgfmt_to_name(upload.selected_sw_imgfmt)); + filters[2] = upload.f; + } + hw_to_sw = false; + need_sws = false; + } else { + mp_err(log, "Failed to create HW uploader for format %s\n", + mp_imgfmt_to_name(src_fmt)); + } + } else if (dst_all_hw && num_fmts > 0) { + bool upload_created = false; + int sw_fmt = imgfmt_is_sw ? img->imgfmt : img->params.hw_subfmt; + + for (int i = 0; i < num_fmts; i++) { + // We can probably use this! Very lazy and very approximate. + struct mp_hwupload upload = mp_hwupload_create(conv, fmts[i], + sw_fmt, false); + if (upload.successful_init) { + mp_info(log, "HW-uploading to %s\n", mp_imgfmt_to_name(fmts[i])); + filters[2] = upload.f; + hwupload_fmt = upload.selected_sw_imgfmt; + fmts = &hwupload_fmt; + num_fmts = hwupload_fmt ? 1 : 0; + hw_to_sw = false; + + // We cannot do format conversions when transferring between + // two hardware devices, so reject this format if that would be + // required. + if (!imgfmt_is_sw && hwupload_fmt != sw_fmt) { + mp_err(log, "Format %s is not supported by %s\n", + mp_imgfmt_to_name(sw_fmt), + mp_imgfmt_to_name(p->imgfmts[i])); + continue; + } + upload_created = true; + break; + } + } + if (!upload_created) { + mp_err(log, "Failed to create HW uploader for format %s\n", + mp_imgfmt_to_name(sw_fmt)); + } + } + + int src_fmt = img->imgfmt; + if (hw_to_sw) { + mp_info(log, "HW-downloading from %s\n", mp_imgfmt_to_name(img->imgfmt)); + int res_fmt = mp_image_hw_download_get_sw_format(img); + if (!res_fmt) { + mp_err(log, "cannot copy surface of this format to CPU memory\n"); + goto fail; + } + struct mp_hwdownload *hwd = mp_hwdownload_create(conv); + if (hwd) { + filters[0] = hwd->f; + src_fmt = res_fmt; + // Downloading from hw will obviously change the parameters. We + // stupidly don't know the result parameters, but if it's + // sufficiently sane, it will only do the following. + imgpar.imgfmt = src_fmt; + imgpar.hw_subfmt = 0; + // Try to compensate for in-sane cases. + mp_image_params_guess_csp(&imgpar); + } + } + + if (p->imgparams_set) { + force_sws_params |= !mp_image_params_equal(&imgpar, &p->imgparams); + need_sws |= force_sws_params; + } + if (!imgfmt_is_sw && dst_all_hw) { + // This is a hw -> hw upload, so the sw format must already be + // mutually understood. No conversion can be done. + need_sws = false; + } + + if (need_sws) { + // Create a new conversion filter. + struct mp_sws_filter *sws = mp_sws_filter_create(conv); + if (!sws) { + mp_err(log, "error creating conversion filter\n"); + goto fail; + } + + sws->force_scaler = c->force_scaler; + + int out = mp_sws_find_best_out_format(sws, src_fmt, fmts, num_fmts); + if (!out) { + mp_err(log, "can't find video conversion for %s\n", + mp_imgfmt_to_name(src_fmt)); + goto fail; + } + + if (out == src_fmt && !force_sws_params) { + // Can happen if hwupload goes to same format. + talloc_free(sws->f); + } else { + sws->out_format = out; + sws->out_params = p->imgparams; + sws->use_out_params = force_sws_params; + mp_info(log, "Converting %s -> %s\n", mp_imgfmt_to_name(src_fmt), + mp_imgfmt_to_name(sws->out_format)); + filters[1] = sws->f; + } + } + + mp_chain_filters(conv->ppins[0], conv->ppins[1], filters, 3); + + *f_out = conv; + return true; + +fail: + talloc_free(conv); + return false; +} + +bool mp_autoconvert_probe_input_video(struct mp_autoconvert *c, + struct mp_image *img) +{ + struct mp_filter *conv = NULL; + bool res = build_image_converter(c, mp_null_log, img, &conv); + talloc_free(conv); + return res; +} + +static void handle_video_frame(struct mp_filter *f) +{ + struct priv *p = f->priv; + + struct mp_image *img = p->sub.frame.data; + + if (p->force_update) + p->in_imgfmt = p->in_subfmt = 0; + + if (img->imgfmt == p->in_imgfmt && img->params.hw_subfmt == p->in_subfmt) { + mp_subfilter_continue(&p->sub); + return; + } + + if (!mp_subfilter_drain_destroy(&p->sub)) { + MP_VERBOSE(f, "Sub-filter requires draining but we must destroy it now.\n"); + mp_subfilter_destroy(&p->sub); + } + + p->in_imgfmt = img->params.imgfmt; + p->in_subfmt = img->params.hw_subfmt; + p->force_update = false; + + struct mp_filter *conv = NULL; + if (build_image_converter(&p->public, p->log, img, &conv)) { + p->sub.filter = conv; + mp_subfilter_continue(&p->sub); + } else { + mp_filter_internal_mark_failed(f); + } +} + +static void handle_audio_frame(struct mp_filter *f) +{ + struct priv *p = f->priv; + + struct mp_aframe *aframe = p->sub.frame.data; + + int afmt = mp_aframe_get_format(aframe); + int srate = mp_aframe_get_rate(aframe); + struct mp_chmap chmap = {0}; + mp_aframe_get_chmap(aframe, &chmap); + + if (p->resampling_forced && !af_fmt_is_pcm(afmt)) { + MP_WARN(p, "ignoring request to resample non-PCM audio for speed change\n"); + p->resampling_forced = false; + } + + bool format_change = afmt != p->in_afmt || + srate != p->in_srate || + !mp_chmap_equals(&chmap, &p->in_chmap) || + p->force_update; + + if (!format_change && (!p->resampling_forced || p->sub.filter)) + goto cont; + + if (!mp_subfilter_drain_destroy(&p->sub)) + return; + + if (format_change && p->public.on_audio_format_change) { + if (p->format_change_blocked) + return; + + if (!p->format_change_cont) { + p->format_change_blocked = true; + p->public. + on_audio_format_change(p->public.on_audio_format_change_opaque); + return; + } + p->format_change_cont = false; + } + + p->in_afmt = afmt; + p->in_srate = srate; + p->in_chmap = chmap; + p->force_update = false; + + int out_afmt = 0; + int best_score = 0; + for (int n = 0; n < p->num_afmts; n++) { + int score = af_format_conversion_score(p->afmts[n], afmt); + if (!out_afmt || score > best_score) { + best_score = score; + out_afmt = p->afmts[n]; + } + } + if (!out_afmt) + out_afmt = afmt; + + // (The p->srates array is 0-terminated already.) + int out_srate = af_select_best_samplerate(srate, p->srates); + if (out_srate <= 0) + out_srate = p->num_srates ? p->srates[0] : srate; + + struct mp_chmap out_chmap = chmap; + if (p->chmaps.num_chmaps) { + if (!mp_chmap_sel_adjust(&p->chmaps, &out_chmap)) + out_chmap = p->chmaps.chmaps[0]; // violently force fallback + } + + if (out_afmt == p->in_afmt && out_srate == p->in_srate && + mp_chmap_equals(&out_chmap, &p->in_chmap) && !p->resampling_forced) + { + goto cont; + } + + MP_VERBOSE(p, "inserting resampler\n"); + + struct mp_swresample *s = mp_swresample_create(f, NULL); + if (!s) + abort(); + + s->out_format = out_afmt; + s->out_rate = out_srate; + s->out_channels = out_chmap; + + p->sub.filter = s->f; + +cont: + + if (p->sub.filter) { + struct mp_filter_command cmd = { + .type = MP_FILTER_COMMAND_SET_SPEED_RESAMPLE, + .speed = p->audio_speed, + }; + mp_filter_command(p->sub.filter, &cmd); + } + + mp_subfilter_continue(&p->sub); +} + +static void process(struct mp_filter *f) +{ + struct priv *p = f->priv; + + if (!mp_subfilter_read(&p->sub)) + return; + + if (p->sub.frame.type == MP_FRAME_VIDEO) { + handle_video_frame(f); + return; + } + + if (p->sub.frame.type == MP_FRAME_AUDIO) { + handle_audio_frame(f); + return; + } + + mp_subfilter_continue(&p->sub); +} + +void mp_autoconvert_format_change_continue(struct mp_autoconvert *c) +{ + struct priv *p = c->f->priv; + + if (p->format_change_blocked) { + p->format_change_cont = true; + p->format_change_blocked = false; + mp_filter_wakeup(c->f); + } +} + +static bool command(struct mp_filter *f, struct mp_filter_command *cmd) +{ + struct priv *p = f->priv; + + if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) { + p->audio_speed = cmd->speed; + // If we needed resampling once, keep forcing resampling, as it might be + // quickly changing between 1.0 and other values for A/V compensation. + if (p->audio_speed != 1.0) + p->resampling_forced = true; + return true; + } + + if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) { + cmd->is_active = !!p->sub.filter; + return true; + } + + return false; +} + +static void reset(struct mp_filter *f) +{ + struct priv *p = f->priv; + + mp_subfilter_reset(&p->sub); + + p->format_change_cont = false; + p->format_change_blocked = false; +} + +static void destroy(struct mp_filter *f) +{ + struct priv *p = f->priv; + + mp_subfilter_reset(&p->sub); + TA_FREEP(&p->sub.filter); +} + +static const struct mp_filter_info autoconvert_filter = { + .name = "autoconvert", + .priv_size = sizeof(struct priv), + .process = process, + .command = command, + .reset = reset, + .destroy = destroy, +}; + +struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent) +{ + struct mp_filter *f = mp_filter_create(parent, &autoconvert_filter); + if (!f) + return NULL; + + mp_filter_add_pin(f, MP_PIN_IN, "in"); + mp_filter_add_pin(f, MP_PIN_OUT, "out"); + + struct priv *p = f->priv; + p->public.f = f; + p->log = f->log; + p->audio_speed = 1.0; + p->sub.in = f->ppins[0]; + p->sub.out = f->ppins[1]; + + return &p->public; +} |