顯示具有 函式庫 標籤的文章。 顯示所有文章
顯示具有 函式庫 標籤的文章。 顯示所有文章

2024/11/22

PugiXML

PugiXML 是一個 C++ XML parser 函式庫,支援 DOM-like interface 與 XPATH 1.0 標準。 PugiXML 在是否容易使用、執行速度以及支援功能中取得良好的平衡,其中一個特點就是容易與其它程式整合, 將 pugixml.cpp, pugixml.hpp 與 pugiconfig.hpp 複製到原始碼目錄下就可以開始使用了。

下面是 tree.xml

<?xml version="1.0"?>
<mesh name="mesh_root">
    <!-- here is a mesh node -->
    some text
    <![CDATA[someothertext]]>
    some more text
    <node attr1="value1" attr2="value2" />
    <node attr1="value2">
        <innernode/>
    </node>
</mesh>
<?include somedata?>

下面是載入 XML 檔案的程式:

#include "pugixml.hpp"
#include <iostream>

int main() {
    pugi::xml_document doc;

    pugi::xml_parse_result result = doc.load_file("tree.xml");

    std::cout << "Load result: " << result.description()
              << ", mesh name: " << doc.child("mesh").attribute("name").value()
              << std::endl;
}

下面的程式使用 libcurl 自網站下載 ATOM XML 的資料, 下載以後使用 PugiXML 分析並且將 title 與 link 的資料儲存為 html 格式。

#include "pugixml.hpp"
#include <cstdio>
#include <cstdlib>
#include <curl/curl.h>
#include <fstream>
#include <iostream>

int get_rss(const char *url, const char *outfile) {
    FILE *feedfile = fopen(outfile, "w");

    if (!feedfile)
        return -1;

    CURL *curl = curl_easy_init();
    if (!curl)
        return -1;

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, feedfile);

    CURLcode res = curl_easy_perform(curl);
    if (res)
        return -1;
    curl_easy_cleanup(curl);
    fclose(feedfile);
    return 0;
}

int main(int argc, char *argv[]) {
    char *url = NULL;
    char *filename = NULL;
    char *outfile = NULL;
    if (argc == 4) {
        url = argv[1];
        filename = argv[2];
        outfile = argv[3];
    } else {
        printf("Not valid arguments.\n");
    }

    get_rss(url, filename);

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(filename);

    pugi::xpath_node_set title = doc.select_nodes("/feed/entry/title");
    pugi::xpath_node_set link =
        doc.select_nodes("/feed/entry/link[@rel='alternate']");

    std::ofstream ofile(outfile);

    pugi::xpath_node_set::const_iterator it1 = title.begin();
    pugi::xpath_node_set::const_iterator it2 = link.begin();
    while (it1 != title.end() && it2 != link.end()) {
        pugi::xpath_node node1 = *it1;
        pugi::xpath_node node2 = *it2;
        ofile << "<a href=\"" << node2.node().attribute("href").value() << "\">"
              << node1.node().text().get() << "</a><br>" << std::endl;

        it1++;
        it2++;
    }
    ofile.close();
}

參考連結

2024/11/21

GNU Libmicrohttpd

GNU Libmicrohttpd 是一個函式庫,授權為 GNU LGPL v2.1 (使用者也可以選擇 LGPL v2.1 之後的版本), 最主要的功能是內嵌 HTTP server 到應用程式內,有多種 threading mode 可以選擇, 支援 HTTP/1.1 與 IPv6,並且透過 GnuTLS 支援 SSL/TLS 協定。 因此,當應用程式需要內嵌 web server 作為簡單的 web 介面時,可以考慮使用 GNU Libmicrohttpd。

在 openSUSE Tumbleweed 安裝相關的開發檔案:

sudo zypper in libmicrohttpd-devel

下面是首頁上的範例:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGE                                                                   \
    "<html><head><title>libmicrohttpd demo</title>"                            \
    "</head><body>libmicrohttpd demo</body></html>"

static enum MHD_Result ahc_echo(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **ptr) {
    static int dummy;
    const char *page = cls;
    struct MHD_Response *response;
    int ret;

    if (0 != strcmp(method, "GET"))
        return MHD_NO; /* unexpected method */
    if (&dummy != *ptr) {
        /* The first time only the headers are valid,
           do not respond in the first round... */
        *ptr = &dummy;
        return MHD_YES;
    }

    if (0 != *upload_data_size)
        return MHD_NO; /* upload data in a GET!? */
        
    *ptr = NULL;       /* clear context pointer */
    response = MHD_create_response_from_buffer(strlen(page), (void *)page,
                                               MHD_RESPMEM_PERSISTENT);
    ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
    MHD_destroy_response(response);

    return ret;
}

int main(int argc, char **argv) {
    struct MHD_Daemon *d;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, atoi(argv[1]), NULL,
                         NULL, &ahc_echo, PAGE, MHD_OPTION_END);
    if (d == NULL)
        return 1;

    (void)getc(stdin);
    MHD_stop_daemon(d);
    return 0;
}

下面是加入 SSL/TLS 以及靜態檔案的支援:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>

#define BUF_SIZE 1024
#define MAX_URL_LEN 255

#define EMPTY_PAGE                                                             \
    "<html><head><title>File not found</title></head><body>File not "          \
    "found</body></html>"

/* test server key */
static const char key_pem[] =
  "-----BEGIN PRIVATE KEY-----\n\
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCff7amw9zNSE+h\n\
rOMhBrzbbsJluUP3gmd8nOKY5MUimoPkxmAXfp2L0il+MPZT/ZEmo11q0k6J2jfG\n\
UBQ+oZW9ahNZ9gCDjbYlBblo/mqTai+LdeLO3qk53d0zrZKXvCO6sA3uKpG2WR+g\n\
+sNKxfYpIHCpanqBU6O+degIV/+WKy3nQ2Fwp7K5HUNj1u0pg0QQ18yf68LTnKFU\n\
HFjZmmaaopWki5wKSBieHivzQy6w+04HSTogHHRK/y/UcoJNSG7xnHmoPPo1vLT8\n\
CMRIYnSSgU3wJ43XBJ80WxrC2dcoZjV2XZz+XdQwCD4ZrC1ihykcAmiQA+sauNm7\n\
dztOMkGzAgMBAAECggEAIbKDzlvXDG/YkxnJqrKXt+yAmak4mNQuNP+YSCEdHSBz\n\
+SOILa6MbnvqVETX5grOXdFp7SWdfjZiTj2g6VKOJkSA7iKxHRoVf2DkOTB3J8np\n\
XZd8YaRdMGKVV1O2guQ20Dxd1RGdU18k9YfFNsj4Jtw5sTFTzHr1P0n9ybV9xCXp\n\
znSxVfRg8U6TcMHoRDJR9EMKQMO4W3OQEmreEPoGt2/+kMuiHjclxLtbwDxKXTLP\n\
pD0gdg3ibvlufk/ccKl/yAglDmd0dfW22oS7NgvRKUve7tzDxY1Q6O5v8BCnLFSW\n\
D+z4hS1PzooYRXRkM0xYudvPkryPyu+1kEpw3fNsoQKBgQDRfXJo82XQvlX8WPdZ\n\
Ts3PfBKKMVu3Wf8J3SYpuvYT816qR3ot6e4Ivv5ZCQkdDwzzBKe2jAv6JddMJIhx\n\
pkGHc0KKOodd9HoBewOd8Td++hapJAGaGblhL5beIidLKjXDjLqtgoHRGlv5Cojo\n\
zHa7Viel1eOPPcBumhp83oJ+mQKBgQDC6PmdETZdrW3QPm7ZXxRzF1vvpC55wmPg\n\
pRfTRM059jzRzAk0QiBgVp3yk2a6Ob3mB2MLfQVDgzGf37h2oO07s5nspSFZTFnM\n\
KgSjFy0xVOAVDLe+0VpbmLp1YUTYvdCNowaoTE7++5rpePUDu3BjAifx07/yaSB+\n\
W+YPOfOuKwKBgQCGK6g5G5qcJSuBIaHZ6yTZvIdLRu2M8vDral5k3793a6m3uWvB\n\
OFAh/eF9ONJDcD5E7zhTLEMHhXDs7YEN+QODMwjs6yuDu27gv97DK5j1lEsrLUpx\n\
XgRjAE3KG2m7NF+WzO1K74khWZaKXHrvTvTEaxudlO3X8h7rN3u7ee9uEQKBgQC2\n\
wI1zeTUZhsiFTlTPWfgppchdHPs6zUqq0wFQ5Zzr8Pa72+zxY+NJkU2NqinTCNsG\n\
ePykQ/gQgk2gUrt595AYv2De40IuoYk9BlTMuql0LNniwsbykwd/BOgnsSlFdEy8\n\
0RQn70zOhgmNSg2qDzDklJvxghLi7zE5aV9//V1/ewKBgFRHHZN1a8q/v8AAOeoB\n\
ROuXfgDDpxNNUKbzLL5MO5odgZGi61PBZlxffrSOqyZoJkzawXycNtoBP47tcVzT\n\
QPq5ZOB3kjHTcN7dRLmPWjji9h4O3eHCX67XaPVMSWiMuNtOZIg2an06+jxGFhLE\n\
qdJNJ1DkyUc9dN2cliX4R+rG\n\
-----END PRIVATE KEY-----";

