UT: add v4l2 decoder + uvm dmabuf test [1/1]
PD#SWPL-20176
Problem:
Need a unit test for UVM+V4L2 decoder
Solution:
*) Use FFMPEG as demuxer, feed framed data into V4L2 decoder.
*) Fetch buffer from DRM-GEM, use DMABUF mode of V4L2 decoder.
*) Use libdrm for render, take 2 fence delay into consideration.
*) Use alpha 0 frame on OSD to show video plane.
Verify:
U212 G12A platform.
//No DW
v4l2-uvm-test -f $FILE_PATH -p 35 -d 0
//DW 1:1
v4l2-uvm-test -f $FILE_PATH -p 35 -d 1
//DW 1:2
v4l2-uvm-test -f $FILE_PATH -p 35 -d 4
//DW only
v4l2-uvm-test -f $FILE_PATH -p 35 -d 16
//Adaptive stream (mpeg2/h265)
Change-Id: I890e8ec5c6b88e60227656b19d2d7af3a04c76c1
Signed-off-by: Song Zhao <song.zhao@amlogic.com>
diff --git a/v4l2-uvm-test/src/demux.c b/v4l2-uvm-test/src/demux.c
new file mode 100644
index 0000000..dd158cc
--- /dev/null
+++ b/v4l2-uvm-test/src/demux.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
+ *
+ * This source code is subject to the terms and conditions defined in the
+ * file 'LICENSE' which is part of this source code package.
+ *
+ * Description:
+ */
+#include <pthread.h>
+#include <stdbool.h>
+#include <libavformat/avformat.h>
+#include "demux.h"
+
+#define H2645(format) ((format) == AV_CODEC_ID_H264 || (format) == AV_CODEC_ID_H265)
+#define WORD_BE(p) ((p[0]<<8) | p[1])
+
+#define HEVC_NAL_TYPE(b) ((b>>1)&0x3F)
+#define HEVC_NAL_VPS 32
+#define HEVC_NAL_SPS 33
+#define HEVC_NAL_PPS 34
+
+extern int ffmpeg_log;
+/* ffmpeg data structure */
+static AVStream *video_stream = NULL;
+static AVFormatContext *fmt_ctx = NULL;
+static int video_stream_idx = -1;
+
+/* for h264/265 */
+static int raw_stream;
+static int nal_size;
+static uint8_t s_nal_3[3] = {0,0,1};
+static uint8_t s_nal_4[4] = {0,0,0,1};
+
+static struct dmx_cb *dec_cb;
+static pthread_t dmx_t;
+static int thread_quit;
+static struct dmx_v_data v_data;
+static int video_frame_count = 0;
+
+static void dump(const char* path, uint8_t *data, int size) {
+ FILE* fd = fopen(path, "wb");
+ if (!fd)
+ return;
+ fwrite(data, 1, size, fd);
+ fflush(fd);
+ fclose(fd);
+}
+
+static int insert_nal() {
+ int size;
+ if (nal_size == 3) {
+ dec_cb->write(s_nal_3, sizeof(s_nal_3));
+ size = 3;
+ } else {
+ dec_cb->write(s_nal_4, sizeof(s_nal_4));
+ size = 4;
+ }
+ return size;
+}
+
+#if 0
+static bool check_nal(uint8_t *data, int size) {
+ if (size < 4)
+ return false;
+ if (nal_size == 3 && data[0] == 0 &&
+ data[1] == 0 && data[2] == 1)
+ return true;
+ if (nal_size == 4 && data[0] == 0 &&
+ data[1] == 0 && data[2] == 0 && data[3] == 1)
+ return true;
+ return false;
+}
+#endif
+
+static int demux_packet(AVPacket* pkt)
+{
+ int decoded = pkt->size;
+
+ if (pkt->stream_index != video_stream_idx)
+ return decoded;
+
+ /* video frame */
+ video_frame_count++;
+#ifdef DEBUG_FRAME
+ printf("video_frame n:%d pts:%llx size:%x\n",
+ video_frame_count,
+ pkt->pts, pkt->size);
+#endif
+
+ /* refer to ffmpeg hevc_mp4toannexb_filter()
+ * and h264_extradata_to_annexb()
+ */
+ if (H2645(video_stream->codecpar->codec_id)) {
+ uint8_t *p = pkt->data;
+ int length_size = nal_size;
+ int nalu_size = 0;
+ int processed = 0;
+
+ /* From raw ES */
+ if (raw_stream) {
+ dec_cb->write(pkt->data, pkt->size);
+ processed = pkt->size;
+ //printf("raw es frame\n");
+ }
+
+ /* data inside pkt->data is like:
+ * len0 + data0 + len1 + data1 ... + lenN + dataN
+ * lenX depends on the nal_size
+ */
+ while (processed < pkt->size) {
+ int i;
+
+ for (i = 0; i < length_size; i++)
+ nalu_size = (nalu_size << 8) | (*p++);
+ processed += length_size;
+
+ insert_nal();
+ dec_cb->write(p, nalu_size);
+
+#ifdef DEBUG_FRAME
+ printf("nalu_size(%04x) %02x %02x %02x %02x",
+ nalu_size, p[0], p[1], p[2], p[3]);
+#endif
+
+ p += nalu_size;
+ processed += nalu_size;
+
+#ifdef DEBUG_FRAME
+ printf(" ... %02x %02x %02x %02x\n",
+ *(p-3), *(p-2), *(p-1), *p);
+#endif
+ }
+ } else {
+ dec_cb->write(pkt->data, pkt->size);
+ }
+ dec_cb->frame_done();
+
+ return decoded;
+}
+
+static int open_codec_context(int *stream_idx,
+ AVFormatContext *fmt_ctx, enum AVMediaType type)
+{
+ int ret, stream_index;
+
+ ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
+ if (ret < 0) {
+ return ret;
+ } else {
+ stream_index = ret;
+ *stream_idx = stream_index;
+ }
+
+ return 0;
+}
+
+/*
+ * Refer to h264_parse.c ff_h264_decode_extradata()
+ * parse AVCDecoderConfigurationRecord
+ * See MPEG-4 Part 15 "Advanced Video Coding (AVC) file format" section 5.2.4.1 for details.
+ * data will be release outside
+ */
+static int h264_header_parse(uint8_t *data, int size,
+ uint8_t **o_data, int *o_size, int *nal_size) {
+ int i, cnt, nalsize;
+ const uint8_t *p = data;
+ int cur_size = 0;
+
+ if (!data || size <= 0)
+ return -1;
+
+ if (data[0] != 1) {
+ if (size < 7) return -1;
+ if (data[0] == 0 && data[1] == 0 && data[2] == 1)
+ *nal_size = 3;
+ else if (data[0] == 0 && data[1] == 0 && data[3] == 0 && data[4] == 1)
+ *nal_size = 4;
+ else {
+ printf("wrong header\n");
+ return -1;
+ }
+
+ raw_stream = 1;
+ *o_data = (uint8_t *)malloc(4096);
+ if (!*o_data) {
+ printf("oom");
+ return -1;
+ }
+ memcpy(*o_data, data, size);
+ *o_size = size;
+ return 0;
+ }
+
+ if (size < 7) {
+ printf("avcC %d too short\n", size);
+ return -1;
+ }
+
+ *o_data = (uint8_t *)malloc(4096);
+ if (!*o_data) {
+ printf("oom");
+ return -1;
+ }
+
+ // Store right nal length size that will be used to parse all other nals
+ *nal_size = (data[4] & 0x03) + 1;
+
+ // Decode sps from avcC
+ cnt = *(p + 5) & 0x1f; // Number of sps
+ printf("sps cnt:%d\n", cnt);
+
+ //NAL
+ if (cnt) {
+ if (*nal_size == 3) {
+ memcpy(*o_data + cur_size, s_nal_3, 3);
+ cur_size += 3;
+ } else if (*nal_size == 4) {
+ memcpy(*o_data + cur_size, s_nal_4, 4);
+ cur_size += 4;
+ } else {
+ printf("invalid nal size:%d\n", *nal_size);
+ goto err_exit;
+ }
+ }
+
+ p += 6;
+ for (i = 0; i < cnt; i++) {
+ nalsize = WORD_BE(p);
+ p += 2;
+ if (nalsize > size - (p - data))
+ goto err_exit;
+ memcpy(*o_data + cur_size, p, nalsize);
+ p += nalsize;
+ cur_size += nalsize;
+ }
+ // Decode pps from avcC
+ cnt = *(p++); // Number of pps
+ printf("pps cnt:%d\n", cnt);
+
+ //NAL
+ if (cnt) {
+ if (*nal_size == 3) {
+ memcpy(*o_data + cur_size, s_nal_3, 3);
+ cur_size += 3;
+ } else if (*nal_size == 4) {
+ memcpy(*o_data + cur_size, s_nal_4, 4);
+ cur_size += 4;
+ } else {
+ printf("invalid nal size:%d\n", *nal_size);
+ goto err_exit;
+ }
+ }
+
+ for (i = 0; i < cnt; i++) {
+ nalsize = WORD_BE(p);
+ p += 2;
+ if (nalsize > size - (p - data))
+ goto err_exit;
+ memcpy(*o_data + cur_size, p, nalsize);
+ p += nalsize;
+ cur_size += nalsize;
+ }
+ printf("header parse done. cur_size:%d\n", cur_size);
+ *o_size = cur_size;
+ return 0;
+err_exit:
+ free(*o_data);
+ return -2;
+}
+
+/*
+ * Refer to hevc_parse.c ff_hevc_decode_extradata()
+ * hvcC
+ * data will be release outside
+ */
+static int h265_header_parse(uint8_t *data, int size,
+ uint8_t **o_data, int *o_size, int *nal_size) {
+ const uint8_t *p = data;
+ int i, cur_size = 0;
+ int num_arrays;
+
+ if (!data || size <= 0)
+ return -1;
+
+ if (size < 7) {
+ printf("header %d too short\n", size);
+ return -1;
+ }
+
+ *o_data = (uint8_t *)malloc(4096);
+ if (!*o_data) {
+ printf("oom");
+ return -1;
+ }
+
+ if ((!data[0] && !data[1] && data[2] == 1) ||
+ (!data[0] && !data[1] && !data[2] && data[3] == 1)) {
+ /* not hvcC format */
+ *o_size = 0;
+ *nal_size = 4;
+ raw_stream = 1;
+ printf("raw header parse done");
+ return 0;
+ }
+ // Store right nal length size that will be used to parse all other nals
+ p += 21;
+ *nal_size = (*p & 0x03) + 1;
+ p++;
+ num_arrays = *p;
+ p++;
+
+ printf("array cnt:%d nal_size:%d\n", num_arrays, *nal_size);
+ // Decode nal units from hvcC
+ for (i = 0; i < num_arrays; i++) {
+ uint8_t nal_type;
+ uint16_t numNalus;
+ int j;
+
+ nal_type = *p & 0x3F;
+ p++;
+ numNalus = WORD_BE(p);
+ p += 2;
+ for (j = 0; j < numNalus; j++) {
+ uint16_t nalsize = WORD_BE(p);
+ p += 2;
+ //printf("%d: len:%u\n", j, nalsize);
+ if (nal_type == HEVC_NAL_VPS || nal_type == HEVC_NAL_SPS ||
+ nal_type == HEVC_NAL_PPS) {
+ //NAL header
+ if (*nal_size == 3) {
+ memcpy(*o_data + cur_size, s_nal_3, 3);
+ cur_size += 3;
+ } else if (*nal_size == 4) {
+ memcpy(*o_data + cur_size, s_nal_4, 4);
+ cur_size += 4;
+ } else {
+ printf("invalid nal size:%d\n", *nal_size);
+ goto err_exit;
+ }
+
+ memcpy(*o_data + cur_size, p, nalsize);
+ cur_size += nalsize;
+ }
+ p += nalsize;
+ }
+ }
+ printf("header parse done. cur_size:%d\n", cur_size);
+ *o_size = cur_size;
+ return 0;
+err_exit:
+ free(*o_data);
+ dump("cur.dat", data, size);
+ return -2;
+}
+
+static void* dmx_thread_func (void *arg) {
+ AVPacket pkt;
+
+ /* initialize packet, set data to NULL, let the demuxer fill it */
+ av_init_packet(&pkt);
+ pkt.data = NULL;
+ pkt.size = 0;
+
+ /* read frames from the file */
+ while (av_read_frame(fmt_ctx, &pkt) >= 0) {
+ if (thread_quit)
+ break;
+ demux_packet(&pkt);
+ av_packet_unref(&pkt);
+ }
+
+ if (!thread_quit)
+ dec_cb->eos();
+
+ printf("dmx done, total vframe:%d\n", video_frame_count);
+ return NULL;
+}
+
+static void log_callback(void *ptr, int level,
+ const char *fmt, va_list vargs)
+{
+ if (ffmpeg_log)
+ vprintf(fmt, vargs);
+}
+
+int demux_init(const char *file, struct dmx_cb *cb)
+{
+ int ret = 0;
+ AVCodecParameters *dec_ctx = NULL;
+
+ if (!file || !cb) {
+ printf("null pointer\n");
+ return 1;
+ }
+
+ dec_cb = cb;
+
+ av_log_set_level(AV_LOG_ERROR);
+ //av_log_set_level(AV_LOG_DEBUG);
+ av_log_set_callback(log_callback);
+
+ fmt_ctx = avformat_alloc_context();
+ /* open input file, and allocate format context */
+ ret = avformat_open_input(&fmt_ctx, file, NULL, NULL);
+ if (ret) {
+ printf("Could not open source file %s ret:%d %s\n", file, ret, av_err2str(ret));
+ return 2;
+ }
+
+ /* retrieve stream information */
+ if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
+ printf("Could not find stream information\n");
+ ret = 3;
+ goto end;
+ }
+
+ if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
+ uint8_t *header;
+ int h_size;
+
+ video_stream = fmt_ctx->streams[video_stream_idx];
+ dec_ctx = video_stream->codecpar;
+
+ /* allocate image where the decoded image will be put */
+ v_data.width = dec_ctx->width;
+ v_data.height = dec_ctx->height;
+ if (dec_ctx->codec_id == AV_CODEC_ID_H264)
+ v_data.type = VIDEO_TYPE_H264;
+ else if (dec_ctx->codec_id == AV_CODEC_ID_H265)
+ v_data.type = VIDEO_TYPE_H265;
+ else if (dec_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
+ v_data.type = VIDEO_TYPE_MPEG2;
+ else if (dec_ctx->codec_id == AV_CODEC_ID_VP9)
+ v_data.type = VIDEO_TYPE_VP9;
+ else if (dec_ctx->codec_id == AV_CODEC_ID_AV1)
+ v_data.type = VIDEO_TYPE_AV1;
+ else {
+ printf("format not supported %d\n", dec_ctx->codec_id);
+ ret = 3;
+ goto end;
+ }
+
+
+ //printf("AV_CODEC_ID_H264:%d AV_CODEC_ID_H265:%d\n", AV_CODEC_ID_H264, AV_CODEC_ID_H265);
+ printf("video stream: format:%d %dx%d\n",
+ dec_ctx->codec_id, v_data.width, v_data.height);
+
+ dec_cb->meta_done(&v_data);
+
+ printf("video header:%d\n", dec_ctx->extradata_size);
+ h_size = dec_ctx->extradata_size;
+ if (dec_ctx->codec_id == AV_CODEC_ID_H264 &&
+ dec_ctx->extradata_size) {
+ if(h264_header_parse(dec_ctx->extradata,
+ dec_ctx->extradata_size,
+ &header, &h_size, &nal_size)) {
+ printf("parse header fail\n");
+ ret = 5;
+ goto end;
+ }
+ dec_cb->write(header, h_size);
+ free(header);
+ } else if (dec_ctx->codec_id == AV_CODEC_ID_H265 &&
+ dec_ctx->extradata_size) {
+ if(h265_header_parse(dec_ctx->extradata,
+ dec_ctx->extradata_size,
+ &header, &h_size, &nal_size)) {
+ printf("parse header fail\n");
+ ret = 6;
+ goto end;
+ }
+ if (h_size)
+ dec_cb->write(header, h_size);
+ free(header);
+ } else if (dec_ctx->extradata_size) {
+ dec_cb->write(dec_ctx->extradata, dec_ctx->extradata_size);
+ }
+ }
+
+ /* dump input information to stderr */
+ av_dump_format(fmt_ctx, 0, file, 0);
+
+ if (!video_stream) {
+ printf("Could not find audio or video stream in the input, aborting\n");
+ ret = 7;
+ goto end;
+ }
+
+ if (pthread_create(&dmx_t, NULL, dmx_thread_func, NULL)) {
+ printf("Could not create thread, aborting\n");
+ ret = 8;
+ goto end;
+ }
+
+ return 0;
+
+end:
+ avformat_close_input(&fmt_ctx);
+ return ret;
+}
+
+int dmx_destroy() {
+ thread_quit = true;
+ pthread_join(dmx_t, NULL);
+ avformat_close_input(&fmt_ctx);
+ if (fmt_ctx)
+ avformat_free_context(fmt_ctx);
+ return 0;
+}