avsync-lib: add new module [1/2]
PD#SWPL-28037
Problem:
New AV sync solution for RDK
Solution:
User space AV sync module for westeros
Verify:
U212 + v4l2-uvm-test
Change-Id: I1a3d23d4ed4876ea0cfcc5d9abba38c4750ec3ce
Signed-off-by: Song Zhao <song.zhao@amlogic.com>
diff --git a/Config.in b/Config.in
index 3ada780..f3d02e0 100755
--- a/Config.in
+++ b/Config.in
@@ -6,6 +6,7 @@
source "../multimedia/dolby_atmos_release/Config.in"
source "../multimedia/dolby_ms12_release/Config.in"
source "../multimedia/v4l2-uvm-test/Config.in"
+source "../multimedia/avsync-lib/Config.in"
# Gstreamer 0.10.x & Plugins
source "../multimedia/gst-fluendo-mpegdemux/Config.in"
diff --git a/avsync-lib/Config.in b/avsync-lib/Config.in
new file mode 100755
index 0000000..de25dd9
--- /dev/null
+++ b/avsync-lib/Config.in
@@ -0,0 +1,5 @@
+config BR2_PACKAGE_AV_SYNC_LIB
+ bool "avsync-lib"
+ help
+ AV sync user library based on kernel tsync driver
+ Unit test also provided.
diff --git a/avsync-lib/Makefile b/avsync-lib/Makefile
new file mode 100644
index 0000000..920849e
--- /dev/null
+++ b/avsync-lib/Makefile
@@ -0,0 +1,9 @@
+#CC=${HOST_GCC}
+
+#export CC BUILD_DIR STAGING_DIR TARGET_DIR
+all:
+ -$(MAKE) -C src all
+install:
+ -$(MAKE) -C src install
+clean:
+ -$(MAKE) -C src clean
diff --git a/avsync-lib/avsync-lib.mk b/avsync-lib/avsync-lib.mk
new file mode 100644
index 0000000..c13ccb9
--- /dev/null
+++ b/avsync-lib/avsync-lib.mk
@@ -0,0 +1,20 @@
+#
+# avsync-lib
+#
+AVSYNC_LIB_VERSION = 0.1
+AVSYNC_LIB_SITE = $(TOPDIR)/../multimedia/avsync-lib/src
+AVSYNC_LIB_SITE_METHOD = local
+
+define AVSYNC_LIB_BUILD_CMDS
+ $(TARGET_CONFIGURE_OPTS) $(MAKE) CC=$(TARGET_CC) -C $(@D)/
+endef
+
+define AVSYNC_LIB_INSTALL_TARGET_CMDS
+ $(TARGET_CONFIGURE_OPTS) $(MAKE) CC=$(TARGET_CC) -C $(@D)/ install
+endef
+
+define AVSYNC_LIB_INSTALL_CLEAN_CMDS
+ $(MAKE) CC=$(TARGET_CC) -C $(@D) clean
+endef
+
+$(eval $(generic-package))
diff --git a/avsync-lib/src/LICENSE b/avsync-lib/src/LICENSE
new file mode 100644
index 0000000..592455c
--- /dev/null
+++ b/avsync-lib/src/LICENSE
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 Amlogic, Inc. All rights reserved.
+//
+// All information contained herein is Amlogic confidential.
+//
+// This software is provided to you pursuant to Software License
+// Agreement (SLA) with Amlogic Inc ("Amlogic"). This software may be
+// used only in accordance with the terms of this agreement.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification is strictly prohibited without prior written permission
+// from Amlogic.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/avsync-lib/src/Makefile b/avsync-lib/src/Makefile
new file mode 100644
index 0000000..64820ad
--- /dev/null
+++ b/avsync-lib/src/Makefile
@@ -0,0 +1,27 @@
+OBJ = avsync.c tsync.c queue.c pattern.c log.c
+
+TARGET = libamlavsync.so
+TEST = avsync_test
+
+# rules
+all: $(TARGET) $(TEST)
+
+LD_FLAG = -lm -lpthread
+
+$(TARGET): $(OBJ)
+ $(CC) $(TARGET_CFLAGS) -g -D_FILE_OFFSET_BITS=64 -Wall -I$(STAGING_DIR)/usr/include/ -L$(STAGING_DIR)/usr/lib $(LD_FLAG) $(OBJ) -shared -fPIC -o $@
+
+$(TEST): $(TARGET) test.c
+ cp $(TARGET) $(STAGING_DIR)/usr/lib/
+ $(CC) $(TARGET_CFLAGS) -g -D_FILE_OFFSET_BITS=64 -Wall -I$(STAGING_DIR)/usr/include/ -L$(STAGING_DIR)/usr/lib -lamlavsync test.c -o $@
+
+.PHONY: clean
+
+clean:
+ rm -f *.o $(TARGET) $(TEST)
+
+install:
+ cp aml_avsync_log.h $(STAGING_DIR)/usr/include/
+ cp aml_avsync.h $(STAGING_DIR)/usr/include/
+ cp $(TARGET) $(TARGET_DIR)/usr/lib/
+ cp $(TEST) $(TARGET_DIR)/usr/bin/
diff --git a/avsync-lib/src/aml_avsync.h b/avsync-lib/src/aml_avsync.h
new file mode 100644
index 0000000..f7660c2
--- /dev/null
+++ b/avsync-lib/src/aml_avsync.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020 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:
+ * User space AV sync module.
+ *
+ * Author: song.zhao@amlogic.com
+ */
+#ifndef AML_AVSYNC_H__
+#define AML_AVSYNC_H__
+
+#include <stdint.h>
+#include <time.h>
+
+typedef uint32_t pts90K;
+struct vframe;
+typedef void (*free_frame)(struct vframe * frame);
+
+struct vframe {
+ /* private user data */
+ void *private;
+ pts90K pts;
+ /* duration of this frame. 0 for invalid value */
+ pts90K duration;
+ /* free function, will be called when multi frames are
+ * toggled in a single VSYNC, on frames not for display.
+ * For the last toggled frame, free won't be called. Caller
+ * of av_sync_pop_frame() are responsible for free poped frame.
+ * For example, if frame 1/2/3 are toggled in a single VSYCN,
+ * free() of 1/2 will be called, but free() of 3 won't.
+ */
+ free_frame free;
+
+ //For internal usage under this line
+ /*holding period */
+ int hold_period;
+};
+
+/* create and attach to kernel session. The returned avsync module will
+ * associated with @session_id.
+ * Params:
+ * @session_id: unique AV sync session ID to bind audio and video
+ * usually get from kernel driver.
+ * @start_thres: The start threshold of AV sync module. Set it to 0 for
+ * a default value. For low latency mode, set it to 1. Bigger
+ * value will increase the delay of the first frame shown.
+ * AV sync will start when frame number reached threshold.
+ * @delay: AV sync delay number. The delay of display pipeline.
+ * 2 for video planes
+ * 1 for osd planes
+ * @vsync_interval: Interval of VSYNC, in uint of 90K.
+ * Return:
+ * null for failure, or handle for avsync module.
+ */
+void* av_sync_create(int session_id, int start_thres, int delay, pts90K vsync_interval);
+
+void av_sync_destroy(void *sync);
+
+/* Pause/Resume AV sync module.
+ * It will return last frame in @av_sync_pop_frame() in pause state
+ * Params:
+ * @sync: AV sync module handle
+ * @pause: pause for true, or resume.
+ * Return:
+ * 0 for OK, or error code
+ */
+int av_sync_pause(void *sync, bool pause);
+
+/* Push a new frame to AV sync module
+ * Params:
+ * @sync: AV sync module handle
+ * Return:
+ * 0 for OK, or error code
+ */
+int av_sync_push_frame(void *sync , struct vframe *frame);
+
+/* Pop video frame for next VSYNC. This API should be VSYNC triggerd.
+ * Params:
+ * @sync: AV sync module handle
+ * Return:
+ * Old frame if current frame is hold
+ * New frame if it is time for a frame toggle.
+ * null if there is no frame to pop out (underrun).
+ * */
+struct vframe *av_sync_pop_frame(void *sync);
+
+/* notify a change in display refresh rate
+ * All AV phase/rate logic will be reset
+ * Params:
+ * @sync: AV sync module handle
+ * @vsync_interval: Interval of VSYNC, in uint of 90K.
+ */
+void av_sync_update_vsync_interval(void *sync, pts90K vsync_interval);
+
+#endif
diff --git a/avsync-lib/src/aml_avsync_log.h b/avsync-lib/src/aml_avsync_log.h
new file mode 100644
index 0000000..144fa9a
--- /dev/null
+++ b/avsync-lib/src/aml_avsync_log.h
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2017 rxi
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MIT license. See `log.c` for details.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#define LOG_VERSION "0.1.0"
+
+typedef void (*log_LockFn)(void *udata, int lock);
+
+enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
+
+#define log_trace(...) log_log(LOG_TRACE, __func__, __LINE__, __VA_ARGS__)
+#define log_debug(...) log_log(LOG_DEBUG, __func__, __LINE__, __VA_ARGS__)
+#define log_info(...) log_log(LOG_INFO, __func__, __LINE__, __VA_ARGS__)
+#define log_warn(...) log_log(LOG_WARN, __func__, __LINE__, __VA_ARGS__)
+#define log_error(...) log_log(LOG_ERROR, __func__, __LINE__, __VA_ARGS__)
+#define log_fatal(...) log_log(LOG_FATAL, __func__, __LINE__, __VA_ARGS__)
+
+void log_set_udata(void *udata);
+void log_set_lock(log_LockFn fn);
+void log_set_fp(FILE *fp);
+void log_set_level(int level);
+void log_set_quiet(int enable);
+
+void log_log(int level, const char *file, int line, const char *fmt, ...);
+
+#endif
diff --git a/avsync-lib/src/avsync.c b/avsync-lib/src/avsync.c
new file mode 100644
index 0000000..38c6214
--- /dev/null
+++ b/avsync-lib/src/avsync.c
@@ -0,0 +1,408 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+
+#include "aml_avsync.h"
+#include "queue.h"
+#include "pattern.h"
+#include "tsync.h"
+#include "aml_avsync_log.h"
+
+enum sync_mode {
+ AV_SYNC_MODE_AMASTER = 0,
+ AV_SYNC_MODE_VMASTER = 1,
+ AV_SYNC_MODE_PCR_MASTER = 2,
+};
+
+enum sync_state {
+ AV_SYNC_STAT_INIT = 0,
+ AV_SYNC_STAT_RUNNING = 1,
+ AV_SYNC_STAT_SYNC_SETUP = 2,
+ AV_SYNC_STAT_SYNC_LOST = 3,
+};
+
+struct av_sync_session {
+ /* session id attached */
+ int session_id;
+ /* playback time, will stop increasing during pause */
+ pts90K stream_time;
+ pts90K vpts;
+
+ /* phase adjustment of stream time for rate control */
+ pts90K phase;
+ bool phase_set;
+
+ /* pts of last rendered frame */
+ pts90K last_pts;
+ struct vframe *last_frame;
+
+ /* monotonic system time, keep increasing during pause */
+ struct timespec system_time;
+ bool first_frame_toggled;
+ /* Whether in pause state */
+ bool paused;
+ enum sync_mode mode;
+ enum sync_state state;
+ void *pattern_detector;
+ void *frame_q;
+ /* start threshold */
+ int start_thres;
+
+ /* display property */
+ int delay;
+ pts90K vsync_interval;
+
+ /* state lock */
+ pthread_mutex_t lock;
+ /* pattern */
+ int last_holding_peroid;
+ bool tsync_started;
+};
+
+#define MAX_FRAME_NUM 32
+#define DEFAULT_START_THRESHOLD 2
+#define TIME_UNIT90K (90000)
+#define AV_DISCONTINUE_THREDHOLD_MIN (TIME_UNIT90K * 3)
+
+static bool frame_expire(struct av_sync_session* avsync,
+ uint32_t systime,
+ struct vframe * frame,
+ struct vframe * next_frame,
+ int toggle_cnt);
+static void pattern_detect(struct av_sync_session* avsync,
+ int cur_period,
+ int last_period);
+
+void* av_sync_create(int session_id, int start_thres,
+ int delay, pts90K vsync_interval)
+{
+ struct av_sync_session *avsync = NULL;
+
+ if (start_thres > 5) {
+ log_error("start_thres too big: %d", start_thres);
+ return NULL;
+ }
+ if (delay != 1 && delay != 2) {
+ log_error("invalid delay: %d\n", delay);
+ return NULL;
+ }
+ if (vsync_interval < 750 || vsync_interval >= 3750) {
+ log_error("invalid vsync interval: %d", vsync_interval);
+ return NULL;
+ }
+
+ avsync = (struct av_sync_session *)calloc(1, sizeof(*avsync));
+ if (!avsync) {
+ log_error("OOM");
+ return NULL;
+ }
+ avsync->pattern_detector = create_pattern_detector();
+ if (!avsync->pattern_detector) {
+ log_error("pd create fail");
+ free(avsync);
+ return NULL;
+ }
+ avsync->state = AV_SYNC_STAT_INIT;
+ avsync->first_frame_toggled = false;
+ avsync->paused = false;
+ avsync->phase_set = false;
+ avsync->session_id = session_id;
+ avsync->mode = AV_SYNC_MODE_AMASTER;
+ avsync->last_frame = NULL;
+ avsync->tsync_started = false;
+ if (!start_thres)
+ avsync->start_thres = DEFAULT_START_THRESHOLD;
+ else
+ avsync->start_thres = start_thres;
+ avsync->delay = delay;
+ avsync->vsync_interval = vsync_interval;
+
+ avsync->frame_q = create_q(MAX_FRAME_NUM);
+ if (!avsync->frame_q) {
+ log_error("create queue fail");
+ destroy_pattern_detector(avsync->pattern_detector);
+ free(avsync);
+ return NULL;
+ }
+ //TODO: connect kernel session
+
+ pthread_mutex_init(&avsync->lock, NULL);
+ log_info("start_thres: %d delay: %d interval: %d done\n",
+ start_thres, delay, vsync_interval);
+ return avsync;
+}
+
+static int internal_stop(struct av_sync_session *avsync)
+{
+ int ret = 0;
+ struct vframe *frame;
+
+ pthread_mutex_lock(&avsync->lock);
+ if (avsync->state == AV_SYNC_STAT_INIT)
+ goto exit;
+
+ while (!dqueue_item(avsync->frame_q, (void **)&frame)) {
+ frame->free(frame);
+ }
+
+ avsync->state = AV_SYNC_STAT_INIT;
+exit:
+ pthread_mutex_unlock(&avsync->lock);
+ return ret;
+}
+
+/* destroy and detach from kernel session */
+void av_sync_destroy(void *sync)
+{
+ struct av_sync_session *avsync = (struct av_sync_session *)sync;
+
+ if (!avsync)
+ return;
+
+ log_info("begin");
+ if (avsync->state != AV_SYNC_STAT_INIT)
+ internal_stop(avsync);
+
+ /* all frames are freed */
+ //TODO: disconnect kernel session
+ tsync_enable(avsync->session_id, false);
+ pthread_mutex_destroy(&avsync->lock);
+ destroy_q(avsync->frame_q);
+ destroy_pattern_detector(avsync->pattern_detector);
+ free(avsync);
+ log_info("done");
+}
+
+int av_sync_pause(void *sync, bool pause)
+{
+ struct av_sync_session *avsync = (struct av_sync_session *)sync;
+
+ if (!avsync)
+ return -1;
+
+ avsync->paused = pause;
+ tsync_send_video_pause(avsync->session_id, pause);
+ log_info("paused:%d\n", pause);
+ return 0;
+}
+
+int av_sync_push_frame(void *sync , struct vframe *frame)
+{
+ int ret;
+ struct av_sync_session *avsync = (struct av_sync_session *)sync;
+
+ if (!avsync)
+ return -1;
+
+ frame->hold_period = 0;
+ ret = queue_item(avsync->frame_q, frame);
+ if (avsync->state == AV_SYNC_STAT_INIT &&
+ queue_size(avsync->frame_q) >= avsync->start_thres) {
+ avsync->state = AV_SYNC_STAT_RUNNING;
+ log_info("state: init --> running");
+ }
+
+ if (ret)
+ log_error("%s queue fail:%d", ret);
+ return ret;
+
+}
+
+struct vframe *av_sync_pop_frame(void *sync)
+{
+ struct vframe *frame = NULL;
+ struct av_sync_session *avsync = (struct av_sync_session *)sync;
+ int toggle_cnt = 0;
+ uint32_t systime = tsync_get_pcr(avsync->session_id);
+
+ pthread_mutex_lock(&avsync->lock);
+ if (avsync->state == AV_SYNC_STAT_INIT)
+ goto exit;
+
+ if (!avsync->tsync_started) {
+ if (peek_item(avsync->frame_q, (void **)&frame, 0) || !frame) {
+ log_info("empty q");
+ goto exit;
+ }
+
+ tsync_enable(avsync->session_id, true);
+ /* video start event */
+ tsync_send_video_start(avsync->session_id, frame->pts);
+ log_info("video start %d", frame->pts);
+ avsync->tsync_started = true;
+ }
+
+ while (!peek_item(avsync->frame_q, (void **)&frame, 0)) {
+ struct vframe *next_frame = NULL;
+
+ peek_item(avsync->frame_q, (void **)&next_frame, 1);
+ if (next_frame)
+ log_debug("cur_f %d next_f %d", frame->pts, next_frame->pts);
+ if (frame_expire(avsync, systime, frame, next_frame, toggle_cnt)) {
+ log_debug("cur_f %d expire", frame->pts);
+ toggle_cnt++;
+
+ pattern_detect(avsync,
+ (avsync->last_frame?avsync->last_frame->hold_period:0),
+ avsync->last_holding_peroid);
+ if (avsync->last_frame)
+ avsync->last_holding_peroid = avsync->last_frame->hold_period;
+
+ dqueue_item(avsync->frame_q, (void **)&frame);
+ if (avsync->last_frame) {
+ /* free frame that are not for display */
+ if (toggle_cnt > 1)
+ avsync->last_frame->free(avsync->last_frame);
+ } else {
+ avsync->first_frame_toggled = true;
+ log_info("first frame %d", frame->pts);
+ }
+ avsync->last_frame = frame;
+ } else
+ break;
+ }
+
+exit:
+ pthread_mutex_unlock(&avsync->lock);
+ if (avsync->last_frame)
+ log_debug("pop %d", avsync->last_frame->pts);
+ else
+ log_debug("pop (nil)");
+ if (avsync->last_frame)
+ avsync->last_frame->hold_period++;
+ return avsync->last_frame;
+}
+
+void av_sync_update_vsync_interval(void *sync, pts90K vsync_interval)
+{
+ struct av_sync_session *avsync = (struct av_sync_session *)sync;
+
+ pthread_mutex_lock(&avsync->lock);
+ avsync->vsync_interval = vsync_interval;
+ if (avsync->state >= AV_SYNC_STAT_RUNNING) {
+ reset_pattern(avsync->pattern_detector);
+ avsync->phase_set = false;
+ }
+ pthread_mutex_unlock(&avsync->lock);
+}
+
+static inline uint32_t abs_diff(uint32_t a, uint32_t b)
+{
+ return a > b ? a - b : b - a;
+}
+
+static bool frame_expire(struct av_sync_session* avsync,
+ uint32_t systime,
+ struct vframe * frame,
+ struct vframe * next_frame,
+ int toggle_cnt)
+{
+ uint32_t fpts = frame->pts;
+ bool expire = false;
+ uint32_t pts_correction = avsync->delay * avsync->vsync_interval;
+
+ if (!fpts) {
+ if (avsync->last_frame) {
+ /* try to accumulate duration as PTS */
+ fpts = avsync->vpts + avsync->last_frame->duration;
+ } else {
+ fpts = avsync->vpts;
+ }
+ }
+ systime += pts_correction;
+
+ /* phase adjustment */
+ if (avsync->phase_set)
+ systime += avsync->phase;
+
+ log_trace("systime:%d phase:%d correct:%d", systime,
+ avsync->phase_set?avsync->phase:0, pts_correction);
+ if (abs_diff(systime, fpts) > AV_DISCONTINUE_THREDHOLD_MIN &&
+ avsync->first_frame_toggled) {
+ /* ignore discontinity under pause */
+ if (avsync->paused && avsync->mode != AV_SYNC_MODE_PCR_MASTER)
+ return false;
+
+ log_warn("sync lost systime:%x fpts:%x", systime, fpts);
+ avsync->state = AV_SYNC_STAT_SYNC_LOST;
+ avsync->phase_set = false;
+ if (systime > fpts) {
+ if (frame->pts)
+ tsync_send_video_disc(avsync->session_id, frame->pts);
+ else if (avsync->mode != AV_SYNC_MODE_PCR_MASTER)
+ tsync_send_video_disc(avsync->session_id, frame->pts);
+ return false;
+ } else if (avsync->mode == AV_SYNC_MODE_PCR_MASTER) {
+ if (frame->pts)
+ tsync_send_video_disc(avsync->session_id, frame->pts);
+ else {
+ tsync_send_video_disc(avsync->session_id, fpts);
+ return true;
+ }
+ }
+ } else {
+ if (avsync->state != AV_SYNC_STAT_SYNC_SETUP)
+ log_info("sync setup");
+ avsync->state = AV_SYNC_STAT_SYNC_SETUP;
+ }
+
+ expire = (systime >= fpts);
+
+ /* scatter the frame in different vsync whenever possible */
+ if (expire && next_frame && next_frame->pts && toggle_cnt) {
+ /* multi frame expired in current vsync but no frame in next vsync */
+ if (systime + avsync->vsync_interval < next_frame->pts) {
+ expire = false;
+ frame->hold_period++;
+ log_debug("unset expire systime:%d inter:%d next_pts:%d toggle_cnt:%d",
+ systime, avsync->vsync_interval, next_frame->pts, toggle_cnt);
+ }
+ } else if (!expire && next_frame && next_frame->pts && !toggle_cnt) {
+ /* next vsync will have at least 2 frame expired */
+ if (systime + avsync->vsync_interval > next_frame->pts) {
+ expire = true;
+ log_debug("set expire systime:%d inter:%d next_pts:%d",
+ systime, avsync->vsync_interval, next_frame->pts);
+ }
+ }
+
+ correct_pattern(avsync->pattern_detector, frame, next_frame,
+ (avsync->last_frame?avsync->last_frame->hold_period:0),
+ avsync->last_holding_peroid, systime,
+ avsync->vsync_interval, &expire);
+
+ if (expire) {
+ avsync->vpts = fpts;
+ /* phase adjustment */
+ if (!avsync->phase_set) {
+ //systime = tsync_get_pcr(avsync->session_id);
+ if ( systime > fpts && (systime - fpts) < avsync->vsync_interval / 4) {
+ /* too aligned, separate them to 1/4 VSYNC */
+ avsync->phase += avsync->vsync_interval / 4 - (systime - fpts);
+ avsync->phase_set = true;
+ log_info("adjust phase to %d", avsync->phase);
+ }
+ }
+ }
+
+ return expire;
+}
+
+static void pattern_detect(struct av_sync_session* avsync, int cur_period, int last_period)
+{
+ log_trace("cur_period: %d last_period: %d", cur_period, last_period);
+ detect_pattern(avsync->pattern_detector, AV_SYNC_FRAME_P32, cur_period, last_period);
+ detect_pattern(avsync->pattern_detector, AV_SYNC_FRAME_P22, cur_period, last_period);
+ detect_pattern(avsync->pattern_detector, AV_SYNC_FRAME_P41, cur_period, last_period);
+ //TODO: add 11 support
+}
diff --git a/avsync-lib/src/log.c b/avsync-lib/src/log.c
new file mode 100644
index 0000000..8978977
--- /dev/null
+++ b/avsync-lib/src/log.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2017 rxi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "aml_avsync_log.h"
+
+static struct {
+ void *udata;
+ log_LockFn lock;
+ FILE *fp;
+ int level;
+ int quiet;
+} L;
+
+
+static const char *level_names[] = {
+ "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
+};
+
+#ifdef LOG_USE_COLOR
+static const char *level_colors[] = {
+ "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
+};
+#endif
+
+
+static void lock(void) {
+ if (L.lock) {
+ L.lock(L.udata, 1);
+ }
+}
+
+
+static void unlock(void) {
+ if (L.lock) {
+ L.lock(L.udata, 0);
+ }
+}
+
+
+void log_set_udata(void *udata) {
+ L.udata = udata;
+}
+
+
+void log_set_lock(log_LockFn fn) {
+ L.lock = fn;
+}
+
+
+void log_set_fp(FILE *fp) {
+ L.fp = fp;
+}
+
+
+void log_set_level(int level) {
+ L.level = level;
+}
+
+
+void log_set_quiet(int enable) {
+ L.quiet = enable ? 1 : 0;
+}
+
+
+void log_log(int level, const char *file, int line, const char *fmt, ...) {
+ if (level < L.level) {
+ return;
+ }
+
+ /* Acquire lock */
+ lock();
+
+ /* Get current time */
+ time_t t = time(NULL);
+ struct tm *lt = localtime(&t);
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ /* Log to stderr */
+ if (!L.quiet) {
+ va_list args;
+ char buf[16];
+ buf[strftime(buf, sizeof(buf), "%H:%M:%S", lt)] = '\0';
+#ifdef LOG_USE_COLOR
+ fprintf(
+ stderr, "%s:%03ld %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
+ buf, tv.tv_usec/1000, level_colors[level], level_names[level], file, line);
+#else
+ fprintf(stderr, "%s:%03ld %-5s %s:%d: ", buf, tv.tv_usec/1000, level_names[level], file, line);
+#endif
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+
+ /* Log to file */
+ if (L.fp) {
+ va_list args;
+ char buf[32];
+ buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt)] = '\0';
+ fprintf(L.fp, "%s:%03ld %-5s %s:%d: ", buf, tv.tv_usec/1000, level_names[level], file, line);
+ va_start(args, fmt);
+ vfprintf(L.fp, fmt, args);
+ va_end(args);
+ fprintf(L.fp, "\n");
+ fflush(L.fp);
+ }
+
+ /* Release lock */
+ unlock();
+}
diff --git a/avsync-lib/src/pattern.c b/avsync-lib/src/pattern.c
new file mode 100644
index 0000000..69f4840
--- /dev/null
+++ b/avsync-lib/src/pattern.c
@@ -0,0 +1,254 @@
+/*
+ * 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: frame pattern API ported from kernel video.c
+ * Author: song.zhao@amlogic.com
+ */
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "aml_avsync.h"
+#include "pattern.h"
+#include "aml_avsync_log.h"
+
+#define PATTERN_32_D_RANGE 10
+#define PATTERN_22_D_RANGE 10
+#define PATTERN_41_D_RANGE 2
+#define PATTERN_32_DURATION 3750
+#define PATTERN_22_DURATION 3000
+
+struct pattern_detector {
+ int match_cnt[AV_SYNC_FRAME_PMAX];
+ int enter_cnt[AV_SYNC_FRAME_PMAX];
+ int exit_cnt[AV_SYNC_FRAME_PMAX];
+
+ int pattern_41[4];
+ int pattern_41_index;
+ int detected;
+
+ /* reset lock */
+ pthread_mutex_t lock;
+};
+
+void* create_pattern_detector()
+{
+ struct pattern_detector *pd;
+
+ pd = (struct pattern_detector *)calloc(1, sizeof(*pd));
+ if (!pd) {
+ log_error("OOM");
+ return NULL;
+ }
+ pthread_mutex_init(&pd->lock, NULL);
+ pd->detected = -1;
+ return pd;
+}
+
+void destroy_pattern_detector(void *handle)
+{
+ struct pattern_detector *pd = (struct pattern_detector *)handle;
+
+ if (pd) {
+ pthread_mutex_destroy(&pd->lock);
+ free(pd);
+ }
+}
+
+void reset_pattern(void *handle)
+{
+ struct pattern_detector *pd = (struct pattern_detector *)handle;
+
+ if (!pd)
+ return;
+
+ pthread_mutex_lock(&pd->lock);
+ pd->detected = -1;
+ memset(pd->match_cnt, 0, sizeof(pd->match_cnt));
+ pthread_mutex_unlock(&pd->lock);
+}
+
+void correct_pattern(void* handle, struct vframe *frame, struct vframe *nextframe,
+ int cur_peroid, int last_peroid,
+ pts90K systime, pts90K vsync_interval, bool *expire)
+{
+ struct pattern_detector *pd = (struct pattern_detector *)handle;
+ int pattern_range, expected_cur_peroid;
+ int expected_prev_interval;
+ int npts = 0;
+
+ /* Dont do anything if we have invalid data */
+ if (!pd || !frame || !frame->pts)
+ return;
+
+ if (nextframe)
+ npts = nextframe->pts;
+
+ pthread_mutex_lock(&pd->lock);
+ switch (pd->detected) {
+ case AV_SYNC_FRAME_P32:
+ pattern_range = PATTERN_32_D_RANGE;
+ switch (last_peroid) {
+ case 3:
+ expected_prev_interval = 3;
+ expected_cur_peroid = 2;
+ break;
+ case 2:
+ expected_prev_interval = 2;
+ expected_cur_peroid = 3;
+ break;
+ default:
+ goto exit;
+ }
+ if (!npts)
+ npts = frame->pts + PATTERN_32_DURATION;
+ break;
+ case AV_SYNC_FRAME_P22:
+ if (last_peroid != 2)
+ goto exit;
+ pattern_range = PATTERN_22_D_RANGE;
+ expected_prev_interval = 2;
+ expected_cur_peroid = 2;
+ if (!npts)
+ npts = frame->pts + PATTERN_22_DURATION;
+ break;
+ case AV_SYNC_FRAME_P41:
+ /* TODO */
+ default:
+ goto exit;
+ }
+
+ /* We do nothing if we dont have enough data*/
+ if (pd->match_cnt[pd->detected] != pattern_range)
+ goto exit;
+
+ if (*expire) {
+ if (cur_peroid < expected_cur_peroid) {
+ /* 2323232323..2233..2323, prev=2, curr=3,*/
+ /* check if next frame will toggle after 3 vsyncs */
+ /* 22222...22222 -> 222..2213(2)22...22 */
+ /* check if next frame will toggle after 3 vsyncs */
+
+ if (((int)(systime + (expected_prev_interval + 1) *
+ vsync_interval - npts) >= 0)) {
+ *expire = false;
+ log_info("hold frame for pattern: %d", pd->detected);
+ }
+
+#if 0 // Frame scattering is the right place to adjust the hold time.
+ /* here need to escape a vsync */
+ if (systime > (frame->pts + vsync_interval)) {
+ *expire = true;
+ pts_escape_vsync = 1;
+ log_info("escape a vsync pattern: %d", pd->detected);
+ }
+#endif
+ }
+ } else {
+ if (cur_peroid == expected_cur_peroid) {
+ /* 23232323..233223...2323 curr=2, prev=3 */
+ /* check if this frame will expire next vsyncs and */
+ /* next frame will expire after 3 vsyncs */
+ /* 22222...22222 -> 222..223122...22 */
+ /* check if this frame will expire next vsyncs and */
+ /* next frame will expire after 2 vsyncs */
+
+ if (((int)(systime + vsync_interval - frame->pts) >= 0) &&
+ ((int)(systime + vsync_interval * (expected_prev_interval - 1) - npts) < 0) &&
+ ((int)(systime + expected_prev_interval * vsync_interval - npts) >= 0)) {
+ *expire = true;
+ log_info("pull frame for pattern: %d", pd->detected);
+ }
+ }
+ }
+exit:
+ pthread_mutex_unlock(&pd->lock);
+}
+
+void detect_pattern(void* handle, enum frame_pattern pattern, int cur_peroid, int last_peroid)
+{
+ struct pattern_detector *pd = (struct pattern_detector *)handle;
+ int factor1 = 0, factor2 = 0, range = 0;
+
+ if (!pd || pattern >= AV_SYNC_FRAME_PMAX)
+ return;
+
+ pthread_mutex_lock(&pd->lock);
+ if (pattern == AV_SYNC_FRAME_P32) {
+ factor1 = 3;
+ factor2 = 2;
+ range = PATTERN_32_D_RANGE;
+ } else if (pattern == AV_SYNC_FRAME_P22) {
+ factor1 = 2;
+ factor2 = 2;
+ range = PATTERN_22_D_RANGE;
+ } else if (pattern == AV_SYNC_FRAME_P41) {
+ /* update 2111 mode detection */
+ if (cur_peroid == 2) {
+ if (pd->pattern_41[1] == 1 && pd->pattern_41[2] == 1 && pd->pattern_41[3] == 1 &&
+ (pd->match_cnt[pattern] < PATTERN_41_D_RANGE)) {
+ pd->match_cnt[pattern]++;
+ if (pd->match_cnt[pattern] == PATTERN_41_D_RANGE) {
+ pd->enter_cnt[pattern]++;
+ pd->detected = pattern;
+ log_info("video 4:1 mode detected");
+ }
+ }
+ pd->pattern_41[0] = 2;
+ pd->pattern_41_index = 1;
+ } else if (cur_peroid == 1) {
+ if ((pd->pattern_41_index < 4) &&
+ (pd->pattern_41_index > 0)) {
+ pd->pattern_41[pd->pattern_41_index] = 1;
+ pd->pattern_41_index++;
+ } else if (pd->match_cnt[pattern] == PATTERN_41_D_RANGE) {
+ pd->match_cnt[pattern] = 0;
+ pd->pattern_41_index = 0;
+ pd->exit_cnt[pattern]++;
+ memset(&pd->pattern_41[0], 0, sizeof(pd->pattern_41));
+ log_info("video 4:1 mode broken");
+ } else {
+ pd->match_cnt[pattern] = 0;
+ pd->pattern_41_index = 0;
+ memset(&pd->pattern_41[0], 0, sizeof(pd->pattern_41));
+ }
+ } else if (pd->match_cnt[pattern] == PATTERN_41_D_RANGE) {
+ pd->match_cnt[pattern] = 0;
+ pd->pattern_41_index = 0;
+ memset(&pd->pattern_41[0], 0, sizeof(pd->pattern_41));
+ pd->exit_cnt[pattern]++;
+ log_info("video 4:1 mode broken");
+ } else {
+ pd->match_cnt[pattern] = 0;
+ pd->pattern_41_index = 0;
+ memset(&pd->pattern_41[0], 0, sizeof(pd->pattern_41));
+ }
+ goto exit;
+ }
+
+ /* update 3:2 or 2:2 mode detection */
+ if (((last_peroid == factor1) && (cur_peroid == factor2)) ||
+ ((last_peroid == factor2) && (cur_peroid == factor1))) {
+ if (pd->match_cnt[pattern] < range) {
+ pd->match_cnt[pattern]++;
+ if (pd->match_cnt[pattern] == range) {
+ pd->enter_cnt[pattern]++;
+ pd->detected = pattern;
+ log_info("video %d:%d mode detected", factor1, factor2);
+ }
+ }
+ } else if (pd->match_cnt[pattern] == range) {
+ pd->match_cnt[pattern] = 0;
+ pd->exit_cnt[pattern]++;
+ log_info("video %d:%d mode broken", factor1, factor2);
+ } else
+ pd->match_cnt[pattern] = 0;
+
+exit:
+ pthread_mutex_unlock(&pd->lock);
+}
diff --git a/avsync-lib/src/pattern.h b/avsync-lib/src/pattern.h
new file mode 100644
index 0000000..2ba86ec
--- /dev/null
+++ b/avsync-lib/src/pattern.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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:
+ * User space AV sync module.
+ *
+ * Author: song.zhao@amlogic.com
+ */
+#ifndef AML_AVSYNC_PATTERN_H__
+#define AML_AVSYNC_PATTERN_H__
+
+enum frame_pattern {
+ AV_SYNC_FRAME_P32 = 0,
+ AV_SYNC_FRAME_P22 = 1,
+ AV_SYNC_FRAME_P41 = 2,
+ AV_SYNC_FRAME_P11 = 3,
+ AV_SYNC_FRAME_PMAX,
+};
+
+void* create_pattern_detector();
+void destroy_pattern_detector(void *handle);
+void reset_pattern(void *handle);
+void detect_pattern(void* handle, enum frame_pattern pattern, int cur_peroid, int last_peroid);
+void correct_pattern(void* handle, struct vframe *frame, struct vframe *nextframe,
+ int cur_peroid, int last_peroid, pts90K systime, pts90K vsync_interval, bool *expire);
+#endif
diff --git a/avsync-lib/src/queue.c b/avsync-lib/src/queue.c
new file mode 100644
index 0000000..e3318c1
--- /dev/null
+++ b/avsync-lib/src/queue.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2020 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: fifo implementation for single reader single writer
+ * Author: song.zhao@amlogic.com
+ */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "aml_avsync.h"
+#include "queue.h"
+
+struct queue {
+ int max_len;
+ int ri; //read index
+ int wi; //write index
+ int total_num;
+ void **items;
+};
+
+void* create_q(int max_len)
+{
+ struct queue *q;
+
+ if (max_len <= 0) {
+ printf("%s %d invalid max_len:%d\n",
+ __func__, __LINE__, max_len);
+ return NULL;
+ }
+
+ q = (struct queue*)calloc(1, sizeof(*q));
+ if (!q) {
+ printf("%s %d OOM\n", __func__, __LINE__);
+ return NULL;
+ }
+ q->items = (void **)calloc(max_len, sizeof(void *));
+ if (!q->items) {
+ printf("%s %d OOM\n", __func__, __LINE__);
+ free(q);
+ return NULL;
+ }
+
+ q->max_len = max_len;
+ q->ri = q->wi = 0;
+ q->total_num = 0;
+ return q;
+}
+
+void destroy_q(void * queue)
+{
+ struct queue *q = queue;
+
+ if (!q)
+ return;
+ free(q->items);
+ free(q);
+}
+
+int queue_item(void *queue, void * item)
+{
+ struct queue *q = queue;
+
+ if (!q || q->total_num == q->max_len)
+ return -1;
+ q->items[q->wi] = item;
+ if (q->wi == q->max_len - 1)
+ q->wi = 0;
+ else
+ q->wi++;
+ q->total_num++;
+
+ return 0;
+}
+
+int peek_item(void *queue, void** p_item, uint32_t cnt)
+{
+ struct queue *q = queue;
+ int32_t index;
+
+ if (!q || !q->total_num || q->total_num <= cnt)
+ return -1;
+
+ index = q->ri;
+ index += cnt;
+ if (index >= q->max_len)
+ index -= q->max_len;
+ *p_item = q->items[index];
+
+ return 0;
+}
+
+int dqueue_item(void *queue, void** p_item)
+{
+ struct queue *q = queue;
+
+ if (!q || !q->total_num)
+ return -1;
+ *p_item = q->items[q->ri];
+ if (q->ri == q->max_len - 1)
+ q->ri = 0;
+ else
+ q->ri++;
+ q->total_num--;
+
+ return 0;
+}
+
+int queue_size(void *queue)
+{
+ struct queue *q = queue;
+
+ if (!q)
+ return -1;
+ return q->total_num;
+}
diff --git a/avsync-lib/src/queue.h b/avsync-lib/src/queue.h
new file mode 100644
index 0000000..1ded643
--- /dev/null
+++ b/avsync-lib/src/queue.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 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: fifo implementation
+ * Author: song.zhao@amlogic.com
+ */
+
+#ifndef _AML_QUEUE_H_
+#define _AML_QUEUE_H_
+
+#include <stdint.h>
+
+void* create_q(int max_len);
+void destroy_q(void * queue);
+int queue_item(void *queue, void * item);
+/* cnt 0 for frist one in fifo, cnt 1 for 2nd one in fifo, etc */
+int peek_item(void *queue, void** p_item, uint32_t cnt);
+int dqueue_item(void *queue, void** p_item);
+int queue_size(void *queue);
+
+#endif
diff --git a/avsync-lib/src/test.c b/avsync-lib/src/test.c
new file mode 100644
index 0000000..ce32718
--- /dev/null
+++ b/avsync-lib/src/test.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2020 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: test for avsync
+ *
+ * Author: song.zhao@amlogic.com
+ */
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "aml_avsync.h"
+#include "aml_avsync_log.h"
+
+#define TSYNC_MODE "/sys/class/tsync/mode"
+#define TSYNC_PCRSCR "/sys/class/tsync/pts_pcrscr"
+#define VPTS_INC_UPINT "/sys/class/video/vsync_pts_inc_upint"
+
+#define FRAME_NUM 32
+#define PATTERN_32_DURATION 3750
+#define PATTERN_22_DURATION 3000
+
+static struct vframe frame[FRAME_NUM];
+static int frame_received;
+
+static int sysfs_set_sysfs_str(const char *path, const char *val)
+{
+ int fd;
+ fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
+ if (fd >= 0) {
+ if(write(fd, val, strlen(val)) != strlen(val))
+ log_error("write fail");
+ close(fd);
+ return 0;
+ } else {
+ log_error("test: unable to open file %s,err: %s", path, strerror(errno));
+ }
+ return -1;
+}
+
+static void frame_free(struct vframe * frame)
+{
+ log_info("free %d\n", (int)frame->private);
+ frame_received++;
+}
+
+static void test(int refresh_rate, int pts_interval, struct vframe* frame)
+{
+ int i = 0;
+ void* handle;
+ int sleep_us = 1000000/refresh_rate;
+ struct vframe *last_frame = NULL, *pop_frame;
+
+ /* vmaster and reset pcr to 0 */
+ sysfs_set_sysfs_str(TSYNC_MODE, "0");
+ sysfs_set_sysfs_str(TSYNC_PCRSCR, "0");
+ sysfs_set_sysfs_str(VPTS_INC_UPINT, "1");
+
+ handle = av_sync_create(0, 2, 2, 90000/refresh_rate);
+ frame = (struct vframe*)calloc(FRAME_NUM, sizeof(*frame));
+ if (!frame) {
+ log_error("oom");
+ exit(1);
+ }
+
+ /* push max frames */
+ while (i < FRAME_NUM) {
+ frame[i].private = (void *)i;
+ frame[i].pts = PATTERN_22_DURATION * i;
+ frame[i].duration = PATTERN_22_DURATION;
+ frame[i].free = frame_free;
+ if (av_sync_push_frame(handle, &frame[i])) {
+ log_error("queue %d fail", i);
+ break;
+ }
+ log_info("queue %d", i);
+ i++;
+ }
+
+ i = 0;
+ while (frame_received < FRAME_NUM) {
+ usleep(sleep_us);
+ pop_frame = av_sync_pop_frame(handle);
+ if (pop_frame)
+ log_info("pop frame %02d", (int)pop_frame->private);
+ if (pop_frame != last_frame) {
+ i++;
+ last_frame = pop_frame;
+ frame_received++;
+ }
+ }
+
+ frame_received = 0;
+ av_sync_destroy(handle);
+}
+
+int main(int argc, const char** argv)
+{
+ log_set_level(LOG_TRACE);
+
+ log_info("\n----------------22 start------------\n");
+ test(60, PATTERN_22_DURATION, frame);
+ log_info("\n----------------22 end--------------\n");
+ log_info("\n----------------32 start------------\n");
+ test(60, PATTERN_32_DURATION, frame);
+ log_info("\n----------------32 start------------\n");
+ log_info("\n----------------41 start------------\n");
+ test(30, PATTERN_32_DURATION, frame);
+ log_info("\n----------------41 end--------------\n");
+ log_info("\n----------------11 start------------\n");
+ test(30, PATTERN_22_DURATION, frame);
+ log_info("\n----------------11 end--------------\n");
+
+ return 0;
+}
diff --git a/avsync-lib/src/tsync.c b/avsync-lib/src/tsync.c
new file mode 100644
index 0000000..d6383a2
--- /dev/null
+++ b/avsync-lib/src/tsync.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020 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: tsync warrper. Single instance ONLY. Session will be ignored
+ * Author: song.zhao@amlogic.com
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include "tsync.h"
+
+#define TSYNC_ENABLE "/sys/class/tsync/enable"
+#define TSYNC_PCRSCR "/sys/class/tsync/pts_pcrscr"
+#define TSYNC_EVENT "/sys/class/tsync/event"
+
+static int config_sys_node(const char* path, const char* value)
+{
+ int fd;
+ fd = open(path, O_RDWR);
+ if (fd < 0) {
+ printf("fail to open %s\n", path);
+ return -1;
+ }
+ if (write(fd, value, strlen(value)) != strlen(value)) {
+ printf("fail to write %s to %s\n", value, path);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ return 0;
+}
+
+static int get_sysfs_uint32(const char *path, uint32_t *value)
+{
+ int fd;
+ char valstr[64];
+ uint32_t val = 0;
+
+ fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ memset(valstr, 0, 64);
+ read(fd, valstr, 64 - 1);
+ valstr[strlen(valstr)] = '\0';
+ close(fd);
+ } else {
+ printf("unable to open file %s\n", path);
+ return -1;
+ }
+ if (sscanf(valstr, "0x%x", &val) < 1) {
+ printf("unable to get pts from: %s", valstr);
+ return -1;
+ }
+ *value = val;
+ return 0;
+}
+
+void tsync_enable(int session, bool enable)
+{
+ const char *val;
+
+ if (enable)
+ val = "1";
+ else
+ val = "0";
+ config_sys_node(TSYNC_ENABLE, val);
+}
+
+uint32_t tsync_get_pcr(int session)
+{
+ uint32_t pcr = 0;
+
+ get_sysfs_uint32(TSYNC_PCRSCR, &pcr);
+ return pcr;
+}
+
+//uint32_t tsync_get_vpts(int session);
+
+int tsync_send_video_start(int session, uint32_t vpts)
+{
+ char val[50];
+
+ snprintf(val, sizeof(val), "VIDEO_START:0x%x", vpts);
+ return config_sys_node(TSYNC_EVENT, val);
+}
+
+int tsync_send_video_pause(int session, bool pause)
+{
+ const char *val;
+
+ if (pause)
+ val = "VIDEO_PAUSE:0x1";
+ else
+ val = "VIDEO_PAUSE:0x0";
+ return config_sys_node(TSYNC_EVENT, val);
+}
+
+int tsync_send_video_disc(int session, uint32_t vpts)
+{
+ char val[50];
+
+ snprintf(val, sizeof(val), "VIDEO_TSTAMP_DISCONTINUITY:0x%x", vpts);
+ return config_sys_node(TSYNC_EVENT, val);
+}
diff --git a/avsync-lib/src/tsync.h b/avsync-lib/src/tsync.h
new file mode 100644
index 0000000..24cce06
--- /dev/null
+++ b/avsync-lib/src/tsync.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2020 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: tsync sysnode wrapper
+ * Author: song.zhao@amlogic.com
+ */
+
+#ifndef _AML_TSYNC_H_
+#define _AML_TSYNC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+void tsync_enable(int session, bool enable);
+uint32_t tsync_get_pcr(int session);
+//uint32_t tsync_get_vpts(int session);
+int tsync_send_video_start(int session, uint32_t vpts);
+int tsync_send_video_pause(int session, bool pause);
+int tsync_send_video_disc(int session, uint32_t vpts);
+
+#endif