/* test server CA signed certificates */
static const char cert_pem[] =
  "-----BEGIN CERTIFICATE-----\n\
MIIFSzCCAzOgAwIBAgIBBDANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCUlUx\n\
DzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9zY293MRswGQYDVQQKDBJ0ZXN0\n\
LWxpYm1pY3JvaHR0cGQxITAfBgkqhkiG9w0BCQEWEm5vYm9keUBleGFtcGxlLm9y\n\
ZzEQMA4GA1UEAwwHdGVzdC1DQTAgFw0yMjA0MjAxODQzMDJaGA8yMTIyMDMyNjE4\n\
NDMwMlowZTELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwG\n\
TW9zY293MRswGQYDVQQKDBJ0ZXN0LWxpYm1pY3JvaHR0cGQxFzAVBgNVBAMMDnRl\n\
c3QtbWhkc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn3+2\n\
psPczUhPoazjIQa8227CZblD94JnfJzimOTFIpqD5MZgF36di9IpfjD2U/2RJqNd\n\
atJOido3xlAUPqGVvWoTWfYAg422JQW5aP5qk2ovi3Xizt6pOd3dM62Sl7wjurAN\n\
7iqRtlkfoPrDSsX2KSBwqWp6gVOjvnXoCFf/list50NhcKeyuR1DY9btKYNEENfM\n\
n+vC05yhVBxY2ZpmmqKVpIucCkgYnh4r80MusPtOB0k6IBx0Sv8v1HKCTUhu8Zx5\n\
qDz6Nby0/AjESGJ0koFN8CeN1wSfNFsawtnXKGY1dl2c/l3UMAg+GawtYocpHAJo\n\
kAPrGrjZu3c7TjJBswIDAQABo4HmMIHjMAsGA1UdDwQEAwIFoDAMBgNVHRMBAf8E\n\
AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMDEGA1UdEQQqMCiCDnRlc3QtbWhk\n\
c2VydmVyhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBQ57Z06WJae\n\
8fJIHId4QGx/HsRgDDAoBglghkgBhvhCAQ0EGxYZVGVzdCBsaWJtaWNyb2h0dHBk\n\
IHNlcnZlcjARBglghkgBhvhCAQEEBAMCBkAwHwYDVR0jBBgwFoAUWHVDwKVqMcOF\n\
Nd0arI3/QB3W6SwwDQYJKoZIhvcNAQELBQADggIBAI7Lggm/XzpugV93H5+KV48x\n\
X+Ct8unNmPCSzCaI5hAHGeBBJpvD0KME5oiJ5p2wfCtK5Dt9zzf0S0xYdRKqU8+N\n\
aKIvPoU1hFixXLwTte1qOp6TviGvA9Xn2Fc4n36dLt6e9aiqDnqPbJgBwcVO82ll\n\
HJxVr3WbrAcQTB3irFUMqgAke/Cva9Bw79VZgX4ghb5EnejDzuyup4pHGzV10Myv\n\
hdg+VWZbAxpCe0S4eKmstZC7mWsFCLeoRTf/9Pk1kQ6+azbTuV/9QOBNfFi8QNyb\n\
18jUjmm8sc2HKo8miCGqb2sFqaGD918hfkWmR+fFkzQ3DZQrT+eYbKq2un3k0pMy\n\
UySy8SRn1eadfab+GwBVb68I9TrPRMrJsIzysNXMX4iKYl2fFE/RSNnaHtPw0C8y\n\
B7memyxPRl+H2xg6UjpoKYh3+8e44/XKm0rNIzXjrwA8f8gnw2TbqmMDkj1YqGnC\n\
SCj5A27zUzaf2pT/YsnQXIWOJjVvbEI+YKj34wKWyTrXA093y8YI8T3mal7Kr9YM\n\
WiIyPts0/aVeziM0Gunglz+8Rj1VesL52FTurobqusPgM/AME82+qb/qnxuPaCKj\n\
OT1qAbIblaRuWqCsid8BzP7ZQiAnAWgMRSUg1gzDwSwRhrYQRRWAyn/Qipzec+27\n\
/w0gW9EVWzFhsFeGEssi\n\
-----END CERTIFICATE-----";

static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
    FILE *file = (FILE *)cls;
    size_t bytes_read;

    /* 'fseek' may not support files larger 2GiB, depending on platform.
     * For production code, make sure that 'pos' has valid values, supported by
     * 'fseek', or use 'fseeko' or similar function. */
    if (0 != fseek(file, (long)pos, SEEK_SET))
        return MHD_CONTENT_READER_END_WITH_ERROR;
    bytes_read = fread(buf, 1, max, file);
    if (0 == bytes_read)
        return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
                                   : MHD_CONTENT_READER_END_OF_STREAM;
    return (ssize_t)bytes_read;
}

static void file_free_callback(void *cls) {
    FILE *file = cls;
    fclose(file);
}

/* HTTP access handler call back */
static enum MHD_Result http_ahc(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **req_cls) {
    static int aptr;
    struct MHD_Response *response;
    enum MHD_Result ret;
    FILE *file;
    int fd;
    struct stat buf;
    (void)cls;              /* Unused. Silent compiler warning. */
    (void)version;          /* Unused. Silent compiler warning. */
    (void)upload_data;      /* Unused. Silent compiler warning. */
    (void)upload_data_size; /* Unused. Silent compiler warning. */

    if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
        return MHD_NO; /* unexpected method */
    if (&aptr != *req_cls) {
        /* do never respond on first call */
        *req_cls = &aptr;
        return MHD_YES;
    }
    *req_cls = NULL; /* reset when done */

    file = fopen(&url[1], "rb");
    if (NULL != file) {
        fd = fileno(file);
        if (-1 == fd) {
            (void)fclose(file);
            return MHD_NO; /* internal error */
        }
        if ((0 != fstat(fd, &buf)) || (!S_ISREG(buf.st_mode))) {
            /* not a regular file, refuse to serve */
            fclose(file);
            file = NULL;
        }
    }

    if (NULL == file) {
        response = MHD_create_response_from_buffer_static(
            strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
        ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
        MHD_destroy_response(response);
    } else {
        response = MHD_create_response_from_callback(
            (size_t)buf.st_size, 32 * 1024, /* 32k page size */
            &file_reader, file, &file_free_callback);
        if (NULL == response) {
            fclose(file);
            return MHD_NO;
        }
        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
        MHD_destroy_response(response);
    }
    return ret;
}

int main(int argc, char *const *argv) {
    struct MHD_Daemon *TLS_daemon;
    int port;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    port = atoi(argv[1]);
    if ((1 > port) || (port > 65535)) {
        fprintf(stderr, "Port must be a number between 1 and 65535.\n");
        return 1;
    }

    TLS_daemon = MHD_start_daemon(
        MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD |
            MHD_USE_ERROR_LOG | MHD_USE_TLS,
        (uint16_t)port, NULL, NULL, &http_ahc, NULL,
        MHD_OPTION_CONNECTION_TIMEOUT, 256, MHD_OPTION_HTTPS_MEM_KEY, key_pem,
        MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_END);
    if (NULL == TLS_daemon) {
        fprintf(stderr, "Error: failed to start TLS_daemon.\n");
        return 1;
    }
    printf("MHD daemon listening on port %u\n", (unsigned int)port);

    (void)getc(stdin);
    MHD_stop_daemon(TLS_daemon);
    return 0;
}

接下來使用自簽憑證設定 HTTPS 並且採用 GnuTLS 載入檔案的方式。
首先建立 ssl.conf 設定檔:

[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req

[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1

透過 OpenSSL 指令建立開發測試用途的自簽憑證:

openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 \
-newkey rsa:2048 -keyout httpd.key -out httpd.crt -config ssl.conf

下面是一個使用的例子:

#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>

#define BUF_SIZE 1024
#define MAX_URL_LEN 255

#define EMPTY_PAGE                                                             \
    "<html><head><title>File not found</title></head><body>File not "          \
    "found</body></html>"

#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>

/**
 * A hostname, server key and certificate.
 */
struct Hosts {
    struct Hosts *next;
    const char *hostname;
    gnutls_pcert_st pcrt;
    gnutls_privkey_t key;
};

#define PAGE                                                                   \
    "<html><head><title>libmicrohttpd https demo</title>"                      \
    "</head><body>libmicrohttpd https demo</body></html>"

static struct Hosts *hosts;

/* Load the certificate and the private key.
 * (This code is largely taken from GnuTLS).
 */
static void load_keys(const char *hostname, const char *CERT_FILE,
                      const char *KEY_FILE) {
    int ret;
    gnutls_datum_t data;
    struct Hosts *host;

    host = malloc(sizeof(struct Hosts));
    if (NULL == host)
        abort();
    host->hostname = hostname;
    host->next = hosts;
    hosts = host;

    ret = gnutls_load_file(CERT_FILE, &data);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading certificate file %s.\n", CERT_FILE);
        exit(1);
    }
    ret = gnutls_pcert_import_x509_raw(&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
                                       0);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading certificate file: %s\n",
                gnutls_strerror(ret));
        exit(1);
    }
    gnutls_free(data.data);

    ret = gnutls_load_file(KEY_FILE, &data);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading key file %s.\n", KEY_FILE);
        exit(1);
    }

    gnutls_privkey_init(&host->key);
    ret = gnutls_privkey_import_x509_raw(host->key, &data, GNUTLS_X509_FMT_PEM,
                                         NULL, 0);
    if (ret < 0) {
        fprintf(stderr, "*** Error loading key file: %s\n",
                gnutls_strerror(ret));
        exit(1);
    }
    gnutls_free(data.data);
}

static int cert_callback(gnutls_session_t session,
                         const gnutls_datum_t *req_ca_dn, int nreqs,
                         const gnutls_pk_algorithm_t *pk_algos,
                         int pk_algos_length, gnutls_pcert_st **pcert,
                         unsigned int *pcert_length, gnutls_privkey_t *pkey) {
    char name[256];
    size_t name_len;
    struct Hosts *host;
    unsigned int type;
    (void)req_ca_dn;
    (void)nreqs;
    (void)pk_algos;
    (void)pk_algos_length; /* Unused. Silent compiler warning. */

    name_len = sizeof(name);
    if (GNUTLS_E_SUCCESS !=
        gnutls_server_name_get(session, name, &name_len, &type, 0 /* index */))
        return -1;
    for (host = hosts; NULL != host; host = host->next)
        if (0 == strncmp(name, host->hostname, name_len))
            break;
    if (NULL == host) {
        fprintf(stderr, "Need certificate for %.*s\n", (int)name_len, name);
        return -1;
    }
#if 1
    fprintf(stderr, "Returning certificate for %.*s\n", (int)name_len, name);
#endif
    *pkey = host->key;
    *pcert_length = 1;
    *pcert = &host->pcrt;
    return 0;
}

static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
    FILE *file = (FILE *)cls;
    size_t bytes_read;

    /* 'fseek' may not support files larger 2GiB, depending on platform.
     * For production code, make sure that 'pos' has valid values, supported by
     * 'fseek', or use 'fseeko' or similar function. */
    if (0 != fseek(file, (long)pos, SEEK_SET))
        return MHD_CONTENT_READER_END_WITH_ERROR;
    bytes_read = fread(buf, 1, max, file);
    if (0 == bytes_read)
        return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
                                   : MHD_CONTENT_READER_END_OF_STREAM;
    return (ssize_t)bytes_read;
}

