Default buffer settings should work with all drivers (mmaped memory from the driver) but would imply useless memcpy()'s if used in the standard way. libavcodec/v4l2.h is now installed to allow applications to get the proper buffers to avoid these copies.
This has been tested on MFC5, on a Samsung exynos 4412 SOC (odroid u3). --- Changelog | 1 + configure | 17 +- libavcodec/Makefile | 15 +- libavcodec/allcodecs.c | 7 + libavcodec/v4l2-buffers.c | 725 ++++++++++++++++++++++++++++++++++++++++++ libavcodec/v4l2-buffers.h | 230 ++++++++++++++ libavcodec/v4l2.h | 66 ++++ libavcodec/v4l2_m2m.c | 277 ++++++++++++++++ libavcodec/v4l2_m2m.h | 86 +++++ libavcodec/v4l2_m2m_avcodec.h | 30 ++ libavcodec/v4l2_m2m_dec.c | 227 +++++++++++++ libavcodec/v4l2_m2m_enc.c | 232 ++++++++++++++ 12 files changed, 1911 insertions(+), 2 deletions(-) create mode 100644 libavcodec/v4l2-buffers.c create mode 100644 libavcodec/v4l2-buffers.h create mode 100644 libavcodec/v4l2.h create mode 100644 libavcodec/v4l2_m2m.c create mode 100644 libavcodec/v4l2_m2m.h create mode 100644 libavcodec/v4l2_m2m_avcodec.h create mode 100644 libavcodec/v4l2_m2m_dec.c create mode 100644 libavcodec/v4l2_m2m_enc.c diff --git a/Changelog b/Changelog index 5f38aea..5910ef6 100644 --- a/Changelog +++ b/Changelog @@ -15,6 +15,7 @@ version <next>: - ffserver supports codec private options - creating DASH compatible fragmented MP4, MPEG-DASH segmenting muxer - WebP muxer with animated WebP support +- V4L2 mem2mem HW accelerated codecs support version 2.4: diff --git a/configure b/configure index 81a6acf..68a64e2 100755 --- a/configure +++ b/configure @@ -150,6 +150,7 @@ Component options: Hardware accelerators: --disable-dxva2 disable DXVA2 code [autodetect] + --disable-v4l2_m2m disable V4L2 mem2mem code [autodetect] --disable-vaapi disable VAAPI code [autodetect] --disable-vda disable VDA code [autodetect] --disable-vdpau disable VDPAU code [autodetect] @@ -1423,6 +1424,7 @@ FEATURE_LIST=" HWACCEL_LIST=" dxva2 + v4l2_m2m vaapi vda vdpau @@ -2053,6 +2055,7 @@ mpegaudiodsp_select="dct" mpegvideo_select="blockdsp h264chroma hpeldsp idctdsp me_cmp videodsp" mpegvideoenc_select="me_cmp mpegvideo pixblockdsp qpeldsp" v4l2_deps_any="linux_videodev2_h sys_videoio_h" +v4l2_m2m_select="v4l2" # decoders / encoders aac_decoder_select="mdct sinewin" @@ -2121,10 +2124,14 @@ h261_decoder_select="mpeg_er mpegvideo" h261_encoder_select="aandcttables mpegvideoenc" h263_decoder_select="error_resilience h263_parser h263dsp mpeg_er mpegvideo qpeldsp" h263_encoder_select="aandcttables h263dsp mpegvideoenc" +h263_v4l2m2m_decoder_deps="v4l2_m2m" +h263_v4l2m2m_encoder_deps="v4l2_m2m" h263i_decoder_select="h263_decoder" h263p_encoder_select="h263_encoder" h264_decoder_select="cabac golomb h264chroma h264dsp h264pred h264qpel startcode videodsp" h264_decoder_suggest="error_resilience" +h264_v4l2m2m_decoder_deps="v4l2_m2m" +h264_v4l2m2m_encoder_deps="v4l2_m2m" hevc_decoder_select="bswapdsp cabac golomb videodsp" huffyuv_decoder_select="bswapdsp huffyuvdsp llviddsp" huffyuv_encoder_select="bswapdsp huffman huffyuvencdsp llviddsp" @@ -2160,13 +2167,17 @@ mpc7_decoder_select="bswapdsp mpegaudiodsp" mpc8_decoder_select="mpegaudiodsp" mpeg_xvmc_decoder_deps="X11_extensions_XvMClib_h" mpeg_xvmc_decoder_select="mpeg2video_decoder" +mpeg1_v4l2m2m_decoder_deps="v4l2_m2m" mpegvideo_decoder_select="error_resilience mpeg_er mpegvideo" mpeg1video_decoder_select="error_resilience mpeg_er mpegvideo" mpeg1video_encoder_select="aandcttables mpegvideoenc h263dsp" +mpeg2_v4l2m2m_decoder_deps="v4l2_m2m" mpeg2video_decoder_select="error_resilience mpeg_er mpegvideo" mpeg2video_encoder_select="aandcttables mpegvideoenc h263dsp" mpeg4_decoder_select="h263_decoder mpeg4video_parser" mpeg4_encoder_select="h263_encoder" +mpeg4_v4l2m2m_decoder_deps="v4l2_m2m" +mpeg4_v4l2m2m_encoder_deps="v4l2_m2m" msmpeg4v1_decoder_select="h263_decoder" msmpeg4v2_decoder_select="h263_decoder" msmpeg4v2_encoder_select="h263_encoder" @@ -2222,6 +2233,7 @@ utvideo_decoder_select="bswapdsp" utvideo_encoder_select="bswapdsp huffman huffyuvencdsp" vble_decoder_select="huffyuvdsp" vc1_decoder_select="blockdsp error_resilience h263_decoder h264chroma h264qpel intrax8 mpeg_er qpeldsp startcode" +vc1_v4l2m2m_decoder_deps="v4l2_m2m" vc1image_decoder_select="vc1_decoder" vorbis_decoder_select="mdct" vorbis_encoder_select="mdct" @@ -2232,6 +2244,8 @@ vp6a_decoder_select="vp6_decoder" vp6f_decoder_select="vp6_decoder" vp7_decoder_select="h264pred videodsp" vp8_decoder_select="h264pred videodsp" +vp8_v4l2m2m_decoder_deps="v4l2_m2m" +vp8_v4l2m2m_encoder_deps="v4l2_m2m" vp9_decoder_select="videodsp vp9_parser" webp_decoder_select="vp8_decoder" wmalossless_decoder_select="llauddsp" @@ -2727,7 +2741,7 @@ sws_max_filter_size_default=256 set_default sws_max_filter_size # Enable hwaccels by default. -enable dxva2 vaapi vda vdpau xvmc +enable dxva2 v4l2_m2m vaapi vda vdpau xvmc enable xlib # build settings @@ -4983,6 +4997,7 @@ check_header linux/fb.h check_header linux/videodev.h check_header linux/videodev2.h check_code cc linux/videodev2.h "struct v4l2_frmsizeenum vfse; vfse.discrete.width = 0;" && enable_safe struct_v4l2_frmivalenum_discrete +check_code cc linux/videodev2.h "int i = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_VIDEO_M2M;" || disable v4l2_m2m check_header sys/videoio.h check_code cc sys/videoio.h "struct v4l2_frmsizeenum vfse; vfse.discrete.width = 0;" && enable_safe struct_v4l2_frmivalenum_discrete diff --git a/libavcodec/Makefile b/libavcodec/Makefile index a134fa6..76c76c1 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -7,6 +7,7 @@ HEADERS = avcodec.h \ dv_profile.h \ dxva2.h \ old_codec_ids.h \ + v4l2.h \ vaapi.h \ vda.h \ vdpau.h \ @@ -95,7 +96,8 @@ OBJS-$(CONFIG_SHARED) += log2_tab.o OBJS-$(CONFIG_SINEWIN) += sinewin.o OBJS-$(CONFIG_STARTCODE) += startcode.o OBJS-$(CONFIG_TPELDSP) += tpeldsp.o -OBJS-$(CONFIG_V4L2) += v4l2-common.o +OBJS-$(CONFIG_V4L2) += v4l2-common.o v4l2-buffers.o +OBJS-$(CONFIG_V4L2_M2M) += v4l2_m2m.o OBJS-$(CONFIG_VIDEODSP) += videodsp.o OBJS-$(CONFIG_VP3DSP) += vp3dsp.o OBJS-$(CONFIG_WMA_FREQS) += wma_freqs.o @@ -255,10 +257,14 @@ OBJS-$(CONFIG_H263_DECODER) += h263dec.o h263.o ituh263dec.o \ intelh263dec.o OBJS-$(CONFIG_H263_ENCODER) += mpeg4videoenc.o mpeg4video.o \ h263.o ituh263enc.o flvenc.o +OBJS-$(CONFIG_H263_V4L2M2M_DECODER) += v4l2_m2m_dec.o +OBJS-$(CONFIG_H263_V4L2M2M_ENCODER) += v4l2_m2m_enc.o OBJS-$(CONFIG_H264_DECODER) += h264.o h264_cabac.o h264_cavlc.o \ h264_direct.o h264_loopfilter.o \ h264_mb.o h264_picture.o h264_ps.o \ h264_refs.o h264_sei.o h264_slice.o +OBJS-$(CONFIG_H264_V4L2M2M_DECODER) += v4l2_m2m_dec.o +OBJS-$(CONFIG_H264_V4L2M2M_ENCODER) += v4l2_m2m_enc.o OBJS-$(CONFIG_H264_VDA_DECODER) += vda_h264_dec.o OBJS-$(CONFIG_HEVC_DECODER) += hevc.o hevc_mvs.o hevc_ps.o hevc_sei.o \ hevc_cabac.o hevc_refs.o hevcpred.o \ @@ -322,11 +328,15 @@ OBJS-$(CONFIG_MP3ON4FLOAT_DECODER) += mpegaudiodec_float.o mpeg4audio.o OBJS-$(CONFIG_MPC7_DECODER) += mpc7.o mpc.o OBJS-$(CONFIG_MPC8_DECODER) += mpc8.o mpc.o OBJS-$(CONFIG_MPEGVIDEO_DECODER) += mpeg12dec.o mpeg12.o mpeg12data.o +OBJS-$(CONFIG_MPEG1_V4L2M2M_DECODER) += v4l2_m2m_dec.o OBJS-$(CONFIG_MPEG1VIDEO_DECODER) += mpeg12dec.o mpeg12.o mpeg12data.o OBJS-$(CONFIG_MPEG1VIDEO_ENCODER) += mpeg12enc.o mpeg12.o +OBJS-$(CONFIG_MPEG2_V4L2M2M_DECODER) += v4l2_m2m_dec.o OBJS-$(CONFIG_MPEG2VIDEO_DECODER) += mpeg12dec.o mpeg12.o mpeg12data.o OBJS-$(CONFIG_MPEG2VIDEO_ENCODER) += mpeg12enc.o mpeg12.o OBJS-$(CONFIG_MPEG4_DECODER) += xvididct.o +OBJS-$(CONFIG_MPEG4_V4L2M2M_DECODER) += v4l2_m2m_dec.o +OBJS-$(CONFIG_MPEG4_V4L2M2M_ENCODER) += v4l2_m2m_enc.o OBJS-$(CONFIG_MPL2_DECODER) += mpl2dec.o ass.o OBJS-$(CONFIG_MSMPEG4V1_DECODER) += msmpeg4dec.o msmpeg4.o msmpeg4data.o OBJS-$(CONFIG_MSMPEG4V2_DECODER) += msmpeg4dec.o msmpeg4.o msmpeg4data.o @@ -482,6 +492,7 @@ OBJS-$(CONFIG_VC1_DECODER) += vc1dec.o vc1_block.o vc1_loopfilter.o vc1dsp.o \ msmpeg4dec.o msmpeg4.o msmpeg4data.o \ wmv2dsp.o +OBJS-$(CONFIG_VC1_V4L2M2M_DECODER) += v4l2_m2m_dec.o OBJS-$(CONFIG_VCR1_DECODER) += vcr1.o OBJS-$(CONFIG_VMDAUDIO_DECODER) += vmdaudio.o OBJS-$(CONFIG_VMDVIDEO_DECODER) += vmdvideo.o @@ -497,6 +508,8 @@ OBJS-$(CONFIG_VP6_DECODER) += vp6.o vp56.o vp56data.o vp56dsp.o \ vp6dsp.o vp56rac.o OBJS-$(CONFIG_VP7_DECODER) += vp8.o vp8dsp.o vp56rac.o OBJS-$(CONFIG_VP8_DECODER) += vp8.o vp8dsp.o vp56rac.o +OBJS-$(CONFIG_VP8_V4L2M2M_DECODER) += v4l2_m2m_dec.o +OBJS-$(CONFIG_VP8_V4L2M2M_ENCODER) += v4l2_m2m_enc.o OBJS-$(CONFIG_VP9_DECODER) += vp9.o vp9dsp.o vp56rac.o OBJS-$(CONFIG_VPLAYER_DECODER) += textdec.o ass.o OBJS-$(CONFIG_VQA_DECODER) += vqavideo.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index d08abd8..a8ab72e 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -164,10 +164,12 @@ void avcodec_register_all(void) REGISTER_ENCDEC (GIF, gif); REGISTER_ENCDEC (H261, h261); REGISTER_ENCDEC (H263, h263); + REGISTER_ENCDEC (H263_V4L2M2M, h263_v4l2m2m); REGISTER_DECODER(H263I, h263i); REGISTER_ENCDEC (H263P, h263p); REGISTER_DECODER(H264, h264); REGISTER_DECODER(H264_CRYSTALHD, h264_crystalhd); + REGISTER_ENCDEC (H264_V4L2M2M, h264_v4l2m2m); REGISTER_DECODER(H264_VDA, h264_vda); REGISTER_DECODER(H264_VDPAU, h264_vdpau); REGISTER_DECODER(HEVC, hevc); @@ -202,11 +204,14 @@ void avcodec_register_all(void) REGISTER_ENCDEC (MPEG2VIDEO, mpeg2video); REGISTER_ENCDEC (MPEG4, mpeg4); REGISTER_DECODER(MPEG4_CRYSTALHD, mpeg4_crystalhd); + REGISTER_ENCDEC (MPEG4_V4L2M2M, mpeg4_v4l2m2m); REGISTER_DECODER(MPEG4_VDPAU, mpeg4_vdpau); REGISTER_DECODER(MPEGVIDEO, mpegvideo); REGISTER_DECODER(MPEG_VDPAU, mpeg_vdpau); REGISTER_DECODER(MPEG1_VDPAU, mpeg1_vdpau); + REGISTER_DECODER(MPEG1_V4L2M2M, mpeg1_v4l2m2m); REGISTER_DECODER(MPEG2_CRYSTALHD, mpeg2_crystalhd); + REGISTER_DECODER(MPEG2_V4L2M2M, mpeg2_v4l2m2m); REGISTER_DECODER(MSA1, msa1); REGISTER_DECODER(MSMPEG4_CRYSTALHD, msmpeg4_crystalhd); REGISTER_DECODER(MSMPEG4V1, msmpeg4v1); @@ -284,6 +289,7 @@ void avcodec_register_all(void) REGISTER_DECODER(VBLE, vble); REGISTER_DECODER(VC1, vc1); REGISTER_DECODER(VC1_CRYSTALHD, vc1_crystalhd); + REGISTER_DECODER(VC1_V4L2M2M, vc1_v4l2m2m); REGISTER_DECODER(VC1_VDPAU, vc1_vdpau); REGISTER_DECODER(VC1IMAGE, vc1image); REGISTER_DECODER(VCR1, vcr1); @@ -296,6 +302,7 @@ void avcodec_register_all(void) REGISTER_DECODER(VP6F, vp6f); REGISTER_DECODER(VP7, vp7); REGISTER_DECODER(VP8, vp8); + REGISTER_ENCDEC (VP8_V4L2M2M, vp8_v4l2m2m); REGISTER_DECODER(VP9, vp9); REGISTER_DECODER(VQA, vqa); REGISTER_DECODER(WEBP, webp); diff --git a/libavcodec/v4l2-buffers.c b/libavcodec/v4l2-buffers.c new file mode 100644 index 0000000..7bcb1f9 --- /dev/null +++ b/libavcodec/v4l2-buffers.c @@ -0,0 +1,725 @@ +/* + * V4L2 buffer{,pool} helper functions. + * Copyright (C) 2014 Alexis Ballier + * + * 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 <sys/ioctl.h> +#include <sys/mman.h> +#include <poll.h> +#include <fcntl.h> +#include <unistd.h> + +#include "avcodec.h" +#include "internal.h" +#include "v4l2.h" +#include "v4l2-buffers.h" +#include "v4l2-common.h" + +#define IS_BP_SUPPORTED(bp) ((bp->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) || (bp->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) || (bp->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || (bp->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)) + +// Define it if you want more AV_LOG_DEBUG output +// #define V4L_BUFFER_DEBUG + +/* + * Missing features: + * - USERPTR support is a bit rough + * - DMABUF ? + */ + +enum V4LBuffer_status { + V4LBUF_AVAILABLE, + V4LBUF_IN_DRIVER, + V4LBUF_RET_USER, +}; + +struct V4LBuffer { + struct V4LBufferPool *pool; + int index; + int num_planes; + int linesize[4]; + size_t lengths[VIDEO_MAX_PLANES]; + void* mmap_addr[VIDEO_MAX_PLANES]; + AVBufferRef *bufrefs[VIDEO_MAX_PLANES]; + enum V4LBuffer_status status; + int flags; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + struct v4l2_buffer buf; + struct timeval timestamp; + int ref_cnt; + V4LBuffer *piped; +}; + +static void buffer_callback(void *opaque, uint8_t *unused); + +static int enqueue_v4lbuf(V4LBuffer* avbuf) { + int ret; + memset(&avbuf->buf, 0, sizeof(avbuf->buf)); + + avbuf->buf.type = avbuf->pool->type; + avbuf->buf.memory = avbuf->pool->memory; + avbuf->buf.index = avbuf->index; + + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + avbuf->buf.m.planes = avbuf->planes; + avbuf->buf.length = avbuf->num_planes; + } else { + avbuf->buf.length = avbuf->planes[avbuf->index].length; + avbuf->buf.bytesused = avbuf->planes[avbuf->index].bytesused; + avbuf->buf.m.userptr = avbuf->planes[avbuf->index].m.userptr; + } + + avbuf->buf.timestamp = avbuf->timestamp; + avbuf->buf.flags = avbuf->pool->default_flags | avbuf->flags; + + if((ret = avbuf->pool->ioctl_f(avbuf->pool->fd, VIDIOC_QBUF, &avbuf->buf)) < 0) + return AVERROR(errno); + avbuf->status = V4LBUF_IN_DRIVER; + avbuf->pool->num_queued++; + +#ifdef V4L_BUFFER_DEBUG + av_log(avbuf->pool->log_ctx, AV_LOG_DEBUG, "Successfuly enqueued a buffer on %s\n", avbuf->pool->name); +#endif + + return 0; +} + +static V4LBuffer* dequeue_v4lbuf(V4LBufferPool *bp) { + int ret; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + struct v4l2_buffer buf = { 0 }; + V4LBuffer* avbuf = NULL; + int i; + + if(bp->num_queued <= bp->min_queued_buffers) { + if(!V4L2_TYPE_IS_OUTPUT(bp->type)) + av_log(bp->log_ctx, AV_LOG_WARNING, "Trying to dequeue on %s with %i buffers queued and requiring %i buffers\n", bp->name, bp->num_queued, bp->min_queued_buffers); + return NULL; + } + + memset(&buf, 0, sizeof(buf)); + buf.type = bp->type; + buf.memory = bp->memory; + + if(V4L2_TYPE_IS_MULTIPLANAR(bp->type)) { + memset(planes, 0, sizeof(planes)); + buf.m.planes = planes; + buf.length = VIDEO_MAX_PLANES; + } + + if(bp->blocking_dequeue) { + struct pollfd pfd = { .fd = bp->fd, .events = POLLIN | POLLERR }; + av_log(bp->log_ctx, AV_LOG_DEBUG, "Waiting for event for %i msec before dequeuing on %s\n", bp->blocking_dequeue, bp->name); + if((ret = poll(&pfd, 1, bp->blocking_dequeue)) <= 0) { + av_log(bp->log_ctx, AV_LOG_WARNING, "No event occurred while waiting\n"); + return NULL; + } + } + + ret = bp->ioctl_f(bp->fd, VIDIOC_DQBUF, &buf); + if(ret) + return NULL; + + avbuf = &(bp->buffers[buf.index]); + avbuf->status = V4LBUF_AVAILABLE; + avbuf->buf = buf; + + if(V4L2_TYPE_IS_MULTIPLANAR(bp->type)) { + memcpy(avbuf->planes, planes, sizeof(planes)); + avbuf->buf.m.planes = avbuf->planes; + } + avbuf->pool->num_queued--; + + /* + * unref buffers if any for output pools. + * capture pools will send it back to user and thus we need to keep the ref for now. + */ + if(V4L2_TYPE_IS_OUTPUT(avbuf->pool->type)) { + for(i=0; i<avbuf->num_planes; i++) { + if(avbuf->bufrefs[i]) { + av_buffer_unref(&avbuf->bufrefs[i]); + } + } + } + +#ifdef V4L_BUFFER_DEBUG + av_log(bp->log_ctx, AV_LOG_DEBUG, "Successfuly dequeued a buffer on %s\n", bp->name); +#endif + + return avbuf; +} + +static inline int init_buffer(V4LBuffer* avbuf) { + int ret, i; + + avbuf->buf.type = avbuf->pool->type; + avbuf->buf.memory = avbuf->pool->memory; + avbuf->buf.index = avbuf->index; + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + avbuf->buf.length = VIDEO_MAX_PLANES; + avbuf->buf.m.planes = avbuf->planes; + } + + if((ret= avbuf->pool->ioctl_f(avbuf->pool->fd, VIDIOC_QUERYBUF, &avbuf->buf)) < 0) + return AVERROR(errno); + + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + for(avbuf->num_planes=0; avbuf->num_planes < avbuf->buf.length && avbuf->buf.m.planes[avbuf->num_planes].length; avbuf->num_planes++); + } else avbuf->num_planes = 1; + + for(i=0; i < avbuf->num_planes; i++) { + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + avbuf->linesize[i] = avbuf->pool->format.fmt.pix_mp.plane_fmt[i].bytesperline; + } else + avbuf->linesize[i] = avbuf->pool->format.fmt.pix.bytesperline; + + switch(avbuf->pool->memory) { + case V4L2_MEMORY_MMAP: + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + avbuf->mmap_addr[i] = avbuf->pool->mmap_f(NULL, avbuf->buf.m.planes[i].length, + PROT_READ | PROT_WRITE, MAP_SHARED, + avbuf->pool->fd, avbuf->buf.m.planes[i].m.mem_offset); + avbuf->lengths[i] = avbuf->buf.m.planes[i].length; + } else { + avbuf->mmap_addr[i] = avbuf->pool->mmap_f(NULL, avbuf->buf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + avbuf->pool->fd, avbuf->buf.m.offset); + avbuf->lengths[i] = avbuf->buf.length; + } + + if(avbuf->mmap_addr[i] == MAP_FAILED) + return AVERROR(ENOMEM); + + break; + case V4L2_MEMORY_USERPTR: + /* Nothing to do */ + break; + default: + av_log(avbuf->pool->log_ctx, AV_LOG_ERROR, "%s: Sorry, memory type %i is not yet supported\n", __func__, avbuf->pool->memory); + return AVERROR_PATCHWELCOME; + } + } + avbuf->status = V4LBUF_AVAILABLE; + + av_log(avbuf->pool->log_ctx, AV_LOG_DEBUG, "Successfully created %i plane(s) for buffer %i in %s\n", avbuf->num_planes, avbuf->buf.index, avbuf->pool->name); + + /* feed capture buffers; can't enqueue userptr buffers now */ + if(!V4L2_TYPE_IS_OUTPUT(avbuf->pool->type) && (avbuf->pool->memory != V4L2_MEMORY_USERPTR)) + return enqueue_v4lbuf(avbuf); + + return 0; +} + +int avpriv_init_v4lbufpool(V4LBufferPool* bufs) { + struct v4l2_requestbuffers req; + int ret, i; + + if(!IS_BP_SUPPORTED(bufs)) { + av_log(bufs->log_ctx, AV_LOG_ERROR, "%s: Sorry, type %i is not supported\n", __func__, bufs->type); + return AVERROR_PATCHWELCOME; + } + +#define SET_WRAPPER(F) if(!bufs->F ## _f) bufs->F ## _f = F + SET_WRAPPER(open); + SET_WRAPPER(close); + SET_WRAPPER(dup); + SET_WRAPPER(ioctl); + SET_WRAPPER(read); + SET_WRAPPER(mmap); + SET_WRAPPER(munmap); +#undef SET_WRAPPER + + memset(&req, 0, sizeof(req)); + req.count = bufs->num_buffers + bufs->min_queued_buffers; + req.type = bufs->type; + req.memory = bufs->memory; + + if((ret = bufs->ioctl_f(bufs->fd, VIDIOC_REQBUFS, &req)) < 0) + return AVERROR(errno); + + if(bufs->num_buffers > req.count) + av_log(bufs->log_ctx, AV_LOG_WARNING, "Requested %i buffers but driver gave us only %i for %s\n", bufs->num_buffers, req.count, bufs->name); + + bufs->num_buffers = req.count; + bufs->num_queued = 0; + bufs->buffers = av_mallocz(bufs->num_buffers * sizeof(V4LBuffer)); + + for(i=0; i < req.count; i++) { + V4LBuffer *avbuf = &bufs->buffers[i]; + + avbuf->pool = bufs; + avbuf->index = i; + if((ret = init_buffer(avbuf)) < 0) { + av_log(bufs->log_ctx, AV_LOG_ERROR, "Buffer initialization for %s failed (%s)\n", bufs->name, av_err2str(ret)); + return ret; + } + } + + return 0; +} + +static void release_buf(V4LBuffer* b) { + int i; + for(i=0; i < b->num_planes; i++) + if(b->mmap_addr[i] && b->lengths[i]) + b->pool->munmap_f(b->mmap_addr[i], b->lengths[i]); +} + +void avpriv_release_buffer_pool(V4LBufferPool* bp) { + if(bp->buffers) { + int i; + for(i=0; i < bp->num_buffers; i++) + release_buf(&bp->buffers[i]); + av_free(bp->buffers); + } +} + +static int avbuf_to_avbuf(V4LBuffer* ibuf, V4LBuffer* obuf) { + int i; + if( ibuf->pool->memory != V4L2_MEMORY_MMAP || + obuf->pool->memory != V4L2_MEMORY_USERPTR || + ibuf->num_planes != obuf->num_planes ) { + av_log(ibuf->pool->log_ctx, AV_LOG_ERROR, "Buffers do not match:\n"); + av_log(ibuf->pool->log_ctx, AV_LOG_ERROR, " Memory: %i vs %i.\n", ibuf->pool->memory, obuf->pool->memory); + av_log(ibuf->pool->log_ctx, AV_LOG_ERROR, " Planes: %i vs %i.\n", ibuf->num_planes, obuf->num_planes); + return AVERROR(EINVAL); + } + + memcpy(obuf->planes , ibuf->planes, sizeof(obuf->planes )); + memcpy(obuf->lengths, ibuf->lengths, sizeof(obuf->lengths)); + + for(i=0; i<ibuf->num_planes; i++) { + if(obuf->bufrefs[i]) + av_buffer_unref(&obuf->bufrefs[i]); + if(ibuf->bufrefs[i]) + av_buffer_unref(&ibuf->bufrefs[i]); + + /* + * We keep two refs here: + * - One in obuf, which belongs to a capture pool. The ref will be "returned" to user when dequeueing it. + * It will be released when the user frees the frame/packet. + * - One in ibuf, which belongs to an output pool. The ref will be released when it'll be dequeued from the output pool. + * i.e. when the buffer is not used by the driver anymore. + */ + ibuf->bufrefs[i] = av_buffer_create(ibuf->mmap_addr[i], ibuf->lengths[i], buffer_callback, ibuf, 0); + if(!ibuf->bufrefs[i]) { + return AVERROR(ENOMEM); + } + + obuf->bufrefs[i] = av_buffer_ref(ibuf->bufrefs[i]); + if(!obuf->bufrefs[i]) { + return AVERROR(ENOMEM); + } + + ibuf->ref_cnt++; + obuf->planes[i].m.userptr = (unsigned long)ibuf->mmap_addr[i]; + } + ibuf->status = V4LBUF_RET_USER; + return 0; +} + +static inline void set_pts(V4LBuffer *out, int64_t pts) { + out->timestamp.tv_sec = pts / INT64_C(1000000); + out->timestamp.tv_usec = pts % INT64_C(1000000); +} + +static inline uint64_t get_pts(V4LBuffer *avbuf) { + if(avbuf->buf.timestamp.tv_sec || avbuf->buf.timestamp.tv_usec) + return avbuf->buf.timestamp.tv_sec * INT64_C(1000000) + avbuf->buf.timestamp.tv_usec; + return AV_NOPTS_VALUE; +} + +static int buf2v4l(V4LBuffer *out, int plane, const uint8_t* data, int size, AVBufferRef* bref) { + if(plane >= out->num_planes) { + av_log(out->pool->log_ctx, AV_LOG_ERROR, "Trying to feed the %ith plane of a V4L buffer with only %i planes\n", plane+1, out->num_planes); + return AVERROR(EINVAL); + } + + switch(out->pool->memory) { + case V4L2_MEMORY_MMAP: + if(out->mmap_addr[plane] != data) { + if(!out->pool->copy_warn) { + av_log(out->pool->log_ctx, AV_LOG_WARNING, "Performing useless memcpy() on %s because buffers do not match\n", out->pool->name); + av_log(out->pool->log_ctx, AV_LOG_WARNING, "This could be avoided by using av_v4l_buffer_pool_get_buffer*() or av_v4l_buffer_pool_make_pipe()\n"); + out->pool->copy_warn = 1; + } + memcpy(out->mmap_addr[plane], data, FFMIN(size, out->lengths[plane])); + } + break; + case V4L2_MEMORY_USERPTR: + if(!bref) { + av_log(out->pool->log_ctx, AV_LOG_ERROR, "%s needs to be fed with an AVBufferRef for USERPTR memory type\n", __func__); + return AVERROR_PATCHWELCOME; + } + + if(out->bufrefs[plane]) { + av_log(out->pool->log_ctx, AV_LOG_WARNING, "%s: V4L buffer already had a buffer referenced\n", __func__); + av_buffer_unref(&out->bufrefs[plane]); + } + + out->bufrefs[plane] = av_buffer_ref(bref); + + if(!out->bufrefs[plane]) + return AVERROR(ENOMEM); + + out->planes[plane].m.userptr = (unsigned long)out->bufrefs[plane]->data; + out->lengths[plane] = out->bufrefs[plane]->size; + break; + default: + av_log(out->pool->log_ctx, AV_LOG_ERROR, "%s: Sorry, memory type %i not supported", __func__, out->pool->memory); + return AVERROR_PATCHWELCOME; + } + + out->planes[plane].bytesused = FFMIN(size, out->lengths[plane]); + out->planes[plane].length = out->lengths[plane]; + + return 0; +} + +static int avframe_to_v4lbuf(const AVFrame *pict, V4LBuffer* out) { + int i, ret; + for(i=0; i<out->num_planes; i++) { + if(ret = buf2v4l(out, i, pict->buf[i]->data, pict->buf[i]->size, pict->buf[i])) + return ret; + } + set_pts(out, pict->pts); + return 0; +} + +static int avpkt_to_v4lbuf(const AVPacket *pkt, V4LBuffer *out) { + int ret; + if(ret = buf2v4l(out, 0, pkt->data, pkt->size, pkt->buf)) + return ret; + + if(pkt->pts != AV_NOPTS_VALUE) + set_pts(out, pkt->pts); + + if(pkt->flags & AV_PKT_FLAG_KEY) + out->flags = V4L2_BUF_FLAG_KEYFRAME; + + return 0; +} + +static inline int v4l2bufref(V4LBuffer *in, int plane, AVBufferRef **buf) { + +#ifdef V4L_BUFFER_DEBUG + av_log(in->pool->log_ctx, AV_LOG_DEBUG, "Making an avbuffer from V4L buffer %i[%i] on %s (%i,%i)\n", in->index, plane, in->pool->name, in->pool->type, in->pool->memory); +#endif + + if(plane >= in->num_planes) { + av_log(in->pool->log_ctx, AV_LOG_ERROR, "Trying to use the %ith plane of a V4L buffer with only %i planes\n", plane+1, in->num_planes); + return AVERROR(EINVAL); + } + + switch(in->pool->memory) { + case V4L2_MEMORY_MMAP: + *buf = av_buffer_create(in->mmap_addr[plane], in->lengths[plane], buffer_callback, in, 0); + if(!*buf) + return AVERROR(ENOMEM); + in->ref_cnt++; + in->status = V4LBUF_RET_USER; + break; + case V4L2_MEMORY_USERPTR: + if(!in->bufrefs[plane]) { + av_log(in->pool->log_ctx, AV_LOG_ERROR, "Calling %s with a USERPTR buffer but no AVBufferRef has been found...\n", __func__); + return AVERROR(EINVAL); + } + *buf = av_buffer_ref(in->bufrefs[plane]); + if(!*buf) + return AVERROR(ENOMEM); + av_buffer_unref(&in->bufrefs[plane]); + in->status = V4LBUF_AVAILABLE; + break; + default: + av_log(in->pool->log_ctx, AV_LOG_ERROR, "%s: Sorry, memory type %i not supported", __func__, in->pool->memory); + return AVERROR_PATCHWELCOME; + } + return 0; +} + +static int v4lbuf_to_avpkt(AVPacket *pkt, V4LBuffer *avbuf) { + int ret; + av_free_packet(pkt); + if(ret = v4l2bufref(avbuf, 0, &pkt->buf)) + return ret; + + pkt->data = pkt->buf->data; + if(V4L2_TYPE_IS_MULTIPLANAR(avbuf->pool->type)) { + pkt->size = avbuf->buf.m.planes[0].bytesused; + } else + pkt->size = avbuf->buf.bytesused; + + if(avbuf->buf.flags & V4L2_BUF_FLAG_KEYFRAME) + pkt->flags |= AV_PKT_FLAG_KEY; + + pkt->pts = get_pts(avbuf); + return 0; +} + +static int v4lbuf_to_avframe(AVFrame *frame, V4LBuffer *avbuf) { + int i, ret; + av_frame_unref(frame); + for(i=0; i<avbuf->num_planes; i++) { + if(ret = v4l2bufref(avbuf, i, &frame->buf[i])) + return ret; + frame->data[i] = frame->buf[i]->data; + frame->linesize[i] = avbuf->linesize[i]; + } + + frame->format = avbuf->pool->av_pix_fmt; + frame->key_frame = !!(avbuf->buf.flags & V4L2_BUF_FLAG_KEYFRAME); + frame->width = avbuf->pool->width; + frame->height = avbuf->pool->height; + frame->pts = get_pts(avbuf); + return 0; +} + +static V4LBuffer* v4lbufpool_get_from_avframe(const AVFrame* frame, V4LBufferPool *p) { + int i; + for(i=0; i<p->num_buffers; i++) { + if(V4LBUF_RET_USER == p->buffers[i].status) { + switch(p->memory) { + case V4L2_MEMORY_MMAP: + if(p->buffers[i].mmap_addr[0] == frame->buf[0]->data) + return &(p->buffers[i]); + break; + default: + av_log(p->log_ctx, AV_LOG_ERROR, "%s: Sorry, memory type %i not supported\n", __func__, p->memory); + return NULL; + } + } + } + return NULL; +} + +V4LBuffer* avpriv_v4lbufpool_getfreebuf(V4LBufferPool *p, const AVFrame *f, const AVPacket* pkt) { + int i; + V4LBuffer* ret = NULL; + +#ifdef V4L_BUFFER_DEBUG + av_log(p->log_ctx, AV_LOG_DEBUG, "Polling for a free buffer on %s\n", p->name); +#endif + + /* For input pools, we prefer to dequeue available buffers first. */ + if(V4L2_TYPE_IS_OUTPUT(p->type)) + while(dequeue_v4lbuf(p)); + + if(f && (ret = v4lbufpool_get_from_avframe(f,p))) + return ret; + + if(!ret) + for(i=0; i<p->num_buffers; i++) + if(p->buffers[i].status == V4LBUF_AVAILABLE) + return &(p->buffers[i]); + return ret; +} + +int avpriv_set_stream_status(V4LBufferPool* bp, int cmd) { + int ret; + int type = bp->type; + if((ret = bp->ioctl_f(bp->fd, cmd, &type)) < 0) + return AVERROR(errno); + bp->streamon = (cmd == VIDIOC_STREAMON); + return 0; +} + +int avpriv_v4l_enqueue_frame_or_pkt_or_buf(V4LBufferPool* bp, const AVFrame* f, const AVPacket* pkt, const uint8_t* buf, int buf_size) { + V4LBuffer* avbuf = NULL; + int ret; + + if(!f && !pkt && !buf) { + av_log(bp->log_ctx, AV_LOG_ERROR, "%s: At least one of AVFrame*, AVPacket* or buf must be non NULL!\n", __func__); + return AVERROR_BUG; + } + + if(!(avbuf = avpriv_v4lbufpool_getfreebuf(bp, f, pkt))) { + if(bp->memory == V4L2_MEMORY_MMAP) + av_log(bp->log_ctx, AV_LOG_ERROR, "No free input buffer found\n"); + return AVERROR(ENOMEM); + } + + if(f && (ret = avframe_to_v4lbuf(f, avbuf))) + return ret; + + if(pkt && (ret = avpkt_to_v4lbuf(pkt, avbuf))) + return ret; + + if(buf && (ret = buf2v4l(avbuf, 0, buf, buf_size, NULL))) + return ret; + + if(ret = enqueue_v4lbuf(avbuf)) + return ret; + + return 0; +} + +int avpriv_v4l_dequeue_frame_or_pkt(V4LBufferPool* bp, AVFrame* f, AVPacket* pkt) { + V4LBuffer* avbuf = NULL; + + if((!f && !pkt) || (f && pkt)) { + av_log(bp->log_ctx, AV_LOG_ERROR, "%s: Exactly one of AVFrame* or AVPacket* must be non NULL!\n", __func__); + return AVERROR_BUG; + } + + if(!(avbuf = dequeue_v4lbuf(bp))) + return AVERROR(EAGAIN); + + if(f) + return v4lbuf_to_avframe(f, avbuf); + + if(pkt) + return v4lbuf_to_avpkt(pkt, avbuf); + + /* Impossible to get here */ + return AVERROR_BUG; +} + +static inline int get_buffer_sanity_check(V4LBufferPool* bp, const char* func) { + if(bp->memory != V4L2_MEMORY_MMAP) { + av_log(bp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense with memory type %i\n", func, bp->memory); + return AVERROR(EINVAL); + } + + if(!V4L2_TYPE_IS_OUTPUT(bp->type)) { + av_log(bp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense on capture pools.\n", func); + return AVERROR(EINVAL); + } + + return 0; +} + +int av_v4l_buffer_pool_get_buffer_frame(void* priv_data, AVFrame* frame) { + V4LBufferPool* bp = priv_data; + V4LBuffer* avbuf = NULL; + int ret; + + if(!frame) + return AVERROR(EINVAL); + + if(ret = get_buffer_sanity_check(bp, __func__)) + return ret; + + if(bp->av_codec_id != AV_CODEC_ID_RAWVIDEO || bp->av_pix_fmt == AV_PIX_FMT_NONE) { + av_log(bp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense on encoded streams.\n", __func__); + return AVERROR(EINVAL); + } + + if(!(avbuf = avpriv_v4lbufpool_getfreebuf(bp, NULL, NULL))) + return AVERROR(ENOMEM); + + if(ret = v4lbuf_to_avframe(frame, avbuf)) { + av_frame_unref(frame); + return ret; + } + + return 0; +} + +int av_v4l_buffer_pool_get_buffer_packet(void* priv_data, AVPacket* pkt) { + V4LBufferPool* bp = priv_data; + V4LBuffer* avbuf = NULL; + int ret; + + if(!pkt) + return AVERROR(EINVAL); + + if(ret = get_buffer_sanity_check(bp, __func__)) + return ret; + + if(bp->av_codec_id == AV_CODEC_ID_RAWVIDEO || bp->av_pix_fmt != AV_PIX_FMT_NONE) { + av_log(bp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense on raw streams.\n", __func__); + return AVERROR(EINVAL); + } + + if(!(avbuf = avpriv_v4lbufpool_getfreebuf(bp, NULL, NULL))) + return AVERROR(ENOMEM); + + if(ret = v4lbuf_to_avpkt(pkt, avbuf)) { + av_free_packet(pkt); + return ret; + } + + return 0; +} + +int av_v4l_buffer_pool_make_pipe(void* src, void* dst) { + int ret; + V4LBuffer *ibuf = NULL, *obuf = NULL; + V4LBufferPool *ibp = src; + V4LBufferPool *obp = dst; + + /* Sanity checks */ + if(ibp->memory != V4L2_MEMORY_MMAP) { + av_log(ibp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense with memory type %i for src\n", __func__, ibp->memory); + return AVERROR(EINVAL); + } + + if(!V4L2_TYPE_IS_OUTPUT(ibp->type)) { + av_log(ibp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense withg src a capture pool.\n", __func__); + return AVERROR(EINVAL); + } + + if(obp->memory != V4L2_MEMORY_USERPTR) { + av_log(obp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense with memory type %i for dst\n", __func__, obp->memory); + return AVERROR(EINVAL); + } + + if(V4L2_TYPE_IS_OUTPUT(obp->type)) { + av_log(obp->log_ctx, AV_LOG_ERROR, "Calling %s does not make sense with dst an output pool.\n", __func__); + return AVERROR(EINVAL); + } + + /* Check for optimal settings */ + if(ibp->num_buffers != obp->num_buffers) + av_log(ibp->log_ctx, AV_LOG_WARNING, + "%s cannot perfectly match buffer pool sizes, please check your settings for optimal results.\n" + "src has %i buffers, while dst has %i\n", __func__, ibp->num_buffers, obp->num_buffers); + + while( (ibuf = avpriv_v4lbufpool_getfreebuf(ibp, NULL, NULL)) && + (obuf = avpriv_v4lbufpool_getfreebuf(obp, NULL, NULL))) { + if(ret = avbuf_to_avbuf(ibuf, obuf)) { + av_log(ibp->log_ctx, AV_LOG_ERROR, "Failed to convert buffers\n"); + return ret; + } + + if(ret = enqueue_v4lbuf(obuf)) { + av_log(obp->log_ctx, AV_LOG_ERROR, "Failed to enqueue buffer\n"); + return ret; + } + + ibuf->piped = obuf; + } + + return 0; +} + +static void buffer_callback(void *opaque, uint8_t *unused) { + V4LBuffer* avbuf = opaque; + if(--avbuf->ref_cnt<=0) { + if(V4LBUF_IN_DRIVER != avbuf->status) { + if(!V4L2_TYPE_IS_OUTPUT(avbuf->pool->type)) { + enqueue_v4lbuf(avbuf); + } else if(avbuf->piped) { + if(avbuf_to_avbuf(avbuf, avbuf->piped) || enqueue_v4lbuf(avbuf->piped)) + av_log(avbuf->pool->log_ctx, AV_LOG_FATAL, "Something went wrong when sending back buffer to its pipe...\n"); + } else { + avbuf->status = V4LBUF_AVAILABLE; + } + } + } +} diff --git a/libavcodec/v4l2-buffers.h b/libavcodec/v4l2-buffers.h new file mode 100644 index 0000000..abf2816 --- /dev/null +++ b/libavcodec/v4l2-buffers.h @@ -0,0 +1,230 @@ +/* + * V4L2 buffer{,pool} helper functions. + * Copyright (C) 2014 Alexis Ballier + * + * 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 AVCODEC_V4L2_BUFFERS_H +#define AVCODEC_V4L2_BUFFERS_H + +#include "v4l2-common.h" +#include "avcodec.h" +#include "libavutil/pixfmt.h" +#include "libavutil/frame.h" + +struct V4LBuffer; +typedef struct V4LBuffer V4LBuffer; + +typedef struct V4LBufferPool { + /** + * Log context (for av_log()). Can be NULL. + */ + void *log_ctx; + + /** + * Pool's name. Must be set before calling avpriv_init_v4lbufpool(). + */ + const char* name; + + /** + * File descriptor obtained from opening the associated device. + * Must be set before calling avpriv_init_v4lbufpool(). + * Readonly after init. + */ + int fd; + + /** + * Wrapper for their corresponding standard functions. + * If unset before calling avpriv_init_v4lbufpool() these will be set to the OS/libc functions. + * If the bufferpool should be used through libv4l2, these must be set to the libv4l2 wrappers + * before calling avpriv_init_v4lbufpool(). + */ + int (*open_f)(const char *file, int oflag, ...); + int (*close_f)(int fd); + int (*dup_f)(int fd); + int (*ioctl_f)(int fd, unsigned long int request, ...); + ssize_t (*read_f)(int fd, void *buffer, size_t n); + void *(*mmap_f)(void *start, size_t length, int prot, int flags, int fd, int64_t offset); + int (*munmap_f)(void *_start, size_t length); + + /** + * Type of this buffer pool. + * See V4L2_BUF_TYPE_VIDEO_* in videodev2.h + * Must be set before calling avpriv_init_v4lbufpool(). + * Readonly after init. + */ + enum v4l2_buf_type type; + + /** + * Memory type this buffer pool uses. + * See V4L2_MEMORY_* in videodev2.h + * Must be set before calling avpriv_init_v4lbufpool(). + * Readonly after init. + */ + enum v4l2_memory memory; + + /** + * AVPixelFormat corresponding to this buffer pool. + * AV_PIX_FMT_NONE means this is an encoded stream. + */ + enum AVPixelFormat av_pix_fmt; + + /** + * AVCodecID corresponding to this buffer pool. + * AV_CODEC_ID_RAWVIDEO means this is a raw stream and av_pix_fmt must be set to a valid value. + */ + enum AVCodecID av_codec_id; + + /** + * Format returned by the driver after initializing the buffer pool. + * Must be set before calling avpriv_init_v4lbufpool(). + * avpriv_set_pool_format() can set it. + * Readonly after init. + */ + struct v4l2_format format; + + /** + * Width and height of the frames it produces (in case of a capture pool, e.g. when decoding) + * or accepts (in case of an output pool, e.g. when encoding). + * + * For output pools, this must must be set before calling avpriv_init_v4lbufpool(). + * For capture pools, it will be set after having received the information from the driver. + */ + int width, height; + + /** + * Default flags to set on buffers to enqueue. + * See V4L2_BUF_FLAG_*. + */ + int default_flags; + + /** + * Used internally to warn about possibly useless memcpy()'s. + * Can be set to 1 to ignore the warning. + */ + int copy_warn; + + /** + * Whether the stream has been started (VIDIOC_STREAMON has been sent). + */ + int streamon; + + /** + * Number of queued buffers. + */ + int num_queued; + + /** + * Time (in ms) we can wait for a buffer before considering it a failure. + */ + int blocking_dequeue; + + /** + * Minimum number of buffers that must be kept queued in this queue. + * + * E.g. for decoders, the drivers might have such requirements to produce proper output. + */ + int min_queued_buffers; + + /** + * The actual number of buffers. + * + * Before calling avpriv_init_v4lbufpool() this is the number of buffers we would like to have available. + * avpriv_init_v4lbufpool() asks for (min_buffers + num_buffers) and sets this value to the actual number + * of buffers the driver gave us. + * Readonly after init. + */ + int num_buffers; + + /** + * Opaque pointers to the actual buffers representations. + * After initialization, it is an array of size num_buffers. + */ + V4LBuffer *buffers; +} V4LBufferPool; + +/** + * Initializes a V4LBufferPool. + * + * @param[in] bp A pointer to a V4LBufferPool. See V4LBufferPool description for required variables. + * @return 0 in case of success, a negative value representing the error otherwise. + */ +int avpriv_init_v4lbufpool(V4LBufferPool* bp); + +/** + * Releases a V4LBufferPool. + * + * @param[in] bp A pointer to a V4LBufferPool. + * The caller is reponsible for freeing it. + * It must not be used after calling this function. + */ +void avpriv_release_buffer_pool(V4LBufferPool* bp); + +/** + * Sets the status of a V4LBufferPool. + * + * @param[in] bp A pointer to a V4LBufferPool. + * @param[in] cmd The status to set (VIDIOC_STREAMON or VIDIOC_STREAMOFF). + * Warning: If VIDIOC_STREAMOFF is sent to a buffer pool that still has some frames buffered, + * those frames will be dropped. + * @return 0 in case of success, a negative value representing the error otherwise. + */ +int avpriv_set_stream_status(V4LBufferPool* bp, int cmd); + +/** + * Dequeues a buffer from a V4LBufferPool to either an AVFrame or an AVPacket. + * + * Exactly one of f or pkt must be non NULL. + * @param[in] bp The V4LBufferPool to dequeue from. + * @param[inout] f The AVFrame to dequeue to. + * @param[inout] pkt The AVPacket to dequeue to. + * @return 0 in case of success, AVERROR(EAGAIN) if no buffer was ready, another negative error in case of error. + */ +int avpriv_v4l_dequeue_frame_or_pkt(V4LBufferPool* bp, AVFrame* f, AVPacket* pkt); + +/** + * Enqueues a buffer to a V4LBufferPool from either an AVFrame, an AVPacket or a raw buffer. + * Exactly one of f, pkt or buf must be non NULL. + * + * @param[in] bp The V4LBufferPool to enqueue to. + * @param[in] f A pointer to an AVFrame to enqueue. + * @param[in] pkt A pointer to an AVPacket to enqueue. + * @param[in] buf A pointer to a buffer to enqueue. + * @param[in] buf_size The size of the buffer pointed by buf. + * @return 0 in case of success, a negative error otherwise. + */ +int avpriv_v4l_enqueue_frame_or_pkt_or_buf(V4LBufferPool* bp, const AVFrame* f, const AVPacket* pkt, const uint8_t* buf, int buf_size); + +/** + * Gets a free V4LBuffer from a V4LBufferPool. + * + * If no matching buffer is found (see below), it tries to dequeue a buffer first + * in order to minimize the size of the V4L queue.e + * + * @param[in] p Pointer to a V4LBufferPool where to get the buffer from. + * @param[in] f A pointer to an existing AVFrame: + * If the AVFrame's buffers match a V4LBuffer, this V4LBuffer will be returned. + * Can be NULL. + * @param[in] pkt A pointer to an existing AVPacket: + * If the AVPacket's buffers match a V4LBuffer, this V4LBuffer will be returned. + * Can be NULL. + * @return A pointer to the V4LBuffer or NULL in case of error. + */ +V4LBuffer* avpriv_v4lbufpool_getfreebuf(V4LBufferPool *p, const AVFrame *f, const AVPacket* pkt); + +#endif // AVCODEC_V4L2_BUFFERS_H diff --git a/libavcodec/v4l2.h b/libavcodec/v4l2.h new file mode 100644 index 0000000..8771b4f --- /dev/null +++ b/libavcodec/v4l2.h @@ -0,0 +1,66 @@ +/* + * V4L2 bufferpools helper functions + * Copyright (C) 2014 Alexis Ballier + * + * 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 AVCODEC_V4L2_H +#define AVCODEC_V4L2_H + +#include <libavutil/frame.h> +#include <libavcodec/avcodec.h> + +/** + * Allocates an AVFrame's buffers for feeding a V4L M2M device whose buffers + * are mmaped in device memory. + * + * @param[in] priv_data A pointer to an output pool. + * @param[in] frame A pre-allocated AVFrame. It will be unref'ed. + * @return 0 in case of success, a negative value indicating the error otherwise. + */ +int av_v4l_buffer_pool_get_buffer_frame(void* priv_data, AVFrame* frame); + +/** + * Allocates an AVPacket's buffers for feeding a V4L M2M device whose buffers + * are mmaped in device memory. + * + * @param[in] priv_data A pointer to an output pool. + * @param[in] frame A pre-allocated AVPacket. Its buffers will be unref'ed. + * @return 0 in case of success, a negative value indicating the error otherwise. + */ +int av_v4l_buffer_pool_get_buffer_packet(void* priv_data, AVPacket* pkt); + +/** + * Makes a "pipe" between src and dst buffer pools. + * + * @param[in] src A mmap'ed output buffer pool. + * @param[in] dst A userptr capture buffer pool. + * + * src and dst should be configured to have the same number of buffers + * for optimal performance. + * + * Buffers from src will be queued to dst, so that dst will write its results there. + * When buffers from dst get fed back to src after processing them, they will be + * queued without copy in src (an output queue). + * When buffers are no longer used by src, they will be automatically queued back to dst. + * + * @return 0 in case of success, a negative value indicating the error otherwise. + */ +int av_v4l_buffer_pool_make_pipe(void* src, void* dst); + +#endif // AVCODEC_V4L2_H diff --git a/libavcodec/v4l2_m2m.c b/libavcodec/v4l2_m2m.c new file mode 100644 index 0000000..772f1c2 --- /dev/null +++ b/libavcodec/v4l2_m2m.c @@ -0,0 +1,277 @@ +/* + * V4L mem2mem wrapper + * Copyright (C) 2014 Alexis Ballier + * + * 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 <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <dirent.h> + + +#include "libavutil/imgutils.h" +#include "libavutil/pixfmt.h" +#include "libavutil/pixdesc.h" +#include "avcodec.h" +#include "v4l2-buffers.h" +#include "v4l2-common.h" +#include "v4l2_m2m.h" +#include "v4l2_m2m_avcodec.h" +#include "v4l2.h" + +#define V4L_MAX_STREAM_SIZE (2*1024*1024) + +static const char opool_name[] = "output pool"; +static const char cpool_name[] = "capture pool"; + +static inline int try_raw_format(V4LBufferPool* bp, enum AVPixelFormat pixfmt) { + struct v4l2_format *fmt = &bp->format; + int i; + + fmt->type = bp->type; + + if(V4L2_TYPE_IS_MULTIPLANAR(bp->type)) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pixfmt); + + fmt->fmt.pix_mp.width = bp->width; + fmt->fmt.pix_mp.height = bp->height; + + fmt->fmt.pix_mp.num_planes = av_pix_fmt_count_planes(pixfmt); + + for(i=0; i<fmt->fmt.pix_mp.num_planes; i++) { + uint32_t h; + fmt->fmt.pix_mp.plane_fmt[i].bytesperline = av_image_get_linesize(pixfmt, bp->width, i); + + if (i == 1 || i == 2) { + h = FF_CEIL_RSHIFT(bp->height, desc->log2_chroma_h); + } else + h = bp->height; + + fmt->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->fmt.pix_mp.plane_fmt[i].bytesperline * h; + } + + fmt->fmt.pix_mp.pixelformat = avpriv_v4l_fmt_ff2v4l(pixfmt, bp->av_codec_id, FF_V4L_PACK_AVFRAME); + if(fmt->fmt.pix_mp.pixelformat && !ioctl(bp->fd, VIDIOC_TRY_FMT, fmt)) + return 0; + } else { + fmt->fmt.pix.width = bp->width; + fmt->fmt.pix.height = bp->height; + + fmt->fmt.pix.bytesperline = av_image_get_linesize(pixfmt, bp->width, 0); + fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline * bp->height; + fmt->fmt.pix.pixelformat = avpriv_v4l_fmt_ff2v4l(pixfmt, bp->av_codec_id, FF_V4L_PACK_AVFRAME); + + if(fmt->fmt.pix.pixelformat && !ioctl(bp->fd, VIDIOC_TRY_FMT, fmt)) + return 0; + } + + return AVERROR(EINVAL); +} + +static int set_raw_format(V4LBufferPool* bp, int set) { + struct v4l2_format *fmt = &bp->format; + enum AVPixelFormat pixfmt = bp->av_pix_fmt; + struct v4l2_fmtdesc fmtdesc = { 0 }; + + fmtdesc.type = bp->type; + + if(AV_PIX_FMT_NONE != pixfmt) { + if(try_raw_format(bp, pixfmt)) { + if(set) + av_log(bp->log_ctx, AV_LOG_INFO, "Suggested pixel format %s is not accepted on %s, will guess one.\n", av_get_pix_fmt_name(pixfmt), bp->name); + pixfmt = AV_PIX_FMT_NONE; + } + } + + while(AV_PIX_FMT_NONE == pixfmt && !ioctl(bp->fd, VIDIOC_ENUM_FMT, &fmtdesc)) { + pixfmt = avpriv_v4l_fmt_v4l2ff(fmtdesc.pixelformat, AV_CODEC_ID_RAWVIDEO); + if(try_raw_format(bp, pixfmt)) + pixfmt = AV_PIX_FMT_NONE; + if(AV_PIX_FMT_NONE != pixfmt && set) { + av_log(bp->log_ctx, AV_LOG_INFO, "Pixelformat %s is accepted on %s, using it.\n", av_get_pix_fmt_name(pixfmt), bp->name); + bp->av_pix_fmt = pixfmt; + } + fmtdesc.index++; + } + + if(AV_PIX_FMT_NONE == pixfmt) + return AVERROR(EINVAL); + + return (set ? ioctl(bp->fd, VIDIOC_S_FMT, fmt) : 0); +} + +static int set_coded_format(V4LBufferPool* bp, int set) { + struct v4l2_format *fmt = &bp->format; + + fmt->type = bp->type; + + if(V4L2_TYPE_IS_MULTIPLANAR(bp->type)) { + fmt->fmt.pix_mp.num_planes = VIDEO_MAX_PLANES; + fmt->fmt.pix_mp.pixelformat = avpriv_v4l_fmt_ff2v4l(bp->av_pix_fmt, bp->av_codec_id, FF_V4L_PACK_AVPACKET); + if(!fmt->fmt.pix_mp.pixelformat) { + av_log(bp->log_ctx, AV_LOG_ERROR, "No matching V4L Codec ID found for %i\n", bp->av_codec_id); + return AVERROR(EINVAL); + } + fmt->fmt.pix_mp.plane_fmt[0].sizeimage = V4L_MAX_STREAM_SIZE; + } else { + fmt->fmt.pix.pixelformat = avpriv_v4l_fmt_ff2v4l(bp->av_pix_fmt, bp->av_codec_id, FF_V4L_PACK_AVPACKET); + if(!fmt->fmt.pix.pixelformat) { + av_log(bp->log_ctx, AV_LOG_ERROR, "No matching V4L Codec ID found for %i\n", bp->av_codec_id); + return AVERROR(EINVAL); + } + fmt->fmt.pix.sizeimage = V4L_MAX_STREAM_SIZE; + } + return (set ? ioctl(bp->fd, VIDIOC_S_FMT, fmt) : ioctl(bp->fd, VIDIOC_TRY_FMT, fmt)); +} + +int avpriv_set_pool_format(V4LBufferPool* bp, int set) { + if(AV_CODEC_ID_RAWVIDEO == bp->av_codec_id) + return set_raw_format(bp, set); + return set_coded_format(bp, set); +} + +static int probe_and_set(V4Lm2mContext* s, void *log_ctx, int set) { + int ret; + + int fail_log_level = ( set ? AV_LOG_ERROR : AV_LOG_DEBUG); + + s->fd = open(s->devname, O_RDWR | O_NONBLOCK, 0); + + if(s->fd < 0) + return AVERROR(errno); + + s->capture_pool.fd = s->fd; + s->capture_pool.log_ctx = log_ctx; + s->capture_pool.name = cpool_name; + s->output_pool.fd = s->fd; + s->output_pool.log_ctx = log_ctx; + s->output_pool.name = opool_name; + + if((ret = ioctl(s->fd, VIDIOC_QUERYCAP, &(s->cap))) < 0) + goto fail; + +#define CHECK_CAPS(s, ncap) ((s->cap.capabilities & (ncap)) == (ncap)) + if(CHECK_CAPS(s, V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING) || CHECK_CAPS(s, V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_STREAMING)) { + s->capture_pool.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + s->output_pool.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + } else if(CHECK_CAPS(s, V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING) || CHECK_CAPS(s, V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING)) { + s->capture_pool.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + s->output_pool.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + } else { + av_log(log_ctx, fail_log_level, "Sorry, driver '%s' on card '%s' is not a V4L mem2mem device\n", s->cap.driver, s->cap.card); + ret = AVERROR(EINVAL); + goto fail; + } +#undef CHECK_CAPS + + if(s->output_pool_needs_format && (ret = avpriv_set_pool_format(&s->output_pool, set))) { + av_log(log_ctx, fail_log_level, "Failed to set input format\n"); + goto fail; + } + + if(s->capture_pool_needs_format && (ret = avpriv_set_pool_format(&s->capture_pool, set))) { + av_log(log_ctx, fail_log_level, "Failed to set output format\n"); + goto fail; + } + + if(s->output_pool_needs_init && set && (ret = avpriv_init_v4lbufpool(&(s->output_pool)))) { + av_log(log_ctx, fail_log_level, "Failed to request output pool's buffers\n"); + goto fail; + } + + if(s->capture_pool_needs_init && set && (ret = avpriv_init_v4lbufpool(&(s->capture_pool)))) { + av_log(log_ctx, fail_log_level, "Failed to request capture pool's buffers\n"); + goto fail; + } + +fail: + if(!set || ret) { + close(s->fd); + s->fd = 0; + } + return ret; +} + +int avpriv_v4lm2m_init(V4Lm2mContext* s, void* log_ctx) { + int ret = AVERROR(EINVAL); + + if(s->devname && *s->devname) { + return probe_and_set(s, log_ctx, 1); + } else { + DIR *dirp; + struct dirent *dp; + const char pref[] = "video"; + char *devname_save = s->devname; + char tmpbuf[PATH_MAX]; + + av_log(log_ctx, AV_LOG_INFO, "Device path not set, probing /dev/video*\n"); + + if(!(dirp = opendir("/dev"))) + return AVERROR(errno); + + for(dp = readdir(dirp); dp; dp = readdir(dirp)) { + if(!strncmp(dp->d_name, pref, sizeof(pref)-1)) { + errno = 0; + snprintf(tmpbuf, sizeof(tmpbuf)-1, "/dev/%s", dp->d_name); + av_log(log_ctx, AV_LOG_DEBUG, "Probing %s\n", tmpbuf); + s->devname = tmpbuf; + if(!(ret = probe_and_set(s, log_ctx, 0))) + break; + } + } + + closedir(dirp); + + if(!ret) { + av_log(log_ctx, AV_LOG_INFO, "Using device %s\n", tmpbuf); + ret = probe_and_set(s, log_ctx, 1); + } else { + av_log(log_ctx, AV_LOG_ERROR, "Could not find a valid device\n"); + } + + s->devname = devname_save; + } + + return ret; +} + + +int ff_v4lm2m_codec_init(AVCodecContext *avctx) { + V4Lm2mContext *s = avctx->priv_data; + return avpriv_v4lm2m_init(s, avctx); +} + +int avpriv_v4lm2m_end(V4Lm2mContext* s) { + avpriv_release_buffer_pool(&s->output_pool); + avpriv_release_buffer_pool(&s->capture_pool); + + avpriv_set_stream_status(&s->output_pool, VIDIOC_STREAMOFF); + avpriv_set_stream_status(&s->capture_pool, VIDIOC_STREAMOFF); + + close(s->fd); + return 0; +} + +int ff_v4lm2m_codec_end(AVCodecContext *avctx) { + V4Lm2mContext *s = avctx->priv_data; + + av_log(avctx, AV_LOG_DEBUG, "Closing context\n"); + return avpriv_v4lm2m_end(s); +} diff --git a/libavcodec/v4l2_m2m.h b/libavcodec/v4l2_m2m.h new file mode 100644 index 0000000..5b0db3c --- /dev/null +++ b/libavcodec/v4l2_m2m.h @@ -0,0 +1,86 @@ +/* + * V4L2 mem2mem helper functions + * Copyright (C) 2014 Alexis Ballier + * + * 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 AVCODEC_V4L2_M2M_H +#define AVCODEC_V4L2_M2M_H + +#include "v4l2-common.h" +#include "v4l2-buffers.h" + +#define V4L_M2M_DEFAULT_OPTS \ + { "device",\ + "Path to the device to use",\ + OFFSET(devname),\ + AV_OPT_TYPE_STRING,\ + {.str = NULL }, 0, 0, FLAGS },\ + { "input_memory",\ + "Input memory model: See V4L2_MEMORY_* in videodev2.h. This depends on the HW but default should work with most but would imply useless memcpy()'s if used improperly.",\ + OFFSET(output_pool.memory),\ + AV_OPT_TYPE_INT,\ + {.i64 = V4L2_MEMORY_MMAP},\ + 0, INT_MAX, FLAGS },\ + { "output_memory",\ + "Output memory model: See V4L2_MEMORY_* in videodev2.h. This depends on the HW but default should work with most.",\ + OFFSET(capture_pool.memory),\ + AV_OPT_TYPE_INT,\ + {.i64 = V4L2_MEMORY_MMAP},\ + 0, INT_MAX, FLAGS },\ + { "num_output_pool_buffers",\ + "Number of buffers in the output pool",\ + OFFSET(output_pool.num_buffers),\ + AV_OPT_TYPE_INT,\ + { .i64 = 16 },\ + 4, INT_MAX, FLAGS },\ + { "output_pool_offset", \ + "Offset of the output buffer pool in the private context.",\ + OFFSET(output_pool_offset_storage),\ + AV_OPT_TYPE_INT64,\ + {.i64 = OFFSET(output_pool) },\ + INT_MIN, INT_MAX, FLAGS },\ + { "capture_pool_offset", \ + "Offset of the capture buffer pool in the private context.",\ + OFFSET(capture_pool_offset_storage),\ + AV_OPT_TYPE_INT64,\ + {.i64 = OFFSET(capture_pool) },\ + INT_MIN, INT_MAX, FLAGS } + +typedef struct V4Lm2mContext { + AVClass *class; + int fd; + char *devname; + struct v4l2_capability cap; + + int64_t output_pool_offset_storage; + int output_pool_needs_format; + int output_pool_needs_init; + V4LBufferPool output_pool; + + int64_t capture_pool_offset_storage; + int capture_pool_needs_format; + int capture_pool_needs_init; + V4LBufferPool capture_pool; +} V4Lm2mContext; + +int avpriv_set_pool_format(V4LBufferPool* bp, int set); +int avpriv_v4lm2m_init(V4Lm2mContext* s, void* log_ctx); +int avpriv_v4lm2m_end(V4Lm2mContext* ctx); + +#endif // AVCODEC_V4L2_M2M_H diff --git a/libavcodec/v4l2_m2m_avcodec.h b/libavcodec/v4l2_m2m_avcodec.h new file mode 100644 index 0000000..c145713 --- /dev/null +++ b/libavcodec/v4l2_m2m_avcodec.h @@ -0,0 +1,30 @@ +/* + * V4L2 mem2mem avcodec helper functions + * Copyright (C) 2014 Alexis Ballier + * + * 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 AVCODEC_V4L2_M2M_AVCODEC_H +#define AVCODEC_V4L2_M2M_AVCODEC_H + +#include "avcodec.h" + +int ff_v4lm2m_codec_init(AVCodecContext *avctx); +int ff_v4lm2m_codec_end(AVCodecContext *avctx); + +#endif diff --git a/libavcodec/v4l2_m2m_dec.c b/libavcodec/v4l2_m2m_dec.c new file mode 100644 index 0000000..2eef728 --- /dev/null +++ b/libavcodec/v4l2_m2m_dec.c @@ -0,0 +1,227 @@ +/* + * V4L2 mem2mem decoders + * Copyright (C) 2014 Alexis Ballier + * + * 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 + */ + +/* + * Missing features: + * - seek (flush) + * - resolution change + */ + +#include <sys/ioctl.h> + +#include "libavutil/pixfmt.h" +#include "libavutil/pixdesc.h" +#include "avcodec.h" +#include "v4l2-buffers.h" +#include "v4l2_m2m.h" +#include "v4l2_m2m_avcodec.h" +#include "v4l2-common.h" + + +#include "libavutil/opt.h" + +static int try_start(AVCodecContext *avctx) { + struct v4l2_control ctrl; + struct v4l2_crop crop; + int ret; + V4Lm2mContext *s = avctx->priv_data; + + if(!s->output_pool.streamon && (ret = avpriv_set_stream_status(&s->output_pool, VIDIOC_STREAMON) < 0)) { + av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON failed on input pool\n"); + return ret; + } + + s->capture_pool.format.type = s->capture_pool.type; + if(ret = ioctl(s->fd, VIDIOC_G_FMT, &s->capture_pool.format)) { + av_log(avctx, AV_LOG_WARNING, "Failed to get output format\n"); + return ret; + } + + crop.type = s->capture_pool.type; + if(ret = ioctl(s->fd, VIDIOC_G_CROP, &crop)) { + av_log(avctx, AV_LOG_WARNING, "Failed to get cropping information\n"); + return ret; + } + + s->capture_pool.width = avctx->width = s->capture_pool.format.fmt.pix_mp.width; + s->capture_pool.height = avctx->height = s->capture_pool.format.fmt.pix_mp.height; + s->capture_pool.av_pix_fmt = avctx->pix_fmt = avpriv_v4l_fmt_v4l2ff(s->capture_pool.format.fmt.pix_mp.pixelformat, AV_CODEC_ID_RAWVIDEO); + + avctx->coded_width = crop.c.width; + avctx->coded_height = crop.c.height; + + ctrl.id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE; + if(ret = ioctl(s->fd, VIDIOC_G_CTRL, &ctrl)) { + av_log(avctx, AV_LOG_WARNING, "Failed to get minimum input packets for decoding\n"); + return ret; + } + + s->capture_pool.min_queued_buffers = ctrl.value; + + /* Init capture pool after starting output pool */ + if(!s->capture_pool.buffers && (ret = avpriv_init_v4lbufpool(&(s->capture_pool)))) { + av_log(avctx, AV_LOG_ERROR, "Failed to request output buffers\n"); + return ret; + } + + if(ret = avpriv_set_stream_status(&s->capture_pool, VIDIOC_STREAMON)) { + av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON failed on output pool\n"); + return ret; + } + + return 0; +} + +static av_cold int v4lm2m_decode_init(AVCodecContext *avctx) { + int ret; + V4Lm2mContext *s = avctx->priv_data; + + s->output_pool.av_pix_fmt = AV_PIX_FMT_NONE; + s->output_pool.av_codec_id = avctx->codec_id; + s->output_pool_needs_format = 1; + s->output_pool_needs_init = 1; + s->output_pool.default_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + s->capture_pool.av_pix_fmt = avctx->pix_fmt; + s->capture_pool.av_codec_id = AV_CODEC_ID_RAWVIDEO; + s->capture_pool.default_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + if(ret = ff_v4lm2m_codec_init(avctx)) + return ret; + + if( avctx->extradata && + avctx->extradata_size && + !avpriv_v4l_enqueue_frame_or_pkt_or_buf(&s->output_pool, NULL, NULL, avctx->extradata, avctx->extradata_size)) + try_start(avctx); + + return 0; +} + + +static int v4lm2m_decode_frame(AVCodecContext *avctx, void *data, + int *got_frame, AVPacket *avpkt) { + int ret; + V4Lm2mContext *s = avctx->priv_data; + AVFrame *frame = data; + *got_frame = 0; + + if( avpkt->size && + !s->capture_pool.streamon && + avctx->extradata && + avctx->extradata_size && + !avpriv_v4l_enqueue_frame_or_pkt_or_buf(&s->output_pool, NULL, NULL, avctx->extradata, avctx->extradata_size)) + try_start(avctx); + + /* Send regular packet, or one empty packet to notify EOS */ + if(avpkt->size || s->output_pool.streamon) + if((ret = avpriv_v4l_enqueue_frame_or_pkt_or_buf(&s->output_pool, NULL, avpkt, NULL, 0)) < 0) + return ret; + + if(!s->capture_pool.streamon) + try_start(avctx); + + /* Got EOS. We already sent the empty packet to the driver to notify EOS. */ + if(!avpkt->size && s->output_pool.streamon) + s->output_pool.streamon = 0; + + if(!s->capture_pool.streamon) + return avpkt->size; + + /* Need to wait for decoder before enqueuing more if output pool is almost full or on EOS */ + if(s->output_pool.num_queued >= s->output_pool.num_buffers - 2 || !avpkt->size) { + s->capture_pool.blocking_dequeue = 1000; + } else { + s->capture_pool.blocking_dequeue = 0; + } + + ret = avpriv_v4l_dequeue_frame_or_pkt(&(s->capture_pool), frame, NULL); + + if(!ret) { + *got_frame = 1; + } else if(AVERROR(EAGAIN) == ret && !s->capture_pool.blocking_dequeue) { + ret = 0; + } + + return (ret < 0 ? ret : avpkt->size); +} + +#define OFFSET(x) offsetof(V4Lm2mContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM + +static const AVOption options[] = { + V4L_M2M_DEFAULT_OPTS, + { "num_capture_pool_extra_buffers", + "Number of extra buffers in the capture pool", + OFFSET(capture_pool.num_buffers), + AV_OPT_TYPE_INT, + {.i64 = 4 }, + 4, INT_MAX, FLAGS }, + { NULL }, +}; + +#define M2MDEC(NAME, LONGNAME, CODEC) \ +static const AVClass v4l2_m2m_ ## NAME ## _dec_class = {\ + .class_name = #NAME "_v4l2_m2m_decoder",\ + .item_name = av_default_item_name,\ + .option = options,\ + .version = LIBAVUTIL_VERSION_INT,\ +};\ +\ +AVCodec ff_ ## NAME ## _v4l2m2m_decoder = { \ + .name = #NAME "_v4l2m2m" ,\ + .long_name = NULL_IF_CONFIG_SMALL("V4L2 mem2mem " LONGNAME " decoder wrapper"),\ + .type = AVMEDIA_TYPE_VIDEO,\ + .id = CODEC ,\ + .priv_data_size = sizeof(V4Lm2mContext),\ + .priv_class = &v4l2_m2m_ ## NAME ## _dec_class,\ + .init = v4lm2m_decode_init,\ + .decode = v4lm2m_decode_frame,\ + .close = ff_v4lm2m_codec_end,\ + .capabilities = CODEC_CAP_DELAY,\ +}; + +#if CONFIG_H263_V4L2M2M_DECODER +M2MDEC(h263 , "H.263" , AV_CODEC_ID_H263 ); +#endif + +#if CONFIG_H264_V4L2M2M_DECODER +M2MDEC(h264 , "H.264" , AV_CODEC_ID_H264 ); +#endif + +#if CONFIG_MPEG1_V4L2M2M_DECODER +M2MDEC(mpeg1, "MPEG1", AV_CODEC_ID_MPEG1VIDEO); +#endif + +#if CONFIG_MPEG2_V4L2M2M_DECODER +M2MDEC(mpeg2, "MPEG2", AV_CODEC_ID_MPEG2VIDEO); +#endif + +#if CONFIG_MPEG4_V4L2M2M_DECODER +M2MDEC(mpeg4, "MPEG4", AV_CODEC_ID_MPEG4); +#endif + +#if CONFIG_VC1_V4L2M2M_DECODER +M2MDEC(vc1 , "VC1" , AV_CODEC_ID_VC1 ); +#endif + +#if CONFIG_VP8_V4L2M2M_DECODER +M2MDEC(vp8 , "VP8" , AV_CODEC_ID_VP8 ); +#endif diff --git a/libavcodec/v4l2_m2m_enc.c b/libavcodec/v4l2_m2m_enc.c new file mode 100644 index 0000000..c70f3c4 --- /dev/null +++ b/libavcodec/v4l2_m2m_enc.c @@ -0,0 +1,232 @@ +/* + * V4L2 mem2mem encoders + * Copyright (C) 2014 Alexis Ballier + * + * 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 <sys/ioctl.h> + +#include "libavutil/pixfmt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/opt.h" +#include "avcodec.h" +#include "v4l2-buffers.h" +#include "v4l2-common.h" +#include "v4l2_m2m.h" +#include "v4l2_m2m_avcodec.h" + + +#define SET_V4L_EXT_CTRL(TYPE, ID, VALUE, CLASS, NAME) \ +{ \ + struct v4l2_ext_control ctrl = { 0 };\ + struct v4l2_ext_controls ctrls = { 0 };\ + ctrl.id = ID ;\ + ctrl.TYPE = VALUE ;\ + ctrls.ctrl_class = CLASS ;\ + ctrls.count = 1;\ + ctrls.controls = &ctrl;\ +\ + if( (ret = ioctl(s->fd, VIDIOC_S_EXT_CTRLS, &ctrls)) < 0)\ + av_log(avctx, AV_LOG_WARNING, "Failed to set " NAME "\n");\ +} + +static inline int v4l_h264_profile_from_ff(int p) { + switch(p) { + case FF_PROFILE_H264_BASELINE : return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; + case FF_PROFILE_H264_CONSTRAINED_BASELINE: return V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE; + case FF_PROFILE_H264_MAIN : return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; + case FF_PROFILE_H264_EXTENDED : return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED; + case FF_PROFILE_H264_HIGH : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; + case FF_PROFILE_H264_HIGH_10 : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10; + case FF_PROFILE_H264_HIGH_10_INTRA : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10_INTRA; + case FF_PROFILE_H264_HIGH_422 : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422; + case FF_PROFILE_H264_HIGH_422_INTRA : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_422_INTRA; + case FF_PROFILE_H264_HIGH_444_PREDICTIVE : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_PREDICTIVE; + case FF_PROFILE_H264_HIGH_444_INTRA : return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_444_INTRA; + } + return -1; +} + +static inline int v4l_mpeg4_profile_from_ff(int p) { + switch(p) { + case FF_PROFILE_MPEG4_SIMPLE : return V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE; + case FF_PROFILE_MPEG4_ADVANCED_SIMPLE: return V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE; + case FF_PROFILE_MPEG4_CORE : return V4L2_MPEG_VIDEO_MPEG4_PROFILE_CORE; + case FF_PROFILE_MPEG4_SIMPLE_SCALABLE: return V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE_SCALABLE; + case FF_PROFILE_MPEG4_ADVANCED_CODING: return V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY; + } + return -1; +} + +static av_cold int v4lm2m_encode_init(AVCodecContext *avctx) { + int ret, val; + V4Lm2mContext *s = avctx->priv_data; + + s->output_pool.av_pix_fmt = avctx->pix_fmt; + s->output_pool.width = avctx->width; + s->output_pool.height = avctx->height; + s->output_pool.av_codec_id = AV_CODEC_ID_RAWVIDEO; + s->output_pool_needs_format = 1; + s->output_pool_needs_init = 1; + s->output_pool.default_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + + s->capture_pool.min_queued_buffers = 1; + s->capture_pool.av_pix_fmt = AV_PIX_FMT_NONE; + s->capture_pool.av_codec_id = avctx->codec_id; + s->capture_pool_needs_format = 1; + s->capture_pool_needs_init = 1; + s->capture_pool.default_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + + if( (ret = ff_v4lm2m_codec_init(avctx)) ) + return ret; + + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_GOP_SIZE, avctx->gop_size, V4L2_CTRL_CLASS_MPEG, "gop size"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_BITRATE , avctx->bit_rate, V4L2_CTRL_CLASS_MPEG, "bit rate"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_HEADER_MODE, V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, V4L2_CTRL_CLASS_MPEG, "header mode"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_B_FRAMES, avctx->max_b_frames, V4L2_CTRL_CLASS_MPEG, "number of B-frames"); + + switch(avctx->codec_id) { + case AV_CODEC_ID_H264: + val = v4l_h264_profile_from_ff(avctx->profile); + if(val >= 0) + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_H264_PROFILE, val, V4L2_CTRL_CLASS_MPEG, "h264 profile"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_H264_MIN_QP, avctx->qmin, V4L2_CTRL_CLASS_MPEG, "minimum video quantizer scale"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_H264_MAX_QP, avctx->qmax, V4L2_CTRL_CLASS_MPEG, "maximum video quantizer scale"); + break; + case AV_CODEC_ID_MPEG4: + val = v4l_mpeg4_profile_from_ff(avctx->profile); + if(val >= 0) + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, val, V4L2_CTRL_CLASS_MPEG, "mpeg4 profile"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP, avctx->qmin, V4L2_CTRL_CLASS_MPEG, "minimum video quantizer scale"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP, avctx->qmax, V4L2_CTRL_CLASS_MPEG, "maximum video quantizer scale"); + if(avctx->flags & CODEC_FLAG_QPEL) + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_MPEG4_QPEL, 1, V4L2_CTRL_CLASS_MPEG, "qpel"); + break; + case AV_CODEC_ID_H263: + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_H263_MIN_QP, avctx->qmin, V4L2_CTRL_CLASS_MPEG, "minimum video quantizer scale"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_H263_MAX_QP, avctx->qmax, V4L2_CTRL_CLASS_MPEG, "maximum video quantizer scale"); + break; + case AV_CODEC_ID_VP8: + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_VPX_MIN_QP, avctx->qmin, V4L2_CTRL_CLASS_MPEG, "minimum video quantizer scale"); + SET_V4L_EXT_CTRL(value, V4L2_CID_MPEG_VIDEO_VPX_MAX_QP, avctx->qmax, V4L2_CTRL_CLASS_MPEG, "maximum video quantizer scale"); + break; + } + return 0; +} + + +static int v4lm2m_encode_frame(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *pict, int *got_packet) { + int ret; + V4Lm2mContext *s = avctx->priv_data; + + if(pict) { + if((ret = avpriv_v4l_enqueue_frame_or_pkt_or_buf(&s->output_pool, pict, NULL, NULL, 0)) < 0) + return ret; + + if(!s->output_pool.streamon && (ret = avpriv_set_stream_status(&s->output_pool, VIDIOC_STREAMON))) { + av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON failed on output pool\n"); + return ret; + } + if(!s->capture_pool.streamon && (ret = avpriv_set_stream_status(&s->capture_pool, VIDIOC_STREAMON))) { + av_log(avctx, AV_LOG_ERROR, "VIDIOC_STREAMON failed on capture pool\n"); + return ret; + } + } else if(s->output_pool.streamon) { + /* last frame, stop encoder */ + struct v4l2_encoder_cmd cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd = V4L2_ENC_CMD_STOP; + if((ret = ioctl(s->fd, VIDIOC_ENCODER_CMD, &cmd))) + av_log(avctx, AV_LOG_ERROR, "Failed to stop encoder (%s)\n", av_err2str(AVERROR(errno))); + /* Wait for event on output queue. 100ms seems enough. Might need tuning. */ + s->capture_pool.blocking_dequeue = 100; + /* set streamon to 0, while not entirely true, it will close soon anyway, so that we don't send the command twice */ + s->output_pool.streamon = 0; + } + + /* Need to wait for encoder before enqueuing more if output pool is almost full or on EOS */ + if(s->output_pool.num_queued >= s->output_pool.num_buffers - 2 || !pict) { + s->capture_pool.blocking_dequeue = 100; + } else { + s->capture_pool.blocking_dequeue = 0; + } + + ret = avpriv_v4l_dequeue_frame_or_pkt(&(s->capture_pool), NULL, pkt); + + if(!ret) { + *got_packet = 1; + } else if(AVERROR(EAGAIN) == ret) { + return 0; + } + + return ret; +} + + +#define OFFSET(x) offsetof(V4Lm2mContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM + +static const AVOption options[] = { + V4L_M2M_DEFAULT_OPTS, + { "num_capture_pool_buffers", + "Number of buffers in the capture pool", + OFFSET(capture_pool.num_buffers), + AV_OPT_TYPE_INT, + {.i64 = 4 }, + 4, INT_MAX, FLAGS }, + { NULL }, +}; + +#define M2MENC(NAME, LONGNAME, CODEC) \ +static const AVClass v4l2_m2m_ ## NAME ## _enc_class = {\ + .class_name = #NAME "_v4l2_m2m_encoder",\ + .item_name = av_default_item_name,\ + .option = options,\ + .version = LIBAVUTIL_VERSION_INT,\ +};\ +\ +AVCodec ff_ ## NAME ## _v4l2m2m_encoder = { \ + .name = #NAME "_v4l2m2m" ,\ + .long_name = NULL_IF_CONFIG_SMALL("V4L2 mem2mem " LONGNAME " encoder wrapper"),\ + .type = AVMEDIA_TYPE_VIDEO,\ + .id = CODEC ,\ + .priv_data_size = sizeof(V4Lm2mContext),\ + .priv_class = &v4l2_m2m_ ## NAME ##_enc_class,\ + .init = v4lm2m_encode_init,\ + .encode2 = v4lm2m_encode_frame,\ + .close = ff_v4lm2m_codec_end,\ + .capabilities = CODEC_CAP_DELAY,\ +}; + +#if CONFIG_H263_V4L2M2M_ENCODER +M2MENC(h263 , "H.263" , AV_CODEC_ID_H263 ); +#endif + +#if CONFIG_H264_V4L2M2M_ENCODER +M2MENC(h264 , "H.264" , AV_CODEC_ID_H264 ); +#endif + +#if CONFIG_MPEG4_V4L2M2M_ENCODER +M2MENC(mpeg4, "MPEG4", AV_CODEC_ID_MPEG4); +#endif + +#if CONFIG_VP8_V4L2M2M_ENCODER +M2MENC(vp8 , "VP8" , AV_CODEC_ID_VP8 ); +#endif -- 2.1.3 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel