Browse Source

Linux_SDK_V0.9.5

thead_admin 1 year ago
commit
c3a37a943c

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/.vscode
+output/

+ 47 - 0
Makefile

@@ -0,0 +1,47 @@
+ifneq ($(wildcard ../.param),)
+	include ../.param
+endif
+
+INC_PATH ?= /usr/include
+LIB_PATH ?= /usr/lib
+
+OUTPUTDIR = ./output
+LIBNAME = $(OUTPUTDIR)/libplink.so
+server_NAME = $(OUTPUTDIR)/plinkserver
+client_NAME = $(OUTPUTDIR)/plinkclient
+stitcher_NAME = $(OUTPUTDIR)/plinkstitcher
+
+INCS = ./inc
+LIBSRCS = ./src/process_linker.c
+LIBOBJS = $(LIBSRCS:.c=.o)
+server_SRCS = ./test/plink_server.c
+server_OBJS = $(server_SRCS:.c=.o)
+client_SRCS = ./test/plink_client.c
+client_OBJS = $(client_SRCS:.c=.o)
+stitcher_SRCS = ./test/plink_stitcher.c
+stitcher_OBJS = $(stitcher_SRCS:.c=.o)
+
+CFLAGS = -I$(INCS) -I$(INC_PATH)/vidmem
+CFLAGS += -pthread -fPIC -O
+
+$(shell if [ ! -e $(OUTPUTDIR) ];then mkdir -p $(OUTPUTDIR); fi)
+
+all: lib server client stitcher
+
+lib: 
+	$(CC) $(LIBSRCS) $(CFLAGS) -shared -o $(LIBNAME)
+
+server: lib
+	$(CC) $(server_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(server_NAME)
+
+client: lib
+	$(CC) $(client_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(client_NAME)
+
+stitcher: lib
+	$(CC) $(stitcher_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(stitcher_NAME)
+
+clean:
+	rm -rf $(OUTPUTDIR)
+
+%.o : %.c
+	$(CC) $(CFLAGS) -c $< -o $@

+ 57 - 0
README.md

@@ -0,0 +1,57 @@
+# Process Linker Repository
+This git module contains a linker library which can be used to connect two process for data sharing and passing file descriptor.
+
+- **src**: c source code of process linker library.
+- **inc**: public header files of process linker library. User should include these files to use process linker.
+- **test**: sample applications. Two sample applications, server and client, are implimented for test and reference purpose.
+
+## How to build
+Just run `make` and binaries will be generated in **output** folder.
+
+## How to use
+- **libplink.so**: shared library of process link. See API doc for more details of usage.
+- **plinkserver**: sample server application
+```shell
+usage: ./plinkserver [options]
+
+  Available options:
+    -l      plink file name (default: /tmp/plink.test)
+    -i      input YUV file name (mandatory)
+    -f      input color format (default: 3)
+                2 - I420
+                3 - NV12
+                4 - P010
+                14 - Bayer Raw 10bit
+                15 - Bayer Raw 12bit
+    -w      video width (mandatory)
+    -h      video height (mandatory)
+    -s      video buffer stride in bytes (default: video width)
+    -n      number of frames to send (default: 10)
+```
+- **plinkclient**: sample client application
+```shell
+./plinkclient [frames] [plink server name] [dump file name]
+```
+
+- **plinkstitcher**: sample implementation of stitching filter, which can stitch up to 4 source videos (NV12 or RAW) into one as NV12 output
+
+```
+usage: ./plinkstitcher [options]
+
+  Stitch multiple pictures to one. Maximum # of pictures to be stitched is 4
+  Available options:
+    -i<n>   plink file name of input port #n (default: /tmp/plink.stitch.in<n>). n is 0 based.
+    -o      plink file name of output port (default: /tmp/plink.stitch.out)
+    -l      layout (default: 0)
+                0 - vertical
+                1 - horizontal
+                2 - matrix
+    -f      output color format (default: 3)
+                3 - NV12
+    -w      output video width (default: 800)
+    -h      output video height (default: 1280)
+    -s      output video buffer stride (default: video width)
+    --help  print this message
+```
+
+Please note the sample applications have dependency on **video-memory** module for memory allocating and dma-buf operations. 

BIN
doc/pics/comm.PNG


BIN
doc/pics/packet.PNG


BIN
doc/pics/plink-sw-stack.PNG


BIN
doc/pics/thead-logo-eng-white-wide.png


File diff suppressed because it is too large
+ 23 - 0
doc/plink-api.md


+ 217 - 0
inc/process_linker.h

@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2021-2022 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef _PROCESS_LINKER_H_
+#define _PROCESS_LINKER_H_
+
+#define PLINK_VERSION_MAJOR     0
+#define PLINK_VERSION_MINOR     1
+#define PLINK_VERSION_REVISION  1
+
+/* Maximum data descriptors in one packet */
+#define PLINK_MAX_DATA_DESCS 4
+
+/* Close all the connections from client. */
+/* Can be used as the second parameter of PLINK_close when the instance is created as SERVER */
+#define PLINK_CLOSE_ALL -1
+
+/* invalid file descriptor */
+#define PLINK_INVALID_FD -1
+
+#define DATA_HEADER_SIZE (sizeof(PlinkDescHdr))
+#define DATA_SIZE(type) (sizeof(type) - DATA_HEADER_SIZE)
+
+typedef void *PlinkHandle;
+typedef void PlinkDescriptor;
+typedef int PlinkChannelID;
+
+typedef enum _PlinkStatus
+{
+    PLINK_STATUS_OK = 0,
+    PLINK_STATUS_MORE_DATA = 1,     /* have more data to parse in the receive buffer */
+    PLINK_STATUS_TIMEOUT = 2,       /* wait timeout, which means no data received within the time */
+    PLINK_STATUS_NO_DATA = 3,       /* no data recieved */
+    PLINK_STATUS_ERROR = -1,        /* general error */
+    PLINK_STATUS_WRONG_PARAMS = -2, /* wrong parameters */
+    PLINK_STATUS_NO_MEMORY = -3,    /* not enough memory */
+} PlinkStatus;
+
+/* plink mode */
+typedef enum _PlinkMode
+{
+    PLINK_MODE_SERVER = 0,      /* run plink as server; server should be launched before client */
+    PLINK_MODE_CLIENT,          /* run plink as client which can connect to server */
+    PLINK_MODE_MAX
+} PlinkMode;
+
+typedef union _PlinkVersion
+{
+    struct process_linker
+    {
+        unsigned char major;
+        unsigned char minor;
+        unsigned char revision;
+        unsigned char step;
+    } v;
+    unsigned int version;
+} PlinkVersion;
+
+typedef struct _PlinkDescHdr
+{
+    unsigned int size;      /* data size, excluding this header */
+    int type;               /* type of this data descriptor */
+    int id;                 /* buffer id if it's buffer descriptor. Only values greater than 0 are valid */
+} PlinkDescHdr;
+
+/* data packet can be sent/received in one send/recv call */
+typedef struct _PlinkPacket
+{
+    int fd;                                         /* file descriptor. If PLINK_INVALID_FD, it's invalid */
+    unsigned int timestamp;                         /* timestamp of this packet, the time for rendering */
+    int num;                                        /* number of valid data descriptor entries in list[] */
+    PlinkDescriptor *list[PLINK_MAX_DATA_DESCS];    /* list of pointers which point to data descriptor. */
+} PlinkPacket;
+
+/**
+ * \brief Create a plink instance.
+ *
+ * Create a plink object with the specified name as server or client.
+ * When mode is PLINK_MODE_SERVER, a file of the specified name will be created.
+ *
+ * \param plink Point to the pointer of plink instance.
+ * \param name Socket file name.
+ * \param mode plink mode, server or client.
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_getVersion(PlinkVersion *version);
+
+/**
+ * \brief Create a plink instance.
+ *
+ * Create a plink object with the specified name as server or client.
+ * When mode is PLINK_MODE_SERVER, a file of the specified name will be created.
+ *
+ * \param plink Point to the pointer of plink instance.
+ * \param name Socket file name.
+ * \param mode plink mode, server or client.
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_create(PlinkHandle *plink, const char *name, PlinkMode mode);
+
+/**
+ * \brief Create a connection between server and client
+ *
+ * Server calls this function to wait for connection and accept.
+ * Client calls this function to connect to server.
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel id of the new connection. Valid for server only. Should be 0 for client
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_connect(PlinkHandle plink, PlinkChannelID *channel);
+
+/**
+ * \brief Create a connection between server and client with timeout
+ *
+ * Server calls this function to wait for connection and accept with timeout.
+ * Client calls this function to connect to server with timeout.
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel id of the new connection. Valid for server only. Should be 0 for client
+ * \param timeout_ms timeout in unit of milliseconds.
+ * \return PLINK_STATUS_OK successful, 
+ * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_connect_ex(PlinkHandle plink, PlinkChannelID *channel, int timeout_ms);
+
+/**
+ * \brief Send a packet
+ *
+ * Send a packet through the channel.
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel The channel to send this packet. Valid for server only. Should be 0 for client
+ * \param pkt Point to the packet to be sent.
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_send(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt);
+
+/**
+ * \brief Wait for data from channel
+ *
+ * This function returns once there is data received from the channel.
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel The channel to receive data. Valid for server only. Should be 0 for client
+ * \param timeout_ms timeout in unit of milliseconds.
+ * \return PLINK_STATUS_OK successful, 
+ * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_wait(PlinkHandle plink, PlinkChannelID channel, int timeout_ms);
+
+/**
+ * \brief Receive data
+ *
+ * Receive data from the channel.
+ * Data descriptors of the packet are stored in the internal buffer, 
+ * and may be overwritten in the next PLINK_recv call. 
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel The channel to receive data. Valid for server only. Should be 0 for client
+ * \param pkt Point to the received packet.
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_recv(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt);
+
+/**
+ * \brief Receive data with timeout
+ *
+ * Receive data from the channel with timeout.
+ * Data descriptors of the packet are stored in the internal buffer, 
+ * and may be overwritten in the next PLINK_recv call. 
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel The channel to receive data. Valid for server only. Should be 0 for client
+ * \param pkt Point to the received packet.
+ * \param timeout_ms timeout in unit of milliseconds.
+ * \return PLINK_STATUS_OK successful, 
+ * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_recv_ex(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt, int timeout_ms);
+
+/**
+ * \brief Close connections
+ *
+ * Close connections. Server can set channel to PLINK_CLOSE_ALL to close all connections.
+ *
+ * \param plink Pointer of plink instance.
+ * \param channel The connection to be closed. Valid for server only. Should be 0 for client
+ * \return PLINK_STATUS_OK successful, 
+ * \return other unsuccessful.
+ */
+PlinkStatus PLINK_close(PlinkHandle plink, PlinkChannelID channel);
+
+#endif /* !_PROCESS_LINKER_H_ */

+ 188 - 0
inc/process_linker_types.h

@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2021-2022 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef _PROCESS_LINKER_TYPES_H_
+#define _PROCESS_LINKER_TYPES_H_
+
+#include "process_linker.h"
+
+/* When set PlinkMsg.msg to this exit code, it means to close the connection */
+#define PLINK_EXIT_CODE -1
+
+/* image/video color format */
+typedef enum _PlinkColorFormat
+{
+    PLINK_COLOR_FormatUnused,
+    PLINK_COLOR_FormatMonochrome,
+    PLINK_COLOR_FormatYUV420Planar,
+    PLINK_COLOR_FormatYUV420SemiPlanar,
+    PLINK_COLOR_FormatYUV420SemiPlanarP010,
+    PLINK_COLOR_FormatYUV422Planar,
+    PLINK_COLOR_FormatYUV422SemiPlanar,
+    PLINK_COLOR_Format32bitBGRA8888,
+    PLINK_COLOR_Format32bitARGB8888,
+    PLINK_COLOR_Format24BitRGB888,
+    PLINK_COLOR_Format24BitRGB888Planar,
+    PLINK_COLOR_Format24BitBGR888,
+    PLINK_COLOR_Format24BitBGR888Planar,
+    PLINK_COLOR_FormatRawBayer8bit,
+    PLINK_COLOR_FormatRawBayer10bit,
+    PLINK_COLOR_FormatRawBayer12bit,
+    PLINK_COLOR_FormatMax
+} PlinkColorFormat;
+
+typedef enum _PlinkBayerPattern
+{
+    PLINK_BAYER_PATTERN_RGGB,
+    PLINK_BAYER_PATTERN_BGGR,
+    PLINK_BAYER_PATTERN_GRBG,
+    PLINK_BAYER_PATTERN_GBRG,
+    PLINK_BAYER_PATTERN_MAX
+} PlinkBayerPattern;
+
+/* Data descriptor type */
+typedef enum _PlinkDescType
+{
+    PLINK_TYPE_1D_BUFFER = 0,   /* PlinkBufferInfo */
+    PLINK_TYPE_2D_YUV,          /* PlinkYuvInfo */
+    PLINK_TYPE_2D_RGB,          /* PlinkRGBInfo */
+    PLINK_TYPE_OBJECT,          /* PlinkObjectInfo */
+    PLINK_TYPE_MESSAGE,         /* PlinkMsg */
+    PLINK_TYPE_TIME,            /* PlinkTimeInfo */
+    PLINK_TYPE_2D_RAW,          /* PlinkRawInfo */
+    PLINK_TYPE_MAX
+} PlinkDescType;
+
+/* time type */
+typedef enum _PlinkTimeType
+{
+    PLINK_TIME_START = 0,       /* start time */
+    PLINK_TIME_CALIBRATION,     /* time delta for calibration */
+    PLINK_TIME_MAX
+} PlinkTimeType;
+
+/* 1D buffer */
+typedef struct _PlinkBufferInfo
+{
+    PlinkDescHdr header;
+    unsigned long long bus_address;
+    unsigned int offset;
+    unsigned int size;
+} PlinkBufferInfo;
+
+/* 2D YUV surface */
+typedef struct _PlinkYuvInfo
+{
+    PlinkDescHdr header;
+    PlinkColorFormat format;
+    unsigned long long bus_address_y;
+    unsigned long long bus_address_u;
+    unsigned long long bus_address_v;
+    unsigned int offset_y;
+    unsigned int offset_u;
+    unsigned int offset_v;
+    unsigned int pic_width;
+    unsigned int pic_height;
+    unsigned int stride_y;
+    unsigned int stride_u;
+    unsigned int stride_v;
+} PlinkYuvInfo;
+
+/* 2D RGB surface */
+typedef struct _PlinkRGBInfo
+{
+    PlinkDescHdr header;
+    PlinkColorFormat format;
+    unsigned long long bus_address_r;
+    unsigned long long bus_address_g;
+    unsigned long long bus_address_b;
+    unsigned long long bus_address_a;
+    unsigned int offset_r;
+    unsigned int offset_g;
+    unsigned int offset_b;
+    unsigned int offset_a;
+    unsigned int img_width;
+    unsigned int img_height;
+    unsigned int stride_r;
+    unsigned int stride_g;
+    unsigned int stride_b;
+    unsigned int stride_a;
+} PlinkRGBInfo;
+
+/* 2D Bayer Raw surface */
+typedef struct _PlinkRawInfo
+{
+    PlinkDescHdr header;
+    PlinkColorFormat format;
+    PlinkBayerPattern pattern;
+    unsigned long long bus_address;
+    unsigned int offset;
+    unsigned int img_width;
+    unsigned int img_height;
+    unsigned int stride;
+} PlinkRawInfo;
+
+/* Feature map buffer after NPU inference */
+typedef struct _PlinkBox
+{
+    float x1;
+    float y1;
+    float x2;
+    float y2;
+} PlinkBox;
+
+typedef struct _PlinkLandmark
+{
+    float x[5];
+    float y[5];
+} PlinkLandmark;
+
+typedef struct _PlinkObjectDetect
+{
+    float score;
+    PlinkBox box;
+    PlinkLandmark landmark;
+} PlinkObjectDetect;
+
+typedef struct _PlinkObjectInfo
+{
+    PlinkDescHdr header;
+    unsigned long long bus_address;
+    unsigned int object_cnt;
+} PlinkObjectInfo;
+
+/* Used to send message */
+typedef struct _PlinkMsg
+{
+    PlinkDescHdr header;
+    int msg;                /* When greater than 0, it means the id of buffer which can be released */
+                            /* When set to 0, it means a buffer can be released, but id is unknown */
+                            /* When set to PLINK_EXIT_CODE, it means to close connection */
+                            /* Other values are reserved */
+} PlinkMsg;
+
+/* time information */
+typedef struct _PlinkTimeInfo
+{
+    PlinkDescHdr header;
+    PlinkTimeType type;
+    long long seconds;
+    long long useconds;
+} PlinkTimeInfo;
+
+#endif /* !_PROCESS_LINKER_TYPES_H_ */

+ 549 - 0
src/process_linker.c

@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2021-2022 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "process_linker.h"
+
+#ifndef NULL
+#define NULL    ((void *)0)
+#endif
+
+#define MAX_CONNECTIONS 3
+#define MAX_BUFFER_SIZE (4 * 1024 * 1024)
+
+#define PLINK_PRINT(level, ...) \
+    { \
+        if (log_level >= PLINK_LOG_##level) \
+        { \
+            struct timeval ts; \
+            gettimeofday(&ts, 0); \
+            printf("PLINK[%d][%ld.%06ld] %s: ", pid, ts.tv_sec, ts.tv_usec, #level); \
+            printf(__VA_ARGS__); \
+        } \
+    }
+
+#define PLINK_PRINT_RETURN(retcode, level, ...) \
+    { \
+        PLINK_PRINT(level, __VA_ARGS__) \
+        return retcode; \
+    }
+
+typedef enum _PlinkLogLevel
+{
+    PLINK_LOG_QUIET = 0,
+    PLINK_LOG_ERROR,
+    PLINK_LOG_WARNING,
+    PLINK_LOG_INFO,
+    PLINK_LOG_DEBUG,
+    PLINK_LOG_TRACE,
+    PLINK_LOG_MAX
+} PlinkLogLevel;
+
+typedef struct _PlinkContext
+{
+    PlinkMode mode;
+    struct sockaddr_un addr;
+    struct iovec io[PLINK_MAX_DATA_DESCS];
+    int sockfd;
+    int cfd[MAX_CONNECTIONS];
+    int connect[MAX_CONNECTIONS];
+    int count; // connected client number
+    char *buffer;
+    int offset;
+    int pid;
+} PlinkContext;
+
+int log_level = PLINK_LOG_ERROR;
+int pid = 0;
+
+static PlinkStatus parseData(PlinkContext *ctx, PlinkPacket *pkt, int total);
+static PlinkStatus wait(int sockfd, int timeout_ms);
+static int getLogLevel();
+
+PlinkStatus
+PLINK_getVersion(PlinkVersion *version)
+{
+    if (version != NULL)
+    {
+        version->v.major = PLINK_VERSION_MAJOR;
+        version->v.minor = PLINK_VERSION_MINOR;
+        version->v.revision = PLINK_VERSION_REVISION;
+        version->v.step = 0;
+        return PLINK_STATUS_OK;
+    }
+    else
+        return PLINK_STATUS_ERROR;
+}
+
+PlinkStatus 
+PLINK_create(PlinkHandle *plink, const char *name, PlinkMode mode)
+{
+    PlinkContext *ctx = NULL;
+    int sockfd;
+
+    log_level = getLogLevel();
+    pid = getpid();
+
+    if (plink == NULL || name == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p, name = %s\n", plink, name);
+
+    ctx = (PlinkContext *)malloc(sizeof(*ctx));
+    if (NULL == ctx)
+        PLINK_PRINT_RETURN(PLINK_STATUS_NO_MEMORY, ERROR,
+            "Failed to allocate memory for plink\n");
+    memset(ctx, 0, sizeof(*ctx));
+    *plink = (PlinkHandle)ctx;
+
+    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (-1 == sockfd)
+        PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+            "Failed to create socket as AF_UNIX, SOCK_STREAM\n");
+
+    ctx->sockfd = sockfd;
+    ctx->mode = mode;
+    ctx->addr.sun_family = AF_UNIX;
+    strncpy(ctx->addr.sun_path, name, sizeof(ctx->addr.sun_path) - 1);
+
+    if (mode == PLINK_MODE_SERVER)
+    {
+        if (unlink (name) == -1 && errno != ENOENT)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+                "Failed to unlink %s\n", name);
+
+        if (bind(sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+                "Failed to bind to AF_UNIX address: %s\n", ctx->addr.sun_path);
+
+        if (listen(sockfd, MAX_CONNECTIONS) == -1)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+                "Failed to listen for connection request\n");
+    }
+
+    ctx->buffer = malloc(MAX_BUFFER_SIZE);
+    if (ctx->buffer == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_NO_MEMORY, ERROR,
+            "Failed to allocate memory for internal buffer\n");
+
+    ctx->pid = getpid();
+
+    return PLINK_STATUS_OK;
+}
+
+PlinkStatus 
+PLINK_connect(PlinkHandle plink, PlinkChannelID *channel)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p\n", plink);
+
+    if (ctx->mode == PLINK_MODE_SERVER)
+    {
+        if (channel == NULL)
+            PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+                "Wrong parameters: channel = %p\n", channel);
+
+        if (ctx->count >= MAX_CONNECTIONS)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+               "Too many connections %d while it is limited to %d\n", ctx->count, MAX_CONNECTIONS);
+
+        // find available slot
+        int i;
+        for (i = 0; i < MAX_CONNECTIONS; i++)
+        {
+            if (ctx->connect[i] == 0)
+            {
+                ctx->connect[i] = 1;
+                *channel = i;
+                break;
+            }
+        }
+
+        // wait for connection from client
+        PLINK_PRINT(INFO, "Waiting for connection...\n");
+        int fd = accept(ctx->sockfd, NULL, NULL);
+        if (fd == -1)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+               "Failed to accept connection\n");
+
+        ctx->cfd[i] = fd;
+        ctx->count++;
+        PLINK_PRINT(INFO, "Accepted connection request from client %d (%d/%d): %d\n", 
+                i, ctx->count, MAX_CONNECTIONS, fd);
+    }
+    else
+    {
+        if (connect(ctx->sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+               "Failed to connect to server %s: %s\n", ctx->addr.sun_path, strerror(errno));
+
+        PLINK_PRINT(INFO, "Connected to server: %d\n", ctx->sockfd);
+    }
+
+    return PLINK_STATUS_OK;
+}
+
+PlinkStatus 
+PLINK_send(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL || pkt == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p, pkt = %p\n", plink, pkt);
+
+    if (pkt->num > PLINK_MAX_DATA_DESCS)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Too many data nodes to send: %d\n", pkt->num);
+
+    char buf[CMSG_SPACE(sizeof(int))];
+    memset(buf, 0, sizeof(buf));
+    for (int i = 0; i < pkt->num; i++)
+    {
+        PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]);
+        ctx->io[i].iov_base = pkt->list[i];
+        ctx->io[i].iov_len = hdr->size + DATA_HEADER_SIZE;
+        PLINK_PRINT(INFO, "Sending %ld bytes\n", ctx->io[i].iov_len);
+    }
+
+    struct msghdr msg = {0};
+    msg.msg_iov = ctx->io;
+    msg.msg_iovlen = pkt->num;
+
+    if (pkt->fd > PLINK_INVALID_FD)
+    {
+        msg.msg_control = buf;
+        msg.msg_controllen = sizeof(buf);
+
+        struct cmsghdr *cmsg;
+        cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+        *((int *)CMSG_DATA(cmsg)) = pkt->fd;
+        PLINK_PRINT(INFO, "Sent fd %d\n", pkt->fd);
+    }
+
+    int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd;
+    if (sendmsg(sockfd, &msg, 0) == -1)
+        PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+            "sendmsg() failed: %s\n", strerror(errno));
+    PLINK_PRINT(INFO, "Sent data to %d\n", sockfd);
+
+    return PLINK_STATUS_OK;
+}
+
+PlinkStatus 
+PLINK_recv(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL || pkt == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p, pkt = %p\n", plink, pkt);
+
+    pkt->num = 0;
+
+    char buf[CMSG_SPACE(sizeof(int))];
+    memset(buf, 0, sizeof(buf));
+    ctx->io[0].iov_base = ctx->buffer + ctx->offset;
+    ctx->io[0].iov_len = MAX_BUFFER_SIZE - ctx->offset;
+
+    struct msghdr msg = {0};
+    msg.msg_iov = ctx->io;
+    msg.msg_iovlen = 1;
+    msg.msg_control = buf;
+    msg.msg_controllen = sizeof(buf);
+
+    int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd;
+    PLINK_PRINT(INFO, "Receiving data from %d\n", sockfd);
+    int total = recvmsg (sockfd, &msg, 0);
+    if (total > 0)
+        PLINK_PRINT(INFO, "Received %d bytes\n", total)
+    else if (total == 0)
+        PLINK_PRINT_RETURN(PLINK_STATUS_NO_DATA, WARNING,
+            "recvmsg() returns %d\n", total)
+    else
+        PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+            "Failed to recieve data from %d: %s\n", sockfd, strerror(errno))
+
+    if (msg.msg_controllen >= CMSG_SPACE(sizeof(int)))
+    {
+        struct cmsghdr *cmsg;
+        cmsg = CMSG_FIRSTHDR(&msg);
+        pkt->fd = *((int *)CMSG_DATA(cmsg));
+        PLINK_PRINT(INFO, "Received fd %d\n", pkt->fd);
+    }
+    else
+        pkt->fd = PLINK_INVALID_FD;
+
+    return parseData(ctx, pkt, ctx->offset + total);
+}
+
+PlinkStatus 
+PLINK_wait(PlinkHandle plink, PlinkChannelID channel, int timeout_ms)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p\n", plink);
+
+    int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd;
+    return wait(sockfd, timeout_ms);
+}
+
+PlinkStatus 
+PLINK_close(PlinkHandle plink, PlinkChannelID channel)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p\n", plink);
+
+    if (ctx->mode == PLINK_MODE_SERVER)
+    {
+        if (channel == PLINK_CLOSE_ALL)
+        {
+            // close all connections
+            int i;
+            for (i = 0; i < MAX_CONNECTIONS; i++)
+            {
+                if (ctx->connect[i] != 0)
+                {
+                    ctx->connect[i] = 0;
+                    close(ctx->cfd[i]);
+                    ctx->count--;
+                    PLINK_PRINT(INFO, "Closed channel %d\n", i);
+                }
+            }
+
+            unlink(ctx->addr.sun_path);
+            close(ctx->sockfd);
+            if (ctx->buffer != NULL)
+                free(ctx->buffer);
+
+            free(ctx);
+        }
+        else if (channel < MAX_CONNECTIONS && ctx->connect[channel] != 0)
+        {
+            close(ctx->cfd[channel]);
+            ctx->count--;
+            PLINK_PRINT(INFO, "Closed channel %d\n", channel);
+        }
+        else
+        {
+            PLINK_PRINT(ERROR, "Invalid channel: %d\n", channel);
+        }
+
+        unlink(ctx->addr.sun_path);
+    }
+    else
+    {
+        close(ctx->sockfd);
+        if (ctx->buffer != NULL)
+            free(ctx->buffer);
+
+        free(ctx);
+    }
+
+    return PLINK_STATUS_OK;
+}
+
+PlinkStatus
+PLINK_connect_ex(PlinkHandle plink, PlinkChannelID *channel, int timeout_ms)
+{
+    PlinkContext *ctx = (PlinkContext *)plink;
+
+    if (ctx == NULL)
+        PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+            "Wrong parameters: plink = %p\n", plink);
+
+    int i = 0;
+    if (ctx->mode == PLINK_MODE_SERVER)
+    {
+        if (channel == NULL)
+            PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR,
+                "Wrong parameters: channel = %p\n", channel);
+
+        if (ctx->count >= MAX_CONNECTIONS)
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+               "Too many connections %d while it is limited to %d\n", ctx->count, MAX_CONNECTIONS);
+
+        // find available slot
+        for (i = 0; i < MAX_CONNECTIONS; i++)
+        {
+            if (ctx->connect[i] == 0)
+            {
+                ctx->connect[i] = 1;
+                *channel = i;
+                break;
+            }
+        }
+
+        // wait for connection from client
+        PLINK_PRINT(INFO, "Waiting for connection...\n");
+        if (wait(ctx->sockfd, timeout_ms) == PLINK_STATUS_OK)
+        {
+            int fd = accept(ctx->sockfd, NULL, NULL);
+            if (fd == -1)
+                PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+                    "Failed to accept connection\n");
+
+            ctx->cfd[i] = fd;
+            ctx->count++;
+            PLINK_PRINT(INFO, "Accepted connection request from client %d (%d/%d): %d\n", 
+                    i, ctx->count, MAX_CONNECTIONS, fd);
+        }
+        else
+            PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+                "No connection request within %dms\n", timeout_ms);
+    }
+    else
+    {
+        int max_tries = (timeout_ms + 1000 - 1) / 1000;
+        for (i = 0; i < max_tries; i++)
+        {
+            if (connect(ctx->sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1)
+                sleep(1);
+            else
+                break;
+        }
+
+        if (i >= max_tries)
+            PLINK_PRINT_RETURN(PLINK_STATUS_TIMEOUT, WARNING,
+               "Failed to connect to server %s\n", ctx->addr.sun_path);
+
+        PLINK_PRINT(INFO, "Connected to server: %d\n", ctx->sockfd);
+    }
+
+    return PLINK_STATUS_OK;
+}
+
+PlinkStatus 
+PLINK_recv_ex(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt, int timeout_ms)
+{
+    int ret = PLINK_wait(plink, channel, timeout_ms);
+    if (ret == PLINK_STATUS_OK)
+    {
+        return PLINK_recv(plink, channel, pkt);
+    }
+
+    return ret;
+}
+
+
+static PlinkStatus 
+parseData(PlinkContext *ctx, PlinkPacket *pkt, int remaining)
+{
+    PlinkStatus sts = PLINK_STATUS_OK;
+
+    if (ctx == NULL)
+        return PLINK_STATUS_ERROR;
+
+    int index = 0;
+    void *buffer = ctx->buffer;
+    while (remaining >= DATA_HEADER_SIZE)
+    {
+        if (index >= PLINK_MAX_DATA_DESCS)
+        {
+            // not enough buffer to store received data, need another recv call
+            sts = PLINK_STATUS_MORE_DATA;
+            break;
+        }
+
+        PlinkDescHdr *hdr = (PlinkDescHdr *)buffer;
+        if (remaining < (unsigned int)hdr->size)
+        {
+            PLINK_PRINT(WARNING, "Not enough data received. Expect %d while only %d available\n", hdr->size, remaining);
+            // return to get more data in next recvmsg call
+            break;
+        }
+
+        pkt->list[index] = buffer;
+
+        buffer += DATA_HEADER_SIZE + hdr->size;
+        remaining -= DATA_HEADER_SIZE + hdr->size;
+        index++;
+    }
+
+    // move the remaining data to the beginning of the buffer
+    if (remaining > 0)
+    {
+        memmove(ctx->buffer, buffer, remaining);
+        ctx->offset = remaining;
+    }
+
+    pkt->num = sts == PLINK_STATUS_MORE_DATA ? index-1 : index;
+
+    return sts;
+}
+
+static int getLogLevel()
+{
+    char *env = getenv("PLINK_LOG_LEVEL");
+    if (env == NULL)
+        return PLINK_LOG_ERROR;
+    else
+    {
+        int level = atoi(env);
+        if (level >= PLINK_LOG_MAX || level < PLINK_LOG_QUIET)
+            return PLINK_LOG_ERROR;
+        else
+            return level;
+    }
+}
+
+static PlinkStatus 
+wait(int sockfd, int timeout_ms)
+{
+    fd_set rfds;
+    struct timeval tv;
+    int ret;
+
+    FD_ZERO(&rfds);
+    FD_SET(sockfd, &rfds);
+
+    tv.tv_sec = timeout_ms / 1000;
+    tv.tv_usec = (timeout_ms % 1000) * 1000;
+    PLINK_PRINT(INFO, "Waiting for data from socket %d, timeout %dms\n", sockfd, timeout_ms);
+    ret = select(sockfd+1, &rfds, NULL, NULL, &tv);
+    if (ret == -1)
+        PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR,
+            "Failed to wait for data\n")
+    else if (ret)
+        return PLINK_STATUS_OK;
+    else if (timeout_ms > 0)
+        PLINK_PRINT_RETURN(PLINK_STATUS_TIMEOUT, WARNING,
+            "Wait timeout\n");
+
+    return PLINK_STATUS_TIMEOUT;
+}