static void file_free_callback(void *cls) {
    FILE *file = cls;
    fclose(file);
}

/* HTTP access handler call back */
static enum MHD_Result http_ahc(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **req_cls) {
    static int aptr;
    struct MHD_Response *response;
    enum MHD_Result ret;
    FILE *file;
    int fd;
    struct stat buf;
    (void)cls;              /* Unused. Silent compiler warning. */
    (void)version;          /* Unused. Silent compiler warning. */
    (void)upload_data;      /* Unused. Silent compiler warning. */
    (void)upload_data_size; /* Unused. Silent compiler warning. */

    if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
        return MHD_NO; /* unexpected method */
    if (&aptr != *req_cls) {
        /* do never respond on first call */
        *req_cls = &aptr;
        return MHD_YES;
    }
    *req_cls = NULL; /* reset when done */

    file = fopen(&url[1], "rb");
    if (NULL != file) {
        fd = fileno(file);
        if (-1 == fd) {
            (void)fclose(file);
            return MHD_NO; /* internal error */
        }
        if ((0 != fstat(fd, &buf)) || (!S_ISREG(buf.st_mode))) {
            /* not a regular file, refuse to serve */
            fclose(file);
            file = NULL;
        }
    }

    if (NULL == file) {
        response = MHD_create_response_from_buffer_static(
            strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
        ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
        MHD_destroy_response(response);
    } else {
        response = MHD_create_response_from_callback(
            (size_t)buf.st_size, 32 * 1024, /* 32k page size */
            &file_reader, file, &file_free_callback);
        if (NULL == response) {
            fclose(file);
            return MHD_NO;
        }
        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
        MHD_destroy_response(response);
    }
    return ret;
}

int main(int argc, char *const *argv) {
    struct MHD_Daemon *TLS_daemon;
    int port;

    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }

    port = atoi(argv[1]);
    if ((1 > port) || (port > 65535)) {
        fprintf(stderr, "Port must be a number between 1 and 65535.\n");
        return 1;
    }

    load_keys("localhost", "./httpd.crt", "./httpd.key");

    TLS_daemon = MHD_start_daemon(
        MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD |
            MHD_USE_ERROR_LOG | MHD_USE_TLS,
        (uint16_t)port, NULL, NULL, &http_ahc, NULL,
        MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)120,
        MHD_OPTION_HTTPS_CERT_CALLBACK, &cert_callback, 
        MHD_OPTION_END);
    if (NULL == TLS_daemon) {
        fprintf(stderr, "Error: failed to start TLS_daemon.\n");
        return 1;
    }
    printf("MHD daemon listening on port %u\n", (unsigned int)port);

    (void)getc(stdin);
    MHD_stop_daemon(TLS_daemon);
    return 0;
}

參考連結

2024/04/17

OpenCL

簡介

OpenCL(Open Computing Language,開放計算語言)是一個為異構平台編寫程式的框架, 此異構平台可由 CPU、GPU、DSP、FPGA 或其他類型的處理器與硬體加速器所組成。 Portable Computing Language (PoCL) 則是 OpenCL 的一個自由軟體實作, 可以在機器上沒有 GPU 的情況下使用 OpenCL API 進行運算。

OpenCL 包括一組 API 和一個程式語言。基本的原理是程式透過 OpenCL API 取得 OpenCL 裝置(例如顯示晶片)的相關資料, 並將要在裝置上執行的程式(使用 OpenCL 程式語言撰寫)編繹成適當的格式以後在裝置上執行。

An OpenCL application is split into host code and device kernel code. Execution of an OpenCL program occurs in two parts: kernelsthat execute on one or more OpenCL devices and a host program that executes on the host.

The most commonly used language for programming the kernels that are compiled and executed across the available parallel processors is called OpenCL C. OpenCL C is based on C99 and is defined as part of the OpenCL specification.

The core of the OpenCL execution model is defined by how the kernels execute. OpenCL regards a kernel program as the basic unit of executable code (similar to a C function). Kernels can execute with data or task-parallelism. An OpenCL program is a collection of kernels and functions (similar to dynamic library with run-time linking).

An OpenCL command queue is used by the host application to send kernels and data transfer functions to a device for execution. By enqueueing commands into a command queue, kernels and data transfer functions may execute asynchronously and in parallel with application host code.

The kernels and functions in a command queue can be executed in-order or out-of-order. A compute device may have multiple command queues.


A complete sequence for executing an OpenCL program is:

  1. Query for available OpenCL platforms and devices
  2. Create a context for one or more OpenCL devices in a platform
  3. Create and build programs for OpenCL devices in the context
  4. Select kernels to execute from the programs
  5. Create memory objects for kernels to operate on
  6. Create command queues to execute commands on an OpenCL device
  7. Enqueue data transfer commands into the memory objects, if needed
  8. Enqueue kernels into the command queue for execution
  9. Enqueue commands to transfer data back to the host, if needed

A host is connected to one or more OpenCL compute devices. Each compute device is collection of one or more compute units where each compute unit is composed of one or more processing elements. Processing elements execute code with SIMD (Single Instruction Multiple Data) or SPMD (Single Program Multiple Data) parallelism.


For example, a compute device could be a GPU. Compute units would then correspond to the streaming multiprocessors (SMs) inside the GPU, and processing elements correspond to individual streaming processors (SPs) inside each SM. Processors typically group processing elements into compute units for implementation efficiency through sharing instruction dispatch and memory resources, and increasing local inter-processor communication.

OpenCL's clEnqueueNDRangeKernel command enables a single kernel program to be initiated to operate in parallel across an N-dimensional data structure. Using a two-dimensional image as a example, the size of the image would be the NDRange, and each pixel is called a work-item that a copy of kernel running on a single processing element will operate on.

As we saw in the Platform Model section above, it is common for processors to group processing elements into compute units for execution efficiency. Therefore, when using the clEnqueueNDRangeKernel command, the program specifies a work-group size that represents groups of individual work-items in an NDRange that can be accommodated on a compute unit. Work-items in the same work-group are able to share local memory, synchronize more easily using work-group barriers, and cooperate more efficiently using work-group functions such as async_work_group_copy that are not available between work-items in separate work-groups.


OpenCL has a hierarchy of memory types:

  • Host memory - available to the host CPU
  • Global/Constant memory - available to all compute units in a compute device
  • Local memory - available to all the processing elements in a compute unit
  • Private memory - available to a single processing element

OpenCL memory management is explicit. None of the above memories are automatically synchronized and so the application explicitly moves data between memory types as needed.


在 openSUSE Tumbleweed 上安裝 OpenCL 的開發檔案:

sudo zypper in ocl-icd-devel opencl-headers clinfo

安裝後執行 clinfo 檢查目前的 OpenCL 裝置資訊。
如果沒有符合的實作,為了學習 OpenCL, 可以安裝 Portable Computing Language (pocl):

sudo zypper in pocl-devel

OpenCL Installable Client Driver (ICD) allows multiple OpenCL implementations to co-exist; also, it allows applications to select between these implementations at runtime.

Use the clGetPlatformIDs() and clGetPlatformInfo() functions to see the list of available OpenCL implementations, and select the one that is best for your requirements.

執行 clinfo 觀察目前的 OpenCL device 資訊。

下面的程式使用 clGetPlatformIDs 函式取得目前可用的 platform 數目 (編譯指令:gcc test.c `pkg-config --libs --cflags OpenCL`):

#include <stdio.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

int main( void ) {
    // OpenCL related declarations
    cl_int err;
    cl_uint num;

    err = clGetPlatformIDs( 0, NULL, &num );
    printf("%d\n", num);

}

下面是另外一個範例:

#include <stdlib.h>
#include <stdio.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

const char *kernel_code =
    "__kernel void vector_add(__global const int *A, __global const int *B, __global int *C) {"
    "    int i = get_global_id(0);"
    "    C[i] = A[i] + B[i];"
    "}";

int main( void ) {
    // OpenCL related declarations
    cl_int err;
    cl_platform_id platform;
    cl_device_id device;
    cl_context_properties props[3] = { CL_CONTEXT_PLATFORM, 0, 0 };
    cl_context ctx;
    cl_program program;
    cl_command_queue queue;
    cl_kernel kernel;
    int i;

    //
    const size_t N = 1024; // vector size
    size_t global_item_size = N; // Process the entire lists
    size_t local_item_size = 64; // Divide work items into groups of 64

    int *A, *B, *C;
    A = (int*) malloc(N * sizeof(*A));
    B = (int*) malloc(N * sizeof(*B));
    C = (int*) malloc(N * sizeof(*C));
    for (i=0; i<N; i++) {
        A[i] = i;
        B[i] = i + 1;
    }
    cl_mem d_A, d_B, d_C;

    /* Setup OpenCL environment. */
    err = clGetPlatformIDs( 1, &platform, NULL );
    err = clGetDeviceIDs( platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, NULL );

    props[1] = (cl_context_properties)platform;
    ctx = clCreateContext( props, 1, &device, NULL, NULL, &err );
    queue = clCreateCommandQueueWithProperties( ctx, device, 0, &err );
    program = clCreateProgramWithSource(ctx, 1, (const char **) &kernel_code, NULL, &err);
    err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    kernel = clCreateKernel(program, "vector_add", &err);

    // initialize buffer with data
    d_A = clCreateBuffer( ctx, CL_MEM_READ_ONLY, N*sizeof(*A), NULL, &err );
    d_B = clCreateBuffer( ctx, CL_MEM_READ_ONLY, N*sizeof(*B), NULL, &err );
    d_C = clCreateBuffer( ctx, CL_MEM_WRITE_ONLY, N*sizeof(*C), NULL, &err );

    err = clEnqueueWriteBuffer( queue, d_A, CL_TRUE, 0, N*sizeof(*A), A, 0, NULL, NULL );
    err = clEnqueueWriteBuffer( queue, d_B, CL_TRUE, 0, N*sizeof(*B), B, 0, NULL, NULL );

    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&d_A);
    err = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&d_B);
    err = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&d_C);

    err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
            &global_item_size, &local_item_size, 0, NULL, NULL);

    err = clFinish(queue);

    err = clEnqueueReadBuffer( queue, d_C, CL_TRUE, 0, N*sizeof(*C), C, 0, NULL, NULL );
    err = clFinish(queue);

    for(i = 0; i < N; i++)
        printf("%d + %d = %d\n", A[i], B[i], C[i]);

    err = clFlush(queue);
    err = clFinish(queue);

    /* Release OpenCL memory objects. */
    clReleaseMemObject( d_A );
    clReleaseMemObject( d_B );
    clReleaseMemObject( d_C );
    free(A);
    free(B);
    free(C);
    clReleaseKernel( kernel );
    clReleaseProgram( program );
    clReleaseCommandQueue( queue );
    clReleaseContext( ctx );

    return 0;
}

