/*****************************************************************************
* cvpx.c: core video buffer to picture converter
*****************************************************************************
* Copyright (C) 2015-2017 VLC authors, VideoLAN and VideoLabs
*
* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
* Thomas Guillem <thomas@gllm.fr>
* Victorien Le Couviour--Tuffet <victorien.lecouiour.tuffet@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <QuartzCore/QuartzCore.h>
#include <TargetConditionals.h>
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_filter.h>
#include <vlc_picture.h>
#include <vlc_chroma_probe.h>
#include <vlc_modules.h>
#include "../codec/vt_utils.h"
#include "../video_chroma/copy.h"
static int Open(filter_t *);
static void Close(filter_t *);
#if !TARGET_OS_IPHONE
static int Open_CVPX_to_CVPX(filter_t *);
static void Close_CVPX_to_CVPX(filter_t *);
static int Open_chain_CVPX(filter_t *);
static void Close_chain_CVPX(filter_t *);
#endif
typedef struct
{
CVPixelBufferPoolRef pool;
union
{
struct
{
video_format_t fmt;
copy_cache_t cache;
} sw;
#if !TARGET_OS_IPHONE
VTPixelTransferSessionRef vttransfer;
#endif
};
} filter_sys_t;
static void ProbeChroma(vlc_chroma_conv_vec *vec)
{
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_BGRA, VLC_CODEC_CVPX_I420, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_BGRA, VLC_CODEC_CVPX_NV12, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_BGRA, VLC_CODEC_CVPX_P010, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_BGRA, VLC_CODEC_CVPX_UYVY, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_NV12, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_P010, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_I420, VLC_CODEC_CVPX_UYVY, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_P010, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_NV12, VLC_CODEC_CVPX_UYVY, true);
vlc_chroma_conv_add(vec, 0.25, VLC_CODEC_CVPX_P010, VLC_CODEC_CVPX_UYVY, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_NV12, VLC_CODEC_NV12, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_NV12, VLC_CODEC_I420, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_P010, VLC_CODEC_P010, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_P010, VLC_CODEC_I420_10L, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_UYVY, VLC_CODEC_UYVY, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_I420, VLC_CODEC_I420, true);
vlc_chroma_conv_add(vec, 1.1, VLC_CODEC_CVPX_BGRA, VLC_CODEC_BGRA, true);
}
vlc_module_begin ()
set_description("Conversions from/to CoreVideo buffers")
set_callback_video_converter(Open, 10)
#if !TARGET_OS_IPHONE
add_submodule()
set_description("Conversions between CoreVideo buffers")
set_callback_video_converter(Open_CVPX_to_CVPX, 10)
add_submodule()
set_description("Fast CoreVideo resize+conversion")
set_callback_video_converter(Open_chain_CVPX, 11)
#endif
add_submodule()
set_callback_chroma_conv_probe(ProbeChroma)
vlc_module_end ()
/********************************
* CVPX to/from I420 conversion *
********************************/
static void Copy(filter_t *p_filter, picture_t *dst, picture_t *src,
unsigned height)
{
filter_sys_t *p_sys = p_filter->p_sys;
const uint8_t *src_planes[3] = { src->p[0].p_pixels,
src->p[1].p_pixels,
src->p[2].p_pixels };
const size_t src_pitches[3] = { src->p[0].i_pitch,
src->p[1].i_pitch,
src->p[2].i_pitch };
#define DO(x) \
x(dst, src_planes, src_pitches, height, &p_sys->sw.cache)
#define DO_S(x, shift) \
x(dst, src_planes, src_pitches, height, shift, &p_sys->sw.cache)
#define DO_P(x) \
x(dst, src_planes[0], src_pitches[0], height, &p_sys->sw.cache)
const vlc_fourcc_t infcc = src->format.i_chroma;
const vlc_fourcc_t outfcc = dst->format.i_chroma;
switch (infcc)
{
case VLC_CODEC_NV12:
if (outfcc == VLC_CODEC_NV12)
DO(Copy420_SP_to_SP);
else
{
assert(outfcc == VLC_CODEC_I420);
DO(Copy420_SP_to_P);
}
break;
case VLC_CODEC_P010:
if (outfcc == VLC_CODEC_P010)
DO(Copy420_SP_to_SP);
else
{
assert(outfcc == VLC_CODEC_I420_10L);
DO_S(Copy420_16_SP_to_P, 6);
}
break;
case VLC_CODEC_I420:
if (outfcc == VLC_CODEC_I420)
DO(Copy420_P_to_P);
else
{
assert(outfcc == VLC_CODEC_NV12);
DO(Copy420_P_to_SP);
}
break;
case VLC_CODEC_I420_10L:
assert(outfcc == VLC_CODEC_P010);
DO_S(Copy420_16_P_to_SP, -6);
break;
case VLC_CODEC_UYVY:
assert(outfcc == VLC_CODEC_UYVY);
DO_P(CopyPacked);
break;
case VLC_CODEC_BGRA:
assert(outfcc == VLC_CODEC_BGRA);
DO_P(CopyPacked);
break;
default:
vlc_assert_unreachable();
}
#undef DO
#undef DO_S
#undef DO_P
}
static picture_t *CVPX_TO_SW_Filter(filter_t *p_filter, picture_t *src)
{
filter_sys_t *p_sys = p_filter->p_sys;
CVPixelBufferRef cvpx = cvpxpic_get_ref(src);
picture_t *src_sw =
cvpxpic_create_mapped(&p_sys->sw.fmt, cvpx, p_filter->vctx_in, true);
if (!src_sw)
{
picture_Release(src);
return NULL;
}
picture_t *dst = filter_NewPicture(p_filter);
if (!dst)
{
picture_Release(src_sw);
picture_Release(src);
return NULL;
}
size_t height = CVPixelBufferGetHeight(cvpx);
Copy(p_filter, dst, src_sw, __MIN(height, dst->format.i_visible_height));
picture_Release(src_sw);
picture_CopyProperties(dst, src);
picture_Release(src);
return dst;
}
static picture_t *SW_TO_CVPX_Filter(filter_t *p_filter, picture_t *src)
{
filter_sys_t *p_sys = p_filter->p_sys;
CVPixelBufferRef cvpx = cvpxpool_new_cvpx(p_sys->pool);
if (cvpx == NULL)
{
picture_Release(src);
return NULL;
}
cvpx_attach_mapped_color_properties(cvpx, &p_sys->sw.fmt);
/* Allocate a CPVX backed picture mapped for read/write */
picture_t *mapped_dst =
cvpxpic_create_mapped(&p_sys->sw.fmt, cvpx, p_filter->vctx_out, false);
CFRelease(cvpx);
if (!mapped_dst)
{
picture_Release(src);
return NULL;
}
/* Allocate a CVPX picture without any context */
picture_t *dst = picture_NewFromFormat(&p_filter->fmt_out.video);
if (!dst)
{
picture_Release(src);
picture_Release(mapped_dst);
return NULL;
}
size_t height = CVPixelBufferGetHeight(cvpx);
Copy(p_filter, mapped_dst, src, __MIN(height, src->format.i_visible_height));
/* Attach the CVPX to a new opaque picture */
cvpxpic_attach(dst, cvpxpic_get_ref(mapped_dst), p_filter->vctx_out, NULL);
/* Unlock and unmap the dst picture */
picture_Release(mapped_dst);
picture_CopyProperties(dst, src);
picture_Release(src);
return dst;
}
static void Close(filter_t *p_filter)
{
filter_sys_t *p_sys = p_filter->p_sys;
if (p_sys->pool != NULL)
CVPixelBufferPoolRelease(p_sys->pool);
CopyCleanCache(&p_sys->sw.cache);
if (p_filter->vctx_out)
vlc_video_context_Release(p_filter->vctx_out);
free(p_sys);
}
static const struct vlc_filter_operations CVPX_TO_SW_ops = {
.filter_video = CVPX_TO_SW_Filter, .close = Close,
};
static const struct vlc_filter_operations SW_TO_CVPX_ops = {
.filter_video = SW_TO_CVPX_Filter, .close = Close,
};
static int Open(filter_t *p_filter)
{
if (p_filter->fmt_in.video.i_height != p_filter->fmt_out.video.i_height
|| p_filter->fmt_in.video.i_width != p_filter->fmt_out.video.i_width)
return VLC_EGENERIC;
video_format_t sw_fmt;
#define CASE_CVPX_INPUT(x, i420_fcc) \
case VLC_CODEC_CVPX_##x: \
sw_fmt = p_filter->fmt_out.video; \
if (p_filter->fmt_out.video.i_chroma == VLC_CODEC_##x) \
p_filter->ops = &CVPX_TO_SW_ops; \
else if (i420_fcc != 0 && p_filter->fmt_out.video.i_chroma == i420_fcc) { \
p_filter->ops = &CVPX_TO_SW_ops; \
sw_fmt.i_chroma = VLC_CODEC_##x; \
} else return VLC_EGENERIC; \
#define CASE_CVPX_OUTPUT(x, i420_fcc) \
case VLC_CODEC_CVPX_##x: \
sw_fmt = p_filter->fmt_in.video; \
if (p_filter->fmt_in.video.i_chroma == VLC_CODEC_##x) { \
p_filter->ops = &SW_TO_CVPX_ops; \
} \
else if (i420_fcc != 0 && p_filter->fmt_in.video.i_chroma == i420_fcc) { \
p_filter->ops = &SW_TO_CVPX_ops; \
sw_fmt.i_chroma = VLC_CODEC_##x; \
} else return VLC_EGENERIC; \
b_need_pool = true;
bool b_need_pool = false;
unsigned i_cache_pixel_bytes = 1;
switch (p_filter->fmt_in.video.i_chroma)
{
CASE_CVPX_INPUT(NV12, VLC_CODEC_I420)
break;
CASE_CVPX_INPUT(P010, VLC_CODEC_I420_10L)
i_cache_pixel_bytes = 2;
break;
CASE_CVPX_INPUT(UYVY, 0)
break;
CASE_CVPX_INPUT(I420, 0)
break;
CASE_CVPX_INPUT(BGRA, 0)
break;
default:
switch (p_filter->fmt_out.video.i_chroma)
{
CASE_CVPX_OUTPUT(NV12, VLC_CODEC_I420)
break;
CASE_CVPX_OUTPUT(P010, VLC_CODEC_I420_10L)
i_cache_pixel_bytes = 2;
break;
CASE_CVPX_OUTPUT(UYVY, 0)
break;
CASE_CVPX_OUTPUT(I420, 0)
break;
CASE_CVPX_OUTPUT(BGRA, 0)
break;
default:
return VLC_EGENERIC;
}
}
filter_sys_t *p_sys = p_filter->p_sys = malloc(sizeof(filter_sys_t));
if (unlikely(!p_sys))
return VLC_ENOMEM;
p_sys->pool = NULL;
p_sys->sw.fmt = sw_fmt;
unsigned i_cache_width = p_filter->fmt_in.video.i_width * i_cache_pixel_bytes;
int ret = CopyInitCache(&p_sys->sw.cache, i_cache_width);
if (ret != VLC_SUCCESS)
goto error;
if (b_need_pool)
{
vlc_decoder_device *dec_dev =
filter_HoldDecoderDeviceType(p_filter,
VLC_DECODER_DEVICE_VIDEOTOOLBOX);
if (dec_dev == NULL)
{
msg_Err(p_filter, "Missing decoder device");
ret = VLC_EGENERIC;
goto error;
}
const static struct vlc_video_context_operations vt_vctx_ops = {
NULL,
};
p_filter->vctx_out =
vlc_video_context_CreateCVPX(dec_dev, CVPX_VIDEO_CONTEXT_DEFAULT,
0, &vt_vctx_ops);
vlc_decoder_device_Release(dec_dev);
if (!p_filter->vctx_out)
{
ret = VLC_ENOMEM;
goto error;
}
p_sys->pool = cvpxpool_create(&p_filter->fmt_out.video, 3);
if (p_sys->pool == NULL)
{
ret = VLC_ENOMEM;
goto error;
}
}
else
{
if (p_filter->vctx_in == NULL ||
vlc_video_context_GetType(p_filter->vctx_in) != VLC_VIDEO_CONTEXT_CVPX)
{
ret = VLC_EGENERIC;
goto error;
}
}
p_filter->fmt_out.i_codec = p_filter->fmt_out.video.i_chroma;
return VLC_SUCCESS;
error:
Close(p_filter);
p_filter->p_sys = NULL;
assert(ret != VLC_SUCCESS);
return ret;
#undef CASE_CVPX_INPUT
#undef CASE_CVPX_OUTPUT
}
/***************************
* CVPX to CVPX conversion *
***************************/
#if !TARGET_OS_IPHONE
static picture_t *
Filter(filter_t *filter, picture_t *src)
{
filter_sys_t *p_sys = filter->p_sys;
CVPixelBufferRef src_cvpx = cvpxpic_get_ref(src);
assert(src_cvpx);
picture_t *dst = filter_NewPicture(filter);
if (!dst)
{
picture_Release(src);
return NULL;
}
CVPixelBufferRef dst_cvpx = cvpxpool_new_cvpx(p_sys->pool);
if (dst_cvpx == NULL)
{
picture_Release(src);
picture_Release(dst);
return NULL;
}
if (VTPixelTransferSessionTransferImage(p_sys->vttransfer,
src_cvpx, dst_cvpx) != noErr)
{
picture_Release(dst);
picture_Release(src);
CVPixelBufferRelease(dst_cvpx);
return NULL;
}
cvpxpic_attach(dst, dst_cvpx, filter->vctx_out, NULL);
picture_CopyProperties(dst, src);
picture_Release(src);
CVPixelBufferRelease(dst_cvpx);
return dst;
}
static vlc_fourcc_t const supported_chromas[] = { VLC_CODEC_CVPX_BGRA,
VLC_CODEC_CVPX_I420,
VLC_CODEC_CVPX_NV12,
VLC_CODEC_CVPX_P010,
VLC_CODEC_CVPX_UYVY };
static const struct vlc_filter_operations filter_ops = {
.filter_video = Filter, .close = Close_CVPX_to_CVPX,
};
static int
Open_CVPX_to_CVPX(filter_t *filter)
{
/* Avoid conversion to self if we're not resizing */
if (filter->fmt_in.video.i_chroma == filter->fmt_out.video.i_chroma &&
filter->fmt_in.video.i_visible_width == filter->fmt_out.video.i_visible_width &&
filter->fmt_in.video.i_visible_height == filter->fmt_out.video.i_visible_height)
return VLC_EGENERIC;
if (filter->vctx_in == NULL ||
vlc_video_context_GetType(filter->vctx_in) != VLC_VIDEO_CONTEXT_CVPX)
return VLC_EGENERIC;
unsigned int i;
#define CHECK_CHROMA(fourcc) \
i = 0; \
while (i < ARRAY_SIZE(supported_chromas) && \
fourcc != supported_chromas[i]) \
++i; \
if (i == ARRAY_SIZE(supported_chromas)) \
return VLC_EGENERIC; \
CHECK_CHROMA(filter->fmt_in.video.i_chroma)
CHECK_CHROMA(filter->fmt_out.video.i_chroma)
#undef CHECK_CHROMA
filter_sys_t *p_sys = calloc(1, sizeof(filter_sys_t));
if (!p_sys)
return VLC_ENOMEM;
if (VTPixelTransferSessionCreate(NULL, &p_sys->vttransfer)
!= noErr)
{
free(p_sys);
return VLC_EGENERIC;
}
if ((p_sys->pool = cvpxpool_create(&filter->fmt_out.video, 3)) == NULL)
{
VTPixelTransferSessionInvalidate(p_sys->vttransfer);
CFRelease(p_sys->vttransfer);
free(p_sys);
return VLC_EGENERIC;
}
filter->p_sys = p_sys;
filter->ops = &filter_ops;
filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
filter->fmt_out.i_codec = filter->fmt_out.video.i_chroma;
return VLC_SUCCESS;
}
static void
Close_CVPX_to_CVPX(filter_t *filter)
{
filter_sys_t *p_sys = filter->p_sys;
VTPixelTransferSessionInvalidate(p_sys->vttransfer);
CFRelease(p_sys->vttransfer);
CVPixelBufferPoolRelease(p_sys->pool);
vlc_video_context_Release(filter->vctx_out);
free(filter->p_sys);
}
static picture_t*
chain_CVPX_Filter(filter_t *filter, picture_t *pic)
{
filter_chain_t *chain = filter->p_sys;
return filter_chain_VideoFilter(chain, pic);
}
static void
chain_CVPX_Flush(filter_t *filter)
{
filter_chain_t *chain = filter->p_sys;
filter_chain_VideoFlush(chain);
}
static vlc_fourcc_t
GetIntermediateChroma(vlc_fourcc_t input_chroma, vlc_fourcc_t output_chroma)
{
vlc_fourcc_t chromas[2] = { input_chroma, output_chroma };
for(size_t i=0; i<ARRAY_SIZE(chromas); ++i)
{
switch (chromas[i])
{
case VLC_CODEC_I420: return VLC_CODEC_CVPX_I420;
case VLC_CODEC_BGRA: return VLC_CODEC_CVPX_BGRA;
case VLC_CODEC_NV12: return VLC_CODEC_CVPX_NV12;
case VLC_CODEC_UYVY: return VLC_CODEC_CVPX_UYVY;
case VLC_CODEC_P010: return VLC_CODEC_CVPX_P010;
default: break;
}
}
vlc_assert_unreachable();
}
static int
PrintConversionChain(filter_t *filter, void *opaque)
{
VLC_UNUSED(opaque);
msg_Dbg(filter, " - conversion %4.4s (%dx%d) -> %4.4s (%dx%d)",
(const char*)&filter->fmt_in.video.i_chroma,
filter->fmt_in.video.i_visible_width,
filter->fmt_in.video.i_visible_height,
(const char*)&filter->fmt_out.video.i_chroma,
filter->fmt_out.video.i_visible_width,
filter->fmt_out.video.i_visible_height);
return VLC_SUCCESS;
}
static const vlc_fourcc_t supported_sw_chromas[] = {
VLC_CODEC_I420, VLC_CODEC_BGRA, VLC_CODEC_NV12,
VLC_CODEC_UYVY, VLC_CODEC_P010,
};
static const struct vlc_filter_operations chain_CVPX_ops = {
.filter_video = chain_CVPX_Filter, .flush = chain_CVPX_Flush,
.close = Close_chain_CVPX,
};
static picture_t *VideoBufferNew(filter_t *filter)
{
filter_t *cvpx_chain = filter->owner.sys;
return filter_NewPicture(cvpx_chain);
}
static struct vlc_decoder_device *VideoHoldDevice(vlc_object_t *obj, void *sys)
{
VLC_UNUSED(obj);
filter_t *cvpx_chain = sys;
return filter_HoldDecoderDevice(cvpx_chain);
}
static int
Open_chain_CVPX(filter_t *filter)
{
bool is_input_valid = false;
bool is_output_valid = false;
/* Check whether we're already in a CVPX chain or not, to avoid
* looping on the same conversion. */
vlc_value_t is_in_chain;
int ret = var_GetChecked(vlc_object_parent(filter), "cvpx-chroma-chain",
VLC_VAR_BOOL, &is_in_chain);
if (ret == VLC_SUCCESS && is_in_chain.b_bool )
return VLC_EGENERIC;
vlc_fourcc_t input_chroma = filter->fmt_in.video.i_chroma;
vlc_fourcc_t output_chroma = filter->fmt_out.video.i_chroma;
for (size_t i=0; i<ARRAY_SIZE(supported_chromas); ++i)
{
is_input_valid |= supported_chromas[i] == input_chroma;
is_output_valid |= supported_chromas[i] == output_chroma;
}
/* If we don't convert from or to CVPX chroma, we don't need to use
* this filter at all. */
if (!is_input_valid && !is_output_valid)
return VLC_EGENERIC;
/* If we convert from CVPX to CVPX, we can directly use the filter
* above without this one. */
if (is_input_valid && is_output_valid)
return VLC_EGENERIC;
/* Store which side was in CVPixelBuffer chroma */
bool is_input_cvpx = is_input_valid;
if (is_input_cvpx)
{
/* CVPX conversion needs a CVPX context */
if (filter->vctx_in == NULL)
return VLC_EGENERIC;
if (vlc_video_context_GetType(filter->vctx_in)
!= VLC_VIDEO_CONTEXT_CVPX)
return VLC_EGENERIC;
}
/* Check whether the other software chroma is supported. */
for (size_t i=0; i<ARRAY_SIZE(supported_sw_chromas); ++i)
{
is_input_valid |= supported_sw_chromas[i] == input_chroma;
is_output_valid |= supported_sw_chromas[i] == output_chroma;
}
/* If one of the side is not true yet, it means we didn't found a matching
* software chroma and hardware chroma for this side. */
if (!is_input_valid || !is_output_valid)
return VLC_EGENERIC;
msg_Dbg(filter, "Starting CVPX conversion chain %4.4s -> %4.4s",
(const char *)&input_chroma,
(const char *)&output_chroma);
static const struct filter_video_callbacks owner_cbs =
{
.buffer_new = VideoBufferNew,
.hold_device = VideoHoldDevice,
};
const struct filter_owner_t owner =
{
.sys = filter,
.video = &owner_cbs,
};
/* We create a filter chain to encapsulate the two converters. */
filter_chain_t *chain =
filter_chain_NewVideo(filter, false, &owner);
if (chain == NULL)
return VLC_ENOMEM;
filter_chain_Reset(chain, &filter->fmt_in, filter->vctx_in, &filter->fmt_out);
/* Check whether we need to resize before or after the
* first conversion. */
es_format_t fmt_out;
if (is_input_cvpx)
es_format_Copy(&fmt_out, &filter->fmt_out);
else
es_format_Copy(&fmt_out, &filter->fmt_in);
fmt_out.video.i_chroma
= fmt_out.i_codec
= GetIntermediateChroma(input_chroma, output_chroma);
var_Create(filter, "cvpx-chroma-chain", VLC_VAR_BOOL);
var_SetBool(filter, "cvpx-chroma-chain", true);
/* Append intermediate CVPX chroma */
ret = filter_chain_AppendConverter(chain, &fmt_out);
if (ret != VLC_SUCCESS)
goto error;
/* Append final chroma, either CVPX or software. */
ret = filter_chain_AppendConverter(chain, NULL);
if (ret != VLC_SUCCESS)
goto error;
struct vlc_video_context *vctx_out =
filter_chain_GetVideoCtxOut(chain);
filter->vctx_out = vctx_out;
filter->p_sys = chain;
filter->ops = &chain_CVPX_ops;
/* Display the current conversion chain in the logs. */
msg_Dbg(filter, "CVPX conversion chain:");
filter_chain_ForEach(chain, PrintConversionChain, NULL);
return VLC_SUCCESS;
error:
msg_Err(filter, "Failed to insert converter for CVPX chain");
filter_chain_Delete(chain);
var_Destroy(filter, "cvpx-chroma-chain");
return VLC_EGENERIC;
}
static void
Close_chain_CVPX(filter_t *filter)
{
filter_chain_t *chain = filter->p_sys;
filter_chain_Delete(chain);
var_Destroy(filter, "cvpx-chroma-chain");
}
#endif
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|