android_external_ffmpeg/libavdevice/drm_vout.c
2025-06-24 22:46:48 -05:00

684 lines
18 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/pixdesc.h"
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_drm.h"
#include "libavformat/mux.h"
#include "avdevice.h"
#include "pthread.h"
#include <semaphore.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#define TRACE_ALL 0
#define DRM_MODULE "vc4"
#define ERRSTR strerror(errno)
struct drm_setup {
int conId;
uint32_t crtcId;
int crtcIdx;
uint32_t planeId;
unsigned int out_fourcc;
struct {
int x, y, width, height;
} compose;
};
typedef struct drm_aux_s {
unsigned int fb_handle;
uint32_t bo_handles[AV_DRM_MAX_PLANES];
AVFrame * frame;
} drm_aux_t;
// 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 drm_display_env_s
{
AVClass *class;
int drm_fd;
uint32_t con_id;
struct drm_setup setup;
enum AVPixelFormat avfmt;
int show_all;
const char * drm_module;
unsigned int ano;
drm_aux_t aux[AUX_SIZE];
pthread_t q_thread;
sem_t q_sem_in;
sem_t q_sem_out;
int q_terminate;
AVFrame * q_next;
} drm_display_env_t;
static int drm_vout_write_trailer(AVFormatContext *s)
{
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s\n", __func__);
#endif
return 0;
}
static int drm_vout_write_header(AVFormatContext *s)
{
const AVCodecParameters * const par = s->streams[0]->codecpar;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s\n", __func__);
#endif
if ( s->nb_streams > 1
|| par->codec_type != AVMEDIA_TYPE_VIDEO
|| par->codec_id != AV_CODEC_ID_WRAPPED_AVFRAME) {
av_log(s, AV_LOG_ERROR, "Only supports one wrapped avframe stream\n");
return AVERROR(EINVAL);
}
return 0;
}
static int find_plane(struct AVFormatContext * const avctx,
const int drmfd, const int crtcidx, const uint32_t format,
uint32_t * const pplane_id)
{
drmModePlaneResPtr planes;
drmModePlanePtr plane;
drmModeObjectPropertiesPtr props = NULL;
drmModePropertyPtr prop = NULL;
unsigned int i;
unsigned int j;
int ret = -1;
planes = drmModeGetPlaneResources(drmfd);
if (!planes)
{
av_log(avctx, AV_LOG_WARNING, "drmModeGetPlaneResources failed: %s\n", ERRSTR);
return -1;
}
for (i = 0; i < planes->count_planes; ++i) {
plane = drmModeGetPlane(drmfd, planes->planes[i]);
if (!planes)
{
av_log(avctx, AV_LOG_WARNING, "drmModeGetPlane failed: %s\n", ERRSTR);
break;
}
if (!(plane->possible_crtcs & (1 << crtcidx))) {
drmModeFreePlane(plane);
continue;
}
for (j = 0; j < plane->count_formats; ++j) {
if (plane->formats[j] == format)
break;
}
if (j == plane->count_formats) {
drmModeFreePlane(plane);
continue;
}
*pplane_id = plane->plane_id;
drmModeFreePlane(plane);
break;
}
if (i == planes->count_planes) {
ret = -1;
goto fail;
}
props = drmModeObjectGetProperties(drmfd, *pplane_id, DRM_MODE_OBJECT_PLANE);
if (!props)
goto fail;
for (i = 0; i != props->count_props; ++i) {
if (prop)
drmModeFreeProperty(prop);
prop = drmModeGetProperty(drmfd, props->props[i]);
if (!prop)
goto fail;
if (strcmp("zpos", prop->name) == 0) {
if (drmModeObjectSetProperty(drmfd, *pplane_id, DRM_MODE_OBJECT_PLANE, props->props[i], prop->values[1]) == 0)
av_log(avctx, AV_LOG_DEBUG, "ZPOS set to %d\n", (int)prop->values[1]);
else
av_log(avctx, AV_LOG_WARNING, "Failed to set ZPOS on DRM plane\n");
break;
}
}
ret = 0;
fail:
if (props)
drmModeFreeObjectProperties(props);
if (prop)
drmModeFreeProperty(prop);
drmModeFreePlaneResources(planes);
return ret;
}
static void da_uninit(drm_display_env_t * const de, drm_aux_t * da)
{
if (da->fb_handle != 0) {
drmModeRmFB(de->drm_fd, da->fb_handle);
da->fb_handle = 0;
}
for (unsigned int i = 0; i != AV_DRM_MAX_PLANES; ++i) {
if (da->bo_handles[i]) {
struct drm_gem_close gem_close = {.handle = da->bo_handles[i]};
drmIoctl(de->drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
da->bo_handles[i] = 0;
}
}
av_frame_free(&da->frame);
}
static int do_display(AVFormatContext * const s, drm_display_env_t * const de, AVFrame * frame)
{
const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor*)frame->data[0];
drm_aux_t * da = de->aux + de->ano;
const uint32_t format = desc->layers[0].format;
int ret = 0;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "<<< %s: fd=%d\n", __func__, desc->objects[0].fd);
#endif
if (de->setup.out_fourcc != format) {
if (find_plane(s, de->drm_fd, de->setup.crtcIdx, format, &de->setup.planeId)) {
av_frame_free(&frame);
av_log(s, AV_LOG_WARNING, "No plane for format: %#x\n", format);
return -1;
}
de->setup.out_fourcc = format;
}
{
drmVBlank vbl = {
.request = {
.type = DRM_VBLANK_RELATIVE,
.sequence = 0
}
};
while (drmWaitVBlank(de->drm_fd, &vbl)) {
if (errno != EINTR) {
// av_log(s, AV_LOG_WARNING, "drmWaitVBlank failed: %s\n", ERRSTR);
break;
}
}
}
da_uninit(de, da);
{
uint32_t pitches[4] = {0};
uint32_t offsets[4] = {0};
uint64_t modifiers[4] = {0};
uint32_t bo_handles[4] = {0};
int has_mods = 0;
int i, j, n;
da->frame = frame;
for (i = 0; i < desc->nb_objects; ++i) {
if (drmPrimeFDToHandle(de->drm_fd, desc->objects[i].fd, da->bo_handles + i) != 0) {
av_log(s, AV_LOG_WARNING, "drmPrimeFDToHandle[%d](%d) failed: %s\n", i, desc->objects[i].fd, ERRSTR);
return -1;
}
if (desc->objects[i].format_modifier != DRM_FORMAT_MOD_LINEAR &&
desc->objects[i].format_modifier != DRM_FORMAT_MOD_INVALID)
has_mods = 1;
}
n = 0;
for (i = 0; i < desc->nb_layers; ++i) {
for (j = 0; j < desc->layers[i].nb_planes; ++j) {
const AVDRMPlaneDescriptor * const p = desc->layers[i].planes + j;
const AVDRMObjectDescriptor * const obj = desc->objects + p->object_index;
pitches[n] = p->pitch;
offsets[n] = p->offset;
modifiers[n] = obj->format_modifier;
bo_handles[n] = da->bo_handles[p->object_index];
++n;
}
}
#if 1 && TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%dx%d, fmt: %x, boh=%d,%d,%d,%d, pitch=%d,%d,%d,%d,"
" offset=%d,%d,%d,%d, mod=%llx,%llx,%llx,%llx\n",
av_frame_cropped_width(frame),
av_frame_cropped_height(frame),
desc->layers[0].format,
bo_handles[0],
bo_handles[1],
bo_handles[2],
bo_handles[3],
pitches[0],
pitches[1],
pitches[2],
pitches[3],
offsets[0],
offsets[1],
offsets[2],
offsets[3],
(long long)modifiers[0],
(long long)modifiers[1],
(long long)modifiers[2],
(long long)modifiers[3]
);
#endif
if (drmModeAddFB2WithModifiers(de->drm_fd,
av_frame_cropped_width(frame),
av_frame_cropped_height(frame),
desc->layers[0].format, bo_handles,
pitches, offsets,
has_mods ? modifiers : NULL,
&da->fb_handle,
has_mods ? DRM_MODE_FB_MODIFIERS : 0) != 0) {
av_log(s, AV_LOG_WARNING, "drmModeAddFB2WithModifiers failed: %s\n", ERRSTR);
return -1;
}
}
ret = drmModeSetPlane(de->drm_fd, de->setup.planeId, de->setup.crtcId,
da->fb_handle, 0,
de->setup.compose.x, de->setup.compose.y,
de->setup.compose.width,
de->setup.compose.height,
0, 0,
av_frame_cropped_width(frame) << 16,
av_frame_cropped_height(frame) << 16);
if (ret != 0) {
av_log(s, AV_LOG_WARNING, "drmModeSetPlane failed: %s\n", ERRSTR);
}
de->ano = de->ano + 1 >= AUX_SIZE ? 0 : de->ano + 1;
return ret;
}
static int do_sem_wait(sem_t * const sem, const int nowait)
{
while (nowait ? sem_trywait(sem) : sem_wait(sem)) {
if (errno != EINTR)
return -errno;
}
return 0;
}
static void * display_thread(void * v)
{
AVFormatContext * const s = v;
drm_display_env_t * const de = s->priv_data;
int i;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
#endif
sem_post(&de->q_sem_out);
for (;;) {
AVFrame * frame;
do_sem_wait(&de->q_sem_in, 0);
if (de->q_terminate)
break;
frame = de->q_next;
de->q_next = NULL;
sem_post(&de->q_sem_out);
do_display(s, de, frame);
}
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
#endif
for (i = 0; i != AUX_SIZE; ++i)
da_uninit(de, de->aux + i);
av_frame_free(&de->q_next);
return NULL;
}
static int drm_vout_write_packet(AVFormatContext *s, AVPacket *pkt)
{
const AVFrame * const src_frame = (AVFrame *)pkt->data;
AVFrame * frame;
drm_display_env_t * const de = s->priv_data;
int ret;
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s\n", __func__);
#endif
if ((src_frame->flags & AV_FRAME_FLAG_CORRUPT) != 0) {
av_log(s, AV_LOG_WARNING, "Discard corrupt frame: fmt=%d, ts=%" PRId64 "\n", src_frame->format, src_frame->pts);
return 0;
}
if (src_frame->format == AV_PIX_FMT_DRM_PRIME) {
frame = av_frame_alloc();
av_frame_ref(frame, src_frame);
}
else if (src_frame->format == AV_PIX_FMT_VAAPI) {
frame = av_frame_alloc();
frame->format = AV_PIX_FMT_DRM_PRIME;
if (av_hwframe_map(frame, src_frame, 0) != 0)
{
av_log(s, AV_LOG_WARNING, "Failed to map frame (format=%d) to DRM_PRiME\n", src_frame->format);
av_frame_free(&frame);
return AVERROR(EINVAL);
}
}
else {
av_log(s, AV_LOG_WARNING, "Frame (format=%d) not DRM_PRiME\n", src_frame->format);
return AVERROR(EINVAL);
}
ret = do_sem_wait(&de->q_sem_out, !de->show_all);
if (ret) {
av_frame_free(&frame);
}
else {
de->q_next = frame;
sem_post(&de->q_sem_in);
}
return 0;
}
static int drm_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;
}
static int drm_vout_control_message(AVFormatContext *s, int type, void *data, size_t data_size)
{
#if TRACE_ALL
av_log(s, AV_LOG_DEBUG, "%s: %d\n", __func__, type);
#endif
switch(type) {
case AV_APP_TO_DEV_WINDOW_REPAINT:
return 0;
default:
break;
}
return AVERROR(ENOSYS);
}
static int find_crtc(struct AVFormatContext * const avctx, int drmfd, struct drm_setup *s, uint32_t * const pConId)
{
int ret = -1;
int i;
drmModeRes *res = drmModeGetResources(drmfd);
drmModeConnector *c;
if(!res)
{
printf( "drmModeGetResources failed: %s\n", ERRSTR);
return -1;
}
if (res->count_crtcs <= 0)
{
printf( "drm: no crts\n");
goto fail_res;
}
if (!s->conId) {
fprintf(stderr,
"No connector ID specified. Choosing default from list:\n");
for (i = 0; i < res->count_connectors; i++) {
drmModeConnector *con =
drmModeGetConnector(drmfd, res->connectors[i]);
drmModeEncoder *enc = NULL;
drmModeCrtc *crtc = NULL;
if (con->encoder_id) {
enc = drmModeGetEncoder(drmfd, con->encoder_id);
if (enc->crtc_id) {
crtc = drmModeGetCrtc(drmfd, enc->crtc_id);
}
}
if (!s->conId && crtc) {
s->conId = con->connector_id;
s->crtcId = crtc->crtc_id;
}
av_log(avctx, AV_LOG_DEBUG, "Connector %d (crtc %d): type %d, %dx%d%s\n",
con->connector_id,
crtc ? crtc->crtc_id : 0,
con->connector_type,
crtc ? crtc->width : 0,
crtc ? crtc->height : 0,
(s->conId == (int)con->connector_id ?
" (chosen)" : ""));
if (crtc)
drmModeFreeCrtc(crtc);
if (enc)
drmModeFreeEncoder(enc);
if (con)
drmModeFreeConnector(con);
}
if (!s->conId) {
av_log(avctx, AV_LOG_ERROR,
"No suitable enabled connector found.\n");
return -1;;
}
}
s->crtcIdx = -1;
for (i = 0; i < res->count_crtcs; ++i) {
if (s->crtcId == res->crtcs[i]) {
s->crtcIdx = i;
break;
}
}
if (s->crtcIdx == -1)
{
av_log(avctx, AV_LOG_WARNING, "drm: CRTC %u not found\n", s->crtcId);
goto fail_res;
}
if (res->count_connectors <= 0)
{
av_log(avctx, AV_LOG_WARNING, "drm: no connectors\n");
goto fail_res;
}
c = drmModeGetConnector(drmfd, s->conId);
if (!c)
{
av_log(avctx, AV_LOG_WARNING, "drmModeGetConnector failed: %s\n", ERRSTR);
goto fail_res;
}
if (!c->count_modes)
{
av_log(avctx, AV_LOG_WARNING, "connector supports no mode\n");
goto fail_conn;
}
{
drmModeCrtc *crtc = drmModeGetCrtc(drmfd, s->crtcId);
s->compose.x = crtc->x;
s->compose.y = crtc->y;
s->compose.width = crtc->width;
s->compose.height = crtc->height;
drmModeFreeCrtc(crtc);
}
if (pConId)
*pConId = c->connector_id;
ret = 0;
fail_conn:
drmModeFreeConnector(c);
fail_res:
drmModeFreeResources(res);
return ret;
}
// deinit is called if init fails so no need to clean up explicity here
static int drm_vout_init(struct AVFormatContext * s)
{
drm_display_env_t * const de = s->priv_data;
int rv;
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
de->drm_fd = -1;
de->con_id = 0;
de->setup = (struct drm_setup){0};
de->q_terminate = 0;
if ((de->drm_fd = drmOpen(de->drm_module, NULL)) < 0)
{
rv = AVERROR(errno);
av_log(s, AV_LOG_ERROR, "Failed to drmOpen %s: %s\n", de->drm_module, av_err2str(rv));
return rv;
}
if (find_crtc(s, de->drm_fd, &de->setup, &de->con_id) != 0)
{
av_log(s, AV_LOG_ERROR, "failed to find valid mode\n");
rv = AVERROR(EINVAL);
goto fail_close;
}
sem_init(&de->q_sem_in, 0, 0);
sem_init(&de->q_sem_out, 0, 0);
if (pthread_create(&de->q_thread, NULL, display_thread, s)) {
rv = AVERROR(errno);
av_log(s, AV_LOG_ERROR, "Failed to create display thread: %s\n", av_err2str(rv));
goto fail_close;
}
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
return 0;
fail_close:
close(de->drm_fd);
de->drm_fd = -1;
av_log(s, AV_LOG_DEBUG, ">>> %s: FAIL\n", __func__);
return rv;
}
static void drm_vout_deinit(struct AVFormatContext * s)
{
drm_display_env_t * const de = s->priv_data;
av_log(s, AV_LOG_DEBUG, "<<< %s\n", __func__);
de->q_terminate = 1;
sem_post(&de->q_sem_in);
pthread_join(de->q_thread, NULL);
sem_destroy(&de->q_sem_in);
sem_destroy(&de->q_sem_out);
for (unsigned int i = 0; i != AUX_SIZE; ++i)
da_uninit(de, de->aux + i);
av_frame_free(&de->q_next);
if (de->drm_fd >= 0) {
close(de->drm_fd);
de->drm_fd = -1;
}
av_log(s, AV_LOG_DEBUG, ">>> %s\n", __func__);
}
#define OFFSET(x) offsetof(drm_display_env_t, x)
static const AVOption options[] = {
{ "show_all", "show all frames", OFFSET(show_all), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
{ "drm_module", "drm_module name to use, default=" DRM_MODULE, OFFSET(drm_module), AV_OPT_TYPE_STRING, { .str = DRM_MODULE }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
{ NULL }
};
static const AVClass drm_vout_class = {
.class_name = "drm vid outdev",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
.category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
};
FFOutputFormat ff_vout_drm_muxer = {
.p = {
.name = "vout_drm",
.long_name = NULL_IF_CONFIG_SMALL("Drm video output device"),
.audio_codec = AV_CODEC_ID_NONE,
.video_codec = AV_CODEC_ID_WRAPPED_AVFRAME,
.flags = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
.priv_class = &drm_vout_class,
},
.priv_data_size = sizeof(drm_display_env_t),
.write_header = drm_vout_write_header,
.write_packet = drm_vout_write_packet,
.write_uncoded_frame = drm_vout_write_frame,
.write_trailer = drm_vout_write_trailer,
.control_message = drm_vout_control_message,
.init = drm_vout_init,
.deinit = drm_vout_deinit,
};