Skip to content
Snippets Groups Projects
Select Git revision
  • 4fcbe3e3363fff03e456a69a80f3cb7f19f7c005
  • rk3288-encoder-wip default
  • master
  • v1p-20210224
4 results

libv4l-encplugin-rockchip.c

Blame
  • libv4l-encplugin-rockchip.c 29.29 KiB
    /* Copyright 2014 The Chromium OS Authors. All rights reserved.
     * Use of this source code is governed by a BSD-style license that can be
     * found in the LICENSE file.
     */
    
    #include <assert.h>
    #include <errno.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/queue.h>
    #include <sys/syscall.h>
    #include "linux/videodev2.h"
    #include "libv4l-plugin.h"
    #include "libvepu/rk_vepu_debug.h"
    #include "libvepu/rk_vepu_interface.h"
    #include "libvepu/ioctls.h"
    
    #define VLOG(log_level, str, ...) ((g_log_level >= log_level) ?			\
    	(void) fprintf(stderr, "%s: " str "\n", __func__, ##__VA_ARGS__)	\
    		: (void) 0)
    
    #define VLOG_FD(log_level, str, ...) ((g_log_level >= log_level) ?		\
    	(void) fprintf(stderr,							\
    		"%s: fd=%d. " str "\n", __func__, fd, ##__VA_ARGS__) : (void) 0)
    
    #if HAVE_VISIBILITY
    #define PLUGIN_PUBLIC __attribute__ ((__visibility__("default")))
    #else
    #define PLUGIN_PUBLIC
    #endif
    
    #define RK3288_VPU_NAME "rk3288-vpu-enc"
    #define HANTRO_VPU_NAME "hantro-vpu"
    #define DEFAULT_FRAME_RATE 30
    #define DEFAULT_BITRATE 1000000
    #define PENDING_BUFFER_QUEUE_SIZE VIDEO_MAX_FRAME
    #define round_down(x, y) ((x) - ((x) % (y)))
    
    /*
     * struct pending_buffer - A v4l2 buffer pending for QBUF.
     * @buffer:	v4l2 buffer for QBUF.
     * @planes:	plane info of v4l2 buffer.
     * @next_runtime_param:	runtime parameters like framerate, bitrate, and
     * 			keyframe for the next buffer.
     */
    struct pending_buffer {
    	struct v4l2_buffer buffer;
    	struct v4l2_plane planes[VIDEO_MAX_PLANES];
    	struct rk_vepu_runtime_param next_runtime_param;
    };
    
    /*
     * struct pending_buffer_queue - a ring buffer of pending buffers.
     * @count: the number of buffers stored in the array.
     * @front: the index of the first ready buffer.
     * @buf_array: pending buffer array.
     */
    struct pending_buffer_queue {
    	uint32_t count;
    	int32_t front;
    	struct pending_buffer buf_array[PENDING_BUFFER_QUEUE_SIZE];
    };
    
    /*
     * struct encoder_context - the context of an encoder instance.
     * @enc:	Encoder instance returned from rk_vepu_create().
     * @mutex:	The mutex to protect encoder_context.
     * @output_streamon_type:	Type of output interface when it streams on.
     * @capture_streamon_type:	Type of capture interface when it streams on.
     * @init_param:	Encoding parameters like input format, resolution, and etc.
     * 		These parameters will be passed to encoder at libvpu
     * 		initialization.
     * @runtime_param:	Runtime parameters like framerate, bitrate, and
     * 			keyframe. This is only used for receiving ext_ctrls
     * 			before streamon and pending buffer queue is empty.
     * @pending_buffers:	The pending v4l2 buffers waiting for the encoding
     *			configuration. After a previous buffer is dequeued,
     *			one buffer from the queue can be queued.
     * @can_qbuf:	Indicate that we can queue one source buffer. This is true only
     *		when the parameters to pass together with the source buffer are
     *		ready; those params are received on dequeing the previous
     *		destination buffer.
     * @v4l2_ctrls:	v4l2 controls for VIDIOC_S_EXT_CTRLS.
     * @flushing:	Indicate the encoder is flushing frame.
     * @eos_buffer:	The last buffer to be flushed. Reset to NULL after the buffer
     *		is enqueued to the driver.
     * @destination_format:	The compressed format of the encoded video.
     * @frame_width:	The width of the input frame.
     * @frame_height:	The height of the input frame.
     * @crop_width:	The width of the cropped rectangle. The value is set when
     * 		VIDIOC_S_FMT or VIDIOC_S_CROP for OUTPUT queue is called
     * 		successfully, and is used as the width of encoded video.
     * 		Note: we store the original value passed from ioctl, instead of
     * 		the value adjusted by the driver. Also, the value should be
     * 		smaller or equal than frame_width.
     * @crop_height:	The height of the croped rectangle. Other description is
     *			the same as crop_width.
     */
    struct encoder_context {
    	void *enc;
    	pthread_mutex_t mutex;
    	enum v4l2_buf_type output_streamon_type;
    	enum v4l2_buf_type capture_streamon_type;
    	struct rk_vepu_init_param init_param;
    	struct rk_vepu_runtime_param runtime_param;
    	struct pending_buffer_queue pending_buffers;
    	bool can_qbuf;
    	struct v4l2_ext_control v4l2_ctrls[MAX_NUM_GET_CONFIG_CTRLS];
    	bool flushing;
    	struct pending_buffer *eos_buffer;
    	uint32_t destination_format;
    	uint32_t frame_width;
    	uint32_t frame_height;
    	uint32_t crop_width;
    	uint32_t crop_height;
    };
    
    static void *plugin_init(int fd);
    static void plugin_close(void *dev_ops_priv);
    static int plugin_ioctl(void *dev_ops_priv, int fd, unsigned long int cmd,
    		void *arg);
    
    /* Functions to handle various ioctl. */
    static int ioctl_streamon_locked(
    	struct encoder_context *ctx, int fd, enum v4l2_buf_type *type);
    static int ioctl_streamoff_locked(
    	struct encoder_context *ctx, int fd, enum v4l2_buf_type *type);
    static int ioctl_qbuf_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_buffer *buffer);
    static int ioctl_dqbuf_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_buffer *buffer);
    static int ioctl_g_ctrl_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_control *ctrl);
    static int ioctl_s_ctrl_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_control *ctrl);
    static int ioctl_g_ext_ctrls_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_ext_controls *ext_ctrls);
    static int ioctl_s_ext_ctrls_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_ext_controls *ext_ctrls);
    static int ioctl_s_parm_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_streamparm *parms);
    static int ioctl_s_fmt_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_format *format);
    static int ioctl_s_crop_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_crop *crop);
    static int ioctl_g_crop_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_crop *crop);
    static int ioctl_reqbufs_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_requestbuffers *reqbufs);
    static int ioctl_encoder_cmd_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_encoder_cmd *argp);
    static int ioctl_queryctrl_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_queryctrl *argp);
    static int ioctl_query_ext_ctrl_locked(struct encoder_context *ctx, int fd,
    	struct v4l2_query_ext_ctrl *argp);
    
    /* Helper functions to manipulate the pending buffer queue. */
    
    static void queue_init(struct pending_buffer_queue *queue);
    static bool queue_empty(struct pending_buffer_queue *queue);
    static bool queue_full(struct pending_buffer_queue *queue);
    /* Insert a buffer to the tail of the queue. */
    static int queue_push_back(struct pending_buffer_queue *queue,
    	struct v4l2_buffer *buffer);
    /* Remove a buffer from the head of the queue. */
    static void queue_pop_front(struct pending_buffer_queue *queue);
    static struct pending_buffer *queue_front(struct pending_buffer_queue *queue);
    static struct pending_buffer *queue_back(struct pending_buffer_queue *queue);
    
    /* Returns true if the fd is Rockchip encoder device. */
    bool is_rockchip_encoder(int fd);
    /* Set encoder configuration to the driver. */
    int set_encoder_config_locked(struct encoder_context *ctx, int fd,
    	size_t num_ctrls, uint32_t ctrls_ids[],
    	void **payloads, uint32_t payload_sizes[]);
    /* QBUF a buffer from the pending buffer queue if it is not empty. */
    static int qbuf_if_pending_buffer_exists_locked(struct encoder_context *ctx,
    	int fd);
    /* Get the encoder parameters using G_FMT and initialize libvpu. */
    static int initialize_libvpu(struct encoder_context *ctx, int fd);
    /* Get the log level from the environment variable LIBV4L_PLUGIN_LOG_LEVEL. */
    static void get_log_level();
    static pthread_once_t g_get_log_level_once = PTHREAD_ONCE_INIT;
    
    static void *plugin_init(int fd)
    {
    	int ret;
    
    	pthread_once(&g_get_log_level_once, get_log_level);
    
    	VLOG_FD(1, "");
    	if (!is_rockchip_encoder(fd))
    		return NULL;
    
    	struct encoder_context *ctx = (struct encoder_context *)
    		calloc(1, sizeof(struct encoder_context));
    	if (ctx == NULL) {
    		errno = ENOMEM;
    		return NULL;
    	}
    	ret = pthread_mutex_init(&ctx->mutex, NULL);
    	if (ret) {
    		free(ctx);
    		return NULL;
    	}
    	queue_init(&ctx->pending_buffers);
    
    	ctx->runtime_param.framerate_numer = DEFAULT_FRAME_RATE;
    	ctx->runtime_param.framerate_denom = 1;
    	ctx->runtime_param.bitrate = DEFAULT_BITRATE;
    	ctx->init_param.h264e.v4l2_h264_profile = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE;
    	ctx->init_param.h264e.v4l2_h264_level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
    	ctx->init_param.h264e.h264_sps_pps_before_idr = true;
    
    	VLOG_FD(1, "Success. ctx=%p", ctx);
    	return ctx;
    
    	plugin_close(ctx);
    	return NULL;
    }
    
    static void plugin_close(void *dev_ops_priv)
    {
    	struct encoder_context *ctx = (struct encoder_context *)dev_ops_priv;
    
    	VLOG(1, "ctx=%p", ctx);
    	if (ctx == NULL)
    		return;
    
    	pthread_mutex_lock(&ctx->mutex);
    	if (ctx->enc)
    		rk_vepu_deinit(ctx->enc);
    	pthread_mutex_unlock(&ctx->mutex);
    	pthread_mutex_destroy(&ctx->mutex);
    
    	free(ctx);
    }
    
    static int plugin_ioctl(void *dev_ops_priv, int fd,
    			unsigned long int cmd, void *arg)
    {
    	int ret;
    	struct encoder_context *ctx = (struct encoder_context *)dev_ops_priv;
    
    	VLOG_FD(2, "%s(%lu)", v4l_cmd2str(cmd), _IOC_NR(cmd));
    
    	pthread_mutex_lock(&ctx->mutex);
    	switch (cmd) {
    	case VIDIOC_STREAMON:
    		ret = ioctl_streamon_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_STREAMOFF:
    		ret = ioctl_streamoff_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_QBUF:
    		ret = ioctl_qbuf_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_DQBUF:
    		ret = ioctl_dqbuf_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_G_CTRL:
    		ret = ioctl_g_ctrl_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_S_CTRL:
    		ret = ioctl_s_ctrl_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_G_EXT_CTRLS:
    		ret = ioctl_g_ext_ctrls_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_S_EXT_CTRLS:
    		ret = ioctl_s_ext_ctrls_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_S_PARM:
    		ret = ioctl_s_parm_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_S_FMT:
    		ret = ioctl_s_fmt_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_S_CROP:
    		ret = ioctl_s_crop_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_G_CROP:
    		ret = ioctl_g_crop_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_REQBUFS:
    		ret = ioctl_reqbufs_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_ENCODER_CMD:
    		ret = ioctl_encoder_cmd_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_QUERYCTRL:
    		ret = ioctl_queryctrl_locked(ctx, fd, arg);
    		break;
    
    	case VIDIOC_QUERY_EXT_CTRL:
    		ret = ioctl_query_ext_ctrl_locked (ctx, fd, arg);
    		break;
    
    	default:
    		ret = SYS_IOCTL(fd, cmd, arg);
    		break;
    	}
    	pthread_mutex_unlock(&ctx->mutex);
    	return ret;
    }
    
    static int ioctl_streamon_locked(
    		struct encoder_context *ctx, int fd, enum v4l2_buf_type *type)
    {
    	int ret = SYS_IOCTL(fd, VIDIOC_STREAMON, type);
    	if (ret)
    		return ret;
    
    	if (V4L2_TYPE_IS_OUTPUT(*type))
    		ctx->output_streamon_type = *type;
    	else
    		ctx->capture_streamon_type = *type;
    	if (ctx->output_streamon_type && ctx->capture_streamon_type) {
    		ret = initialize_libvpu(ctx, fd);
    		if (ret)
    			return ret;
    		ctx->can_qbuf = true;
    		return qbuf_if_pending_buffer_exists_locked(ctx, fd);
    	}
    	return 0;
    }
    
    static int ioctl_streamoff_locked(
    		struct encoder_context *ctx, int fd, enum v4l2_buf_type *type)
    {
    	int ret = SYS_IOCTL(fd, VIDIOC_STREAMOFF, type);
    	if (ret)
    		return ret;
    
    	if (V4L2_TYPE_IS_OUTPUT(*type))
    		ctx->output_streamon_type = 0;
    	else
    		ctx->capture_streamon_type = 0;
    	return 0;
    }
    
    static int ioctl_qbuf_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_buffer *buffer)
    {
    	size_t num_ctrls = 0;
    	uint32_t *ctrl_ids = NULL, *payload_sizes = NULL;
    	void **payloads = NULL;
    	int ret;
    
    	if (!V4L2_TYPE_IS_OUTPUT(buffer->type)) {
    		return SYS_IOCTL(fd, VIDIOC_QBUF, buffer);
    	}
    
    	if (!ctx->can_qbuf) {
    		VLOG_FD(1, "Put buffer (%d) in the pending queue.",
    			buffer->index);
    		/*
    		 * The last frame is not encoded yet. Put the buffer to the
    		 * pending queue.
    		 */
    		return queue_push_back(&ctx->pending_buffers, buffer);
    	}
    	/* Get the encoder configuration from the library. */
    	if (rk_vepu_get_config(ctx->enc, &num_ctrls, &ctrl_ids, &payloads,
    			&payload_sizes)) {
    		VLOG_FD(0, "rk_vepu_get_config failed");
    		return -EIO;
    	}
    	/* Set the encoder configuration to the driver. */
    	ret = set_encoder_config_locked(ctx, fd, num_ctrls, ctrl_ids,
    			payloads, payload_sizes);
    	if (ret)
    		return ret;
    
    	ret = SYS_IOCTL(fd, VIDIOC_QBUF, buffer);
    	if (ret == 0)
    		ctx->can_qbuf = false;
    	else
    		VLOG_FD(0, "QBUF failed. errno=%d", errno);
    	return ret;
    }
    
    static int ioctl_dqbuf_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_buffer *buffer)
    {
    	int ret;
    	uint32_t bytesused;
    
    	if (V4L2_TYPE_IS_OUTPUT(buffer->type)) {
    		return SYS_IOCTL(fd, VIDIOC_DQBUF, buffer);
    	}
    
    	ret = SYS_IOCTL(fd, VIDIOC_DQBUF, buffer);
    	if (ret) {
    		/* In some cases we may not have a buffer with the V4L2_BUF_FLAG_LAST set,
    		 * make sure to reset our flushing state */
    		if (errno == EPIPE) {
    			ctx->flushing = false;
    			ctx->can_qbuf = true;
    		}
    		return ret;
    	}
    
    	ret = SYS_IOCTL(fd, VIDIOC_QUERYBUF, buffer);
    	if (ret)
    		return ret;
    	/* Get the encoder configuration and update the library.
    	 * Because we dequeue a null frame to indicate flush is finished, do not
    	 * update the config with this frame. */
    	bytesused = V4L2_TYPE_IS_MULTIPLANAR(buffer->type) ?
    		buffer->m.planes[0].bytesused : buffer->bytesused;
    	if (bytesused) {
    		rk_vepu_assemble_bitstream(ctx->enc, fd, buffer);
    
    		if (rk_vepu_update_config(ctx->enc, fd, bytesused)) {
    			VLOG_FD(0, "rk_vepu_update_config failed.");
    			return -EIO;
    		}
    	}
    
    	/* When flushing buffer, we don't queue any new buffer.
    	 * So we ignore checking can_qbuf and trying to qbuf new buffer. */
    	assert(ctx->flushing || !ctx->can_qbuf);
    	if (buffer->flags & V4L2_BUF_FLAG_LAST)
    		ctx->flushing = false;
    	if (ctx->flushing)
    		return 0;
    	ctx->can_qbuf = true;
    	return qbuf_if_pending_buffer_exists_locked(ctx, fd);
    }
    
    static int ioctl_g_ctrl_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_control *ctrl)
    {
    	switch (ctrl->id) {
    	case V4L2_CID_MPEG_VIDEO_BITRATE:
    		ctrl->value = ctx->runtime_param.bitrate;
    		return 0;
    	case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
    		ctrl->value = ctx->init_param.h264e.h264_sps_pps_before_idr;
    		return 0;
    	default:
    		return SYS_IOCTL(fd, VIDIOC_G_CTRL, ctrl);
    		break;
    	}
    }
    
    static int ioctl_s_ctrl_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_control *ctrl)
    {
    	struct rk_vepu_runtime_param *runtime_param_ptr;
    
    	bool no_pending_buffer = queue_empty(&ctx->pending_buffers);
    	/*
    	 * If buffer queue is empty, update parameters directly.
    	 * If buffer queue is not empty, save parameters to the last buffer. And
    	 * these values will be sent again when the buffer is ready to deliver.
    	 */
    	if (!no_pending_buffer) {
    		struct pending_buffer *element = queue_back(&ctx->pending_buffers);
    		runtime_param_ptr = &element->next_runtime_param;
    	} else {
    		runtime_param_ptr = &ctx->runtime_param;
    	}
    
    	/*
    	 * Only support init_param for now.
    	 */
    	switch (ctrl->id) {
    	case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME:
    		runtime_param_ptr->keyframe_request = true;
    		return 0;
    	case V4L2_CID_MPEG_VIDEO_BITRATE:
    		VLOG_FD(2, "Setting bitrate to %d", ctrl->value);
    		runtime_param_ptr->bitrate = ctrl->value;
    		return 0;
    	case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
    		ctx->init_param.h264e.h264_sps_pps_before_idr = ctrl->value;
    		return 0;
    	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
    		VLOG_FD(2, "setting H264 profile %d.", ctrl->value);
    		ctx->init_param.h264e.v4l2_h264_profile = ctrl->value;
    		break;
    	case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
    		ctx->init_param.h264e.v4l2_h264_level = ctrl->value;
    		break;
    	case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE:
    		ctx->init_param.hevce.v4l2_hevc_profile = ctrl->value;
    		break;
    	case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL:
    		ctx->init_param.hevce.v4l2_hevc_level = ctrl->value;
    		break;
    	default:
    		break;
    	}
    
    	return SYS_IOCTL(fd, VIDIOC_S_CTRL, ctrl);
    }
    
    static int ioctl_g_ext_ctrls_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_ext_controls *ext_ctrls)
    {
    	unsigned int i;
    	bool success = false;
    	int ret;
    
    	for (i = 0; i < ext_ctrls->count; i++) {
    		switch (ext_ctrls->controls[i].id) {
    		case V4L2_CID_MPEG_VIDEO_BITRATE:
    			ext_ctrls->controls[i].value = ctx->runtime_param.bitrate;
    			success = true;
    			break;
    		case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
    			ext_ctrls->controls[i].value = ctx->init_param.h264e.h264_sps_pps_before_idr;
    			success = true;
    			break;
    		default:
    			break;
    		}
    	}
    
    	ret = SYS_IOCTL(fd, VIDIOC_G_EXT_CTRLS, ext_ctrls);
    
    	if (success)
    		ret = 0;
    
    	return ret;
    }
    
    static int ioctl_s_ext_ctrls_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_ext_controls *ext_ctrls)
    {
    	size_t i;
    	struct rk_vepu_runtime_param *runtime_param_ptr;
    
    	bool no_pending_buffer = queue_empty(&ctx->pending_buffers);
    	/*
    	 * If buffer queue is empty, update parameters directly.
    	 * If buffer queue is not empty, save parameters to the last buffer. And
    	 * these values will be sent again when the buffer is ready to deliver.
    	 */
    	if (!no_pending_buffer) {
    		struct pending_buffer *element = queue_back(&ctx->pending_buffers);
    		runtime_param_ptr = &element->next_runtime_param;
    	} else {
    		runtime_param_ptr = &ctx->runtime_param;
    	}
    
    	/*
    	 * Check each extension control to update keyframe and bitrate
    	 * parameters.
    	 */
    	for (i = 0; i < ext_ctrls->count; i++) {
    		switch (ext_ctrls->controls[i].id) {
    		case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME:
    			runtime_param_ptr->keyframe_request = true;
    			break;
    		case V4L2_CID_MPEG_VIDEO_BITRATE:
    			runtime_param_ptr->bitrate = ext_ctrls->controls[i].value;
    			break;
    		case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR:
    			ctx->init_param.h264e.h264_sps_pps_before_idr =
    				ext_ctrls->controls[i].value;
    			break;
    		case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
    			ctx->init_param.h264e.v4l2_h264_profile =
    				ext_ctrls->controls[i].value;
    			break;
    		case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
    			ctx->init_param.h264e.v4l2_h264_level =
    				ext_ctrls->controls[i].value;
    			break;
    		case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE:
    			ctx->init_param.hevce.v4l2_hevc_profile = ext_ctrls->controls[i].value;
    			break;
    		case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL:
    			ctx->init_param.hevce.v4l2_hevc_level = ext_ctrls->controls[i].value;
    			break;
    		default:
    			break;
    		}
    	}
    
    	if (no_pending_buffer && ctx->enc) {
    		if (rk_vepu_update_param(ctx->enc, runtime_param_ptr)) {
    			VLOG_FD(0, "rk_vepu_update_param failed.");
    			return -EIO;
    		}
    		memset(runtime_param_ptr, 0, sizeof(*runtime_param_ptr));
    	}
    	/* Driver should ignore keyframe and bitrate controls */
    	return SYS_IOCTL(fd, VIDIOC_S_EXT_CTRLS, ext_ctrls);
    }
    
    static int ioctl_s_parm_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_streamparm *parms)
    {
    	if (V4L2_TYPE_IS_OUTPUT(parms->type)
    			&& parms->parm.output.timeperframe.denominator) {
    		struct rk_vepu_runtime_param *runtime_param_ptr;
    		bool no_pending_buffer = queue_empty(&ctx->pending_buffers);
    		struct pending_buffer *element = queue_back(&ctx->pending_buffers);
    
    		runtime_param_ptr = no_pending_buffer ? &ctx->runtime_param :
    			&element->next_runtime_param;
    		runtime_param_ptr->framerate_numer =
    			parms->parm.output.timeperframe.denominator;
    		runtime_param_ptr->framerate_denom =
    			parms->parm.output.timeperframe.numerator;
    
    		if (!no_pending_buffer || !ctx->enc)
    			return 0;
    		if (rk_vepu_update_param(ctx->enc, runtime_param_ptr)) {
    			VLOG_FD(0, "rk_vepu_update_param failed.");
    			return -EIO;
    		}
    		memset(runtime_param_ptr, 0, sizeof(*runtime_param_ptr));
    		return 0;
    	}
    	return SYS_IOCTL(fd, VIDIOC_S_PARM, parms);
    }
    
    static int ioctl_s_fmt_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_format *format)
    {
    	uint32_t width = format->fmt.pix_mp.width;
    	uint32_t height = format->fmt.pix_mp.height;
    	int ret = SYS_IOCTL(fd, VIDIOC_S_FMT, format);
    	if (ret)
    		return ret;
    	if (!V4L2_TYPE_IS_OUTPUT(format->type)) {
    		ctx->destination_format = format->fmt.pix_mp.pixelformat;
    		return 0;
    	}
    
    	/* The width and height of H264 video should be even. */
    	if (ctx->destination_format == V4L2_PIX_FMT_H264) {
    		width = round_down(width, 2);
    		height = round_down(height, 2);
    	}
    	ctx->frame_width = width;
    	ctx->frame_height = height;
    	ctx->crop_width = width;
    	ctx->crop_height = height;
    	return 0;
    }
    
    static int ioctl_s_crop_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_crop *crop)
    {
    	/*
    	 * We do not support offsets, and the crop size should not be
    	 * bigger than the frame size. In this case, we reset the crop
    	 * size to full frame size.
    	 */
    	if (crop->c.left != 0 || crop->c.top != 0 ||
    			crop->c.width > ctx->frame_width ||
    			crop->c.height > ctx->frame_height) {
    		crop->c.left = 0;
    		crop->c.top = 0;
    		crop->c.width = ctx->frame_width;
    		crop->c.height = ctx->frame_height;
    	}
    
    	uint32_t width = crop->c.width;
    	uint32_t height = crop->c.height;
    	int ret = SYS_IOCTL(fd, VIDIOC_S_CROP, crop);
    	if (ret || !V4L2_TYPE_IS_OUTPUT(crop->type))
    		return ret;
    
    	/* The width and height of H264 video should be even. */
    	if (ctx->destination_format == V4L2_PIX_FMT_H264) {
    		width = round_down(width, 2);
    		height = round_down(height, 2);
    	}
    	ctx->crop_width = width;
    	ctx->crop_height = height;
    	return 0;
    }
    
    static int ioctl_g_crop_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_crop *crop)
    {
    	int ret = SYS_IOCTL(fd, VIDIOC_G_CROP, crop);
    	if (ret || !V4L2_TYPE_IS_OUTPUT(crop->type))
    		return ret;
    
    	crop->c.width = ctx->crop_width;
    	crop->c.height = ctx->crop_height;
    	return 0;
    }
    
    static int ioctl_reqbufs_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_requestbuffers *reqbufs)
    {
    	int ret = SYS_IOCTL(fd, VIDIOC_REQBUFS, reqbufs);
    	if (ret)
    		return ret;
    	queue_init(&ctx->pending_buffers);
    	return 0;
    }
    
    static int ioctl_encoder_cmd_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_encoder_cmd *argp)
    {
    	if (argp->cmd == V4L2_ENC_CMD_STOP) {
    		if (ctx->flushing) {
    			VLOG_FD(0, "previous stop command is not handled yet.");
    			return -EINVAL;
    		}
    		ctx->flushing = true;
    		/* If there is any pending buffer, then send the stop command
    		 * after we enqueue the last buffer. Otherwise, we just send
    		 * the command to the kernel instantly. */
    		if (!queue_empty(&ctx->pending_buffers)) {
    			VLOG_FD(1, "queueing stop command.");
    			ctx->eos_buffer = queue_back(&ctx->pending_buffers);
    			return 0;
    		}
    	}
    	VLOG_FD(1, "sending stop command.");
    	return SYS_IOCTL(fd, VIDIOC_ENCODER_CMD, argp);
    }
    
    static bool match_cid (__u32 prev_cid, __u32 next_cid, __u32 cid)
    {
    	return prev_cid < cid && cid < next_cid;
    }
    
    static void set_bitrate_queryctrl (struct v4l2_queryctrl *argp)
    {
    	argp->id = V4L2_CID_MPEG_VIDEO_BITRATE;
    	argp->type = V4L2_CTRL_TYPE_INTEGER;
    	argp->minimum = 1;
    	argp->maximum = INT32_MAX;
    	argp->default_value = DEFAULT_BITRATE;
    	argp->step = 1;
    
    	strncpy((char*)argp->name, "Video Bitrate", sizeof (argp->name));
    	argp->name[sizeof(argp->name) - 1] = '\0';
    }
    
    static void set_bitrate_query_ext_ctrl (struct v4l2_query_ext_ctrl *argp)
    {
    	argp->id = V4L2_CID_MPEG_VIDEO_BITRATE;
    	argp->type = V4L2_CTRL_TYPE_INTEGER;
    	argp->minimum = 1;
    	argp->maximum = INT32_MAX;
    	argp->default_value = DEFAULT_BITRATE;
    	argp->step = 1;
    
    	strncpy((char*)argp->name, "Video Bitrate", sizeof (argp->name));
    	argp->name[sizeof(argp->name) - 1] = '\0';
    }
    
    static int ioctl_queryctrl_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_queryctrl *argp)
    {
    	if (argp->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
    		int ret;
    		__u32 prev_id, next_id = UINT32_MAX;
    
    		prev_id = argp->id & V4L2_CTRL_ID_MASK;
    		ret = SYS_IOCTL(fd, VIDIOC_QUERYCTRL, argp);
    
    		if (ret == 0)
    			next_id = argp->id;
    
    		if (match_cid (prev_id, next_id, V4L2_CID_MPEG_VIDEO_BITRATE)) {
    			set_bitrate_queryctrl (argp);
    			ret = 0;
    		}
    
    		return ret;
    	} else {
    		if (argp->id == V4L2_CID_MPEG_VIDEO_BITRATE) {
    			set_bitrate_queryctrl (argp);
    			return 0;
    		}
    		return SYS_IOCTL(fd, VIDIOC_QUERYCTRL, argp);
    	}
    }
    
    static int ioctl_query_ext_ctrl_locked(struct encoder_context *ctx, int fd,
    		struct v4l2_query_ext_ctrl *argp)
    {
    	if (argp->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
    		int ret;
    		__u32 prev_id, next_id = UINT32_MAX;
    
    		prev_id = argp->id & V4L2_CTRL_ID_MASK;
    		ret = SYS_IOCTL(fd, VIDIOC_QUERY_EXT_CTRL, argp);
    
    		if (ret == 0)
    			next_id = argp->id;
    
    		if (match_cid (prev_id, next_id, V4L2_CID_MPEG_VIDEO_BITRATE)) {
    			set_bitrate_query_ext_ctrl (argp);
    			ret = 0;
    		}
    
    		return ret;
    	} else {
    		if (argp->id == V4L2_CID_MPEG_VIDEO_BITRATE) {
    			set_bitrate_query_ext_ctrl (argp);
    			return 0;
    		}
    		return SYS_IOCTL(fd, VIDIOC_QUERY_EXT_CTRL, argp);
    	}
    }
    
    bool is_rockchip_encoder(int fd) {
    	struct v4l2_capability cap;
    	memset(&cap, 0, sizeof(cap));
    	int ret = SYS_IOCTL(fd, VIDIOC_QUERYCAP, &cap);
    	if (ret)
    		return false;
    	VLOG_FD(1, "driver name return %s\n", (char*)cap.card);
    	return strcmp(RK3288_VPU_NAME, (const char *)cap.driver) == 0 ||
    	       strcmp(HANTRO_VPU_NAME, (const char *)cap.driver) == 0;
    }
    
    int set_encoder_config_locked(struct encoder_context *ctx, int fd,
    		size_t num_ctrls, uint32_t ctrl_ids[],
    		void **payloads, uint32_t payload_sizes[])
    {
    	size_t i;
    	struct v4l2_ext_controls ext_ctrls;
    
    	if (num_ctrls <= 0)
    		return 0;
    
    	assert(num_ctrls <= MAX_NUM_GET_CONFIG_CTRLS);
    	if (num_ctrls > MAX_NUM_GET_CONFIG_CTRLS) {
    		VLOG_FD(0, "The number of controls exceeds limit.");
    		return -EIO;
    	}
    	memset(&ext_ctrls, 0, sizeof(ext_ctrls));
    	ext_ctrls.count = num_ctrls;
    	ext_ctrls.controls = ctx->v4l2_ctrls;
    	memset(ctx->v4l2_ctrls, 0, sizeof(ctx->v4l2_ctrls));
    	for (i = 0; i < num_ctrls; ++i) {
    		ctx->v4l2_ctrls[i].id = ctrl_ids[i];
    		ctx->v4l2_ctrls[i].ptr = payloads[i];
    		ctx->v4l2_ctrls[i].size = payload_sizes[i];
    	}
    	int ret = SYS_IOCTL(fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls);
    	if (ret) {
    		return ret;
    	}
    	return 0;
    }
    
    static int qbuf_if_pending_buffer_exists_locked(struct encoder_context *ctx,
    		int fd)
    {
    	if (!queue_empty(&ctx->pending_buffers)) {
    		int ret;
    		struct pending_buffer *element = queue_front(&ctx->pending_buffers);
    		VLOG_FD(1, "QBUF a buffer (%d) from the pending queue.",
    			element->buffer.index);
    		if (rk_vepu_update_param(ctx->enc, &element->next_runtime_param)) {
    			VLOG_FD(0, "rk_vepu_update_param failed.");
    			return -EIO;
    		}
    		memset(&element->next_runtime_param, 0,
    			sizeof(element->next_runtime_param));
    		ret = ioctl_qbuf_locked(ctx, fd, &element->buffer);
    		if (ret)
    			return ret;
    		queue_pop_front(&ctx->pending_buffers);
    		/* QBUF the last buffer to be flushed, send stop command. */
    		if (element == ctx->eos_buffer) {
    			ctx->eos_buffer = NULL;
    			struct v4l2_encoder_cmd argp = {
    				.cmd = V4L2_ENC_CMD_STOP,
    			};
    			VLOG_FD(1, "sending stop command.");
    			return SYS_IOCTL(fd, VIDIOC_ENCODER_CMD, &argp);
    		}
    	}
    	return 0;
    }
    
    static int initialize_libvpu(struct encoder_context *ctx, int fd)
    {
    	struct rk_vepu_init_param init_param;
    	memset(&init_param, 0, sizeof(init_param));
    
    	/* Get the source format. */
    	struct v4l2_format format;
    	memset(&format, 0, sizeof(format));
    	format.type = ctx->output_streamon_type;
    	int ret = SYS_IOCTL(fd, VIDIOC_G_FMT, &format);
    	if (ret)
    		return ret;
    	init_param.source_format = format.fmt.pix_mp.pixelformat;
    
    	/* Get the destination format. */
    	memset(&format, 0, sizeof(format));
    	format.type = ctx->capture_streamon_type;
    	ret = SYS_IOCTL(fd, VIDIOC_G_FMT, &format);
    	if (ret)
    		return ret;
    	init_param.destination_format = format.fmt.pix_mp.pixelformat;
    	init_param.width = ctx->crop_width;
    	init_param.height = ctx->crop_height;
    
    	switch (init_param.destination_format) {
    	case V4L2_PIX_FMT_H264:
    		init_param.h264e = ctx->init_param.h264e;
    		break;
    	case V4L2_PIX_FMT_HEVC:
    		init_param.hevce = ctx->init_param.hevce;
    		break;
    	default:
    		;
    	}
    
    	/*
    	 * If the encoder library has initialized and parameters have not
    	 * changed, skip the initialization.
    	 */
    	if (ctx->enc && memcmp(&init_param, &ctx->init_param, sizeof(init_param))) {
    		rk_vepu_deinit(ctx->enc);
    		ctx->enc = NULL;
    	}
    	if (!ctx->enc) {
    		memcpy(&ctx->init_param, &init_param, sizeof(init_param));
    		ctx->enc = rk_vepu_init(&init_param);
    		if (ctx->enc == NULL) {
    			VLOG_FD(0, "Failed to initialize encoder library.");
    			return -EIO;
    		}
    	}
    	if (rk_vepu_update_param(ctx->enc, &ctx->runtime_param)) {
    		VLOG_FD(0, "rk_vepu_update_param failed.");
    		return -EIO;
    	}
    	memset(&ctx->runtime_param, 0, sizeof(struct rk_vepu_runtime_param));
    	return 0;
    }
    
    static void queue_init(struct pending_buffer_queue *queue)
    {
    	memset(queue, 0, sizeof(struct pending_buffer_queue));
    }
    
    static bool queue_empty(struct pending_buffer_queue *queue)
    {
    	return queue->count == 0;
    }
    
    static bool queue_full(struct pending_buffer_queue *queue)
    {
    	return queue->count == PENDING_BUFFER_QUEUE_SIZE;
    }
    
    static int queue_push_back(struct pending_buffer_queue *queue,
    		struct v4l2_buffer *buffer)
    {
    	if (queue_full(queue))
    	  return -ENOMEM;
    	int rear = (queue->front + queue->count) % PENDING_BUFFER_QUEUE_SIZE;
    	queue->count++;
    	struct pending_buffer *entry = &queue->buf_array[rear];
    	memset(entry, 0, sizeof(*entry));
    
    	entry->buffer = *buffer;
    	if (V4L2_TYPE_IS_MULTIPLANAR(buffer->type)) {
    		memcpy(entry->planes, buffer->m.planes,
    			sizeof(struct v4l2_plane) * buffer->length);
    		entry->buffer.m.planes = entry->planes;
    	}
    	return 0;
    }
    
    static void queue_pop_front(struct pending_buffer_queue *queue)
    {
    	assert(!queue_empty(queue));
    	queue->count--;
    	queue->front = (queue->front + 1) % PENDING_BUFFER_QUEUE_SIZE;
    }
    
    static struct pending_buffer *queue_front(struct pending_buffer_queue *queue)
    {
    	if (queue_empty(queue))
    		return NULL;
    	return &queue->buf_array[queue->front];
    }
    
    static struct pending_buffer *queue_back(struct pending_buffer_queue *queue)
    {
    	if (queue_empty(queue))
    		return NULL;
    	return &queue->buf_array[(queue->front + queue->count - 1) %
    		PENDING_BUFFER_QUEUE_SIZE];
    }
    
    static void get_log_level()
    {
    	char *log_level_str = getenv("LIBV4L_PLUGIN_LOG_LEVEL");
    	if (log_level_str != NULL)
    		g_log_level = strtol(log_level_str, NULL, 10);
    }
    
    PLUGIN_PUBLIC const struct libv4l_dev_ops libv4l2_plugin = {
    	.init = &plugin_init,
    	.close = &plugin_close,
    	.ioctl = &plugin_ioctl,
    };