Android Visca 协议控制电子云镜(Android ndk)

项目是个教学类TV应用,有一个功能是要控制摄像机的云台控制,就是控制跟踪摄像机的方向(上,下,左,右);
摄像机的焦距(远和近),预制位调用等功能。与跟踪摄像机的通信基于sony 的visca协议,visca协议是我自己封装,用C++代码实现,所以本篇博客主要有两个目的,一方面可以让对不熟悉ndk开发的朋友简单练习写jni代码,C++代码,ndk开发。另一方面 可以用作实际项目中。

项目地址:https://github.com/zxd1991/ViscaAndroid

JNI 接口:

public class ViscaJni {

    static {
        System.loadLibrary("native-lib");
    }

    private static ViscaJni instance;

    public static ViscaJni getInstance() {
        if (instance == null) {
            instance = new ViscaJni();
        }
        return instance;
    }

    /**
     *  connect the camera
     * @param ip camera server ip
     * @param port camera server port
     * @return 0 success,otherwise failure.
     */
    public native int setTraceCameraAddress(String ip,int port);

    /**
     * 控制云台方向位置
     * @param camId 0 means teacher camera,1 means student camera
     * @param dir include left,up,right,bottom
     * @param continuous true means change the pantilt pos continuously
     * @return 0 success,otherwise failure.
     */
    public native int setPantiltPos(int camId, int dir,boolean continuous);

    /**
     *  设置云台方向回到Home位置
     * @param camId int 值,1 为老师,2为学生
     * @return 0 success,otherwise failure.
     */
    public native int setPantiltHome(int camId);

    /**
     * 设置云台速度
     * @param camId int 值,1 为老师,2为学生
     * @param speed
     * @return 0 success,otherwise failure.
     */
    public native int setPantileSpeed(int camId, int speed);

    /**
     * 设置预制位
     * @param camId int 值,1 为老师,2为学生
     * @param channel 预制位
     * @return 0 success,otherwise failure.
     */
    public native int setCamMemory(int camId, int channel);

    /**
     *  调用预制位
     * @param camId int 值,1 为老师,2为学生
     * @param channel 预制位
     * @return  0 success,otherwise failure.
     */
    public native int reCallCamMemory(int camId, int channel);

    /**
     *  设置焦距
     * @param camId int 值,1 为老师,2为学生
     * @param far true 远 false 近
     * @return 0 success,otherwise failure.
     */
    public native int setZoomPosition(int camId,boolean far);

}

c++ 层入口

#include 
#include "visca.h"

extern "C" {
JNIEXPORT jint  JNICALL
Java_androidjni_example_com_viscaandroid_ViscaJni_setTraceCameraAddress(JNIEnv *env, jobject,
                                                                        jstring ip, jint port) {
    const char *address = (char *) env->GetStringUTFChars(ip, JNI_FALSE);
    ViscaManager viscaHelper;
    jint result = viscaHelper.connectHostCam(address, port);
    env->ReleaseStringUTFChars(ip, address);
    return result;
}
JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_setPantiltPos
        (JNIEnv *env, jobject jobject, jint camId, jint dir) {
    ViscaManager viscaHelper;
    return viscaHelper.setPantiltPos(camId, dir);
}

JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_setPantiltHome
        (JNIEnv *env, jobject jobject, jint camId) {
    ViscaManager viscaHelper;
    return viscaHelper.setPantiltHome(camId);
}

JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_setPantileSpeed
        (JNIEnv *env, jobject jobject, jint camId, jint speed) {
    ViscaManager viscaHelper;
    return viscaHelper.setPantiltSpeed(camId, speed);
}

JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_setCamMemory
        (JNIEnv *env, jobject jobject, jint camId, jint channel) {
    ViscaManager viscaHelper;
    return viscaHelper.setCamMemory(camId, channel);
}

JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_reCallCamMemory
        (JNIEnv *env, jobject jobject, jint camId, jint channel) {
    ViscaManager viscaHelper;
    return viscaHelper.recallCamMemory(camId, channel);
}
JNIEXPORT jint JNICALL Java_androidjni_example_com_viscaandroid_ViscaJni_setZoomPosition
        (JNIEnv *env, jobject jobject, jint camId, jboolean far) {
    ViscaManager viscaHelper;
    return viscaHelper.setZoomPosition(camId, far);
}

}




Visca 控制协议头文件


    #define    VISCA_SUCCESS                       0
#define    VISCA_FAILURE                      -1

class ViscaManager {

public:
    //
    enum CAM_ID {
        TEACHER = 1,
        STUDENT
    };

