2 changed files with 376 additions and 0 deletions
@ -0,0 +1,367 @@ |
|||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|||
/*****************************************************************************
|
|||
* amf_frc: Frame Rate doubler video with low frame rate |
|||
***************************************************************************** |
|||
* Copyright © 2024 Videolabs, VLC authors and VideoLAN |
|||
* |
|||
* Authors: Steve Lhomme <robux4@videolabs.io> |
|||
*****************************************************************************/ |
|||
|
|||
#ifdef HAVE_CONFIG_H |
|||
# include "config.h" |
|||
#endif |
|||
|
|||
#include <vlc_common.h> |
|||
#include <vlc_filter.h> |
|||
#include <vlc_plugin.h> |
|||
#include <vlc_configuration.h> |
|||
|
|||
#define COBJMACROS |
|||
#include "../hw/amf/amf_helper.h" |
|||
#include <AMF/components/FRC.h> |
|||
|
|||
#include "../video_chroma/d3d11_fmt.h" |
|||
|
|||
#include <assert.h> |
|||
|
|||
static const char *const ppsz_filter_options[] = { |
|||
"frc-indicator", NULL |
|||
}; |
|||
|
|||
static int D3D11CreateAMFFRC(filter_t *); |
|||
|
|||
vlc_module_begin() |
|||
set_description(N_("AMD Frame Rate Doubler")) |
|||
add_shortcut(N_("amf_frc")) |
|||
set_subcategory(SUBCAT_VIDEO_VFILTER) |
|||
set_callback_video_filter(D3D11CreateAMFFRC) |
|||
|
|||
add_bool( "frc-indicator", false, N_("Show indicator"), NULL ) |
|||
vlc_module_end() |
|||
|
|||
|
|||
struct filter_sys_t |
|||
{ |
|||
struct vlc_amf_context amf; |
|||
AMFComponent *amf_frc; |
|||
AMFSurface *amfInput; |
|||
const d3d_format_t *cfg; |
|||
|
|||
enum AMF_FRC_MODE_TYPE mode; |
|||
bool source_rate; |
|||
date_t next_output_pts; |
|||
}; |
|||
|
|||
static picture_t *PictureFromTexture(filter_t *filter, d3d11_device_t *d3d_dev, ID3D11Texture2D *out) |
|||
{ |
|||
struct filter_sys_t *sys = filter->p_sys; |
|||
|
|||
struct d3d11_pic_context *pic_ctx = calloc(1, sizeof(*pic_ctx)); |
|||
if (unlikely(pic_ctx == NULL)) |
|||
return NULL; |
|||
|
|||
picture_resource_t dummy_res = { .p_sys = NULL }; |
|||
picture_t *p_dst = picture_NewFromResource(&filter->fmt_out.video, &dummy_res); |
|||
if (p_dst == NULL) { |
|||
msg_Err(filter, "Failed to map create the temporary picture."); |
|||
goto done; |
|||
} |
|||
p_dst->context = &pic_ctx->s; |
|||
|
|||
D3D11_PictureAttach(p_dst, out, sys->cfg); |
|||
|
|||
if (unlikely(D3D11_AllocateResourceView(vlc_object_logger(filter), d3d_dev->d3ddevice, sys->cfg, |
|||
pic_ctx->picsys.texture, 0, pic_ctx->picsys.renderSrc) != VLC_SUCCESS)) |
|||
goto done; |
|||
|
|||
pic_ctx->s = (picture_context_t) { |
|||
d3d11_pic_context_destroy, d3d11_pic_context_copy, |
|||
vlc_video_context_Hold(filter->vctx_out), |
|||
}; |
|||
pic_ctx->picsys.sharedHandle = INVALID_HANDLE_VALUE; |
|||
|
|||
return p_dst; |
|||
done: |
|||
if (p_dst != NULL) |
|||
picture_Release(p_dst); |
|||
free(pic_ctx); |
|||
return NULL; |
|||
} |
|||
|
|||
static picture_t * Filter(filter_t *filter, picture_t *p_pic) |
|||
{ |
|||
struct filter_sys_t *sys = filter->p_sys; |
|||
|
|||
picture_sys_d3d11_t *src_sys = ActiveD3D11PictureSys(p_pic); |
|||
|
|||
AMF_RESULT res; |
|||
AMFSurface *submitSurface; |
|||
|
|||
AMFPlane *packedStaging = sys->amfInput->pVtbl->GetPlane(sys->amfInput, AMF_PLANE_PACKED); |
|||
ID3D11Resource *amfStaging = packedStaging->pVtbl->GetNative(packedStaging); |
|||
|
|||
#ifndef NDEBUG |
|||
ID3D11Texture2D *staging = (ID3D11Texture2D *)amfStaging; |
|||
D3D11_TEXTURE2D_DESC stagingDesc, inputDesc; |
|||
ID3D11Texture2D_GetDesc(staging, &stagingDesc); |
|||
ID3D11Texture2D_GetDesc(src_sys->texture[KNOWN_DXGI_INDEX], &inputDesc); |
|||
assert(stagingDesc.Width == inputDesc.Width); |
|||
assert(stagingDesc.Height == inputDesc.Height); |
|||
assert(stagingDesc.Format == inputDesc.Format); |
|||
#endif |
|||
|
|||
d3d11_decoder_device_t *dev_sys = GetD3D11OpaqueContext( filter->vctx_in ); |
|||
|
|||
#if 0 |
|||
if (src_sys->slice_index == 0) |
|||
sys->amf.Context->pVtbl->CreateSurfaceFromDX11Native(sys->amf.Context, ) |
|||
#endif |
|||
// copy source into staging as it may not be shared and we can't select a slice
|
|||
d3d11_device_lock( &dev_sys->d3d_dev ); |
|||
ID3D11DeviceContext_CopySubresourceRegion(dev_sys->d3d_dev.d3dcontext, amfStaging, |
|||
0, |
|||
0, 0, 0, |
|||
src_sys->resource[KNOWN_DXGI_INDEX], |
|||
src_sys->slice_index, |
|||
NULL); |
|||
d3d11_device_unlock( &dev_sys->d3d_dev ); |
|||
submitSurface = sys->amfInput; |
|||
|
|||
res = sys->amf_frc->pVtbl->SubmitInput(sys->amf_frc, (AMFData*)submitSurface); |
|||
if (res == AMF_INPUT_FULL) |
|||
{ |
|||
msg_Dbg(filter, "filter input full, skip this frame"); |
|||
return p_pic; |
|||
} |
|||
if (res != AMF_OK) |
|||
{ |
|||
msg_Err(filter, "filter input failed (err=%d)", res); |
|||
return p_pic; |
|||
} |
|||
|
|||
date_Set( &sys->next_output_pts, p_pic->date ); |
|||
bool got_output = sys->mode != FRC_x2_PRESENT; |
|||
do { |
|||
AMFData *amfOutput = NULL; |
|||
res = sys->amf_frc->pVtbl->QueryOutput(sys->amf_frc, &amfOutput); |
|||
if (res != AMF_OK && res != AMF_REPEAT) |
|||
{ |
|||
msg_Err(filter, "filter gave no output (err=%d)", res); |
|||
break; |
|||
} |
|||
|
|||
AMFSurface *amfOutputSurface = (AMFSurface*)amfOutput; |
|||
AMFPlane *packed = amfOutputSurface->pVtbl->GetPlane(amfOutputSurface, AMF_PLANE_PACKED); |
|||
|
|||
assert(amfOutput->pVtbl->GetMemoryType(amfOutput) == AMF_MEMORY_DX11); |
|||
ID3D11Texture2D *out = packed->pVtbl->GetNative(packed); |
|||
picture_t *dst = PictureFromTexture(filter, &dev_sys->d3d_dev, out); |
|||
amfOutput->pVtbl->Release(amfOutput); |
|||
if (dst == NULL) |
|||
break; |
|||
|
|||
picture_CopyProperties(dst, p_pic); |
|||
if (!got_output) |
|||
{ |
|||
picture_Release(p_pic); |
|||
p_pic = dst; |
|||
got_output = true; |
|||
} |
|||
else |
|||
{ |
|||
if (sys->mode == FRC_x2_PRESENT) |
|||
{ |
|||
// teh first frame is the interpolated one with the previous frame
|
|||
dst->date = date_Get( &sys->next_output_pts ); |
|||
p_pic->date = date_Decrement( &sys->next_output_pts, 1 ); |
|||
vlc_picture_chain_AppendChain(p_pic, dst); |
|||
} |
|||
else |
|||
{ |
|||
// past interpolated then source
|
|||
vlc_picture_chain_AppendChain(dst, p_pic); |
|||
dst->date = date_Decrement( &sys->next_output_pts, 1 ); |
|||
p_pic = dst; |
|||
} |
|||
} |
|||
} while (res == AMF_REPEAT); |
|||
|
|||
return p_pic; |
|||
} |
|||
|
|||
static void D3D11CloseAMFFRC(filter_t *filter) |
|||
{ |
|||
struct filter_sys_t *sys = filter->p_sys; |
|||
sys->amfInput->pVtbl->Release(sys->amfInput); |
|||
sys->amf_frc->pVtbl->Release(sys->amf_frc); |
|||
vlc_video_context_Release(filter->vctx_out); |
|||
} |
|||
|
|||
static int D3D11CreateAMFFRC(filter_t *filter) |
|||
{ |
|||
if (!is_d3d11_opaque(filter->fmt_in.video.i_chroma)) |
|||
return VLC_EGENERIC; |
|||
if ( GetD3D11ContextPrivate(filter->vctx_in) == NULL ) |
|||
return VLC_EGENERIC; |
|||
if (!video_format_IsSimilar(&filter->fmt_in.video, &filter->fmt_out.video)) |
|||
return VLC_EGENERIC; |
|||
|
|||
d3d11_decoder_device_t *dev_sys = GetD3D11OpaqueContext( filter->vctx_in ); |
|||
if (dev_sys->d3d_dev.adapterDesc.VendorId != GPU_MANUFACTURER_AMD) |
|||
{ |
|||
msg_Err(filter, "AMF filter only supported with AMD GPUs"); |
|||
return VLC_ENOTSUP; |
|||
} |
|||
|
|||
config_ChainParse( filter, "", ppsz_filter_options, filter->p_cfg ); |
|||
|
|||
struct filter_sys_t *sys = vlc_obj_calloc(VLC_OBJECT(filter), 1, sizeof(*sys)); |
|||
if (unlikely(sys == NULL)) |
|||
return VLC_ENOMEM; |
|||
|
|||
sys->mode = FRC_x2_PRESENT; //FRC_ONLY_INTERPOLATED;
|
|||
|
|||
d3d11_video_context_t *vctx_sys = GetD3D11ContextPrivate( filter->vctx_in ); |
|||
DXGI_FORMAT input_format = vctx_sys->format; |
|||
const d3d_format_t *cfg; |
|||
for (cfg = DxgiGetRenderFormatList(); cfg->name != NULL; ++cfg) |
|||
{ |
|||
if (cfg->formatTexture == input_format && |
|||
is_d3d11_opaque(cfg->fourcc)) |
|||
break; |
|||
} |
|||
assert(cfg != NULL); |
|||
|
|||
AMF_SURFACE_FORMAT amf_fmt = DXGIToAMF(input_format); |
|||
if (amf_fmt == AMF_SURFACE_UNKNOWN) |
|||
{ |
|||
msg_Err(filter, "Unsupported DXGI format %s", cfg->name); |
|||
return VLC_EGENERIC; |
|||
} |
|||
|
|||
int err = vlc_AMFCreateContext(&sys->amf); |
|||
if (err != VLC_SUCCESS) |
|||
return err; |
|||
|
|||
#if 0 |
|||
if (sys->amf.Version < AMF_MAKE_FULL_VERSION(1,4,34,0)) |
|||
{ |
|||
msg_Dbg(filter, "AMF version %u.%u.%u too old for FRC (1.4.34 needed)", |
|||
(unsigned)AMF_GET_MAJOR_VERSION(sys->amf.Version), |
|||
(unsigned)AMF_GET_MINOR_VERSION(sys->amf.Version), |
|||
(unsigned)AMF_GET_SUBMINOR_VERSION(sys->amf.Version)); |
|||
goto error; |
|||
} |
|||
#endif |
|||
|
|||
AMF_RESULT res; |
|||
res = sys->amf.Context->pVtbl->InitDX11(sys->amf.Context, dev_sys->d3d_dev.d3ddevice, AMF_DX11_0); |
|||
if (res != AMF_OK) |
|||
goto error; |
|||
|
|||
res = sys->amf.pFactory->pVtbl->CreateComponent(sys->amf.pFactory, sys->amf.Context, AMFFRC, &sys->amf_frc); |
|||
if (res != AMF_OK || sys->amf_frc == NULL) |
|||
goto error; |
|||
|
|||
// TODO AMF_STREAM_VIDEO_FRAME_RATE
|
|||
|
|||
AMFVariantStruct val; |
|||
val.int64Value = FRC_ENGINE_DX11; |
|||
val.type = AMF_VARIANT_INT64; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_ENGINE_TYPE, val); |
|||
if (unlikely(res != AMF_OK)) |
|||
{ |
|||
msg_Err(filter, "Failed to set D3D11 engine type (err=%d)", res); |
|||
goto error; |
|||
} |
|||
|
|||
val.boolValue = false; |
|||
val.type = AMF_VARIANT_BOOL; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_ENABLE_FALLBACK, val); |
|||
if (unlikely(res != AMF_OK)) |
|||
{ |
|||
msg_Err(filter, "Failed to disable fallback (err=%d)", res); |
|||
goto error; |
|||
} |
|||
|
|||
val.int64Value = FRC_PROFILE_HIGH; |
|||
val.type = AMF_VARIANT_INT64; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_PROFILE, val); |
|||
if (unlikely(res != AMF_OK)) |
|||
{ |
|||
msg_Err(filter, "Failed to set FRC profile to %" PRId64 " (err=%d)", val.int64Value, res); |
|||
goto error; |
|||
} |
|||
|
|||
val.int64Value = FRC_MV_SEARCH_NATIVE; |
|||
val.type = AMF_VARIANT_INT64; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_MV_SEARCH_MODE, val); |
|||
if (unlikely(res != AMF_OK)) |
|||
{ |
|||
msg_Err(filter, "Failed to set FRC mv search to %" PRId64 " (err=%d)", val.int64Value, res); |
|||
goto error; |
|||
} |
|||
|
|||
val.int64Value = sys->mode; |
|||
val.type = AMF_VARIANT_INT64; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_MODE, val); |
|||
if (unlikely(res != AMF_OK)) |
|||
{ |
|||
msg_Err(filter, "Failed to set FRC mode to %" PRId64 " (err=%d)", val.int64Value, res); |
|||
goto error; |
|||
} |
|||
|
|||
val.boolValue = var_GetBool (filter, "frc-indicator"); |
|||
val.type = AMF_VARIANT_BOOL; |
|||
res = sys->amf_frc->pVtbl->SetProperty(sys->amf_frc, AMF_FRC_INDICATOR, val); |
|||
if (res != AMF_OK) |
|||
goto error; |
|||
|
|||
res = sys->amf_frc->pVtbl->Init(sys->amf_frc, amf_fmt, |
|||
filter->fmt_in.video.i_width, |
|||
filter->fmt_in.video.i_height); |
|||
if (res != AMF_OK) |
|||
goto error; |
|||
|
|||
res = sys->amf.Context->pVtbl->AllocSurface(sys->amf.Context, AMF_MEMORY_DX11, |
|||
amf_fmt, |
|||
filter->fmt_in.video.i_width, |
|||
filter->fmt_in.video.i_height, |
|||
&sys->amfInput); |
|||
if (res != AMF_OK) |
|||
goto error; |
|||
|
|||
sys->cfg = cfg; |
|||
static const struct vlc_filter_operations filter_ops = |
|||
{ |
|||
.filter_video = Filter, |
|||
.close = D3D11CloseAMFFRC, |
|||
}; |
|||
filter->ops = &filter_ops; |
|||
filter->p_sys = sys; |
|||
filter->vctx_out = vlc_video_context_Hold(filter->vctx_in); |
|||
|
|||
sys->source_rate = filter->fmt_out.video.i_frame_rate_base != 0 && |
|||
filter->fmt_out.video.i_frame_rate != 0; |
|||
|
|||
if (!sys->source_rate) |
|||
{ |
|||
msg_Warn( filter, "Missing frame rate, assuming 25fps source" ); |
|||
filter->fmt_out.video.i_frame_rate = 25; |
|||
filter->fmt_out.video.i_frame_rate_base = 1; |
|||
} |
|||
|
|||
filter->fmt_out.video.i_frame_rate = 2 * filter->fmt_out.video.i_frame_rate; |
|||
|
|||
date_Init( &sys->next_output_pts, |
|||
filter->fmt_out.video.i_frame_rate, filter->fmt_out.video.i_frame_rate_base ); |
|||
|
|||
return VLC_SUCCESS; |
|||
error: |
|||
if (sys->amfInput) |
|||
sys->amfInput->pVtbl->Release(sys->amfInput); |
|||
if (sys->amf_frc != NULL) |
|||
sys->amf_frc->pVtbl->Release(sys->amf_frc); |
|||
vlc_AMFReleaseContext(&sys->amf); |
|||
return VLC_EGENERIC; |
|||
} |
|||
Loading…
Reference in new issue