mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-22 01:40:28 +00:00
436 lines
15 KiB
C++
436 lines
15 KiB
C++
/**
|
|
* @file video_decompress/cineform.cpp
|
|
* @author Martin Piatka <piatka@cesnet.cz>
|
|
*/
|
|
/*
|
|
* Copyright (c) 2019-2023 CESNET, z. s. p. o.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, is permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of CESNET nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
|
|
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#include "config_unix.h"
|
|
#include "config_win32.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "debug.h"
|
|
#include "host.h"
|
|
#include "lib_common.h"
|
|
#include "tv.h"
|
|
#include "video.h"
|
|
#include "video_decompress.h"
|
|
#include "utils/macros.h" // to_fourcc
|
|
|
|
#include "CFHDTypes.h"
|
|
#include "CFHDDecoder.h"
|
|
|
|
#define MOD_NAME "[cineform] "
|
|
|
|
struct state_cineform_decompress {
|
|
int pitch = 0;
|
|
int rshift = 0;
|
|
int gshift = 0;
|
|
int bshift = 0;
|
|
video_desc desc = {};
|
|
codec_t in_codec = VIDEO_CODEC_NONE;
|
|
codec_t out_codec = VIDEO_CODEC_NONE;
|
|
CFHD_PixelFormat decode_codec = CFHD_PIXEL_FORMAT_UNKNOWN;
|
|
int decode_linesize = 0;
|
|
|
|
using convert_fun_t = void (*)(unsigned char *dst_buffer,
|
|
unsigned char *src_buffer,
|
|
int width, int height, int pitch);
|
|
convert_fun_t convert = nullptr;
|
|
|
|
std::vector<unsigned char> conv_buf;
|
|
|
|
bool prepared_to_decode = false;
|
|
|
|
CFHD_DecoderRef decoderRef = nullptr;
|
|
CFHD_MetadataRef metadataRef = nullptr;
|
|
};
|
|
|
|
static void *cineform_decompress_init(){
|
|
auto s = std::make_unique<state_cineform_decompress>();
|
|
|
|
CFHD_Error status;
|
|
status = CFHD_OpenDecoder(&s->decoderRef, nullptr);
|
|
if(status != CFHD_ERROR_OKAY){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to open decoder\n");
|
|
return nullptr;
|
|
}
|
|
status = CFHD_OpenMetadata(&s->metadataRef);
|
|
if(status != CFHD_ERROR_OKAY){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to open metadata\n");
|
|
CFHD_CloseDecoder(s->decoderRef);
|
|
return nullptr;
|
|
}
|
|
|
|
return s.release();
|
|
}
|
|
|
|
static void cineform_decompress_done(void *state)
|
|
{
|
|
auto s = static_cast<struct state_cineform_decompress *>(state);
|
|
|
|
CFHD_CloseDecoder(s->decoderRef);
|
|
CFHD_CloseMetadata(s->metadataRef);
|
|
delete s;
|
|
}
|
|
|
|
static void rg48_to_r12l(unsigned char *dst_buffer,
|
|
unsigned char *src_buffer,
|
|
int width, int height, int pitch)
|
|
{
|
|
int src_pitch = vc_get_linesize(width, RG48);
|
|
int dst_len = vc_get_linesize(width, R12L);
|
|
decoder_t vc_copylineRG48toR12L = get_decoder_from_to(RG48, R12L);
|
|
|
|
for(int i = 0; i < height; i++){
|
|
vc_copylineRG48toR12L(dst_buffer, src_buffer, dst_len, 0, 0, 0);
|
|
src_buffer += src_pitch;
|
|
dst_buffer += pitch;
|
|
}
|
|
}
|
|
|
|
static void abgr_to_rgba(unsigned char *dst_buffer,
|
|
unsigned char *src_buffer,
|
|
int width, int height, int pitch)
|
|
{
|
|
int linesize = vc_get_linesize(width, RGBA);
|
|
|
|
for(int i = 0; i < height; i++){
|
|
vc_copylineRGBA(dst_buffer, src_buffer, linesize, 16, 8, 0);
|
|
src_buffer += linesize;
|
|
dst_buffer += pitch;
|
|
}
|
|
}
|
|
|
|
static void bgr_to_rgb_invert(unsigned char *dst_buffer,
|
|
unsigned char *src_buffer,
|
|
int width, int height, int pitch)
|
|
{
|
|
int linesize = vc_get_linesize(width, RGB);
|
|
src_buffer += linesize * (height - 1);
|
|
decoder_t vc_copylineBGRtoRGB = get_decoder_from_to(BGR, RGB);
|
|
|
|
for(int i = 0; i < height; i++){
|
|
vc_copylineBGRtoRGB(dst_buffer, src_buffer, linesize, 0, 0, 0);
|
|
src_buffer -= linesize;
|
|
dst_buffer += pitch;
|
|
}
|
|
}
|
|
|
|
static const struct {
|
|
codec_t ug_codec;
|
|
CFHD_PixelFormat cfhd_pixfmt;
|
|
void (*convert)(unsigned char *dst_buffer,
|
|
unsigned char *src_buffer,
|
|
int width, int height, int pitch);
|
|
} decode_codecs[] = {
|
|
{R12L, CFHD_PIXEL_FORMAT_RG48, rg48_to_r12l},
|
|
{RG48, CFHD_PIXEL_FORMAT_RG48, nullptr},
|
|
{UYVY, CFHD_PIXEL_FORMAT_2VUY, nullptr},
|
|
{R10k, CFHD_PIXEL_FORMAT_DPX0, nullptr},
|
|
{v210, CFHD_PIXEL_FORMAT_V210, nullptr},
|
|
{RGB, CFHD_PIXEL_FORMAT_RG24, bgr_to_rgb_invert},
|
|
{RGBA, CFHD_PIXEL_FORMAT_BGRa, abgr_to_rgba},
|
|
};
|
|
|
|
static int cineform_decompress_reconfigure(void *state, struct video_desc desc,
|
|
int rshift, int gshift, int bshift, int pitch, codec_t out_codec)
|
|
{
|
|
auto s = static_cast<struct state_cineform_decompress *>(state);
|
|
|
|
s->pitch = pitch;
|
|
s->rshift = rshift;
|
|
s->gshift = gshift;
|
|
s->bshift = bshift;
|
|
s->in_codec = desc.color_spec;
|
|
s->out_codec = out_codec;
|
|
s->desc = desc;
|
|
s->prepared_to_decode = false;
|
|
|
|
if(s->out_codec == VIDEO_CODEC_NONE){
|
|
log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Will probe for internal format.\n");
|
|
return true;
|
|
}
|
|
for(const auto& i : decode_codecs){
|
|
if(i.ug_codec == s->out_codec){
|
|
s->decode_codec = i.cfhd_pixfmt;
|
|
s->convert = i.convert;
|
|
CFHD_GetImagePitch(desc.width, i.cfhd_pixfmt, &s->decode_linesize);
|
|
if(i.ug_codec == R12L){
|
|
log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Decoding to 12-bit RGB.\n");
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool prepare(struct state_cineform_decompress *s,
|
|
unsigned char *src,
|
|
unsigned int src_len)
|
|
{
|
|
if(s->prepared_to_decode){
|
|
return true;
|
|
}
|
|
|
|
CFHD_Error status;
|
|
|
|
int actualWidth;
|
|
int actualHeight;
|
|
CFHD_PixelFormat actualFormat;
|
|
status = CFHD_PrepareToDecode(s->decoderRef,
|
|
s->desc.width,
|
|
s->desc.height,
|
|
s->decode_codec,
|
|
CFHD_DECODED_RESOLUTION_FULL,
|
|
CFHD_DECODING_FLAGS_NONE,
|
|
src,
|
|
src_len,
|
|
&actualWidth,
|
|
&actualHeight,
|
|
&actualFormat
|
|
);
|
|
assert(actualWidth == (int) s->desc.width);
|
|
assert(actualHeight == (int) s->desc.height);
|
|
assert(actualFormat == s->decode_codec);
|
|
if(s->convert){
|
|
int actualPitch;
|
|
CFHD_GetImagePitch(actualWidth, actualFormat, &actualPitch);
|
|
assert(actualPitch == s->decode_linesize);
|
|
s->conv_buf.resize(s->desc.height * s->decode_linesize);
|
|
} else {
|
|
s->conv_buf.clear();
|
|
}
|
|
if(status != CFHD_ERROR_OKAY){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to prepare for decoding\n");
|
|
return false;
|
|
}
|
|
|
|
s->prepared_to_decode = true;
|
|
return true;
|
|
}
|
|
|
|
static decompress_status probe_internal_cineform(struct state_cineform_decompress *s,
|
|
unsigned char *src,
|
|
unsigned src_len,
|
|
struct pixfmt_desc *internal_prop)
|
|
{
|
|
CFHD_Error status;
|
|
CFHD_PixelFormat fmt_list[64];
|
|
int count = 0;
|
|
|
|
status = CFHD_GetOutputFormats(s->decoderRef,
|
|
src,
|
|
src_len,
|
|
fmt_list,
|
|
std::size(fmt_list),
|
|
&count);
|
|
log_msg(LOG_LEVEL_DEBUG, MOD_NAME "probing...\n");
|
|
|
|
if(status != CFHD_ERROR_OKAY){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "probe failed\n");
|
|
return DECODER_NO_FRAME;
|
|
}
|
|
|
|
for(int i = 0; i < count; i++){
|
|
for(const auto &codec : decode_codecs){
|
|
if(codec.cfhd_pixfmt == fmt_list[i]){
|
|
*internal_prop = get_pixfmt_desc(codec.ug_codec);
|
|
return DECODER_GOT_CODEC;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Unknown internal format. This should never happen since cineform can
|
|
//decode any internal codec to CFHD_PIXEL_FORMAT_YUY2.
|
|
//Here we just select UYVY and hope for the best.
|
|
*internal_prop = get_pixfmt_desc(UYVY);
|
|
return DECODER_GOT_CODEC;
|
|
}
|
|
|
|
static void write_fcc(char *out, int pixelformat){
|
|
out[4] = '\0';
|
|
|
|
for(int i = 0; i < 4; i++){
|
|
out[i] = pixelformat & 0xff;
|
|
pixelformat >>= 8;
|
|
}
|
|
}
|
|
|
|
static decompress_status probe_internal(struct state_cineform_decompress *s,
|
|
unsigned char *src,
|
|
unsigned src_len,
|
|
struct pixfmt_desc *internal_prop)
|
|
{
|
|
CFHD_Error status;
|
|
|
|
status = CFHD_InitSampleMetadata(s->metadataRef,
|
|
METADATATYPE_ORIGINAL,
|
|
src,
|
|
src_len);
|
|
if(status != CFHD_ERROR_OKAY){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "InitSampleMetadata failed\n");
|
|
return DECODER_NO_FRAME;
|
|
}
|
|
|
|
CFHD_MetadataTag tag;
|
|
CFHD_MetadataType type;
|
|
void *data;
|
|
CFHD_MetadataSize size;
|
|
char fcc[5];
|
|
while((status = CFHD_ReadMetadata(s->metadataRef, &tag, &type, &data, &size)) == CFHD_ERROR_OKAY){
|
|
write_fcc(fcc, tag);
|
|
log_msg(LOG_LEVEL_DEBUG, MOD_NAME "Metadata found. tag = %s \n", fcc);
|
|
}
|
|
|
|
status = CFHD_FindMetadata(s->metadataRef,
|
|
to_fourcc('U', 'G', 'P', 'F'),
|
|
&type,
|
|
&data,
|
|
&size);
|
|
if(status != CFHD_ERROR_OKAY || type != METADATATYPE_UINT32){
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "UGPF metadata not found or wrong type, "
|
|
"falling back to cineform internal format detection.\n");
|
|
} else {
|
|
codec_t pf = *static_cast<codec_t *>(data);
|
|
*internal_prop = get_pixfmt_desc(pf);
|
|
log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Codec determined from metadata: %s \n", get_codec_name(pf));
|
|
return DECODER_GOT_CODEC;
|
|
}
|
|
|
|
return probe_internal_cineform(s, src, src_len, internal_prop);
|
|
}
|
|
|
|
static decompress_status cineform_decompress(void *state, unsigned char *dst, unsigned char *src,
|
|
unsigned int src_len, int frame_seq, struct video_frame_callbacks *callbacks,
|
|
struct pixfmt_desc *internal_prop)
|
|
{
|
|
UNUSED(frame_seq);
|
|
UNUSED(callbacks);
|
|
auto s = static_cast<struct state_cineform_decompress *>(state);
|
|
decompress_status res = DECODER_NO_FRAME;
|
|
|
|
CFHD_Error status;
|
|
|
|
if(s->out_codec == VIDEO_CODEC_NONE){
|
|
return probe_internal(s, src, src_len, internal_prop);
|
|
}
|
|
|
|
if(!prepare(s, src, src_len)){
|
|
return res;
|
|
}
|
|
|
|
unsigned char *decode_dst = s->convert ? s->conv_buf.data() : dst;
|
|
int pitch = s->convert ? s->decode_linesize : s->pitch;
|
|
|
|
status = CFHD_DecodeSample(s->decoderRef,
|
|
src,
|
|
src_len,
|
|
decode_dst,
|
|
pitch);
|
|
|
|
if(status == CFHD_ERROR_OKAY){
|
|
if(s->convert){
|
|
s->convert(dst, decode_dst, s->desc.width, s->desc.height, s->pitch);
|
|
}
|
|
res = DECODER_GOT_FRAME;
|
|
} else {
|
|
log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to decode %i\n", status);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static int cineform_decompress_get_property(void *state, int property, void *val, size_t *len)
|
|
{
|
|
auto s = static_cast<struct state_cineform_decompress *>(state);
|
|
UNUSED(s);
|
|
int ret = false;
|
|
|
|
switch(property) {
|
|
case DECOMPRESS_PROPERTY_ACCEPTS_CORRUPTED_FRAME:
|
|
if(*len >= sizeof(int)) {
|
|
#ifdef CINEFORM_ACCEPT_CORRUPTED
|
|
*(int *) val = true;
|
|
#else
|
|
*(int *) val = false;
|
|
#endif
|
|
*len = sizeof(int);
|
|
ret = true;
|
|
}
|
|
break;
|
|
default:
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cineform_decompress_get_priority(codec_t compression, struct pixfmt_desc internal, codec_t ugc) {
|
|
UNUSED(internal);
|
|
if (compression != CFHD) {
|
|
return -1;
|
|
}
|
|
switch (ugc) {
|
|
case VIDEO_CODEC_NONE: // probe
|
|
return 50;
|
|
case UYVY:
|
|
case RGBA:
|
|
case R10k:
|
|
case R12L:
|
|
case RG48:
|
|
case v210:
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return VDEC_PRIO_PREFERRED;
|
|
}
|
|
|
|
static const struct video_decompress_info cineform_info = {
|
|
cineform_decompress_init,
|
|
cineform_decompress_reconfigure,
|
|
cineform_decompress,
|
|
cineform_decompress_get_property,
|
|
cineform_decompress_done,
|
|
cineform_decompress_get_priority,
|
|
};
|
|
|
|
REGISTER_MODULE(cineform, &cineform_info, LIBRARY_CLASS_VIDEO_DECOMPRESS, VIDEO_DECOMPRESS_ABI_VERSION);
|