    //
    enum CAM_DIR {
        LEFT = 0,
        UP,
        RIGHT,
        DOWN,
        HOME
    };

    ViscaManager();

    ~ViscaManager();

    //@brief connect the host camera,@ipAddress represents the host ip,@port represents the server's port
    //@return 0 means successful result, or  -1 means failure result
    int connectHostCam(const char *ipAddress, uint16_t port);

    //@brief set the pantilt pos according to direction
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    // represents the id of student camera
    //@param dir @see CAM_DIR,@see CAM_DIR#LEFT,CAM_DIR#UP,CAM_DIR#RIGHT,CAM_DIR#DOWN,CAM_DI#HOME
    //@return 0 means successful result, or  -1 means failure result
    int setPantiltPos(uint8_t camera_id, uint8_t dir);

    //@brief set the dev return home position
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    //@return 0 is suc, or -1 failed
    int setPantiltHome(uint8_t camera_id);

    //@brief set the speed of pantilt
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    //@speed the speed of moving the pantilt,from minimum 0x01 to maximum 0x14
    //@return 0 is suc, or -1 failed
    int setPantiltSpeed(uint8_t camera_id, uint8_t speed);

    //@brief set the preset of camera,including stats of the direction,zoom..
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    //@channel the preset of states of camera,from 0x00 to 0xFF .
    //@return 0 is suc, or -1 failed
    int setCamMemory(uint8_t camera_id, uint8_t channel);

    //@brief set the recall of camera preset,including the states of the direction,zoom.
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    //@channel the preset of states of camera,from 0x00 to 0xFF .
    //@return 0 is suc, or -1 failed
    int recallCamMemory(uint8_t camera_id, uint8_t channel);

    //@brief set the recall of camera preset,including stats of the direction,zoom..
    //@param camera_id @ see CAM_ROLE#TEACHER represents the id of teacher camera,@ see CAM_ROLE#STUDENT
    //@wide means the far zoom position,or near zoom position
    //@return 0 is suc, or -1 failed
    int setZoomPosition(uint8_t camera_id, bool wide);

private:
    void *m_priv;
};


Visca cpp文件

#include 
#include "visca.h"
#include "visca_data.h"
#include 

#define LOG_TAG "VISCA"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define    VISCA_MEMORY                        0x3F
#define    VISCA_COMMAND                      0x01
#define    VISCA_INQUIRY                      0x09
#define    VISCA_CATEGORY                     0x04
#define    VISCA_PT_HOME                       0x04
#define    VISCA_TERMINATOR                   0xFF
#define    VISCA_MEMORY_SET                    0x01
#define    VISCA_MEMORY_RECALL                 0x02
#define    VISCA_ZOOM_VALUE                    0x47
#define    VISCA_PT_POSITION_INQ               0x12
#define    VISCA_RESPONSE_COMPLETED             0x50
#define    VISCA_PANTILT_SPEED_MAX              0x14
#define    VISCA_PANTILT_SPEED_MIN              0x01
#define    VISCA_CATEGORY_PAN_TILTER            0x06
#define    VISCA_PT_ABSOLUTE_POSITION           0x02
const static uint8_t COMMAND_RESPONSE_LEN = 6;
const static uint8_t COMMAND_RESPONSE_INDEX = 4;
const static uint8_t INQUIRY_ZOOM_POS_RESP_LEN = 7;
const static uint8_t INQUIRY_PANTILT_RESP_LEN = 11;
const static uint8_t PAN_TILT_DEGREE_DUR = 20;
const static uint8_t ZOOM_POS_CHANGE_DUR = 24;
const static uint16_t PAN_TILT_DEGREE_HORIZ_MAX = 0x09a4;
const static uint16_t PAN_TILT_DEGREE_VER_MAX = 0x570;
const static uint16_t ZOOM_POS_MAX = 0x0900;
const static uint16_t ZOOM_POS_MIN = 0x0500;
static uint8_t pan_tilt_speed_teac = 1;
static uint8_t pan_tilt_speed_stud = 1;

#include 

#define INIT_PRIV(viscaManagerImpl) \
    ViscaManagerImpl *viscaManagerImpl = NULL; \
    if (m_priv) { \
        viscaManagerImpl = static_cast(m_priv); \
    } \
    assert(viscaManagerImpl); \


class ViscaManagerImpl {
public:
    ViscaManagerImpl();

    ~ViscaManagerImpl();

    struct ViscaPacket {
        unsigned char bytes[32];
        uint32_t length;
    };

    uint16_t getZoomValue(uint8_t camera_id);