下面的程式是使用 stb_image 讀取圖檔,測試 image object 功能的程式。

#include <stdio.h>
#include <stdlib.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

const char *kernel_code =
    "__kernel void PixelAccess(__read_only image2d_t imageIn,__write_only image2d_t imageOut)"
    "{"
    "  sampler_t srcSampler = CLK_NORMALIZED_COORDS_FALSE | "
    "    CLK_ADDRESS_CLAMP_TO_EDGE |"
    "    CLK_FILTER_NEAREST;"
    "  int2 imageCoord = (int2) (get_global_id(0), get_global_id(1));"
    "  uint4 pixel = read_imageui(imageIn, srcSampler, imageCoord);"
    "  write_imageui (imageOut, imageCoord, pixel);"
    "}";


int main( int argc, char *argv[] ) {
    // OpenCL related declarations
    cl_int err;
    cl_platform_id platform;
    cl_device_id device;
    cl_context_properties props[3] = { CL_CONTEXT_PLATFORM, 0, 0 };
    cl_context ctx;
    cl_program program;
    cl_command_queue queue;
    cl_kernel kernel;
    int i;
    int width = 0, height = 0, channel = 0;
    unsigned char *data = NULL;
    const char *filename = NULL;

    if (argc < 2) {
        printf("Please give a filename.\n");
        return 0;
    } else if (argc == 2) {
        filename =  argv[1];
    }

    // Load image data
    data = stbi_load(filename, &width, &height, &channel, 0);
    if(!data) {
        fprintf(stderr, "Open image failed.\n");
        return 0;
    }

    cl_mem myClImageInBuffer;
    cl_mem myClImageOutBuffer;
    cl_sampler sampler;

    cl_image_format format;
    if (channel==4) {
        format.image_channel_order = CL_RGBA;
    } else {
        printf("Not supported image format.\n");
        return 0;
    }
    format.image_channel_data_type = CL_UNSIGNED_INT8;

    err = clGetPlatformIDs( 1, &platform, NULL );
    err = clGetDeviceIDs( platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, NULL );

    cl_bool imageSupport = CL_FALSE;
    clGetDeviceInfo(device, CL_DEVICE_IMAGE_SUPPORT, sizeof(cl_bool),
                    &imageSupport, NULL);

    if (imageSupport != CL_TRUE)
    {
        printf("OpenCL device does not support images.\n");
        return 1;
    }

    props[1] = (cl_context_properties)platform;
    ctx = clCreateContext( props, 1, &device, NULL, NULL, &err );
    queue = clCreateCommandQueueWithProperties( ctx, device, 0, &err );
    program = clCreateProgramWithSource(ctx, 1, (const char **) &kernel_code, NULL, &err);
    err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    kernel = clCreateKernel(program, "PixelAccess", &err);

    //
    // For OpenCL 1.2
    cl_image_desc clImageDesc;
    clImageDesc.image_type = CL_MEM_OBJECT_IMAGE2D;
    clImageDesc.image_width = width;
    clImageDesc.image_height = height;
    clImageDesc.image_row_pitch = 0;
    clImageDesc.image_slice_pitch = 0;
    clImageDesc.num_mip_levels = 0;
    clImageDesc.num_samples = 0;
    clImageDesc.buffer = NULL;

    myClImageInBuffer = clCreateImage(ctx, CL_MEM_READ_ONLY,
                            &format, &clImageDesc, NULL, &err);
    if (!myClImageInBuffer) {
        printf("Create myClImageInBuffer failed.\n");
    }

    myClImageOutBuffer = clCreateImage(ctx, CL_MEM_READ_WRITE,
                            &format, &clImageDesc, NULL, &err);
    if (!myClImageOutBuffer) {
        printf("Create myClImageOutBuffer failed.\n");
    }

    size_t origin[3] = {0, 0, 0};
    size_t region[3] = {width, height, 1};

    err = clEnqueueWriteImage(
            queue, myClImageInBuffer,
            CL_TRUE, origin, region,
            0,
            0, data,
            0, NULL, NULL);

    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *) &myClImageInBuffer);
    err = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *) &myClImageOutBuffer);

    size_t global_item_size[2] = {width, height};
    size_t local_item_size[2] = {1, 1};

    err = clEnqueueNDRangeKernel(queue, kernel, 2, NULL,
            global_item_size, local_item_size, 0, NULL, NULL);

    err = clFinish(queue);

    unsigned char *data2 = NULL;
    data2 = (unsigned char *) malloc(width * height  * channel);
    err = clEnqueueReadImage( queue,
          myClImageOutBuffer, CL_TRUE,
          origin, region,
          width * sizeof(unsigned char) * 4,
          0, data2,
          0, NULL, NULL);

    err = clFinish(queue);

    stbi_write_png("output.png", width, height, channel, data2, 0);

    free(data2);
    stbi_image_free(data);

    clReleaseMemObject( myClImageInBuffer );
    clReleaseMemObject( myClImageOutBuffer );
    clReleaseKernel( kernel );
    clReleaseProgram( program );
    clReleaseCommandQueue( queue );
    clReleaseContext( ctx );

    return 0;
}

參考連結

Audio library

簡介

如果是輕量的函式庫,可以分為二個部份:

  • 讀取 audio file 並且轉為 raw audio data
  • audio I/O

第一個部份,有二個函式庫可以考慮:

第二個部份,有下面的函式庫可以使用:

如果需要查詢與修改 audio 檔案的 meta-data,可以考慮 TagLib, 因為 TagLib 支援十分廣泛的音訊檔案格式。

libao

LibAO is developed under Xiph umbrella. Xiph is the organization who brought you Ogg/Vorbis, FLAC, Theora and currently they are hammering together next generation video codec Daala.

Opus-audio codec standard is also Xiph project. LibAO rised from Xiph’s need multi-platform audio output library for Vorbis-audio codec.

The libao API makes a distinction between drivers and devices. A driver is a set of functions that allow audio to be played on a particular platform (i.e. Solaris, ESD, etc.). A device is a particular output target that uses a driver. In addition, libao distinguishes between live output drivers, which write audio to playback devices (sound cards, etc.), and file output drivers, which write audio to disk in a particular format.

To use libao in your program, you need to follow these steps:

  • Include the <ao/ao.h> header into your program.
  • Call ao_initialize() to initialize the library. This loads the plugins from disk, reads the libao configuration files, and identifies an appropriate default output driver if none is specified in the configuration files.
  • Call ao_default_driver_id() to get the ID number of the default output driver. This may not be successful if no audio hardware is available, it is in use, or is not in the "standard" configuration. If you want to specify a particular output driver, you may call ao_driver_id() with a string corresponding to the short name of the device (i.e. "oss", "wav", etc.) instead.
  • If you are using the default device, no extra options are needed. However, if you wish to to pass special options to the driver, you will need to:
    • Create an option list pointer of type (ao_option *) and initialize it to NULL.
    • Through successive calls to ao_append_option(), add any driver-specific options you need. Note that the options take the form of key/value pairs where supported keys are listed in the driver documentation.
  • Call ao_open_live() and save the returned device pointer. If you are using a file output driver, you will need to call ao_open_file() instead.
  • Call ao_play() to output each block of audio.
  • Call ao_close() to close the device. Note that this will automatically free the memory that was allocated for the device. Do not attempt to free the device pointer yourself!
  • Call ao_shutdown() to close the library.
下面是一個使用 libao 與 libsndfile 的範例:
#include <ao/ao.h>
#include <signal.h>
#include <sndfile.h>

#define BUFFER_SIZE 8192

int cancel_playback;

void on_cancel_playback(int sig) {
    if (sig != SIGINT) {
        return;
    }

    cancel_playback = 1;
    exit(0);
}

static void clean(ao_device *device, SNDFILE *file) {
    ao_close(device);
    sf_close(file);
    ao_shutdown();
}

int play(const char *filename) {
    ao_device *device;
    ao_sample_format format;
    SF_INFO sfinfo;

    int default_driver;

    short *buffer;

    signal(SIGINT, on_cancel_playback);

    SNDFILE *file = sf_open(filename, SFM_READ, &sfinfo);
    if (file == NULL)
        return -1;

    printf("Samples: %d\n", sfinfo.frames);
    printf("Sample rate: %d\n", sfinfo.samplerate);
    printf("Channels: %d\n", sfinfo.channels);

    ao_initialize();

    default_driver = ao_default_driver_id();

    switch (sfinfo.format & SF_FORMAT_SUBMASK) {
    case SF_FORMAT_PCM_16:
        format.bits = 16;
        break;
    case SF_FORMAT_PCM_24:
        format.bits = 24;
        break;
    case SF_FORMAT_PCM_32:
        format.bits = 32;
        break;
    case SF_FORMAT_PCM_S8:
        format.bits = 8;
        break;
    case SF_FORMAT_PCM_U8:
        format.bits = 8;
        break;
    default:
        format.bits = 16;
        break;
    }

    format.channels = sfinfo.channels;
    format.rate = sfinfo.samplerate;
    format.byte_format = AO_FMT_NATIVE;
    format.matrix = 0;

    device = ao_open_live(default_driver, &format, NULL);

    if (device == NULL) {
        fprintf(stderr, "Error opening device.\n");
        return 1;
    }

    buffer = calloc(BUFFER_SIZE, sizeof(short));

    while (1) {
        int read = sf_read_short(file, buffer, BUFFER_SIZE);

        if (read <= 0) {
            break;
        }

        if (ao_play(device, (char *)buffer, (uint_32)(read * sizeof(short))) ==
            0) {
            printf("ao_play: failed.\n");
            clean(device, file);
            break;
        }

        if (cancel_playback) {
            clean(device, file);
            break;
        }
    }

    clean(device, file);

    return 0;
}

