├── .gitignore ├── h264.h ├── conf.h ├── Makefile ├── cam.h ├── util.h ├── util.c ├── README.md ├── main.c ├── h264.c └── cam.c /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.o 3 | .vscode 4 | -------------------------------------------------------------------------------- /h264.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _h264_h_ 3 | #define _h264_h_ 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "conf.h" 11 | #include "util.h" 12 | 13 | int h264_init(int width, int height, int fps); 14 | 15 | int h264_encode(unsigned char *AddrVirY, unsigned char *AddrVirC); 16 | 17 | void h264_deinit(); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /conf.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _conf_h_ 3 | #define _conf_h_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | //#define ENABLE_DEBUG 10 | #define ENABLE_DLOG 11 | #define ENABLE_SAVE 0 12 | 13 | #define G_BUF_COUNT 5 14 | #define G_WIDTH 1920 15 | #define G_HEIGHT 1080 16 | #define G_V4L2_PIX_FMT V4L2_PIX_FMT_NV12 17 | #define G_FPS 30 18 | #define G_SUBDEV_ENTITY_NAME "ov5640 1-003c" 19 | 20 | #define G_FRAMES 450 // 15 seconds at 30fps 21 | 22 | #define G_CEDARC_PIX_FMT VENC_PIXEL_YUV420SP 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # If you're not building this package within Buildroot, then 3 | # you need to uncomment & adapt the following three variables, 4 | # and comment out the existing LDFLAGS variable (since it 5 | # contains $(TARGET_DIR), which is not set outside the 6 | # Buildroot environment. 7 | 8 | # BR2 = /path/to/buildroot-x.y.z 9 | # CC = $(BR2)/output/host/bin/arm-buildroot-linux-gnueabihf-gcc 10 | # LDFLAGS = -L$(BR2)/output/target/usr/lib 11 | 12 | LDFLAGS = -L$(TARGET_DIR)/usr/lib 13 | LDLIBS = -lVE -lvencoder -lMemAdapter 14 | 15 | all: main 16 | 17 | main: conf.h cam.o util.o h264.o 18 | h264.o: conf.h util.o 19 | cam.o: conf.h util.o 20 | util.o: conf.h util.h 21 | 22 | clean: 23 | rm -f $(wildcard *.o) 24 | rm -f main 25 | 26 | -------------------------------------------------------------------------------- /cam.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _cam_h_ 3 | #define _cam_h_ 4 | 5 | #include 6 | #include 7 | #include "conf.h" 8 | #include "util.h" 9 | 10 | // custom ioctl to translate virtual address to physical addresses 11 | #define CAM_V2P_IOCTL 12345677 12 | 13 | typedef struct { 14 | void *start; 15 | void *addrVirY; 16 | void *addrVirC; 17 | void *addrPhyY; 18 | void *addrPhyC; 19 | size_t length; 20 | } buffer_t; 21 | 22 | /* 23 | All functions return 0 upon success 24 | and negative upon failure, unless 25 | explicitly stated otherwise. 26 | */ 27 | 28 | int cam_open(); 29 | 30 | int cam_init(unsigned int width, unsigned int height, unsigned int pixfmt, unsigned int fps); 31 | 32 | int cam_start_capture(); 33 | 34 | // Returns non-negative dequeued buffer index upon success 35 | int cam_dqbuf(); 36 | 37 | // Returns pointer to buffer upon success, NULL otherwise 38 | buffer_t *cam_get_buf(int idx); 39 | 40 | int cam_qbuf(); 41 | 42 | int cam_stop_capture(); 43 | 44 | void cam_deinit(); 45 | 46 | void cam_close(); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _util_h_ 3 | #define _util_h_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define CLEAR(x) memset(&(x), 0, sizeof(x)) 10 | 11 | #define perror_ret(x, y) do{ if ((x)<0) {perror(y); return -1;} }while(0) 12 | #define perror_cleanup(x, y) do{ if ((x)<0) {perror(y); ret = -1; goto cleanup;} }while(0) 13 | //#define dlog_cleanup(x, ...) do{ if ((x)<0) {dlog(__VA_ARGS__); goto cleanup;} }while(0) 14 | #define dlog_cleanup(x, y) do{ if ((x)<0) {dlog(y); ret = -1; goto cleanup;} }while(0) 15 | 16 | #define ALIGN_4K(x) (((x) + 4095) & ~4095) 17 | #define ALIGN_1K(x) (((x) + 1023) & ~1023) 18 | #define ALIGN_32B(x) (((x) + 31) & ~31) 19 | #define ALIGN_16B(x) (((x) + 15) & ~15) 20 | #define ALIGN_8B(x) (((x) + 7) & ~7) 21 | 22 | // Debug logging function (simple wrapper to fprintf) 23 | 24 | #define DLOG_MAX_LEN 512 25 | 26 | #define DLOG_SOH "\001" 27 | #define DLOG_DEBUG DLOG_SOH "1" 28 | #define DLOG_INFO DLOG_SOH "2" 29 | #define DLOG_WARN DLOG_SOH "3" 30 | #define DLOG_ERR DLOG_SOH "4" 31 | #define DLOG_CRIT DLOG_SOH "5" 32 | 33 | #define DLOG_SOH_CHAR '\001' 34 | #define DLOG_DEBUG_CHAR '1' 35 | #define DLOG_INFO_CHAR '2' 36 | #define DLOG_WARN_CHAR '3' 37 | #define DLOG_ERR_CHAR '4' 38 | #define DLOG_CRIT_CHAR '5' 39 | 40 | #define DLOG_DEFAULT_LEVEL_CHAR DLOG_INFO_CHAR 41 | 42 | void dlog_set_level(char *level); 43 | void dlog(const char *format, ...); 44 | 45 | void rt_timer_start(); 46 | void rt_timer_stop(); 47 | double rt_timer_elapsed(); 48 | 49 | #endif -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "conf.h" 11 | #include "util.h" 12 | 13 | static char DLOG_LEVEL = DLOG_DEFAULT_LEVEL_CHAR; 14 | #define DLOG_FP(level) (level >= DLOG_ERR_CHAR ? stderr : stdout) 15 | 16 | void dlog(const char *format, ...) { 17 | #ifdef ENABLE_DLOG 18 | va_list args; 19 | va_start(args, format); 20 | if (strnlen(format, DLOG_MAX_LEN) >= 2) { 21 | if (format[0] == DLOG_SOH_CHAR) { 22 | if (format[1] >= DLOG_LEVEL) 23 | vfprintf(DLOG_FP(format[1]), format + 2, args); 24 | goto done; 25 | } 26 | } 27 | // No level specified; defaulting to DLOG_INFO 28 | if (DLOG_INFO_CHAR >= DLOG_LEVEL) 29 | vfprintf(stdout, format, args); 30 | done: 31 | va_end(args); 32 | #endif 33 | } 34 | 35 | void dlog_set_level(char *level) { 36 | dlog(DLOG_INFO "Info: DLOG_LEVEL changed to %c\n", level[1]); 37 | DLOG_LEVEL = level[1]; 38 | } 39 | 40 | 41 | // --- FOR SINGLE THREAD USE ONLY --- 42 | static struct timespec _time_start, _time_stop; 43 | 44 | void rt_timer_start() { 45 | clock_gettime(CLOCK_REALTIME, &_time_start); 46 | } 47 | 48 | void rt_timer_stop() { 49 | clock_gettime(CLOCK_REALTIME, &_time_stop); 50 | } 51 | 52 | double rt_timer_elapsed() { 53 | long seconds = _time_stop.tv_sec - _time_start.tv_sec; 54 | long nanoseconds = _time_stop.tv_nsec - _time_start.tv_nsec; 55 | return (double) (seconds + (double) nanoseconds / 1e9); 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Zero-Copy H.264 Encoding Demo on Mainline Linux 5.19 for Allwinner V3s/S3 3 | 4 | This demo program encodes frames from the DVP camera into H.264 NAL units, and is tested with mainline Linux (5.19.3). This demo is best used as a package within the [v3s3](https://github.com/Unturned3/v3s3) buildroot tree, since it relies on a few patches to the kernel to work. 5 | 6 | > Allwinner S3 support is unverified, due to the lack of test hardware; however, it uses the same die as V3s, so I wouldn't expect any differences. 7 | 8 | ## Features 9 | 10 | - Uses the latest mainline software (Linux v5.19) with minimum modifications. No dependency on Allwinner's old Linux 3.4 BSP kernel. 11 | - Uses [cedar](https://github.com/aodzip/cedar/) and [libcedarc](https://github.com/aodzip/libcedarc) to interface with the CedarVE hardware video codec block. Unfortunately, some binary blobs are still used (in libcedarc). 12 | - 1920x1080 @ 30FPS video encoding, tested on the LicheePi Zero development board with an OV5640 image sensor 13 | - Zero-copy: frames captured by DVP camera is stored in a buffer shared with the video encoding engine (achieved by a hack detailed below) 14 | - Efficient memory usage; CPU consumption during video encoding is nearly 0%. 15 | 16 | ## Zero-Copy Video Encoding 17 | 18 | The proper way to achieve zero-copy would be to utilize `V4L2_MEMORY_DMABUF` or `V4L2_MEMORY_USERPTR` to share buffers. However, the former is unsupported by `libcedarc`, while the latter is unsupported by `sun6i-video`. This leaves us with `V4L2_MEMORY_MMAP`, but there is no way to obtain the physical addresses of the allocated buffers, which the video engine needs if we want to share our pre-allocated buffers with it. 19 | 20 | This issue was resolved by a dirty hack: I added a custom ioctl() to the existing `sun6i-video` driver and borrowed some logic from deep within the `videobuf2` and `dma-buf` frameworks, which allowed me to find the DMA address (i.e. `dma_addr_t`) associated with the buffer allocated by `V4L2_MMAP` and convert it to a physical address (`phys_addr_t`) via `virt_to_phys()`. I'm 99% sure this is NOT the proper way to do this, but I've tested it many times and it _seems_ to work... If anyone knows a better way to achieve zero-copy video encoding on this platform, I'd love to hear about it. 21 | 22 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "h264.h" 20 | #include "cam.h" 21 | #include "util.h" 22 | #include "conf.h" 23 | 24 | /* 25 | More detail on the v4l2 / media-ctl API: 26 | https://docs.kernel.org/userspace-api/media/v4l/v4l2.html 27 | https://www.kernel.org/doc/html/latest/userspace-api/media/mediactl/media-controller.html 28 | 29 | Getting subdevice name from major & minor numbers: 30 | https://git.linuxtv.org/v4l-utils.git/tree/utils/media-ctl/libmediactl.c 31 | in function 'media_get_devname_sysfs()' 32 | */ 33 | 34 | void usage(char *argv0) { 35 | dlog(DLOG_WARN 36 | "Usage: %s [width] [height] [FPS] [n_frames]\n" 37 | "Supported formats: 640x480, 1280x720, 1920x1080\n" 38 | "All formats support 30FPS; 640x480 also supports 60FPS.\n" 39 | "n_frames: number of frames to capture; defaults to 450 if omitted.\n" 40 | , argv0); 41 | } 42 | 43 | int main(int argc, char **argv) { 44 | #ifdef ENABLE_DEBUG 45 | dlog_set_level(DLOG_DEBUG); 46 | #endif 47 | int ret = 0; 48 | 49 | if (argc < 4 || argc > 5) { 50 | usage(argv[0]); 51 | return 0; 52 | } 53 | 54 | int width = atoi(argv[1]); 55 | int height = atoi(argv[2]); 56 | int fps = atoi(argv[3]); 57 | int n_frames = G_FRAMES; 58 | if (argc == 5) { 59 | n_frames = atoi(argv[4]); 60 | dlog_cleanup(n_frames, DLOG_CRIT "Error: n_frames must be non-negative\n"); 61 | } 62 | 63 | if ((width == 640 && height == 480) || 64 | (width == 1280 && height == 720) || 65 | (width == 1920 && height == 1080)) { 66 | 67 | dlog_cleanup(h264_init(width, height, fps), DLOG_CRIT "Error: h264_init() failed\n"); 68 | dlog_cleanup(cam_open(), DLOG_CRIT "Error: cam_open() failed\n"); 69 | dlog_cleanup(cam_init(width, height, G_V4L2_PIX_FMT, fps), 70 | DLOG_CRIT "Error: cam_init() failed\n"); 71 | } 72 | else { 73 | dlog(DLOG_CRIT "Error: unsupported width/height\n"); 74 | usage(argv[0]); 75 | return 0; 76 | } 77 | 78 | dlog_cleanup(cam_start_capture(), DLOG_CRIT "Error: cam_start_capture() failed\n"); 79 | 80 | rt_timer_start(); 81 | 82 | // Capture frames 83 | for (int i=0; iaddrPhyY, buf->addrPhyC), DLOG_CRIT "Error: h264_encode() failed\n"); 92 | 93 | if (ENABLE_SAVE) { 94 | char output_file[32] = "frame"; 95 | char suffix_str[32]; 96 | sprintf(suffix_str, "%02d", i); 97 | strcat(output_file, suffix_str); 98 | FILE *fp = fopen(output_file, "wb"); 99 | fwrite(buf->start, 1, buf->length, fp); 100 | fclose(fp); 101 | } 102 | 103 | // Queue the recently dequeued buffer back to the device 104 | dlog_cleanup(cam_qbuf(), DLOG_CRIT "Error: cam_qbuf() failed\n"); 105 | } 106 | 107 | rt_timer_stop(); 108 | double elapsed = rt_timer_elapsed(); 109 | 110 | dlog("\nInfo: captured %d frames in %.2fs; FPS = %.1f\n", 111 | n_frames, elapsed, n_frames / elapsed); 112 | 113 | cleanup: 114 | cam_stop_capture(); 115 | cam_deinit(); 116 | cam_close(); 117 | h264_deinit(); 118 | return ret; 119 | } 120 | 121 | -------------------------------------------------------------------------------- /h264.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "cam.h" 10 | #include "util.h" 11 | #include "conf.h" 12 | 13 | static VideoEncoder *gVideoEnc = NULL; 14 | static VencBaseConfig baseConfig; 15 | static int g_width = G_WIDTH; 16 | static int g_height = G_HEIGHT; 17 | static int g_pix_fmt = G_CEDARC_PIX_FMT; 18 | static int g_fps = G_FPS; 19 | 20 | FILE *fpH264 = NULL; 21 | 22 | int h264_init(int width, int height, int fps) { 23 | 24 | g_width = width; 25 | g_height = height; 26 | g_fps = fps; 27 | 28 | fpH264 = fopen("/mnt/out.h264", "wb"); 29 | if (fpH264 == NULL) { 30 | dlog(DLOG_CRIT "Error: failed to open /mnt/out.h264 for writing\n"); 31 | return -1; 32 | } 33 | 34 | VencH264Param h264Param = { 35 | .bEntropyCodingCABAC = 1, 36 | .nBitrate = 8 * 1024 * 1024, 37 | .nFramerate = g_fps, 38 | .nCodingMode = VENC_FRAME_CODING, 39 | .nMaxKeyInterval = 30, 40 | .sProfileLevel.nProfile = VENC_H264ProfileHigh, 41 | .sProfileLevel.nLevel = VENC_H264Level42, 42 | .sQPRange.nMinqp = 10, 43 | .sQPRange.nMaxqp = 30, 44 | }; 45 | 46 | CLEAR(baseConfig); 47 | baseConfig.memops = MemAdapterGetOpsS(); 48 | if (baseConfig.memops == NULL) { 49 | dlog(DLOG_CRIT "Error: MemAdapterGetOpsS() failed\n"); 50 | return -1; 51 | } 52 | CdcMemOpen(baseConfig.memops); 53 | 54 | baseConfig.nInputWidth = g_width; 55 | baseConfig.nInputHeight = g_height; 56 | baseConfig.nStride = g_width; 57 | baseConfig.nDstWidth = g_width; 58 | baseConfig.nDstHeight = g_height; 59 | baseConfig.eInputFormat = g_pix_fmt; 60 | 61 | gVideoEnc = VideoEncCreate(VENC_CODEC_H264); 62 | if (gVideoEnc == NULL) { 63 | dlog(DLOG_CRIT "Error: VideoEncCreate() failed\n"); 64 | return -1; 65 | } 66 | 67 | { 68 | VideoEncSetParameter(gVideoEnc, VENC_IndexParamH264Param, &h264Param); 69 | int value = 0; 70 | VideoEncSetParameter(gVideoEnc, VENC_IndexParamIfilter, &value); 71 | value = 0; 72 | VideoEncSetParameter(gVideoEnc, VENC_IndexParamRotation, &value); 73 | value = 0; 74 | VideoEncSetParameter(gVideoEnc, VENC_IndexParamSetPSkip, &value); 75 | } 76 | VideoEncInit(gVideoEnc, &baseConfig); 77 | 78 | // Write SPS, PPS NAL units 79 | VencHeaderData sps_pps_data; 80 | VideoEncGetParameter(gVideoEnc, VENC_IndexParamH264SPSPPS, &sps_pps_data); 81 | fwrite(sps_pps_data.pBuffer, 1, sps_pps_data.nLength, fpH264); 82 | 83 | dlog(DLOG_INFO "Info: h264 encoder init OK\n"); 84 | return 0; 85 | } 86 | 87 | int h264_encode(unsigned char *addrPhyY, unsigned char *addrPhyC) { 88 | // Prepare buffers 89 | VencInputBuffer inputBuffer; 90 | VencOutputBuffer outputBuffer; 91 | CLEAR(inputBuffer); 92 | CLEAR(outputBuffer); 93 | // Pass pre-allocated buffer (from V4L2) to CedarVE 94 | // No need to use AllocInputBuffer() and copy data unnecessarily 95 | inputBuffer.pAddrPhyY = addrPhyY; 96 | inputBuffer.pAddrPhyC = addrPhyC; 97 | AddOneInputBuffer(gVideoEnc, &inputBuffer); 98 | 99 | if (VideoEncodeOneFrame(gVideoEnc) != VENC_RESULT_OK) { 100 | dlog("Error: VideoEncodeOneFrame() failed\n"); 101 | return -1; 102 | } 103 | 104 | // Mark buffer as used, and get output H.264 bitstream 105 | AlreadyUsedInputBuffer(gVideoEnc, &inputBuffer); 106 | GetOneBitstreamFrame(gVideoEnc, &outputBuffer); 107 | 108 | if (outputBuffer.nSize0 > 0) 109 | fwrite(outputBuffer.pData0, 1, outputBuffer.nSize0, fpH264); 110 | if (outputBuffer.nSize1 > 0) 111 | fwrite(outputBuffer.pData1, 1, outputBuffer.nSize1, fpH264); 112 | 113 | FreeOneBitStreamFrame(gVideoEnc, &outputBuffer); 114 | return 0; 115 | } 116 | 117 | void h264_deinit() { 118 | if (baseConfig.memops) { 119 | CdcMemClose(baseConfig.memops); 120 | baseConfig.memops = NULL; 121 | } 122 | if (gVideoEnc) { 123 | ReleaseAllocInputBuffer(gVideoEnc); 124 | VideoEncDestroy(gVideoEnc); 125 | gVideoEnc = NULL; 126 | } 127 | if (fpH264) { 128 | fclose(fpH264); 129 | fpH264 = NULL; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /cam.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "cam.h" 21 | #include "util.h" 22 | #include "conf.h" 23 | 24 | static int fd = -1; 25 | static int g_width = 640, g_height = 480, g_fps = G_FPS; 26 | static int g_buf_count = G_BUF_COUNT; 27 | static int buf_idx = 0; 28 | static buffer_t *buffers = NULL; 29 | 30 | // List of V4L2 controls that will be set upon device init 31 | #define NUM_CTRLS 2 32 | struct v4l2_control ctrls[NUM_CTRLS] = { 33 | {V4L2_CID_HFLIP, 1}, 34 | {V4L2_CID_VFLIP, 1} 35 | }; 36 | 37 | #ifdef ENABLE_DEBUG 38 | static int check_cnt = 0; 39 | #endif 40 | 41 | int sanity_check(int buf_idx, int offset) { 42 | #ifdef ENABLE_DEBUG 43 | int addr = offset; 44 | if (check_cnt % 30 == 29) { 45 | dlog(DLOG_DEBUG "Debug: sanity check: offset = %d\n", offset); 46 | } 47 | perror_ret(ioctl(fd, CAM_V2P_IOCTL, &addr), "CAM_V2P_IOCTL"); 48 | assert(buffers[buf_idx].addrPhyY == (void *) addr); 49 | check_cnt += 1; 50 | #endif 51 | return 0; 52 | } 53 | 54 | int cam_open() { 55 | fd = open("/dev/video0", O_RDWR, 0); 56 | perror_ret(fd, "open /dev/video0"); 57 | return 0; 58 | } 59 | 60 | // Initialize media bus settings 61 | // This is needed for DVP cameras that uses the V4L2 subdev API 62 | // Probably won't work for USB webcams and such 63 | // For more info, see 64 | // https://www.kernel.org/doc/html/latest/userspace-api/media/mediactl/media-controller.html 65 | // 66 | static int cam_media_init() { 67 | int ret = 0; 68 | struct media_v2_entity *mve = NULL; 69 | struct media_v2_pad *mvp = NULL; 70 | 71 | // Open media / subdev file descriptors 72 | 73 | int mfd = open("/dev/media0", O_RDWR, 0); 74 | perror_cleanup(mfd, "open /dev/media0"); 75 | int sfd = open("/dev/v4l-subdev0", O_RDWR); 76 | perror_cleanup(sfd, "open /dev/v4l2-subdev0"); 77 | 78 | // Query media API topology 79 | 80 | struct media_v2_topology mvt; 81 | CLEAR(mvt); 82 | perror_cleanup(ioctl(mfd, MEDIA_IOC_G_TOPOLOGY, &mvt), "MEDIA_IOC_G_TOPOLOGY"); 83 | 84 | dlog(DLOG_DEBUG "Debug: %d media entities detected\n", mvt.num_entities); 85 | 86 | mve = calloc(mvt.num_entities, sizeof(*mve)); 87 | mvp = calloc(mvt.num_pads, sizeof(*mvp)); 88 | if (!mve || !mvp) { 89 | dlog(DLOG_CRIT "Error: mve/mvp calloc() failed\n"); 90 | ret = -1; 91 | goto cleanup; 92 | } 93 | mvt.ptr_entities = (unsigned long) mve; 94 | mvt.ptr_pads = (unsigned long) mvp; 95 | perror_cleanup(ioctl(mfd, MEDIA_IOC_G_TOPOLOGY, &mvt), "MEDIA_IOC_G_TOPOLOGY"); 96 | 97 | // Find entity id and subdev pad of device 98 | 99 | int entity_id = -1, subdev_pad = -1; 100 | 101 | for (int i=0; i= 0) { 176 | close(sfd); 177 | sfd = -1; 178 | } 179 | if (mve) 180 | free(mve); 181 | if (mvp) 182 | free(mvp); 183 | if (mfd >= 0) { 184 | close(mfd); 185 | mfd = -1; 186 | } 187 | return ret; 188 | } 189 | 190 | int cam_init(unsigned int width, unsigned int height, unsigned int pixfmt, unsigned int fps) { 191 | 192 | g_width = width; 193 | g_height = height; 194 | g_fps = fps; 195 | 196 | if (cam_media_init() < 0) { 197 | dlog(DLOG_CRIT "Error: cam_media_init() failed\n"); 198 | return -1; 199 | } 200 | 201 | // Query device capabilities 202 | struct v4l2_capability cap; 203 | CLEAR(cap); 204 | perror_ret(ioctl(fd, VIDIOC_QUERYCAP, &cap), "VIDIOC_QUERYCAP"); 205 | 206 | if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || 207 | !(cap.capabilities & V4L2_CAP_STREAMING)) { 208 | dlog(DLOG_CRIT "Error: V4L2_CAP_VIDEO_CAPTURE or V4L2_CAP_STREAMING not supported\n"); 209 | return -1; 210 | } 211 | 212 | // Set V4L2 format 213 | struct v4l2_format fmt = { 214 | .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, 215 | .fmt.pix.width = width, 216 | .fmt.pix.height = height, 217 | .fmt.pix.pixelformat = pixfmt, 218 | }; 219 | perror_ret(ioctl(fd, VIDIOC_S_FMT, &fmt), "VIDIOC_S_FMT"); 220 | 221 | // Set V4L2 controls specified in ctrls[] 222 | for (int i=0; i