    uint32_t getPantiltPosition(uint8_t camera_id);

    void appendPantiltPosPacket(ViscaPacket *packet, uint8_t camera_id, uint16_t horizpos,
                                uint16_t verpos);

    int checkPanTiltPos(uint16_t pan_pos_horiz, uint16_t pan_pos_ver, uint8_t dir);

    char *sendViscaPacket(uint8_t camera_id, ViscaPacket *packet);

    void viscaPacketInit(ViscaPacket *packet);

    void viscaAppendByte(ViscaPacket *packet, unsigned char byte);

    int getCommandResult (char * result);
};

ViscaManager::ViscaManager()
        : m_priv(new ViscaManagerImpl()) {}

ViscaManager::~ViscaManager() {
    if (m_priv) {
        ViscaManagerImpl *d = static_cast(m_priv);
        if (d) {
            delete d;
            m_priv = NULL;
        }
    }
}

ViscaManagerImpl::ViscaManagerImpl() {}

ViscaManagerImpl::~ViscaManagerImpl() {}

int ViscaManager::connectHostCam(const char *ip, uint16_t port) {
    return connectMyServer(ip, port);
}

int ViscaManager::setPantiltPos(uint8_t camera_id, uint8_t dir) {
    INIT_PRIV(viscaManagerImpl);
    uint32_t pan_pos = viscaManagerImpl->getPantiltPosition(camera_id);
    u_int16_t pan_pos_horiz = pan_pos >> 16;
    u_int16_t pan_pos_ver = (pan_pos << 16) >> 16;
    if (viscaManagerImpl->checkPanTiltPos(pan_pos_horiz, pan_pos_ver, dir) != VISCA_SUCCESS) {
        LOGE("exceed the max");
        return VISCA_FAILURE;
    }
    LOGD("pan_pos_horiz:%d,pan_pos_ver:%d", pan_pos_horiz, pan_pos_ver);
    ViscaManagerImpl::ViscaPacket packet;
    viscaManagerImpl->viscaPacketInit(&packet);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_COMMAND);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_CATEGORY_PAN_TILTER);

    switch (dir) {
        case LEFT:
            pan_pos_horiz -= PAN_TILT_DEGREE_DUR;
            break;
        case UP:
            pan_pos_ver -= PAN_TILT_DEGREE_DUR;
            break;
        case RIGHT:
            pan_pos_horiz += PAN_TILT_DEGREE_DUR;
            break;
        case DOWN:
            pan_pos_ver += PAN_TILT_DEGREE_DUR;
            break;
        case HOME:
            viscaManagerImpl->viscaAppendByte(&packet, VISCA_PT_HOME);
            break;
    }
    if (dir != HOME) {
        viscaManagerImpl->viscaAppendByte(&packet, VISCA_PT_ABSOLUTE_POSITION);
        viscaManagerImpl->appendPantiltPosPacket(&packet, camera_id, pan_pos_horiz, pan_pos_ver);
    }
    char *result = viscaManagerImpl->sendViscaPacket(camera_id, &packet);
    if (viscaManagerImpl->getCommandResult(result) != VISCA_SUCCESS) {
        return VISCA_FAILURE;
    }
    return VISCA_SUCCESS;
}

int ViscaManager::setPantiltHome(uint8_t camera_id) {
    INIT_PRIV(viscaManagerImpl);
    ViscaManagerImpl::ViscaPacket packet;
    viscaManagerImpl->viscaPacketInit(&packet);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_COMMAND);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_CATEGORY_PAN_TILTER);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_PT_HOME);
    char *result = viscaManagerImpl->sendViscaPacket(camera_id, &packet);
    return viscaManagerImpl->getCommandResult(result);
}

int ViscaManagerImpl::checkPanTiltPos(uint16_t pan_pos_horiz, uint16_t pan_pos_ver, uint8_t dir) {
    switch (dir) {
        case ViscaManager::LEFT:
            if (pan_pos_horiz <= PAN_TILT_DEGREE_DUR) {
                return VISCA_FAILURE;
            }
            break;
        case ViscaManager::UP:
            if (pan_pos_ver <= PAN_TILT_DEGREE_DUR) {
                return VISCA_FAILURE;
            }
            break;
        case ViscaManager::RIGHT:
            if (pan_pos_horiz >= PAN_TILT_DEGREE_HORIZ_MAX) {
                return VISCA_FAILURE;
            }
            break;
        case ViscaManager::DOWN:
            if (pan_pos_ver >= PAN_TILT_DEGREE_VER_MAX) {
                return VISCA_FAILURE;
            }
            break;
        default:
            break;
    }

    return VISCA_SUCCESS;
}