int main(int argc, char *argv[]) {
    if (argc > 1) {
        play(argv[1]);
    }
}
編譯方式:
gcc output.c -lsndfile -lao -o output

再來是 libao 與 libmpg123 的例子:
#include <ao/ao.h>
#include <mpg123.h>

#define BITS 8

int main(int argc, char *argv[]) {
    mpg123_handle *mh;
    unsigned char *buffer;
    size_t buffer_size;
    size_t done;
    int err;

    int driver;
    ao_device *dev;

    ao_sample_format format;
    int channels, encoding;
    long rate;

    if (argc < 2)
        exit(0);

    /* initializations */
    ao_initialize();
    driver = ao_default_driver_id();
    mpg123_init();
    mh = mpg123_new(NULL, &err);
    buffer_size = mpg123_outblock(mh);
    buffer = (unsigned char *)malloc(buffer_size * sizeof(unsigned char));

    /* open the file and get the decoding format */
    mpg123_open(mh, argv[1]);
    mpg123_getformat(mh, &rate, &channels, &encoding);

    /* set the output format and open the output device */
    format.bits = mpg123_encsize(encoding) * BITS;
    format.rate = rate;
    format.channels = channels;
    format.byte_format = AO_FMT_NATIVE;
    format.matrix = 0;
    dev = ao_open_live(driver, &format, NULL);

    /* decode and play */
    while (mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK)
        ao_play(dev, buffer, done);

    /* clean up */
    free(buffer);
    ao_close(dev);
    mpg123_close(mh);
    mpg123_delete(mh);
    mpg123_exit();
    ao_shutdown();

    return 0;
}

OpenAL

OpenAL (Open Audio Library) is a cross-platform audio application programming interface (API). It is designed for efficient rendering of multichannel three-dimensional positional audio, for creation of a virtual 3D world of sound. Its API style and conventions deliberately resemble those of OpenGL.

The application programmer can specify the location, the speed and the direction of the sources of sounds and of the listener.

Objects

Since OpenAL is about audio, it also introduces new concepts, in particular:
  • the listener object
  • the source object
  • the buffer object

Each of these different objects have properties which can be set with their respective API (al*Object*).

  • context: 要播放聲音的地方,可以想成OpenGL裡面的Window
  • listener: OpenAL 支援3D音效,所以在這裡設定聽者的資料
  • sources: 聲源的資訊
  • buffer: 負責聲源的內容

The very first thing to do is to open a handle to a device. This is done like this:

ALCdevice *device;

device = alcOpenDevice(NULL);
if (!device)
        // handle errors

Prior to attempting an enumeration, Open AL provides an extension querying mechanism which allows you to know whether the runtime Open AL implementation supports a specific extension. In our case, we want to check whether Open AL supports enumerating devices:

ALboolean enumeration;

enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
if (enumeration == AL_FALSE)
        // enumeration not supported
else
        // enumeration supported

If the enumeration extension is supported, we can procede with listing the audio devices. If the enumeration is not supported listing audio devices only returns the default device, which is expected in order not to break any application.

static void list_audio_devices(const ALCchar *devices)
{
        const ALCchar *device = devices, *next = devices + 1;
        size_t len = 0;

        fprintf(stdout, "Devices list:\n");
        fprintf(stdout, "----------\n");
        while (device && *device != '\0' && next && *next != '\0') {
                fprintf(stdout, "%s\n", device);
                len = strlen(device);
                device += (len + 1);
                next += (len + 2);
        }
        fprintf(stdout, "----------\n");
}

list_audio_devices(alcGetString(NULL, ALC_DEVICE_SPECIFIER));

Passsing NULL to alcGetString() indicates that we do not want the device specifier of a particular device, but all of them.


In order to render an audio scene, we need to create and initialize a context for this. We do this by the following calls:

ALCcontext *context;

context = alcCreateContext(device, NULL);
if (!alcMakeContextCurrent(context))
        // failed to make context current
// test for errors here using alGetError();

There is nothing specific for our context, so NULL is specified as argument.

Since there is a default listener, we do not need to explicitely create one because it is already present in our scene. If we want to define some of our listener properties however, we can proceed like this:

ALfloat listenerOri[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f };

alListener3f(AL_POSITION, 0, 0, 1.0f);
// check for errors
alListener3f(AL_VELOCITY, 0, 0, 0);
// check for errors
alListenerfv(AL_ORIENTATION, listenerOri);
// check for errors

In order to playback audio, we must create an audio source objet, this source is actually the "origin" of the audio sound. And as such must be defined in the audio scene. If you combine audio with graphics, most likely quite a lot of your graphics objects will also include an audio source object.

Note that you hold a reference (id) to a source object, you don’t manipulate the source object directly.

ALuint source;


alGenSources((ALuint)1, &source);
// check for errors

alSourcef(source, AL_PITCH, 1);
// check for errors
alSourcef(source, AL_GAIN, 1);
// check for errors
alSource3f(source, AL_POSITION, 0, 0, 0);
// check for errors
alSource3f(source, AL_VELOCITY, 0, 0, 0);
// check for errors
alSourcei(source, AL_LOOPING, AL_FALSE);
// check for errros

The buffer object is the object actually holding the raw audio stream, alone a buffer does not do much but occupying memory, so we will see later on what to do with it. Just like sources, we hold a reference to the buffer object.

ALuint buffer;

alGenBuffers((ALuint)1, &buffer);
// check for errors

下面就是載入 Audio raw data 以後的使用範例:

static inline ALenum to_al_format(short channels, short samples)
{
        bool stereo = (channels > 1);

        switch (samples) {
        case 16:
                if (stereo)
                        return AL_FORMAT_STEREO16;
                else
                        return AL_FORMAT_MONO16;
        case 8:
                if (stereo)
                        return AL_FORMAT_STEREO8;
                else
                        return AL_FORMAT_MONO8;
        default:
                return -1;
        }
}

alBufferData(buffer, to_al_format(wave->channels, wave->bitsPerSample),
                bufferData, wave->dataSize, wave->sampleRate);
// check for errors

In order to actually output something to the playback device, we need to bind the source with its buffer. Obviously you can bind the same buffer to several sources and mix different buffers to the same source. Binding is done like this:

alSourcei(source, AL_BUFFER, buffer);
// check for errors

We now have everything ready to start playing our source.

alSourcePlay(source);
// check for errors

alGetSourcei(source, AL_SOURCE_STATE, &source_state);
// check for errors
while (source_state == AL_PLAYING) {
        alGetSourcei(source, AL_SOURCE_STATE, &source_state);
        // check for errors
}

Obviously each and every single object "generated" must be freed, the following does this for us:

alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
device = alcGetContextsDevice(context);
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);

問題與解法

Fix jack server is not running or cannot be started message (for openal-soft)

如果你在 OpenSUSE Leap 15.0 使用 OpenAL 時發現會出現 jack server is not running or cannot be started 的訊息, 這是因為 openal-soft 支援 jack,所以會嘗試進行初始化的動作。

解決的方法是在 /etc/openal 下新增加 alsoft.conf,並且加入下面的內容:
[general]
drivers = -jack,

Libao debug message

在 OpenSUSE Tumbleweed 中預設的 driver 是 PulseAudio,但是仍然會嘗試載入 ALSA driver 並且印出錯誤訊息。 如果遇到這個情況想要停止印出錯誤訊息,修改 /etc/libao.conf,加入一行 quiet

default_driver=pulse
quiet

參考連結

OpenCV

簡介

OpenCV 的全稱是 Open Source Computer Vision Library, 是一個跨平台的電腦視覺庫,由英特爾公司發起並參與開發。

在 openSUSE Tumbleweed 安裝:

sudo zypper in opencv-devel

安裝完成以後,接下來進行是否安裝成功的測試。下面是一個顯示圖片的 display.cpp

#include <stdio.h>
#include <opencv2/opencv.hpp>

int main(int argc, char* argv[]) {

  if ( argc != 2 ) {
    printf("usage: display <File_Path>\n");
    return -1;
  }

  cv::Mat image = cv::imread( argv[1], 1 );

  if ( !image.data ) {
    printf("No image data\n");
    return -1;
  }

  cv::namedWindow("Display Image", cv::WINDOW_AUTOSIZE);
  cv::imshow("Display Image", image);
  cv::waitKey(0);
  cv::destroyWindow("Display Image");

  return 0;
}

如果直接編譯,使用:

g++ display.cpp -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lopencv_highgui -o display

或者是使用 pkg-config,假設是 OpenCV 4,那麼使用下列的指令:

g++ display.cpp `pkg-config opencv4 --libs` -o display

或者使用 CMake。這個例子所需要的 CMake 設定檔 CMakeLists.txt 內容如下:

cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
add_executable( display display.cpp )
target_link_libraries( display ${OpenCV_LIBS} )

接下來使用下列的指令編譯:

cmake .
make

要注意的是,OpenCL 預設使用的 color model 為 BGR,而一般流行使用的 color model 為 RGB。

還有的 color model 為 LAB:

  • L – Lightness ( Intensity ).
  • a – color component ranging from Green to Magenta.
  • b – color component ranging from Blue to Yellow.
cv::cvtColor(bright, brightLAB, cv::COLOR_BGR2LAB);

The YCrCb color space is derived from the RGB color space and has the following three compoenents.

  • Y – Luminance or Luma component obtained from RGB after gamma correction.
  • Cr = R – Y ( how far is the red component from Luma ).
  • Cb = B – Y ( how far is the blue component from Luma ).
cv::cvtColor(bright, brightYCB, cv::COLOR_BGR2YCrCb);

The HSV color space has the following three components

  • H – Hue ( Dominant Wavelength ).
  • S – Saturation ( Purity / shades of the color ).
  • V – Value ( Intensity ).
cv::cvtColor(bright, brightHSV, cv::COLOR_BGR2HSV);

要注意的是,要觀察圖片的軌跡或者是偵測線條(edge detection)時,很常用的手法是將圖轉為灰階,然後再使用各種演算法取得結果。


再來是建立一個空白的 image 並且顯示:

#include <opencv2/opencv.hpp>
#include <string>

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
  Mat image(600, 800, CV_8UC3, Scalar(200, 150, 130));

  string windowName = "Window with Blank Image";

  namedWindow(windowName);
  imshow(windowName, image);
  waitKey(0);
  destroyWindow(windowName);

  return 0;
}

