mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-04-05 08:04:41 +00:00
fixes decode broken since the commit 7dd9a44 (2025-10-23)
refers to
<https://github.com/CESNET/UltraGrid/issues/479>
closes
<https://github.com/CESNET/UltraGrid/issues/481>
422 lines
14 KiB
C
422 lines
14 KiB
C
/**
|
|
* @file lavc_common.c
|
|
* @author Martin Pulec <pulec@cesnet.cz>
|
|
* @author Martin Piatka <445597@mail.muni.cz>
|
|
*/
|
|
/*
|
|
* Copyright (c) 2013-2025 CESNET, zájmové sdružení právnických osob
|
|
* 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.
|
|
*/
|
|
/**
|
|
* @file
|
|
* References:
|
|
* 1. [v210](https://wiki.multimedia.cx/index.php/V210)
|
|
*
|
|
* @todo
|
|
* Some conversions to RGBA ignore RGB-shifts - either fix that or deprecate RGB-shifts
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "debug.h"
|
|
#include "host.h"
|
|
#include "libavcodec/lavc_common.h"
|
|
#include <libavutil/channel_layout.h>
|
|
#include "utils/macros.h"
|
|
#include "video.h"
|
|
|
|
#define MOD_NAME "[lavc_common] "
|
|
|
|
//
|
|
// UG <-> FFMPEG format translations
|
|
//
|
|
static const struct {
|
|
enum AVCodecID av;
|
|
codec_t uv;
|
|
} av_to_uv_map[] = {
|
|
{ AV_CODEC_ID_H264, H264 },
|
|
{ AV_CODEC_ID_HEVC, H265 },
|
|
{ AV_CODEC_ID_MJPEG, JPEG },
|
|
{ AV_CODEC_ID_JPEG2000, J2K },
|
|
{ AV_CODEC_ID_VP8, VP8 },
|
|
{ AV_CODEC_ID_VP9, VP9 },
|
|
{ AV_CODEC_ID_HUFFYUV, HFYU },
|
|
{ AV_CODEC_ID_FFV1, FFV1 },
|
|
{ AV_CODEC_ID_AV1, AV1 },
|
|
{ AV_CODEC_ID_PRORES, PRORES },
|
|
{ AV_CODEC_ID_PRORES, PRORES_4444 },
|
|
{ AV_CODEC_ID_PRORES, PRORES_4444_XQ },
|
|
{ AV_CODEC_ID_PRORES, PRORES_422_HQ },
|
|
{ AV_CODEC_ID_PRORES, PRORES_422 },
|
|
{ AV_CODEC_ID_PRORES, PRORES_422_PROXY },
|
|
{ AV_CODEC_ID_PRORES, PRORES_422_LT },
|
|
{ AV_CODEC_ID_APV, APV },
|
|
};
|
|
|
|
codec_t get_av_to_ug_codec(enum AVCodecID av_codec)
|
|
{
|
|
for (unsigned int i = 0; i < sizeof av_to_uv_map / sizeof av_to_uv_map[0]; ++i) {
|
|
if (av_to_uv_map[i].av == av_codec) {
|
|
return av_to_uv_map[i].uv;
|
|
}
|
|
}
|
|
return VIDEO_CODEC_NONE;
|
|
}
|
|
|
|
enum AVCodecID get_ug_to_av_codec(codec_t ug_codec)
|
|
{
|
|
for (unsigned int i = 0; i < sizeof av_to_uv_map / sizeof av_to_uv_map[0]; ++i) {
|
|
if (av_to_uv_map[i].uv == ug_codec) {
|
|
return av_to_uv_map[i].av;
|
|
}
|
|
}
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
//
|
|
// utility functions
|
|
//
|
|
void print_libav_error(int verbosity, const char *msg, int rc) {
|
|
char errbuf[1024];
|
|
av_strerror(rc, errbuf, sizeof(errbuf));
|
|
|
|
log_msg(verbosity, "%s: %s\n", msg, errbuf);
|
|
}
|
|
|
|
void printf_libav_error(int verbosity, int rc, const char *msg, ...) {
|
|
char message[1024];
|
|
|
|
va_list ap;
|
|
va_start(ap, msg);
|
|
vsnprintf(message, sizeof message, msg, ap);
|
|
va_end(ap);
|
|
|
|
print_libav_error(verbosity, message, rc);
|
|
}
|
|
|
|
bool libav_codec_has_extradata(codec_t codec) {
|
|
return codec == HFYU || codec == FFV1;
|
|
}
|
|
|
|
static inline int av_to_uv_log(int level) {
|
|
enum {
|
|
AV_TO_UV_MULT = 8, // FF uses multiples of 8
|
|
};
|
|
if (level <= AV_LOG_PANIC) { // AV_LOG_QUIET(-8) -> LOG_LEVEL_QUIET(0)
|
|
level += AV_TO_UV_MULT;// AV_LOG_PANIC(0) -> LOG_LEVEL_FATAL(1)
|
|
}
|
|
// AV_LOG_FATAL(8), ERROR and WARNING map directly to UG counterparts /8
|
|
if (level >= AV_LOG_INFO) { // AV_LOG_INFO(32) -> LOG_LEVEL_INFO(5)
|
|
level += AV_TO_UV_MULT; // and so on for the rest
|
|
}
|
|
return level / AV_TO_UV_MULT;
|
|
}
|
|
|
|
/**
|
|
* Filters out annoying messages that should not be passed to UltraGrid logger,
|
|
* eg. complains on JPEG APP markers that FFmpeg decoder almost doesn't use.
|
|
* @returns 0 - should be printed; 1 - filtered
|
|
*/
|
|
static _Bool av_log_filter(const char *ff_module_name, const char *fmt) {
|
|
if (ff_module_name && strcmp(ff_module_name, "mjpeg") == 0 && strstr(fmt, "APP") != NULL) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void av_log_ug_callback(void *avcl, int av_level, const char *fmt, va_list vl) {
|
|
int level = av_to_uv_log(av_level);
|
|
if (level > log_level) {
|
|
return;
|
|
}
|
|
// avcl handling is taken from av_log_default_callback
|
|
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
|
|
const char *ff_module_name = avc ? avc->item_name(avcl) : NULL;
|
|
if (av_log_filter(ff_module_name, fmt)) {
|
|
return;
|
|
}
|
|
static _Thread_local char buf[STR_LEN];
|
|
if (strlen(buf) == 0) {
|
|
snprintf(buf + strlen(buf), sizeof buf - strlen(buf), "[lavc");
|
|
if (ff_module_name) {
|
|
snprintf(buf + strlen(buf), sizeof buf - strlen(buf),
|
|
" %s @ %p", ff_module_name, avcl);
|
|
}
|
|
snprintf(buf + strlen(buf), sizeof buf - strlen(buf), "] ");
|
|
}
|
|
vsnprintf(buf + strlen(buf), sizeof buf - strlen(buf), fmt, vl);
|
|
if (buf[strlen(buf) - 1] != '\n' && strlen(buf) < sizeof buf - 1) {
|
|
return;
|
|
}
|
|
if (strlen(buf) == sizeof buf - 1 && buf[strlen(buf) - 1] != '\n') {
|
|
MSG(WARNING, "logger buffer full! flushing output:\n");
|
|
}
|
|
log_msg(level, "%s", buf);
|
|
buf[0] = '\0';
|
|
}
|
|
|
|
ADD_TO_PARAM("lavc-log-level",
|
|
"* lavc-log-level=<num>\n"
|
|
" Set libavcodec log level (FFmpeg range semantics, bypasses UG logger)\n"
|
|
" - 'D' - use FFmpeg default log handler\n");
|
|
/// Sets specified log level either given explicitly or from UG-wide log_level
|
|
void ug_set_av_logging() {
|
|
const char *param = get_commandline_param("lavc-log-level");
|
|
if (param == NULL) {
|
|
av_log_set_callback(av_log_ug_callback);
|
|
return;
|
|
}
|
|
char *endptr = NULL;
|
|
const long av_log_level = strtol(param, &endptr, 10);
|
|
if (av_log_level < AV_LOG_QUIET || av_log_level > AV_LOG_TRACE ||
|
|
endptr == param) {
|
|
MSG(ERROR, "Wrong log level for FFmpeg '%s'\n", param);
|
|
}
|
|
av_log_set_level((int) av_log_level);
|
|
}
|
|
|
|
/// @returns subsampling in 'JabA' format (compatible with @ref get_subsamping)
|
|
int av_pixfmt_get_subsampling(enum AVPixelFormat fmt) {
|
|
const struct AVPixFmtDescriptor *pd = av_pix_fmt_desc_get(fmt);
|
|
if (pd->log2_chroma_w == 0 && pd->log2_chroma_h == 0) {
|
|
return 4440;
|
|
}
|
|
if (pd->log2_chroma_w == 1 && pd->log2_chroma_h == 0) {
|
|
return 4220;
|
|
}
|
|
if (pd->log2_chroma_w == 1 && pd->log2_chroma_h == 1) {
|
|
return 4200;
|
|
}
|
|
return 0; // other (todo)
|
|
}
|
|
|
|
struct pixfmt_desc av_pixfmt_get_desc(enum AVPixelFormat pixfmt) {
|
|
struct pixfmt_desc ret = { 0 };
|
|
const struct AVPixFmtDescriptor *avd = av_pix_fmt_desc_get(pixfmt);
|
|
ret.depth = avd->comp[0].depth;
|
|
ret.rgb = avd->flags & AV_PIX_FMT_FLAG_RGB;
|
|
ret.subsampling = av_pixfmt_get_subsampling(pixfmt);
|
|
return ret;
|
|
}
|
|
|
|
|
|
void lavd_flush(AVCodecContext *codec_ctx) {
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
|
|
int ret = 0;
|
|
char errbuf[AV_ERROR_MAX_STRING_SIZE];
|
|
ret = avcodec_send_packet(codec_ctx, NULL);
|
|
if (ret != 0) {
|
|
av_strerror(ret, errbuf, sizeof errbuf);
|
|
log_msg(LOG_LEVEL_WARNING,
|
|
MOD_NAME
|
|
"lavd_flush send - unexpected return value: %s (%d)\n",
|
|
errbuf, ret);
|
|
}
|
|
AVFrame *frame = av_frame_alloc();
|
|
do {
|
|
ret = avcodec_receive_frame(codec_ctx, frame);
|
|
} while (ret >= 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN));
|
|
if (ret < 0 && ret != AVERROR_EOF && ret != AVERROR(EAGAIN)) {
|
|
av_strerror(ret, errbuf, sizeof errbuf);
|
|
log_msg(LOG_LEVEL_WARNING,
|
|
MOD_NAME
|
|
"lavd_flush recv - unexpected return value: %s (%d)\n",
|
|
errbuf, ret);
|
|
}
|
|
av_frame_free(&frame);
|
|
#else
|
|
UNUSED(codec_ctx);
|
|
#endif
|
|
}
|
|
void print_decoder_error(const char *mod_name, int rc) {
|
|
char buf[1024];
|
|
switch (rc) {
|
|
case 0:
|
|
break;
|
|
case EAGAIN:
|
|
log_msg(LOG_LEVEL_VERBOSE,
|
|
"%sNo frame returned - needs more input data.\n",
|
|
mod_name);
|
|
break;
|
|
case EINVAL:
|
|
log_msg(LOG_LEVEL_ERROR, "%sDecoder in invalid state!\n",
|
|
mod_name);
|
|
break;
|
|
default:
|
|
av_strerror(rc, buf, sizeof buf);
|
|
log_msg(LOG_LEVEL_WARNING,
|
|
"%sError while decoding frame (rc == %d): %s.\n",
|
|
mod_name, rc, buf);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *lavc_thread_type_to_str(int thread_type) {
|
|
static _Thread_local char buf[128];
|
|
memset(buf, 0, sizeof buf);
|
|
if ((thread_type & FF_THREAD_FRAME) != 0) {
|
|
strncpy(buf, "thread", sizeof buf - 1);
|
|
}
|
|
if ((thread_type & FF_THREAD_SLICE) != 0) {
|
|
if (strlen(buf) > 0) {
|
|
strncpy(buf, ", ", sizeof buf - 1);
|
|
}
|
|
strncpy(buf, "slice", sizeof buf - 1);
|
|
}
|
|
if (strlen(buf) == 0) {
|
|
return "(other)";
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
struct audio_desc
|
|
audio_desc_from_av_frame(const AVFrame *frm)
|
|
{
|
|
struct audio_desc desc = { 0 };
|
|
desc.bps = av_get_bytes_per_sample(frm->format);
|
|
#if FF_API_NEW_CHANNEL_LAYOUT
|
|
desc.ch_count = frm->ch_layout.nb_channels;
|
|
#else
|
|
desc.ch_count = av_get_channel_layout_nb_channels(frm->channel_layout);
|
|
#endif
|
|
desc.codec = AC_PCM;
|
|
desc.sample_rate = frm->sample_rate;
|
|
|
|
return desc;
|
|
}
|
|
|
|
enum AVSampleFormat
|
|
audio_bps_to_av_sample_fmt(int bps, bool planar)
|
|
{
|
|
switch (bps) {
|
|
case 1:
|
|
return planar ? AV_SAMPLE_FMT_U8P : AV_SAMPLE_FMT_U8;
|
|
case 2:
|
|
return planar ? AV_SAMPLE_FMT_S16P : AV_SAMPLE_FMT_S16;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
return planar ? AV_SAMPLE_FMT_S32P : AV_SAMPLE_FMT_S32;
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints space-separated names of AVPixelFormats in AV_PIX_FMT_NONE-terminated
|
|
* pixfmts list to given buf and returns pointer to given buf.
|
|
*/
|
|
const char *
|
|
get_avpixfmts_names(const enum AVPixelFormat *pixfmts)
|
|
{
|
|
_Thread_local static char buf[STR_LEN];
|
|
if (pixfmts == NULL || *pixfmts == AV_PIX_FMT_NONE) {
|
|
snprintf(buf, sizeof buf, "(none)");
|
|
return buf;
|
|
}
|
|
buf[0] = '\0';
|
|
const enum AVPixelFormat *it = pixfmts;
|
|
while (*it != AV_PIX_FMT_NONE) {
|
|
snprintf(buf + strlen(buf), sizeof buf - strlen(buf), "%s%s",
|
|
it != pixfmts ? " " : "", av_get_pix_fmt_name(*it));
|
|
it++;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @param ctx may be nullptr if codec is not
|
|
* @param codec may be nullptr if ctx is not
|
|
*
|
|
* If passed ctx, values such as `strict_std_compliance` may affect the result.
|
|
*/
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(61, 13, 100)
|
|
static const void *
|
|
avc_get_supported_config(const AVCodecContext *ctx, const AVCodec *codec,
|
|
enum AVCodecConfig config)
|
|
{
|
|
const void *ret = NULL;
|
|
int unused_count = 0;
|
|
const int rc = avcodec_get_supported_config(
|
|
ctx, codec, config, /*flags*/ 0, &ret,
|
|
&unused_count);
|
|
if (rc != 0) {
|
|
MSG(ERROR, "Cannot get list of supported config %d for %s!\n",
|
|
(int) config, codec->name);
|
|
return NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
///< @copydoc avc_get_supported_config
|
|
const enum AVPixelFormat *
|
|
avc_get_supported_pix_fmts(const AVCodecContext *ctx, const AVCodec *codec)
|
|
{
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(61, 13, 100)
|
|
return avc_get_supported_config(ctx, codec, AV_CODEC_CONFIG_PIX_FORMAT);
|
|
#else
|
|
if (ctx != NULL) {
|
|
return ctx->codec->pix_fmts;
|
|
}
|
|
return codec->pix_fmts;
|
|
#endif
|
|
}
|
|
///< @copydoc avc_get_supported_config
|
|
const enum AVSampleFormat *
|
|
avc_get_supported_sample_fmts(const AVCodecContext *ctx, const AVCodec *codec)
|
|
{
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(61, 13, 100)
|
|
return avc_get_supported_config(ctx, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT);
|
|
#else
|
|
if (ctx != NULL) {
|
|
return ctx->codec->sample_fmts;
|
|
}
|
|
return codec->sample_fmts;
|
|
#endif
|
|
}
|
|
///< @copydoc avc_get_supported_config
|
|
const int *avc_get_supported_sample_rates(const AVCodecContext *ctx, const AVCodec *codec)
|
|
{
|
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(61, 13, 100)
|
|
return avc_get_supported_config(ctx, codec, AV_CODEC_CONFIG_SAMPLE_RATE);
|
|
#else
|
|
if (ctx != NULL) {
|
|
return ctx->codec->supported_samplerates;
|
|
}
|
|
return codec->supported_samplerates;
|
|
#endif
|
|
}
|
|
/* vi: set expandtab sw=8: */
|