void ViscaManagerImpl::appendPantiltPosPacket
        (ViscaPacket *packet, uint8_t camera_Id, u_int16_t horizpos,
         uint16_t verpos) {
    LOGD("appendPantiltPosPacket hori: %d,ver:%d", horizpos, verpos);
    if (camera_Id == ViscaManager::TEACHER) {
        viscaAppendByte(packet, pan_tilt_speed_teac);
        viscaAppendByte(packet, pan_tilt_speed_teac);
    } else {
        viscaAppendByte(packet, pan_tilt_speed_stud);
        viscaAppendByte(packet, pan_tilt_speed_stud);
    }
    viscaAppendByte(packet, (horizpos & 0xF000) >> 12);
    viscaAppendByte(packet, (horizpos & 0x0F00) >> 8);
    viscaAppendByte(packet, (horizpos & 0x00F0) >> 4);
    viscaAppendByte(packet, (horizpos & 0x000F));

    viscaAppendByte(packet, (verpos & 0xF000) >> 12);
    viscaAppendByte(packet, (verpos & 0x0F00) >> 8);
    viscaAppendByte(packet, (verpos & 0x00F0) >> 4);
    viscaAppendByte(packet, (verpos & 0x000F));
}


int ViscaManager::setPantiltSpeed(uint8_t camera_id, uint8_t speed) {
    if (speed > VISCA_PANTILT_SPEED_MAX || speed < VISCA_PANTILT_SPEED_MIN) {
        return VISCA_FAILURE;
    }
    if (camera_id == TEACHER) {
        pan_tilt_speed_teac = speed;
    } else if (camera_id == STUDENT) {
        pan_tilt_speed_stud = speed;
    }
    return VISCA_SUCCESS;
}

int ViscaManager::setCamMemory(uint8_t camera_id, uint8_t channel) {
    INIT_PRIV(viscaManagerImpl);
    ViscaManagerImpl::ViscaPacket packet;
    viscaManagerImpl->viscaPacketInit(&packet);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_COMMAND);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_CATEGORY);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_MEMORY);

    viscaManagerImpl->viscaAppendByte(&packet, VISCA_MEMORY_SET);
    viscaManagerImpl->viscaAppendByte(&packet, channel);
    char *result = viscaManagerImpl->sendViscaPacket(camera_id, &packet);
    return viscaManagerImpl->getCommandResult(result);
}

int ViscaManager::recallCamMemory(uint8_t camera_id, uint8_t channel) {
    INIT_PRIV(viscaManagerImpl);
    ViscaManagerImpl::ViscaPacket packet;
    viscaManagerImpl->viscaPacketInit(&packet);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_COMMAND);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_CATEGORY);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_MEMORY);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_MEMORY_RECALL);
    viscaManagerImpl->viscaAppendByte(&packet, channel);
    char *result = viscaManagerImpl->sendViscaPacket(camera_id, &packet);
    return viscaManagerImpl->getCommandResult(result);
}

int ViscaManager::setZoomPosition(uint8_t camera_id, bool far) {
    INIT_PRIV(viscaManagerImpl);
    uint16_t zoomPos = viscaManagerImpl->getZoomValue(camera_id);
    LOGD("zoomPos:%d", zoomPos);
    if ((far && zoomPos >= ZOOM_POS_MAX) || (!far && zoomPos <= ZOOM_POS_MIN)) {
        return VISCA_FAILURE;
    }
    if (far) {
        zoomPos += ZOOM_POS_CHANGE_DUR;
    } else {
        zoomPos -= ZOOM_POS_CHANGE_DUR;
    }
    ViscaManagerImpl::ViscaPacket packet;
    viscaManagerImpl->viscaPacketInit(&packet);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_COMMAND);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_CATEGORY);
    viscaManagerImpl->viscaAppendByte(&packet, VISCA_ZOOM_VALUE);
    viscaManagerImpl->viscaAppendByte(&packet, ((zoomPos & 0xF000) >> 12));
    viscaManagerImpl->viscaAppendByte(&packet, ((zoomPos & 0x0F00) >> 8));
    viscaManagerImpl->viscaAppendByte(&packet, ((zoomPos & 0x00F0) >> 4));
    viscaManagerImpl->viscaAppendByte(&packet, ((zoomPos & 0x00F)));
    char *result = viscaManagerImpl->sendViscaPacket(camera_id, &packet);
    return viscaManagerImpl->getCommandResult(result);
}