+ 203 - 0
test/plink_client.c

@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2021 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <memory.h>
+#include "process_linker_types.h"
+#include "video_mem.h"
+
+#ifndef NULL
+#define NULL    ((void *)0)
+#endif
+
+#define NUM_OF_BUFFERS  5
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+int main(int argc, char **argv) {
+    PlinkStatus sts = PLINK_STATUS_OK;
+    PlinkPacket sendpkt, recvpkt;
+    PlinkMsg msg;
+    PlinkHandle plink = NULL;
+    VmemParams params;
+    void *vmem = NULL;
+    FILE *fp = NULL;
+    int exitcode = 0;
+
+    int frames = argc > 1 ? atoi(argv[1]) : 1000;
+    char *plinkname = argc > 2 ? argv[2] : "/tmp/plink.test";
+    char *dumpname = argc > 3 ? argv[3] : NULL;
+
+    if (dumpname != NULL)
+    {
+        fp = fopen(dumpname, "wb");
+        if (fp == NULL)
+            errExit("fopen");
+    }
+
+    if (VMEM_create(&vmem) != VMEM_STATUS_OK)
+        errExit("Failed to create VMEM.");
+
+    if (PLINK_create(&plink, plinkname, PLINK_MODE_CLIENT) != PLINK_STATUS_OK)
+        errExit("Failed to create PLINK.");
+
+    if (PLINK_connect(plink, NULL) != PLINK_STATUS_OK)
+        errExit("Failed to connect to server.");
+
+    int frmcnt = 0;
+    do {
+        sts = PLINK_recv(plink, 0, &recvpkt);
+        memset(&params, 0, sizeof(params));
+        if (recvpkt.fd != PLINK_INVALID_FD)
+        {
+            params.fd = recvpkt.fd;
+            if (VMEM_import(vmem, &params) != VMEM_STATUS_OK)
+                errExit("Failed to import fd.");
+            if (VMEM_mmap(vmem, &params) != VMEM_STATUS_OK)
+                errExit("Failed to mmap buffer.");
+        }
+
+        for (int i = 0; i < recvpkt.num; i++)
+        {
+            PlinkDescHdr *hdr = (PlinkDescHdr *)(recvpkt.list[i]);
+            if (hdr->type == PLINK_TYPE_2D_YUV)
+            {
+                PlinkYuvInfo *pic = (PlinkYuvInfo *)(recvpkt.list[i]);
+                printf("[CLIENT] Received YUV frame %d 0x%010llx: fd %d, %dx%d, stride = luma %d, chroma %d\n", 
+                        pic->header.id, pic->bus_address_y, recvpkt.fd,
+                        pic->pic_width, pic->pic_height,
+                        pic->stride_y, pic->stride_u);
+
+                // Save YUV data to file
+                if (fp != NULL && params.vir_address != NULL)
+                {
+                    void *buffer = params.vir_address;
+                    for (int i = 0; i < pic->pic_height * 3 / 2; i++)
+                    {
+                        fwrite(buffer, pic->pic_width, 1, fp);
+                        buffer += pic->stride_y;
+                    }
+                }
+
+                // return the buffer to source
+                msg.header.type = PLINK_TYPE_MESSAGE;
+                msg.header.size = DATA_SIZE(PlinkMsg);
+                msg.msg = hdr->id;
+                sendpkt.list[0] = &msg;
+                sendpkt.num = 1;
+                sendpkt.fd = PLINK_INVALID_FD;
+                if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR)
+                    errExit("Failed to send data.");
+            }
+            if (hdr->type == PLINK_TYPE_2D_RGB)
+            {
+                PlinkRGBInfo *pic = (PlinkRGBInfo *)(recvpkt.list[i]);
+                printf("[CLIENT] Received RGB picture %d 0x%010llx: fd %d, %dx%d, stride %d/%d/%d/%d\n", 
+                        pic->header.id, pic->bus_address_r, recvpkt.fd,
+                        pic->img_width, pic->img_height,
+                        pic->stride_r, pic->stride_g, pic->stride_b, pic->stride_a);
+
+                // Save RGB data to file
+                if (fp != NULL && params.vir_address != NULL && pic->format == PLINK_COLOR_Format24BitBGR888Planar)
+                {
+                    void *buffer = params.vir_address;
+                    for (int i = 0; i < pic->img_height * 3; i++)
+                    {
+                        fwrite(buffer, pic->img_width, 1, fp);
+                        buffer += pic->stride_r;
+                    }
+                }
+
+                // return the buffer to source
+                msg.header.type = PLINK_TYPE_MESSAGE;
+                msg.header.size = DATA_SIZE(PlinkMsg);
+                msg.msg = hdr->id;
+                sendpkt.list[0] = &msg;
+                sendpkt.num = 1;
+                sendpkt.fd = PLINK_INVALID_FD;
+                if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR)
+                    errExit("Failed to send data.");
+            }
+            if (hdr->type == PLINK_TYPE_2D_RAW)
+            {
+                PlinkRawInfo *pic = (PlinkRawInfo *)(recvpkt.list[i]);
+                printf("[CLIENT] Received RAW picture %d 0x%010llx: fd %d, %dx%d, stride %d\n", 
+                        pic->header.id, pic->bus_address, recvpkt.fd,
+                        pic->img_width, pic->img_height, pic->stride);
+
+                // Save RAW data to file
+                if (fp != NULL && params.vir_address != NULL)
+                    fwrite(params.vir_address, pic->stride * pic->img_height, 1, fp);
+
+                // return the buffer to source
+                msg.header.type = PLINK_TYPE_MESSAGE;
+                msg.header.size = DATA_SIZE(PlinkMsg);
+                msg.msg = hdr->id;
+                sendpkt.list[0] = &msg;
+                sendpkt.num = 1;
+                sendpkt.fd = PLINK_INVALID_FD;
+                if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR)
+                    errExit("Failed to send data.");
+            }
+            else if (hdr->type == PLINK_TYPE_MESSAGE)
+            {
+                PlinkMsg *msg = (PlinkMsg *)(recvpkt.list[i]);
+                if (msg->msg == PLINK_EXIT_CODE)
+                {
+                    exitcode = 1;
+                    printf("Exit\n");
+                    break;
+                }
+            }
+        }
+
+        if (VMEM_release(vmem, &params) != VMEM_STATUS_OK)
+            errExit("Failed to release buffer.");
+
+        if (recvpkt.fd != PLINK_INVALID_FD)
+            close(recvpkt.fd);
+
+        frmcnt++;
+
+        if (frmcnt >= frames)
+        {
+            msg.header.type = PLINK_TYPE_MESSAGE;
+            msg.header.size = DATA_SIZE(PlinkMsg);
+            msg.msg = PLINK_EXIT_CODE;
+            sendpkt.list[0] = &msg;
+            sendpkt.num = 1;
+            sendpkt.fd = PLINK_INVALID_FD;
+            if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR)
+                errExit("Failed to send data.");
+            break;
+        }
+    } while (exitcode == 0);
+
+cleanup:
+    sleep(1); // Sleep one second to make sure server is ready for exit
+    PLINK_close(plink, 0);
+    VMEM_destroy(vmem);
+    if (fp != NULL)
+        fclose(fp);
+    exit(EXIT_SUCCESS);
+}

