Browse Source

ffmpeg_test: add ffmpeg_test package

1. get v4l2 video and display on drm. support sensor with NV12 format and uvc camera with YUYV format

Signed-off-by: andy.hu <andy.hu@starfivetech.com>
andy.hu 2 years ago
parent
commit
afd4e3c90c

+ 1 - 0
package/starfive/Config.in

@@ -7,3 +7,4 @@ source "package/starfive/v4l2_test/Config.in"
 source "package/starfive/pp_test/Config.in"
 source "package/starfive/libcamera-apps/Config.in"
 source "package/starfive/drm_test/Config.in"
+source "package/starfive/ffmpeg_test/Config.in"

+ 8 - 0
package/starfive/ffmpeg_test/Config.in

@@ -0,0 +1,8 @@
+comment "ffmpeg test package"
+
+config BR2_PACKAGE_FFMPEG_TEST
+	bool "ffmpeg_test"
+	depends on BR2_PACKAGE_LIBDRM
+	depends on BR2_PACKAGE_FFMPEG
+	help
+	  Utility for ffmpeg test 

+ 19 - 0
package/starfive/ffmpeg_test/ffmpeg_test.mk

@@ -0,0 +1,19 @@
+################################################################################
+#
+# ffmpegtest
+#
+################################################################################
+FFMPEG_TEST_VERSION:=1.0.0
+FFMPEG_TEST_SITE=$(TOPDIR)/package/starfive/ffmpeg_test/src
+FFMPEG_TEST_SITE_METHOD=local
+
+define FFMPEG_TEST_BUILD_CMDS
+	$(TARGET_MAKE_ENV) $(MAKE) -C $(@D) -f $(@D)/Makefile
+endef
+
+define FFMPEG_TEST_INSTALL_TARGET_CMDS
+	$(INSTALL) -D -m 0755 $(@D)/ffmpeg_dec_test $(TARGET_DIR)/usr/bin/
+endef
+
+FFMPEG_TEST_DEPENDENCIES = libdrm ffmpeg
+$(eval $(generic-package))

+ 31 - 0
package/starfive/ffmpeg_test/src/Makefile

@@ -0,0 +1,31 @@
+CROSS_CC_PREFIX = riscv64-buildroot-linux-gnu-
+CC  = $(CROSS_CC_PREFIX)gcc
+CXX = $(CROSS_CC_PREFIX)g++
+LINKER = $(CC)
+AR  = $(CROSS_CC_PREFIX)ar
+
+CFLAGS = -Wall -g -O2 -D_FILE_OFFSET_BITS=64 
+CFLAGS += -I$(STAGING_DIR)/usr/include/ -I$(STAGING_DIR)/usr/include/drm/
+LDLIBS += -lavdevice -lavformat -lavfilter -lavcodec -lswresample -lswscale \
+		  -lavutil -ldrm -ljpeg
+
+COMMON_SRC = stf_log.c common.c stf_drm.c
+
+TARGET1 = ffmpeg_input_test
+TARGET1_SRC := $(addsuffix .c,$(TARGET1)) $(COMMON_SRC)
+TARGET1_OBJ := $(subst .c,.o,$(TARGET1_SRC))
+
+TARGET2 = ffmpeg_dec_test
+TARGET2_SRC := $(addsuffix .c,$(TARGET2))
+TARGET2_OBJ := $(subst .c,.o,$(TARGET2_SRC))
+
+all: $(TARGET1) $(TARGET2)
+
+$(TARGET1): $(TARGET1_OBJ)
+$(TARGET2): $(TARGET2_OBJ)
+
+clean:
+	rm -rf *.o $(TARGET1) $(TARGET2)
+
+# %.o: %.c
+# 	$(CC) -fPIC -shared $(FLAGS) -Wall -Werror -c $< -o $@ -MD -MF $(@:.o=.dep)

+ 261 - 0
package/starfive/ffmpeg_test/src/common.c

@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include "common.h"
+#include <stddef.h>
+#include <jpeglib.h>
+#include <libdrm/drm_fourcc.h>
+#include <libavcodec/avcodec.h>
+
+#include "stf_log.h"
+
+/**
+    Print error message and terminate programm with EXIT_FAILURE return code.
+    \param s error message to print
+*/
+void errno_exit(const char* s)
+{
+    LOG(STF_LEVEL_ERR, "%s error %d, %s\n", s, errno, strerror(errno));
+    exit(EXIT_FAILURE);
+}
+
+void errno_print(const char *s)
+{
+    LOG(STF_LEVEL_ERR, "%s error %d, %s\n", s, errno, strerror(errno));
+}
+
+void dump_fourcc(uint32_t fourcc)
+{
+    LOG(STF_LEVEL_LOG, " %c%c%c%c \n",
+        fourcc,
+        fourcc >> 8,
+        fourcc >> 16,
+        fourcc >> 24);
+}
+
+// convert v4l2 format to fb format
+int v4l2fmt_to_fbfmt(uint32_t format)
+{
+    int pixformat = COLOR_RGB565;
+
+    switch (format) {
+    case V4L2_PIX_FMT_RGB565:
+        pixformat = COLOR_RGB565;
+        break;
+    case V4L2_PIX_FMT_RGB24:
+        pixformat = COLOR_RGB888_ARGB;
+        break;
+    case V4L2_PIX_FMT_YUV420:
+        pixformat = COLOR_YUV420P;
+        break;
+    case V4L2_PIX_FMT_YUYV:
+        pixformat = COLOR_YUV422_YUYV;
+        break;
+    case V4L2_PIX_FMT_NV21:
+        pixformat = COLOR_YUV420_NV21;
+        break;
+    case V4L2_PIX_FMT_NV12:
+        pixformat = COLOR_YUV420_NV12;
+        break;
+    case V4L2_PIX_FMT_YVYU:
+        pixformat = COLOR_YUV422_YVYU;
+        break;
+    default:
+        pixformat = COLOR_RGB565;
+        break;
+    }
+
+    return pixformat;
+}
+
+// convert v4l2 format to drm format
+uint32_t v4l2fmt_to_drmfmt(uint32_t v4l2_fmt)
+{
+    uint32_t drm_fmt;
+    // dump_fourcc(v4l2_fmt);
+
+    switch (v4l2_fmt) {
+    case V4L2_PIX_FMT_RGB565:
+        drm_fmt = DRM_FORMAT_RGB565;
+        break;
+    case V4L2_PIX_FMT_RGB24:
+        drm_fmt = DRM_FORMAT_ARGB8888;
+        break;
+    case V4L2_PIX_FMT_YUV420:
+        drm_fmt = DRM_FORMAT_YUV420;
+        break;
+    case V4L2_PIX_FMT_YUYV:
+        drm_fmt = DRM_FORMAT_YUYV;
+        break;
+    case V4L2_PIX_FMT_YVYU:
+        drm_fmt = DRM_FORMAT_YVYU;
+        break;
+    case V4L2_PIX_FMT_NV21:
+        drm_fmt = DRM_FORMAT_NV21;
+        break;
+    case V4L2_PIX_FMT_NV12:
+        drm_fmt = DRM_FORMAT_NV12;
+        break;
+    default:
+        drm_fmt = DRM_FORMAT_NV21;
+        LOG(STF_LEVEL_WARN, "drm not support the V4L2_format\n");
+        break;
+    }
+    return drm_fmt;
+}
+
+// convert ffmpeg format to drm format
+uint32_t ffmpegfmt_to_drmfmt(uint32_t ffmpeg_fmt)
+{
+    uint32_t drm_fmt;
+    // dump_fourcc(ffmpeg_fmt);
+
+    switch (ffmpeg_fmt) {
+    case AV_PIX_FMT_ARGB:
+        drm_fmt = DRM_FORMAT_RGB565;
+        break;
+    case V4L2_PIX_FMT_RGB24:
+        drm_fmt = DRM_FORMAT_ARGB8888;
+        break;
+    case AV_PIX_FMT_YUV420P:
+        drm_fmt = DRM_FORMAT_YUV420;
+        break;
+    case AV_PIX_FMT_YUYV422:
+        drm_fmt = DRM_FORMAT_YUYV;
+        break;
+    case AV_PIX_FMT_YVYU422:
+        drm_fmt = DRM_FORMAT_YVYU;
+        break;
+    case AV_PIX_FMT_NV21:
+        drm_fmt = DRM_FORMAT_NV21;
+        break;
+    case AV_PIX_FMT_NV12:
+        drm_fmt = DRM_FORMAT_NV12;
+        break;
+    default:
+        drm_fmt = DRM_FORMAT_NV21;
+        LOG(STF_LEVEL_WARN, "drm not support the FFMPEG_format %d\n", ffmpeg_fmt);
+        break;
+    }
+    return drm_fmt;
+}
+
+int write_file(char * filename, const uint8_t *image_buffer, int size)
+{
+    /*  More stuff */
+    FILE * outfile;               /*  target file */
+    if ((outfile = fopen(filename, "w+")) == NULL) {
+        LOG(STF_LEVEL_ERR, "can't open %s\n", filename);
+        return -1;
+    }
+    fwrite(image_buffer, size, 1, outfile);
+    fclose(outfile);
+    return 0 ;
+}
+
+int write_JPEG_file(char * filename, uint8_t *image_buffer,
+        int image_width, int image_height, int quality)
+{
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    /*  More stuff */
+    FILE * outfile;               /*  target file */
+    JSAMPROW row_pointer[1];      /*  pointer to JSAMPLE row[s] */
+    int row_stride;               /*  physical row width in image buffer */
+
+    /*  Step 1: allocate and initialize JPEG compression object */
+    cinfo.err = jpeg_std_error(&jerr);
+    /*  Now we can initialize the JPEG compression object. */
+    jpeg_create_compress(&cinfo);
+
+    /*  Step 2: specify data destination (eg, a file) */
+    /*  Note: steps 2 and 3 can be done in either order. */
+    if ((outfile = fopen(filename, "w+")) == NULL) {
+        LOG(STF_LEVEL_ERR, "can't open %s\n", filename);
+        return -1;
+    }
+    jpeg_stdio_dest(&cinfo, outfile);
+
+    /*  Step 3: set parameters for compression */
+    cinfo.image_width = image_width;      /*  image width and height, in pixels */
+    cinfo.image_height = image_height;
+    cinfo.input_components = 3;           /*  # of color components per pixel */
+    cinfo.in_color_space = JCS_RGB;       /*  colorspace of input image */
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, TRUE /*  limit to baseline-JPEG values */);
+
+    /*  Step 4: Start compressor */
+    jpeg_start_compress(&cinfo, TRUE);
+
+    /*  Step 5: while (scan lines remain to be written) */
+    row_stride = image_width * 3; /*  JSAMPLEs per row in image_buffer */
+    while (cinfo.next_scanline < cinfo.image_height) {
+        row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
+        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+    }
+
+    /*  Step 6: Finish compression */
+    jpeg_finish_compress(&cinfo);
+    /*  After finish_compress, we can close the output file. */
+    fclose(outfile);
+
+    /*  Step 7: release JPEG compression object */
+    /*  This is an important step since it will release a good deal of memory. */
+    jpeg_destroy_compress(&cinfo);
+
+    /*  And we're done! */
+    return 0;
+}
+
+void jpegWrite(uint8_t* img, char* jpegFilename,
+        uint32_t width, uint32_t height, int jpegQuality)
+{
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    JSAMPROW row_pointer[1];
+    FILE *outfile = fopen( jpegFilename, "wb" );
+
+    // try to open file for saving
+    if (!outfile) {
+        errno_exit("jpeg");
+    }
+
+    // create jpeg data
+    cinfo.err = jpeg_std_error( &jerr );
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, outfile);
+
+    // set image parameters
+    cinfo.image_width = width;
+    cinfo.image_height = height;
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_YCbCr;
+
+    // set jpeg compression parameters to default
+    jpeg_set_defaults(&cinfo);
+    // and then adjust quality setting
+    jpeg_set_quality(&cinfo, jpegQuality, TRUE);
+
+    // start compress
+    jpeg_start_compress(&cinfo, TRUE);
+
+    // feed data
+    while (cinfo.next_scanline < cinfo.image_height) {
+        row_pointer[0] = &img[cinfo.next_scanline * cinfo.image_width *  cinfo.input_components];
+        jpeg_write_scanlines(&cinfo, row_pointer, 1);
+    }
+
+    // finish compression
+    jpeg_finish_compress(&cinfo);
+
+    // destroy jpeg data
+    jpeg_destroy_compress(&cinfo);
+
+    // close output file
+    fclose(outfile);
+}
+
+

