You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

374 lines
11 KiB

// SPDX-License-Identifier: LGPL-2.1-or-later
// openapv.c : APV decoder using openapv library
// Copyright © 2025 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_plugin.h>
#include <vlc_codec.h>
#include <vlc_modules.h>
#include <vlc_bits.h>
#include "../packetizer/iso_color_tables.h"
#include <oapv.h>
#include <limits.h>
#define THREAD_TILES_TEXT N_("Tile Threads")
#define THREAD_TILES_LONGTEXT N_( "Max number of threads used for decoding, default 0=auto" )
static int OpenAPVDecoder(vlc_object_t *);
static void CloseAPVDecoder(vlc_object_t *);
vlc_module_begin ()
set_description(N_("OpenAPV video decoder"))
set_capability("video decoder", 10)
set_callbacks(OpenAPVDecoder, CloseAPVDecoder)
set_subcategory(SUBCAT_INPUT_VCODEC)
add_integer_with_range( "openapv-threads", 0, 0, INT_MAX,
THREAD_TILES_TEXT, THREAD_TILES_LONGTEXT)
vlc_module_end ()
struct oapv_sys
{
oapvd_t decoder;
int cs;
};
static const struct {
vlc_fourcc_t vlc;
int oapv;
} vlc_oapv_chromas[] = {
{ VLC_CODEC_I422_10L, OAPV_CS_SET(OAPV_CF_YCBCR422, 10, 0) },
{ VLC_CODEC_I422_12L, OAPV_CS_SET(OAPV_CF_YCBCR422, 12, 0) },
{ VLC_CODEC_I422_16L, OAPV_CS_SET(OAPV_CF_YCBCR422, 16, 0) },
{ VLC_CODEC_I444_10L, OAPV_CS_SET(OAPV_CF_YCBCR444, 10, 0) },
{ VLC_CODEC_I444_12L, OAPV_CS_SET(OAPV_CF_YCBCR444, 12, 0) },
{ VLC_CODEC_I444_16L, OAPV_CS_SET(OAPV_CF_YCBCR444, 16, 0) },
{ VLC_CODEC_GREY_10L, OAPV_CS_SET(OAPV_CF_YCBCR400, 10, 0) },
{ VLC_CODEC_GREY_12L, OAPV_CS_SET(OAPV_CF_YCBCR400, 12, 0) },
{ VLC_CODEC_GREY_16L, OAPV_CS_SET(OAPV_CF_YCBCR400, 16, 0) },
};
static inline vlc_fourcc_t FindVlcChroma(uint8_t profile_idc, uint8_t bit_depth_minus8, uint8_t chroma_format_idc)
{
if (profile_idc == 33) // 422-10 profile
{
assert(chroma_format_idc == 2);
assert(bit_depth_minus8 == 2);
return VLC_CODEC_I422_10L;
}
if (profile_idc == 44) // 422-12 profile
{
assert(chroma_format_idc == 2);
assert(bit_depth_minus8 >= 2 && bit_depth_minus8 <= 4);
return VLC_CODEC_I422_12L;
}
if (profile_idc == 55) // 444-10 profile
{
assert(chroma_format_idc == 2 || chroma_format_idc == 3);
assert(bit_depth_minus8 == 2);
return VLC_CODEC_I444_10L;
}
if (profile_idc == 66) // 444-12 profile
{
assert(chroma_format_idc == 2 || chroma_format_idc == 3);
assert(bit_depth_minus8 >= 2 && bit_depth_minus8 <= 4);
return VLC_CODEC_I444_12L;
}
// if (profile_idc == 77) // 4444-10 profile
// {
// assert(chroma_format_idc >= 2 && chroma_format_idc <= 4);
// assert(bit_depth_minus8 == 2);
// return VLC_CODEC_V410;
// }
// if (profile_idc == 88) // 4444-12 profile
// {
// assert(chroma_format_idc >= 2 && chroma_format_idc <= 4);
// assert(bit_depth_minus8 >= 2 && bit_depth_minus8 <= 4);
// return VLC_CODEC_V410;
// }
if (bit_depth_minus8 == 2) // 10-bit
{
switch(chroma_format_idc)
{
case 0: return VLC_CODEC_GREY_10L;
case 2: return VLC_CODEC_I422_10L;
case 3: return VLC_CODEC_I444_10L;
// case 4: return VLC_CODEC_I4444_10L;
default: return 0;
}
}
if (bit_depth_minus8 == 4) // 12-bit
{
switch(chroma_format_idc)
{
case 0: return VLC_CODEC_GREY_12L;
case 2: return VLC_CODEC_I422_12L;
case 3: return VLC_CODEC_I444_12L;
// case 4: return VLC_CODEC_I4444_12L;
default: return 0;
}
}
// if (bit_depth_minus8 == 6) // 14-bit
// {
// switch(chroma_format_idc)
// {
// case 0: return VLC_CODEC_GREY_14L;
// case 2: return VLC_CODEC_I422_14L;
// case 3: return VLC_CODEC_I444_14L;
// // case 4: return VLC_CODEC_I4444_14L;
// default: return 0;
// }
// }
if (bit_depth_minus8 == 8) // 16-bit
{
switch(chroma_format_idc)
{
case 0: return VLC_CODEC_GREY_16L;
case 2: return VLC_CODEC_I422_16L;
case 3: return VLC_CODEC_I444_16L;
// case 4: return VLC_CODEC_I4444_16L;
default: return 0;
}
}
return 0;
}
static int imgb_addref(oapv_imgb_t *imgb)
{
return imgb->refcnt++;
}
static int imgb_getref(oapv_imgb_t *imgb)
{
return imgb->refcnt;
}
static int imgb_release(oapv_imgb_t *imgb)
{
if (--imgb->refcnt == 0)
{
picture_Release(imgb->pdata[0]);
free(imgb);
return 0;
}
return imgb->refcnt;
}
static oapv_imgb_t *GetImage( decoder_t *p_dec )
{
struct oapv_sys *sys = p_dec->p_sys;
oapv_imgb_t *imgb = calloc(1, sizeof(*imgb));
if (imgb == NULL)
return NULL;
picture_t *pic = decoder_NewPicture( p_dec );
if (pic == NULL)
goto fail;
imgb->refcnt = 1;
imgb->cs = sys->cs;
imgb->pdata[0] = pic;
imgb->np = pic->i_planes;
for (int i = 0; i < pic->i_planes; i++)
{
imgb->w[i] = pic->p[i].i_visible_pitch / pic->p[i].i_pixel_pitch;
imgb->h[i] = pic->p[i].i_visible_lines / pic->p[i].i_pixel_pitch;
imgb->aw[i] = pic->p[i].i_pitch / pic->p[i].i_pixel_pitch;
imgb->s[i] = pic->p[i].i_pitch;
imgb->ah[i] = pic->p[i].i_lines / pic->p[i].i_pixel_pitch;
imgb->e[i] = pic->p[i].i_lines;
imgb->bsize[i] = imgb->s[i] * imgb->e[i];
imgb->a[i] = pic->p[i].p_pixels;
}
imgb->addref = imgb_addref;
imgb->getref = imgb_getref;
imgb->release = imgb_release;
return imgb;
fail:
free(imgb);
return NULL;
}
static int Decode( decoder_t *p_dec, block_t *p_block )
{
struct oapv_sys *sys = p_dec->p_sys;
if (p_block == NULL) // drain nothing to do
return VLCDEC_SUCCESS;
oapv_frms_t ofrms = { 0 };
oapvm_t mid = { 0 };
oapv_bitb_t bitb = { 0 };
oapvd_stat_t stats = { 0 };
int err;
ofrms.num_frms = 1;
ofrms.frm[0].imgb = GetImage(p_dec);
if (ofrms.frm[0].imgb == NULL)
return VLCDEC_ECRITICAL; // no more memory ?
bitb.addr = p_block->p_buffer;
bitb.ssize = p_block->i_buffer;
err = oapvd_decode(sys->decoder, &bitb, &ofrms, &mid, &stats);
if (unlikely(err != OAPV_OK))
{
msg_Err( p_dec, "decoding error %d", err );
ofrms.frm[0].imgb->release(ofrms.frm[0].imgb);
return VLCDEC_ECRITICAL;
}
assert(stats.aui.num_frms == 1 || stats.aui.num_frms == 0);
if (stats.aui.num_frms == 1)
{
picture_t *decoded = ofrms.frm[0].imgb->pdata[0];
picture_Hold(decoded);
decoded->date = p_block->i_pts != VLC_TICK_INVALID ? p_block->i_pts : p_block->i_dts;
decoder_QueueVideo( p_dec, decoded );
}
for (int i=0; i<stats.aui.num_frms; i++)
{
ofrms.frm[i].imgb->release(ofrms.frm[i].imgb);
}
return VLCDEC_SUCCESS;
}
int OpenAPVDecoder(vlc_object_t *o)
{
decoder_t *dec = container_of(o, decoder_t, obj);
if (dec->fmt_in->i_codec != VLC_CODEC_APV)
return VLC_ENOTSUP;
uint8_t color_primaries = 2;
uint8_t transfer_characteristics = 2;
uint8_t matrix_coefficients = 2;
uint8_t profile_idc = 0;
uint8_t chroma_format_idc = 0;
uint8_t bit_depth_minus8 = 2;
bool full_range_flag = false;
uint32_t frame_width = dec->fmt_in->video.i_width;
uint32_t frame_height = dec->fmt_in->video.i_height;
// parse extradata to get stream info
bs_t extra;
bs_init(&extra, dec->fmt_in->p_extra, dec->fmt_in->i_extra);
uint8_t configurationVersion = bs_read(&extra, 8);
if (configurationVersion != 1)
{
msg_Dbg(o, "unknown configuration %" PRIu8, configurationVersion);
return VLC_ENOTSUP;
}
uint32_t number_of_configuration_entry = bs_read(&extra, 8);
if (number_of_configuration_entry != 1)
{
msg_Err(o, "unsupported number of configurations (%u)", (unsigned)number_of_configuration_entry);
return VLC_EGENERIC;
}
bs_skip(&extra, 8); // pbu_type
uint8_t number_of_frame_info = bs_read(&extra, 8);
if (number_of_frame_info != 1)
{
msg_Err(o, "unsupported number of frames (%u)", (unsigned)number_of_frame_info);
return VLC_EGENERIC;
}
bs_skip(&extra, 6);
bool color_description_present_flag = bs_read1(&extra);
bs_skip(&extra, 1); // capture_time_distance_ignored
profile_idc = bs_read(&extra, 8);
bs_skip(&extra, 8); // level_idc
bs_skip(&extra, 8); // band_idc
frame_width = bs_read(&extra, 32);
frame_height = bs_read(&extra, 32);
chroma_format_idc = bs_read(&extra, 4);
bit_depth_minus8 = bs_read(&extra, 4);
bs_skip(&extra, 8); // capture_time_distance
if (color_description_present_flag)
{
color_primaries = bs_read(&extra, 8);
transfer_characteristics = bs_read(&extra, 8);
matrix_coefficients = bs_read(&extra, 8);
full_range_flag = bs_read1(&extra);
bs_skip(&extra, 7); // reserved
}
struct oapv_sys *sys = vlc_obj_malloc(o, sizeof(*sys));
if (unlikely(sys == NULL))
return VLC_ENOMEM;
oapvd_cdesc_t desc = { 0 };
desc.threads = var_InheritInteger( o, "openapv-threads" );
sys->decoder = oapvd_create(&desc, NULL);
if (sys->decoder == NULL)
{
msg_Err(o, "failed to create decoder");
return VLC_EGENERIC;
}
if (dec->fmt_in->video.primaries == COLOR_PRIMARIES_UNDEF)
dec->fmt_out.video.primaries = iso_23001_8_cp_to_vlc_primaries(color_primaries);
else
dec->fmt_out.video.primaries = dec->fmt_in->video.primaries;
if (dec->fmt_in->video.transfer == TRANSFER_FUNC_UNDEF)
dec->fmt_out.video.transfer = iso_23001_8_tc_to_vlc_xfer(transfer_characteristics);
else
dec->fmt_out.video.transfer = dec->fmt_in->video.transfer;
if (dec->fmt_in->video.space == COLOR_SPACE_UNDEF)
dec->fmt_out.video.space = iso_23001_8_mc_to_vlc_coeffs(matrix_coefficients);
else
dec->fmt_out.video.space = dec->fmt_in->video.space;
if (dec->fmt_in->video.color_range == COLOR_RANGE_UNDEF)
dec->fmt_out.video.color_range = full_range_flag ? COLOR_RANGE_FULL : COLOR_RANGE_LIMITED;
else
dec->fmt_out.video.color_range = dec->fmt_in->video.color_range;
dec->fmt_out.video.i_width = frame_width;
dec->fmt_out.video.i_height = frame_height;
dec->fmt_out.i_codec = FindVlcChroma(profile_idc, bit_depth_minus8, chroma_format_idc);
dec->fmt_out.video.i_chroma = dec->fmt_out.i_codec;
if (decoder_UpdateVideoOutput(dec, NULL) != 0)
{
msg_Err(o, "decoder_UpdateVideoOutput failed");
CloseAPVDecoder(o);
return VLC_EGENERIC;
}
for (size_t i=0; i < ARRAY_SIZE(vlc_oapv_chromas); i++)
{
if (dec->fmt_out.video.i_chroma == vlc_oapv_chromas[i].vlc)
{
sys->cs = vlc_oapv_chromas[i].oapv;
break;
}
}
dec->p_sys = sys;
dec->pf_decode = Decode;
return VLC_SUCCESS;
}
void CloseAPVDecoder(vlc_object_t *o)
{
decoder_t *dec = container_of(o, decoder_t, obj);
struct oapv_sys *sys = dec->p_sys;
oapvd_delete(sys->decoder);
}