android_external_ffmpeg/libavformat/conformenc.c
2025-06-24 22:46:48 -05:00

276 lines
8.7 KiB
C

/*
* Copyright (c) 2020 John Cox for Raspberry Pi Trading
*
* This file is part of FFmpeg.
*
* FFmpeg 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.
*
* FFmpeg 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 FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// *** This module is a work in progress and its utility is strictly
// limited to testing.
#include "libavutil/opt.h"
#include "libavutil/frame.h"
#include "libavutil/md5.h"
#include "libavutil/mem.h"
#include "libavutil/pixdesc.h"
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_drm.h"
#include "mux.h"
#include "pthread.h"
#include <semaphore.h>
#include <unistd.h>
#define TRACE_ALL 1
#define DRM_MODULE "vc4"
// Aux size should only need to be 2, but on a few streams (Hobbit) under FKMS
// we get initial flicker probably due to dodgy drm timing
#define AUX_SIZE 3
typedef struct conform_display_env_s
{
AVClass *class;
void * line_buf;
size_t line_size;
struct AVMD5 * frame_md5;
struct AVMD5 * md5;
int conform_file;
int frame_md5_flag;
unsigned long long foffset;
unsigned int fno;
} conform_display_env_t;
static int conform_vout_write_trailer(AVFormatContext *s)
{
conform_display_env_t * const de = s->priv_data;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s\n", __func__);
#endif
if (de->md5) {
uint8_t m[16];
av_md5_final(de->md5, m);
avio_printf(s->pb, "MD5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]);
}
return 0;
}
static int conform_vout_write_header(AVFormatContext *s)
{
conform_display_env_t * const de = s->priv_data;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s\n", __func__);
#endif
if (de->md5)
av_md5_init(de->md5);
de->fno = 1;
de->foffset = 0;
return 0;
}
static int conform_vout_write_packet(AVFormatContext *s, AVPacket *pkt)
{
const AVFrame * const sf = (AVFrame *)pkt->data;
AVFrame * cf = NULL;
const AVFrame * f = sf;
conform_display_env_t * const de = s->priv_data;
const AVPixFmtDescriptor * pix_desc = av_pix_fmt_desc_get(sf->format);
int is_hw = (pix_desc->flags & AV_PIX_FMT_FLAG_HWACCEL) != 0;
enum AVPixelFormat fmt = is_hw ? AV_PIX_FMT_NONE : sf->format;
unsigned int i;
if (is_hw) {
enum AVPixelFormat *xfmts = NULL;
av_hwframe_transfer_get_formats(sf->hw_frames_ctx, AV_HWFRAME_TRANSFER_DIRECTION_FROM, &xfmts, 0);
fmt = *xfmts;
av_free(xfmts);
}
av_log(s, AV_LOG_DEBUG, "%s: Frame %3d: %#08llx %dx%d crop(ltrb) %zd,%zd,%zd,%zd fmt %s -> %s\n", __func__,
de->fno, de->foffset,
sf->width, sf->height, sf->crop_left, sf->crop_top, sf->crop_right, sf->crop_bottom,
av_get_pix_fmt_name(sf->format), av_get_pix_fmt_name(fmt));
if ((sf->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
av_log(s, AV_LOG_WARNING, "Discard corrupt frame: fmt=%d, ts=%" PRId64 "\n", sf->format, sf->pts);
if (de->frame_md5)
avio_printf(s->pb, "MD5-Frame-%d=*BAD*\n", de->fno);
++de->fno;
return 0;
}
if (!is_hw)
f = sf;
else
{
cf = av_frame_alloc();
cf->format = fmt;
av_hwframe_transfer_data(cf, sf, AV_HWFRAME_TRANSFER_DIRECTION_FROM);
pix_desc = av_pix_fmt_desc_get(cf->format);
f = cf;
}
if (de->frame_md5)
av_md5_init(de->frame_md5);
// This is fully generic - much optimisation possible
for (i = 0; i != pix_desc->nb_components; ++i) {
const AVComponentDescriptor * const cd = pix_desc->comp + i;
const unsigned int srw = ((i == 1 || i == 2) ? pix_desc->log2_chroma_w : 0);
const unsigned int rndw = (1 << srw) - 1;
const unsigned int srh = ((i == 1 || i == 2) ? pix_desc->log2_chroma_h : 0);
const unsigned int rndh = (1 << srh) - 1;
const unsigned int srp = cd->shift;
const unsigned int bpp = cd->depth > 8 ? 2 : 1;
const unsigned int h = (f->height - (f->crop_top + f->crop_bottom) + rndh) >> srh;
unsigned int y;
for (y = 0; y < h; ++y) {
const void *const lstart = f->data[cd->plane] + (y + (f->crop_top >> srh)) * f->linesize[cd->plane] + cd->offset + (f->crop_left >> srw) * cd->step;
const unsigned int w = (f->width - (f->crop_left + f->crop_right) + rndw) >> srw;
unsigned int x;
if (bpp == 1) {
uint8_t *d = de->line_buf;
const uint8_t *s = lstart;
for (x = 0; x != w; ++x) {
*d++ = *s >> srp;
s += cd->step;
}
}
else {
uint16_t *d = de->line_buf;
const uint8_t *s = lstart;
for (x = 0; x != w; ++x) {
*d++ = *(uint16_t*)s >> srp;
s += cd->step;
}
}
// We have one line
if (de->frame_md5)
av_md5_update(de->frame_md5, de->line_buf, w * bpp);
if (de->md5)
av_md5_update(de->md5, de->line_buf, w * bpp);
else {
avio_write(s->pb, de->line_buf, w * bpp);
de->foffset += w * bpp;
}
}
}
if (de->frame_md5) {
uint8_t m[16];
av_md5_final(de->frame_md5, m);
avio_printf(s->pb, "MD5-Frame-%d=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
de->fno,
m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]);
}
++de->fno;
av_frame_free(&cf);
return 0;
}
static int conform_vout_write_frame(AVFormatContext *s, int stream_index, AVFrame **ppframe,
unsigned flags)
{
av_log(s, AV_LOG_ERROR, "%s: NIF: idx=%d, flags=%#x\n", __func__, stream_index, flags);
return AVERROR_PATCHWELCOME;
}
// deinit is called if init fails so no need to clean up explicity here
static int conform_vout_init(struct AVFormatContext * s)
{
conform_display_env_t * const de = s->priv_data;
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
de->line_size = (8192 * 4); // 4bpp * 8k seems plenty
de->line_buf = av_malloc(de->line_size);
if (!de->conform_file) {
de->md5 = av_md5_alloc();
if (de->frame_md5_flag)
de->frame_md5 = av_md5_alloc();
}
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
return 0;
}
static void conform_vout_deinit(struct AVFormatContext * s)
{
conform_display_env_t * const de = s->priv_data;
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
av_freep(&de->line_buf);
av_freep(&de->md5);
av_freep(&de->frame_md5);
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
}
#define OFFSET(x) offsetof(conform_display_env_t, x)
static const AVOption options[] = {
{ "conform_frame_md5", "Produce per-frame MD5s as well as final", OFFSET(frame_md5_flag), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ "conform_yuv", "Output yuv file rather than md5", OFFSET(conform_file), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ NULL }
};
static const AVClass conform_vid_class = {
.class_name = "conform vid muxer",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
};
const FFOutputFormat ff_conform_muxer = {
.p = {
.name = "conform",
.long_name = NULL_IF_CONFIG_SMALL("Video out conformance test helper"),
.audio_codec = AV_CODEC_ID_NONE,
.video_codec = AV_CODEC_ID_WRAPPED_AVFRAME,
.flags = AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
.priv_class = &conform_vid_class,
},
.priv_data_size = sizeof(conform_display_env_t),
.write_header = conform_vout_write_header,
.write_packet = conform_vout_write_packet,
.write_uncoded_frame = conform_vout_write_frame,
.write_trailer = conform_vout_write_trailer,
.init = conform_vout_init,
.deinit = conform_vout_deinit,
};