+ 89 - 0
package/starfive/ffmpeg_test/src/common.h

@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+#include <asm/types.h>
+#include <linux/videodev2.h>
+#include <linux/fb.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <malloc.h>
+
+#ifndef TEST_VERSION
+#define TEST_VERSION "v0.0.1"
+#endif
+
+#define BUFCOUNT 4
+
+#define CLEAR(x)  memset (&(x), 0, sizeof (x))
+#define PCLEAR(x) memset ((x), 0, sizeof (*x))
+
+struct buffer {
+    void*   start;
+    size_t  length;
+    int     dmabuf_fd;
+    int     index;
+};
+
+enum COLOR_FORMAT{
+    COLOR_YUV422_UYVY = 0,  // 00={Y1,V0,Y0,U0}
+    COLOR_YUV422_VYUY = 1,  // 01={Y1,U0,Y0,V0}
+    COLOR_YUV422_YUYV = 2,  // 10={V0,Y1,U0,Y0}
+    COLOR_YUV422_YVYU = 3,  // 11={U0,Y1,V0,Y0}
+
+    COLOR_YUV420P,          // 4
+    COLOR_YUV420_NV21,      // 5
+    COLOR_YUV420_NV12,      // 6
+
+    COLOR_RGB888_ARGB,      // 7
+    COLOR_RGB888_ABGR,      // 8
+    COLOR_RGB888_RGBA,      // 9
+    COLOR_RGB888_BGRA,      // 10
+    COLOR_RGB565,           // 11
+};
+
+typedef enum IOMethod {
+    IO_METHOD_MMAP,
+    IO_METHOD_USERPTR,
+    IO_METHOD_DMABUF,
+    IO_METHOD_READ
+} IOMethod;
+
+typedef enum STF_DISP_TYPE {
+    STF_DISP_NONE = 0, // Not display
+    // STF_DISP_FB,       // Use framebuffer framework display
+    STF_DISP_DRM       // Use drm framework display
+} STF_DISP_TYPE;
+
+extern void dump_fourcc(uint32_t fourcc);
+extern int v4l2fmt_to_fbfmt(uint32_t format);
+extern uint32_t v4l2fmt_to_drmfmt(uint32_t v4l2_fmt);
+extern uint32_t ffmpegfmt_to_drmfmt(uint32_t ffmpeg_fmt);
+extern void errno_exit(const char* s);
+extern void errno_print(const char *s);
+extern int write_file(char * filename, const unsigned char *image_buffer, int size);
+// extern void jpegWrite(unsigned char* img, char* jpegFilename);
+extern void jpegWrite(unsigned char* img, char* jpegFilename,
+        uint32_t width, uint32_t height, int jpegQuality);
+extern int write_JPEG_file(char * filename,unsigned char *image_buffer,
+        int image_width, int image_height, int quality);
+
+// inline int clip(int value, int min, int max) {
+//     return (value > max ? max : value < min ? min : value);
+// }
+
+
+#endif // __COMMON_H__

+ 222 - 0
package/starfive/ffmpeg_test/src/ffmpeg_dec_test.c