+ 432 - 0
test/plink_server.c

@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2021 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <memory.h>
+#include <errno.h>
+#include "process_linker_types.h"
+#include "video_mem.h"
+
+#ifndef NULL
+#define NULL    ((void *)0)
+#endif
+
+#define NUM_OF_BUFFERS  5
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+
+typedef struct _ServerParams
+{
+    char *plinkname;
+    char *inputfile;
+    PlinkColorFormat format;
+    int width;
+    int height;
+    int stride;
+    int frames;
+} ServerParams;
+
+typedef struct _PlinkChannel
+{
+    PlinkChannelID id;
+    PlinkHandle plink;
+    PlinkPacket pkt;
+    int sendid;
+    int backid;
+    int exit;
+    int available_bufs;
+} PlinkChannel;
+
+typedef struct _PictureBuffer
+{
+  unsigned int bus_address;
+  void *virtual_address;
+  unsigned int size;
+  int fd;
+} PictureBuffer;
+
+void printUsage(char *name)
+{
+    printf("usage: %s [options]\n"
+           "\n"
+           "  Available options:\n"
+           "    -l      plink file name (default: /tmp/plink.test)\n"
+           "    -i      input YUV file name (mandatory)\n"
+           "    -f      input color format (default: 3)\n"
+           "                2 - I420\n"
+           "                3 - NV12\n"
+           "                4 - P010\n"
+           "                14 - Bayer Raw 10bit\n"
+           "                15 - Bayer Raw 12bit\n"
+           "    -w      video width (mandatory)\n"
+           "    -h      video height (mandatory)\n"
+           "    -s      video buffer stride in bytes (default: video width)\n"
+           "    -n      number of frames to send (default: 10)\n"
+           "\n", name);
+}
+
+void parseParams(int argc, char **argv, ServerParams *params)
+{
+    int i = 1;
+    memset(params, 0, sizeof(*params));
+    params->plinkname = "/tmp/plink.test";
+    params->format = PLINK_COLOR_FormatYUV420SemiPlanar;
+    params->frames = 10;
+    while (i < argc)
+    {
+        if (argv[i][0] != '-' || strlen(argv[i]) < 2)
+        {
+            i++;
+            continue;
+        }
+
+        if (argv[i][1] == 'l')
+        {
+            if (++i < argc)
+            {
+                params->plinkname = argv[i++];
+            }
+        }
+        else if (argv[i][1] == 'i')
+        {
+            if (++i < argc)
+            {
+                params->inputfile = argv[i++];
+            }
+        }
+        else if (argv[i][1] == 'f')
+        {
+            if (++i < argc)
+            {
+                params->format = atoi(argv[i++]);
+            }
+        }
+        else if (argv[i][1] == 'w')
+        {
+            if (++i < argc)
+            {
+                params->width = atoi(argv[i++]);
+            }
+        }
+        else if (argv[i][1] == 'h')
+        {
+            if (++i < argc)
+            {
+                params->height = atoi(argv[i++]);
+            }
+        }
+        else if (argv[i][1] == 's')
+        {
+            if (++i < argc)
+            {
+                params->stride = atoi(argv[i++]);
+            }
+        }
+        else if (argv[i][1] == 'n')
+        {
+            if (++i < argc)
+            {
+                params->frames = atoi(argv[i++]);
+            }
+        }
+    }
+
+    if ((params->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 ||
+            params->format == PLINK_COLOR_FormatRawBayer10bit ||
+            params->format == PLINK_COLOR_FormatRawBayer12bit) &&
+        params->stride < params->width*2)
+    {
+        params->stride = params->width*2;
+    }
+    else if (params->stride < params->width)
+    {
+        params->stride = params->width;
+    }
+}
+
+int checkParams(ServerParams *params)
+{
+    if (params->plinkname == NULL ||
+        params->inputfile == NULL ||
+        params->format == PLINK_COLOR_FormatUnused ||
+        params->width == 0 ||
+        params->height == 0 ||
+        params->stride == 0)
+        return -1;
+    return 0;
+}
+
+int getBufferSize(ServerParams *params)
+{
+    int size = 0;
+    switch (params->format)
+    {
+        case PLINK_COLOR_FormatYUV420Planar:
+        case PLINK_COLOR_FormatYUV420SemiPlanar:
+            size = params->stride * params->height * 3 / 2;
+            break;
+        case PLINK_COLOR_FormatYUV420SemiPlanarP010:
+            size = params->stride * params->height * 3;
+            break;
+        case PLINK_COLOR_FormatRawBayer10bit:
+        case PLINK_COLOR_FormatRawBayer12bit:
+            size = params->stride * params->height;
+            break;
+        default:
+            size = 0;
+    }
+    return size;
+}
+
+void AllocateBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], unsigned int size, void *vmem)
+{
+    unsigned int buffer_size = (size + 0xFFF) & ~0xFFF;
+    VmemParams params;
+    params.size = buffer_size;
+    params.flags = VMEM_FLAG_CONTIGUOUS | VMEM_FLAG_4GB_ADDR;
+    for (int i = 0; i < NUM_OF_BUFFERS; i++)
+    {
+        params.fd = 0;
+        VMEM_allocate(vmem, &params);
+        VMEM_mmap(vmem, &params);
+        VMEM_export(vmem, &params);
+        printf("[SERVER] mmap %p from %x with size %d, dma-buf fd %d\n", 
+                params.vir_address, params.phy_address, params.size, params.fd);
+        picbuffers[i].virtual_address = params.vir_address;
+        picbuffers[i].bus_address = params.phy_address;
+        picbuffers[i].size = buffer_size;
+        picbuffers[i].fd = params.fd;
+    }
+}
+
+void FreeBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], void *vmem)
+{
+    VmemParams params;
+    memset(&params, 0, sizeof(params));
+    for (int i = 0; i < NUM_OF_BUFFERS; i++)
+    {
+        close(picbuffers[i].fd);
+        params.size = picbuffers[i].size;
+        params.vir_address = picbuffers[i].virtual_address;
+        params.phy_address = picbuffers[i].bus_address;
+        VMEM_free(vmem, &params);
+    }
+}
+
+void ProcessOneFrame(void *virtual_address, FILE *fp, int size)
+{
+    if(virtual_address != MAP_FAILED && fp != NULL)
+    {
+        if (fread(virtual_address, size, 1, fp) < 1)
+            fprintf(stderr, "ERROR: Failed to read %d bytes from file (eof %d): %s\n",
+                size, feof(fp), strerror(errno));
+    }
+}
+
+void constructYuvInfo(PlinkYuvInfo *info, ServerParams *params, unsigned int bus_address, int id)
+{
+    int size_y = params->width * params->stride;
+    int size_uv = size_y / 2;
+    int size_yuv = size_y + size_uv;
+    
+    info->header.type = PLINK_TYPE_2D_YUV;
+    info->header.size = DATA_SIZE(*info);
+    info->header.id = id + 1;
+
+    info->format = params->format;
+    info->bus_address_y = bus_address;
+    info->bus_address_u = info->bus_address_y + size_y;
+    info->bus_address_v = info->bus_address_u + (size_uv / 2);
+    info->pic_width = params->width;
+    info->pic_height = params->height;
+    info->stride_y = params->stride;
+    info->stride_u = 
+        params->format == PLINK_COLOR_FormatYUV420Planar ? 
+        params->stride/2 : params->stride;
+    info->stride_v = info->stride_u;
+}
+
+void constructRawInfo(PlinkRawInfo *info, ServerParams *params, unsigned int bus_address, int id)
+{
+    int size_y = params->width * params->stride;
+    int size_uv = size_y / 2;
+    int size_yuv = size_y + size_uv;
+    
+    info->header.type = PLINK_TYPE_2D_RAW;
+    info->header.size = DATA_SIZE(*info);
+    info->header.id = id + 1;
+
+    info->format = params->format;
+    info->bus_address = bus_address;
+    info->img_width = params->width;
+    info->img_height = params->height;
+    info->stride = params->stride;
+}
+
+int getBufferCount(PlinkPacket *pkt)
+{
+    int ret = 0;
+    for (int i = 0; i < pkt->num; i++)
+    {
+        PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]);
+        if (hdr->type == PLINK_TYPE_MESSAGE)
+        {
+            int *data = (int *)(pkt->list[i] + DATA_HEADER_SIZE);
+            if (*data == PLINK_EXIT_CODE)
+            {
+                ret |= 0x80000000; // set bit 31 to 1 to indicate 'exit'
+            }
+            else if (*data >= 0)
+                ret++;
+        }
+    }
+
+    return ret;
+}
+
+void retreiveSentBuffers(PlinkHandle plink, PlinkChannel *channel)
+{
+    PlinkStatus sts = PLINK_STATUS_OK;
+    while (channel->available_bufs < NUM_OF_BUFFERS && sts == PLINK_STATUS_OK)
+    {
+        do
+        {
+            sts = PLINK_recv(plink, channel->id, &channel->pkt);
+            int count = getBufferCount(&channel->pkt);
+            if (count > 0)
+            {
+                channel->available_bufs += count;
+            }
+        } while (sts == PLINK_STATUS_MORE_DATA);
+    }
+}
+
+
+int main(int argc, char **argv) {
+    PlinkStatus sts = PLINK_STATUS_OK;
+    ServerParams params;
+    PlinkChannel channel[2];
+    PlinkHandle plink = NULL;
+    PlinkYuvInfo pic = {0};
+    PlinkRawInfo img = {0};
+    PlinkMsg msg;
+
+    parseParams(argc, argv, &params);
+    if (checkParams(&params) != 0)
+    {
+        printUsage(argv[0]);
+        return 0;
+    }
+
+    FILE *fp = fopen(params.inputfile, "rb");
+    if (fp == NULL)
+        errExit("fopen");
+
+    void *vmem = NULL;
+    if (VMEM_create(&vmem) != VMEM_STATUS_OK)
+        errExit("Failed to create VMEM.");
+
+    int width = params.width;
+    int height = params.height;
+    int stride = params.stride;
+    int frames = params.frames;
+    int size = getBufferSize(&params);
+    if (size == 0)
+        errExit("Wrong format or wrong resolution.");
+    PictureBuffer picbuffers[NUM_OF_BUFFERS];
+    AllocateBuffers(picbuffers, size, vmem);
+
+    sts = PLINK_create(&plink, params.plinkname, PLINK_MODE_SERVER);
+
+    memset(&channel[0], 0, sizeof(channel[0]));
+    channel[0].available_bufs = NUM_OF_BUFFERS;
+    sts = PLINK_connect(plink, &channel[0].id);
+
+    int frmcnt = 0;
+    do {
+        int sendid = channel[0].sendid;
+        ProcessOneFrame(picbuffers[sendid].virtual_address, fp, size);
+        if (params.format == PLINK_COLOR_FormatRawBayer8bit ||
+            params.format == PLINK_COLOR_FormatRawBayer10bit ||
+            params.format == PLINK_COLOR_FormatRawBayer12bit)
+        {
+            constructRawInfo(&img, &params, picbuffers[sendid].bus_address, sendid);
+            printf("[SERVER] Processed frame %d 0x%010llx: %dx%d, stride %d\n", 
+                    sendid, img.bus_address, img.img_width, img.img_height, img.stride);
+            channel[0].pkt.list[0] = &img;
+        }
+        else // YUV
+        {
+            constructYuvInfo(&pic, &params, picbuffers[sendid].bus_address, sendid);
+            printf("[SERVER] Processed frame %d 0x%010llx: %dx%d, stride = luma %d, chroma %d\n", 
+                    sendid, pic.bus_address_y, 
+                    pic.pic_width, pic.pic_height,
+                    pic.stride_y, pic.stride_u);
+            channel[0].pkt.list[0] = &pic;
+        }
+
+        channel[0].pkt.num = 1;
+        channel[0].pkt.fd = picbuffers[sendid].fd;
+        sts = PLINK_send(plink, channel[0].id, &channel[0].pkt);
+        channel[0].sendid = (channel[0].sendid + 1) % NUM_OF_BUFFERS;
+        channel[0].available_bufs -= 1;
+
+        int timeout = 0;
+        if (channel[0].available_bufs == 0)
+            timeout = 60000; // wait up to 60 seconds if buffers are used up
+
+        if (PLINK_wait(plink, channel[0].id, timeout) == PLINK_STATUS_OK)
+        {
+            do
+            {
+                sts = PLINK_recv(plink, channel[0].id, &channel[0].pkt);
+                int count = getBufferCount(&channel[0].pkt);
+                if (count < 0)
+                    channel[0].exit = 1;
+                channel[0].available_bufs += count;
+            } while (sts == PLINK_STATUS_MORE_DATA);
+        }
+
+        frmcnt++;
+    } while (channel[0].exit == 0 && frmcnt < frames);
+
+cleanup:
+    msg.header.type = PLINK_TYPE_MESSAGE;
+    msg.header.size = DATA_SIZE(PlinkMsg);
+    msg.msg = PLINK_EXIT_CODE;
+    channel[0].pkt.list[0] = &msg;
+    channel[0].pkt.num = 1;
+    channel[0].pkt.fd = PLINK_INVALID_FD;
+    sts = PLINK_send(plink, channel[0].id, &channel[0].pkt);
+    retreiveSentBuffers(plink, &channel[0]);
+    PLINK_recv_ex(plink, channel[0].id, &channel[0].pkt, 1000);
+    //sleep(1); // Sleep one second to make sure client is ready for exit
+    if (vmem)
+        FreeBuffers(picbuffers, vmem);
+    PLINK_close(plink, PLINK_CLOSE_ALL);
+    VMEM_destroy(vmem);
+    if (fp != NULL)
+        fclose(fp);
+    exit(EXIT_SUCCESS);
+}
+

