diff --git a/.forgejo/CODEOWNERS b/.forgejo/CODEOWNERS index 45f501a4c3..16e2df19fa 100644 --- a/.forgejo/CODEOWNERS +++ b/.forgejo/CODEOWNERS @@ -167,6 +167,7 @@ libavutil/film_grain.* @haasn libavutil/dovi_meta.* @haasn libavutil/hwcontext_oh.* @quink libavutil/hwcontext_mediacodec.* @quink +libavutil/hwcontext_v4l2request.* @Kwiboo @jernej @knaerzche libavutil/hwcontext_videotoolbox.* @ePirat libavutil/iamf.* @jamrial libavutil/integer.* @michaelni diff --git a/configure b/configure index 86a20445cf..b6c116f4f5 100755 --- a/configure +++ b/configure @@ -367,6 +367,7 @@ External library support: --enable-omx-rpi enable OpenMAX IL code for Raspberry Pi [no] --enable-rkmpp enable Rockchip Media Process Platform code [no] --disable-v4l2-m2m disable V4L2 mem2mem code [autodetect] + --enable-v4l2-request enable V4L2 Request API code [no] --disable-vaapi disable Video Acceleration API (mainly Unix/Intel) code [autodetect] --disable-vdpau disable Nvidia Video Decode and Presentation API for Unix code [autodetect] --disable-videotoolbox disable VideoToolbox code [autodetect] @@ -2087,6 +2088,7 @@ HWACCEL_LIBRARY_LIST=" mmal omx opencl + v4l2_request " DOCUMENT_LIST=" @@ -3246,6 +3248,8 @@ dxva2_deps="dxva2api_h DXVA2_ConfigPictureDecode ole32 user32" ffnvcodec_deps_any="libdl LoadLibrary" mediacodec_deps="android mediandk pthreads" nvdec_deps="ffnvcodec" +v4l2_request_deps="linux_media_h v4l2_timeval_to_ns libdrm" +v4l2_request_suggest="libdrm" vaapi_x11_deps="xlib_x11" videotoolbox_hwaccel_deps="videotoolbox pthreads" videotoolbox_hwaccel_extralibs="-framework QuartzCore" @@ -7472,6 +7476,10 @@ if enabled v4l2_m2m; then check_cc vp9_v4l2_m2m linux/videodev2.h "int i = V4L2_PIX_FMT_VP9;" fi +if enabled v4l2_request; then + check_func_headers "linux/media.h linux/videodev2.h" v4l2_timeval_to_ns +fi + check_headers sys/videoio.h test_code cc sys/videoio.h "struct v4l2_frmsizeenum vfse; vfse.discrete.width = 0;" && enable_sanitized struct_v4l2_frmivalenum_discrete diff --git a/libavutil/Makefile b/libavutil/Makefile index ee77e51c08..109381ff13 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -52,6 +52,7 @@ HEADERS = adler32.h \ hwcontext_mediacodec.h \ hwcontext_opencl.h \ hwcontext_oh.h \ + hwcontext_v4l2request.h \ hwcontext_vaapi.h \ hwcontext_videotoolbox.h \ hwcontext_vdpau.h \ @@ -214,6 +215,7 @@ OBJS-$(CONFIG_MEDIACODEC) += hwcontext_mediacodec.o OBJS-$(CONFIG_OHCODEC) += hwcontext_oh.o OBJS-$(CONFIG_OPENCL) += hwcontext_opencl.o OBJS-$(CONFIG_QSV) += hwcontext_qsv.o +OBJS-$(CONFIG_V4L2_REQUEST) += hwcontext_v4l2request.o OBJS-$(CONFIG_VAAPI) += hwcontext_vaapi.o OBJS-$(CONFIG_VIDEOTOOLBOX) += hwcontext_videotoolbox.o OBJS-$(CONFIG_VDPAU) += hwcontext_vdpau.o @@ -244,6 +246,8 @@ SKIPHEADERS-$(CONFIG_AMF) += hwcontext_amf.h \ hwcontext_amf_internal.h SKIPHEADERS-$(CONFIG_QSV) += hwcontext_qsv.h SKIPHEADERS-$(CONFIG_OPENCL) += hwcontext_opencl.h +SKIPHEADERS-$(CONFIG_V4L2_REQUEST) += hwcontext_v4l2request.h \ + hwcontext_v4l2request_internal.h SKIPHEADERS-$(CONFIG_VAAPI) += hwcontext_vaapi.h SKIPHEADERS-$(CONFIG_VIDEOTOOLBOX) += hwcontext_videotoolbox.h SKIPHEADERS-$(CONFIG_VDPAU) += hwcontext_vdpau.h diff --git a/libavutil/hwcontext.c b/libavutil/hwcontext.c index 83bd7457e8..7f93d90c8e 100644 --- a/libavutil/hwcontext.c +++ b/libavutil/hwcontext.c @@ -71,6 +71,9 @@ static const HWContextType * const hw_table[] = { #endif #if CONFIG_OHCODEC &ff_hwcontext_type_oh, +#endif +#if CONFIG_V4L2_REQUEST + &ff_hwcontext_type_v4l2request, #endif NULL, }; @@ -83,6 +86,7 @@ static const char *const hw_type_names[] = { [AV_HWDEVICE_TYPE_D3D12VA] = "d3d12va", [AV_HWDEVICE_TYPE_OPENCL] = "opencl", [AV_HWDEVICE_TYPE_QSV] = "qsv", + [AV_HWDEVICE_TYPE_V4L2REQUEST] = "v4l2request", [AV_HWDEVICE_TYPE_VAAPI] = "vaapi", [AV_HWDEVICE_TYPE_VDPAU] = "vdpau", [AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox", diff --git a/libavutil/hwcontext.h b/libavutil/hwcontext.h index 29374cf0a7..88e47cc7f5 100644 --- a/libavutil/hwcontext.h +++ b/libavutil/hwcontext.h @@ -41,6 +41,7 @@ enum AVHWDeviceType { AV_HWDEVICE_TYPE_AMF, /* OpenHarmony Codec device */ AV_HWDEVICE_TYPE_OHCODEC, + AV_HWDEVICE_TYPE_V4L2REQUEST, }; /** diff --git a/libavutil/hwcontext_internal.h b/libavutil/hwcontext_internal.h index dcfdc2016a..94e4da16c0 100644 --- a/libavutil/hwcontext_internal.h +++ b/libavutil/hwcontext_internal.h @@ -158,6 +158,7 @@ extern const HWContextType ff_hwcontext_type_drm; extern const HWContextType ff_hwcontext_type_dxva2; extern const HWContextType ff_hwcontext_type_opencl; extern const HWContextType ff_hwcontext_type_qsv; +extern const HWContextType ff_hwcontext_type_v4l2request; extern const HWContextType ff_hwcontext_type_vaapi; extern const HWContextType ff_hwcontext_type_vdpau; extern const HWContextType ff_hwcontext_type_videotoolbox; diff --git a/libavutil/hwcontext_v4l2request.c b/libavutil/hwcontext_v4l2request.c new file mode 100644 index 0000000000..56d3fef8e2 --- /dev/null +++ b/libavutil/hwcontext_v4l2request.c @@ -0,0 +1,596 @@ +/* + * 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "avassert.h" +#include "hwcontext_drm.h" +#include "hwcontext_internal.h" +#include "hwcontext_v4l2request_internal.h" +#include "mem.h" + +typedef struct V4L2RequestDeviceContext { +} V4L2RequestDeviceContext; + +typedef struct V4L2RequestFramesContext { + AVV4L2RequestFramesContext p; + AVV4L2RequestFramesContextInternal internal; +} V4L2RequestFramesContext; + +typedef struct V4L2RequestFrameDescriptor { + AVDRMFrameDescriptor base; + AVBufferRef *ref; + uint32_t index; + int fd[AV_DRM_MAX_PLANES]; +} V4L2RequestFrameDescriptor; + +static const struct { + uint32_t pixelformat; + enum AVPixelFormat sw_format; + uint32_t drm_format; + uint64_t format_modifier; + uint32_t bit_depth; +} v4l2request_capture_pixelformats[] = { + { V4L2_PIX_FMT_NV12, AV_PIX_FMT_NV12, DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR, 8 }, +#if defined(V4L2_PIX_FMT_NV12_32L32) + { V4L2_PIX_FMT_NV12_32L32, AV_PIX_FMT_YUV420P, DRM_FORMAT_NV12, DRM_FORMAT_MOD_ALLWINNER_TILED, 8 }, +#endif +#if defined(V4L2_PIX_FMT_NV15) && defined(DRM_FORMAT_NV15) + { V4L2_PIX_FMT_NV15, AV_PIX_FMT_YUV420P10, DRM_FORMAT_NV15, DRM_FORMAT_MOD_LINEAR, 10 }, +#endif + { V4L2_PIX_FMT_NV16, AV_PIX_FMT_NV16, DRM_FORMAT_NV16, DRM_FORMAT_MOD_LINEAR, 8 }, +#if defined(V4L2_PIX_FMT_NV20) && defined(DRM_FORMAT_NV20) + { V4L2_PIX_FMT_NV20, AV_PIX_FMT_YUV422P10, DRM_FORMAT_NV20, DRM_FORMAT_MOD_LINEAR, 10 }, +#endif +#if defined(V4L2_PIX_FMT_P010) && defined(DRM_FORMAT_P010) + { V4L2_PIX_FMT_P010, AV_PIX_FMT_P010, DRM_FORMAT_P010, DRM_FORMAT_MOD_LINEAR, 10 }, +#endif +}; + +static int v4l2request_set_drm_descriptor(AVDRMFrameDescriptor *desc, + struct v4l2_format *format) +{ + AVDRMLayerDescriptor *layer = &desc->layers[0]; + uint32_t pixelformat = V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.pixelformat : + format->fmt.pix.pixelformat; + uint64_t format_modifier; + + layer->format = 0; + for (int i = 0; i < FF_ARRAY_ELEMS(v4l2request_capture_pixelformats); i++) { + if (pixelformat == v4l2request_capture_pixelformats[i].pixelformat) { + layer->format = v4l2request_capture_pixelformats[i].drm_format; + format_modifier = v4l2request_capture_pixelformats[i].format_modifier; + break; + } + } + if (!layer->format) + return AVERROR(ENOENT); + + for (int i = 0; i < desc->nb_objects; i++) { + desc->objects[i].format_modifier = format_modifier; + desc->objects[i].size = V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.plane_fmt[i].sizeimage : + format->fmt.pix.sizeimage; + } + + desc->nb_layers = 1; + layer->nb_planes = 2; + + layer->planes[0].object_index = 0; + layer->planes[0].offset = 0; + layer->planes[0].pitch = V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.plane_fmt[0].bytesperline : + format->fmt.pix.bytesperline; + + layer->planes[1].object_index = 0; + layer->planes[1].offset = layer->planes[0].pitch * + (V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.height : + format->fmt.pix.height); + layer->planes[1].pitch = layer->planes[0].pitch; + + return 0; +} + +static void v4l2request_device_uninit(AVHWDeviceContext *hwdev) +{ +} + +static int v4l2request_device_create(AVHWDeviceContext *hwdev, const char *device, + AVDictionary *opts, int flags) +{ + return 0; +} + +static int v4l2request_open_decoder(AVHWFramesContext *hwfc) +{ + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + + // Ensure codec pixelformat is set + if (!fctx->pixelformat) + return AVERROR(EINVAL); + + // TODO: locate a decoder supporting the requested pixelformat + + return AVERROR(ENOSYS); +} + +static AVBufferRef *v4l2request_v4l2_buffer_alloc(AVHWFramesContext *hwfc, + struct v4l2_format *format) +{ + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; + struct v4l2_create_buffers buffers = { + .count = 1, + .memory = V4L2_MEMORY_MMAP, + .format = *format, + }; + struct v4l2_buffer *buffer; + uint8_t num_planes; + AVBufferRef *ref; + + num_planes = V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.num_planes : 0; + + ref = av_buffer_allocz(sizeof(struct v4l2_buffer) + + (sizeof(struct v4l2_plane) * num_planes)); + if (!ref) + return NULL; + + buffer = (struct v4l2_buffer *)ref->data; + buffer->type = format->type; + + if (num_planes) { + buffer->length = num_planes; + buffer->m.planes = (struct v4l2_plane *)(buffer + 1); + } + + // Create the buffer + if (ioctl(fctxi->video_fd, VIDIOC_CREATE_BUFS, &buffers) < 0) { + av_log(hwfc, AV_LOG_ERROR, "Failed to create buffer of type %d: %s (%d)\n", + buffer->type, strerror(errno), errno); + goto fail; + } + + buffer->memory = buffers.memory; + buffer->index = buffers.index; + + // Query more details of the created buffer + if (ioctl(fctxi->video_fd, VIDIOC_QUERYBUF, buffer) < 0) { + av_log(hwfc, AV_LOG_ERROR, "Failed to query buffer %d of type %d: %s (%d)\n", + buffer->index, buffer->type, strerror(errno), errno); + goto fail; + } + + return ref; + +fail: + av_buffer_unref(&ref); + return NULL; +} + +static AVBufferRef *v4l2request_capture_buffer_alloc(void *opaque, size_t size) +{ + AVHWFramesContext *hwfc = opaque; + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; + + return v4l2request_v4l2_buffer_alloc(hwfc, &fctxi->capture.format); +} + +static AVBufferRef *v4l2request_output_buffer_alloc(void *opaque, size_t size) +{ + AVHWFramesContext *hwfc = opaque; + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; + + return v4l2request_v4l2_buffer_alloc(hwfc, &fctxi->output.format); +} + +static void v4l2request_frame_free(void *opaque, uint8_t *data) +{ + V4L2RequestFrameDescriptor *desc = (V4L2RequestFrameDescriptor *)data; + + // Close the exported CAPTURE buffer memory planes + for (int i = 0; i < FF_ARRAY_ELEMS(desc->fd); i++) { + if (desc->fd[i] >= 0) { + close(desc->fd[i]); + desc->fd[i] = -1; + } + } + + // Return the CAPTURE buffer to the frames context CAPTURE pool + av_buffer_unref(&desc->ref); +} + +static AVBufferRef *v4l2request_frame_alloc(void *opaque, size_t size) +{ + AVHWFramesContext *hwfc = opaque; + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; + struct v4l2_format *format = &fctxi->capture.format; + V4L2RequestFrameDescriptor *desc; + struct v4l2_buffer *buffer; + AVBufferRef *ref; + uint8_t *data; + + data = av_mallocz(size); + if (!data) + return NULL; + + ref = av_buffer_create(data, size, v4l2request_frame_free, + hwfc, AV_BUFFER_FLAG_READONLY); + if (!ref) { + av_free(data); + return NULL; + } + + // Set initial default values + desc = (V4L2RequestFrameDescriptor *)data; + for (int i = 0; i < FF_ARRAY_ELEMS(desc->fd); i++) + desc->fd[i] = -1; + + // Get a CAPTURE buffer from frames context CAPTURE pool + desc->ref = av_buffer_pool_get(fctxi->capture.pool); + if (!desc->ref) + goto fail; + + buffer = (struct v4l2_buffer *)desc->ref->data; + desc->index = buffer->index; + + // Export CAPTURE buffer memory planes + desc->base.nb_objects = V4L2_TYPE_IS_MULTIPLANAR(format->type) ? + format->fmt.pix_mp.num_planes : 1; + av_assert0(desc->base.nb_objects <= AV_DRM_MAX_PLANES); + for (int i = 0; i < desc->base.nb_objects; i++) { + struct v4l2_exportbuffer exportbuffer = { + .type = buffer->type, + .index = buffer->index, + .plane = i, + .flags = O_RDONLY, + }; + if (ioctl(fctxi->video_fd, VIDIOC_EXPBUF, &exportbuffer) < 0) { + av_log(hwfc, AV_LOG_ERROR, "Failed to export memory plane %d (%d): %s (%d)\n", + i, buffer->index, strerror(errno), errno); + goto fail; + } + desc->base.objects[i].fd = desc->fd[i] = exportbuffer.fd; + } + + // Set AVDRMFrameDescriptor based on CAPTURE buffer format + if (v4l2request_set_drm_descriptor(&desc->base, format) < 0) + goto fail; + + return ref; + +fail: + av_buffer_unref(&ref); + return NULL; +} + +static int v4l2request_frames_init(AVHWFramesContext *hwfc) +{ + V4L2RequestFramesContext *hwctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi; + uint32_t pixelformat; + int ret; + + // Set initial default values + fctxi = &hwctx->internal; + hwctx->p.internal = fctxi; + fctxi->media_fd = -1; + fctxi->video_fd = -1; + + // Locate and open a capable video decoder device + ret = v4l2request_open_decoder(hwfc); + if (ret < 0) + return ret; + + // Reset init controls after video device is opened + hwctx->p.init_controls = NULL; + hwctx->p.nb_init_controls = 0; + + // Update frames context with CAPTURE format details + if (V4L2_TYPE_IS_MULTIPLANAR(fctxi->capture.format.type)) { + hwfc->width = fctxi->capture.format.fmt.pix_mp.width; + hwfc->height = fctxi->capture.format.fmt.pix_mp.height; + pixelformat = fctxi->capture.format.fmt.pix_mp.pixelformat; + } else { + hwfc->width = fctxi->capture.format.fmt.pix.width; + hwfc->height = fctxi->capture.format.fmt.pix.height; + pixelformat = fctxi->capture.format.fmt.pix.pixelformat; + } + + hwfc->sw_format = AV_PIX_FMT_NONE; + for (int i = 0; i < FF_ARRAY_ELEMS(v4l2request_capture_pixelformats); i++) { + if (pixelformat == v4l2request_capture_pixelformats[i].pixelformat) { + hwctx->p.bit_depth = v4l2request_capture_pixelformats[i].bit_depth; + hwfc->sw_format = v4l2request_capture_pixelformats[i].sw_format; + break; + } + } + + // Initialize buffer pool for CAPTURE buffers + fctxi->capture.pool = av_buffer_pool_init2(sizeof(struct v4l2_buffer), hwfc, + v4l2request_capture_buffer_alloc, NULL); + if (!fctxi->capture.pool) + return AVERROR(ENOMEM); + + // Initialize buffer pool for OUTPUT buffers + fctxi->output.pool = av_buffer_pool_init2(sizeof(struct v4l2_buffer), hwfc, + v4l2request_output_buffer_alloc, NULL); + if (!fctxi->output.pool) + return AVERROR(ENOMEM); + + // Initialize buffer pool for frame descriptors + ffhwframesctx(hwfc)->pool_internal = + av_buffer_pool_init2(sizeof(V4L2RequestFrameDescriptor), hwfc, + v4l2request_frame_alloc, NULL); + if (!ffhwframesctx(hwfc)->pool_internal) + return AVERROR(ENOMEM); + + av_log(hwfc, AV_LOG_VERBOSE, "Using CAPTURE buffer format %s (%dx%d)\n", + av_fourcc2str(pixelformat), hwfc->width, hwfc->height); + + return 0; +} + +static void v4l2request_frames_uninit(AVHWFramesContext *hwfc) +{ + AVV4L2RequestFramesContext *fctx = hwfc->hwctx; + AVV4L2RequestFramesContextInternal *fctxi = fctx->internal; + + av_buffer_pool_uninit(&fctxi->capture.pool); + av_buffer_pool_uninit(&fctxi->output.pool); + + if (fctxi->video_fd >= 0) { + close(fctxi->video_fd); + fctxi->video_fd = -1; + } + + if (fctxi->media_fd) { + close(fctxi->media_fd); + fctxi->media_fd = -1; + } +} + +static int v4l2request_get_buffer(AVHWFramesContext *hwfc, AVFrame *frame) +{ + V4L2RequestFrameDescriptor *desc; + + frame->buf[0] = av_buffer_pool_get(hwfc->pool); + if (!frame->buf[0]) + return AVERROR(ENOMEM); + + desc = (V4L2RequestFrameDescriptor *)frame->buf[0]->data; + frame->data[0] = (uint8_t *)&desc->base; + frame->data[1] = (uint8_t *)(uintptr_t)desc->index; + + frame->format = AV_PIX_FMT_DRM_PRIME; + frame->width = hwfc->width; + frame->height = hwfc->height; + + return 0; +} + +typedef struct V4L2RequestMapping { + // Address and length of each mmap()ed region. + int nb_regions; + int object[AV_DRM_MAX_PLANES]; + void *address[AV_DRM_MAX_PLANES]; + size_t length[AV_DRM_MAX_PLANES]; +} V4L2RequestMapping; + +static void v4l2request_unmap_frame(AVHWFramesContext *hwfc, + HWMapDescriptor *hwmap) +{ + V4L2RequestMapping *map = hwmap->priv; + + for (int i = 0; i < map->nb_regions; i++) { + struct dma_buf_sync sync = { + .flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ, + }; + ioctl(map->object[i], DMA_BUF_IOCTL_SYNC, &sync); + munmap(map->address[i], map->length[i]); + } + + av_free(map); +} + +static int v4l2request_map_frame(AVHWFramesContext *hwfc, + AVFrame *dst, const AVFrame *src) +{ + const AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)src->data[0]; + struct dma_buf_sync sync = { + .flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ, + }; + V4L2RequestMapping *map; + int ret, i, p, plane; + void *addr; + + map = av_mallocz(sizeof(*map)); + if (!map) + return AVERROR(ENOMEM); + + av_assert0(desc->nb_objects <= AV_DRM_MAX_PLANES); + for (i = 0; i < desc->nb_objects; i++) { + addr = mmap(NULL, desc->objects[i].size, PROT_READ, MAP_SHARED, + desc->objects[i].fd, 0); + if (addr == MAP_FAILED) { + ret = AVERROR(errno); + av_log(hwfc, AV_LOG_ERROR, "Failed to map DRM object %d to memory: %s (%d)\n", + desc->objects[i].fd, strerror(errno), errno); + goto fail; + } + + map->address[i] = addr; + map->length[i] = desc->objects[i].size; + map->object[i] = desc->objects[i].fd; + + /* + * We're not checking for errors here because the kernel may not + * support the ioctl, in which case its okay to carry on + */ + ioctl(desc->objects[i].fd, DMA_BUF_IOCTL_SYNC, &sync); + } + map->nb_regions = i; + + plane = 0; + for (i = 0; i < desc->nb_layers; i++) { + const AVDRMLayerDescriptor *layer = &desc->layers[i]; + for (p = 0; p < layer->nb_planes; p++) { + dst->data[plane] = + (uint8_t *)map->address[layer->planes[p].object_index] + + layer->planes[p].offset; + dst->linesize[plane] = layer->planes[p].pitch; + ++plane; + } + } + av_assert0(plane <= AV_DRM_MAX_PLANES); + + dst->width = src->width; + dst->height = src->height; + + ret = ff_hwframe_map_create(src->hw_frames_ctx, dst, src, + v4l2request_unmap_frame, map); + if (ret < 0) + goto fail; + + return 0; + +fail: + for (i = 0; i < desc->nb_objects; i++) { + if (map->address[i]) + munmap(map->address[i], map->length[i]); + } + av_free(map); + return ret; +} + +static int v4l2request_transfer_get_formats(AVHWFramesContext *hwfc, + enum AVHWFrameTransferDirection dir, + enum AVPixelFormat **formats) +{ + enum AVPixelFormat *fmts; + + if (dir == AV_HWFRAME_TRANSFER_DIRECTION_TO) + return AVERROR(ENOSYS); + + fmts = av_malloc_array(2, sizeof(*fmts)); + if (!fmts) + return AVERROR(ENOMEM); + + fmts[0] = hwfc->sw_format; + fmts[1] = AV_PIX_FMT_NONE; + + if (hwfc->sw_format == AV_PIX_FMT_YUV420P || + hwfc->sw_format == AV_PIX_FMT_YUV420P10 || + hwfc->sw_format == AV_PIX_FMT_YUV422P10) + fmts[0] = AV_PIX_FMT_NONE; + + *formats = fmts; + return 0; +} + +static int v4l2request_transfer_data_from(AVHWFramesContext *hwfc, + AVFrame *dst, const AVFrame *src) +{ + AVFrame *map; + int ret; + + if (dst->width > hwfc->width || dst->height > hwfc->height) + return AVERROR(EINVAL); + + map = av_frame_alloc(); + if (!map) + return AVERROR(ENOMEM); + map->format = dst->format; + + ret = v4l2request_map_frame(hwfc, map, src); + if (ret) + goto fail; + + map->width = dst->width; + map->height = dst->height; + + ret = av_frame_copy(dst, map); + if (ret) + goto fail; + + ret = 0; +fail: + av_frame_free(&map); + return ret; +} + +static int v4l2request_map_from(AVHWFramesContext *hwfc, AVFrame *dst, + const AVFrame *src, int flags) +{ + int ret; + + if (!(flags & AV_HWFRAME_MAP_READ)) + return AVERROR(ENOSYS); + + if (hwfc->sw_format == AV_PIX_FMT_NONE || + hwfc->sw_format == AV_PIX_FMT_YUV420P || + hwfc->sw_format == AV_PIX_FMT_YUV420P10 || + hwfc->sw_format == AV_PIX_FMT_YUV422P10) + return AVERROR(ENOSYS); + else if (dst->format == AV_PIX_FMT_NONE) + dst->format = hwfc->sw_format; + else if (hwfc->sw_format != dst->format) + return AVERROR(ENOSYS); + + ret = v4l2request_map_frame(hwfc, dst, src); + if (ret) + return ret; + + return av_frame_copy_props(dst, src); +} + +const HWContextType ff_hwcontext_type_v4l2request = { + .type = AV_HWDEVICE_TYPE_V4L2REQUEST, + .name = "V4L2 Request API", + + .device_hwctx_size = sizeof(V4L2RequestDeviceContext), + .device_create = v4l2request_device_create, + .device_uninit = v4l2request_device_uninit, + + .frames_hwctx_size = sizeof(V4L2RequestFramesContext), + .frames_init = v4l2request_frames_init, + .frames_uninit = v4l2request_frames_uninit, + .frames_get_buffer = v4l2request_get_buffer, + .transfer_get_formats = v4l2request_transfer_get_formats, + .transfer_data_from = v4l2request_transfer_data_from, + .map_from = v4l2request_map_from, + + .pix_fmts = (const enum AVPixelFormat[]) { + AV_PIX_FMT_DRM_PRIME, + AV_PIX_FMT_NONE + }, +}; diff --git a/libavutil/hwcontext_v4l2request.h b/libavutil/hwcontext_v4l2request.h new file mode 100644 index 0000000000..ec6beadd64 --- /dev/null +++ b/libavutil/hwcontext_v4l2request.h @@ -0,0 +1,70 @@ +/* + * 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 + */ + +#ifndef AVUTIL_HWCONTEXT_V4L2REQUEST_H +#define AVUTIL_HWCONTEXT_V4L2REQUEST_H + +#include +#include + +/** + * @file + * An API-specific header for AV_HWDEVICE_TYPE_V4L2REQUEST. + */ + +typedef struct AVV4L2RequestFramesContextInternal AVV4L2RequestFramesContextInternal; + +/** + * V4L2 Request API frames context. + * + * This struct is allocated as AVHWFramesContext.hwctx + */ +typedef struct AVV4L2RequestFramesContext { + /** + * Internal context for the initialized V4L2 stateless decoder/encoder session. + */ + AVV4L2RequestFramesContextInternal *internal; + + /** + * V4L2_PIX_FMT_* coded pixel format to set on the OUTPUT queue (decoders) + * or the CAPTURE queue (encoders) during initialization. + * + * This field must be set by caller before av_hwframe_ctx_init() is called. + */ + uint32_t pixelformat; + + /** + * Optional bit depth of the frame pixel format, e.g. 8 or 10. + * + * This field should be set by caller before av_hwframe_ctx_init() is called, + * the field will be updated to match the selected frame pixel format after + * successful initialization. + */ + uint32_t bit_depth; + + /** + * Optional codec-specific extended controls to be set during initialization. + * + * These fields should be set by caller before av_hwframe_ctx_init() is called, + * fields are reset to NULL and 0 after successful initialization. + */ + struct v4l2_ext_control *init_controls; + int nb_init_controls; +} AVV4L2RequestFramesContext; + +#endif /* AVUTIL_HWCONTEXT_V4L2REQUEST_H */ diff --git a/libavutil/hwcontext_v4l2request_internal.h b/libavutil/hwcontext_v4l2request_internal.h new file mode 100644 index 0000000000..20436b2b2d --- /dev/null +++ b/libavutil/hwcontext_v4l2request_internal.h @@ -0,0 +1,67 @@ +/* + * 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 + */ + +#ifndef AVUTIL_HWCONTEXT_V4L2REQUEST_INTERNAL_H +#define AVUTIL_HWCONTEXT_V4L2REQUEST_INTERNAL_H + +#include "buffer.h" +#include "hwcontext_v4l2request.h" + +/** + * @file + * FFmpeg internal API-specific header for AV_HWDEVICE_TYPE_V4L2REQUEST. + */ + +/** + * Internal context for the initialized V4L2 stateless decoder/encoder session. + */ +struct AVV4L2RequestFramesContextInternal { + /** + * Media device file descriptor of the initialized session. + */ + int media_fd; + + /** + * Video device file descriptor of the initialized session. + */ + int video_fd; + + /** + * Details of the initialized CAPTURE and OUTPUT queues. + */ + struct { + /** + * V4L2 buffer format. + */ + struct v4l2_format format; + + /** + * V4L2 buffer capabilities flags. + */ + uint32_t capabilities; + + /** + * Buffer pool of allocated V4L2 buffers. + * + * AVBufferRef.data points to a struct v4l2_buffer for the created buffer. + */ + AVBufferPool *pool; + } capture, output; +}; + +#endif /* AVUTIL_HWCONTEXT_V4L2REQUEST_INTERNAL_H */