uint16_t ViscaManagerImpl::getZoomValue(uint8_t camera_id) {
    ViscaPacket packet;
    viscaPacketInit(&packet);
    viscaAppendByte(&packet, VISCA_INQUIRY);
    viscaAppendByte(&packet, VISCA_CATEGORY);
    viscaAppendByte(&packet, VISCA_ZOOM_VALUE);
    char *result = sendViscaPacket(camera_id, &packet);
    unsigned char temp[INQUIRY_ZOOM_POS_RESP_LEN];
    memset(temp, 0, INQUIRY_ZOOM_POS_RESP_LEN);
    memcpy(temp, result, INQUIRY_ZOOM_POS_RESP_LEN);
    delete []result;
    uint16_t zoomValue =
            ((temp[2] & 0x0F) << 12) + ((temp[3] & 0x0F) << 8) + ((temp[4] & 0x0F) << 4) +
            ((temp[5] & 0x0F));
    return zoomValue;
}

uint32_t ViscaManagerImpl::getPantiltPosition(uint8_t camera_id) {
    ViscaPacket packet;
    viscaPacketInit(&packet);
    viscaAppendByte(&packet, VISCA_INQUIRY);
    viscaAppendByte(&packet, VISCA_CATEGORY_PAN_TILTER);
    viscaAppendByte(&packet, VISCA_PT_POSITION_INQ);
    char *result = sendViscaPacket(camera_id, &packet);
    unsigned char temp[INQUIRY_PANTILT_RESP_LEN];
    memset(temp, 0, INQUIRY_PANTILT_RESP_LEN);
    memcpy(temp, result, INQUIRY_PANTILT_RESP_LEN);
    delete []result;
    uint32_t pantile_pos = 0;
    pantile_pos = (temp[2] << 28) + (temp[3] << 24) + (temp[4] << 20) + (temp[5] << 16) +
                  (temp[6] << 12) + (temp[7]
            << 8) + (temp[8] << 4) + temp[9];
    return pantile_pos;
}

int ViscaManagerImpl::getCommandResult(char *result) {
    unsigned char temp[COMMAND_RESPONSE_LEN];
    memset(temp, 0, COMMAND_RESPONSE_LEN);
    memcpy(temp, result, COMMAND_RESPONSE_LEN);
    int command_resp = result[COMMAND_RESPONSE_INDEX];
    command_resp = command_resp & 0xF0;
    delete []result;
    if (command_resp == VISCA_RESPONSE_COMPLETED) {
        return VISCA_SUCCESS;
    }
    return VISCA_FAILURE;
}


void ViscaManagerImpl::viscaPacketInit(ViscaPacket *packet) {
    packet->length = 1;
}

 char *ViscaManagerImpl::sendViscaPacket(uint8_t cam_id, ViscaPacket *packet) {
    packet->bytes[0] = 0x80;
    packet->bytes[0] |= cam_id;
    viscaAppendByte(packet, VISCA_TERMINATOR);
    return sendData(packet->bytes,packet->length);
}

void ViscaManagerImpl::viscaAppendByte(ViscaPacket *packet, unsigned char byte) {
    packet->bytes[packet->length] = byte;
    (packet->length)++;
}



通信部分

//
// Created by zhangxd on 2019/3/12.
//
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LOG_TAG "VISCA"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
int socketfd;
extern "C"
int connectMyServer(const char *ip, uint16_t port) {
    socketfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(ip);
    if (connect(socketfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        LOGE("connect failure");
        return VISCA_FAILURE;
    }
    LOGD("connect success");
    return VISCA_SUCCESS;
}

char *sendData(unsigned char *data, uint8_t length) {
    LOGD("sendData size:%d", length);
    uint8_t sendbuffer[length];
    memset(sendbuffer, 0, length);
    memcpy(sendbuffer, data, length);
    if (send(socketfd, sendbuffer, length, 0) < 0) {
        LOGE("sendData failure");
        return NULL;
    };
    int i = 0;
    while (i < sizeof(sendbuffer)) {
        LOGD("send data: %x", sendbuffer[i]);
        i++;
    }
    char *result = new char[20];
    clock_t start = clock();
    LOGD("enter1 %d", start / CLOCKS_PER_SEC);
    while (((clock() - start) / CLOCKS_PER_SEC) < 5) {
        int size = recv(socketfd, result, 20, 0);
        int num = 0;
        while (num < size) {
            LOGD("receive data %x", result[num]);
            num++;
        }
        if (size > 0) {
            return result;
        }
    }

    return result;

}

int closeSocket() {
    close(socketfd);
}


你可能感兴趣的:(Open,Source,Project)