再來是讀取 Vedio 檔案並且播放的範例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a vedio file." << endl;
        return 0;
    }

    VideoCapture vid_capture(argv[1]);

    if (!vid_capture.isOpened())
    {
        cout << "Error opening video stream or file" << endl;
    }

    else
    {
        int fps = vid_capture.get(CAP_PROP_FPS);
        cout << "Frames per second :" << fps;

        int frame_count = vid_capture.get(CAP_PROP_FRAME_COUNT);
        cout << "  Frame count :" << frame_count;
    }

    while (vid_capture.isOpened())
    {
        Mat frame;

        bool isSuccess = vid_capture.read(frame);

        if (isSuccess == true)
        {
            imshow("Frame", frame);
        }

        if (isSuccess == false)
        {
            cout << "Video camera is disconnected" << endl;
            break;
        }

        int key = waitKey(20);
        if (key == 'q')
        {
            cout << "q key is pressed by the user. Stopping the video" << endl;
            break;
        }
    }

    vid_capture.release();
    destroyAllWindows();
    return 0;
}

下面是 image resize 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    imshow("Original Image", image);

    int down_width = 300;
    int down_height = 200;
    Mat resized_down;
    resize(image, resized_down, Size(down_width, down_height), INTER_LINEAR);

    int up_width = 600;
    int up_height = 400;
    Mat resized_up;
    resize(image, resized_up, Size(up_width, up_height), INTER_LINEAR);

    imshow("Resized Down by defining height and width", resized_down);
    waitKey();
    imshow("Resized Up image by defining height and width", resized_up);
    waitKey();

    destroyAllWindows();
    return 0;
}

下面是 crop image 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);
    cout << "Width : " << img.size().width << endl;
    cout << "Height: " << img.size().height << endl;
    cout << "Channels: :" << img.channels() << endl;

    int firstx = img.size().width / 4;
    int firsty = img.size().height / 4;
    int secondx = img.size().width / 4 * 3;
    int secondy = img.size().height / 4 * 3;

    Mat cropped_image = img(Range(firsty, secondy), Range(firstx, secondx));

    imshow(" Original Image", img);
    imshow("Cropped Image", cropped_image);

    //Save the cropped Image
    imwrite("CroppedImage.jpg", cropped_image);

    waitKey(0);
    destroyAllWindows();
    return 0;
}

下面是 image rotation 的例子:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    imshow("image", image);
    waitKey(0);
    double angle = 45;

    Point2f center((image.cols - 1) / 2.0, (image.rows - 1) / 2.0);
    Mat rotation_matix = getRotationMatrix2D(center, angle, 1.0);

    Mat rotated_image;
    // rotate the image using warpAffine
    warpAffine(image, rotated_image, rotation_matix, image.size());
    imshow("Rotated image", rotated_image);
    waitKey(0);
    imwrite("rotated_im.jpg", rotated_image);

    return 0;
}

當我們對圖片加上某個常數,就是增加亮度或者是減少亮度。例如下面的式子是增加亮度:

new_image (i, j) = image(i, j) + c

所以我們可以這樣寫:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);

    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }

    Mat imageBrighnessHigh50;
    image.convertTo(imageBrighnessHigh50, -1, 1, 50); //increase the brightness by 50

    Mat imageBrighnessHigh100;
    image.convertTo(imageBrighnessHigh100, -1, 1, 100);

    Mat imageBrighnessLow50;
    image.convertTo(imageBrighnessLow50, -1, 1, -50); //decrease the brightness by 50

    Mat imageBrighnessLow100;
    image.convertTo(imageBrighnessLow100, -1, 1, -100);

    String windowNameOriginalImage = "Original Image";
    String windowNameBrightnessHigh50 = "Brightness Increased by 50";
    String windowNameWithBrightnessHigh100 = "Brightness Increased by 100";
    String windowNameBrightnessLow50 = "Brightness Decreased by 50";
    String windowNameBrightnessLow100 = "Brightness Decreased by 100";

    namedWindow(windowNameOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessHigh50, WINDOW_NORMAL);
    namedWindow(windowNameWithBrightnessHigh100, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessLow50, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessLow100, WINDOW_NORMAL);

    imshow(windowNameOriginalImage, image);
    imshow(windowNameBrightnessHigh50, imageBrighnessHigh50);
    imshow(windowNameWithBrightnessHigh100, imageBrighnessHigh100);
    imshow(windowNameBrightnessLow50, imageBrighnessLow50);
    imshow(windowNameBrightnessLow100, imageBrighnessLow100);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

對於 Contrast 來說,就是乘以某個數(> 1 就是增加對比,1 > c > 0 就是減少對比)。

new_image (i, j) = image(i, j) * c

所以我們可以這樣寫:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        cin.get();
        return 0;
    }

    Mat imageContrastHigh2;
    image.convertTo(imageContrastHigh2, -1, 2, 0); //increase the contrast by 2

    Mat imageContrastHigh4;
    image.convertTo(imageContrastHigh4, -1, 4, 0);

    Mat imageContrastLow0_5;
    image.convertTo(imageContrastLow0_5, -1, 0.5, 0);

    Mat imageContrastLow0_25;
    image.convertTo(imageContrastLow0_25, -1, 0.25, 0);

    String windowNameOriginalImage = "Original Image";
    String windowNameContrastHigh2 = "Contrast Increased by 2";
    String windowNameContrastHigh4 = "Contrast Increased by 4";
    String windowNameContrastLow0_5 = "Contrast Decreased by 0.5";
    String windowNameContrastLow0_25 = "Contrast Decreased by 0.25";

    namedWindow(windowNameOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameContrastHigh2, WINDOW_NORMAL);
    namedWindow(windowNameContrastHigh4, WINDOW_NORMAL);
    namedWindow(windowNameContrastLow0_5, WINDOW_NORMAL);
    namedWindow(windowNameContrastLow0_25, WINDOW_NORMAL);

    imshow(windowNameOriginalImage, image);
    imshow(windowNameContrastHigh2, imageContrastHigh2);
    imshow(windowNameContrastHigh4, imageContrastHigh4);
    imshow(windowNameContrastLow0_5, imageContrastLow0_5);
    imshow(windowNameContrastLow0_25, imageContrastLow0_25);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

OpenCV 也具有繪圖(例如畫線、畫圓,以及加上文字等)的能力。下面是一個例子。

// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);
    imshow("Original Image", img);
    waitKey();
    if (img.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat imageChanged = img.clone();
    Point pointA(350, 370);
    Point pointB(500, 370);
    line(imageChanged, pointA, pointB, Scalar(255, 255, 0), 3, 8, 0);

    Point circle_center(420, 130);
    int radius = 150;
    circle(imageChanged, circle_center, radius, Scalar(0, 0, 255), 3, 8, 0);

    putText(imageChanged, "The best!", Point(50, 500), FONT_HERSHEY_COMPLEX,
            1.5, Scalar(250, 225, 100));

    imshow("Changed Image", imageChanged);
    waitKey();

    // save image
    imwrite("ChangedImage.jpg", imageChanged);
    destroyAllWindows();

    return 0;
}

下面是輸出的結果:


OpenCV 提供了函數 cv::threshold(),可以用來執行圖像二值化的工作。 圖像的二值化就是將圖像上的像素點的灰度值設置為 0 或 255,這樣將使整個圖像呈現出明顯的黑白效果。 在圖像處理中,二值圖像佔有非常重要的地位,圖像的二值化使圖像的數據大為減少,從而凸顯出目標的輪廓。

#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat src = imread(argv[1], IMREAD_GRAYSCALE);
    Mat dst;


    // Basic threhold example
    threshold(src,dst,0, 255, THRESH_BINARY);
    imwrite("opencv-threshold-example.jpg", dst);

    // Thresholding with maxval set to 128
    threshold(src, dst, 0, 128, THRESH_BINARY);
    imwrite("opencv-thresh-binary-maxval.jpg", dst);

    // Thresholding with threshold value set 127
    threshold(src,dst,127,255, THRESH_BINARY);
    imwrite("opencv-thresh-binary.jpg", dst);

    // Thresholding using THRESH_BINARY_INV
    threshold(src,dst,127,255, THRESH_BINARY_INV);
    imwrite("opencv-thresh-binary-inv.jpg", dst);

    // Thresholding using THRESH_TRUNC
    threshold(src,dst,127,255, THRESH_TRUNC);
    imwrite("opencv-thresh-trunc.jpg", dst);

    // Thresholding using THRESH_TOZERO
    threshold(src,dst,127,255, THRESH_TOZERO);
    imwrite("opencv-thresh-tozero.jpg", dst);

    // Thresholding using THRESH_TOZERO_INV
    threshold(src,dst,127,255, THRESH_TOZERO_INV);
    imwrite("opencv-thresh-to-zero-inv.jpg", dst);
}

Histogram of an image is the graphical representation of the distribution of intensities of pixels. It provides an estimate of where pixel values are concentrated and whether there are unusual deviations.

#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);

    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }

    cvtColor(image, image, COLOR_BGR2GRAY);

    Mat hist_equalized_image;
    equalizeHist(image, hist_equalized_image);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0);
    destroyAllWindows();
}

下面是針對彩色的範例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat hist_equalized_image;
    cvtColor(image, hist_equalized_image, COLOR_BGR2YCrCb);

    vector<Mat> vec_channels;
    split(hist_equalized_image, vec_channels);

    equalizeHist(vec_channels[0], vec_channels[0]);

    merge(vec_channels, hist_equalized_image);

    cvtColor(hist_equalized_image, hist_equalized_image, COLOR_YCrCb2BGR);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Color Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0); // Wait for any keystroke in any one of the windows

    destroyAllWindows(); //Destroy all opened windows

    return 0;
}

equalizeHist 在一些情況下會製造雜訊,也可以使用 CLAHE 來作為改進的演算法:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat hist_equalized_image;
    cvtColor(image, hist_equalized_image, COLOR_BGR2YCrCb);

    vector<Mat> vec_channels;
    Ptr<CLAHE> clahe = createCLAHE();
    split(hist_equalized_image, vec_channels);

    clahe->apply(vec_channels[0], vec_channels[0]);

    merge(vec_channels, hist_equalized_image);

    cvtColor(hist_equalized_image, hist_equalized_image, COLOR_YCrCb2BGR);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Color Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0); // Wait for any keystroke in any one of the windows

    destroyAllWindows(); //Destroy all opened windows

    return 0;
}