@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <getopt.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <libv4l2.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libavcodec/avcodec.h>
+
+#define INBUF_SIZE 4096
+#define DRM_DEVICE_NAME      "/dev/dri/card0"
+
+static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
+        char *filename)
+{
+    FILE *f;
+    int i;
+printf("[%s,%d]: wrap=%d, xsize=%d, ysize=%d, filename=%s\n",__FUNCTION__,__LINE__, wrap, xsize, ysize, filename);
+    f = fopen(filename,"w");
+    for (i = 0; i < ysize; i++)
+        fwrite(buf + i * wrap, 1, xsize, f);
+    fclose(f);
+}
+
+static void yuv_save(unsigned char **buf, int *wrap, int xsize, int ysize,
+                     char *filename)
+{
+    FILE *f;
+    int i, j, plane_idx = 0;
+printf("[%s,%d]: wrap=%d, xsize=%d, ysize=%d, filename=%s\n",__FUNCTION__,__LINE__, wrap, xsize, ysize, filename);
+    f = fopen(filename,"w");
+//    fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
+
+    // while (buf[plane_idx] && wrap[plane_idx]) {
+    //     for (i = 0; i < ysize; i++) {
+    //         fwrite(buf[plane_idx] + i * wrap[plane_idx], 1, wrap[plane_idx], f);
+    //     }
+    //     plane_idx ++;
+    // }
+
+    unsigned char *y = buf[0];
+    unsigned char *u = buf[1];
+    unsigned char *v = buf[2];
+    int y_linesize = wrap[0];
+    int u_linesize = wrap[1];
+    int v_linesize = wrap[2];
+
+    fwrite(y, 1, y_linesize*ysize, f);
+    for (i = 0; i < ysize * u_linesize; i++) {
+        fwrite(u+i, 1, 1, f);
+        fwrite(v+i, 1, 1, f);
+    }
+    // for (i = 0; i < ysize; i++) {
+    //     for (j = 0; j < u_linesize; j++) {
+    //         fwrite(u+i*, 1, y_linesize*ysize, f);
+    //     }
+    // }
+
+    fclose(f);
+}
+
+
+static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
+                   const char *filename)
+{
+    char buf[1024];
+    int ret;
+
+    ret = avcodec_send_packet(dec_ctx, pkt);
+    if (ret < 0) {
+        fprintf(stderr, "Error sending a packet for decoding\n");
+        exit(1);
+    }
+
+    while (ret >= 0) {
+        ret = avcodec_receive_frame(dec_ctx, frame);
+        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+            return;
+        else if (ret < 0) {
+            fprintf(stderr, "Error during decoding\n");
+            exit(1);
+        }
+
+        printf("saving frame %3d\n", dec_ctx->frame_number);
+        fflush(stdout);
+
+        /* the picture is allocated by the decoder. no need to
+           free it */
+        snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
+        // pgm_save(frame->data[0], frame->linesize[0],
+        //          frame->width, frame->height, buf);
+        yuv_save(frame->data, frame->linesize,
+                 frame->width, frame->height, buf);
+printf("[%s,%d]: line=%d, %d, %d, %d. frame->data=%p, %p,%p,%p\n",
+    __FUNCTION__,__LINE__, frame->linesize[0], frame->linesize[1], frame->linesize[2], frame->linesize[3],
+    frame->data[0], frame->data[1], frame->data[2],frame->data[3]);
+
+    }
+}
+
+int main(int argc, char **argv)
+{
+    const char *filename, *outfilename;
+    const AVCodec *codec;
+    AVCodecParserContext *parser;
+    AVCodecContext *c= NULL;
+    FILE *f;
+    AVFrame *frame;
+    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
+    uint8_t *data;
+    size_t   data_size;
+    int ret;
+    AVPacket *pkt;
+
+    av_log_set_level(AV_LOG_TRACE);
+    if (argc <= 2) {
+        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
+        exit(0);
+    }
+    filename    = argv[1];
+    outfilename = argv[2];
+
+    pkt = av_packet_alloc();
+    if (!pkt)
+        exit(1);
+
+    /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
+    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
+    /* find the MPEG-1 video decoder */
+    // codec = avcodec_find_decoder(AV_CODEC_ID_H264); // AV_CODEC_ID_MPEG1VIDEO
+    // if (!codec) {
+    //     fprintf(stderr, "Codec not found\n");
+    //     exit(1);
+    // }
+
+	/* find the video decoder: ie: h264_v4l2m2m */
+	codec = avcodec_find_decoder_by_name("h264");  // h264_omx
+	if (!codec) {
+		err("Codec not found\n");
+		exit(1);
+	}
+
+    parser = av_parser_init(codec->id);
+    if (!parser) {
+        fprintf(stderr, "parser not found\n");
+        exit(1);
+    }
+
+    c = avcodec_alloc_context3(codec);
+    if (!c) {
+        fprintf(stderr, "Could not allocate video codec context\n");
+        exit(1);
+    }
+printf("[%s,%d]: \n",__FUNCTION__,__LINE__);
+
+    /* For some codecs, such as msmpeg4 and mpeg4, width and height
+       MUST be initialized there because this information is not
+       available in the bitstream. */
+
+    /* open it */
+    if (avcodec_open2(c, codec, NULL) < 0) {
+printf("[%s,%d]: \n",__FUNCTION__,__LINE__);
+        fprintf(stderr, "Could not open codec\n");
+        exit(1);
+    }
+printf("[%s,%d]: \n",__FUNCTION__,__LINE__);
+    f = fopen(filename, "rb");
+    if (!f) {
+        fprintf(stderr, "Could not open %s\n", filename);
+        exit(1);
+    }
+
+    frame = av_frame_alloc();
+    if (!frame) {
+        fprintf(stderr, "Could not allocate video frame\n");
+        exit(1);
+    }
+
+    while (!feof(f)) {
+        /* read raw data from the input file */
+        data_size = fread(inbuf, 1, INBUF_SIZE, f);
+        if (!data_size)
+            break;
+
+        /* use the parser to split the data into frames */
+        data = inbuf;
+        while (data_size > 0) {
+            ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
+                                   data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
+            if (ret < 0) {
+                fprintf(stderr, "Error while parsing\n");
+                exit(1);
+            }
+            data      += ret;
+            data_size -= ret;
+
+            if (pkt->size)
+                decode(c, frame, pkt, outfilename);
+        }
+    }
+
+    /* flush the decoder */
+    decode(c, frame, NULL, outfilename);
+
+    fclose(f);
+
+    av_parser_close(parser);
+    avcodec_free_context(&c);
+    av_frame_free(&frame);
+    av_packet_free(&pkt);
+
+    return 0;
+}
+

+ 473 - 0
package/starfive/ffmpeg_test/src/ffmpeg_input_test.c

