summaryrefslogtreecommitdiff
path: root/lib/igt_chamelium_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/igt_chamelium_stream.c')
-rw-r--r--lib/igt_chamelium_stream.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/lib/igt_chamelium_stream.c b/lib/igt_chamelium_stream.c
new file mode 100644
index 00000000..68ddb217
--- /dev/null
+++ b/lib/igt_chamelium_stream.c
@@ -0,0 +1,589 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Authors: Simon Ser <simon.ser@intel.com>
+ */
+
+#include "config.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "igt_chamelium_stream.h"
+#include "igt_core.h"
+#include "igt_rc.h"
+
+#define STREAM_PORT 9994
+#define STREAM_VERSION_MAJOR 1
+#define STREAM_VERSION_MINOR 0
+
+enum stream_error {
+ STREAM_ERROR_NONE = 0,
+ STREAM_ERROR_COMMAND = 1,
+ STREAM_ERROR_ARGUMENT = 2,
+ STREAM_ERROR_EXISTS = 3,
+ STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
+ STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
+ STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
+ STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
+ STREAM_ERROR_NO_MEM = 8,
+};
+
+enum stream_message_kind {
+ STREAM_MESSAGE_REQUEST = 0,
+ STREAM_MESSAGE_RESPONSE = 1,
+ STREAM_MESSAGE_DATA = 2,
+};
+
+enum stream_message_type {
+ STREAM_MESSAGE_RESET = 0,
+ STREAM_MESSAGE_GET_VERSION = 1,
+ STREAM_MESSAGE_VIDEO_STREAM = 2,
+ STREAM_MESSAGE_SHRINK_VIDEO = 3,
+ STREAM_MESSAGE_VIDEO_FRAME = 4,
+ STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
+ STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
+ STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
+ STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
+};
+
+struct chamelium_stream {
+ char *host;
+ unsigned int port;
+
+ int fd;
+};
+
+static const char *stream_error_str(enum stream_error err)
+{
+ switch (err) {
+ case STREAM_ERROR_NONE:
+ return "no error";
+ case STREAM_ERROR_COMMAND:
+ return "invalid command";
+ case STREAM_ERROR_ARGUMENT:
+ return "invalid arguments";
+ case STREAM_ERROR_EXISTS:
+ return "dump already started";
+ case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
+ return "video dump stopped after overflow";
+ case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
+ return "video frame dropped after overflow";
+ case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
+ return "audio dump stoppred after overflow";
+ case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
+ return "audio page dropped after overflow";
+ case STREAM_ERROR_NO_MEM:
+ return "out of memory";
+ }
+ return "unknown error";
+}
+
+/**
+ * The Chamelium URL is specified in the configuration file. We need to extract
+ * the host to connect to the stream server.
+ */
+static char *parse_url_host(const char *url)
+{
+ static const char prefix[] = "http://";
+ char *colon;
+
+ if (strstr(url, prefix) != url)
+ return NULL;
+ url += strlen(prefix);
+
+ colon = strchr(url, ':');
+ if (!colon)
+ return NULL;
+
+ return strndup(url, colon - url);
+}
+
+static bool chamelium_stream_read_config(struct chamelium_stream *client)
+{
+ GError *error = NULL;
+ gchar *chamelium_url;
+
+ if (!igt_key_file) {
+ igt_warn("No configuration file available for chamelium\n");
+ return false;
+ }
+
+ chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
+ &error);
+ if (!chamelium_url) {
+ igt_warn("Couldn't read Chamelium URL from config file: %s\n",
+ error->message);
+ return false;
+ }
+
+ client->host = parse_url_host(chamelium_url);
+ if (!client->host) {
+ igt_warn("Invalid Chamelium URL in config file: %s\n",
+ chamelium_url);
+ return false;
+ }
+ client->port = STREAM_PORT;
+
+ return true;
+}
+
+static bool chamelium_stream_connect(struct chamelium_stream *client)
+{
+ int ret;
+ char port_str[16];
+ struct addrinfo hints = {};
+ struct addrinfo *results, *ai;
+ struct timeval tv = {};
+
+ igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
+ client->host, client->port);
+
+ snprintf(port_str, sizeof(port_str), "%u", client->port);
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ ret = getaddrinfo(client->host, port_str, &hints, &results);
+ if (ret != 0) {
+ igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
+ return false;
+ }
+
+ client->fd = -1;
+ for (ai = results; ai != NULL; ai = ai->ai_next) {
+ client->fd = socket(ai->ai_family, ai->ai_socktype,
+ ai->ai_protocol);
+ if (client->fd == -1)
+ continue;
+
+ if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
+ close(client->fd);
+ client->fd = -1;
+ continue;
+ }
+
+ break;
+ }
+
+ freeaddrinfo(results);
+
+ if (client->fd < 0) {
+ igt_warn("Failed to connect to Chamelium stream server\n");
+ return false;
+ }
+
+ /* Set a read and write timeout of 5 seconds. */
+ tv.tv_sec = 5;
+ setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+ setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
+
+ return true;
+}
+
+static bool read_whole(int fd, void *buf, size_t buf_len)
+{
+ ssize_t ret;
+ size_t n = 0;
+ char *ptr;
+
+ while (n < buf_len) {
+ ptr = (char *) buf + n;
+ ret = read(fd, ptr, buf_len - n);
+ if (ret < 0) {
+ igt_warn("read failed: %s\n", strerror(errno));
+ return false;
+ } else if (ret == 0) {
+ igt_warn("short read\n");
+ return false;
+ }
+ n += ret;
+ }
+
+ return true;
+}
+
+static bool write_whole(int fd, void *buf, size_t buf_len)
+{
+ ssize_t ret;
+ size_t n = 0;
+ char *ptr;
+
+ while (n < buf_len) {
+ ptr = (char *) buf + n;
+ ret = write(fd, ptr, buf_len - n);
+ if (ret < 0) {
+ igt_warn("write failed: %s\n", strerror(errno));
+ return false;
+ } else if (ret == 0) {
+ igt_warn("short write\n");
+ return false;
+ }
+ n += ret;
+ }
+
+ return true;
+}
+
+static bool read_and_discard(int fd, size_t len)
+{
+ char buf[1024];
+ size_t n;
+
+ while (len > 0) {
+ n = len;
+ if (n > sizeof(buf))
+ n = sizeof(buf);
+
+ if (!read_whole(fd, buf, n))
+ return false;
+
+ len -= n;
+ }
+
+ return true;
+}
+
+/** Read a message header from the socket.
+ *
+ * The header is laid out as follows:
+ * - u16: message type
+ * - u16: error code
+ * - u32: message length
+ */
+static bool chamelium_stream_read_header(struct chamelium_stream *client,
+ enum stream_message_kind *kind,
+ enum stream_message_type *type,
+ enum stream_error *err,
+ size_t *len)
+{
+ uint16_t _type;
+ char buf[8];
+
+ if (!read_whole(client->fd, buf, sizeof(buf)))
+ return false;
+
+ _type = ntohs(*(uint16_t *) &buf[0]);
+ *type = _type & 0xFF;
+ *kind = _type >> 8;
+ *err = ntohs(*(uint16_t *) &buf[2]);
+ *len = ntohl(*(uint32_t *) &buf[4]);
+
+ return true;
+}
+
+static bool chamelium_stream_write_header(struct chamelium_stream *client,
+ enum stream_message_type type,
+ enum stream_error err,
+ size_t len)
+{
+ char buf[8];
+ uint16_t _type;
+
+ _type = type | (STREAM_MESSAGE_REQUEST << 8);
+
+ *(uint16_t *) &buf[0] = htons(_type);
+ *(uint16_t *) &buf[2] = htons(err);
+ *(uint32_t *) &buf[4] = htonl(len);
+
+ return write_whole(client->fd, buf, sizeof(buf));
+}
+
+static bool chamelium_stream_read_response(struct chamelium_stream *client,
+ enum stream_message_type type,
+ void *buf, size_t buf_len)
+{
+ enum stream_message_kind read_kind;
+ enum stream_message_type read_type;
+ enum stream_error read_err;
+ size_t read_len;
+
+ if (!chamelium_stream_read_header(client, &read_kind, &read_type,
+ &read_err, &read_len))
+ return false;
+
+ if (read_kind != STREAM_MESSAGE_RESPONSE) {
+ igt_warn("Expected a response, got kind %d\n", read_kind);
+ return false;
+ }
+ if (read_type != type) {
+ igt_warn("Expected message type %d, got %d\n",
+ type, read_type);
+ return false;
+ }
+ if (read_err != STREAM_ERROR_NONE) {
+ igt_warn("Received error: %s (%d)\n",
+ stream_error_str(read_err), read_err);
+ return false;
+ }
+ if (buf_len != read_len) {
+ igt_warn("Received invalid message body size "
+ "(got %zu bytes, want %zu bytes)\n",
+ read_len, buf_len);
+ return false;
+ }
+
+ return read_whole(client->fd, buf, buf_len);
+}
+
+static bool chamelium_stream_write_request(struct chamelium_stream *client,
+ enum stream_message_type type,
+ void *buf, size_t buf_len)
+{
+ if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
+ buf_len))
+ return false;
+
+ if (buf_len == 0)
+ return true;
+
+ return write_whole(client->fd, buf, buf_len);
+}
+
+static bool chamelium_stream_call(struct chamelium_stream *client,
+ enum stream_message_type type,
+ void *req_buf, size_t req_len,
+ void *resp_buf, size_t resp_len)
+{
+ if (!chamelium_stream_write_request(client, type, req_buf, req_len))
+ return false;
+
+ return chamelium_stream_read_response(client, type, resp_buf, resp_len);
+}
+
+static bool chamelium_stream_check_version(struct chamelium_stream *client)
+{
+ char resp[2];
+ uint8_t major, minor;
+
+ if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
+ NULL, 0, resp, sizeof(resp)))
+ return false;
+
+ major = resp[0];
+ minor = resp[1];
+ if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
+ igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
+ STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
+ major, minor);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * chamelium_stream_dump_realtime_audio:
+ *
+ * Starts audio capture. The caller can then call
+ * #chamelium_stream_receive_realtime_audio to receive audio pages.
+ */
+bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
+ enum chamelium_stream_realtime_mode mode)
+{
+ char req[1];
+
+ igt_debug("Starting real-time audio capture\n");
+
+ req[0] = mode;
+ return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
+ req, sizeof(req), NULL, 0);
+}
+
+/**
+ * chamelium_stream_receive_realtime_audio:
+ * @page_count: if non-NULL, will be set to the dumped page number
+ * @buf: must either point to a dynamically allocated memory region or NULL
+ * @buf_len: number of elements of *@buf, for zero if @buf is NULL
+ *
+ * Receives one audio page from the streaming server.
+ *
+ * In "best effort" mode, some pages can be dropped. This can be detected via
+ * the page count.
+ *
+ * buf_len will be set to the size of the page. The caller is responsible for
+ * calling free(3) on *buf.
+ */
+bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
+ size_t *page_count,
+ int32_t **buf, size_t *buf_len)
+{
+ enum stream_message_kind kind;
+ enum stream_message_type type;
+ enum stream_error err;
+ size_t body_len;
+ char page_count_buf[4];
+ int32_t *ptr;
+
+ while (true) {
+ if (!chamelium_stream_read_header(client, &kind, &type,
+ &err, &body_len))
+ return false;
+
+ if (kind != STREAM_MESSAGE_DATA) {
+ igt_warn("Expected a data message, got kind %d\n", kind);
+ return false;
+ }
+ if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
+ igt_warn("Expected real-time audio dump message, "
+ "got type %d\n", type);
+ return false;
+ }
+
+ if (err == STREAM_ERROR_NONE)
+ break;
+ else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
+ igt_warn("Received error: %s (%d)\n",
+ stream_error_str(err), err);
+ return false;
+ }
+
+ igt_debug("Dropped an audio page because of an overflow\n");
+ igt_assert(body_len == 0);
+ }
+
+ igt_assert(body_len >= sizeof(page_count_buf));
+
+ if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
+ return false;
+ if (page_count)
+ *page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
+ body_len -= sizeof(page_count_buf);
+
+ igt_assert(body_len % sizeof(int32_t) == 0);
+ if (*buf_len * sizeof(int32_t) != body_len) {
+ ptr = realloc(*buf, body_len);
+ if (!ptr) {
+ igt_warn("realloc failed: %s\n", strerror(errno));
+ return false;
+ }
+ *buf = ptr;
+ *buf_len = body_len / sizeof(int32_t);
+ }
+
+ return read_whole(client->fd, *buf, body_len);
+}
+
+/**
+ * chamelium_stream_stop_realtime_audio:
+ *
+ * Stops real-time audio capture. This also drops any buffered audio pages.
+ * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
+ * stopping audio capture.
+ */
+bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
+{
+ enum stream_message_kind kind;
+ enum stream_message_type type;
+ enum stream_error err;
+ size_t len;
+
+ igt_debug("Stopping real-time audio capture\n");
+
+ if (!chamelium_stream_write_request(client,
+ STREAM_MESSAGE_STOP_DUMP_AUDIO,
+ NULL, 0))
+ return false;
+
+ while (true) {
+ if (!chamelium_stream_read_header(client, &kind, &type,
+ &err, &len))
+ return false;
+
+ if (kind == STREAM_MESSAGE_RESPONSE)
+ break;
+
+ if (!read_and_discard(client->fd, len))
+ return false;
+ }
+
+ if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
+ igt_warn("Unexpected response type %d\n", type);
+ return false;
+ }
+ if (err != STREAM_ERROR_NONE) {
+ igt_warn("Received error: %s (%d)\n",
+ stream_error_str(err), err);
+ return false;
+ }
+ if (len != 0) {
+ igt_warn("Expected an empty response, got %zu bytes\n", len);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * chamelium_stream_audio_format:
+ *
+ * Gets the format used for audio pages.
+ *
+ * Data will always be captured in raw pages of S32_LE elements. This function
+ * exposes the sampling rate and the number of channels.
+ */
+void chamelium_stream_audio_format(struct chamelium_stream *stream,
+ int *rate, int *channels)
+{
+ /* TODO: the Chamelium streaming server doesn't expose those yet.
+ * Just hardcode the values for now. */
+ *rate = 48000;
+ *channels = 8;
+}
+
+/**
+ * chamelium_stream_init:
+ *
+ * Connects to the Chamelium streaming server.
+ */
+struct chamelium_stream *chamelium_stream_init(void)
+{
+ struct chamelium_stream *client;
+
+ client = calloc(1, sizeof(*client));
+
+ if (!chamelium_stream_read_config(client))
+ goto error_client;
+ if (!chamelium_stream_connect(client))
+ goto error_client;
+ if (!chamelium_stream_check_version(client))
+ goto error_fd;
+
+ return client;
+
+error_fd:
+ close(client->fd);
+error_client:
+ free(client);
+ return NULL;
+}
+
+void chamelium_stream_deinit(struct chamelium_stream *client)
+{
+ if (close(client->fd) != 0)
+ igt_warn("close failed: %s\n", strerror(errno));
+ free(client);
+}