Edge detection is an image-processing technique, which is used to identify the boundaries (edges) of objects, or regions within an image.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);

    if (img.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }
    imshow("original Image", img);
    waitKey(0);

    Mat img_gray;
    cvtColor(img, img_gray, COLOR_BGR2GRAY);
    Mat img_blur;
    GaussianBlur(img_gray, img_blur, Size(3,3), 0);

    Mat sobelx, sobely, sobelxy;
    Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);
    Sobel(img_blur, sobely, CV_64F, 0, 1, 5);
    Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);
    imshow("Sobel X", sobelx);
    waitKey(0);
    imshow("Sobel Y", sobely);
    waitKey(0);
    imshow("Sobel XY using Sobel() function", sobelxy);
    waitKey(0);

    Mat edges;
    Canny(img_blur, edges, 100, 200, 3, false);
    imshow("Canny edge detection", edges);
    waitKey(0);

    destroyAllWindows();
    return 0;
}

The image filtering is a neighborhood operation in which the value of any given pixel in the output image is determined by applying a certain algorithm to the pixel values ​​in the vicinity of the corresponding input pixel.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat image_blurred_with_3x3_kernel;
    blur(image, image_blurred_with_3x3_kernel, Size(3, 3));

    Mat image_blurred_with_5x5_kernel;
    blur(image, image_blurred_with_5x5_kernel, Size(5, 5));

    String window_name = "Display";
    String window_name_blurred_with_3x3_kernel = "Display Blurred with 3 x 3 Kernel";
    String window_name_blurred_with_5x5_kernel = "Display Blurred with 5 x 5 Kernel";

    namedWindow(window_name);
    namedWindow(window_name_blurred_with_3x3_kernel);
    namedWindow(window_name_blurred_with_5x5_kernel);

    imshow(window_name, image);
    imshow(window_name_blurred_with_3x3_kernel, image_blurred_with_3x3_kernel);
    imshow(window_name_blurred_with_5x5_kernel, image_blurred_with_5x5_kernel);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

下面是另外的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat kernel1 = (Mat_<double>(3, 3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
    Mat identity;
    filter2D(image, identity, -1, kernel1, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Identity", identity);
    waitKey();
    imwrite("identity.jpg", identity);
    destroyAllWindows();

    Mat kernel2 = Mat::ones(5, 5, CV_64F);
    kernel2 = kernel2 / 25;
    Mat img;
    filter2D(image, img, -1, kernel2, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Kernel blur", img);
    imwrite("blur_kernel.jpg", img);
    waitKey();
    destroyAllWindows();
}

下面是 mouse callback function 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void CallBackFunc(int event, int x, int y, int flags, void *userdata)
{
    if (event == EVENT_LBUTTONDOWN)
    {
        cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_RBUTTONDOWN)
    {
        cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_MBUTTONDOWN)
    {
        cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_MOUSEMOVE)
    {
        cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl;
    }
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    namedWindow("My Window", 1);
    setMouseCallback("My Window", CallBackFunc, NULL);
    imshow("My Window", image);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

使用 SimpleBlobDetector 執行 Blob detection:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    Mat im = imread("blob.jpg", IMREAD_GRAYSCALE);

    // Setup SimpleBlobDetector parameters.
    SimpleBlobDetector::Params params;

    // Change thresholds
    params.minThreshold = 10;
    params.maxThreshold = 200;

    // Filter by Area.
    //params.filterByArea = true;
    //params.minArea = 1500;

    // Filter by Circularity
    //params.filterByCircularity = true;
    //params.minCircularity = 0.1;

    // Filter by Convexity
    params.filterByConvexity = true;
    params.minConvexity = 0.5;

    // Filter by Inertia
    //params.filterByInertia = true;
    //params.minInertiaRatio = 0.01;

    Ptr<SimpleBlobDetector> detector = SimpleBlobDetector::create(params);

    std::vector<KeyPoint> keypoints;
    detector->detect(im, keypoints);

    Mat im_with_keypoints;
    drawKeypoints(im, keypoints, im_with_keypoints, Scalar(0, 0, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

    namedWindow("keypoints", 1);
    imshow("keypoints", im_with_keypoints);
    waitKey(0);
}

下面是針對單一圖片的 Face Detection 例子。如果要改寫成使用 camera,那就是你拿到 frame 以後,使用同樣的方式去偵測。

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

CascadeClassifier face_cascade;

int main( int argc, const char** argv )
{
    if ( argc != 2 ) {
        printf("usage: display <File_Path>\n");
        return -1;
    }

    /*
     * From https://github.com/opencv/opencv/tree/master/data/haarcascades
     */
    string face_cascade_name("haarcascade_frontalface_default.xml");

    if( !face_cascade.load( face_cascade_name ) )
    {
        cout << "--(!)Error loading face cascade\n";
        return -1;
    };

    cv::Mat frame = cv::imread( argv[1], 1 );
    if ( !frame.data ) {
        printf("No image data\n");
        return -1;
    }
    cv::namedWindow("Display", cv::WINDOW_AUTOSIZE);

    Mat frame_gray;
    cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
    equalizeHist( frame_gray, frame_gray );

    std::vector<Rect> faces;
    face_cascade.detectMultiScale( frame_gray, faces );
    cout << "Find: " << faces.size() << endl;
    for (auto&& feature : faces) {
        cv::rectangle(frame, feature, cv::Scalar(0, 0, 255), 2);
    }

    imshow( "Display", frame );
    waitKey(0);
    return 0;
}

參考連結

2024/03/10

SQLite

SQLite 是一個很小的 C 語言函式庫。 這個函式庫本身就完全包含資料庫引擎的功能,而且可以嵌入至其他程式中。

SQLite 支援 In-Memory 與 Disk-based 等形式的使用方式, 下面是一個使用 C API 來開啟一個 In-Memory Database 的例子(使用 ":memory:" 檔名):

rc = sqlite3_open(":memory:", &db);

列舉資料庫中的表格

From within a C/C++ program (or a script using Tcl/Ruby/Perl/Python bindings) you can get access to table and index names by doing a SELECT on a special table named "SQLITE_MASTER". Every SQLite database has an SQLITE_MASTER table that defines the schema for the database. The SQLITE_MASTER table looks like this:

CREATE TABLE sqlite_master (
  type TEXT,
  name TEXT,
  tbl_name TEXT,
  rootpage INTEGER,
  sql TEXT
);
所以我們可以使用下列的方式來列舉資料庫中的表格:
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;

Auto-increment column

下面二種寫法都會建立 autoincrementing column; the AUTOINCREMENT keyword only prevents reusing deleted values.
CREATE TABLE t1 (col1 INTEGER PRIMARY KEY);
CREATE TABLE t1 (col1 INTEGER PRIMARY KEY AUTOINCREMENT);

UPSERT

對於 SQLite 而言,有幾種方式提供 UPSERT 的語法。SQLite 提供了 REPLACE INTO 的語法,同時提供了與 PostgreSQL 類似的 INSERT INTO ... ON CONFLICT clause 的方式來處理。

C API

使用 C 語言開發 Sqlite 的程式非常容易,下面是一個簡單的例子:
#include <sqlite3.h>
#include <stdio.h>

static char *createsql = "CREATE TABLE Contact("
                         "ID INTEGER PRIMARY KEY,"
                         "Name VARCHAR(10),"
                         "PhoneNumber VARCHAR(10));";

static char *insertsql =
    "INSERT INTO Contact VALUES(1, 'Richard', '09990123456');";

static char *querysql = "SELECT * FROM Contact;";

void main(void) {
    int rows, cols;
    sqlite3 *db;
    char *errMsg = NULL;
    char **result;
    int i, j;

    if (sqlite3_open_v2("example.db3", &db,
                        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) {
        return;
    }

    sqlite3_exec(db, createsql, 0, 0, &errMsg);
    sqlite3_exec(db, insertsql, 0, 0, &errMsg);

    printf("%d\n", sqlite3_last_insert_rowid(db));

    sqlite3_get_table(db, querysql, &result, &rows, &cols, &errMsg);
    for (i = 0; i <= rows; i++) {
        for (j = 0; j < cols; j++) {
            printf("%s\t", result[i * cols + j]);
        }
        printf("\n");
    }

    sqlite3_free_table(result);
    sqlite3_close(db);
}

編譯時只要連結 sqlite 函式庫:

gcc  -lsqlite3  -o sqlite_ex sqlite_ex.c

2024/02/21

ODBC

ODBC(Open Database Connectivity)提供了一組標準的 API 來訪問資料庫管理系統(DBMS)。 這些 API 利用 SQL 來完成其大部分任務。 ODBC 是針對 C 的 API,不過很多的程式語言都有相關的 bindings。如果想使用 C 來寫 ODBC 程式, 可以從 ODBC from C Tutorial Part 1 開始學習如何撰寫。

The ODBC architecture has four components:

  • Application Performs processing and calls ODBC functions to submit SQL statements and retrieve results.

  • Driver Manager Loads and unloads drivers on behalf of an application. Processes ODBC function calls or passes them to a driver.

  • Driver Processes ODBC function calls, submits SQL requests to a specific data source, and returns results to the application. If necessary, the driver modifies an application's request so that the request conforms to syntax supported by the associated DBMS.

  • Data Source Consists of the data the user wants to access and its associated operating system, DBMS, and network platform (if any) used to access the DBMS.

In ODBC there are four main handle types and you will need to know at least three to do anything useful:

  • SQLHENV - environment handle

    This is the first handle you will need as everything else is effectively in the environment. Once you have an environment handle you can define the version of ODBC you require, enable connection pooling and allocate connection handles with SQLSetEnvAttr and SQLAllocHandle.

  • SQLHDBC - connection handle

    You need one connection handle for each data source you are going to connect to. Like environment handles, connection handles have attributes which you can retrieve and set with SQLSetConnectAttr and SQLGetConnectAttr.

  • SQLHSTMT - statement handle

    Once you have a connection handle and have connected to a data source you allocate statement handles to execute SQL or retrieve meta data. As with the other handles you can set and get statement attributes with SQLSetStmtAttr and SQLGetStmtAttr.

  • SQLHDESC - descriptor handle

    Descriptor handles are rarely used by applications even though they are very useful for more complex operations. Descriptor handles will be covered in later tutorials.

如果要查詢目前 unixODBC 的版本,使用下列的指令:

odbcinst --version

Listing Installed Drivers and Data Sources

如果是使用 unixODBC,可以使用下列命令列出已安裝的資料來源:

odbcinst -q -s

如果自己寫一個程式列出來已安裝的資料來源:

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>

int main() {
    SQLHENV env = NULL;
    char driver[256];
    char attr[256];
    SQLSMALLINT driver_ret;
    SQLSMALLINT attr_ret;
    SQLUSMALLINT direction;
    SQLRETURN ret;

    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    if (ret != SQL_SUCCESS) {
        printf("SQLAllocHandle failed.\n");
    }

    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);

    direction = SQL_FETCH_FIRST;
    while (SQL_SUCCEEDED(ret = SQLDrivers(env, direction, driver,
                                          sizeof(driver), &driver_ret, attr,
                                          sizeof(attr), &attr_ret))) {
        direction = SQL_FETCH_NEXT;
        printf("%s - %s\n", driver, attr);
        if (ret == SQL_SUCCESS_WITH_INFO)
            printf("\tdata truncation\n");
    }

    if (env != NULL) {
        SQLFreeHandle(SQL_HANDLE_ENV, env);
    }
    return 0;
}

在 Windows 平台,需要連結 odbc32 library,使用 UnixODBC 平台則需要連結 libodbc (-lodbc) 才行。

接下來使用 PostgreSQL ODBC driver 測試,測試是否能夠正確連接到資料庫。

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>

void extract_error(char *fn, SQLHANDLE handle, SQLSMALLINT type) {
    SQLINTEGER i = 0;
    SQLINTEGER native;
    SQLCHAR state[7];
    SQLCHAR text[256];
    SQLSMALLINT len;
    SQLRETURN ret;

    fprintf(stderr,
            "\n"
            "The driver reported the following diagnostics whilst running "
            "%s\n\n",
            fn);

    do {
        ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
                            sizeof(text), &len);
        if (SQL_SUCCEEDED(ret))
            printf("%s:%ld:%ld:%s\n", state, i, native, text);
    } while (ret == SQL_SUCCESS);
}

int main() {
    SQLHENV env = NULL;
    SQLHDBC dbc = NULL;
    SQLRETURN ret;
    SQLCHAR outstr[1024];
    SQLSMALLINT outstrlen;

    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return(1);
    }

    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
    ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return(1);
    }

    ret = SQLDriverConnect(dbc, NULL, "DSN=PostgreSQL;", SQL_NTS, outstr,
                           sizeof(outstr), &outstrlen, SQL_DRIVER_COMPLETE);
    if (SQL_SUCCEEDED(ret)) {
        printf("Connected\n");
        printf("Returned connection string was:\n\t%s\n", outstr);
        if (ret == SQL_SUCCESS_WITH_INFO) {
            printf("Driver reported the following diagnostics\n");
            extract_error("SQLDriverConnect", dbc, SQL_HANDLE_DBC);
        }
        SQLDisconnect(dbc); /* disconnect from driver */
    } else {
        fprintf(stderr, "Failed to connect\n");
        extract_error("SQLDriverConnect", dbc, SQL_HANDLE_DBC);
    }

    if (dbc)
        SQLFreeHandle(SQL_HANDLE_DBC, dbc);

    if (env)
        SQLFreeHandle(SQL_HANDLE_ENV, env);

    return 0;
}

下面列出目前資料庫中表格的資料。

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>

int main() {
    SQLHENV env = NULL;
    SQLHDBC dbc = NULL;
    SQLHSTMT stmt = NULL;
    SQLRETURN ret;
    SQLSMALLINT columns; /* number of columns in result-set */
    SQLCHAR buf[5][64];
    int row = 0;
    SQLLEN indicator[5];
    int i;

    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
    ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    ret = SQLDriverConnect(dbc, NULL, "DSN=PostgreSQL;", SQL_NTS, NULL, 0, NULL,
                           SQL_DRIVER_COMPLETE);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "Failed to connect\n");
        goto End;
    }

    ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "Failed to connect\n");
        goto End;
    }

    SQLTables(stmt, NULL, 0, NULL, 0, NULL, 0, "TABLE", SQL_NTS);
    SQLNumResultCols(stmt, &columns);
    for (i = 0; i < columns; i++) {
        SQLBindCol(stmt, i + 1, SQL_C_CHAR, buf[i], sizeof(buf[i]),
                   &indicator[i]);
    }

    /* Fetch the data */
    while (SQL_SUCCEEDED(SQLFetch(stmt))) {
        /* display the results that will now be in the bound area's */
        for (i = 0; i < columns; i++) {
            if (indicator[i] == SQL_NULL_DATA) {
                printf("  Column %u : NULL\n", i);
            } else {
                printf("  Column %u : %s\n", i, buf[i]);
            }
        }
    }