+ 766 - 0
test/plink_stitcher.c

@@ -0,0 +1,766 @@
+/*
+ * Copyright (c) 2022 Alibaba Group. All rights reserved.
+ * License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <memory.h>
+#include "process_linker_types.h"
+#include "video_mem.h"
+
+#ifndef NULL
+#define NULL    ((void *)0)
+#endif
+
+#define MAX_NUM_OF_INPUTS   4
+#define NUM_OF_BUFFERS      5
+#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
+                        } while (0)
+#define STITCHER_MIN(a, b) ((a) < (b) ? (a) : (b))
+
+
+typedef enum _StitchLayout
+{
+    STITCH_LAYOUT_Vertical = 0,
+    STITCH_LAYOUT_Horizontal,
+    STITCH_LAYOUT_Matrix,
+    STITCH_LAYOUT_Max
+} StitchLayout;
+
+typedef struct _StitherRegion
+{
+    int width;
+    int height;
+    int offset_uv;
+    int offset_y;
+} StitcherRegion;
+
+typedef struct _StitcherParams
+{
+    char *in_name[MAX_NUM_OF_INPUTS];
+    char *out_name;
+    StitchLayout layout;
+    PlinkColorFormat format;
+    int width;
+    int height;
+    int stride;
+} StitcherParams;
+
+typedef struct _StitcherPort
+{
+    char *name;
+    int index;
+    PlinkChannelID id;
+    PlinkHandle plink;
+    int sendid;
+    int backid;
+    int *exit;
+    int available_bufs;
+    void *vmem;
+    pthread_mutex_t pic_mutex;
+    pthread_mutex_t *count_mutex;
+    sem_t *sem_ready;
+    sem_t *sem_done;
+    StitchLayout layout;
+    PlinkColorFormat format;
+    void *buffer;
+    int width;
+    int height;
+    int stride;
+    int offset;
+    int offset_uv;
+    int *in_count;
+} StitcherPort;
+
+typedef struct _StitcherContext
+{
+    StitcherPort in[MAX_NUM_OF_INPUTS];
+    StitcherPort out;
+    pthread_mutex_t count_mutex;
+    sem_t sem_ready;
+    sem_t sem_done;
+    int in_count;
+    int exitcode;
+} StitcherContext;
+
+typedef struct _PictureBuffer
+{
+  unsigned int bus_address;
+  void *virtual_address;
+  unsigned int size;
+  int fd;
+} PictureBuffer;
+
+static void printUsage(char *name)
+{
+    printf("usage: %s [options]\n"
+           "\n"
+           "  Stitch multiple pictures to one. Maximum # of pictures to be stitched is %d\n"
+           "  Available options:\n"
+           "    -i<n>   plink file name of input port #n (default: /tmp/plink.stitch.in<n>). n is 0 based.\n"
+           "    -o      plink file name of output port (default: /tmp/plink.stitch.out)\n"
+           "    -l      layout (default: 0)\n"
+           "                0 - vertical\n"
+           "                1 - horizontal\n"
+           "                2 - matrix\n"
+           "    -f      output color format (default: 3)\n"
+           "                3 - NV12\n"
+           "    -w      output video width (default: 800)\n"
+           "    -h      output video height (default: 1280)\n"
+           "    -s      output video buffer stride (default: video width)\n"
+           "    --help  print this message\n"
+           "\n", name, MAX_NUM_OF_INPUTS);
+}
+
+static void parseParams(int argc, char **argv, StitcherParams *params)
+{
+    int i = 1;
+    memset(params, 0, sizeof(*params));
+    params->in_name[0] = "/tmp/plink.stitch.in0";
+    params->in_name[1] = "/tmp/plink.stitch.in1";
+    params->in_name[2] = "/tmp/plink.stitch.in2";
+    params->in_name[3] = "/tmp/plink.stitch.in3";
+    params->out_name = "/tmp/plink.stitch.out";
+    params->layout = STITCH_LAYOUT_Vertical;
+    params->format = PLINK_COLOR_FormatYUV420SemiPlanar;
+    params->width = 800;
+    params->height = 1280;
+    while (i < argc)
+    {
+        if (argv[i][0] != '-' || strlen(argv[i]) < 2)
+        {
+            i++;
+            continue;
+        }
+        
+        if (argv[i][1] == 'i')
+        {
+            if (++i < argc)
+            {
+                if (strlen(argv[i-1]) > 2) // input name
+                {
+                    int id = atoi(argv[i-1]+2);
+                    if (id < MAX_NUM_OF_INPUTS)
+                        params->in_name[id] = argv[i++];
+                }
+                else
+                    params->in_name[0] = argv[i++];
+            }
+        }
+        else if (argv[i][1] == 'o')
+        {
+            if (++i < argc)
+                params->out_name = argv[i++];
+        }
+        else if (argv[i][1] == 'l')
+        {
+            if (++i < argc)
+                params->layout = atoi(argv[i++]);
+        }
+        else if (argv[i][1] == 'f')
+        {
+            if (++i < argc)
+                params->format = atoi(argv[i++]);
+        }
+        else if (argv[i][1] == 'w')
+        {
+            if (++i < argc)
+                params->width = atoi(argv[i++]);
+        }
+        else if (argv[i][1] == 'h')
+        {
+            if (++i < argc)
+                params->height = atoi(argv[i++]);
+        }
+        else if (argv[i][1] == 's')
+        {
+            if (++i < argc)
+                params->stride = atoi(argv[i++]);
+        }
+        else if (strcmp(argv[i], "--help") == 0)
+        {
+            params->layout = STITCH_LAYOUT_Max;
+            i++;
+        }
+    }
+
+    if (params->stride == 0)
+        params->stride = params->width;
+
+    printf("[STITCHER] Input 0 Name         : %s\n", params->in_name[0]);
+    printf("[STITCHER] Input 1 Name         : %s\n", params->in_name[1]);
+    printf("[STITCHER] Input 2 Name         : %s\n", params->in_name[2]);
+    printf("[STITCHER] Input 3 Name         : %s\n", params->in_name[3]);
+    printf("[STITCHER] Output Name          : %s\n", params->out_name);
+    printf("[STITCHER] Output Layout        : %d\n", params->layout);
+    printf("[STITCHER] Output Format        : %d\n", params->format);
+    printf("[STITCHER] Output Resolution    : %dx%d\n", params->width, params->height);
+    printf("[STITCHER] Output Stride        : %d\n", params->stride);
+}
+
+static int checkParams(StitcherParams *params)
+{
+    if (params->format != PLINK_COLOR_FormatYUV420SemiPlanar ||
+        params->layout >= STITCH_LAYOUT_Max)
+        return -1;
+    return 0;
+}
+
+static int getBufferSize(StitcherParams *params)
+{
+    int size = 0;
+    switch (params->format)
+    {
+        case PLINK_COLOR_FormatYUV420Planar:
+        case PLINK_COLOR_FormatYUV420SemiPlanar:
+            size = params->stride * params->height * 3 / 2;
+            break;
+        default:
+            size = 0;
+    }
+    return size;
+}
+
+static void AllocateBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], unsigned int size, void *vmem)
+{
+    unsigned int buffer_size = (size + 0xFFF) & ~0xFFF;
+    VmemParams params;
+    params.size = buffer_size;
+    params.flags = VMEM_FLAG_CONTIGUOUS | VMEM_FLAG_4GB_ADDR;
+    for (int i = 0; i < NUM_OF_BUFFERS; i++)
+    {
+        params.fd = 0;
+        VMEM_allocate(vmem, &params);
+        VMEM_mmap(vmem, &params);
+        VMEM_export(vmem, &params);
+        printf("[STITCHER] mmap %p from %x with size %d, dma-buf fd %d\n", 
+                params.vir_address, params.phy_address, params.size, params.fd);
+        picbuffers[i].virtual_address = params.vir_address;
+        picbuffers[i].bus_address = params.phy_address;
+        picbuffers[i].size = buffer_size;
+        picbuffers[i].fd = params.fd;
+    }
+}
+
+static void FreeBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], void *vmem)
+{
+    VmemParams params;
+    memset(&params, 0, sizeof(params));
+    for (int i = 0; i < NUM_OF_BUFFERS; i++)
+    {
+        close(picbuffers[i].fd);
+        params.size = picbuffers[i].size;
+        params.vir_address = picbuffers[i].virtual_address;
+        params.phy_address = picbuffers[i].bus_address;
+        VMEM_free(vmem, &params);
+    }
+}
+
+static void getRegion(StitcherPort *out, int index, int in_count, StitcherRegion *region)
+{
+    region->offset_uv = 0; //out->stride * out->height;
+    if (out->layout == STITCH_LAYOUT_Horizontal)
+    {
+        int width = (out->width / in_count + 1) & ~1;
+        region->offset_y = width * index;
+        region->offset_uv += width * index;
+        region->width = width;
+        region->height = out->height;
+    }
+    else if (out->layout == STITCH_LAYOUT_Matrix && in_count >= 3)
+    {
+        int y = index >> 1;
+        int x = index & 1;
+        int width = (out->width / 2 + 1) & ~1;
+        int height = (out->height / 2 + 1) & ~1;
+        region->offset_y = out->stride * (height * y);
+        region->offset_y += width * x;
+        region->offset_uv += out->stride * (height * y / 2);
+        region->offset_uv += width * x;
+        region->width = width;
+        region->height = height;
+    }
+    else // (out->layout == STITCH_LAYOUT_Vertical)
+    {
+        int height = (out->height / in_count + 1) & ~1;
+        region->offset_y = out->stride * (height * index);
+        region->offset_uv += out->stride * (height * index / 2);
+        region->width = out->width;
+        region->height = height;
+    }
+}
+
+static int stitchOneFrame(StitcherContext *ctx)
+{
+    StitcherPort *in = NULL;
+    StitcherPort *out = &ctx->out;
+    int in_count = 0;
+    do
+    {
+        pthread_mutex_lock(out->count_mutex);
+        in_count = ctx->in_count;
+        pthread_mutex_unlock(out->count_mutex);
+        if (in_count > 0)
+            break;
+        else
+            sleep(1);
+    } while (1);
+
+    for (int i = 0; i < in_count; i++)
+    {
+        in = &ctx->in[i];
+        if (in->index == 0)
+        {
+            sem_wait(&ctx->sem_ready); // wait for picture from input port0.
+            if (ctx->exitcode == 1)
+                return 1;
+        }
+        StitcherRegion region;
+        getRegion(out, in->index, in_count, &region);
+        pthread_mutex_lock(&in->pic_mutex);
+        int width = STITCHER_MIN(region.width, in->width);
+        int height = STITCHER_MIN(region.height, in->height);
+        void *dst = out->buffer + out->offset + region.offset_y;
+        void *src = in->buffer + in->offset;
+        //fprintf(stderr, "%d: %p, %d, %d, %p, %d\n", in->index, out->buffer, out->offset, region.offset_y, in->buffer, in->offset);
+        //fprintf(stderr, "%d: %dx%d, %d, %d\n", in->index, width, height, in->stride, out->stride);
+        if (in->available_bufs > 0)
+        {
+            if (in->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 ||
+                in->format == PLINK_COLOR_FormatRawBayer10bit ||
+                in->format == PLINK_COLOR_FormatRawBayer12bit)
+            {
+                int shift = in->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 ? 2 : 8;
+                unsigned int temp[4];
+                for (int h = 0; h < height; h++)
+                {
+                    unsigned int *dst32 = (unsigned int *)dst;
+                    unsigned short *src16 = (unsigned short *)src;
+                    for (int w = 0; w < width; w+=4)
+                    {
+                        temp[0] = ((unsigned int)(src16[w+0] >> shift) & 0xFF);
+                        temp[1] = ((unsigned int)(src16[w+1] >> shift) & 0xFF) << 8;
+                        temp[2] = ((unsigned int)(src16[w+2] >> shift) & 0xFF) << 16;
+                        temp[3] = ((unsigned int)(src16[w+3] >> shift) & 0xFF) << 24;
+                        dst32[w>>2] = temp[0] | temp[1] | temp[2] | temp[3];
+                    }
+                    dst += out->stride;
+                    src += in->stride;
+                }
+            }
+            else
+            {
+                for (int h = 0; h < height; h++)
+                {
+                    memcpy(dst, src, width);
+                    dst += out->stride;
+                    src += in->stride;
+                }
+            }
+        }
+        else
+        {
+            for (int h = 0; h < height; h++)
+            {
+                memset(dst, 0x00, width);
+                dst += out->stride;
+            }
+        }
+        dst = out->buffer + out->offset_uv + region.offset_uv;
+        src = in->buffer + in->offset_uv;
+        //fprintf(stderr, "%d: %p, %d, %d, %p, %d\n", in->index, out->buffer, out->offset_uv, region.offset_uv, in->buffer, in->offset_uv);
+        if (in->format == PLINK_COLOR_FormatYUV420SemiPlanar &&
+            in->available_bufs > 0)
+        {
+            for (int h = 0; h < height/2; h++)
+            {
+                memcpy(dst, src, width);
+                dst += out->stride;
+                src += in->stride;
+            }
+        }
+        else // treat all the other formats as monochrome
+        {
+            for (int h = 0; h < height/2; h++)
+            {
+                memset(dst, 0x80, width);
+                dst += out->stride;
+            }
+        }
+        pthread_mutex_unlock(&in->pic_mutex);
+        if (in->index == 0)
+            sem_post(&ctx->sem_done); // Resume input port0.
+        // !Assume output thread is fast enough, so input port0 won't be blocked for long time.
+    }
+
+    return 0;
+}
+
+static void constructYuvInfo(PlinkYuvInfo *info, StitcherParams *params, unsigned int bus_address, int id)
+{
+    int size_y = params->width * params->stride;
+    int size_uv = size_y / 2;
+    int size_yuv = size_y + size_uv;
+    
+    info->header.type = PLINK_TYPE_2D_YUV;
+    info->header.size = DATA_SIZE(*info);
+    info->header.id = id + 1;
+
+    info->format = params->format;
+    info->bus_address_y = bus_address;
+    info->bus_address_u = info->bus_address_y + size_y;
+    info->bus_address_v = info->bus_address_u + (size_uv / 2);
+    info->pic_width = params->width;
+    info->pic_height = params->height;
+    info->stride_y = params->stride;
+    info->stride_u = 
+        params->format == PLINK_COLOR_FormatYUV420Planar ? 
+        params->stride/2 : params->stride;
+    info->stride_v = info->stride_u;
+}
+
+int getBufferCount(PlinkPacket *pkt)
+{
+    int ret = 0;
+    for (int i = 0; i < pkt->num; i++)
+    {
+        PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]);
+        if (hdr->type == PLINK_TYPE_MESSAGE)
+        {
+            int *data = (int *)(pkt->list[i] + DATA_HEADER_SIZE);
+            if (*data == PLINK_EXIT_CODE)
+            {
+                ret |= 0x80000000; // set bit 31 to 1 to indicate 'exit'
+            }
+            else if (*data >= 0)
+                ret++;
+        }
+    }
+
+    return ret;
+}
+
+static void retreiveSentBuffers(PlinkHandle plink, StitcherPort *port)
+{
+    PlinkStatus sts = PLINK_STATUS_OK;
+    PlinkPacket pkt = {0};
+    while (port->available_bufs < NUM_OF_BUFFERS && sts == PLINK_STATUS_OK)
+    {
+        do
+        {
+            sts = PLINK_recv(plink, port->id, &pkt);
+            int count = getBufferCount(&pkt);
+            if (count > 0)
+            {
+                port->available_bufs += count;
+            }
+        } while (sts == PLINK_STATUS_MORE_DATA);
+    }
+}
+
+static void *input_thread(void *args)
+{
+    StitcherPort *port = (StitcherPort *)args;
+    PlinkHandle plink = NULL;
+    PlinkStatus sts = PLINK_STATUS_OK;
+    void *vmem = port->vmem;
+
+    pthread_mutex_init(&port->pic_mutex, NULL);
+
+    if (PLINK_create(&plink, port->name, PLINK_MODE_CLIENT) != PLINK_STATUS_OK)
+        return NULL;
+
+    do
+    {
+        sts = PLINK_connect_ex(plink, NULL, 1000);
+    } while (sts == PLINK_STATUS_TIMEOUT && *port->exit == 0);
+    if (sts != PLINK_STATUS_OK)
+    {
+        PLINK_close(plink, 0);
+        return NULL;
+    }
+
+    pthread_mutex_lock(port->count_mutex);
+    port->index = *(port->in_count);
+    *(port->in_count) = *(port->in_count) + 1;
+    pthread_mutex_unlock(port->count_mutex);
+
+    PlinkPacket sendpkt = {0};
+    PlinkPacket recvpkt = {0};
+    PlinkMsg msg = {0};
+    VmemParams params;
+    int exitcode = 0;
+    do {
+        sts = PLINK_recv(plink, 0, &recvpkt);
+        if (sts == PLINK_STATUS_ERROR)
+            break;
+
+        for (int i = 0; i < recvpkt.num; i++)
+        {
+            PlinkDescHdr *hdr = (PlinkDescHdr *)(recvpkt.list[i]);
+            if (hdr->type == PLINK_TYPE_2D_YUV || 
+                hdr->type == PLINK_TYPE_2D_RAW)
+            {
+                // return previous buffer to source
+                if (sendpkt.num > 0)
+                {
+                    if (port->index == 0)
+                        sem_wait(port->sem_done);
+                    pthread_mutex_lock(&port->pic_mutex);
+                    if (VMEM_release(vmem, &params) != VMEM_STATUS_OK)
+                        fprintf(stderr, "[STITCHER] ERROR: Failed to release buffer.\n");
+                    sts = PLINK_send(plink, 0, &sendpkt);
+                    if (sts == PLINK_STATUS_ERROR)
+                        break;
+                    if (sendpkt.fd != PLINK_INVALID_FD)
+                        close(sendpkt.fd);
+                    port->available_bufs--;
+                    sendpkt.num = 0;
+                }
+                else
+                    pthread_mutex_lock(&port->pic_mutex);
+
+                memset(&params, 0, sizeof(params));
+                if (recvpkt.fd != PLINK_INVALID_FD)
+                {
+                    params.fd = recvpkt.fd;
+                    if (VMEM_import(vmem, &params) != VMEM_STATUS_OK)
+                        break;
+                    if (VMEM_mmap(vmem, &params) != VMEM_STATUS_OK)
+                        break;
+                }
+
+                if (port->index == 0)
+                    sem_post(port->sem_ready); // signal output thread that one picture is ready
+            }
+
+            if (hdr->type == PLINK_TYPE_2D_YUV)
+            {
+                PlinkYuvInfo *pic = (PlinkYuvInfo *)(recvpkt.list[i]);
+                printf("[STITCHER] Input%d: Received YUV frame %d 0x%010llx from %s: fd %d, %dx%d, stride = luma %d, chroma %d\n", 
+                        port->index, pic->header.id, pic->bus_address_y, port->name, recvpkt.fd,
+                        pic->pic_width, pic->pic_height,
+                        pic->stride_y, pic->stride_u);
+
+                port->buffer = params.vir_address;
+                port->format = pic->format;
+                port->width = pic->pic_width;
+                port->height = pic->pic_height;
+                port->stride = pic->stride_y;
+                port->offset = pic->offset_y;
+                port->offset_uv = pic->offset_u > 0 ? pic->offset_u : (pic->offset_y + pic->pic_height*pic->stride_y);
+            }
+            else if (hdr->type == PLINK_TYPE_2D_RAW)
+            {
+                PlinkRawInfo *img = (PlinkRawInfo *)(recvpkt.list[i]);
+                printf("[STITCHER] Input%d: Received RAW frame %d 0x%010llx from %s: fd %d, %dx%d, stride %d\n", 
+                        port->index, img->header.id, img->bus_address, port->name, recvpkt.fd,
+                        img->img_width, img->img_height, img->stride);
+
+                port->buffer = params.vir_address;
+                port->format = img->format;
+                port->width = img->img_width;
+                port->height = img->img_height;
+                port->stride = img->stride;
+                port->offset = img->offset;
+            }
+            else if (hdr->type == PLINK_TYPE_MESSAGE)
+            {
+                PlinkMsg *msg = (PlinkMsg *)(recvpkt.list[i]);
+                if (msg->msg == PLINK_EXIT_CODE)
+                {
+                    exitcode = 1;
+                    printf("[STITCHER] Input %d: Exit\n", port->index);
+                    break;
+                }
+            }
+
+            if (hdr->type == PLINK_TYPE_2D_YUV ||
+                hdr->type == PLINK_TYPE_2D_RAW)
+            {
+                port->available_bufs++;
+                pthread_mutex_unlock(&port->pic_mutex);
+
+                msg.header.type = PLINK_TYPE_MESSAGE;
+                msg.header.size = DATA_SIZE(PlinkMsg);
+                msg.msg = hdr->id;
+                sendpkt.list[0] = &msg;
+                sendpkt.num = 1;
+                sendpkt.fd = recvpkt.fd;
+            }
+        }
+    } while (exitcode == 0 && *port->exit == 0 && sts != PLINK_STATUS_ERROR);
+
+    if (sendpkt.num > 0)
+    {
+        pthread_mutex_lock(&port->pic_mutex);
+        if (VMEM_release(vmem, &params) != VMEM_STATUS_OK)
+            fprintf(stderr, "[STITCHER] ERROR: Failed to release buffer.\n");
+        sts = PLINK_send(plink, 0, &sendpkt);
+        port->available_bufs--;
+        pthread_mutex_unlock(&port->pic_mutex);
+    }
+
+    if (port->index == 0)
+    {
+        *port->exit = 1;
+        sem_post(port->sem_ready);
+    }
+    msg.header.type = PLINK_TYPE_MESSAGE;
+    msg.header.size = DATA_SIZE(PlinkMsg);
+    msg.msg = PLINK_EXIT_CODE;
+    sendpkt.list[0] = &msg;
+    sendpkt.num = 1;
+    sendpkt.fd = PLINK_INVALID_FD;
+    sts = PLINK_send(plink, 0, &sendpkt);
+    PLINK_close(plink, 0);
+    pthread_mutex_destroy(&port->pic_mutex);
+    return NULL;
+}
+
+int main(int argc, char **argv) {
+    PlinkStatus sts = PLINK_STATUS_OK;
+    StitcherParams params;
+    StitcherContext ctx;
+    PlinkHandle plink = NULL;
+    PlinkPacket pkt = {0};
+    PlinkYuvInfo pic;
+    PlinkMsg msg;
+
+    parseParams(argc, argv, &params);
+    if (checkParams(&params) != 0)
+    {
+        printUsage(argv[0]);
+        return 0;
+    }
+
+    memset(&ctx, 0, sizeof(ctx));
+
+    void *vmem = NULL;
+    if (VMEM_create(&vmem) != VMEM_STATUS_OK)
+        errExit("Failed to create VMEM.");
+
+    int width = params.width;
+    int height = params.height;
+    int stride = params.stride;
+    StitchLayout layout = params.layout;
+    int size = getBufferSize(&params);
+    if (size == 0)
+        errExit("Wrong format or wrong resolution.");
+    PictureBuffer picbuffers[NUM_OF_BUFFERS];
+    AllocateBuffers(picbuffers, size, vmem);
+
+    sem_init(&ctx.sem_ready, 0, 0);
+    sem_init(&ctx.sem_done, 0, 0);
+    pthread_mutex_init(&ctx.count_mutex, NULL);
+
+    pthread_t thread_in[MAX_NUM_OF_INPUTS];
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    for (int i = 0; i < MAX_NUM_OF_INPUTS; i++)
+    {
+        ctx.in[i].name = params.in_name[i];
+        ctx.in[i].count_mutex = &ctx.count_mutex;
+        ctx.in[i].sem_ready = &ctx.sem_ready;
+        ctx.in[i].sem_done = &ctx.sem_done;
+        ctx.in[i].in_count = &ctx.in_count;
+        ctx.in[i].exit = &ctx.exitcode;
+        ctx.in[i].vmem = vmem;
+        if (pthread_create(&thread_in[i], &attr, input_thread, &ctx.in[i]) != 0)
+            fprintf(stderr, "[STITCHER] ERROR: Failed to create thread for input %d\n", i);
+    }
+
+    sts = PLINK_create(&plink, params.out_name, PLINK_MODE_SERVER);
+
+    StitcherPort *out = &ctx.out;
+    out->available_bufs = NUM_OF_BUFFERS;
+    out->count_mutex = &ctx.count_mutex;
+    out->sem_ready = &ctx.sem_ready;
+    out->sem_done = &ctx.sem_done;
+    out->format = params.format;
+    out->layout = params.layout;
+    out->width = params.width;
+    out->height = params.height;
+    out->stride = params.stride;
+    out->offset = 0;
+    out->offset_uv = params.height * params.stride;
+    out->exit = &ctx.exitcode;
+    sts = PLINK_connect(plink, &out->id);
+
+    int exitcode = 0;
+    do {
+        int sendid = out->sendid;
+        out->buffer = picbuffers[sendid].virtual_address;
+        if (stitchOneFrame(&ctx) != 0)
+            break;
+        constructYuvInfo(&pic, &params, picbuffers[sendid].bus_address, sendid);
+        printf("[STITCHER] Processed frame %d 0x%010llx: %dx%d, stride = luma %d, chroma %d\n", 
+                sendid, pic.bus_address_y, 
+                pic.pic_width, pic.pic_height,
+                pic.stride_y, pic.stride_u);
+
+        pkt.list[0] = &pic;
+        pkt.num = 1;
+        pkt.fd = picbuffers[sendid].fd;
+        sts = PLINK_send(plink, out->id, &pkt);
+        out->sendid = (out->sendid + 1) % NUM_OF_BUFFERS;
+        out->available_bufs -= 1;
+
+        int timeout = out->available_bufs == 0 ? 100 : 0;
+        if (PLINK_wait(plink, out->id, timeout) == PLINK_STATUS_OK)
+        {
+            do
+            {
+                sts = PLINK_recv(plink, out->id, &pkt);
+                int count = getBufferCount(&pkt);
+                if (count < 0)
+                    exitcode = 1;
+                out->available_bufs += count;
+            } while (sts == PLINK_STATUS_MORE_DATA);
+        }
+    } while (exitcode == 0);
+
+cleanup:
+    ctx.exitcode = 1;
+    sem_post(&ctx.sem_done);
+    msg.header.type = PLINK_TYPE_MESSAGE;
+    msg.header.size = DATA_SIZE(PlinkMsg);
+    msg.msg = PLINK_EXIT_CODE;
+    pkt.list[0] = &msg;
+    pkt.num = 1;
+    pkt.fd = PLINK_INVALID_FD;
+    sts = PLINK_send(plink, out->id, &pkt);
+    retreiveSentBuffers(plink, out);
+    PLINK_recv_ex(plink, out->id, &pkt, 1000);
+    for (int i = 0; i < MAX_NUM_OF_INPUTS; i++)
+        pthread_join(thread_in[i], NULL);
+    //sleep(1); // Sleep one second to make sure client is ready for exit
+    if (vmem)
+        FreeBuffers(picbuffers, vmem);
+    PLINK_close(plink, PLINK_CLOSE_ALL);
+    VMEM_destroy(vmem);
+    pthread_mutex_destroy(&ctx.count_mutex);
+    sem_destroy(&ctx.sem_ready);
+    sem_destroy(&ctx.sem_done);
+    exit(EXIT_SUCCESS);
+}
+

Some files were not shown because too many files changed in this diff