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