@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <libv4l2.h>
+#include <poll.h>
+#include <libavcodec/avcodec.h>
+#include <libavdevice/avdevice.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include "common.h"
+#include "stf_drm.h"
+#include "stf_log.h"
+
+#define DRM_DEVICE_NAME      "/dev/dri/card0"
+#define V4L2_DFT_DEVICE_NAME "/dev/video0"
+#define FFMPEG_INPUT_NAME    "video4linux2"
+
+typedef struct enum_value_t {
+    int    value;
+    const char *name;
+} enum_value_t;
+
+static const enum_value_t g_disp_values[] = {
+    { STF_DISP_NONE, "NONE"},
+    // { STF_DISP_FB,   "FB"},
+    { STF_DISP_DRM,  "DRM"}
+};
+
+static const enum_value_t g_iomthd_values[] = {
+    { IO_METHOD_MMAP,    "MMAP"},
+    { IO_METHOD_USERPTR, "USERPTR"},
+    { IO_METHOD_DMABUF,  "DMABUF"},
+    { IO_METHOD_READ,    "READ"}
+};
+
+typedef struct {
+    DRMParam_t drm_param;
+
+    char *device_name;  // v4l2 device name
+    char* out_file;    // save file
+    FILE* out_fp;  // for save file
+
+    uint32_t format;    // V4L2_PIX_FMT_RGB565
+    uint32_t width;
+    uint32_t height;
+    uint32_t fps;
+    uint32_t image_size;
+
+    enum STF_DISP_TYPE disp_type;
+    enum IOMethod    io_mthd;
+    int continuous;
+
+    uint8_t jpegQuality;
+
+    int dmabufs[BUFCOUNT];  // for dmabuf use, mmap not use it
+
+    AVFormatContext* format_ctx;
+    AVCodecContext* vcodec_ctx;
+    AVPacket* packet;
+} ConfigParam_t;
+ConfigParam_t *gp_cfg_param = NULL;
+
+static void sync_handler (int fd, unsigned int frame,
+        unsigned int sec, unsigned int usec,
+        void *data)
+{
+    int *waiting;
+    waiting = data;
+    *waiting = 0;
+}
+
+int sync_drm_frame(int buf_id)
+{
+    int ret;
+    int waiting = 1;
+    struct pollfd* fds = NULL;
+    uint32_t nfds = 0;
+    drmEventContext evctxt = {
+        .version = DRM_EVENT_CONTEXT_VERSION,
+        .page_flip_handler = sync_handler,
+        .vblank_handler = NULL,
+    };
+
+    ret = drmModePageFlip (gp_cfg_param->drm_param.fd, gp_cfg_param->drm_param.dev_head->crtc_id,
+            gp_cfg_param->drm_param.dev_head->bufs[buf_id].fb_id,
+            DRM_MODE_PAGE_FLIP_EVENT, &waiting);
+    if (ret) {
+        goto pageflip_failed;
+    }
+
+    nfds = 1;
+    fds = (struct pollfd*)malloc(sizeof(struct pollfd) * nfds);
+    memset(fds, 0, sizeof(struct pollfd) * nfds);
+    fds[0].fd = gp_cfg_param->drm_param.fd;
+    fds[0].events = POLLIN;
+
+    while (waiting) {
+        do {
+            ret = poll(fds, nfds, 3000);
+        } while (ret == -1 && (errno == EAGAIN || errno == EINTR));
+        ret = drmHandleEvent(gp_cfg_param->drm_param.fd, &evctxt);
+        if (ret) {
+            goto event_failed;
+        }
+    }
+    return 0;
+
+pageflip_failed:
+    {
+        LOG(STF_LEVEL_ERR, "drmModePageFlip failed: %s (%d)\n",
+                strerror(errno), errno);
+        return 1;
+    }
+event_failed:
+    {
+        LOG(STF_LEVEL_ERR, "drmHandleEvent failed: %s (%d)\n",
+                strerror(errno), errno);
+        return 1;
+    }
+}
+
+void mainloop(void)
+{
+    AVFormatContext* fmtCtx = gp_cfg_param->format_ctx;
+    AVPacket* packet = gp_cfg_param->packet;
+    FILE* fp = gp_cfg_param->out_fp;
+    uint32_t buf_id = 0;
+
+    while (gp_cfg_param->continuous) {
+        av_read_frame(fmtCtx, packet);
+        if (STF_DISP_DRM == gp_cfg_param->disp_type) {
+            LOG(STF_LEVEL_TRACE, "data length = %d, drm buf length = %d\n", packet->size, 
+                    gp_cfg_param->drm_param.dev_head->bufs[buf_id].size);
+            memcpy(gp_cfg_param->drm_param.dev_head->bufs[buf_id].buf, packet->data, packet->size);
+            sync_drm_frame(buf_id++);
+            if (buf_id >= BUFCOUNT) {
+                buf_id = 0;
+            }
+        }
+        if (fp) {
+            LOG(STF_LEVEL_INFO, "save file: %s\n", gp_cfg_param->out_file);
+            fwrite(packet->data, 1, packet->size, fp);
+        }
+        av_packet_unref(packet);
+    }
+}
+
+static void alloc_default_config(ConfigParam_t **pp_data)
+{
+    ConfigParam_t *cfg_param = NULL;
+    cfg_param = malloc(sizeof(*cfg_param));
+    if (!cfg_param) {
+        errno_exit("malloc");
+    }
+    memset(cfg_param, 0, sizeof(*cfg_param));
+
+    cfg_param->device_name = V4L2_DFT_DEVICE_NAME;
+    cfg_param->io_mthd = IO_METHOD_MMAP;
+    cfg_param->format = V4L2_PIX_FMT_NV12;
+    cfg_param->continuous = 1;
+    *pp_data = cfg_param;
+}
+
+static void usage(FILE* fp, int argc, char** argv)
+{
+    fprintf(fp,
+        "Usage: %s [options]\n\n"
+        "Options:\n"
+        "-d | --device name   Video device name [default /dev/video0]\n"
+        "-h | --help          Print this message\n"
+        "-o | --output        Set output filename\n"
+        "-W | --width         Set v4l2 image width, default 1920\n"
+        "-H | --height        Set v4l2 image height, default 1080\n"
+        "-I | --interval      Set frame interval (fps) (-1 to skip)\n"
+        "-c | --continuous    Do continous capture, stop with SIGINT.\n"
+        "-v | --version       Print version\n"
+        "-f | --format        image format, default 4\n"
+        "                0: V4L2_PIX_FMT_RGB565\n"
+        "                1: V4L2_PIX_FMT_RGB24\n"
+        "                2: V4L2_PIX_FMT_YUV420\n"
+        "                3: V4L2_PIX_FMT_YUYV\n"
+        "                4: V4L2_PIX_FMT_NV21\n"
+        "                5: V4L2_PIX_FMT_NV12\n"
+        "                6: V4L2_PIX_FMT_YVYU\n"
+        "                7: V4L2_PIX_FMT_SRGGB12\n"
+        "                8: V4L2_PIX_FMT_SGRBG12\n"
+        "                9: V4L2_PIX_FMT_SGBRG12\n"
+        "                10: V4L2_PIX_FMT_SBGGR12\n"
+        "                default: V4L2_PIX_FMT_RGB565\n"
+        "-t | --distype       set display type, default 0\n"
+        "                0: Not display\n"
+        "                1: Use DRM Display\n"
+        "\n"
+        "Eg:\n"
+        "\t sensor : ffmpeg_input_test -d /dev/video2 -f 5 -W 640 -H 480 -I 30 -t 1\n"
+        "\t uvc cam: ffmpeg_input_test -d /dev/video5 -f 3 -W 640 -H 480 -I 30 -t 1\n"
+        "\n"
+        "Open debug log level: \n"
+        "\t export V4L2_DEBUG=3\n"
+        "\t default level 1, level range 0 ~ 7\n"
+        "",
+        argv[0]);
+}
+
+static const char short_options [] = "d:ho:W:H:I:vcf:t:X:Y:";
+
+static const struct option long_options [] = {
+    { "device",     required_argument,      NULL,           'd' },
+    { "help",       no_argument,            NULL,           'h' },
+    { "output",     required_argument,      NULL,           'o' },
+    { "width",      required_argument,      NULL,           'W' },
+    { "height",     required_argument,      NULL,           'H' },
+    { "interval",   required_argument,      NULL,           'I' },
+    { "version",    no_argument,            NULL,           'v' },
+    { "continuous", no_argument,            NULL,           'c' },
+    { "format",     required_argument,      NULL,           'f' },
+    { "distype",    required_argument,      NULL,           't' },
+    { 0, 0, 0, 0 }
+};
+
+void parse_options(int argc, char **argv, ConfigParam_t *cfg_param)
+{
+    int index, c = 0;
+    int value = 0;
+
+    while ((c = getopt_long(argc, argv, short_options, long_options, &index)) != -1) {
+        switch (c) {
+        case 0: /* getopt_long() flag */
+            break;
+
+        case 'd':
+            cfg_param->device_name = strdup(optarg);
+            break;
+
+        case 'h':
+            usage(stdout, argc, argv);
+            exit(EXIT_SUCCESS);
+
+        case 'o':
+            // set filename
+            cfg_param->out_file = strdup(optarg);
+            LOG(STF_LEVEL_INFO, "save file: %s\n", cfg_param->out_file);
+            break;
+
+        case 'W':
+            // set v4l2 width
+            cfg_param->width = atoi(optarg);
+            break;
+
+        case 'H':
+            // set v4l2 height
+            cfg_param->height = atoi(optarg);
+            break;
+
+        case 'I':
+            // set fps
+            cfg_param->fps = atoi(optarg);
+            break;
+
+        case 'c':
+            // set flag for continuous capture, interuptible by sigint
+            cfg_param->continuous = 1;
+            // InstallSIGINTHandler();
+            break;
+
+        case 'v':
+            printf("Version: %s\n", TEST_VERSION);
+            exit(EXIT_SUCCESS);
+            break;
+
+        case 'f':
+            LOG(STF_LEVEL_INFO, "v4l2 format: %s\n", optarg);
+            value = atoi(optarg);
+            LOG(STF_LEVEL_INFO, "v4l2 format: %d\n", value);
+            switch (value) {
+            case  0:
+                value = V4L2_PIX_FMT_RGB565;
+                break;
+            case  1:
+                value = V4L2_PIX_FMT_RGB24;
+                break;
+            case  2:
+                value = V4L2_PIX_FMT_YUV420;
+                break;
+            case  3:
+                value = V4L2_PIX_FMT_YUYV;
+                break;
+            case  4:
+                value = V4L2_PIX_FMT_NV21;
+                break;
+            case  5:
+                value = V4L2_PIX_FMT_NV12;
+                break;
+            case  6:
+                value = V4L2_PIX_FMT_YVYU;
+                break;
+            case  7:
+                value = V4L2_PIX_FMT_SRGGB12;
+                break;
+            case  8:
+                value = V4L2_PIX_FMT_SGRBG12;
+                break;
+            case  9:
+                value = V4L2_PIX_FMT_SGBRG12;
+                break;
+            case  10:
+                value = V4L2_PIX_FMT_SBGGR12;
+                break;
+            default:
+                value = V4L2_PIX_FMT_RGB565;
+                break;
+            }
+            cfg_param->format = value;
+            break;
+
+        case 't':
+            value = atoi(optarg);
+            if (value < STF_DISP_NONE || value > STF_DISP_DRM) {
+                LOG(STF_LEVEL_ERR, "Display Type %d is out of range [%d, %d]\n", value,
+                        STF_DISP_NONE, STF_DISP_DRM);
+                exit(EXIT_FAILURE);
+            }
+            LOG(STF_LEVEL_INFO, "Display Type: %s\n", g_disp_values[value].name);
+            cfg_param->disp_type = value;
+            break;
+
+        default:
+            usage(stderr, argc, argv);
+            exit(EXIT_FAILURE);
+        }
+    }
+}
+
+void StopContCapture(int sig_id)
+{
+    LOG(STF_LEVEL_INFO, "stoping continuous capture\n");
+    gp_cfg_param->continuous = 0;
+}
+void signal_handler()
+{
+    struct sigaction sa;
+    CLEAR(sa);
+
+    sa.sa_handler = StopContCapture;
+    if (sigaction(SIGINT, &sa, 0) != 0) {
+        LOG(STF_LEVEL_ERR, "could not install SIGINT handler, continuous capture disabled\n");
+        gp_cfg_param->continuous = 0;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    AVFormatContext* fmtCtx = NULL;
+    AVInputFormat* inputFmt;
+    AVCodecContext* pCodecCtx;
+    AVCodec* pCodec;
+    int videoindex, i;
+    char str_data[32] = {0};
+    AVDictionary *options = NULL;
+
+    // init param and log
+    signal_handler();
+    init_log();
+    alloc_default_config(&gp_cfg_param);
+    parse_options(argc, argv, gp_cfg_param);
+    // check_cfg_params(gp_cfg_param);
+
+    // init ffmpeg env
+    av_log_set_level(AV_LOG_TRACE);
+    avcodec_register_all();
+    avdevice_register_all();
+
+    inputFmt = av_find_input_format(FFMPEG_INPUT_NAME);
+    if (inputFmt == NULL) {
+        LOG(STF_LEVEL_ERR, "can not find_input_format\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // open and set parameter to v4l2 device
+    av_dict_set(&options, "f", "v4l2", 0);              // speed up probe process
+    if (V4L2_PIX_FMT_NV12 == gp_cfg_param->format) {    // format
+        av_dict_set(&options, "input_format", "nv12", 0);
+    } else if (V4L2_PIX_FMT_YUYV == gp_cfg_param->format) {
+        av_dict_set(&options, "input_format", "yuyv422", 0);
+    } else {
+        LOG(STF_LEVEL_ERR, "Current only support nv12 format\n");
+        exit(EXIT_FAILURE);
+    }
+    if (gp_cfg_param->width && gp_cfg_param->height) {
+        snprintf(str_data, 32, "%d*%d", gp_cfg_param->width, gp_cfg_param->height);
+        av_dict_set(&options, "video_size", str_data, 0); // resolution eg: "640*480"
+    }
+    if (gp_cfg_param->fps) {
+        snprintf(str_data, 32, "%d", gp_cfg_param->fps);
+        av_dict_set(&options, "framerate", str_data, 0);  // framerate eg: "15"
+    }
+    if (avformat_open_input(&fmtCtx, gp_cfg_param->device_name, inputFmt, &options) < 0) {
+        LOG(STF_LEVEL_ERR, "can not open input_file\n");
+        exit(EXIT_FAILURE);
+    }
+    av_dict_free(&options);
+
+    /* print device information*/
+    av_dump_format(fmtCtx, 0, gp_cfg_param->device_name, 0);
+
+    videoindex = -1;
+    for (i = 0; i < fmtCtx->nb_streams; i++) {
+        if (fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+            videoindex = i;
+            break;
+        }
+    }
+    if (videoindex == -1) {
+        LOG(STF_LEVEL_ERR, "fail to find a video stream\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pCodecCtx = fmtCtx->streams[videoindex]->codec;
+    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+    LOG(STF_LEVEL_INFO, "picture width   =  %d \n", pCodecCtx->width);
+    LOG(STF_LEVEL_INFO, "picture height  =  %d \n", pCodecCtx->height);
+    LOG(STF_LEVEL_INFO, "Pixel   format  =  %d \n", pCodecCtx->pix_fmt);
+    LOG(STF_LEVEL_INFO, "codec_id  =  %d \n", pCodecCtx->codec_id); // AV_CODEC_ID_RAWVIDEO
+
+    if (gp_cfg_param->out_file && !gp_cfg_param->out_fp) {
+        gp_cfg_param->out_fp = fopen(gp_cfg_param->out_file, "wb");
+        if (!gp_cfg_param->out_fp) {
+            LOG(STF_LEVEL_ERR, "fopen file failed %s\n", gp_cfg_param->out_file);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    gp_cfg_param->packet = (AVPacket*)av_malloc(sizeof(AVPacket));
+    gp_cfg_param->format_ctx = fmtCtx;
+    gp_cfg_param->vcodec_ctx = pCodecCtx;
+
+    if (STF_DISP_DRM == gp_cfg_param->disp_type) {
+        stf_drm_open(&gp_cfg_param->drm_param, DRM_DEVICE_NAME, gp_cfg_param->io_mthd);
+        stf_drm_init(&gp_cfg_param->drm_param, gp_cfg_param->vcodec_ctx->width,
+                gp_cfg_param->vcodec_ctx->height, gp_cfg_param->vcodec_ctx->pix_fmt,
+                gp_cfg_param->io_mthd, gp_cfg_param->dmabufs,
+                sizeof(gp_cfg_param->dmabufs) / sizeof(gp_cfg_param->dmabufs[0]));
+    }
+
+    // process frames
+    mainloop();
+
+    // release resource
+    if (gp_cfg_param->out_fp) {
+        fclose(gp_cfg_param->out_fp);
+    }
+    if (gp_cfg_param->packet) {
+        av_free_packet(gp_cfg_param->packet);
+    }
+    if (gp_cfg_param->format_ctx) {
+        avformat_close_input(&gp_cfg_param->format_ctx);
+    }
+    if (STF_DISP_DRM == gp_cfg_param->disp_type) {
+        stf_drm_close(&gp_cfg_param->drm_param);
+    }
+    deinit_log();
+    free(gp_cfg_param);
+    return 0;
+}

+ 127 - 0
package/starfive/ffmpeg_test/src/ffmpeg_input_test_2.c

@@ -0,0 +1,127 @@
+/*=============================================================================
+ * #     FileName: read_device.c
+ * #         Desc: use ffmpeg read a frame data from v4l2, and convert
+ * #			   the output data format
+ * #       Author: licaibiao
+ * #   LastChange: 2017-03-28
+ * =============================================================================*/
+#include <libavcodec/avcodec.h>
+#include <libavdevice/avdevice.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/file.h>
+#include <libavutil/imgutils.h>
+#include <libswscale/swscale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+char* input_name = "video4linux2";
+char* file_name = "/dev/video2";
+char* out_file = "yuv420.yuv";
+
+void captureOneFrame(void)
+{
+    AVFormatContext* fmtCtx = NULL;
+    AVInputFormat* inputFmt;
+    AVPacket* packet;
+    AVCodecContext* pCodecCtx;
+    AVCodec* pCodec;
+    struct SwsContext* sws_ctx;
+    FILE* fp;
+    int i;
+    int ret;
+    int videoindex;
+
+    enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_NV12; // AV_PIX_FMT_YUV420P;
+    const char* dst_size = NULL;
+    const char* src_size = NULL;
+    uint8_t* src_data[4];
+    uint8_t* dst_data[4];
+    int src_linesize[4];
+    int dst_linesize[4];
+    int src_bufsize;
+    int dst_bufsize;
+    int src_w;
+    int src_h;
+    int dst_w = 1920;
+    int dst_h = 1080;
+
+    av_log_set_level(AV_LOG_TRACE);
+
+    fp = fopen(out_file, "wb");
+    if (fp < 0) {
+        printf("open frame data file failed\n");
+        return;
+    }
+
+    inputFmt = av_find_input_format(input_name);
+    if (inputFmt == NULL) {
+        printf("can not find_input_format\n");
+        return;
+    }
+
+    AVDictionary *options = NULL;
+    av_dict_set(&options, "f", "v4l2", 0);  // speed probe
+    av_dict_set(&options, "input_format", "nv12", 0); // format
+    av_dict_set(&options, "video_size", "1920*1080", 0); // resolution
+    av_dict_set(&options, "framerate", "15", 0); // framerate
+
+    if (avformat_open_input(&fmtCtx, file_name, inputFmt, &options) < 0) {
+        printf("can not open_input_file\n");
+        return;
+    }
+    av_dict_free(&options);
+
+    av_dump_format(fmtCtx, 0, file_name, 0);
+    printf("fmtCtx->nb_streams   =  %d \n", fmtCtx->nb_streams);
+    videoindex = -1;
+    for (i = 0; i < fmtCtx->nb_streams; i++) {
+        if (fmtCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+            videoindex = i;
+            break;
+        }
+    }
+    if (videoindex == -1) {
+        printf("Didn't find a video stream.\n");
+        return -1;
+    }
+
+    pCodecCtx = fmtCtx->streams[videoindex]->codec;
+    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
+
+    printf("picture width   =  %d \n", pCodecCtx->width);
+    printf("picture height  =  %d \n", pCodecCtx->height);
+    printf("Pixel   Format  =  %d \n", pCodecCtx->pix_fmt);
+
+    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, dst_w, dst_h, dst_pix_fmt,
+        SWS_BILINEAR, NULL, NULL, NULL);
+
+    src_bufsize = av_image_alloc(src_data, src_linesize, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 16);
+    dst_bufsize = av_image_alloc(dst_data, dst_linesize, dst_w, dst_h, dst_pix_fmt, 1);
+
+    packet = (AVPacket*)av_malloc(sizeof(AVPacket));
+
+    int loop = 1000;
+    //	while(loop--){
+    av_read_frame(fmtCtx, packet);
+    memcpy(src_data[0], packet->data, packet->size);
+    sws_scale(sws_ctx, src_data, src_linesize, 0, pCodecCtx->height, dst_data, dst_linesize);
+    fwrite(dst_data[0], 1, dst_bufsize, fp);
+    //	}
+
+    fclose(fp);
+    av_free_packet(packet);
+    av_freep(&dst_data[0]);
+    sws_freeContext(sws_ctx);
+    avformat_close_input(&fmtCtx);
+}
+
+int main(void)
+{
+    avcodec_register_all();
+    avdevice_register_all();
+    captureOneFrame();
+    return 0;
+}

+ 486 - 0
package/starfive/ffmpeg_test/src/stf_drm.c

@@ -0,0 +1,486 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <sys/mman.h>
+#include <libdrm/drm_fourcc.h>
+#include "stf_drm.h"
+#include "stf_log.h"
+
+static int drm_open(const char *path, int need_dumb, int need_prime)
+{
+    int fd, flags;
+    uint64_t has_it;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    if ((fd = open(path, O_RDWR)) < 0) {
+        LOG(STF_LEVEL_ERR, "cannot open \"%s\"\n", path);
+        error("open");
+    }
+
+    /* set FD_CLOEXEC flag */
+    if ((flags = fcntl(fd, F_GETFD)) < 0
+        || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+        fatal("fcntl FD_CLOEXEC failed");
+
+    if (need_dumb) {
+        if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_it) < 0)
+            error("drmGetCap DRM_CAP_DUMB_BUFFER failed!");
+        if (has_it == 0)
+            fatal("can't give us dumb buffers");
+    }
+
+    if (need_prime) {
+        /* check prime */
+        if (drmGetCap(fd, DRM_CAP_PRIME, &has_it) < 0)
+            error("drmGetCap DRM_CAP_PRIME failed!");
+        if (!(has_it & DRM_PRIME_CAP_EXPORT))
+            fatal("can't export dmabuf");
+    }
+
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+    return fd;
+}
+
+static struct drm_dev_t *drm_find_dev(int fd, uint32_t width, uint32_t height)
+{
+    int i, m;
+    struct drm_dev_t *dev = NULL, *dev_head = NULL;
+    drmModeRes *res;
+    drmModeConnector *conn;
+    drmModeEncoder *enc;
+    drmModeModeInfo *mode = NULL, *preferred = NULL;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    if ((res = drmModeGetResources(fd)) == NULL) {
+        fatal("drmModeGetResources() failed");
+    }
+
+    LOG(STF_LEVEL_DEBUG, "count_connectors=%d\n", res->count_connectors);
+    /* find all available connectors */
+    for (i = 0; i < res->count_connectors; i++) {
+        conn = drmModeGetConnector(fd, res->connectors[i]);
+        if (conn != NULL && conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
+            dev = (struct drm_dev_t *) malloc(sizeof(struct drm_dev_t));
+            memset(dev, 0, sizeof(struct drm_dev_t));
+
+            /* find preferred mode */
+            for (m = 0; m < conn->count_modes; m++) {
+                mode = &conn->modes[m];
+                if (mode->hdisplay == width && mode->vdisplay == height) {
+                    preferred = mode;
+                    LOG(STF_LEVEL_INFO, "find the matched mode, modes index=%d, %dx%d\n",
+                            m, width, height);
+                    break;
+                }
+                if (mode->type & DRM_MODE_TYPE_PREFERRED) {
+                    preferred = mode;
+                    LOG(STF_LEVEL_INFO, "find perferred mode, modes index=%d\n", m);
+                }
+                LOG(STF_LEVEL_DEBUG, "mode: %dx%d %s\n", mode->hdisplay, mode->vdisplay,
+                        mode->type & DRM_MODE_TYPE_PREFERRED ? "*" : "");
+            }
+
+            if (!preferred)
+                preferred = &conn->modes[0];
+
+            dev->conn_id = conn->connector_id;
+            // dev->next = NULL;
+            if (conn->count_encoders) {
+                if (conn->encoder_id) {
+                    dev->enc_id = conn->encoder_id;
+                } else {
+                    dev->enc_id = conn->encoders[0];
+                }
+            } else {
+                fatal("count_encoders is 0");
+            }
+
+            memcpy(&dev->mode, preferred, sizeof(drmModeModeInfo));
+            dev->width = preferred->hdisplay;
+            dev->height = preferred->vdisplay;
+
+            /* FIXME: use default encoder/crtc pair */
+            if ((enc = drmModeGetEncoder(fd, dev->enc_id)) == NULL)
+                fatal("drmModeGetEncoder() faild");
+            if (enc->crtc_id) {
+                dev->crtc_id = enc->crtc_id;
+            } else {
+                dev->crtc_id = res->crtcs[0];
+            }
+            drmModeFreeEncoder(enc);
+
+            dev->saved_crtc = NULL;
+
+            /* create dev list */
+            dev->next = dev_head;
+            dev_head = dev;
+        }
+        drmModeFreeConnector(conn);
+    }
+
+    drmModeFreeResources(res);
+
+    LOG(STF_LEVEL_INFO, "selected connector(s)\n");
+    for (dev = dev_head; dev != NULL; dev = dev->next) {
+        LOG(STF_LEVEL_INFO, "connector id:%d\n", dev->conn_id);
+        LOG(STF_LEVEL_INFO, "\tencoder id:%d crtc id:%d\n", dev->enc_id, dev->crtc_id);
+        LOG(STF_LEVEL_INFO, "\twidth:%d height:%d\n", dev->width, dev->height);
+    }
+
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+    return dev_head;
+}
+
+static void drm_setup_buffer(int fd, struct drm_dev_t *dev,
+        int width, int height,
+        struct drm_buffer_t *buffer, int map, int export)
+{
+    struct drm_mode_create_dumb create_req;
+    struct drm_mode_map_dumb map_req;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    buffer->dmabuf_fd = -1;
+
+    memset(&create_req, 0, sizeof(struct drm_mode_create_dumb));
+    create_req.width = width;
+    create_req.height = height;
+    create_req.bpp = 24;
+    if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
+        fatal("drmIoctl DRM_IOCTL_MODE_CREATE_DUMB failed");
+    }
+
+    buffer->pitch = create_req.pitch;
+    buffer->size = create_req.size;
+    /* GEM buffer handle */
+    buffer->bo_handle = create_req.handle;
+
+    if (export) {
+        int ret;
+        ret = drmPrimeHandleToFD(fd, buffer->bo_handle,
+                DRM_CLOEXEC | DRM_RDWR, &buffer->dmabuf_fd);
+        if (ret < 0) {
+            fatal("could not export the dump buffer");
+        }
+        LOG(STF_LEVEL_INFO, "drm fd=%d, bo_handle=%d, dmabuf_fd=%d\n",
+                fd, buffer->bo_handle, buffer->dmabuf_fd);
+    }
+
+    if (map) {
+        memset(&map_req, 0, sizeof(struct drm_mode_map_dumb));
+        map_req.handle = buffer->bo_handle;
+        if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) {
+            fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed");
+        }
+        buffer->buf = (uint8_t *)mmap(0, buffer->size,
+                PROT_READ | PROT_WRITE, MAP_SHARED,
+                fd, map_req.offset);
+        if (buffer->buf == MAP_FAILED) {
+            error("mmap");
+        }
+    }
+
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+static uint32_t drm_get_bpp_from_drm(uint32_t drmfmt)
+{
+    uint32_t bpp;
+
+    switch (drmfmt) {
+    case DRM_FORMAT_YUV420:
+    case DRM_FORMAT_YVU420:
+    case DRM_FORMAT_YUV422:
+    case DRM_FORMAT_NV12:
+    case DRM_FORMAT_NV21:
+    case DRM_FORMAT_NV16:
+        bpp = 8;
+        break;
+    case DRM_FORMAT_P010:
+        bpp = 10;
+        break;
+    case DRM_FORMAT_UYVY:
+    case DRM_FORMAT_YUYV:
+    case DRM_FORMAT_YVYU:
+    case DRM_FORMAT_P016:
+        bpp = 16;
+        break;
+    case DRM_FORMAT_BGR888:
+    case DRM_FORMAT_RGB888:
+        bpp = 24;
+        break;
+    default:
+        bpp = 32;
+        break;
+    }
+
+    return bpp;
+}
+
+static uint32_t drm_get_height_from_drm (uint32_t drmfmt, uint32_t height)
+{
+    uint32_t ret;
+
+    switch (drmfmt) {
+    case DRM_FORMAT_YUV420:
+    case DRM_FORMAT_YVU420:
+    case DRM_FORMAT_YUV422:
+    case DRM_FORMAT_NV12:
+    case DRM_FORMAT_NV21:
+    case DRM_FORMAT_P010:
+    case DRM_FORMAT_P016:
+        ret = height * 3 / 2;
+        break;
+    case DRM_FORMAT_NV16:
+        ret = height * 2;
+        break;
+    default:
+        ret = height;
+        break;
+    }
+
+    return ret;
+}
+
+static int drm_setup_buffer2(int fd, struct drm_dev_t *dev,
+        int width, int height,
+        struct drm_buffer_t *buffer, int map, int export)
+{
+    struct drm_mode_create_dumb create_req;
+    struct drm_mode_map_dumb map_req;
+    int ret = 0;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    buffer->dmabuf_fd = -1;
+
+    memset(&create_req, 0, sizeof(struct drm_mode_create_dumb));
+    // For NV12 or NV21, bpp is 8, height is height * 3 / 2
+    create_req.width = width;
+    create_req.height = drm_get_height_from_drm(dev->drm_format, height);
+    create_req.bpp = drm_get_bpp_from_drm(dev->drm_format);
+    if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req) < 0) {
+        fatal("drmIoctl DRM_IOCTL_MODE_CREATE_DUMB failed");
+    }
+    buffer->pitch = create_req.pitch;
+    buffer->size = create_req.size;
+    buffer->bo_handle = create_req.handle; /* GEM buffer handle */
+
+    if (export) {
+#if 0
+        memset(&map_req, 0, sizeof(struct drm_mode_map_dumb));
+        map_req.handle = buffer->bo_handle;
+        ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
+        if (ret) {
+            fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed");
+        }
+        buffer->buf = (uint8_t *) mmap(0, buffer->size,
+            PROT_READ | PROT_WRITE, MAP_SHARED,
+            fd, map_req.offset);
+        if (buffer->buf == MAP_FAILED) {
+            error("drm mmap");
+        }
+        LOG(STF_LEVEL_INFO, "buffer->buf= %p\n", buffer->buf);
+#endif
+
+        ret = drmPrimeHandleToFD(fd, buffer->bo_handle,
+                DRM_CLOEXEC | DRM_RDWR, &buffer->dmabuf_fd);
+        if (ret < 0) {
+            fatal("could not export the dump buffer");
+        }
+        LOG(STF_LEVEL_DEBUG, "drm fd=%d, bo_handle=%d, dmabuf_fd=%d\n",
+                fd, buffer->bo_handle, buffer->dmabuf_fd);
+    }
+
+    if (map) {
+        memset(&map_req, 0, sizeof(struct drm_mode_map_dumb));
+        map_req.handle = buffer->bo_handle;
+
+        if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req)) {
+            fatal("drmIoctl DRM_IOCTL_MODE_MAP_DUMB failed");
+        }
+        buffer->buf = (uint8_t *)mmap(0, buffer->size,
+                PROT_READ | PROT_WRITE, MAP_SHARED,
+                fd, map_req.offset);
+        if (buffer->buf == MAP_FAILED) {
+            error("mmap");
+        }
+    }
+
+    uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
+    handles[0] = buffer->bo_handle;
+    pitches[0] = buffer->pitch;
+    offsets[0] = 0;
+    handles[1] = buffer->bo_handle;
+    pitches[1] = buffer->pitch;
+    offsets[1] = buffer->pitch * height;
+    // planes[0] = virtual;
+    // planes[1] = virtual + offsets[1];
+
+    LOG(STF_LEVEL_INFO, "map=%d, export=%d. width=%d, height=%d, "
+            "pitch=%d, size=%d, bo_handle=%d, dmabuf_fd=%d\n",
+            map, export, width, height, buffer->pitch, buffer->size,
+            buffer->bo_handle, buffer->dmabuf_fd);
+
+    //DRM_FORMAT_NV12
+    ret = drmModeAddFB2(fd, dev->width, dev->height,
+            dev->drm_format, handles, pitches, offsets, &(buffer->fb_id), 0);
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+    return ret;
+}
+
+static void drm_setup_dummy(int fd, struct drm_dev_t *dev, int map, int export)
+{
+    int i;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    for (i = 0; i < BUFCOUNT; i++) {
+        drm_setup_buffer(fd, dev, dev->width, dev->height,
+                &dev->bufs[i], map, export);
+    }
+
+    /* Assume all buffers have the same pitch */
+    dev->pitch = dev->bufs[0].pitch;
+    LOG(STF_LEVEL_INFO, "DRM: buffer pitch = %d bytes\n", dev->pitch);
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+static void drm_setup_fb(int fd, struct drm_dev_t *dev, int map, int export)
+{
+    int i;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+#if 0
+    for (i = 0; i < BUFCOUNT; i++) {
+        int ret;
+
+        drm_setup_buffer(fd, dev, dev->width, dev->height,
+                    &dev->bufs[i], map, export);
+
+        ret = drmModeAddFB(fd, dev->width, dev->height,
+            DEPTH, BPP, dev->bufs[i].pitch,
+            dev->bufs[i].bo_handle, &dev->bufs[i].fb_id);
+        if (ret)
+            fatal("drmModeAddFB failed");
+LOG(STF_LEVEL_INFO, "width=%d,height=%d,pitch=%d,bo_handle=%d,fb_id=%d\n",
+    dev->width, dev->height,dev->bufs[i].pitch,dev->bufs[i].bo_handle, dev->bufs[i].fb_id);
+    }
+#else
+    for (i = 0; i < BUFCOUNT; i++) {
+        int ret = -1;
+        ret = drm_setup_buffer2(fd, dev, dev->width, dev->height,
+                &dev->bufs[i], map, export);
+        if (ret) {
+            fatal("drmModeAddFB failed");
+        }
+    }
+#endif
+
+    /* Assume all buffers have the same pitch */
+    dev->pitch = dev->bufs[0].pitch;
+    LOG(STF_LEVEL_DEBUG, "DRM: buffer pitch %d bytes\n", dev->pitch);
+
+    dev->saved_crtc = drmModeGetCrtc(fd, dev->crtc_id); /* must store crtc data */
+
+    /* First buffer to DRM */
+    if (drmModeSetCrtc(fd, dev->crtc_id, dev->bufs[0].fb_id, 0, 0,
+            &dev->conn_id, 1, &dev->mode)) {
+        fatal("drmModeSetCrtc() failed");
+    }
+
+    /* First flip */
+    drmModePageFlip(fd, dev->crtc_id,
+                        dev->bufs[0].fb_id, DRM_MODE_PAGE_FLIP_EVENT,
+                        dev);
+
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+static void drm_destroy(int fd, struct drm_dev_t *dev_head)
+{
+    struct drm_dev_t *devp, *devp_tmp;
+    int i;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    for (devp = dev_head; devp != NULL;) {
+        if (devp->saved_crtc) {
+            drmModeSetCrtc(fd, devp->saved_crtc->crtc_id, devp->saved_crtc->buffer_id,
+                devp->saved_crtc->x, devp->saved_crtc->y, &devp->conn_id, 1, &devp->saved_crtc->mode);
+            drmModeFreeCrtc(devp->saved_crtc);
+        }
+
+        for (i = 0; i < BUFCOUNT; i++) {
+            struct drm_mode_destroy_dumb dreq = { .handle = devp->bufs[i].bo_handle };
+
+            if (devp->bufs[i].buf) {
+                munmap(devp->bufs[i].buf, devp->bufs[i].size);
+            }
+            if (devp->bufs[i].dmabuf_fd >= 0) {
+                close(devp->bufs[i].dmabuf_fd);
+            }
+            drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+            drmModeRmFB(fd, devp->bufs[i].fb_id);
+        }
+
+        devp_tmp = devp;
+        devp = devp->next;
+        free(devp_tmp);
+    }
+
+    drmClose(fd);
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+void stf_drm_open(DRMParam_t *param, char *device_name, int iomthd)
+{
+    int drm_fd;
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+
+    if (IO_METHOD_MMAP == iomthd) {
+        drm_fd = drm_open(device_name, 1, 0);
+    } else if (IO_METHOD_DMABUF == iomthd) {
+        drm_fd = drm_open(device_name, 1, 1);
+    } else {
+        LOG(STF_LEVEL_ERR, "drm: not support io_method %d\n", iomthd);
+        exit (EXIT_FAILURE);
+    }
+
+    param->fd = drm_fd;
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+void stf_drm_init(DRMParam_t *param, uint32_t width, uint32_t height,
+        uint32_t input_fmt, int iomthd, int *dmabufs, int nsize)
+{
+    struct drm_dev_t *dev_head = NULL;
+    int i = 0;
+
+    LOG(STF_LEVEL_TRACE, "Enter\n");
+    dev_head = drm_find_dev(param->fd, width, height);
+    if (dev_head == NULL) {
+        LOG(STF_LEVEL_ERR, "available drm_dev not found\n");
+        exit (EXIT_FAILURE);
+    }
+
+    dev_head->drm_format = ffmpegfmt_to_drmfmt(input_fmt);
+
+    if (IO_METHOD_MMAP == iomthd) {
+        drm_setup_fb(param->fd, dev_head, 1, 0);
+    } else if (IO_METHOD_DMABUF == iomthd) {
+        drm_setup_fb(param->fd, dev_head, 0, 1);
+        assert(nsize == BUFCOUNT);
+        for (i = 0; i < nsize; i++) {
+            dmabufs[i] = dev_head->bufs[i].dmabuf_fd;
+        }
+    } else {
+        LOG(STF_LEVEL_ERR, "drm: not support io_method %d\n", iomthd);
+        exit (EXIT_FAILURE);
+    }
+
+    param->dev_head = dev_head;
+    LOG(STF_LEVEL_TRACE, "Exit\n");
+}
+
+void stf_drm_close(DRMParam_t *param)
+{
+    drm_destroy(param->fd, param->dev_head);
+}

+ 60 - 0
package/starfive/ffmpeg_test/src/stf_drm.h

@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __STF_DRM_H__
+#define __STF_DRM_H__
+
+#include <libdrm/drm.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include "common.h"
+#include "stf_log.h"
+
+struct drm_buffer_t {
+    uint32_t pitch, size;
+
+    uint32_t fb_id;
+    int dmabuf_fd;  // used for dmabuf
+    int bo_handle;
+    uint8_t *buf;
+};
+
+typedef struct drm_dev_t {
+    uint32_t conn_id, enc_id, crtc_id;
+    uint32_t width, height, pitch;
+    drmModeModeInfo mode;
+    drmModeCrtc *saved_crtc;
+
+    // int v4l2_fd;
+    // int drm_fd;
+    uint32_t drm_format;
+
+    struct drm_buffer_t bufs[BUFCOUNT];
+    struct drm_dev_t *next;
+} drm_dev_t;
+
+typedef struct DRMParam_t {
+    drm_dev_t* dev_head;
+    int fd;
+} DRMParam_t;
+
+
+inline static void fatal(char *str)
+{
+    LOG(STF_LEVEL_ERR, "%s\n", str);
+    exit(EXIT_FAILURE);
+}
+
+inline static void error(char *str)
+{
+    perror(str);
+    exit(EXIT_FAILURE);
+}
+
+extern void stf_drm_open(DRMParam_t *param, char *device_name, int iomthd);
+extern void stf_drm_init(DRMParam_t *param, uint32_t width, uint32_t height,
+        uint32_t input_fmt, int iomthd, int *dmabufs, int nsize);
+extern void stf_drm_close(DRMParam_t *param);
+
+#endif // __STF_DRM_H__

+ 104 - 0
package/starfive/ffmpeg_test/src/stf_log.c

@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include "stf_log.h"
+
+#define ANSI_COLOR_ERR      "\x1b[31m"       // RED
+#define ANSI_COLOR_TRACE    "\x1b[32m"       // GREEN
+#define ANSI_COLOR_WARN     "\x1b[33m"       // YELLOW
+#define ANSI_COLOR_BLUE     "\x1b[34m"       // BLUE
+#define ANSI_COLOR_INFO     ""
+// For future
+#define ANSI_COLOR_MAGENTA  "\x1b[35m"       // MAGENTA
+#define ANSI_COLOR_CYAN     "\x1b[36m"       // CYAN
+#define ANSI_COLOR_RESET    "\x1b[0m"        // RESET
+
+#define MAX_PRINT_LENGTH        512
+#define STF_LOG_FILE_PATH       "./STF_ErrorLog.txt"
+#define STF_LOG_ENV_VARIABLE    "V4L2_DEBUG"
+
+// static unsigned int log_decor = LOG_HAS_TIME | LOG_HAS_FILE | LOG_HAS_COLOR;
+static unsigned int log_decor = LOG_HAS_TIME | LOG_HAS_COLOR;
+static FILE *fpLog  = NULL;
+static int max_log_level = STF_LEVEL_ERR;
+
+int init_log()
+{
+    char *strDebug_level = NULL;
+    int level;
+
+    if ((log_decor & LOG_HAS_FILE) && !fpLog) {
+        fpLog = fopen(STF_LOG_FILE_PATH, "w");
+    }
+
+    strDebug_level = getenv(STF_LOG_ENV_VARIABLE);
+    if (strDebug_level) {
+        level = atoi(strDebug_level);
+        if (level >=0) {
+            max_log_level = level;
+        }
+    }
+
+    return 0;
+}
+
+void deinit_log()
+{
+    if (fpLog) {
+        fclose(fpLog);
+        fpLog = NULL;
+    }
+}
+
+void set_maxLogLevel(int level)
+{
+    max_log_level = level;
+}
+
+int get_maxLogLevel()
+{
+    return max_log_level;
+}
+
+void logmsg(int level, const char *format, ...)
+{
+    va_list ptr;
+    char    logBuf[MAX_PRINT_LENGTH] = {0};
+    char*   prefix = "";
+    char*   postfix= "";
+
+    if (level > max_log_level)
+        return;
+
+    if ((log_decor & LOG_HAS_COLOR)) {
+        postfix = ANSI_COLOR_RESET;
+        switch (level) {
+        case STF_LEVEL_ERR: prefix = ANSI_COLOR_ERR "[ERROR]";   break;
+        case STF_LEVEL_WARN:  prefix = ANSI_COLOR_WARN"[WARN ]";  break;
+        case STF_LEVEL_INFO:  prefix = ANSI_COLOR_INFO"[INFO ]";  break;
+        case STF_LEVEL_DEBUG:  prefix = ANSI_COLOR_INFO"[DEBUG]";  break;
+        case STF_LEVEL_LOG:  prefix = ANSI_COLOR_INFO"[LOG  ]";  break;
+        case STF_LEVEL_TRACE: prefix = ANSI_COLOR_TRACE"[TRACE]"; break;
+        default:    prefix = "";               break;
+        }
+    }
+
+    va_start(ptr, format);
+    vsnprintf(logBuf, MAX_PRINT_LENGTH, format, ptr);
+    va_end(ptr);
+
+    fputs(prefix,  stderr);
+    fputs(logBuf,  stderr);
+    fputs(postfix, stderr);
+
+    if ((log_decor & LOG_HAS_FILE) && fpLog) {
+        fwrite(logBuf, strlen(logBuf), 1,fpLog);
+        fflush(fpLog);
+    }
+}
+

+ 58 - 0
package/starfive/ffmpeg_test/src/stf_log.h

@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ */
+#ifndef __STF_LOG_H__
+#define __STF_LOG_H__
+
+// Log Debug
+typedef enum {
+  STF_LEVEL_NONE = 0,
+  STF_LEVEL_ERR,
+  STF_LEVEL_WARN,
+  STF_LEVEL_INFO,
+  STF_LEVEL_DEBUG,
+  STF_LEVEL_LOG,
+  STF_LEVEL_TRACE,
+  STF_LEVEL_ALL
+} StfDebugLevelType;
+
+enum {
+    LOG_HAS_DAY_NAME   =    1, /**< Include day name [default: no]         */
+    LOG_HAS_YEAR       =    2, /**< Include year digit [no]                */
+    LOG_HAS_MONTH      =    4, /**< Include month [no]                     */
+    LOG_HAS_DAY_OF_MON =    8, /**< Include day of month [no]              */
+    LOG_HAS_TIME       =   16, /**< Include time [yes]                     */
+    LOG_HAS_MICRO_SEC  =   32, /**< Include microseconds [yes]             */
+    LOG_HAS_FILE       =   64, /**< Include sender in the log [yes]        */
+    LOG_HAS_NEWLINE    =  128, /**< Terminate each call with newline [yes] */
+    LOG_HAS_CR         =  256, /**< Include carriage return [no]           */
+    LOG_HAS_SPACE      =  512, /**< Include two spaces before log [yes]    */
+    LOG_HAS_COLOR      = 1024, /**< Colorize logs [yes on win32]           */
+    LOG_HAS_LEVEL_TEXT = 2048 /**< Include level text string [no]          */
+};
+enum {
+    TERM_COLOR_R      = 2,   /**< Red            */
+    TERM_COLOR_G      = 4,   /**< Green          */
+    TERM_COLOR_B      = 1,   /**< Blue.          */
+    TERM_COLOR_BRIGHT = 8    /**< Bright mask.   */
+};
+
+int init_log(void);
+void deinit_log(void);
+void set_maxLogLevel(int level);
+int get_maxLogLevel(void);
+void logmsg(int level, const char *format, ...);
+
+#define ENABLE_DEBUG
+#ifdef ENABLE_DEBUG
+#define LOG(level, fmt, ...)  logmsg(level, "[%s,%d]: " fmt, __FUNCTION__, __LINE__, ##__VA_ARGS__);
+#define FuncIn()  logmsg(STF_LEVEL_DEBUG, "[%s,%d]:  FUNC IN\n",  __FUNCTION__, __LINE__);
+#define FuncOut() logmsg(STF_LEVEL_DEBUG, "[%s,%d]:  FUNC OUT\n",  __FUNCTION__, __LINE__);
+#else
+#define LOG(level, fmt, ...)
+#define FuncIn()
+#define FuncOut()
+#endif
+
+#endif // __STF_LOG_H__