| /* |
| * Copyright 2021 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. |
| */ |
| // https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/dev-encoder.html |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <linux/videodev2.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #if defined(USE_V4LPLUGIN) |
| // Some devices (e.g. Rockchip) have a userspace "libv4l" library to bridge the |
| // stateful V4L2 encoder API with a stateless encoder hardware. |
| #include <libv4l2.h> |
| #endif |
| |
| // TODO(mcasas): Consider trying all /dev/video-enc* device files if a platform |
| // has more than two. |
| static const char* kEncodeDeviceFiles[] = {"/dev/video-enc0", |
| "/dev/video-enc1"}; |
| static const int kInputbufferMaxSize = 4 * 1024 * 1024; |
| static const int kRequestBufferCount = 8; |
| static const uint32_t kIVFHeaderSignature = v4l2_fourcc('D', 'K', 'I', 'F'); |
| |
| int do_ioctl(int fd, int request, void* data) { |
| #if defined(USE_V4LPLUGIN) |
| return v4l2_ioctl(fd, request, data); |
| #else |
| return ioctl(fd, request, data); |
| #endif |
| } |
| |
| struct file_buffer { |
| uint8_t* buffer; |
| uint32_t frame_size; |
| }; |
| |
| struct file_info { |
| FILE* fp; |
| uint32_t format; |
| }; |
| |
| struct mmap_buffers { |
| void* start[VIDEO_MAX_PLANES]; |
| size_t length[VIDEO_MAX_PLANES]; |
| struct gbm_bo* bo; |
| }; |
| |
| struct queue { |
| int v4lfd; |
| enum v4l2_buf_type type; |
| uint32_t fourcc; |
| struct mmap_buffers* buffers; |
| uint32_t raw_width; |
| uint32_t raw_height; |
| uint32_t encoded_width; |
| uint32_t encoded_height; |
| uint32_t cnt; |
| uint32_t frame_cnt; |
| uint32_t num_planes; |
| uint32_t framerate; |
| uint32_t strides[VIDEO_MAX_PLANES]; |
| }; |
| |
| struct encoder_control { |
| uint32_t id; |
| uint32_t value; |
| uint32_t enabled; |
| char *name; |
| }; |
| |
| #define ENCODER_CTL(ID, VALUE) \ |
| {.id = ID, .value = VALUE, .enabled = 1, .name = #ID} |
| |
| struct ivf_file_header { |
| uint32_t signature; |
| uint16_t version; |
| uint16_t header_length; |
| uint32_t fourcc; |
| uint16_t width; |
| uint16_t height; |
| uint32_t denominator; |
| uint32_t numerator; |
| uint32_t frame_cnt; |
| uint32_t unused; |
| } __attribute__((packed)); |
| |
| struct ivf_frame_header { |
| uint32_t size; |
| uint64_t timestamp; |
| } __attribute__((packed)); |
| |
| void print_fourcc(uint32_t fourcc) { |
| printf("%c%c%c%c\n", fourcc & 0xff, fourcc >> 8 & 0xff, fourcc >> 16 & 0xff, |
| fourcc >> 24 & 0xff); |
| } |
| |
| int query_format(int v4lfd, enum v4l2_buf_type type, uint32_t fourcc) { |
| struct v4l2_fmtdesc fmtdesc; |
| memset(&fmtdesc, 0, sizeof(fmtdesc)); |
| |
| fmtdesc.type = type; |
| while (do_ioctl(v4lfd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) { |
| if (fourcc == 0) |
| print_fourcc(fmtdesc.pixelformat); |
| else if (fourcc == fmtdesc.pixelformat) |
| return 1; |
| fmtdesc.index++; |
| } |
| |
| return 0; |
| } |
| |
| void enumerate_menu(int v4lfd, uint32_t id, uint32_t min, uint32_t max) { |
| struct v4l2_querymenu querymenu; |
| memset(&querymenu, 0, sizeof(querymenu)); |
| |
| querymenu.id = id; |
| for (querymenu.index = min; querymenu.index <= max; querymenu.index++) { |
| if (0 == do_ioctl(v4lfd, VIDIOC_QUERYMENU, &querymenu)) |
| fprintf(stderr, " %s\n", querymenu.name); |
| } |
| } |
| |
| int verify_capabilities(int v4lfd, |
| uint32_t OUTPUT_format, |
| uint32_t CAPTURE_format) { |
| struct v4l2_capability cap; |
| memset(&cap, 0, sizeof(cap)); |
| int ret = do_ioctl(v4lfd, VIDIOC_QUERYCAP, &cap); |
| if (ret != 0) |
| perror("VIDIOC_QUERYCAP failed"); |
| |
| printf("driver=\"%s\" bus_info=\"%s\" card=\"%s\" fd=0x%x\n", cap.driver, |
| cap.bus_info, cap.card, v4lfd); |
| |
| if (!query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, OUTPUT_format)) { |
| printf("Insufficient supported OUTPUT formats:\n"); |
| query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, 0); |
| printf("Wanted: "); |
| print_fourcc(OUTPUT_format); |
| ret = 1; |
| } |
| |
| if (!query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, |
| CAPTURE_format)) { |
| printf("Insufficient supported CAPTURE formats:\n"); |
| query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, 0); |
| printf("Wanted: "); |
| print_fourcc(CAPTURE_format); |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| int queue_OUTPUT_buffer(struct queue* queue, |
| struct mmap_buffers* buffers, |
| uint32_t index) { |
| // compute frame timestamp |
| const float usec_per_frame = (1.0 / queue->framerate) * 1000000; |
| const uint64_t usec_time_stamp = usec_per_frame * queue->frame_cnt; |
| const uint64_t tv_sec = usec_time_stamp / 1000000; |
| |
| struct v4l2_buffer v4l2_buffer; |
| struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
| memset(&v4l2_buffer, 0, sizeof(v4l2_buffer)); |
| |
| v4l2_buffer.index = index; |
| v4l2_buffer.type = queue->type; |
| v4l2_buffer.memory = V4L2_MEMORY_MMAP; |
| v4l2_buffer.length = queue->num_planes; |
| v4l2_buffer.timestamp.tv_sec = tv_sec; |
| v4l2_buffer.timestamp.tv_usec = usec_time_stamp - tv_sec; |
| v4l2_buffer.sequence = queue->frame_cnt; |
| v4l2_buffer.m.planes = planes; |
| for (uint32_t i = 0; i < queue->num_planes; ++i) { |
| v4l2_buffer.m.planes[i].length = buffers[index].length[i]; |
| v4l2_buffer.m.planes[i].bytesused = buffers[index].length[i]; |
| v4l2_buffer.m.planes[i].data_offset = 0; |
| } |
| |
| int ret = do_ioctl(queue->v4lfd, VIDIOC_QBUF, &v4l2_buffer); |
| if (ret != 0) { |
| perror("VIDIOC_QBUF failed"); |
| return -1; |
| } |
| |
| queue->frame_cnt++; |
| |
| return 0; |
| } |
| |
| // This function copies the contents pointed by |frame_buffer| |
| // to |queue|s |index| buffer. Handle the case where the format |
| // of the file on disk is the same as the encoding format. |
| int submit_raw_frame_in_bulk(const uint8_t* frame_buffer, |
| struct queue* queue, |
| uint32_t index) { |
| assert(queue->num_planes == 1 || queue->num_planes == 2); |
| assert(queue->raw_width == queue->encoded_width); |
| // TODO: the code below assumes NV12 because the Chroma planes are copied in |
| // one call. Extend to YV12 if ever the need arises. |
| assert(queue->fourcc == v4l2_fourcc('N', 'V', '1', '2')); |
| |
| struct mmap_buffers* buffers = queue->buffers; |
| |
| // Read luma plane first. |
| const size_t luma_plane_size = queue->raw_width * queue->raw_height; |
| uint8_t* buffer = buffers[index].start[0]; |
| memcpy(buffer, frame_buffer, luma_plane_size); |
| |
| // Now read both chroma planes together. |
| if (queue->num_planes == 2) |
| buffer = buffers[index].start[1]; |
| else |
| buffer += queue->encoded_width * queue->encoded_height; |
| |
| const size_t chroma_planes_size = luma_plane_size / 2; |
| memcpy(buffer, frame_buffer + luma_plane_size, chroma_planes_size); |
| |
| return queue_OUTPUT_buffer(queue, buffers, index); |
| } |
| |
| // This function copies the contents pointed by |frame_buffer| |
| // to |queue|s |index| buffer. Copy row by row for the situations |
| // where the width/height of the v4l2 buffer is different than |
| // that of the file on disk, or when conversion between formats is necessary. |
| int submit_raw_frame_row_by_row(const uint8_t* frame_buffer, |
| uint32_t file_format, |
| struct queue* queue, |
| uint32_t index) { |
| assert(queue->num_planes == 1 || queue->num_planes == 3); |
| assert(queue->fourcc == v4l2_fourcc('Y', 'V', '1', '2') || |
| queue->fourcc == v4l2_fourcc('Y', 'M', '1', '2') || |
| queue->fourcc == v4l2_fourcc('N', 'V', '1', '2')); |
| assert(file_format == v4l2_fourcc('Y', 'V', '1', '2')); |
| |
| struct mmap_buffers* buffers = queue->buffers; |
| const size_t luma_raw_width = queue->raw_width; |
| |
| // Read luma plane first, row by row. |
| uint8_t* buffer = buffers[index].start[0]; |
| for (int row = 0; row < queue->raw_height; ++row) { |
| memcpy(buffer, frame_buffer, luma_raw_width); |
| frame_buffer += luma_raw_width; |
| buffer += queue->encoded_width; |
| } |
| |
| const size_t chroma_raw_width = luma_raw_width / 2; |
| // Now read the chroma planes. |
| if ((queue->fourcc == v4l2_fourcc('Y', 'V', '1', '2') || |
| queue->fourcc == v4l2_fourcc('Y', 'M', '1', '2')) && |
| file_format == v4l2_fourcc('Y', 'V', '1', '2')) { |
| assert((queue->fourcc == v4l2_fourcc('Y', 'M', '1', '2') && |
| queue->num_planes == 3) || |
| (queue->fourcc == v4l2_fourcc('Y', 'V', '1', '2') && |
| queue->num_planes == 1)); |
| |
| buffer = buffers[index].start[1]; |
| const uint32_t stride_u = queue->strides[1]; |
| |
| for (int row = 0; row < queue->raw_height / 2; ++row) { |
| memcpy(buffer, frame_buffer, chroma_raw_width); |
| frame_buffer += chroma_raw_width; |
| buffer += stride_u; |
| } |
| |
| uint32_t stride_v = stride_u; |
| if (queue->num_planes == 3) { |
| buffer = buffers[index].start[2]; |
| stride_v = queue->strides[2]; |
| } else { |
| assert(queue->num_planes == 1); |
| buffer = buffers[index].start[0] + |
| 5 * queue->encoded_width * queue->encoded_height / 4; |
| } |
| |
| for (int row = 0; row < queue->raw_height / 2; ++row) { |
| memcpy(buffer, frame_buffer, chroma_raw_width); |
| frame_buffer += chroma_raw_width; |
| buffer += stride_v; |
| } |
| |
| } else if (queue->fourcc == v4l2_fourcc('N', 'V', '1', '2') && |
| file_format == v4l2_fourcc('Y', 'V', '1', '2') && |
| queue->num_planes == 1) { |
| assert(queue->num_planes == 1); |
| buffer = |
| buffers[index].start[0] + queue->encoded_width * queue->encoded_height; |
| const uint8_t* u_ptr = frame_buffer; |
| const uint8_t* v_ptr = frame_buffer + |
| ((queue->raw_width * queue->raw_height) / 4); |
| for (int row = 0; row < queue->raw_height / 2; ++row) { |
| for (int col = 0; col < chroma_raw_width; ++col) { |
| buffer[col * 2] = u_ptr[col]; |
| buffer[col * 2 + 1] = v_ptr[col]; |
| } |
| buffer += queue->encoded_width; |
| u_ptr += chroma_raw_width; |
| v_ptr += chroma_raw_width; |
| } |
| } else { |
| fprintf(stderr, |
| "combination of queue format, number of planes, and file format " |
| "unsupported\n"); |
| return -1; |
| } |
| |
| return queue_OUTPUT_buffer(queue, buffers, index); |
| } |
| |
| // Read the contents of a frame from the file on disk to a memory buffer. |
| // This makes later format conversions quicker as there is no need to |
| // read a byte at a time from the disk. |
| // The frame is then copied to a |queue| buffer and submitted to the driver |
| // by the leaf functions. |
| int submit_raw_frame(const struct file_buffer* fb, |
| const struct file_info* fi, |
| struct queue* queue, |
| uint32_t index) { |
| if (fread(fb->buffer, fb->frame_size, 1, fi->fp) != 1) { |
| fprintf(stderr, "unable to read frame into memory\n"); |
| return -1; |
| } |
| |
| if (queue->raw_width == queue->encoded_width && |
| queue->fourcc == fi->format && |
| queue->fourcc == v4l2_fourcc('N', 'V', '1', '2')) { |
| return submit_raw_frame_in_bulk(fb->buffer, queue, index); |
| } |
| |
| return submit_raw_frame_row_by_row(fb->buffer, fi->format, queue, index); |
| } |
| |
| void cleanup_queue(struct queue* queue) { |
| if (queue->cnt) { |
| struct mmap_buffers* buffers = queue->buffers; |
| |
| for (uint32_t i = 0; i < queue->cnt; i++) |
| for (uint32_t j = 0; j < queue->num_planes; j++) { |
| munmap(buffers[i].start[j], buffers[i].length[j]); |
| } |
| |
| free(queue->buffers); |
| queue->cnt = 0; |
| } |
| } |
| |
| int request_mmap_buffers(struct queue* queue, |
| struct v4l2_requestbuffers* reqbuf) { |
| const int v4lfd = queue->v4lfd; |
| const uint32_t buffer_alloc = reqbuf->count * sizeof(struct mmap_buffers); |
| struct mmap_buffers* buffers = (struct mmap_buffers*)malloc(buffer_alloc); |
| memset(buffers, 0, buffer_alloc); |
| queue->buffers = buffers; |
| queue->cnt = reqbuf->count; |
| |
| int ret; |
| for (uint32_t i = 0; i < reqbuf->count; i++) { |
| struct v4l2_buffer buffer; |
| struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
| memset(&buffer, 0, sizeof(buffer)); |
| buffer.type = reqbuf->type; |
| buffer.memory = V4L2_MEMORY_MMAP; |
| buffer.index = i; |
| buffer.length = queue->num_planes; |
| buffer.m.planes = planes; |
| ret = do_ioctl(v4lfd, VIDIOC_QUERYBUF, &buffer); |
| if (ret != 0) { |
| printf("VIDIOC_QUERYBUF failed: %d\n", ret); |
| break; |
| } |
| |
| for (uint32_t j = 0; j < queue->num_planes; j++) { |
| buffers[i].length[j] = buffer.m.planes[j].length; |
| buffers[i].start[j] = |
| mmap(NULL, buffer.m.planes[j].length, PROT_READ | PROT_WRITE, |
| MAP_SHARED, v4lfd, buffer.m.planes[j].m.mem_offset); |
| if (MAP_FAILED == buffers[i].start[j]) { |
| fprintf(stderr, |
| "failed to mmap buffer of length(%d) and offset(0x%x)\n", |
| buffer.m.planes[j].length, buffer.m.planes[j].m.mem_offset); |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| int queue_CAPTURE_buffer(struct queue* queue, uint32_t index) { |
| struct mmap_buffers* buffers = queue->buffers; |
| struct v4l2_buffer v4l2_buffer; |
| struct v4l2_plane planes[VIDEO_MAX_PLANES]; |
| memset(&v4l2_buffer, 0, sizeof v4l2_buffer); |
| memset(&planes, 0, sizeof planes); |
| |
| v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| v4l2_buffer.memory = V4L2_MEMORY_MMAP; |
| v4l2_buffer.index = index; |
| v4l2_buffer.m.planes = planes; |
| v4l2_buffer.length = queue->num_planes; |
| |
| v4l2_buffer.m.planes[0].length = buffers[index].length[0]; |
| v4l2_buffer.m.planes[0].bytesused = buffers[index].length[0]; |
| v4l2_buffer.m.planes[0].data_offset = 0; |
| |
| int ret = do_ioctl(queue->v4lfd, VIDIOC_QBUF, &v4l2_buffer); |
| if (ret != 0) { |
| perror("VIDIOC_QBUF failed"); |
| } |
| |
| return ret; |
| } |
| |
| // 4.5.2.5. Initialization |
| int Initialization(struct queue* OUTPUT_queue, struct queue* CAPTURE_queue) { |
| int ret = 0; |
| |
| // 1. Set the coded format on the CAPTURE queue via VIDIOC_S_FMT(). |
| if (!ret) { |
| struct v4l2_format fmt; |
| memset(&fmt, 0, sizeof(fmt)); |
| |
| fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; |
| fmt.fmt.pix_mp.pixelformat = CAPTURE_queue->fourcc; |
| fmt.fmt.pix_mp.width = CAPTURE_queue->raw_width; |
| fmt.fmt.pix_mp.height = CAPTURE_queue->raw_height; |
| fmt.fmt.pix_mp.plane_fmt[0].sizeimage = kInputbufferMaxSize; |
| fmt.fmt.pix_mp.num_planes = 1; |
| |
| int ret = do_ioctl(CAPTURE_queue->v4lfd, VIDIOC_S_FMT, &fmt); |
| if (ret != 0) |
| perror("VIDIOC_S_FMT failed"); |
| |
| CAPTURE_queue->encoded_width = fmt.fmt.pix_mp.width; |
| CAPTURE_queue->encoded_height = fmt.fmt.pix_mp.height; |
| } |
| |
| // 3. Set the raw source format on the OUTPUT queue via VIDIOC_S_FMT(). |
| if (!ret) { |
| struct v4l2_format fmt; |
| memset(&fmt, 0, sizeof(fmt)); |
| |
| fmt.type = OUTPUT_queue->type; |
| fmt.fmt.pix_mp.pixelformat = OUTPUT_queue->fourcc; |
| fmt.fmt.pix_mp.width = OUTPUT_queue->raw_width; |
| fmt.fmt.pix_mp.height = OUTPUT_queue->raw_height; |
| fmt.fmt.pix_mp.num_planes = 1; |
| |
| int ret = do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_S_FMT, &fmt); |
| if (ret != 0) |
| perror("VIDIOC_S_FMT failed"); |
| |
| OUTPUT_queue->encoded_width = fmt.fmt.pix_mp.width; |
| OUTPUT_queue->encoded_height = fmt.fmt.pix_mp.height; |
| |
| OUTPUT_queue->num_planes = fmt.fmt.pix_mp.num_planes; |
| assert(OUTPUT_queue->num_planes <= VIDEO_MAX_PLANES); |
| printf("OUTPUT_queue %d planes, (%dx%d)\n", OUTPUT_queue->num_planes, |
| OUTPUT_queue->encoded_width, OUTPUT_queue->encoded_height); |
| for (int plane = 0; plane < OUTPUT_queue->num_planes; ++plane) { |
| OUTPUT_queue->strides[plane] = |
| fmt.fmt.pix_mp.plane_fmt[plane].bytesperline; |
| printf(" plane %d, stride %d\n", plane, OUTPUT_queue->strides[plane]); |
| } |
| } |
| |
| // 4. Set the raw frame interval on the OUTPUT queue via VIDIOC_S_PARM() |
| if (!ret) { |
| struct v4l2_streamparm parms; |
| memset(&parms, 0, sizeof(parms)); |
| parms.type = OUTPUT_queue->type; |
| // Note that we are provided "frames per second" but V4L2 expects "time per |
| // frame"; hence we provide the reciprocal of the framerate here. |
| parms.parm.output.timeperframe.numerator = 1; |
| parms.parm.output.timeperframe.denominator = OUTPUT_queue->framerate; |
| |
| const int temp_ret = do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_S_PARM, &parms); |
| if (temp_ret != 0) |
| perror("Optional frame rate VIDIOC_S_PARAM failed"); |
| } |
| |
| // 6. Optional. Set the visible resolution for the stream metadata via |
| // VIDIOC_S_SELECTION() on the OUTPUT queue if it is desired to be |
| // different than the full OUTPUT resolution. |
| if (!ret) { |
| struct v4l2_selection selection_arg; |
| memset(&selection_arg, 0, sizeof(selection_arg)); |
| selection_arg.type = OUTPUT_queue->type; |
| selection_arg.target = V4L2_SEL_TGT_CROP; |
| selection_arg.r.left = 0; |
| selection_arg.r.top = 0; |
| selection_arg.r.width = OUTPUT_queue->raw_width; |
| selection_arg.r.height = OUTPUT_queue->raw_height; |
| |
| const int temp_ret = |
| do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_S_SELECTION, &selection_arg); |
| if (temp_ret != 0) |
| perror("Optional visible rectangle VIDIOC_S_SELECTION failed"); |
| |
| // TODO(fritz) : check returned values are same as sent values |
| } |
| |
| // 7. Allocate buffers for both OUTPUT and CAPTURE via VIDIOC_REQBUFS(). |
| // This may be performed in any order. |
| if (!ret) { |
| struct v4l2_requestbuffers reqbuf; |
| memset(&reqbuf, 0, sizeof(reqbuf)); |
| reqbuf.count = kRequestBufferCount; |
| reqbuf.type = OUTPUT_queue->type; |
| reqbuf.memory = V4L2_MEMORY_MMAP; |
| |
| ret = do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_REQBUFS, &reqbuf); |
| if (ret != 0) |
| perror("VIDIOC_REQBUFS failed"); |
| |
| printf( |
| "%d buffers requested, %d buffers for uncompressed frames returned\n", |
| kRequestBufferCount, reqbuf.count); |
| |
| ret = request_mmap_buffers(OUTPUT_queue, &reqbuf); |
| } |
| |
| if (!ret) { |
| struct v4l2_requestbuffers reqbuf; |
| memset(&reqbuf, 0, sizeof(reqbuf)); |
| reqbuf.count = kRequestBufferCount; |
| reqbuf.type = CAPTURE_queue->type; |
| reqbuf.memory = V4L2_MEMORY_MMAP; |
| |
| ret = do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_REQBUFS, &reqbuf); |
| if (ret != 0) |
| perror("VIDIOC_REQBUFS failed"); |
| |
| printf("%d buffers requested, %d buffers for compressed frames returned\n", |
| kRequestBufferCount, reqbuf.count); |
| |
| ret = request_mmap_buffers(CAPTURE_queue, &reqbuf); |
| for (uint32_t i = 0; i < reqbuf.count; i++) { |
| queue_CAPTURE_buffer(CAPTURE_queue, i); |
| } |
| } |
| |
| return ret; |
| } |
| |
| void disable_unsupported_controls(int v4lfd, |
| struct encoder_control* encoder_ctrl) { |
| while (encoder_ctrl->id != 0) { |
| struct v4l2_query_ext_ctrl query_ext_ctrl; |
| memset(&query_ext_ctrl, 0, sizeof(query_ext_ctrl)); |
| |
| query_ext_ctrl.id = V4L2_CTRL_CLASS_MPEG | encoder_ctrl->id; |
| |
| int ret = do_ioctl(v4lfd, VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl); |
| |
| encoder_ctrl->enabled = (ret == 0); |
| |
| if (ret != 0) { |
| fprintf(stderr, |
| "%s control does not exist on this platform\n" , encoder_ctrl->name); |
| } |
| |
| encoder_ctrl++; |
| }; |
| } |
| |
| int enabled_control_count(const struct encoder_control* encoder_ctrl) { |
| int cnt = 0; |
| |
| for (; encoder_ctrl && encoder_ctrl->id != 0; ++encoder_ctrl) |
| cnt += encoder_ctrl->enabled; |
| |
| return cnt; |
| } |
| |
| void set_control_value(struct encoder_control* encoder_ctrl, |
| uint32_t id, |
| uint32_t value) { |
| for (; encoder_ctrl && encoder_ctrl->id != 0; ++encoder_ctrl) { |
| if (encoder_ctrl->id == id){ |
| encoder_ctrl->value = value; |
| break; |
| } |
| } |
| } |
| |
| int get_control_value(const struct encoder_control* encoder_ctrl, uint32_t id) { |
| for (; encoder_ctrl && encoder_ctrl->id != 0; ++encoder_ctrl) { |
| if (encoder_ctrl->id == id) |
| return encoder_ctrl->value; |
| }; |
| |
| return -1; |
| } |
| |
| const char *get_control_name(const struct encoder_control* encoder_ctrl, |
| uint32_t id) { |
| for (; encoder_ctrl && encoder_ctrl->id != 0; ++encoder_ctrl) { |
| if (encoder_ctrl->id == id) |
| return encoder_ctrl->name; |
| }; |
| |
| return "unknown"; |
| } |
| |
| void fill_ext_controls(const struct encoder_control* encoder_ctrl, |
| struct v4l2_ext_control *ext_ctrl) { |
| int enabled_i = 0; |
| for (; encoder_ctrl && encoder_ctrl->id != 0; ++encoder_ctrl) { |
| if (encoder_ctrl->enabled) { |
| ext_ctrl[enabled_i].id = encoder_ctrl->id; |
| ext_ctrl[enabled_i].value = encoder_ctrl->value; |
| enabled_i++; |
| } |
| } |
| } |
| |
| int set_ext_controls(int v4lfd, const struct encoder_control* encoder_ctrl) { |
| const int ctrl_cnt = enabled_control_count(encoder_ctrl); |
| |
| struct v4l2_ext_control ext_ctrl[ctrl_cnt]; |
| memset(&ext_ctrl, 0, sizeof(ext_ctrl)); |
| |
| fill_ext_controls(encoder_ctrl, ext_ctrl); |
| |
| struct v4l2_ext_controls ext_ctrls; |
| memset(&ext_ctrls, 0, sizeof(ext_ctrls)); |
| |
| ext_ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; |
| ext_ctrls.count = ctrl_cnt; |
| ext_ctrls.controls = ext_ctrl; |
| |
| const int set_ret = do_ioctl(v4lfd, VIDIOC_S_EXT_CTRLS, &ext_ctrls); |
| if (set_ret != 0) |
| perror("VIDIOC_S_EXT_CTRLS failed"); |
| |
| for (uint32_t i = 0; i < ctrl_cnt; ++i) |
| ext_ctrl[i].value = 0; |
| |
| const int get_ret = do_ioctl(v4lfd, VIDIOC_G_EXT_CTRLS, &ext_ctrls); |
| if (get_ret != 0) |
| perror("VIDIOC_G_EXT_CTRLS failed"); |
| |
| |
| for (int i = 0; i < ctrl_cnt; ++i) { |
| int ctrl_value = get_control_value(encoder_ctrl, ext_ctrl[i].id); |
| if (ext_ctrl[i].value != ctrl_value){ |
| fprintf(stderr, |
| "control (%s) used a value of (%d) instead of the requested (%d)\n", |
| get_control_name(encoder_ctrl, ext_ctrl[i].id), |
| ext_ctrl[i].value, |
| ctrl_value); |
| } |
| } |
| |
| return set_ret | get_ret; |
| } |
| |
| int dequeue_buffer(struct queue* queue, |
| uint32_t* index, |
| uint32_t* bytesused, |
| uint32_t* data_offset, |
| uint64_t* timestamp, |
| uint32_t* flags) { |
| struct v4l2_buffer v4l2_buffer; |
| struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; |
| memset(&v4l2_buffer, 0, sizeof(v4l2_buffer)); |
| v4l2_buffer.type = queue->type; |
| v4l2_buffer.length = queue->num_planes; |
| v4l2_buffer.m.planes = planes; |
| v4l2_buffer.m.planes[0].bytesused = 0; |
| int ret = do_ioctl(queue->v4lfd, VIDIOC_DQBUF, &v4l2_buffer); |
| |
| if (ret != 0 && errno != EAGAIN) |
| perror("VIDIOC_DQBUF failed"); |
| |
| *index = v4l2_buffer.index; |
| if (bytesused) |
| *bytesused = v4l2_buffer.m.planes[0].bytesused; |
| if (data_offset) |
| *data_offset = v4l2_buffer.m.planes[0].data_offset; |
| if (timestamp) |
| *timestamp = v4l2_buffer.timestamp.tv_usec; |
| if (flags) |
| *flags = v4l2_buffer.flags; |
| |
| return ret; |
| } |
| |
| // 4.5.2.8. Drain |
| void drain(struct queue* OUTPUT_queue, |
| struct queue* CAPTURE_queue) { |
| |
| // 1. Begin the drain sequence by issuing VIDIOC_ENCODER_CMD(). |
| struct v4l2_encoder_cmd cmd; |
| memset(&cmd, 0, sizeof(cmd)); |
| cmd.cmd = V4L2_ENC_CMD_STOP; |
| |
| // V4L2_ENC_CMD_STOP may not be supported, don't worry about result |
| do_ioctl(CAPTURE_queue->v4lfd, VIDIOC_ENCODER_CMD, &cmd); |
| |
| // 2. Dequeue buffers |
| // The way the encode loop is set up, there shouldn't be any buffers |
| // left to dequeue. |
| { |
| uint32_t index = 0; |
| uint32_t bytesused = 0; |
| uint32_t flags = 0; |
| |
| // check to make sure the queue is empty |
| dequeue_buffer(CAPTURE_queue, &index, &bytesused, 0, 0, &flags); |
| |
| if (!(flags & V4L2_BUF_FLAG_LAST) && (bytesused != 0)) |
| fprintf(stderr, "WARNING: CAPTURE queue did not clean up.\n"); |
| } |
| |
| // 3. Reset by issuing VIDIOC_STREAMOFF |
| int ret = |
| do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_STREAMOFF, &OUTPUT_queue->type); |
| if (ret != 0) |
| perror("VIDIOC_STREAMOFF failed on OUTPUT"); |
| |
| ret = do_ioctl(CAPTURE_queue->v4lfd, VIDIOC_STREAMOFF, &CAPTURE_queue->type); |
| if (ret != 0) |
| perror("VIDIOC_STREAMOFF failed on CAPTURE"); |
| } |
| |
| int encode(const struct file_buffer* fb, |
| const struct file_info* fi, |
| char* output_file_name, |
| struct queue* OUTPUT_queue, |
| struct queue* CAPTURE_queue, |
| uint32_t frames_to_encode) { |
| if (OUTPUT_queue->num_planes == 0 || OUTPUT_queue->num_planes > 3) { |
| fprintf(stderr, " unsupported number of planes: %d\n", |
| OUTPUT_queue->num_planes); |
| return -1; |
| } |
| fprintf(stderr, "encoding\n"); |
| |
| const int use_ivf = CAPTURE_queue->fourcc == v4l2_fourcc('V', 'P', '8', '0'); |
| strcat(output_file_name, use_ivf ? ".ivf" : ".h264"); |
| |
| int ret = 0; |
| FILE* fp_output = fopen(output_file_name, "wb"); |
| if (!fp_output) { |
| fprintf(stderr, "unable to write to file: %s\n", output_file_name); |
| ret = 1; |
| } |
| |
| // write header |
| if (use_ivf) { |
| struct ivf_file_header header; |
| header.signature = kIVFHeaderSignature; |
| header.version = 0; |
| header.header_length = sizeof(struct ivf_file_header); |
| header.fourcc = CAPTURE_queue->fourcc; |
| header.width = CAPTURE_queue->raw_width; |
| header.height = CAPTURE_queue->raw_height; |
| // hard coded 30fps |
| header.denominator = 30; |
| header.numerator = 1; |
| header.frame_cnt = frames_to_encode; |
| header.unused = 0; |
| |
| if (fwrite(&header, sizeof(struct ivf_file_header), 1, fp_output) != 1) { |
| fprintf(stderr, "unable to write ivf file header\n"); |
| } |
| } |
| |
| struct timespec start, stop; |
| clock_gettime(CLOCK_REALTIME, &start); |
| if (!ret) { |
| // prime input by filling up the OUTPUT queue with raw frames |
| for (uint32_t i = 0; i < OUTPUT_queue->cnt; ++i) { |
| if (submit_raw_frame(fb, fi, OUTPUT_queue, i)) { |
| fprintf(stderr, "unable to submit raw frame\n"); |
| ret = 1; |
| } |
| } |
| } |
| |
| if (!ret) { |
| ret = do_ioctl(OUTPUT_queue->v4lfd, VIDIOC_STREAMON, &OUTPUT_queue->type); |
| if (ret != 0) |
| perror("VIDIOC_STREAMON failed on OUTPUT"); |
| } |
| |
| if (!ret) { |
| ret = do_ioctl(CAPTURE_queue->v4lfd, VIDIOC_STREAMON, &CAPTURE_queue->type); |
| if (ret != 0) |
| perror("VIDIOC_STREAMON failed on CAPTURE"); |
| } |
| |
| uint32_t cnt = OUTPUT_queue->cnt; // We pre-uploaded a few before. |
| if (!ret) { |
| while (cnt < frames_to_encode) { |
| // handle CAPTURE queue first |
| { |
| uint32_t index = 0; |
| uint32_t bytesused = 0; |
| uint32_t data_offset = 0; |
| uint64_t timestamp = 0; |
| |
| // first get the newly encoded frame |
| ret = dequeue_buffer(CAPTURE_queue, &index, &bytesused, &data_offset, |
| ×tamp, 0); |
| if (ret != 0) |
| continue; |
| |
| if (use_ivf) { |
| struct ivf_frame_header header; |
| header.size = bytesused - data_offset; |
| header.timestamp = timestamp; |
| |
| if (fwrite(&header, sizeof(struct ivf_frame_header), 1, fp_output) != |
| 1) { |
| fprintf(stderr, "unable to write ivf frame header\n"); |
| } |
| } |
| fwrite(CAPTURE_queue->buffers[index].start[0] + data_offset, |
| bytesused - data_offset, 1, fp_output); |
| |
| // done with the buffer, queue it back up |
| queue_CAPTURE_buffer(CAPTURE_queue, index); |
| } |
| |
| // handle OUTPUT queue second |
| { |
| uint32_t index = 0; |
| |
| ret = dequeue_buffer(OUTPUT_queue, &index, 0, 0, 0, 0); |
| if (ret != 0) |
| continue; |
| |
| if (submit_raw_frame(fb, fi, OUTPUT_queue, index)) |
| break; |
| } |
| cnt++; |
| } |
| } |
| |
| drain(OUTPUT_queue, CAPTURE_queue); |
| |
| clock_gettime(CLOCK_REALTIME, &stop); |
| const double elapsed_ns = |
| (stop.tv_sec - start.tv_sec) * 1e9 + (stop.tv_nsec - start.tv_nsec); |
| const double fps = cnt * 1e9 / elapsed_ns; |
| printf("%d frames encoded in %fns (%ffps)\n", cnt, elapsed_ns, fps); |
| |
| if (fp_output) { |
| fclose(fp_output); |
| } |
| return ret; |
| } |
| |
| static void print_help(const char* argv0) { |
| printf("usage: %s [OPTIONS]\n", argv0); |
| printf(" -f, --file file to encode\n"); |
| printf(" -i, --file_format pixel format of the file (yv12, nv12)\n"); |
| printf( |
| " -o, --output output file name (will get a .h264 or .ivf suffix " |
| "added)\n"); |
| printf(" -w, --width width of image\n"); |
| printf(" -h, --height height of image\n"); |
| printf(" -m, --max max number of frames to decode\n"); |
| printf(" -r, --rate frames per second\n"); |
| printf(" -b, --bitrate bits per second\n"); |
| printf(" -g, --gop gop length\n"); |
| printf(" -c, --codec codec\n"); |
| printf(" -e, --end_usage rate control mode: VBR (default), CBR\n"); |
| printf(" -q, --buffer_fmt OUTPUT queue format\n"); |
| } |
| |
| static const struct option longopts[] = { |
| {"file", required_argument, NULL, 'f'}, |
| {"file_format", required_argument, NULL, 'i'}, |
| {"output", required_argument, NULL, 'o'}, |
| {"width", required_argument, NULL, 'w'}, |
| {"height", required_argument, NULL, 'h'}, |
| {"max", required_argument, NULL, 'm'}, |
| {"fps", required_argument, NULL, 'r'}, |
| {"bitrate", required_argument, NULL, 'b'}, |
| {"gop", required_argument, NULL, 'g'}, |
| {"codec", required_argument, NULL, 'c'}, |
| {"end_usage", required_argument, NULL, 'e'}, |
| {"buffer_fmt", required_argument, NULL, 'q'}, |
| {0, 0, 0, 0}, |
| }; |
| |
| int main(int argc, char* argv[]) { |
| uint32_t file_format = v4l2_fourcc('N', 'V', '1', '2'); |
| uint32_t OUTPUT_format = v4l2_fourcc('N', 'V', '1', '2'); |
| uint32_t CAPTURE_format = v4l2_fourcc('H', '2', '6', '4'); |
| char* file_name = NULL; |
| char* output_file_name = NULL; |
| uint32_t width = 0; |
| uint32_t height = 0; |
| uint32_t frames_to_encode = 0; |
| uint32_t framerate = 30; |
| int c; |
| |
| struct encoder_control common_control_list[] = { |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_BITRATE, 1000), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_GOP_SIZE, 20), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 1), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_BITRATE_MODE, |
| V4L2_MPEG_VIDEO_BITRATE_MODE_VBR), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, 1500), |
| ENCODER_CTL(0, 0) |
| }; |
| |
| struct encoder_control h264_control_list[] = { |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_H264_PROFILE, |
| V4L2_MPEG_VIDEO_H264_PROFILE_MAIN), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_H264_LEVEL, |
| V4L2_MPEG_VIDEO_H264_LEVEL_4_0), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, |
| V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC), |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_HEADER_MODE, |
| V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME), |
| #if defined(USE_V4LPLUGIN) |
| // We need to explicitly request SPSPPS otherwise libv4l doesn't add it. |
| ENCODER_CTL(V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR, 1), |
| #endif |
| ENCODER_CTL(0, 0) |
| }; |
| |
| while ((c = getopt_long(argc, argv, "f:i:o:w:h:m:r:b:g:c:e:q:", longopts, |
| NULL)) != -1) { |
| switch (c) { |
| case 'f': |
| file_name = strdup(optarg); |
| break; |
| case 'i': |
| if (strlen(optarg) == 4) { |
| file_format = v4l2_fourcc(toupper(optarg[0]), toupper(optarg[1]), |
| toupper(optarg[2]), toupper(optarg[3])); |
| printf("using (%s) as the file format\n", optarg); |
| } |
| break; |
| case 'o': |
| output_file_name = strdup(optarg); |
| break; |
| case 'w': |
| width = atoi(optarg); |
| break; |
| case 'h': |
| height = atoi(optarg); |
| break; |
| case 'm': |
| frames_to_encode = atoi(optarg); |
| break; |
| case 'r': |
| framerate = atoi(optarg); |
| break; |
| case 'b': |
| set_control_value(common_control_list, |
| V4L2_CID_MPEG_VIDEO_BITRATE, |
| atoi(optarg)); |
| set_control_value(common_control_list, |
| V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, |
| atoi(optarg) * 2); |
| break; |
| case 'g': |
| set_control_value(common_control_list, |
| V4L2_CID_MPEG_VIDEO_GOP_SIZE, |
| atoi(optarg)); |
| break; |
| case 'c': |
| if (strlen(optarg) == 4) { |
| CAPTURE_format = v4l2_fourcc(toupper(optarg[0]), toupper(optarg[1]), |
| toupper(optarg[2]), toupper(optarg[3])); |
| printf("using (%s) as the codec\n", optarg); |
| } |
| break; |
| case 'e': |
| if (strlen(optarg) == 3 && toupper(optarg[0]) == 'C' && |
| toupper(optarg[1]) == 'B' && toupper(optarg[2]) == 'R') { |
| set_control_value(common_control_list, |
| V4L2_CID_MPEG_VIDEO_BITRATE_MODE, |
| V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); |
| } |
| break; |
| case 'q': |
| if (strlen(optarg) == 4) { |
| OUTPUT_format = v4l2_fourcc(toupper(optarg[0]), toupper(optarg[1]), |
| toupper(optarg[2]), toupper(optarg[3])); |
| printf("using (%s) as the OUTPUT_queue buffer format\n", optarg); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| const int bitrate_mode = get_control_value(common_control_list, |
| V4L2_CID_MPEG_VIDEO_BITRATE_MODE); |
| fprintf( |
| stderr, "encoding %d frames using %s bitrate control\n", frames_to_encode, |
| (bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) ? "CBR" : "VBR"); |
| |
| int v4lfd = -1; |
| const size_t kEncodeDeviceFilesSize = |
| sizeof(kEncodeDeviceFiles) / sizeof(char*); |
| for (int index = 0; index < kEncodeDeviceFilesSize; ++index) { |
| int temp_fd = |
| open(kEncodeDeviceFiles[index], O_RDWR | O_NONBLOCK | O_CLOEXEC); |
| if (temp_fd < 0) |
| continue; |
| #if defined(USE_V4LPLUGIN) |
| v4l2_fd_open(temp_fd, V4L2_DISABLE_CONVERSION); |
| #endif |
| |
| if (verify_capabilities(temp_fd, OUTPUT_format, CAPTURE_format) == 0) { |
| v4lfd = temp_fd; |
| break; |
| } else { |
| close(temp_fd); |
| } |
| } |
| |
| if (v4lfd < 0) { |
| fprintf(stderr, |
| "Did not find a device file with the needed capabilities\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (!file_name || !output_file_name || width == 0 || height == 0) { |
| fprintf(stderr, "Invalid parameters!\n"); |
| print_help(argv[0]); |
| exit(1); |
| } |
| |
| struct file_info fi = {.format = file_format}; |
| fi.fp = fopen(file_name, "rb"); |
| if (!fi.fp) { |
| fprintf(stderr, "%s: unable to open file.\n", file_name); |
| exit(1); |
| } |
| |
| // frame calculations assume 4:2:0 |
| const uint32_t frame_size = (3 * width * height) >> 1; |
| if (!frames_to_encode) { |
| fseek(fi.fp, 0, SEEK_END); |
| uint64_t length = ftell(fi.fp); |
| frames_to_encode = length / frame_size; |
| fseek(fi.fp, 0, SEEK_SET); |
| } |
| |
| uint8_t* frame_buffer = malloc(frame_size); |
| struct file_buffer fb = {.buffer = frame_buffer, |
| .frame_size = frame_size}; |
| |
| fprintf(stderr, "encoding %d frames using %s bitrate control\n", |
| frames_to_encode, |
| (bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) ? "CBR" : "VBR"); |
| |
| struct queue OUTPUT_queue = {.v4lfd = v4lfd, |
| .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, |
| .fourcc = OUTPUT_format, |
| .raw_width = width, |
| .raw_height = height, |
| .frame_cnt = 0, |
| .num_planes = 1, |
| .framerate = framerate}; |
| |
| struct queue CAPTURE_queue = {.v4lfd = v4lfd, |
| .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, |
| .fourcc = CAPTURE_format, |
| .raw_width = width, |
| .raw_height = height, |
| .num_planes = 1, |
| .framerate = framerate}; |
| |
| int ret = Initialization(&OUTPUT_queue, &CAPTURE_queue); |
| |
| if (!ret) { |
| // not all configurations are supported on all platforms |
| disable_unsupported_controls(v4lfd, common_control_list); |
| disable_unsupported_controls(v4lfd, h264_control_list); |
| ret = set_ext_controls(v4lfd, common_control_list); |
| |
| if (!ret && v4l2_fourcc('H', '2', '6', '4') == CAPTURE_format) |
| ret = set_ext_controls(v4lfd, h264_control_list); |
| } |
| |
| if (!ret) { |
| ret = encode(&fb, &fi, output_file_name, &OUTPUT_queue, |
| &CAPTURE_queue, frames_to_encode); |
| } |
| |
| cleanup_queue(&OUTPUT_queue); |
| cleanup_queue(&CAPTURE_queue); |
| #if defined(USE_V4LPLUGIN) |
| v4l2_close(v4lfd); |
| #endif |
| |
| free(fb.buffer); |
| close(v4lfd); |
| |
| return 0; |
| } |