End:
    if (stmt)
        SQLFreeHandle(SQL_HANDLE_DBC, stmt);

    if (dbc)
        SQLFreeHandle(SQL_HANDLE_DBC, dbc);

    if (env)
        SQLFreeHandle(SQL_HANDLE_ENV, env);

    return 0;
}

接下來的程式使用 SQLExecDirect 與 SQLFetch 取得 PostgreSQL 的版本資訊。

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    SQLHENV henv = SQL_NULL_HENV;
    SQLHDBC hdbc = SQL_NULL_HDBC;
    SQLHSTMT hstmt = SQL_NULL_HSTMT;

    SQLRETURN ret;
    SQLCHAR Version[255];
    SQLLEN siVersion;

    char *sqlStatement = "Select version() as version";

    ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
    ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    ret = SQLDriverConnect(hdbc, NULL, "DSN=PostgreSQL;", SQL_NTS, NULL, 0,
                           NULL, SQL_DRIVER_COMPLETE);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "Failed to connect\n");
        goto End;
    }

    ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLAllocHandle(SQL_HANDLE_STMT) failed.\n");
        goto End;
    }

    ret = SQLExecDirect(hstmt, sqlStatement, strlen(sqlStatement));
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLExecDirect() failed.\n");
        goto End;
    }

    while (1) {
        ret = SQLFetch(hstmt);
        if (ret == SQL_ERROR || ret == SQL_SUCCESS_WITH_INFO) {
            fprintf(stderr, "SQLFetch(hstmt) failed.\n");
            goto End;
        }

        if (ret == SQL_NO_DATA) {
            break;
        }

        if (ret == SQL_SUCCESS) {
            ret = SQLGetData(hstmt, 1, SQL_C_CHAR, Version, 255, &siVersion);
            printf("Versoin:\n%s\n", Version);
        }
    }

End:

    if (hstmt != SQL_NULL_HSTMT) {
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
        hstmt = SQL_NULL_HSTMT;
    }

    if (hdbc != SQL_NULL_HDBC) {
        SQLDisconnect(hdbc);
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
        hdbc = SQL_NULL_HDBC;
    }

    if (henv != SQL_NULL_HENV) {
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
        hstmt = SQL_NULL_HSTMT;
    }

    return 0;
}

下面是 SQLPrepare/SQLExecute 的範例(包含使用 SQLBindParameter)。

#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    SQLHENV henv = SQL_NULL_HENV;
    SQLHDBC hdbc = SQL_NULL_HDBC;
    SQLHSTMT hstmt = SQL_NULL_HSTMT;

    SQLRETURN ret;
    int RetParam = 1;
    SQLLEN cbRetParam = SQL_NTS;
    SQLCHAR Name[40];
    SQLLEN lenName = 0;
    SQLSMALLINT NumParams;

    char *sqlstr = NULL;

    ret = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
    ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (!SQL_SUCCEEDED(ret)) {
        printf("SQLAllocHandle failed.\n");
        return (1);
    }

    ret = SQLDriverConnect(hdbc, NULL, "DSN=PostgreSQL;", SQL_NTS, NULL, 0,
                           NULL, SQL_DRIVER_COMPLETE);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "Failed to connect\n");
        goto End;
    }

    ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLAllocHandle(SQL_HANDLE_STMT) failed.\n");
        goto End;
    }

    sqlstr = "drop table if exists person";
    ret = SQLPrepare(hstmt, (SQLCHAR *)sqlstr, strlen(sqlstr));
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLPrepare() failed.\n");
        goto End;
    }

    ret = SQLExecute(hstmt);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLExecute(drop) failed.\n");
        goto End;
    }

    sqlstr = "create table if not exists person (id integer, name varchar(40) "
             "not null)";
    ret = SQLPrepare(hstmt, (SQLCHAR *)sqlstr, strlen(sqlstr));
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLPrepare(create) failed.\n");
        goto End;
    }

    ret = SQLExecute(hstmt);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLExecute() failed.\n");
        goto End;
    }

    ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER,
                           0, 0, &RetParam, 0, &cbRetParam);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLBindParameter(1) failed.\n");
        goto End;
    }

    ret = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 40,
                           0, Name, 40, &lenName);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLBindParameter(2) failed.\n");
        goto End;
    }

    ret = SQLPrepare(hstmt,
                     (SQLCHAR *)"insert into person (id, name) values (?, ?)",
                     SQL_NTS);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLPrepare(insert) failed.\n");
        goto End;
    }

    SQLNumParams(hstmt, &NumParams);
    if (!SQL_SUCCEEDED(ret)) {
        fprintf(stderr, "SQLNumParams() failed.\n");
        goto End;
    }

    printf("Num params : %i\n", NumParams);
    strcpy(Name, "Orange");
    lenName = strlen(Name);

    ret = SQLExecute(hstmt);
    if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
        printf("Status : Success\n");
    } else {
        fprintf(stderr, "SQLExecute(insert) failed.\n");
    }

End:

    if (hstmt != SQL_NULL_HSTMT) {
        SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
        hstmt = SQL_NULL_HSTMT;
    }

    if (hdbc != SQL_NULL_HDBC) {
        SQLDisconnect(hdbc);
        SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
        hdbc = SQL_NULL_HDBC;
    }

    if (henv != SQL_NULL_HENV) {
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
        hstmt = SQL_NULL_HSTMT;
    }

    return 0;
}