blob: 3446fc65d9425192c1c7b89d5c7dc6f8aca567cf [file] [log] [blame]
/*
* 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,
&timestamp, 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;
}