// SPDX-License-Identifier: GPL-2.0+ /* Encoder for virtio video device. * * Copyright 2020 OpenSynergy GmbH. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "virtio_video.h" static int virtio_video_enc_start_streaming(struct vb2_queue *vq, unsigned int count) { struct virtio_video_stream *stream = vb2_get_drv_priv(vq); bool input_queue = V4L2_TYPE_IS_OUTPUT(vq->type); if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (virtio_video_state(stream) == STREAM_STATE_INIT || (!input_queue && virtio_video_state(stream) == STREAM_STATE_RESET) || (input_queue && virtio_video_state(stream) == STREAM_STATE_STOPPED)) virtio_video_state_update(stream, STREAM_STATE_RUNNING); return 0; } static void virtio_video_enc_stop_streaming(struct vb2_queue *vq) { int ret, queue_type; struct virtio_video_stream *stream = vb2_get_drv_priv(vq); if (V4L2_TYPE_IS_OUTPUT(vq->type)) queue_type = VIRTIO_VIDEO_QUEUE_TYPE_INPUT; else queue_type = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT; ret = virtio_video_queue_release_buffers(stream, queue_type); if (ret) return; vb2_wait_for_all_buffers(vq); if (V4L2_TYPE_IS_OUTPUT(vq->type)) virtio_video_state_update(stream, STREAM_STATE_STOPPED); else virtio_video_state_update(stream, STREAM_STATE_RESET); } static const struct vb2_ops virtio_video_enc_qops = { .queue_setup = virtio_video_queue_setup, .buf_init = virtio_video_buf_init, .buf_cleanup = virtio_video_buf_cleanup, .buf_queue = virtio_video_buf_queue, .start_streaming = virtio_video_enc_start_streaming, .stop_streaming = virtio_video_enc_stop_streaming, .wait_prepare = vb2_ops_wait_prepare, .wait_finish = vb2_ops_wait_finish, }; static int virtio_video_enc_s_ctrl(struct v4l2_ctrl *ctrl) { int ret = 0; struct virtio_video_stream *stream = ctrl2stream(ctrl); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); uint32_t control, value; if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; control = virtio_video_v4l2_control_to_virtio(ctrl->id); switch (ctrl->id) { case V4L2_CID_MPEG_VIDEO_BITRATE: ret = virtio_video_cmd_set_control(vvd, stream->stream_id, control, ctrl->val); break; case V4L2_CID_MPEG_VIDEO_H264_LEVEL: value = virtio_video_v4l2_level_to_virtio(ctrl->val); ret = virtio_video_cmd_set_control(vvd, stream->stream_id, control, value); break; case V4L2_CID_MPEG_VIDEO_H264_PROFILE: value = virtio_video_v4l2_profile_to_virtio(ctrl->val); ret = virtio_video_cmd_set_control(vvd, stream->stream_id, control, value); break; default: ret = -EINVAL; break; } return ret; } static int virtio_video_enc_g_ctrl(struct v4l2_ctrl *ctrl) { int ret = 0; struct virtio_video_stream *stream = ctrl2stream(ctrl); if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; switch (ctrl->id) { case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: if (virtio_video_state(stream) >= STREAM_STATE_INIT) ctrl->val = stream->in_info.min_buffers; else ctrl->val = 0; break; default: ret = -EINVAL; break; } return ret; } static const struct v4l2_ctrl_ops virtio_video_enc_ctrl_ops = { .g_volatile_ctrl = virtio_video_enc_g_ctrl, .s_ctrl = virtio_video_enc_s_ctrl, }; int virtio_video_enc_init_ctrls(struct virtio_video_stream *stream) { struct v4l2_ctrl *ctrl; struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct video_control_format *c_fmt = NULL; v4l2_ctrl_handler_init(&stream->ctrl_handler, 1); ctrl = v4l2_ctrl_new_std(&stream->ctrl_handler, &virtio_video_enc_ctrl_ops, V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, MIN_BUFS_MIN, MIN_BUFS_MAX, MIN_BUFS_STEP, MIN_BUFS_DEF); if (ctrl) ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; list_for_each_entry(c_fmt, &vvd->controls_fmt_list, controls_list_entry) { switch (c_fmt->format) { case V4L2_PIX_FMT_H264: if (c_fmt->profile) v4l2_ctrl_new_std_menu (&stream->ctrl_handler, &virtio_video_enc_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_PROFILE, c_fmt->profile->max, c_fmt->profile->skip_mask, c_fmt->profile->min); if (c_fmt->level) v4l2_ctrl_new_std_menu (&stream->ctrl_handler, &virtio_video_enc_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, c_fmt->level->max, c_fmt->level->skip_mask, c_fmt->level->min); break; default: v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, "unsupported format\n"); break; } } if (stream->control.bitrate) { v4l2_ctrl_new_std(&stream->ctrl_handler, &virtio_video_enc_ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE, 1, S32_MAX, 1, stream->control.bitrate); } if (stream->ctrl_handler.error) return stream->ctrl_handler.error; v4l2_ctrl_handler_setup(&stream->ctrl_handler); return 0; } int virtio_video_enc_init_queues(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { int ret; struct virtio_video_stream *stream = priv; struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct device *dev = vvd->v4l2_dev.dev; src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; src_vq->io_modes = VB2_MMAP | VB2_DMABUF; src_vq->drv_priv = stream; src_vq->buf_struct_size = sizeof(struct virtio_video_buffer); src_vq->ops = &virtio_video_enc_qops; src_vq->mem_ops = virtio_video_mem_ops(vvd); src_vq->min_buffers_needed = stream->in_info.min_buffers; src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; src_vq->lock = &stream->vq_mutex; src_vq->gfp_flags = virtio_video_gfp_flags(vvd); src_vq->dev = dev; ret = vb2_queue_init(src_vq); if (ret) return ret; dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; dst_vq->drv_priv = stream; dst_vq->buf_struct_size = sizeof(struct virtio_video_buffer); dst_vq->ops = &virtio_video_enc_qops; dst_vq->mem_ops = virtio_video_mem_ops(vvd); dst_vq->min_buffers_needed = stream->out_info.min_buffers; dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; dst_vq->lock = &stream->vq_mutex; dst_vq->gfp_flags = virtio_video_gfp_flags(vvd); dst_vq->dev = dev; return vb2_queue_init(dst_vq); } static int virtio_video_try_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *cmd) { struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = video_drvdata(file); if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (virtio_video_state(stream) == STREAM_STATE_DRAIN) return -EBUSY; switch (cmd->cmd) { case V4L2_ENC_CMD_STOP: case V4L2_ENC_CMD_START: if (cmd->flags != 0) { v4l2_err(&vvd->v4l2_dev, "flags=%u are not supported", cmd->flags); return -EINVAL; } break; default: return -EINVAL; } return 0; } static int virtio_video_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *cmd) { int ret; struct vb2_queue *src_vq, *dst_vq; struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = video_drvdata(file); ret = virtio_video_try_encoder_cmd(file, fh, cmd); if (ret < 0) return ret; dst_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); switch (cmd->cmd) { case V4L2_ENC_CMD_START: vb2_clear_last_buffer_dequeued(dst_vq); virtio_video_state_update(stream, STREAM_STATE_RUNNING); break; case V4L2_ENC_CMD_STOP: src_vq = v4l2_m2m_get_vq(stream->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); if (!vb2_is_streaming(src_vq)) { v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, "output is not streaming\n"); return 0; } if (!vb2_is_streaming(dst_vq)) { v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, "capture is not streaming\n"); return 0; } ret = virtio_video_cmd_stream_drain(vvd, stream->stream_id); if (ret) { v4l2_err(&vvd->v4l2_dev, "failed to drain stream\n"); return ret; } virtio_video_state_update(stream, STREAM_STATE_DRAIN); break; default: return -EINVAL; } return 0; } static int virtio_video_enc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct video_format *fmt; int idx = 0; if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; if (f->index >= vvd->num_output_fmts) return -EINVAL; list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { if (f->index == idx) { f->pixelformat = fmt->desc.format; return 0; } idx++; } return -EINVAL; } static int virtio_video_enc_enum_fmt_vid_out(struct file *file, void *fh, struct v4l2_fmtdesc *f) { struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct video_format_info *info = NULL; struct video_format *fmt = NULL; unsigned long output_mask = 0; int idx = 0, bit_num = 0; if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) return -EINVAL; if (f->index >= vvd->num_input_fmts) return -EINVAL; info = &stream->out_info; list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) { if (info->fourcc_format == fmt->desc.format) { output_mask = fmt->desc.mask; break; } } if (output_mask == 0) return -EINVAL; list_for_each_entry(fmt, &vvd->input_fmt_list, formats_list_entry) { if (test_bit(bit_num, &output_mask)) { if (f->index == idx) { f->pixelformat = fmt->desc.format; return 0; } idx++; } bit_num++; } return -EINVAL; } static int virtio_video_enc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) { int ret; struct virtio_video_stream *stream = file2stream(file); ret = virtio_video_s_fmt(file, fh, f); if (ret) return ret; if (!V4L2_TYPE_IS_OUTPUT(f->type)) { if (virtio_video_state(stream) == STREAM_STATE_IDLE) virtio_video_state_update(stream, STREAM_STATE_INIT); } return 0; } static int virtio_video_enc_try_framerate(struct virtio_video_stream *stream, unsigned int fps) { int rate_idx; struct video_format_frame *frame = NULL; if (stream->current_frame == NULL) return -EINVAL; frame = stream->current_frame; for (rate_idx = 0; rate_idx < frame->frame.num_rates; rate_idx++) { struct virtio_video_format_range *frame_rate = &frame->frame_rates[rate_idx]; if (within_range(frame_rate->min, fps, frame_rate->max)) return 0; } return -EINVAL; } static void virtio_video_timeperframe_from_info(struct video_format_info *info, struct v4l2_fract *timeperframe) { timeperframe->numerator = 1; timeperframe->denominator = info->frame_rate; } static int virtio_video_enc_g_parm(struct file *file, void *priv, struct v4l2_streamparm *a) { struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct v4l2_outputparm *out = &a->parm.output; struct v4l2_fract *timeperframe = &out->timeperframe; if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (!V4L2_TYPE_IS_OUTPUT(a->type)) { v4l2_err(&vvd->v4l2_dev, "getting FPS is only possible for the output queue\n"); return -EINVAL; } out->capability = V4L2_CAP_TIMEPERFRAME; virtio_video_timeperframe_from_info(&stream->in_info, timeperframe); return 0; } static int virtio_video_enc_s_parm(struct file *file, void *priv, struct v4l2_streamparm *a) { int ret; u64 frame_interval, frame_rate; struct video_format_info info; struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); struct v4l2_outputparm *out = &a->parm.output; struct v4l2_fract *timeperframe = &out->timeperframe; if (virtio_video_state(stream) == STREAM_STATE_ERROR) return -EIO; if (V4L2_TYPE_IS_OUTPUT(a->type)) { frame_interval = timeperframe->numerator * (u64)USEC_PER_SEC; do_div(frame_interval, timeperframe->denominator); if (!frame_interval) return -EINVAL; frame_rate = (u64)USEC_PER_SEC; do_div(frame_rate, frame_interval); } else { v4l2_err(&vvd->v4l2_dev, "setting FPS is only possible for the output queue\n"); return -EINVAL; } ret = virtio_video_enc_try_framerate(stream, frame_rate); if (ret) return ret; virtio_video_format_fill_default_info(&info, &stream->in_info); info.frame_rate = frame_rate; virtio_video_cmd_set_params(vvd, stream, &info, VIRTIO_VIDEO_QUEUE_TYPE_INPUT); virtio_video_stream_get_params(vvd, stream); out->capability = V4L2_CAP_TIMEPERFRAME; virtio_video_timeperframe_from_info(&stream->in_info, timeperframe); return 0; } static int virtio_video_enc_s_selection(struct file *file, void *fh, struct v4l2_selection *sel) { struct virtio_video_stream *stream = file2stream(file); struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev); int ret; if (!V4L2_TYPE_IS_OUTPUT(sel->type)) return -EINVAL; switch (sel->target) { case V4L2_SEL_TGT_CROP: stream->in_info.crop.top = sel->r.top; stream->in_info.crop.left = sel->r.left; stream->in_info.crop.width = sel->r.width; stream->in_info.crop.height = sel->r.height; break; default: return -EINVAL; } ret = virtio_video_cmd_set_params(vvd, stream, &stream->in_info, VIRTIO_VIDEO_QUEUE_TYPE_INPUT); if (ret) return -EINVAL; return virtio_video_cmd_get_params(vvd, stream, VIRTIO_VIDEO_QUEUE_TYPE_INPUT); } static const struct v4l2_ioctl_ops virtio_video_enc_ioctl_ops = { .vidioc_querycap = virtio_video_querycap, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 3, 0)) .vidioc_enum_fmt_vid_cap = virtio_video_enc_enum_fmt_vid_cap, .vidioc_enum_fmt_vid_out = virtio_video_enc_enum_fmt_vid_out, #else .vidioc_enum_fmt_vid_cap_mplane = virtio_video_enc_enum_fmt_vid_cap, .vidioc_enum_fmt_vid_out_mplane = virtio_video_enc_enum_fmt_vid_out, #endif .vidioc_g_fmt_vid_cap_mplane = virtio_video_g_fmt, .vidioc_s_fmt_vid_cap_mplane = virtio_video_enc_s_fmt, .vidioc_g_fmt_vid_out_mplane = virtio_video_g_fmt, .vidioc_s_fmt_vid_out_mplane = virtio_video_enc_s_fmt, .vidioc_try_encoder_cmd = virtio_video_try_encoder_cmd, .vidioc_encoder_cmd = virtio_video_encoder_cmd, .vidioc_enum_frameintervals = virtio_video_enum_framemintervals, .vidioc_enum_framesizes = virtio_video_enum_framesizes, .vidioc_g_selection = virtio_video_g_selection, .vidioc_s_selection = virtio_video_enc_s_selection, .vidioc_reqbufs = virtio_video_reqbufs, .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, .vidioc_qbuf = virtio_video_qbuf, .vidioc_dqbuf = virtio_video_dqbuf, .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, .vidioc_streamon = v4l2_m2m_ioctl_streamon, .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, .vidioc_s_parm = virtio_video_enc_s_parm, .vidioc_g_parm = virtio_video_enc_g_parm, .vidioc_subscribe_event = virtio_video_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, }; void *virtio_video_enc_get_fmt_list(struct virtio_video_device *vvd) { return &vvd->output_fmt_list; } static struct virtio_video_device_ops virtio_video_enc_ops = { .init_ctrls = virtio_video_enc_init_ctrls, .init_queues = virtio_video_enc_init_queues, .get_fmt_list = virtio_video_enc_get_fmt_list, }; int virtio_video_enc_init(struct virtio_video_device *vvd) { ssize_t num; struct video_device *vd = &vvd->video_dev; vd->ioctl_ops = &virtio_video_enc_ioctl_ops; vvd->ops = &virtio_video_enc_ops; num = strscpy(vd->name, "stateful-encoder", sizeof(vd->name)); if (num < 0) return num; return 0; }