summaryrefslogtreecommitdiffstats
path: root/misc
diff options
context:
space:
mode:
Diffstat (limited to 'misc')
-rw-r--r--misc/bstr.c469
-rw-r--r--misc/bstr.h231
-rw-r--r--misc/charset_conv.c235
-rw-r--r--misc/charset_conv.h22
-rw-r--r--misc/ctype.h19
-rw-r--r--misc/dispatch.c417
-rw-r--r--misc/dispatch.h32
-rw-r--r--misc/jni.c429
-rw-r--r--misc/jni.h161
-rw-r--r--misc/json.c359
-rw-r--r--misc/json.h31
-rw-r--r--misc/language.c362
-rw-r--r--misc/language.h31
-rw-r--r--misc/linked_list.h107
-rw-r--r--misc/natural_sort.c67
-rw-r--r--misc/natural_sort.h23
-rw-r--r--misc/node.c159
-rw-r--r--misc/node.h20
-rw-r--r--misc/random.c75
-rw-r--r--misc/random.h41
-rw-r--r--misc/rendezvous.c55
-rw-r--r--misc/rendezvous.h8
-rw-r--r--misc/thread_pool.c223
-rw-r--r--misc/thread_pool.h35
-rw-r--r--misc/thread_tools.c276
-rw-r--r--misc/thread_tools.h83
-rw-r--r--misc/uuid.c141
-rw-r--r--misc/uuid.h146
28 files changed, 4257 insertions, 0 deletions
diff --git a/misc/bstr.c b/misc/bstr.c
new file mode 100644
index 0000000..4f1e862
--- /dev/null
+++ b/misc/bstr.c
@@ -0,0 +1,469 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <strings.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "mpv_talloc.h"
+
+#include "common/common.h"
+#include "misc/ctype.h"
+#include "bstr.h"
+
+int bstrcmp(struct bstr str1, struct bstr str2)
+{
+ int ret = 0;
+ if (str1.len && str2.len)
+ ret = memcmp(str1.start, str2.start, MPMIN(str1.len, str2.len));
+
+ if (!ret) {
+ if (str1.len == str2.len)
+ return 0;
+ else if (str1.len > str2.len)
+ return 1;
+ else
+ return -1;
+ }
+ return ret;
+}
+
+int bstrcasecmp(struct bstr str1, struct bstr str2)
+{
+ int ret = 0;
+ if (str1.len && str2.len)
+ ret = strncasecmp(str1.start, str2.start, MPMIN(str1.len, str2.len));
+
+ if (!ret) {
+ if (str1.len == str2.len)
+ return 0;
+ else if (str1.len > str2.len)
+ return 1;
+ else
+ return -1;
+ }
+ return ret;
+}
+
+int bstrchr(struct bstr str, int c)
+{
+ for (int i = 0; i < str.len; i++)
+ if (str.start[i] == c)
+ return i;
+ return -1;
+}
+
+int bstrrchr(struct bstr str, int c)
+{
+ for (int i = str.len - 1; i >= 0; i--)
+ if (str.start[i] == c)
+ return i;
+ return -1;
+}
+
+int bstrcspn(struct bstr str, const char *reject)
+{
+ int i;
+ for (i = 0; i < str.len; i++)
+ if (strchr(reject, str.start[i]))
+ break;
+ return i;
+}
+
+int bstrspn(struct bstr str, const char *accept)
+{
+ int i;
+ for (i = 0; i < str.len; i++)
+ if (!strchr(accept, str.start[i]))
+ break;
+ return i;
+}
+
+int bstr_find(struct bstr haystack, struct bstr needle)
+{
+ for (int i = 0; i < haystack.len; i++)
+ if (bstr_startswith(bstr_splice(haystack, i, haystack.len), needle))
+ return i;
+ return -1;
+}
+
+struct bstr bstr_lstrip(struct bstr str)
+{
+ while (str.len && mp_isspace(*str.start)) {
+ str.start++;
+ str.len--;
+ }
+ return str;
+}
+
+struct bstr bstr_strip(struct bstr str)
+{
+ str = bstr_lstrip(str);
+ while (str.len && mp_isspace(str.start[str.len - 1]))
+ str.len--;
+ return str;
+}
+
+struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest)
+{
+ int start;
+ for (start = 0; start < str.len; start++)
+ if (!strchr(sep, str.start[start]))
+ break;
+ str = bstr_cut(str, start);
+ int end = bstrcspn(str, sep);
+ if (rest) {
+ *rest = bstr_cut(str, end);
+ }
+ return bstr_splice(str, 0, end);
+}
+
+// Unlike with bstr_split(), tok is a string, and not a set of char.
+// If tok is in str, return true, and: concat(out_left, tok, out_right) == str
+// Otherwise, return false, and set out_left==str, out_right==""
+bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right)
+{
+ bstr bsep = bstr0(tok);
+ int pos = bstr_find(str, bsep);
+ if (pos < 0)
+ pos = str.len;
+ *out_left = bstr_splice(str, 0, pos);
+ *out_right = bstr_cut(str, pos + bsep.len);
+ return pos != str.len;
+}
+
+struct bstr bstr_splice(struct bstr str, int start, int end)
+{
+ if (start < 0)
+ start += str.len;
+ if (end < 0)
+ end += str.len;
+ end = MPMIN(end, str.len);
+ start = MPMAX(start, 0);
+ end = MPMAX(end, start);
+ str.start += start;
+ str.len = end - start;
+ return str;
+}
+
+long long bstrtoll(struct bstr str, struct bstr *rest, int base)
+{
+ str = bstr_lstrip(str);
+ char buf[51];
+ int len = MPMIN(str.len, 50);
+ memcpy(buf, str.start, len);
+ buf[len] = 0;
+ char *endptr;
+ long long r = strtoll(buf, &endptr, base);
+ if (rest)
+ *rest = bstr_cut(str, endptr - buf);
+ return r;
+}
+
+double bstrtod(struct bstr str, struct bstr *rest)
+{
+ str = bstr_lstrip(str);
+ char buf[101];
+ int len = MPMIN(str.len, 100);
+ memcpy(buf, str.start, len);
+ buf[len] = 0;
+ char *endptr;
+ double r = strtod(buf, &endptr);
+ if (rest)
+ *rest = bstr_cut(str, endptr - buf);
+ return r;
+}
+
+struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
+{
+ int pos = bstrchr(str, c);
+ if (pos < 0)
+ pos = str.len;
+ if (rest)
+ *rest = bstr_cut(str, pos + 1);
+ return bstr_splice(str, 0, pos + 1);
+}
+
+struct bstr bstr_strip_linebreaks(struct bstr str)
+{
+ if (bstr_endswith0(str, "\r\n")) {
+ str = bstr_splice(str, 0, str.len - 2);
+ } else if (bstr_endswith0(str, "\n")) {
+ str = bstr_splice(str, 0, str.len - 1);
+ }
+ return str;
+}
+
+bool bstr_eatstart(struct bstr *s, struct bstr prefix)
+{
+ if (!bstr_startswith(*s, prefix))
+ return false;
+ *s = bstr_cut(*s, prefix.len);
+ return true;
+}
+
+bool bstr_eatend(struct bstr *s, struct bstr prefix)
+{
+ if (!bstr_endswith(*s, prefix))
+ return false;
+ s->len -= prefix.len;
+ return true;
+}
+
+void bstr_lower(struct bstr str)
+{
+ for (int i = 0; i < str.len; i++)
+ str.start[i] = mp_tolower(str.start[i]);
+}
+
+int bstr_sscanf(struct bstr str, const char *format, ...)
+{
+ char *ptr = bstrdup0(NULL, str);
+ va_list va;
+ va_start(va, format);
+ int ret = vsscanf(ptr, format, va);
+ va_end(va);
+ talloc_free(ptr);
+ return ret;
+}
+
+int bstr_parse_utf8_code_length(unsigned char b)
+{
+ if (b < 128)
+ return 1;
+ int bytes = 7 - mp_log2(b ^ 255);
+ return (bytes >= 2 && bytes <= 4) ? bytes : -1;
+}
+
+int bstr_decode_utf8(struct bstr s, struct bstr *out_next)
+{
+ if (s.len == 0)
+ return -1;
+ unsigned int codepoint = s.start[0];
+ s.start++; s.len--;
+ if (codepoint >= 128) {
+ int bytes = bstr_parse_utf8_code_length(codepoint);
+ if (bytes < 1 || s.len < bytes - 1)
+ return -1;
+ codepoint &= 127 >> bytes;
+ for (int n = 1; n < bytes; n++) {
+ int tmp = (unsigned char)s.start[0];
+ if ((tmp & 0xC0) != 0x80)
+ return -1;
+ codepoint = (codepoint << 6) | (tmp & ~0xC0);
+ s.start++; s.len--;
+ }
+ if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF))
+ return -1;
+ // Overlong sequences - check taken from libavcodec.
+ // (The only reason we even bother with this is to make libavcodec's
+ // retarded subtitle utf-8 check happy.)
+ unsigned int min = bytes == 2 ? 0x80 : 1 << (5 * bytes - 4);
+ if (codepoint < min)
+ return -1;
+ }
+ if (out_next)
+ *out_next = s;
+ return codepoint;
+}
+
+struct bstr bstr_split_utf8(struct bstr str, struct bstr *out_next)
+{
+ bstr rest;
+ int code = bstr_decode_utf8(str, &rest);
+ if (code < 0)
+ return (bstr){0};
+ if (out_next)
+ *out_next = rest;
+ return bstr_splice(str, 0, str.len - rest.len);
+}
+
+int bstr_validate_utf8(struct bstr s)
+{
+ while (s.len) {
+ if (bstr_decode_utf8(s, &s) < 0) {
+ // Try to guess whether the sequence was just cut-off.
+ unsigned int codepoint = (unsigned char)s.start[0];
+ int bytes = bstr_parse_utf8_code_length(codepoint);
+ if (bytes > 1 && s.len < 6) {
+ // Manually check validity of left bytes
+ for (int n = 1; n < bytes; n++) {
+ if (n >= s.len) {
+ // Everything valid until now - just cut off.
+ return -(bytes - s.len);
+ }
+ int tmp = (unsigned char)s.start[n];
+ if ((tmp & 0xC0) != 0x80)
+ break;
+ }
+ }
+ return -8;
+ }
+ }
+ return 0;
+}
+
+struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s)
+{
+ bstr new = {0};
+ bstr left = s;
+ unsigned char *first_ok = s.start;
+ while (left.len) {
+ int r = bstr_decode_utf8(left, &left);
+ if (r < 0) {
+ bstr_xappend(talloc_ctx, &new, (bstr){first_ok, left.start - first_ok});
+ mp_append_utf8_bstr(talloc_ctx, &new, (unsigned char)left.start[0]);
+ left.start += 1;
+ left.len -= 1;
+ first_ok = left.start;
+ }
+ }
+ if (!new.start)
+ return s;
+ if (first_ok != left.start)
+ bstr_xappend(talloc_ctx, &new, (bstr){first_ok, left.start - first_ok});
+ return new;
+}
+
+static void resize_append(void *talloc_ctx, bstr *s, size_t append_min)
+{
+ size_t size = talloc_get_size(s->start);
+ assert(s->len <= size);
+ if (append_min > size - s->len) {
+ if (append_min < size)
+ append_min = size; // preallocate in power of 2s
+ if (size >= SIZE_MAX / 2 || append_min >= SIZE_MAX / 2)
+ abort(); // oom
+ s->start = talloc_realloc_size(talloc_ctx, s->start, size + append_min);
+ }
+}
+
+// Append the string, so that *s = *s + append. s->start is expected to be
+// a talloc allocation (which can be realloced) or NULL.
+// This function will always implicitly append a \0 after the new string for
+// convenience.
+// talloc_ctx will be used as parent context, if s->start is NULL.
+void bstr_xappend(void *talloc_ctx, bstr *s, bstr append)
+{
+ if (!append.len)
+ return;
+ resize_append(talloc_ctx, s, append.len + 1);
+ memcpy(s->start + s->len, append.start, append.len);
+ s->len += append.len;
+ s->start[s->len] = '\0';
+}
+
+void bstr_xappend_asprintf(void *talloc_ctx, bstr *s, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ bstr_xappend_vasprintf(talloc_ctx, s, fmt, ap);
+ va_end(ap);
+}
+
+// Exactly as bstr_xappend(), but with a formatted string.
+void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt,
+ va_list ap)
+{
+ int size;
+ va_list copy;
+ va_copy(copy, ap);
+ size_t avail = talloc_get_size(s->start) - s->len;
+ char *dest = s->start ? s->start + s->len : NULL;
+ char c;
+ if (avail < 1)
+ dest = &c;
+ size = vsnprintf(dest, MPMAX(avail, 1), fmt, copy);
+ va_end(copy);
+
+ if (size < 0)
+ abort();
+
+ if (avail < 1 || size + 1 > avail) {
+ resize_append(talloc_ctx, s, size + 1);
+ vsnprintf(s->start + s->len, size + 1, fmt, ap);
+ }
+ s->len += size;
+}
+
+bool bstr_case_startswith(struct bstr s, struct bstr prefix)
+{
+ struct bstr start = bstr_splice(s, 0, prefix.len);
+ return start.len == prefix.len && bstrcasecmp(start, prefix) == 0;
+}
+
+bool bstr_case_endswith(struct bstr s, struct bstr suffix)
+{
+ struct bstr end = bstr_cut(s, -suffix.len);
+ return end.len == suffix.len && bstrcasecmp(end, suffix) == 0;
+}
+
+struct bstr bstr_strip_ext(struct bstr str)
+{
+ int dotpos = bstrrchr(str, '.');
+ if (dotpos < 0)
+ return str;
+ return (struct bstr){str.start, dotpos};
+}
+
+struct bstr bstr_get_ext(struct bstr s)
+{
+ int dotpos = bstrrchr(s, '.');
+ if (dotpos < 0)
+ return (struct bstr){NULL, 0};
+ return bstr_splice(s, dotpos + 1, s.len);
+}
+
+static int h_to_i(unsigned char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+
+ return -1; // invalid char
+}
+
+bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out)
+{
+ if (!out)
+ return false;
+
+ char *arr = talloc_array(talloc_ctx, char, hex.len / 2);
+ int len = 0;
+
+ while (hex.len >= 2) {
+ int a = h_to_i(hex.start[0]);
+ int b = h_to_i(hex.start[1]);
+ hex = bstr_splice(hex, 2, hex.len);
+
+ if (a < 0 || b < 0) {
+ talloc_free(arr);
+ return false;
+ }
+
+ arr[len++] = (a << 4) | b;
+ }
+
+ *out = (struct bstr){ .start = arr, .len = len };
+ return true;
+}
diff --git a/misc/bstr.h b/misc/bstr.h
new file mode 100644
index 0000000..dc8ad40
--- /dev/null
+++ b/misc/bstr.h
@@ -0,0 +1,231 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_BSTR_H
+#define MPLAYER_BSTR_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdarg.h>
+
+#include "mpv_talloc.h"
+#include "osdep/compiler.h"
+
+/* NOTE: 'len' is size_t, but most string-handling functions below assume
+ * that input size has been sanity checked and len fits in an int.
+ */
+typedef struct bstr {
+ unsigned char *start;
+ size_t len;
+} bstr;
+
+// If str.start is NULL, return NULL.
+static inline char *bstrdup0(void *talloc_ctx, struct bstr str)
+{
+ return talloc_strndup(talloc_ctx, (char *)str.start, str.len);
+}
+
+// Like bstrdup0(), but always return a valid C-string.
+static inline char *bstrto0(void *talloc_ctx, struct bstr str)
+{
+ return str.start ? bstrdup0(talloc_ctx, str) : talloc_strdup(talloc_ctx, "");
+}
+
+// Return start = NULL iff that is true for the original.
+static inline struct bstr bstrdup(void *talloc_ctx, struct bstr str)
+{
+ struct bstr r = { NULL, str.len };
+ if (str.start)
+ r.start = (unsigned char *)talloc_memdup(talloc_ctx, str.start, str.len);
+ return r;
+}
+
+static inline struct bstr bstr0(const char *s)
+{
+ return (struct bstr){(unsigned char *)s, s ? strlen(s) : 0};
+}
+
+int bstrcmp(struct bstr str1, struct bstr str2);
+int bstrcasecmp(struct bstr str1, struct bstr str2);
+int bstrchr(struct bstr str, int c);
+int bstrrchr(struct bstr str, int c);
+int bstrspn(struct bstr str, const char *accept);
+int bstrcspn(struct bstr str, const char *reject);
+
+int bstr_find(struct bstr haystack, struct bstr needle);
+struct bstr bstr_lstrip(struct bstr str);
+struct bstr bstr_strip(struct bstr str);
+struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest);
+bool bstr_split_tok(bstr str, const char *tok, bstr *out_left, bstr *out_right);
+struct bstr bstr_splice(struct bstr str, int start, int end);
+long long bstrtoll(struct bstr str, struct bstr *rest, int base);
+double bstrtod(struct bstr str, struct bstr *rest);
+void bstr_lower(struct bstr str);
+int bstr_sscanf(struct bstr str, const char *format, ...);
+
+// Decode a string containing hexadecimal data. All whitespace will be silently
+// ignored. When successful, this allocates a new array to store the output.
+bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out);
+
+// Decode the UTF-8 code point at the start of the string, and return the
+// character.
+// After calling this function, *out_next will point to the next character.
+// out_next can be NULL.
+// On error, -1 is returned, and *out_next is not modified.
+int bstr_decode_utf8(struct bstr str, struct bstr *out_next);
+
+// Return the UTF-8 code point at the start of the string.
+// After calling this function, *out_next will point to the next character.
+// out_next can be NULL.
+// On error, an empty string is returned, and *out_next is not modified.
+struct bstr bstr_split_utf8(struct bstr str, struct bstr *out_next);
+
+// Return the length of the UTF-8 sequence that starts with the given byte.
+// Given a string char *s, the next UTF-8 code point is to be expected at
+// s + bstr_parse_utf8_code_length(s[0])
+// On error, -1 is returned. On success, it returns a value in the range [1, 4].
+int bstr_parse_utf8_code_length(unsigned char b);
+
+// Return >= 0 if the string is valid UTF-8, otherwise negative error code.
+// Embedded \0 bytes are considered valid.
+// This returns -N if the UTF-8 string was likely just cut-off in the middle of
+// an UTF-8 sequence: -1 means 1 byte was missing, -5 5 bytes missing.
+// If the string was likely not cut off, -8 is returned.
+// Use (return_value > -8) to check whether the string is valid UTF-8 or valid
+// but cut-off UTF-8.
+int bstr_validate_utf8(struct bstr s);
+
+// Force the input string to valid UTF-8. If invalid UTF-8 encoding is
+// encountered, the invalid bytes are interpreted as Latin-1.
+// Embedded \0 bytes are considered valid.
+// If replacement happens, a newly allocated string is returned (with a \0
+// byte added past its end for convenience). The string is allocated via
+// talloc, with talloc_ctx as parent.
+struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
+
+// Return the text before the occurrence of a character, and return it. Change
+// *rest to point to the text following this character. (rest can be NULL.)
+struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
+
+// Like bstr_splitchar. Trailing newlines are not stripped.
+static inline struct bstr bstr_getline(struct bstr str, struct bstr *rest)
+{
+ return bstr_splitchar(str, rest, '\n');
+}
+
+// Strip one trailing line break. This is intended for use with bstr_getline,
+// and will remove the trailing \n or \r\n sequence.
+struct bstr bstr_strip_linebreaks(struct bstr str);
+
+void bstr_xappend(void *talloc_ctx, bstr *s, bstr append);
+void bstr_xappend_asprintf(void *talloc_ctx, bstr *s, const char *fmt, ...)
+ PRINTF_ATTRIBUTE(3, 4);
+void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, va_list va)
+ PRINTF_ATTRIBUTE(3, 0);
+
+// If s starts/ends with prefix, return true and return the rest of the string
+// in s.
+bool bstr_eatstart(struct bstr *s, struct bstr prefix);
+bool bstr_eatend(struct bstr *s, struct bstr prefix);
+
+bool bstr_case_startswith(struct bstr s, struct bstr prefix);
+bool bstr_case_endswith(struct bstr s, struct bstr suffix);
+struct bstr bstr_strip_ext(struct bstr str);
+struct bstr bstr_get_ext(struct bstr s);
+
+static inline struct bstr bstr_cut(struct bstr str, int n)
+{
+ if (n < 0) {
+ n += str.len;
+ if (n < 0)
+ n = 0;
+ }
+ if (((size_t)n) > str.len)
+ n = str.len;
+ return (struct bstr){str.start + n, str.len - n};
+}
+
+static inline bool bstr_startswith(struct bstr str, struct bstr prefix)
+{
+ if (str.len < prefix.len)
+ return false;
+ return !memcmp(str.start, prefix.start, prefix.len);
+}
+
+static inline bool bstr_startswith0(struct bstr str, const char *prefix)
+{
+ return bstr_startswith(str, bstr0(prefix));
+}
+
+static inline bool bstr_endswith(struct bstr str, struct bstr suffix)
+{
+ if (str.len < suffix.len)
+ return false;
+ return !memcmp(str.start + str.len - suffix.len, suffix.start, suffix.len);
+}
+
+static inline bool bstr_endswith0(struct bstr str, const char *suffix)
+{
+ return bstr_endswith(str, bstr0(suffix));
+}
+
+static inline int bstrcmp0(struct bstr str1, const char *str2)
+{
+ return bstrcmp(str1, bstr0(str2));
+}
+
+static inline bool bstr_equals(struct bstr str1, struct bstr str2)
+{
+ if (str1.len != str2.len)
+ return false;
+
+ return str1.start == str2.start || bstrcmp(str1, str2) == 0;
+}
+
+static inline bool bstr_equals0(struct bstr str1, const char *str2)
+{
+ return bstr_equals(str1, bstr0(str2));
+}
+
+static inline int bstrcasecmp0(struct bstr str1, const char *str2)
+{
+ return bstrcasecmp(str1, bstr0(str2));
+}
+
+static inline int bstr_find0(struct bstr haystack, const char *needle)
+{
+ return bstr_find(haystack, bstr0(needle));
+}
+
+static inline bool bstr_eatstart0(struct bstr *s, const char *prefix)
+{
+ return bstr_eatstart(s, bstr0(prefix));
+}
+
+static inline bool bstr_eatend0(struct bstr *s, const char *prefix)
+{
+ return bstr_eatend(s, bstr0(prefix));
+}
+
+// create a pair (not single value!) for "%.*s" printf syntax
+#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")
+
+#define WHITESPACE " \f\n\r\t\v"
+
+#endif /* MPLAYER_BSTR_H */
diff --git a/misc/charset_conv.c b/misc/charset_conv.c
new file mode 100644
index 0000000..b54f636
--- /dev/null
+++ b/misc/charset_conv.c
@@ -0,0 +1,235 @@
+/*
+ * This file is part of mpv.
+ *
+ * Based on code taken from libass (ISC license), which was originally part
+ * of MPlayer (GPL).
+ * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <strings.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include "common/msg.h"
+
+#if HAVE_UCHARDET
+#include <uchardet.h>
+#endif
+
+#if HAVE_ICONV
+#include <iconv.h>
+#endif
+
+#include "charset_conv.h"
+
+bool mp_charset_is_utf8(const char *user_cp)
+{
+ return user_cp && (strcasecmp(user_cp, "utf8") == 0 ||
+ strcasecmp(user_cp, "utf-8") == 0);
+}
+
+bool mp_charset_is_utf16(const char *user_cp)
+{
+ bstr s = bstr0(user_cp);
+ return bstr_case_startswith(s, bstr0("utf16")) ||
+ bstr_case_startswith(s, bstr0("utf-16"));
+}
+
+static const char *const utf_bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"};
+static const char *const utf_enc[3] = {"utf-8", "utf-16le", "utf-16be"};
+
+static const char *ms_bom_guess(bstr buf)
+{
+ for (int n = 0; n < 3; n++) {
+ if (bstr_startswith0(buf, utf_bom[n]))
+ return utf_enc[n];
+ }
+ return NULL;
+}
+
+#if HAVE_UCHARDET
+static const char *mp_uchardet(void *talloc_ctx, struct mp_log *log, bstr buf)
+{
+ uchardet_t det = uchardet_new();
+ if (!det)
+ return NULL;
+ if (uchardet_handle_data(det, buf.start, buf.len) != 0) {
+ uchardet_delete(det);
+ return NULL;
+ }
+ uchardet_data_end(det);
+ char *res = talloc_strdup(talloc_ctx, uchardet_get_charset(det));
+ if (res && !res[0])
+ res = NULL;
+ if (res) {
+ mp_verbose(log, "libuchardet detected charset as %s\n", res);
+ iconv_t icdsc = iconv_open("UTF-8", res);
+ if (icdsc == (iconv_t)(-1)) {
+ mp_warn(log, "Charset '%s' not supported by iconv.\n", res);
+ res = NULL;
+ } else {
+ iconv_close(icdsc);
+ }
+ }
+ uchardet_delete(det);
+ return res;
+}
+#endif
+
+// Runs charset auto-detection on the input buffer, and returns the result.
+// If auto-detection fails, NULL is returned.
+// If user_cp doesn't refer to any known auto-detection (for example because
+// it's a real iconv codepage), user_cp is returned without even looking at
+// the buf data.
+// The return value may (but doesn't have to) be allocated under talloc_ctx.
+const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
+ const char *user_cp, int flags)
+{
+ if (user_cp[0] == '+') {
+ mp_verbose(log, "Forcing charset '%s'.\n", user_cp + 1);
+ return user_cp + 1;
+ }
+
+ const char *bom_cp = ms_bom_guess(buf);
+ if (bom_cp) {
+ mp_verbose(log, "Data has a BOM, assuming %s as charset.\n", bom_cp);
+ return bom_cp;
+ }
+
+ int r = bstr_validate_utf8(buf);
+ if (r >= 0 || (r > -8 && (flags & MP_ICONV_ALLOW_CUTOFF))) {
+ if (strcmp(user_cp, "auto") != 0 && !mp_charset_is_utf8(user_cp))
+ mp_verbose(log, "Data looks like UTF-8, ignoring user-provided charset.\n");
+ return "utf-8";
+ }
+
+ const char *res = NULL;
+ if (strcasecmp(user_cp, "auto") == 0) {
+#if HAVE_UCHARDET
+ res = mp_uchardet(talloc_ctx, log, buf);
+#endif
+ if (!res) {
+ mp_verbose(log, "Charset auto-detection failed.\n");
+ res = "UTF-8-BROKEN";
+ }
+ } else {
+ res = user_cp;
+ }
+
+ mp_verbose(log, "Using charset '%s'.\n", res);
+ return res;
+}
+
+// Use iconv to convert buf to UTF-8.
+// Returns buf.start==NULL on error. Returns buf if cp is NULL, or if there is
+// obviously no conversion required (e.g. if cp is "UTF-8").
+// Returns a newly allocated buffer if conversion is done and succeeds. The
+// buffer will be terminated with 0 for convenience (the terminating 0 is not
+// included in the returned length).
+// Free the returned buffer with talloc_free().
+// buf: input data
+// cp: iconv codepage (or NULL)
+// flags: combination of MP_ICONV_* flags
+// returns: buf (no conversion), .start==NULL (error), or allocated buffer
+bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags)
+{
+#if HAVE_ICONV
+ if (!buf.len)
+ return buf;
+
+ if (!cp || !cp[0] || mp_charset_is_utf8(cp))
+ return buf;
+
+ if (strcasecmp(cp, "ASCII") == 0)
+ return buf;
+
+ if (strcasecmp(cp, "UTF-8-BROKEN") == 0)
+ return bstr_sanitize_utf8_latin1(NULL, buf);
+
+ // Force CP949 over EUC-KR since iconv distinguishes them and
+ // EUC-KR causes error on CP949 encoded data
+ if (strcasecmp(cp, "EUC-KR") == 0)
+ cp = "CP949";
+
+ iconv_t icdsc;
+ if ((icdsc = iconv_open("UTF-8", cp)) == (iconv_t) (-1)) {
+ if (flags & MP_ICONV_VERBOSE)
+ mp_err(log, "Error opening iconv with codepage '%s'\n", cp);
+ goto failure;
+ }
+
+ size_t size = buf.len;
+ size_t osize = size;
+ size_t ileft = size;
+ size_t oleft = size - 1;
+
+ char *outbuf = talloc_size(NULL, osize);
+ char *ip = buf.start;
+ char *op = outbuf;
+
+ while (1) {
+ int clear = 0;
+ size_t rc;
+ if (ileft)
+ rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
+ else {
+ clear = 1; // clear the conversion state and leave
+ rc = iconv(icdsc, NULL, NULL, &op, &oleft);
+ }
+ if (rc == (size_t) (-1)) {
+ if (errno == E2BIG) {
+ size_t offset = op - outbuf;
+ outbuf = talloc_realloc_size(NULL, outbuf, osize + size);
+ op = outbuf + offset;
+ osize += size;
+ oleft += size;
+ } else {
+ if (errno == EINVAL && (flags & MP_ICONV_ALLOW_CUTOFF)) {
+ // This is intended for cases where the input buffer is cut
+ // at a random byte position. If this happens in the middle
+ // of the buffer, it should still be an error. We say it's
+ // fine if the error is within 10 bytes of the end.
+ if (ileft <= 10)
+ break;
+ }
+ if (flags & MP_ICONV_VERBOSE) {
+ mp_err(log, "Error recoding text with codepage '%s'\n", cp);
+ }
+ talloc_free(outbuf);
+ iconv_close(icdsc);
+ goto failure;
+ }
+ } else if (clear)
+ break;
+ }
+
+ iconv_close(icdsc);
+
+ outbuf[osize - oleft - 1] = 0;
+ return (bstr){outbuf, osize - oleft - 1};
+
+failure:
+#endif
+
+ if (flags & MP_NO_LATIN1_FALLBACK) {
+ return buf;
+ } else {
+ return bstr_sanitize_utf8_latin1(NULL, buf);
+ }
+}
diff --git a/misc/charset_conv.h b/misc/charset_conv.h
new file mode 100644
index 0000000..ccaa17e
--- /dev/null
+++ b/misc/charset_conv.h
@@ -0,0 +1,22 @@
+#ifndef MP_CHARSET_CONV_H
+#define MP_CHARSET_CONV_H
+
+#include <stdbool.h>
+#include "misc/bstr.h"
+
+struct mp_log;
+
+enum {
+ MP_ICONV_VERBOSE = 1, // print errors instead of failing silently
+ MP_ICONV_ALLOW_CUTOFF = 2, // allow partial input data
+ MP_STRICT_UTF8 = 4, // don't fall back to UTF-8-BROKEN when guessing
+ MP_NO_LATIN1_FALLBACK = 8, // fall back to input buffer instead of latin1
+};
+
+bool mp_charset_is_utf8(const char *user_cp);
+bool mp_charset_is_utf16(const char *user_cp);
+const char *mp_charset_guess(void *talloc_ctx, struct mp_log *log, bstr buf,
+ const char *user_cp, int flags);
+bstr mp_iconv_to_utf8(struct mp_log *log, bstr buf, const char *cp, int flags);
+
+#endif
diff --git a/misc/ctype.h b/misc/ctype.h
new file mode 100644
index 0000000..cbff799
--- /dev/null
+++ b/misc/ctype.h
@@ -0,0 +1,19 @@
+#ifndef MP_CTYPE_H_
+#define MP_CTYPE_H_
+
+// Roughly follows C semantics, but doesn't account for EOF, allows char as
+// parameter, and is locale independent (always uses "C" locale).
+
+static inline int mp_isprint(char c) { return (unsigned char)c >= 32; }
+static inline int mp_isspace(char c) { return c == ' ' || c == '\f' || c == '\n' ||
+ c == '\r' || c == '\t' || c =='\v'; }
+static inline int mp_isupper(char c) { return c >= 'A' && c <= 'Z'; }
+static inline int mp_islower(char c) { return c >= 'a' && c <= 'z'; }
+static inline int mp_isdigit(char c) { return c >= '0' && c <= '9'; }
+static inline int mp_isalpha(char c) { return mp_isupper(c) || mp_islower(c); }
+static inline int mp_isalnum(char c) { return mp_isalpha(c) || mp_isdigit(c); }
+
+static inline char mp_tolower(char c) { return mp_isupper(c) ? c - 'A' + 'a' : c; }
+static inline char mp_toupper(char c) { return mp_islower(c) ? c - 'a' + 'A' : c; }
+
+#endif
diff --git a/misc/dispatch.c b/misc/dispatch.c
new file mode 100644
index 0000000..6fd9fe1
--- /dev/null
+++ b/misc/dispatch.c
@@ -0,0 +1,417 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdbool.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "osdep/threads.h"
+#include "osdep/timer.h"
+
+#include "dispatch.h"
+
+struct mp_dispatch_queue {
+ struct mp_dispatch_item *head, *tail;
+ mp_mutex lock;
+ mp_cond cond;
+ void (*wakeup_fn)(void *wakeup_ctx);
+ void *wakeup_ctx;
+ void (*onlock_fn)(void *onlock_ctx);
+ void *onlock_ctx;
+ // Time at which mp_dispatch_queue_process() should return.
+ int64_t wait;
+ // Make mp_dispatch_queue_process() exit if it's idle.
+ bool interrupted;
+ // The target thread is in mp_dispatch_queue_process() (and either idling,
+ // locked, or running a dispatch callback).
+ bool in_process;
+ mp_thread_id in_process_thread_id;
+ // The target thread is in mp_dispatch_queue_process(), and currently
+ // something has exclusive access to it (e.g. running a dispatch callback,
+ // or a different thread got it with mp_dispatch_lock()).
+ bool locked;
+ // A mp_dispatch_lock() call is requesting an exclusive lock.
+ size_t lock_requests;
+ // locked==true is due to a mp_dispatch_lock() call (for debugging).
+ bool locked_explicit;
+ mp_thread_id locked_explicit_thread_id;
+};
+
+struct mp_dispatch_item {
+ mp_dispatch_fn fn;
+ void *fn_data;
+ bool asynchronous;
+ bool mergeable;
+ bool completed;
+ struct mp_dispatch_item *next;
+};
+
+static void queue_dtor(void *p)
+{
+ struct mp_dispatch_queue *queue = p;
+ assert(!queue->head);
+ assert(!queue->in_process);
+ assert(!queue->lock_requests);
+ assert(!queue->locked);
+ mp_cond_destroy(&queue->cond);
+ mp_mutex_destroy(&queue->lock);
+}
+
+// A dispatch queue lets other threads run callbacks in a target thread.
+// The target thread is the thread which calls mp_dispatch_queue_process().
+// Free the dispatch queue with talloc_free(). At the time of destruction,
+// the queue must be empty. The easiest way to guarantee this is to
+// terminate all potential senders, then call mp_dispatch_run() with a
+// function that e.g. makes the target thread exit, then mp_thread_join() the
+// target thread, and finally destroy the queue. Another way is calling
+// mp_dispatch_queue_process() after terminating all potential senders, and
+// then destroying the queue.
+struct mp_dispatch_queue *mp_dispatch_create(void *ta_parent)
+{
+ struct mp_dispatch_queue *queue = talloc_ptrtype(ta_parent, queue);
+ *queue = (struct mp_dispatch_queue){0};
+ talloc_set_destructor(queue, queue_dtor);
+ mp_mutex_init(&queue->lock);
+ mp_cond_init(&queue->cond);
+ return queue;
+}
+
+// Set a custom function that should be called to guarantee that the target
+// thread wakes up. This is intended for use with code that needs to block
+// on non-pthread primitives, such as e.g. select(). In the case of select(),
+// the wakeup_fn could for example write a byte into a "wakeup" pipe in order
+// to unblock the select(). The wakeup_fn is called from the dispatch queue
+// when there are new dispatch items, and the target thread should then enter
+// mp_dispatch_queue_process() as soon as possible.
+// Note that this setter does not do internal synchronization, so you must set
+// it before other threads see it.
+void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
+ void (*wakeup_fn)(void *wakeup_ctx),
+ void *wakeup_ctx)
+{
+ queue->wakeup_fn = wakeup_fn;
+ queue->wakeup_ctx = wakeup_ctx;
+}
+
+// Set a function that will be called by mp_dispatch_lock() if the target thread
+// is not calling mp_dispatch_queue_process() right now. This is an obscure,
+// optional mechanism to make a worker thread react to external events more
+// quickly. The idea is that the callback will make the worker thread to stop
+// doing whatever (e.g. by setting a flag), and call mp_dispatch_queue_process()
+// in order to let mp_dispatch_lock() calls continue sooner.
+// Like wakeup_fn, this setter does no internal synchronization, and you must
+// not access the dispatch queue itself from the callback.
+void mp_dispatch_set_onlock_fn(struct mp_dispatch_queue *queue,
+ void (*onlock_fn)(void *onlock_ctx),
+ void *onlock_ctx)
+{
+ queue->onlock_fn = onlock_fn;
+ queue->onlock_ctx = onlock_ctx;
+}
+
+static void mp_dispatch_append(struct mp_dispatch_queue *queue,
+ struct mp_dispatch_item *item)
+{
+ mp_mutex_lock(&queue->lock);
+ if (item->mergeable) {
+ for (struct mp_dispatch_item *cur = queue->head; cur; cur = cur->next) {
+ if (cur->mergeable && cur->fn == item->fn &&
+ cur->fn_data == item->fn_data)
+ {
+ talloc_free(item);
+ mp_mutex_unlock(&queue->lock);
+ return;
+ }
+ }
+ }
+
+ if (queue->tail) {
+ queue->tail->next = item;
+ } else {
+ queue->head = item;
+ }
+ queue->tail = item;
+
+ // Wake up the main thread; note that other threads might wait on this
+ // condition for reasons, so broadcast the condition.
+ mp_cond_broadcast(&queue->cond);
+ // No wakeup callback -> assume mp_dispatch_queue_process() needs to be
+ // interrupted instead.
+ if (!queue->wakeup_fn)
+ queue->interrupted = true;
+ mp_mutex_unlock(&queue->lock);
+
+ if (queue->wakeup_fn)
+ queue->wakeup_fn(queue->wakeup_ctx);
+}
+
+// Enqueue a callback to run it on the target thread asynchronously. The target
+// thread will run fn(fn_data) as soon as it enter mp_dispatch_queue_process.
+// Note that mp_dispatch_enqueue() will usually return long before that happens.
+// It's up to the user to signal completion of the callback. It's also up to
+// the user to guarantee that the context fn_data has correct lifetime, i.e.
+// lives until the callback is run, and is freed after that.
+void mp_dispatch_enqueue(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data)
+{
+ struct mp_dispatch_item *item = talloc_ptrtype(NULL, item);
+ *item = (struct mp_dispatch_item){
+ .fn = fn,
+ .fn_data = fn_data,
+ .asynchronous = true,
+ };
+ mp_dispatch_append(queue, item);
+}
+
+// Like mp_dispatch_enqueue(), but the queue code will call talloc_free(fn_data)
+// after the fn callback has been run. (The callback could trivially do that
+// itself, but it makes it easier to implement synchronous and asynchronous
+// requests with the same callback implementation.)
+void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data)
+{
+ struct mp_dispatch_item *item = talloc_ptrtype(NULL, item);
+ *item = (struct mp_dispatch_item){
+ .fn = fn,
+ .fn_data = talloc_steal(item, fn_data),
+ .asynchronous = true,
+ };
+ mp_dispatch_append(queue, item);
+}
+
+// Like mp_dispatch_enqueue(), but
+void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data)
+{
+ struct mp_dispatch_item *item = talloc_ptrtype(NULL, item);
+ *item = (struct mp_dispatch_item){
+ .fn = fn,
+ .fn_data = fn_data,
+ .mergeable = true,
+ .asynchronous = true,
+ };
+ mp_dispatch_append(queue, item);
+}
+
+// Remove already queued item. Only items enqueued with the following functions
+// can be canceled:
+// - mp_dispatch_enqueue()
+// - mp_dispatch_enqueue_notify()
+// Items which were enqueued, and which are currently executing, can not be
+// canceled anymore. This function is mostly for being called from the same
+// context as mp_dispatch_queue_process(), where the "currently executing" case
+// can be excluded.
+void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data)
+{
+ mp_mutex_lock(&queue->lock);
+ struct mp_dispatch_item **pcur = &queue->head;
+ queue->tail = NULL;
+ while (*pcur) {
+ struct mp_dispatch_item *cur = *pcur;
+ if (cur->fn == fn && cur->fn_data == fn_data) {
+ *pcur = cur->next;
+ talloc_free(cur);
+ } else {
+ queue->tail = cur;
+ pcur = &cur->next;
+ }
+ }
+ mp_mutex_unlock(&queue->lock);
+}
+
+// Run fn(fn_data) on the target thread synchronously. This function enqueues
+// the callback and waits until the target thread is done doing this.
+// This is redundant to calling the function inside mp_dispatch_[un]lock(),
+// but can be helpful with code that relies on TLS (such as OpenGL).
+void mp_dispatch_run(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data)
+{
+ struct mp_dispatch_item item = {
+ .fn = fn,
+ .fn_data = fn_data,
+ };
+ mp_dispatch_append(queue, &item);
+
+ mp_mutex_lock(&queue->lock);
+ while (!item.completed)
+ mp_cond_wait(&queue->cond, &queue->lock);
+ mp_mutex_unlock(&queue->lock);
+}
+
+// Process any outstanding dispatch items in the queue. This also handles
+// suspending or locking the this thread from another thread via
+// mp_dispatch_lock().
+// The timeout specifies the minimum wait time. The actual time spent in this
+// function can be much higher if the suspending/locking functions are used, or
+// if executing the dispatch items takes time. On the other hand, this function
+// can return much earlier than the timeout due to sporadic wakeups.
+// Note that this will strictly return only after:
+// - timeout has passed,
+// - all queue items were processed,
+// - the possibly acquired lock has been released
+// It's possible to cancel the timeout by calling mp_dispatch_interrupt().
+// Reentrant calls are not allowed. There can be only 1 thread calling
+// mp_dispatch_queue_process() at a time. In addition, mp_dispatch_lock() can
+// not be called from a thread that is calling mp_dispatch_queue_process() (i.e.
+// no enqueued callback can call the lock/unlock functions).
+void mp_dispatch_queue_process(struct mp_dispatch_queue *queue, double timeout)
+{
+ mp_mutex_lock(&queue->lock);
+ queue->wait = timeout > 0 ? mp_time_ns_add(mp_time_ns(), timeout) : 0;
+ assert(!queue->in_process); // recursion not allowed
+ queue->in_process = true;
+ queue->in_process_thread_id = mp_thread_current_id();
+ // Wake up thread which called mp_dispatch_lock().
+ if (queue->lock_requests)
+ mp_cond_broadcast(&queue->cond);
+ while (1) {
+ if (queue->lock_requests) {
+ // Block due to something having called mp_dispatch_lock().
+ mp_cond_wait(&queue->cond, &queue->lock);
+ } else if (queue->head) {
+ struct mp_dispatch_item *item = queue->head;
+ queue->head = item->next;
+ if (!queue->head)
+ queue->tail = NULL;
+ item->next = NULL;
+ // Unlock, because we want to allow other threads to queue items
+ // while the dispatch item is processed.
+ // At the same time, we must prevent other threads from returning
+ // from mp_dispatch_lock(), which is done by locked=true.
+ assert(!queue->locked);
+ queue->locked = true;
+ mp_mutex_unlock(&queue->lock);
+
+ item->fn(item->fn_data);
+
+ mp_mutex_lock(&queue->lock);
+ assert(queue->locked);
+ queue->locked = false;
+ // Wakeup mp_dispatch_run(), also mp_dispatch_lock().
+ mp_cond_broadcast(&queue->cond);
+ if (item->asynchronous) {
+ talloc_free(item);
+ } else {
+ item->completed = true;
+ }
+ } else if (queue->wait > 0 && !queue->interrupted) {
+ if (mp_cond_timedwait_until(&queue->cond, &queue->lock, queue->wait))
+ queue->wait = 0;
+ } else {
+ break;
+ }
+ }
+ assert(!queue->locked);
+ queue->in_process = false;
+ queue->interrupted = false;
+ mp_mutex_unlock(&queue->lock);
+}
+
+// If the queue is inside of mp_dispatch_queue_process(), make it return as
+// soon as all work items have been run, without waiting for the timeout. This
+// does not make it return early if it's blocked by a mp_dispatch_lock().
+// If the queue is _not_ inside of mp_dispatch_queue_process(), make the next
+// call of it use a timeout of 0 (this is useful behavior if you need to
+// wakeup the main thread from another thread in a race free way).
+void mp_dispatch_interrupt(struct mp_dispatch_queue *queue)
+{
+ mp_mutex_lock(&queue->lock);
+ queue->interrupted = true;
+ mp_cond_broadcast(&queue->cond);
+ mp_mutex_unlock(&queue->lock);
+}
+
+// If a mp_dispatch_queue_process() call is in progress, then adjust the maximum
+// time it blocks due to its timeout argument. Otherwise does nothing. (It
+// makes sense to call this in code that uses both mp_dispatch_[un]lock() and
+// a normal event loop.)
+// Does not work correctly with queues that have mp_dispatch_set_wakeup_fn()
+// called on them, because this implies you actually do waiting via
+// mp_dispatch_queue_process(), while wakeup callbacks are used when you need
+// to wait in external APIs.
+void mp_dispatch_adjust_timeout(struct mp_dispatch_queue *queue, int64_t until)
+{
+ mp_mutex_lock(&queue->lock);
+ if (queue->in_process && queue->wait > until) {
+ queue->wait = until;
+ mp_cond_broadcast(&queue->cond);
+ }
+ mp_mutex_unlock(&queue->lock);
+}
+
+// Grant exclusive access to the target thread's state. While this is active,
+// no other thread can return from mp_dispatch_lock() (i.e. it behaves like
+// a pthread mutex), and no other thread can get dispatch items completed.
+// Other threads can still queue asynchronous dispatch items without waiting,
+// and the mutex behavior applies to this function and dispatch callbacks only.
+// The lock is non-recursive, and dispatch callback functions can be thought of
+// already holding the dispatch lock.
+void mp_dispatch_lock(struct mp_dispatch_queue *queue)
+{
+ mp_mutex_lock(&queue->lock);
+ // Must not be called recursively from dispatched callbacks.
+ if (queue->in_process)
+ assert(!mp_thread_id_equal(queue->in_process_thread_id, mp_thread_current_id()));
+ // Must not be called recursively at all.
+ if (queue->locked_explicit)
+ assert(!mp_thread_id_equal(queue->locked_explicit_thread_id, mp_thread_current_id()));
+ queue->lock_requests += 1;
+ // And now wait until the target thread gets "trapped" within the
+ // mp_dispatch_queue_process() call, which will mean we get exclusive
+ // access to the target's thread state.
+ if (queue->onlock_fn)
+ queue->onlock_fn(queue->onlock_ctx);
+ while (!queue->in_process) {
+ mp_mutex_unlock(&queue->lock);
+ if (queue->wakeup_fn)
+ queue->wakeup_fn(queue->wakeup_ctx);
+ mp_mutex_lock(&queue->lock);
+ if (queue->in_process)
+ break;
+ mp_cond_wait(&queue->cond, &queue->lock);
+ }
+ // Wait until we can get the lock.
+ while (!queue->in_process || queue->locked)
+ mp_cond_wait(&queue->cond, &queue->lock);
+ // "Lock".
+ assert(queue->lock_requests);
+ assert(!queue->locked);
+ assert(!queue->locked_explicit);
+ queue->locked = true;
+ queue->locked_explicit = true;
+ queue->locked_explicit_thread_id = mp_thread_current_id();
+ mp_mutex_unlock(&queue->lock);
+}
+
+// Undo mp_dispatch_lock().
+void mp_dispatch_unlock(struct mp_dispatch_queue *queue)
+{
+ mp_mutex_lock(&queue->lock);
+ assert(queue->locked);
+ // Must be called after a mp_dispatch_lock(), from the same thread.
+ assert(queue->locked_explicit);
+ assert(mp_thread_id_equal(queue->locked_explicit_thread_id, mp_thread_current_id()));
+ // "Unlock".
+ queue->locked = false;
+ queue->locked_explicit = false;
+ queue->lock_requests -= 1;
+ // Wakeup mp_dispatch_queue_process(), and maybe other mp_dispatch_lock()s.
+ // (Would be nice to wake up only 1 other locker if lock_requests>0.)
+ mp_cond_broadcast(&queue->cond);
+ mp_mutex_unlock(&queue->lock);
+}
diff --git a/misc/dispatch.h b/misc/dispatch.h
new file mode 100644
index 0000000..fbf8260
--- /dev/null
+++ b/misc/dispatch.h
@@ -0,0 +1,32 @@
+#ifndef MP_DISPATCH_H_
+#define MP_DISPATCH_H_
+
+#include <stdint.h>
+
+typedef void (*mp_dispatch_fn)(void *data);
+struct mp_dispatch_queue;
+
+struct mp_dispatch_queue *mp_dispatch_create(void *talloc_parent);
+void mp_dispatch_set_wakeup_fn(struct mp_dispatch_queue *queue,
+ void (*wakeup_fn)(void *wakeup_ctx),
+ void *wakeup_ctx);
+void mp_dispatch_set_onlock_fn(struct mp_dispatch_queue *queue,
+ void (*onlock_fn)(void *onlock_ctx),
+ void *onlock_ctx);
+void mp_dispatch_enqueue(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data);
+void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data);
+void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data);
+void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data);
+void mp_dispatch_run(struct mp_dispatch_queue *queue,
+ mp_dispatch_fn fn, void *fn_data);
+void mp_dispatch_queue_process(struct mp_dispatch_queue *queue, double timeout);
+void mp_dispatch_interrupt(struct mp_dispatch_queue *queue);
+void mp_dispatch_adjust_timeout(struct mp_dispatch_queue *queue, int64_t until);
+void mp_dispatch_lock(struct mp_dispatch_queue *queue);
+void mp_dispatch_unlock(struct mp_dispatch_queue *queue);
+
+#endif
diff --git a/misc/jni.c b/misc/jni.c
new file mode 100644
index 0000000..82f6356
--- /dev/null
+++ b/misc/jni.c
@@ -0,0 +1,429 @@
+/*
+ * JNI utility functions
+ *
+ * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libavcodec/jni.h>
+#include <libavutil/mem.h>
+#include <libavutil/bprint.h>
+#include <stdlib.h>
+
+#include "jni.h"
+#include "osdep/threads.h"
+
+static JavaVM *java_vm;
+static pthread_key_t current_env;
+static mp_once once = MP_STATIC_ONCE_INITIALIZER;
+static mp_static_mutex lock = MP_STATIC_MUTEX_INITIALIZER;
+
+static void jni_detach_env(void *data)
+{
+ if (java_vm) {
+ (*java_vm)->DetachCurrentThread(java_vm);
+ }
+}
+
+static void jni_create_pthread_key(void)
+{
+ pthread_key_create(&current_env, jni_detach_env);
+}
+
+JNIEnv *mp_jni_get_env(struct mp_log *log)
+{
+ int ret = 0;
+ JNIEnv *env = NULL;
+
+ mp_mutex_lock(&lock);
+ if (java_vm == NULL) {
+ java_vm = av_jni_get_java_vm(NULL);
+ }
+
+ if (!java_vm) {
+ mp_err(log, "No Java virtual machine has been registered\n");
+ goto done;
+ }
+
+ mp_exec_once(&once, jni_create_pthread_key);
+
+ if ((env = pthread_getspecific(current_env)) != NULL) {
+ goto done;
+ }
+
+ ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
+ switch(ret) {
+ case JNI_EDETACHED:
+ if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
+ mp_err(log, "Failed to attach the JNI environment to the current thread\n");
+ env = NULL;
+ } else {
+ pthread_setspecific(current_env, env);
+ }
+ break;
+ case JNI_OK:
+ break;
+ case JNI_EVERSION:
+ mp_err(log, "The specified JNI version is not supported\n");
+ break;
+ default:
+ mp_err(log, "Failed to get the JNI environment attached to this thread\n");
+ break;
+ }
+
+done:
+ mp_mutex_unlock(&lock);
+ return env;
+}
+
+char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log)
+{
+ char *ret = NULL;
+ const char *utf_chars = NULL;
+
+ jboolean copy = 0;
+
+ if (!string) {
+ return NULL;
+ }
+
+ utf_chars = (*env)->GetStringUTFChars(env, string, &copy);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "String.getStringUTFChars() threw an exception\n");
+ return NULL;
+ }
+
+ ret = av_strdup(utf_chars);
+
+ (*env)->ReleaseStringUTFChars(env, string, utf_chars);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "String.releaseStringUTFChars() threw an exception\n");
+ return NULL;
+ }
+
+ return ret;
+}
+
+jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log)
+{
+ jstring ret;
+
+ ret = (*env)->NewStringUTF(env, utf_chars);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "NewStringUTF() threw an exception\n");
+ return NULL;
+ }
+
+ return ret;
+}
+
+int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log)
+{
+ int ret = 0;
+
+ AVBPrint bp;
+
+ char *name = NULL;
+ char *message = NULL;
+
+ jclass class_class = NULL;
+ jmethodID get_name_id = NULL;
+
+ jclass exception_class = NULL;
+ jmethodID get_message_id = NULL;
+
+ jstring string = NULL;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+ exception_class = (*env)->GetObjectClass(env, exception);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Could not find Throwable class\n");
+ ret = -1;
+ goto done;
+ }
+
+ class_class = (*env)->GetObjectClass(env, exception_class);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Could not find Throwable class's class\n");
+ ret = -1;
+ goto done;
+ }
+
+ get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Could not find method Class.getName()\n");
+ ret = -1;
+ goto done;
+ }
+
+ string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Class.getName() threw an exception\n");
+ ret = -1;
+ goto done;
+ }
+
+ if (string) {
+ name = mp_jni_jstring_to_utf_chars(env, string, log);
+ (*env)->DeleteLocalRef(env, string);
+ string = NULL;
+ }
+
+ get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Could not find method java/lang/Throwable.getMessage()\n");
+ ret = -1;
+ goto done;
+ }
+
+ string = (*env)->CallObjectMethod(env, exception, get_message_id);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionClear(env);
+ mp_err(log, "Throwable.getMessage() threw an exception\n");
+ ret = -1;
+ goto done;
+ }
+
+ if (string) {
+ message = mp_jni_jstring_to_utf_chars(env, string, log);
+ (*env)->DeleteLocalRef(env, string);
+ string = NULL;
+ }
+
+ if (name && message) {
+ av_bprintf(&bp, "%s: %s", name, message);
+ } else if (name && !message) {
+ av_bprintf(&bp, "%s occurred", name);
+ } else if (!name && message) {
+ av_bprintf(&bp, "Exception: %s", message);
+ } else {
+ mp_warn(log, "Could not retrieve exception name and message\n");
+ av_bprintf(&bp, "Exception occurred");
+ }
+
+ ret = av_bprint_finalize(&bp, error);
+done:
+
+ av_free(name);
+ av_free(message);
+
+ if (class_class) {
+ (*env)->DeleteLocalRef(env, class_class);
+ }
+
+ if (exception_class) {
+ (*env)->DeleteLocalRef(env, exception_class);
+ }
+
+ if (string) {
+ (*env)->DeleteLocalRef(env, string);
+ }
+
+ return ret;
+}
+
+int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log)
+{
+ int ret;
+
+ jthrowable exception;
+
+ char *message = NULL;
+
+ if (!(*(env))->ExceptionCheck((env))) {
+ return 0;
+ }
+
+ if (!logging) {
+ (*(env))->ExceptionClear((env));
+ return -1;
+ }
+
+ exception = (*env)->ExceptionOccurred(env);
+ (*(env))->ExceptionClear((env));
+
+ if ((ret = mp_jni_exception_get_summary(env, exception, &message, log)) < 0) {
+ (*env)->DeleteLocalRef(env, exception);
+ return ret;
+ }
+
+ (*env)->DeleteLocalRef(env, exception);
+
+ mp_err(log, "%s\n", message);
+ av_free(message);
+
+ return -1;
+}
+
+int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log)
+{
+ int i, ret = 0;
+ jclass last_clazz = NULL;
+
+ for (i = 0; jfields_mapping[i].name; i++) {
+ int mandatory = jfields_mapping[i].mandatory;
+ enum MPJniFieldType type = jfields_mapping[i].type;
+
+ if (type == MP_JNI_CLASS) {
+ jclass clazz;
+
+ last_clazz = NULL;
+
+ clazz = (*env)->FindClass(env, jfields_mapping[i].name);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+
+ last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
+ global ? (*env)->NewGlobalRef(env, clazz) : clazz;
+
+ if (global) {
+ (*env)->DeleteLocalRef(env, clazz);
+ }
+
+ } else {
+
+ if (!last_clazz) {
+ ret = -1;
+ break;
+ }
+
+ switch(type) {
+ case MP_JNI_FIELD: {
+ jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+
+ *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
+ break;
+ }
+ case MP_JNI_STATIC_FIELD_AS_INT:
+ case MP_JNI_STATIC_FIELD: {
+ jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+
+ if (type == MP_JNI_STATIC_FIELD_AS_INT) {
+ if (field_id) {
+ jint value = (*env)->GetStaticIntField(env, last_clazz, field_id);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+ *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = value;
+ }
+ } else {
+ *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
+ }
+ break;
+ }
+ case MP_JNI_METHOD: {
+ jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+
+ *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
+ break;
+ }
+ case MP_JNI_STATIC_METHOD: {
+ jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
+ if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
+ goto done;
+ }
+
+ *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
+ break;
+ }
+ default:
+ mp_err(log, "Unknown JNI field type\n");
+ ret = -1;
+ goto done;
+ }
+
+ ret = 0;
+ }
+ }
+
+done:
+ if (ret < 0) {
+ /* reset jfields in case of failure so it does not leak references */
+ mp_jni_reset_jfields(env, jfields, jfields_mapping, global, log);
+ }
+
+ return ret;
+}
+
+int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log)
+{
+ int i;
+
+ for (i = 0; jfields_mapping[i].name; i++) {
+ enum MPJniFieldType type = jfields_mapping[i].type;
+
+ switch(type) {
+ case MP_JNI_CLASS: {
+ jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
+ if (!clazz)
+ continue;
+
+ if (global) {
+ (*env)->DeleteGlobalRef(env, clazz);
+ } else {
+ (*env)->DeleteLocalRef(env, clazz);
+ }
+
+ *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
+ break;
+ }
+ case MP_JNI_FIELD: {
+ *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
+ break;
+ }
+ case MP_JNI_STATIC_FIELD: {
+ *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
+ break;
+ }
+ case MP_JNI_STATIC_FIELD_AS_INT: {
+ *(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = 0;
+ break;
+ }
+ case MP_JNI_METHOD: {
+ *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
+ break;
+ }
+ case MP_JNI_STATIC_METHOD: {
+ *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
+ break;
+ }
+ default:
+ mp_err(log, "Unknown JNI field type\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/misc/jni.h b/misc/jni.h
new file mode 100644
index 0000000..c9e4c28
--- /dev/null
+++ b/misc/jni.h
@@ -0,0 +1,161 @@
+/*
+ * JNI utility functions
+ *
+ * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_JNI_H
+#define MP_JNI_H
+
+#include <jni.h>
+#include "common/msg.h"
+
+/* Convenience macros */
+#define MP_JNI_GET_ENV(obj) mp_jni_get_env((obj)->log)
+#define MP_JNI_EXCEPTION_CHECK() mp_jni_exception_check(env, 0, NULL)
+#define MP_JNI_EXCEPTION_LOG(obj) mp_jni_exception_check(env, 1, (obj)->log)
+#define MP_JNI_DO(what, obj, method, ...) (*env)->what(env, obj, method, ##__VA_ARGS__)
+#define MP_JNI_NEW(clazz, method, ...) MP_JNI_DO(NewObject, clazz, method, ##__VA_ARGS__)
+#define MP_JNI_CALL_INT(obj, method, ...) MP_JNI_DO(CallIntMethod, obj, method, ##__VA_ARGS__)
+#define MP_JNI_CALL_BOOL(obj, method, ...) MP_JNI_DO(CallBooleanMethod, obj, method, ##__VA_ARGS__)
+#define MP_JNI_CALL_VOID(obj, method, ...) MP_JNI_DO(CallVoidMethod, obj, method, ##__VA_ARGS__)
+#define MP_JNI_CALL_STATIC_INT(clazz, method, ...) MP_JNI_DO(CallStaticIntMethod, clazz, method, ##__VA_ARGS__)
+#define MP_JNI_CALL_OBJECT(obj, method, ...) MP_JNI_DO(CallObjectMethod, obj, method, ##__VA_ARGS__)
+#define MP_JNI_GET_INT(obj, field) MP_JNI_DO(GetIntField, obj, field)
+#define MP_JNI_GET_LONG(obj, field) MP_JNI_DO(GetLongField, obj, field)
+#define MP_JNI_GET_BOOL(obj, field) MP_JNI_DO(GetBoolField, obj, field)
+
+/*
+ * Attach permanently a JNI environment to the current thread and retrieve it.
+ *
+ * If successfully attached, the JNI environment will automatically be detached
+ * at thread destruction.
+ *
+ * @param attached pointer to an integer that will be set to 1 if the
+ * environment has been attached to the current thread or 0 if it is
+ * already attached.
+ * @param log context used for logging
+ * @return the JNI environment on success, NULL otherwise
+ */
+JNIEnv *mp_jni_get_env(struct mp_log *log);
+
+/*
+ * Convert a jstring to its utf characters equivalent.
+ *
+ * @param env JNI environment
+ * @param string Java string to convert
+ * @param log context used for logging
+ * @return a pointer to an array of unicode characters on success, NULL
+ * otherwise
+ */
+char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log);
+
+/*
+ * Convert utf chars to its jstring equivalent.
+ *
+ * @param env JNI environment
+ * @param utf_chars a pointer to an array of unicode characters
+ * @param log context used for logging
+ * @return a Java string object on success, NULL otherwise
+ */
+jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log);
+
+/*
+ * Extract the error summary from a jthrowable in the form of "className: errorMessage"
+ *
+ * @param env JNI environment
+ * @param exception exception to get the summary from
+ * @param error address pointing to the error, the value is updated if a
+ * summary can be extracted
+ * @param log context used for logging
+ * @return 0 on success, < 0 otherwise
+ */
+int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log);
+
+/*
+ * Check if an exception has occurred,log it using av_log and clear it.
+ *
+ * @param env JNI environment
+ * @param value used to enable logging if an exception has occurred,
+ * 0 disables logging, != 0 enables logging
+ * @param log context used for logging
+ */
+int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log);
+
+/*
+ * Jni field type.
+ */
+enum MPJniFieldType {
+
+ MP_JNI_CLASS,
+ MP_JNI_FIELD,
+ MP_JNI_STATIC_FIELD,
+ MP_JNI_STATIC_FIELD_AS_INT,
+ MP_JNI_METHOD,
+ MP_JNI_STATIC_METHOD
+
+};
+
+/*
+ * Jni field describing a class, a field or a method to be retrieved using
+ * the mp_jni_init_jfields method.
+ */
+struct MPJniField {
+
+ const char *name;
+ const char *method;
+ const char *signature;
+ enum MPJniFieldType type;
+ int offset;
+ int mandatory;
+
+};
+
+/*
+ * Retrieve class references, field ids and method ids to an arbitrary structure.
+ *
+ * @param env JNI environment
+ * @param jfields a pointer to an arbitrary structure where the different
+ * fields are declared and where the MPJNIField mapping table offsets are
+ * pointing to
+ * @param jfields_mapping null terminated array of MPJNIFields describing
+ * the class/field/method to be retrieved
+ * @param global make the classes references global. It is the caller
+ * responsibility to properly release global references.
+ * @param log_ctx context used for logging, can be NULL
+ * @return 0 on success, < 0 otherwise
+ */
+int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log);
+
+/*
+ * Delete class references, field ids and method ids of an arbitrary structure.
+ *
+ * @param env JNI environment
+ * @param jfields a pointer to an arbitrary structure where the different
+ * fields are declared and where the MPJNIField mapping table offsets are
+ * pointing to
+ * @param jfields_mapping null terminated array of MPJNIFields describing
+ * the class/field/method to be deleted
+ * @param global treat the classes references as global and delete them
+ * accordingly
+ * @param log_ctx context used for logging, can be NULL
+ * @return 0 on success, < 0 otherwise
+ */
+int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log);
+
+#endif
diff --git a/misc/json.c b/misc/json.c
new file mode 100644
index 0000000..608cfad
--- /dev/null
+++ b/misc/json.c
@@ -0,0 +1,359 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* JSON parser:
+ *
+ * Unlike standard JSON, \u escapes don't allow you to specify UTF-16 surrogate
+ * pairs. There may be some differences how numbers are parsed (this parser
+ * doesn't verify what's passed to strtod(), and also prefers parsing numbers
+ * as integers with stroll() if possible).
+ *
+ * It has some non-standard extensions which shouldn't conflict with JSON:
+ * - a list or object item can have a trailing ","
+ * - object syntax accepts "=" in addition of ":"
+ * - object keys can be unquoted, if they start with a character in [A-Za-z_]
+ * and contain only characters in [A-Za-z0-9_]
+ * - byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
+ *
+ * Also see: http://tools.ietf.org/html/rfc8259
+ *
+ * JSON writer:
+ *
+ * Doesn't insert whitespace. It's literally a waste of space.
+ *
+ * Can output invalid UTF-8, if input is invalid UTF-8. Consumers are supposed
+ * to deal with somehow: either by using byte-strings for JSON, or by running
+ * a "fixup" pass on the input data. The latter could for example change
+ * invalid UTF-8 sequences to replacement characters.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "misc/bstr.h"
+#include "misc/ctype.h"
+
+#include "json.h"
+
+static bool eat_c(char **s, char c)
+{
+ if (**s == c) {
+ *s += 1;
+ return true;
+ }
+ return false;
+}
+
+static void eat_ws(char **src)
+{
+ while (1) {
+ char c = **src;
+ if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
+ return;
+ *src += 1;
+ }
+}
+
+void json_skip_whitespace(char **src)
+{
+ eat_ws(src);
+}
+
+static int read_id(void *ta_parent, struct mpv_node *dst, char **src)
+{
+ char *start = *src;
+ if (!mp_isalpha(**src) && **src != '_')
+ return -1;
+ while (mp_isalnum(**src) || **src == '_')
+ *src += 1;
+ if (**src == ' ') {
+ **src = '\0'; // we're allowed to mutate it => can avoid the strndup
+ *src += 1;
+ } else {
+ start = talloc_strndup(ta_parent, start, *src - start);
+ }
+ dst->format = MPV_FORMAT_STRING;
+ dst->u.string = start;
+ return 0;
+}
+
+static int read_str(void *ta_parent, struct mpv_node *dst, char **src)
+{
+ if (!eat_c(src, '"'))
+ return -1; // not a string
+ char *str = *src;
+ char *cur = str;
+ bool has_escapes = false;
+ while (cur[0] && cur[0] != '"') {
+ if (cur[0] == '\\') {
+ has_escapes = true;
+ // skip >\"< and >\\< (latter to handle >\\"< correctly)
+ if (cur[1] == '"' || cur[1] == '\\')
+ cur++;
+ }
+ cur++;
+ }
+ if (cur[0] != '"')
+ return -1; // invalid termination
+ // Mutate input string so we have a null-terminated string to the literal.
+ // This is a stupid micro-optimization, so we can avoid allocation.
+ cur[0] = '\0';
+ *src = cur + 1;
+ if (has_escapes) {
+ bstr unescaped = {0};
+ bstr r = bstr0(str);
+ if (!mp_append_escaped_string(ta_parent, &unescaped, &r))
+ return -1; // broken escapes
+ str = unescaped.start; // the function guarantees null-termination
+ }
+ dst->format = MPV_FORMAT_STRING;
+ dst->u.string = str;
+ return 0;
+}
+
+static int read_sub(void *ta_parent, struct mpv_node *dst, char **src,
+ int max_depth)
+{
+ bool is_arr = eat_c(src, '[');
+ bool is_obj = !is_arr && eat_c(src, '{');
+ if (!is_arr && !is_obj)
+ return -1; // not an array or object
+ char term = is_obj ? '}' : ']';
+ struct mpv_node_list *list = talloc_zero(ta_parent, struct mpv_node_list);
+ while (1) {
+ eat_ws(src);
+ if (eat_c(src, term))
+ break;
+ if (list->num > 0 && !eat_c(src, ','))
+ return -1; // missing ','
+ eat_ws(src);
+ // non-standard extension: allow a trailing ","
+ if (eat_c(src, term))
+ break;
+ if (is_obj) {
+ struct mpv_node keynode;
+ // non-standard extension: allow unquoted strings as keys
+ if (read_id(list, &keynode, src) < 0 &&
+ read_str(list, &keynode, src) < 0)
+ return -1; // key is not a string
+ eat_ws(src);
+ // non-standard extension: allow "=" instead of ":"
+ if (!eat_c(src, ':') && !eat_c(src, '='))
+ return -1; // ':' missing
+ eat_ws(src);
+ MP_TARRAY_GROW(list, list->keys, list->num);
+ list->keys[list->num] = keynode.u.string;
+ }
+ MP_TARRAY_GROW(list, list->values, list->num);
+ if (json_parse(ta_parent, &list->values[list->num], src, max_depth) < 0)
+ return -1;
+ list->num++;
+ }
+ dst->format = is_obj ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
+ dst->u.list = list;
+ return 0;
+}
+
+/* Parse the string in *src as JSON, and write the result into *dst.
+ * max_depth limits the recursion and JSON tree depth.
+ * Warning: this overwrites the input string (what *src points to)!
+ * Returns:
+ * 0: success, *dst is valid, *src points to the end (the caller must check
+ * whether *src really terminates)
+ * -1: failure, *dst is invalid, there may be dead allocs under ta_parent
+ * (ta_free_children(ta_parent) is the only way to free them)
+ * The input string can be mutated in both cases. *dst might contain string
+ * elements, which point into the (mutated) input string.
+ */
+int json_parse(void *ta_parent, struct mpv_node *dst, char **src, int max_depth)
+{
+ max_depth -= 1;
+ if (max_depth < 0)
+ return -1;
+
+ eat_ws(src);
+
+ char c = **src;
+ if (!c)
+ return -1; // early EOF
+ if (c == 'n' && strncmp(*src, "null", 4) == 0) {
+ *src += 4;
+ dst->format = MPV_FORMAT_NONE;
+ return 0;
+ } else if (c == 't' && strncmp(*src, "true", 4) == 0) {
+ *src += 4;
+ dst->format = MPV_FORMAT_FLAG;
+ dst->u.flag = 1;
+ return 0;
+ } else if (c == 'f' && strncmp(*src, "false", 5) == 0) {
+ *src += 5;
+ dst->format = MPV_FORMAT_FLAG;
+ dst->u.flag = 0;
+ return 0;
+ } else if (c == '"') {
+ return read_str(ta_parent, dst, src);
+ } else if (c == '[' || c == '{') {
+ return read_sub(ta_parent, dst, src, max_depth);
+ } else if (c == '-' || (c >= '0' && c <= '9')) {
+ // The number could be either a float or an int. JSON doesn't make a
+ // difference, but the client API does.
+ char *nsrci = *src, *nsrcf = *src;
+ errno = 0;
+ long long int numi = strtoll(*src, &nsrci, 0);
+ if (errno)
+ nsrci = *src;
+ errno = 0;
+ double numf = strtod(*src, &nsrcf);
+ if (errno)
+ nsrcf = *src;
+ if (nsrci >= nsrcf) {
+ *src = nsrci;
+ dst->format = MPV_FORMAT_INT64; // long long is usually 64 bits
+ dst->u.int64 = numi;
+ return 0;
+ }
+ if (nsrcf > *src && isfinite(numf)) {
+ *src = nsrcf;
+ dst->format = MPV_FORMAT_DOUBLE;
+ dst->u.double_ = numf;
+ return 0;
+ }
+ return -1;
+ }
+ return -1; // character doesn't start a valid token
+}
+
+
+#define APPEND(b, s) bstr_xappend(NULL, (b), bstr0(s))
+
+static const char special_escape[] = {
+ ['\b'] = 'b',
+ ['\f'] = 'f',
+ ['\n'] = 'n',
+ ['\r'] = 'r',
+ ['\t'] = 't',
+};
+
+static void write_json_str(bstr *b, unsigned char *str)
+{
+ APPEND(b, "\"");
+ while (1) {
+ unsigned char *cur = str;
+ while (cur[0] >= 32 && cur[0] != '"' && cur[0] != '\\')
+ cur++;
+ if (!cur[0])
+ break;
+ bstr_xappend(NULL, b, (bstr){str, cur - str});
+ if (cur[0] == '\"') {
+ bstr_xappend(NULL, b, (bstr){"\\\"", 2});
+ } else if (cur[0] == '\\') {
+ bstr_xappend(NULL, b, (bstr){"\\\\", 2});
+ } else if (cur[0] < sizeof(special_escape) && special_escape[cur[0]]) {
+ bstr_xappend_asprintf(NULL, b, "\\%c", special_escape[cur[0]]);
+ } else {
+ bstr_xappend_asprintf(NULL, b, "\\u%04x", (unsigned char)cur[0]);
+ }
+ str = cur + 1;
+ }
+ APPEND(b, str);
+ APPEND(b, "\"");
+}
+
+static void add_indent(bstr *b, int indent)
+{
+ if (indent < 0)
+ return;
+ bstr_xappend(NULL, b, bstr0("\n"));
+ for (int n = 0; n < indent; n++)
+ bstr_xappend(NULL, b, bstr0(" "));
+}
+
+static int json_append(bstr *b, const struct mpv_node *src, int indent)
+{
+ switch (src->format) {
+ case MPV_FORMAT_NONE:
+ APPEND(b, "null");
+ return 0;
+ case MPV_FORMAT_FLAG:
+ APPEND(b, src->u.flag ? "true" : "false");
+ return 0;
+ case MPV_FORMAT_INT64:
+ bstr_xappend_asprintf(NULL, b, "%"PRId64, src->u.int64);
+ return 0;
+ case MPV_FORMAT_DOUBLE: {
+ const char *px = (isfinite(src->u.double_) || indent == 0) ? "" : "\"";
+ bstr_xappend_asprintf(NULL, b, "%s%f%s", px, src->u.double_, px);
+ return 0;
+ }
+ case MPV_FORMAT_STRING:
+ if (indent == 0)
+ APPEND(b, src->u.string);
+ else
+ write_json_str(b, src->u.string);
+ return 0;
+ case MPV_FORMAT_NODE_ARRAY:
+ case MPV_FORMAT_NODE_MAP: {
+ struct mpv_node_list *list = src->u.list;
+ bool is_obj = src->format == MPV_FORMAT_NODE_MAP;
+ APPEND(b, is_obj ? "{" : "[");
+ int next_indent = indent >= 0 ? indent + 1 : -1;
+ for (int n = 0; n < list->num; n++) {
+ if (n)
+ APPEND(b, ",");
+ add_indent(b, next_indent);
+ if (is_obj) {
+ write_json_str(b, list->keys[n]);
+ APPEND(b, ":");
+ }
+ json_append(b, &list->values[n], next_indent);
+ }
+ add_indent(b, indent);
+ APPEND(b, is_obj ? "}" : "]");
+ return 0;
+ }
+ }
+ return -1; // unknown format
+}
+
+static int json_append_str(char **dst, struct mpv_node *src, int indent)
+{
+ bstr buffer = bstr0(*dst);
+ int r = json_append(&buffer, src, indent);
+ *dst = buffer.start;
+ return r;
+}
+
+/* Write the contents of *src as JSON, and append the JSON string to *dst.
+ * This will use strlen() to determine the start offset, and ta_get_size()
+ * and ta_realloc() to extend the memory allocation of *dst.
+ * Returns: 0 on success, <0 on failure.
+ */
+int json_write(char **dst, struct mpv_node *src)
+{
+ return json_append_str(dst, src, -1);
+}
+
+// Same as json_write(), but add whitespace to make it readable.
+int json_write_pretty(char **dst, struct mpv_node *src)
+{
+ return json_append_str(dst, src, 0);
+}
diff --git a/misc/json.h b/misc/json.h
new file mode 100644
index 0000000..b99fc36
--- /dev/null
+++ b/misc/json.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_JSON_H
+#define MP_JSON_H
+
+// We reuse mpv_node.
+#include "libmpv/client.h"
+
+#define MAX_JSON_DEPTH 50
+
+int json_parse(void *ta_parent, struct mpv_node *dst, char **src, int max_depth);
+void json_skip_whitespace(char **src);
+int json_write(char **s, struct mpv_node *src);
+int json_write_pretty(char **s, struct mpv_node *src);
+
+#endif
diff --git a/misc/language.c b/misc/language.c
new file mode 100644
index 0000000..92857f7
--- /dev/null
+++ b/misc/language.c
@@ -0,0 +1,362 @@
+/*
+ * Language code utility functions
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "language.h"
+
+#include "common/common.h"
+#include "osdep/strnlen.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+static const struct lang {
+ char match[4];
+ char canonical[4];
+} langmap[] = {
+ {"aa", "aar"},
+ {"ab", "abk"},
+ {"ae", "ave"},
+ {"af", "afr"},
+ {"ak", "aka"},
+ {"am", "amh"},
+ {"an", "arg"},
+ {"ar", "ara"},
+ {"as", "asm"},
+ {"av", "ava"},
+ {"ay", "aym"},
+ {"az", "aze"},
+ {"ba", "bak"},
+ {"be", "bel"},
+ {"bg", "bul"},
+ {"bh", "bih"},
+ {"bi", "bis"},
+ {"bm", "bam"},
+ {"bn", "ben"},
+ {"bo", "tib"},
+ {"bod", "tib"},
+ {"br", "bre"},
+ {"bs", "bos"},
+ {"ca", "cat"},
+ {"ce", "che"},
+ {"ces", "cze"},
+ {"ch", "cha"},
+ {"co", "cos"},
+ {"cr", "cre"},
+ {"cs", "cze"},
+ {"cu", "chu"},
+ {"cv", "chv"},
+ {"cy", "wel"},
+ {"cym", "wel"},
+ {"da", "dan"},
+ {"de", "ger"},
+ {"deu", "ger"},
+ {"dv", "div"},
+ {"dz", "dzo"},
+ {"ee", "ewe"},
+ {"el", "gre"},
+ {"ell", "gre"},
+ {"en", "eng"},
+ {"eo", "epo"},
+ {"es", "spa"},
+ {"et", "est"},
+ {"eu", "baq"},
+ {"eus", "baq"},
+ {"fa", "per"},
+ {"fas", "per"},
+ {"ff", "ful"},
+ {"fi", "fin"},
+ {"fj", "fij"},
+ {"fo", "fao"},
+ {"fr", "fre"},
+ {"fra", "fre"},
+ {"fy", "fry"},
+ {"ga", "gle"},
+ {"gd", "gla"},
+ {"gl", "glg"},
+ {"gn", "grn"},
+ {"gu", "guj"},
+ {"gv", "glv"},
+ {"ha", "hau"},
+ {"he", "heb"},
+ {"hi", "hin"},
+ {"ho", "hmo"},
+ {"hr", "hrv"},
+ {"ht", "hat"},
+ {"hu", "hun"},
+ {"hy", "arm"},
+ {"hye", "arm"},
+ {"hz", "her"},
+ {"ia", "ina"},
+ {"id", "ind"},
+ {"ie", "ile"},
+ {"ig", "ibo"},
+ {"ii", "iii"},
+ {"ik", "ipk"},
+ {"io", "ido"},
+ {"is", "ice"},
+ {"isl", "ice"},
+ {"it", "ita"},
+ {"iu", "iku"},
+ {"ja", "jpn"},
+ {"jv", "jav"},
+ {"ka", "geo"},
+ {"kat", "geo"},
+ {"kg", "kon"},
+ {"ki", "kik"},
+ {"kj", "kua"},
+ {"kk", "kaz"},
+ {"kl", "kal"},
+ {"km", "khm"},
+ {"kn", "kan"},
+ {"ko", "kor"},
+ {"kr", "kau"},
+ {"ks", "kas"},
+ {"ku", "kur"},
+ {"kv", "kom"},
+ {"kw", "cor"},
+ {"ky", "kir"},
+ {"la", "lat"},
+ {"lb", "ltz"},
+ {"lg", "lug"},
+ {"li", "lim"},
+ {"ln", "lin"},
+ {"lo", "lao"},
+ {"lt", "lit"},
+ {"lu", "lub"},
+ {"lv", "lav"},
+ {"mg", "mlg"},
+ {"mh", "mah"},
+ {"mi", "mao"},
+ {"mk", "mac"},
+ {"mkd", "mac"},
+ {"ml", "mal"},
+ {"mn", "mon"},
+ {"mr", "mar"},
+ {"mri", "mao"},
+ {"ms", "may"},
+ {"msa", "may"},
+ {"mt", "mlt"},
+ {"my", "bur"},
+ {"mya", "bur"},
+ {"na", "nau"},
+ {"nb", "nob"},
+ {"nd", "nde"},
+ {"ne", "nep"},
+ {"ng", "ndo"},
+ {"nl", "dut"},
+ {"nld", "dut"},
+ {"nn", "nno"},
+ {"no", "nor"},
+ {"nr", "nbl"},
+ {"nv", "nav"},
+ {"ny", "nya"},
+ {"oc", "oci"},
+ {"oj", "oji"},
+ {"om", "orm"},
+ {"or", "ori"},
+ {"os", "oss"},
+ {"pa", "pan"},
+ {"pi", "pli"},
+ {"pl", "pol"},
+ {"ps", "pus"},
+ {"pt", "por"},
+ {"qu", "que"},
+ {"rm", "roh"},
+ {"rn", "run"},
+ {"ro", "rum"},
+ {"ron", "rum"},
+ {"ru", "rus"},
+ {"rw", "kin"},
+ {"sa", "san"},
+ {"sc", "srd"},
+ {"sd", "snd"},
+ {"se", "sme"},
+ {"sg", "sag"},
+ {"si", "sin"},
+ {"sk", "slo"},
+ {"sl", "slv"},
+ {"slk", "slo"},
+ {"sm", "smo"},
+ {"sn", "sna"},
+ {"so", "som"},
+ {"sq", "alb"},
+ {"sqi", "alb"},
+ {"sr", "srp"},
+ {"ss", "ssw"},
+ {"st", "sot"},
+ {"su", "sun"},
+ {"sv", "swe"},
+ {"sw", "swa"},
+ {"ta", "tam"},
+ {"te", "tel"},
+ {"tg", "tgk"},
+ {"th", "tha"},
+ {"ti", "tir"},
+ {"tk", "tuk"},
+ {"tl", "tgl"},
+ {"tn", "tsn"},
+ {"to", "ton"},
+ {"tr", "tur"},
+ {"ts", "tso"},
+ {"tt", "tat"},
+ {"tw", "twi"},
+ {"ty", "tah"},
+ {"ug", "uig"},
+ {"uk", "ukr"},
+ {"ur", "urd"},
+ {"uz", "uzb"},
+ {"ve", "ven"},
+ {"vi", "vie"},
+ {"vo", "vol"},
+ {"wa", "wln"},
+ {"wo", "wol"},
+ {"xh", "xho"},
+ {"yi", "yid"},
+ {"yo", "yor"},
+ {"za", "zha"},
+ {"zh", "chi"},
+ {"zho", "chi"},
+ {"zu", "zul"},
+};
+
+struct langsearch {
+ const char *str;
+ size_t size;
+};
+
+static int lang_compare(const void *s, const void *k)
+{
+ const struct langsearch *search = s;
+ const struct lang *key = k;
+
+ int ret = strncasecmp(search->str, key->match, search->size);
+ if (!ret && search->size < sizeof(key->match) && key->match[search->size])
+ return 1;
+ return ret;
+}
+
+static void canonicalize(const char **lang, size_t *size)
+{
+ if (*size > sizeof(langmap[0].match))
+ return;
+
+ struct langsearch search = {*lang, *size};
+ struct lang *l = bsearch(&search, langmap, MP_ARRAY_SIZE(langmap), sizeof(langmap[0]),
+ &lang_compare);
+
+ if (l) {
+ *lang = l->canonical;
+ *size = strnlen(l->canonical, sizeof(l->canonical));
+ }
+}
+
+static bool tag_matches(const char *l1, size_t s1, const char *l2, size_t s2)
+{
+ return s1 == s2 && !strncasecmp(l1, l2, s1);
+}
+
+int mp_match_lang_single(const char *l1, const char *l2)
+{
+ // We never consider null or empty strings to match
+ if (!l1 || !l2 || !*l1 || !*l2)
+ return 0;
+
+ // The first subtag should always be a language; canonicalize to 3-letter ISO 639-2B (arbitrarily chosen)
+ size_t s1 = strcspn(l1, "-_");
+ size_t s2 = strcspn(l2, "-_");
+
+ const char *l1c = l1;
+ const char *l2c = l2;
+ size_t s1c = s1;
+ size_t s2c = s2;
+
+ canonicalize(&l1c, &s1c);
+ canonicalize(&l2c, &s2c);
+
+ // If the first subtags don't match, we have no match at all
+ if (!tag_matches(l1c, s1c, l2c, s2c))
+ return 0;
+
+ // Attempt to match each subtag in each string against each in the other
+ int score = 1;
+ bool x1 = false;
+ int count = 0;
+ for (;;) {
+ l1 += s1;
+
+ while (*l1 == '-' || *l1 == '_')
+ l1++;
+
+ if (!*l1)
+ break;
+
+ s1 = strcspn(l1, "-_");
+ if (tag_matches(l1, s1, "x", 1)) {
+ x1 = true;
+ continue;
+ }
+
+ const char *l2o = l2;
+ size_t s2o = s2;
+ bool x2 = false;
+ for (;;) {
+ l2 += s2;
+
+ while (*l2 == '-' || *l2 == '_')
+ l2++;
+
+ if (!*l2)
+ break;
+
+ s2 = strcspn(l2, "-_");
+ if (tag_matches(l2, s2, "x", 1)) {
+ x2 = true;
+ if (!x1)
+ break;
+ continue;
+ }
+
+ // Private-use subtags only match against other private-use subtags
+ if (x1 && !x2)
+ continue;
+
+ if (tag_matches(l1c, s1c, l2c, s2c)) {
+ // Matches for subtags earlier in the user's string take priority over later ones,
+ // for up to LANGUAGE_SCORE_BITS subtags
+ int shift = (LANGUAGE_SCORE_BITS - count - 1);
+ if (shift < 0)
+ shift = 0;
+ score += (1 << shift);
+
+ if (score >= LANGUAGE_SCORE_MAX)
+ return LANGUAGE_SCORE_MAX;
+ }
+ }
+
+ l2 = l2o;
+ s2 = s2o;
+
+ count++;
+ }
+
+ return score;
+}
diff --git a/misc/language.h b/misc/language.h
new file mode 100644
index 0000000..250d391
--- /dev/null
+++ b/misc/language.h
@@ -0,0 +1,31 @@
+/*
+ * Language code utility functions
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_LANGUAGE_H
+#define MP_LANGUAGE_H
+
+#define LANGUAGE_SCORE_BITS 16
+#define LANGUAGE_SCORE_MAX (1 << LANGUAGE_SCORE_BITS)
+
+// Where applicable, l1 is the user-specified code and l2 is the code being checked against it
+int mp_match_lang_single(const char *l1, const char *l2);
+
+char **mp_get_user_langs(void);
+
+#endif /* MP_LANGUAGE_H */
diff --git a/misc/linked_list.h b/misc/linked_list.h
new file mode 100644
index 0000000..b43b227
--- /dev/null
+++ b/misc/linked_list.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include <stddef.h>
+
+/*
+ * Doubly linked list macros. All of these require that each list item is a
+ * struct, that contains a field, that is another struct with prev/next fields:
+ *
+ * struct example_item {
+ * struct {
+ * struct example_item *prev, *next;
+ * } mylist;
+ * };
+ *
+ * And a struct somewhere that represents the "list" and has head/tail fields:
+ *
+ * struct {
+ * struct example_item *head, *tail;
+ * } mylist_var;
+ *
+ * Then you can e.g. insert elements like this:
+ *
+ * struct example_item item;
+ * LL_APPEND(mylist, &mylist_var, &item);
+ *
+ * The first macro argument is always the name if the field in the item that
+ * contains the prev/next pointers, in this case struct example_item.mylist.
+ * This was done so that a single item can be in multiple lists.
+ *
+ * The list is started/terminated with NULL. Nothing ever points _to_ the
+ * list head, so the list head memory location can be safely moved.
+ *
+ * General rules are:
+ * - list head is initialized by setting head/tail to NULL
+ * - list items do not need to be initialized before inserting them
+ * - next/prev fields of list items are not cleared when they are removed
+ * - there's no way to know whether an item is in the list or not (unless
+ * you clear prev/next on init/removal, _and_ check whether items with
+ * prev/next==NULL are referenced by head/tail)
+ */
+
+// Insert item at the end of the list (list->tail == item).
+// Undefined behavior if item is already in the list.
+#define LL_APPEND(field, list, item) do { \
+ (item)->field.prev = (list)->tail; \
+ (item)->field.next = NULL; \
+ LL_RELINK_(field, list, item) \
+} while (0)
+
+// Insert item enew after eprev (i.e. eprev->next == enew). If eprev is NULL,
+// then insert it as head (list->head == enew).
+// Undefined behavior if enew is already in the list, or eprev isn't.
+#define LL_INSERT_AFTER(field, list, eprev, enew) do { \
+ (enew)->field.prev = (eprev); \
+ (enew)->field.next = (eprev) ? (eprev)->field.next : (list)->head; \
+ LL_RELINK_(field, list, enew) \
+} while (0)
+
+// Insert item at the start of the list (list->head == item).
+// Undefined behavior if item is already in the list.
+#define LL_PREPEND(field, list, item) do { \
+ (item)->field.prev = NULL; \
+ (item)->field.next = (list)->head; \
+ LL_RELINK_(field, list, item) \
+} while (0)
+
+// Insert item enew before enext (i.e. enew->next == enext). If enext is NULL,
+// then insert it as tail (list->tail == enew).
+// Undefined behavior if enew is already in the list, or enext isn't.
+#define LL_INSERT_BEFORE(field, list, enext, enew) do { \
+ (enew)->field.prev = (enext) ? (enext)->field.prev : (list)->tail; \
+ (enew)->field.next = (enext); \
+ LL_RELINK_(field, list, enew) \
+} while (0)
+
+// Remove the item from the list.
+// Undefined behavior if item is not in the list.
+#define LL_REMOVE(field, list, item) do { \
+ if ((item)->field.prev) { \
+ (item)->field.prev->field.next = (item)->field.next; \
+ } else { \
+ (list)->head = (item)->field.next; \
+ } \
+ if ((item)->field.next) { \
+ (item)->field.next->field.prev = (item)->field.prev; \
+ } else { \
+ (list)->tail = (item)->field.prev; \
+ } \
+} while (0)
+
+// Remove all items from the list.
+#define LL_CLEAR(field, list) do { \
+ (list)->head = (list)->tail = NULL; \
+} while (0)
+
+// Internal helper.
+#define LL_RELINK_(field, list, item) \
+ if ((item)->field.prev) { \
+ (item)->field.prev->field.next = (item); \
+ } else { \
+ (list)->head = (item); \
+ } \
+ if ((item)->field.next) { \
+ (item)->field.next->field.prev = (item); \
+ } else { \
+ (list)->tail = (item); \
+ }
diff --git a/misc/natural_sort.c b/misc/natural_sort.c
new file mode 100644
index 0000000..3e0bab0
--- /dev/null
+++ b/misc/natural_sort.c
@@ -0,0 +1,67 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "misc/ctype.h"
+
+#include "natural_sort.h"
+
+// Comparison function for an ASCII-only "natural" sort. Case is ignored and
+// numbers are ordered by value regardless of padding. Two filenames that differ
+// only in the padding of numbers will be considered equal and end up in
+// arbitrary order. Bytes outside of A-Z/a-z/0-9 will by sorted by byte value.
+int mp_natural_sort_cmp(const char *name1, const char *name2)
+{
+ while (name1[0] && name2[0]) {
+ if (mp_isdigit(name1[0]) && mp_isdigit(name2[0])) {
+ while (name1[0] == '0')
+ name1++;
+ while (name2[0] == '0')
+ name2++;
+ const char *end1 = name1, *end2 = name2;
+ while (mp_isdigit(*end1))
+ end1++;
+ while (mp_isdigit(*end2))
+ end2++;
+ // With padding stripped, a number with more digits is bigger.
+ if ((end1 - name1) < (end2 - name2))
+ return -1;
+ if ((end1 - name1) > (end2 - name2))
+ return 1;
+ // Same length, lexicographical works.
+ while (name1 < end1) {
+ if (name1[0] < name2[0])
+ return -1;
+ if (name1[0] > name2[0])
+ return 1;
+ name1++;
+ name2++;
+ }
+ } else {
+ if (mp_tolower(name1[0]) < mp_tolower(name2[0]))
+ return -1;
+ if (mp_tolower(name1[0]) > mp_tolower(name2[0]))
+ return 1;
+ name1++;
+ name2++;
+ }
+ }
+ if (name2[0])
+ return -1;
+ if (name1[0])
+ return 1;
+ return 0;
+}
diff --git a/misc/natural_sort.h b/misc/natural_sort.h
new file mode 100644
index 0000000..47b9a7a
--- /dev/null
+++ b/misc/natural_sort.h
@@ -0,0 +1,23 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MP_NATURAL_SORT_H
+#define MP_NATURAL_SORT_H
+
+int mp_natural_sort_cmp(const char *name1, const char *name2);
+
+#endif
diff --git a/misc/node.c b/misc/node.c
new file mode 100644
index 0000000..5bf3211
--- /dev/null
+++ b/misc/node.c
@@ -0,0 +1,159 @@
+#include "common/common.h"
+
+#include "node.h"
+
+// Init a node with the given format. If parent is not NULL, it is set as
+// parent allocation according to m_option_type_node rules (which means
+// the mpv_node_list allocs are used for chaining the TA allocations).
+// format == MPV_FORMAT_NONE will simply initialize it with all-0.
+void node_init(struct mpv_node *dst, int format, struct mpv_node *parent)
+{
+ // Other formats need to be initialized manually.
+ assert(format == MPV_FORMAT_NODE_MAP || format == MPV_FORMAT_NODE_ARRAY ||
+ format == MPV_FORMAT_FLAG || format == MPV_FORMAT_INT64 ||
+ format == MPV_FORMAT_DOUBLE || format == MPV_FORMAT_BYTE_ARRAY ||
+ format == MPV_FORMAT_NONE);
+
+ void *ta_parent = NULL;
+ if (parent) {
+ assert(parent->format == MPV_FORMAT_NODE_MAP ||
+ parent->format == MPV_FORMAT_NODE_ARRAY);
+ ta_parent = parent->u.list;
+ }
+
+ *dst = (struct mpv_node){ .format = format };
+ if (format == MPV_FORMAT_NODE_MAP || format == MPV_FORMAT_NODE_ARRAY)
+ dst->u.list = talloc_zero(ta_parent, struct mpv_node_list);
+ if (format == MPV_FORMAT_BYTE_ARRAY)
+ dst->u.ba = talloc_zero(ta_parent, struct mpv_byte_array);
+}
+
+// Add an entry to a MPV_FORMAT_NODE_ARRAY.
+// m_option_type_node memory management rules apply.
+struct mpv_node *node_array_add(struct mpv_node *dst, int format)
+{
+ struct mpv_node_list *list = dst->u.list;
+ assert(dst->format == MPV_FORMAT_NODE_ARRAY && dst->u.list);
+ MP_TARRAY_GROW(list, list->values, list->num);
+ node_init(&list->values[list->num], format, dst);
+ return &list->values[list->num++];
+}
+
+// Add an entry to a MPV_FORMAT_NODE_MAP. Keep in mind that this does
+// not check for already existing entries under the same key.
+// m_option_type_node memory management rules apply.
+struct mpv_node *node_map_add(struct mpv_node *dst, const char *key, int format)
+{
+ assert(key);
+ return node_map_badd(dst, bstr0(key), format);
+}
+
+struct mpv_node *node_map_badd(struct mpv_node *dst, struct bstr key, int format)
+{
+ assert(key.start);
+
+ struct mpv_node_list *list = dst->u.list;
+ assert(dst->format == MPV_FORMAT_NODE_MAP && dst->u.list);
+ MP_TARRAY_GROW(list, list->values, list->num);
+ MP_TARRAY_GROW(list, list->keys, list->num);
+ list->keys[list->num] = bstrdup0(list, key);
+ node_init(&list->values[list->num], format, dst);
+ return &list->values[list->num++];
+}
+
+// Add a string entry to a MPV_FORMAT_NODE_MAP. Keep in mind that this does
+// not check for already existing entries under the same key.
+// m_option_type_node memory management rules apply.
+void node_map_add_string(struct mpv_node *dst, const char *key, const char *val)
+{
+ assert(val);
+
+ struct mpv_node *entry = node_map_add(dst, key, MPV_FORMAT_NONE);
+ entry->format = MPV_FORMAT_STRING;
+ entry->u.string = talloc_strdup(dst->u.list, val);
+}
+
+void node_map_add_int64(struct mpv_node *dst, const char *key, int64_t v)
+{
+ node_map_add(dst, key, MPV_FORMAT_INT64)->u.int64 = v;
+}
+
+void node_map_add_double(struct mpv_node *dst, const char *key, double v)
+{
+ node_map_add(dst, key, MPV_FORMAT_DOUBLE)->u.double_ = v;
+}
+
+void node_map_add_flag(struct mpv_node *dst, const char *key, bool v)
+{
+ node_map_add(dst, key, MPV_FORMAT_FLAG)->u.flag = v;
+}
+
+mpv_node *node_map_get(mpv_node *src, const char *key)
+{
+ return node_map_bget(src, bstr0(key));
+}
+
+mpv_node *node_map_bget(mpv_node *src, struct bstr key)
+{
+ if (src->format != MPV_FORMAT_NODE_MAP)
+ return NULL;
+
+ for (int i = 0; i < src->u.list->num; i++) {
+ if (bstr_equals0(key, src->u.list->keys[i]))
+ return &src->u.list->values[i];
+ }
+
+ return NULL;
+}
+
+// Note: for MPV_FORMAT_NODE_MAP, this (incorrectly) takes the order into
+// account, instead of treating it as set.
+bool equal_mpv_value(const void *a, const void *b, mpv_format format)
+{
+ switch (format) {
+ case MPV_FORMAT_NONE:
+ return true;
+ case MPV_FORMAT_STRING:
+ case MPV_FORMAT_OSD_STRING:
+ return strcmp(*(char **)a, *(char **)b) == 0;
+ case MPV_FORMAT_FLAG:
+ return *(int *)a == *(int *)b;
+ case MPV_FORMAT_INT64:
+ return *(int64_t *)a == *(int64_t *)b;
+ case MPV_FORMAT_DOUBLE:
+ return *(double *)a == *(double *)b;
+ case MPV_FORMAT_NODE:
+ return equal_mpv_node(a, b);
+ case MPV_FORMAT_BYTE_ARRAY: {
+ const struct mpv_byte_array *a_r = a, *b_r = b;
+ if (a_r->size != b_r->size)
+ return false;
+ return memcmp(a_r->data, b_r->data, a_r->size) == 0;
+ }
+ case MPV_FORMAT_NODE_ARRAY:
+ case MPV_FORMAT_NODE_MAP:
+ {
+ mpv_node_list *l_a = *(mpv_node_list **)a, *l_b = *(mpv_node_list **)b;
+ if (l_a->num != l_b->num)
+ return false;
+ for (int n = 0; n < l_a->num; n++) {
+ if (format == MPV_FORMAT_NODE_MAP) {
+ if (strcmp(l_a->keys[n], l_b->keys[n]) != 0)
+ return false;
+ }
+ if (!equal_mpv_node(&l_a->values[n], &l_b->values[n]))
+ return false;
+ }
+ return true;
+ }
+ }
+ MP_ASSERT_UNREACHABLE(); // supposed to be able to handle all defined types
+}
+
+// Remarks see equal_mpv_value().
+bool equal_mpv_node(const struct mpv_node *a, const struct mpv_node *b)
+{
+ if (a->format != b->format)
+ return false;
+ return equal_mpv_value(&a->u, &b->u, a->format);
+}
diff --git a/misc/node.h b/misc/node.h
new file mode 100644
index 0000000..688b0a8
--- /dev/null
+++ b/misc/node.h
@@ -0,0 +1,20 @@
+#ifndef MP_MISC_NODE_H_
+#define MP_MISC_NODE_H_
+
+#include "libmpv/client.h"
+#include "misc/bstr.h"
+
+void node_init(struct mpv_node *dst, int format, struct mpv_node *parent);
+struct mpv_node *node_array_add(struct mpv_node *dst, int format);
+struct mpv_node *node_map_add(struct mpv_node *dst, const char *key, int format);
+struct mpv_node *node_map_badd(struct mpv_node *dst, struct bstr key, int format);
+void node_map_add_string(struct mpv_node *dst, const char *key, const char *val);
+void node_map_add_int64(struct mpv_node *dst, const char *key, int64_t v);
+void node_map_add_double(struct mpv_node *dst, const char *key, double v);
+void node_map_add_flag(struct mpv_node *dst, const char *key, bool v);
+mpv_node *node_map_get(mpv_node *src, const char *key);
+mpv_node *node_map_bget(mpv_node *src, struct bstr key);
+bool equal_mpv_value(const void *a, const void *b, mpv_format format);
+bool equal_mpv_node(const struct mpv_node *a, const struct mpv_node *b);
+
+#endif
diff --git a/misc/random.c b/misc/random.c
new file mode 100644
index 0000000..e622ab7
--- /dev/null
+++ b/misc/random.c
@@ -0,0 +1,75 @@
+/*
+ * Implementation of non-cryptographic pseudo-random number
+ * generator algorithm known as xoshiro.
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#include "osdep/threads.h"
+#include "random.h"
+
+static uint64_t state[4];
+static mp_static_mutex state_mutex = MP_STATIC_MUTEX_INITIALIZER;
+
+static inline uint64_t rotl_u64(const uint64_t x, const int k)
+{
+ return (x << k) | (x >> (64 - k));
+}
+
+static inline uint64_t splitmix64(uint64_t *const x)
+{
+ uint64_t z = (*x += UINT64_C(0x9e3779b97f4a7c15));
+ z = (z ^ (z >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
+ z = (z ^ (z >> 27)) * UINT64_C(0x94d049bb133111eb);
+ return z ^ (z >> 31);
+}
+
+void mp_rand_seed(uint64_t seed)
+{
+ mp_mutex_lock(&state_mutex);
+ state[0] = seed;
+ for (int i = 1; i < 4; i++)
+ state[i] = splitmix64(&seed);
+ mp_mutex_unlock(&state_mutex);
+}
+
+uint64_t mp_rand_next(void)
+{
+ uint64_t result, t;
+
+ mp_mutex_lock(&state_mutex);
+
+ result = rotl_u64(state[1] * 5, 7) * 9;
+ t = state[1] << 17;
+
+ state[2] ^= state[0];
+ state[3] ^= state[1];
+ state[1] ^= state[2];
+ state[0] ^= state[3];
+ state[2] ^= t;
+ state[3] = rotl_u64(state[3], 45);
+
+ mp_mutex_unlock(&state_mutex);
+
+ return result;
+}
+
+double mp_rand_next_double(void)
+{
+ return (mp_rand_next() >> 11) * 0x1.0p-53;
+}
diff --git a/misc/random.h b/misc/random.h
new file mode 100644
index 0000000..dae66a0
--- /dev/null
+++ b/misc/random.h
@@ -0,0 +1,41 @@
+/*
+ * Implementation of non-cryptographic pseudo-random number
+ * generator algorithm known as xoshiro.
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*
+ * Initialize the pseudo-random number generator's state with
+ * the given 64-bit seed.
+ */
+void mp_rand_seed(uint64_t seed);
+
+/*
+ * Return the next 64-bit pseudo-random integer, and update the state
+ * accordingly.
+ */
+uint64_t mp_rand_next(void);
+
+/*
+ * Return a double value in the range of [0.0, 1.0) with uniform
+ * distribution, and update the state accordingly.
+ */
+double mp_rand_next_double(void);
diff --git a/misc/rendezvous.c b/misc/rendezvous.c
new file mode 100644
index 0000000..1fe5724
--- /dev/null
+++ b/misc/rendezvous.c
@@ -0,0 +1,55 @@
+
+#include "rendezvous.h"
+
+#include "osdep/threads.h"
+
+static mp_static_mutex lock = MP_STATIC_MUTEX_INITIALIZER;
+static mp_cond wakeup = MP_STATIC_COND_INITIALIZER;
+
+static struct waiter *waiters;
+
+struct waiter {
+ void *tag;
+ struct waiter *next;
+ intptr_t *value;
+};
+
+/* A barrier for 2 threads, which can exchange a value when they meet.
+ * The first thread to call this function will block. As soon as two threads
+ * are calling this function with the same tag value, they will unblock, and
+ * on each thread the call returns the value parameter of the _other_ thread.
+ *
+ * tag is an arbitrary value, but it must be an unique pointer. If there are
+ * more than 2 threads using the same tag, things won't work. Typically, it
+ * will have to point to a memory allocation or to the stack, while pointing
+ * it to static data is always a bug.
+ *
+ * This shouldn't be used for performance critical code (uses a linked list
+ * of _all_ waiters in the process, and temporarily wakes up _all_ waiters on
+ * each second call).
+ *
+ * This is inspired by: http://9atom.org/magic/man2html/2/rendezvous */
+intptr_t mp_rendezvous(void *tag, intptr_t value)
+{
+ struct waiter wait = { .tag = tag, .value = &value };
+ mp_mutex_lock(&lock);
+ struct waiter **prev = &waiters;
+ while (*prev) {
+ if ((*prev)->tag == tag) {
+ intptr_t tmp = *(*prev)->value;
+ *(*prev)->value = value;
+ value = tmp;
+ (*prev)->value = NULL; // signals completion
+ *prev = (*prev)->next; // unlink
+ mp_cond_broadcast(&wakeup);
+ goto done;
+ }
+ prev = &(*prev)->next;
+ }
+ *prev = &wait;
+ while (wait.value)
+ mp_cond_wait(&wakeup, &lock);
+done:
+ mp_mutex_unlock(&lock);
+ return value;
+}
diff --git a/misc/rendezvous.h b/misc/rendezvous.h
new file mode 100644
index 0000000..ffcc89a
--- /dev/null
+++ b/misc/rendezvous.h
@@ -0,0 +1,8 @@
+#ifndef MP_RENDEZVOUS_H_
+#define MP_RENDEZVOUS_H_
+
+#include <stdint.h>
+
+intptr_t mp_rendezvous(void *tag, intptr_t value);
+
+#endif
diff --git a/misc/thread_pool.c b/misc/thread_pool.c
new file mode 100644
index 0000000..e20d9d0
--- /dev/null
+++ b/misc/thread_pool.c
@@ -0,0 +1,223 @@
+/* Copyright (C) 2018 the mpv developers
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "common/common.h"
+#include "osdep/threads.h"
+#include "osdep/timer.h"
+
+#include "thread_pool.h"
+
+// Threads destroy themselves after this many seconds, if there's no new work
+// and the thread count is above the configured minimum.
+#define DESTROY_TIMEOUT 10
+
+struct work {
+ void (*fn)(void *ctx);
+ void *fn_ctx;
+};
+
+struct mp_thread_pool {
+ int min_threads, max_threads;
+
+ mp_mutex lock;
+ mp_cond wakeup;
+
+ // --- the following fields are protected by lock
+
+ mp_thread *threads;
+ int num_threads;
+
+ // Number of threads which have taken up work and are still processing it.
+ int busy_threads;
+
+ bool terminate;
+
+ struct work *work;
+ int num_work;
+};
+
+static MP_THREAD_VOID worker_thread(void *arg)
+{
+ struct mp_thread_pool *pool = arg;
+
+ mp_thread_set_name("worker");
+
+ mp_mutex_lock(&pool->lock);
+
+ int64_t destroy_deadline = 0;
+ bool got_timeout = false;
+ while (1) {
+ struct work work = {0};
+ if (pool->num_work > 0) {
+ work = pool->work[pool->num_work - 1];
+ pool->num_work -= 1;
+ }
+
+ if (!work.fn) {
+ if (got_timeout || pool->terminate)
+ break;
+
+ if (pool->num_threads > pool->min_threads) {
+ if (!destroy_deadline)
+ destroy_deadline = mp_time_ns() + MP_TIME_S_TO_NS(DESTROY_TIMEOUT);
+ if (mp_cond_timedwait_until(&pool->wakeup, &pool->lock, destroy_deadline))
+ got_timeout = pool->num_threads > pool->min_threads;
+ } else {
+ mp_cond_wait(&pool->wakeup, &pool->lock);
+ }
+ continue;
+ }
+
+ pool->busy_threads += 1;
+ mp_mutex_unlock(&pool->lock);
+
+ work.fn(work.fn_ctx);
+
+ mp_mutex_lock(&pool->lock);
+ pool->busy_threads -= 1;
+
+ destroy_deadline = 0;
+ got_timeout = false;
+ }
+
+ // If no termination signal was given, it must mean we died because of a
+ // timeout, and nobody is waiting for us. We have to remove ourselves.
+ if (!pool->terminate) {
+ for (int n = 0; n < pool->num_threads; n++) {
+ if (mp_thread_id_equal(mp_thread_get_id(pool->threads[n]),
+ mp_thread_current_id()))
+ {
+ mp_thread_detach(pool->threads[n]);
+ MP_TARRAY_REMOVE_AT(pool->threads, pool->num_threads, n);
+ mp_mutex_unlock(&pool->lock);
+ MP_THREAD_RETURN();
+ }
+ }
+ MP_ASSERT_UNREACHABLE();
+ }
+
+ mp_mutex_unlock(&pool->lock);
+ MP_THREAD_RETURN();
+}
+
+static void thread_pool_dtor(void *ctx)
+{
+ struct mp_thread_pool *pool = ctx;
+
+
+ mp_mutex_lock(&pool->lock);
+
+ pool->terminate = true;
+ mp_cond_broadcast(&pool->wakeup);
+
+ mp_thread *threads = pool->threads;
+ int num_threads = pool->num_threads;
+
+ pool->threads = NULL;
+ pool->num_threads = 0;
+
+ mp_mutex_unlock(&pool->lock);
+
+ for (int n = 0; n < num_threads; n++)
+ mp_thread_join(threads[n]);
+
+ assert(pool->num_work == 0);
+ assert(pool->num_threads == 0);
+ mp_cond_destroy(&pool->wakeup);
+ mp_mutex_destroy(&pool->lock);
+}
+
+static bool add_thread(struct mp_thread_pool *pool)
+{
+ mp_thread thread;
+
+ if (mp_thread_create(&thread, worker_thread, pool) != 0)
+ return false;
+
+ MP_TARRAY_APPEND(pool, pool->threads, pool->num_threads, thread);
+ return true;
+}
+
+struct mp_thread_pool *mp_thread_pool_create(void *ta_parent, int init_threads,
+ int min_threads, int max_threads)
+{
+ assert(min_threads >= 0);
+ assert(init_threads <= min_threads);
+ assert(max_threads > 0 && max_threads >= min_threads);
+
+ struct mp_thread_pool *pool = talloc_zero(ta_parent, struct mp_thread_pool);
+ talloc_set_destructor(pool, thread_pool_dtor);
+
+ mp_mutex_init(&pool->lock);
+ mp_cond_init(&pool->wakeup);
+
+ pool->min_threads = min_threads;
+ pool->max_threads = max_threads;
+
+ mp_mutex_lock(&pool->lock);
+ for (int n = 0; n < init_threads; n++)
+ add_thread(pool);
+ bool ok = pool->num_threads >= init_threads;
+ mp_mutex_unlock(&pool->lock);
+
+ if (!ok)
+ TA_FREEP(&pool);
+
+ return pool;
+}
+
+static bool thread_pool_add(struct mp_thread_pool *pool, void (*fn)(void *ctx),
+ void *fn_ctx, bool allow_queue)
+{
+ bool ok = true;
+
+ assert(fn);
+
+ mp_mutex_lock(&pool->lock);
+ struct work work = {fn, fn_ctx};
+
+ // If there are not enough threads to process all at once, but we can
+ // create a new thread, then do so. If work is queued quickly, it can
+ // happen that not all available threads have picked up work yet (up to
+ // num_threads - busy_threads threads), which has to be accounted for.
+ if (pool->busy_threads + pool->num_work + 1 > pool->num_threads &&
+ pool->num_threads < pool->max_threads)
+ {
+ if (!add_thread(pool)) {
+ // If we can queue it, it'll get done as long as there is 1 thread.
+ ok = allow_queue && pool->num_threads > 0;
+ }
+ }
+
+ if (ok) {
+ MP_TARRAY_INSERT_AT(pool, pool->work, pool->num_work, 0, work);
+ mp_cond_signal(&pool->wakeup);
+ }
+
+ mp_mutex_unlock(&pool->lock);
+ return ok;
+}
+
+bool mp_thread_pool_queue(struct mp_thread_pool *pool, void (*fn)(void *ctx),
+ void *fn_ctx)
+{
+ return thread_pool_add(pool, fn, fn_ctx, true);
+}
+
+bool mp_thread_pool_run(struct mp_thread_pool *pool, void (*fn)(void *ctx),
+ void *fn_ctx)
+{
+ return thread_pool_add(pool, fn, fn_ctx, false);
+}
diff --git a/misc/thread_pool.h b/misc/thread_pool.h
new file mode 100644
index 0000000..39106ee
--- /dev/null
+++ b/misc/thread_pool.h
@@ -0,0 +1,35 @@
+#ifndef MPV_MP_THREAD_POOL_H
+#define MPV_MP_THREAD_POOL_H
+
+#include <stdbool.h>
+struct mp_thread_pool;
+
+// Create a thread pool with the given number of worker threads. This can return
+// NULL if the worker threads could not be created. The thread pool can be
+// destroyed with talloc_free(pool), or indirectly with talloc_free(ta_parent).
+// If there are still work items on freeing, it will block until all work items
+// are done, and the threads terminate.
+// init_threads is the number of threads created in this function (and it fails
+// if it could not be done). min_threads must be >=, if it's >, then the
+// remaining threads will be created on demand, but never destroyed.
+// If init_threads > 0, then mp_thread_pool_queue() can never fail.
+// If init_threads == 0, mp_thread_pool_create() itself can never fail.
+struct mp_thread_pool *mp_thread_pool_create(void *ta_parent, int init_threads,
+ int min_threads, int max_threads);
+
+// Queue a function to be run on a worker thread: fn(fn_ctx)
+// If no worker thread is currently available, it's appended to a list in memory
+// with unbounded size. This function always returns immediately.
+// Concurrent queue calls are allowed, as long as it does not overlap with
+// pool destruction.
+// This function is explicitly thread-safe.
+// Cannot fail if thread pool was created with at least 1 thread.
+bool mp_thread_pool_queue(struct mp_thread_pool *pool, void (*fn)(void *ctx),
+ void *fn_ctx);
+
+// Like mp_thread_pool_queue(), but only queue the item and succeed if a thread
+// can be reserved for the item (i.e. minimal wait time instead of unbounded).
+bool mp_thread_pool_run(struct mp_thread_pool *pool, void (*fn)(void *ctx),
+ void *fn_ctx);
+
+#endif
diff --git a/misc/thread_tools.c b/misc/thread_tools.c
new file mode 100644
index 0000000..0f7fe8f
--- /dev/null
+++ b/misc/thread_tools.c
@@ -0,0 +1,276 @@
+/* Copyright (C) 2018 the mpv developers
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdatomic.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef __MINGW32__
+#include <windows.h>
+#else
+#include <poll.h>
+#endif
+
+#include "common/common.h"
+#include "misc/linked_list.h"
+#include "osdep/io.h"
+#include "osdep/timer.h"
+
+#include "thread_tools.h"
+
+uintptr_t mp_waiter_wait(struct mp_waiter *waiter)
+{
+ mp_mutex_lock(&waiter->lock);
+ while (!waiter->done)
+ mp_cond_wait(&waiter->wakeup, &waiter->lock);
+ mp_mutex_unlock(&waiter->lock);
+
+ uintptr_t ret = waiter->value;
+
+ // We document that after mp_waiter_wait() the waiter object becomes
+ // invalid. (It strictly returns only after mp_waiter_wakeup() has returned,
+ // and the object is "single-shot".) So destroy it here.
+
+ // Normally, we expect that the system uses futexes, in which case the
+ // following functions will do nearly nothing. This is true for Windows
+ // and Linux. But some lesser OSes still might allocate kernel objects
+ // when initializing mutexes, so destroy them here.
+ mp_mutex_destroy(&waiter->lock);
+ mp_cond_destroy(&waiter->wakeup);
+
+ memset(waiter, 0xCA, sizeof(*waiter)); // for debugging
+
+ return ret;
+}
+
+void mp_waiter_wakeup(struct mp_waiter *waiter, uintptr_t value)
+{
+ mp_mutex_lock(&waiter->lock);
+ assert(!waiter->done);
+ waiter->done = true;
+ waiter->value = value;
+ mp_cond_signal(&waiter->wakeup);
+ mp_mutex_unlock(&waiter->lock);
+}
+
+bool mp_waiter_poll(struct mp_waiter *waiter)
+{
+ mp_mutex_lock(&waiter->lock);
+ bool r = waiter->done;
+ mp_mutex_unlock(&waiter->lock);
+ return r;
+}
+
+struct mp_cancel {
+ mp_mutex lock;
+ mp_cond wakeup;
+
+ // Semaphore state and "mirrors".
+ atomic_bool triggered;
+ void (*cb)(void *ctx);
+ void *cb_ctx;
+ int wakeup_pipe[2];
+ void *win32_event; // actually HANDLE
+
+ // Slave list. These are automatically notified as well.
+ struct {
+ struct mp_cancel *head, *tail;
+ } slaves;
+
+ // For slaves. Synchronization is managed by parent.lock!
+ struct mp_cancel *parent;
+ struct {
+ struct mp_cancel *next, *prev;
+ } siblings;
+};
+
+static void cancel_destroy(void *p)
+{
+ struct mp_cancel *c = p;
+
+ assert(!c->slaves.head); // API user error
+
+ mp_cancel_set_parent(c, NULL);
+
+ if (c->wakeup_pipe[0] >= 0) {
+ close(c->wakeup_pipe[0]);
+ close(c->wakeup_pipe[1]);
+ }
+
+#ifdef __MINGW32__
+ if (c->win32_event)
+ CloseHandle(c->win32_event);
+#endif
+
+ mp_mutex_destroy(&c->lock);
+ mp_cond_destroy(&c->wakeup);
+}
+
+struct mp_cancel *mp_cancel_new(void *talloc_ctx)
+{
+ struct mp_cancel *c = talloc_ptrtype(talloc_ctx, c);
+ talloc_set_destructor(c, cancel_destroy);
+ *c = (struct mp_cancel){
+ .triggered = false,
+ .wakeup_pipe = {-1, -1},
+ };
+ mp_mutex_init(&c->lock);
+ mp_cond_init(&c->wakeup);
+ return c;
+}
+
+static void trigger_locked(struct mp_cancel *c)
+{
+ atomic_store(&c->triggered, true);
+
+ mp_cond_broadcast(&c->wakeup); // condition bound to c->triggered
+
+ if (c->cb)
+ c->cb(c->cb_ctx);
+
+ for (struct mp_cancel *sub = c->slaves.head; sub; sub = sub->siblings.next)
+ mp_cancel_trigger(sub);
+
+ if (c->wakeup_pipe[1] >= 0)
+ (void)write(c->wakeup_pipe[1], &(char){0}, 1);
+
+#ifdef __MINGW32__
+ if (c->win32_event)
+ SetEvent(c->win32_event);
+#endif
+}
+
+void mp_cancel_trigger(struct mp_cancel *c)
+{
+ mp_mutex_lock(&c->lock);
+ trigger_locked(c);
+ mp_mutex_unlock(&c->lock);
+}
+
+void mp_cancel_reset(struct mp_cancel *c)
+{
+ mp_mutex_lock(&c->lock);
+
+ atomic_store(&c->triggered, false);
+
+ if (c->wakeup_pipe[0] >= 0) {
+ // Flush it fully.
+ while (1) {
+ int r = read(c->wakeup_pipe[0], &(char[256]){0}, 256);
+ if (r <= 0 && !(r < 0 && errno == EINTR))
+ break;
+ }
+ }
+
+#ifdef __MINGW32__
+ if (c->win32_event)
+ ResetEvent(c->win32_event);
+#endif
+
+ mp_mutex_unlock(&c->lock);
+}
+
+bool mp_cancel_test(struct mp_cancel *c)
+{
+ return c ? atomic_load_explicit(&c->triggered, memory_order_relaxed) : false;
+}
+
+bool mp_cancel_wait(struct mp_cancel *c, double timeout)
+{
+ int64_t wait_until = mp_time_ns_add(mp_time_ns(), timeout);
+ mp_mutex_lock(&c->lock);
+ while (!mp_cancel_test(c)) {
+ if (mp_cond_timedwait_until(&c->wakeup, &c->lock, wait_until))
+ break;
+ }
+ mp_mutex_unlock(&c->lock);
+
+ return mp_cancel_test(c);
+}
+
+// If a new notification mechanism was added, and the mp_cancel state was
+// already triggered, make sure the newly added mechanism is also triggered.
+static void retrigger_locked(struct mp_cancel *c)
+{
+ if (mp_cancel_test(c))
+ trigger_locked(c);
+}
+
+void mp_cancel_set_cb(struct mp_cancel *c, void (*cb)(void *ctx), void *ctx)
+{
+ mp_mutex_lock(&c->lock);
+ c->cb = cb;
+ c->cb_ctx = ctx;
+ retrigger_locked(c);
+ mp_mutex_unlock(&c->lock);
+}
+
+void mp_cancel_set_parent(struct mp_cancel *slave, struct mp_cancel *parent)
+{
+ // We can access c->parent without synchronization, because:
+ // - concurrent mp_cancel_set_parent() calls to slave are not allowed
+ // - slave->parent needs to stay valid as long as the slave exists
+ if (slave->parent == parent)
+ return;
+ if (slave->parent) {
+ mp_mutex_lock(&slave->parent->lock);
+ LL_REMOVE(siblings, &slave->parent->slaves, slave);
+ mp_mutex_unlock(&slave->parent->lock);
+ }
+ slave->parent = parent;
+ if (slave->parent) {
+ mp_mutex_lock(&slave->parent->lock);
+ LL_APPEND(siblings, &slave->parent->slaves, slave);
+ retrigger_locked(slave->parent);
+ mp_mutex_unlock(&slave->parent->lock);
+ }
+}
+
+int mp_cancel_get_fd(struct mp_cancel *c)
+{
+ mp_mutex_lock(&c->lock);
+ if (c->wakeup_pipe[0] < 0) {
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstringop-overflow="
+#endif
+ mp_make_wakeup_pipe(c->wakeup_pipe);
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+ retrigger_locked(c);
+ }
+ mp_mutex_unlock(&c->lock);
+
+
+ return c->wakeup_pipe[0];
+}
+
+#ifdef __MINGW32__
+void *mp_cancel_get_event(struct mp_cancel *c)
+{
+ mp_mutex_lock(&c->lock);
+ if (!c->win32_event) {
+ c->win32_event = CreateEventW(NULL, TRUE, FALSE, NULL);
+ retrigger_locked(c);
+ }
+ mp_mutex_unlock(&c->lock);
+
+ return c->win32_event;
+}
+#endif
diff --git a/misc/thread_tools.h b/misc/thread_tools.h
new file mode 100644
index 0000000..a07257b
--- /dev/null
+++ b/misc/thread_tools.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "osdep/threads.h"
+
+// This is basically a single-shot semaphore, intended as light-weight solution
+// for just making a thread wait for another thread.
+struct mp_waiter {
+ // All fields are considered private. Use MP_WAITER_INITIALIZER to init.
+ mp_mutex lock;
+ mp_cond wakeup;
+ bool done;
+ uintptr_t value;
+};
+
+// Initialize a mp_waiter object for use with mp_waiter_*().
+#define MP_WAITER_INITIALIZER { \
+ .lock = MP_STATIC_MUTEX_INITIALIZER, \
+ .wakeup = MP_STATIC_COND_INITIALIZER, \
+ }
+
+// Block until some other thread calls mp_waiter_wakeup(). The function returns
+// the value argument of that wakeup call. After this, the waiter object must
+// not be used anymore. Although you can reinit it with MP_WAITER_INITIALIZER
+// (then you must make sure nothing calls mp_waiter_wakeup() before this).
+uintptr_t mp_waiter_wait(struct mp_waiter *waiter);
+
+// Unblock the thread waiting with mp_waiter_wait(), and make it return the
+// provided value. If the other thread did not enter that call yet, it will
+// return immediately once it does (mp_waiter_wakeup() always returns
+// immediately). Calling this more than once is not allowed.
+void mp_waiter_wakeup(struct mp_waiter *waiter, uintptr_t value);
+
+// Query whether the waiter was woken up. If true, mp_waiter_wait() will return
+// immediately. This is useful if you want to use another way to block and
+// wakeup (in parallel to mp_waiter).
+// You still need to call mp_waiter_wait() to free resources.
+bool mp_waiter_poll(struct mp_waiter *waiter);
+
+// Basically a binary semaphore that supports signaling the semaphore value to
+// a bunch of other complicated mechanisms (such as wakeup pipes). It was made
+// for aborting I/O and thus has according naming.
+struct mp_cancel;
+
+struct mp_cancel *mp_cancel_new(void *talloc_ctx);
+
+// Request abort.
+void mp_cancel_trigger(struct mp_cancel *c);
+
+// Return whether the caller should abort.
+// For convenience, c==NULL is allowed.
+bool mp_cancel_test(struct mp_cancel *c);
+
+// Wait until the even is signaled. If the timeout (in seconds) expires, return
+// false. timeout==0 polls, timeout<0 waits forever.
+bool mp_cancel_wait(struct mp_cancel *c, double timeout);
+
+// Restore original state. (Allows reusing a mp_cancel.)
+void mp_cancel_reset(struct mp_cancel *c);
+
+// Add a callback to invoke when mp_cancel gets triggered. If it's already
+// triggered, call it from mp_cancel_add_cb() directly. May be called multiple
+// times even if the trigger state changes; not called if it resets. In all
+// cases, this may be called with internal locks held (either in mp_cancel, or
+// other locks held by whoever calls mp_cancel_trigger()).
+// There is only one callback. Create a slave mp_cancel to get a private one.
+void mp_cancel_set_cb(struct mp_cancel *c, void (*cb)(void *ctx), void *ctx);
+
+// If parent gets triggered, automatically trigger slave. There is only 1
+// parent; setting NULL clears the parent. Freeing slave also automatically
+// ends the parent link, but the parent mp_cancel must remain valid until the
+// slave is manually removed or destroyed. Destroying a mp_cancel that still
+// has slaves is an error.
+void mp_cancel_set_parent(struct mp_cancel *slave, struct mp_cancel *parent);
+
+// win32 "Event" HANDLE that indicates the current mp_cancel state.
+void *mp_cancel_get_event(struct mp_cancel *c);
+
+// The FD becomes readable if mp_cancel_test() would return true.
+// Don't actually read from it, just use it for poll().
+int mp_cancel_get_fd(struct mp_cancel *c);
diff --git a/misc/uuid.c b/misc/uuid.c
new file mode 100644
index 0000000..c739b3c
--- /dev/null
+++ b/misc/uuid.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2022 Pierre-Anthony Lemieux <pal@palemieux.com>
+ * Zane van Iperen <zane@zanevaniperen.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/**
+ * @file
+ * UUID parsing and serialization utilities.
+ * The library treat the UUID as an opaque sequence of 16 unsigned bytes,
+ * i.e. ignoring the internal layout of the UUID, which depends on the type
+ * of the UUID.
+ *
+ * @author Pierre-Anthony Lemieux <pal@palemieux.com>
+ * @author Zane van Iperen <zane@zanevaniperen.com>
+ */
+
+#include "uuid.h"
+#include "libavutil/error.h"
+#include "libavutil/avstring.h"
+
+int av_uuid_parse(const char *in, AVUUID uu)
+{
+ if (strlen(in) != 36)
+ return AVERROR(EINVAL);
+
+ return av_uuid_parse_range(in, in + 36, uu);
+}
+
+static int xdigit_to_int(char c)
+{
+ c = av_tolower(c);
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ return -1;
+}
+
+int av_uuid_parse_range(const char *in_start, const char *in_end, AVUUID uu)
+{
+ int i;
+ const char *cp;
+
+ if ((in_end - in_start) != 36)
+ return AVERROR(EINVAL);
+
+ for (i = 0, cp = in_start; i < 16; i++) {
+ int hi;
+ int lo;
+
+ if (i == 4 || i == 6 || i == 8 || i == 10)
+ cp++;
+
+ hi = xdigit_to_int(*cp++);
+ lo = xdigit_to_int(*cp++);
+
+ if (hi == -1 || lo == -1)
+ return AVERROR(EINVAL);
+
+ uu[i] = (hi << 4) + lo;
+ }
+
+ return 0;
+}
+
+static const char hexdigits_lower[16] = "0123456789abcdef";
+
+void av_uuid_unparse(const AVUUID uuid, char *out)
+{
+ char *p = out;
+
+ for (int i = 0; i < 16; i++) {
+ uint8_t tmp;
+
+ if (i == 4 || i == 6 || i == 8 || i == 10)
+ *p++ = '-';
+
+ tmp = uuid[i];
+ *p++ = hexdigits_lower[tmp >> 4];
+ *p++ = hexdigits_lower[tmp & 15];
+ }
+
+ *p = '\0';
+}
+
+int av_uuid_urn_parse(const char *in, AVUUID uu)
+{
+ if (av_stristr(in, "urn:uuid:") != in)
+ return AVERROR(EINVAL);
+
+ return av_uuid_parse(in + 9, uu);
+}
diff --git a/misc/uuid.h b/misc/uuid.h
new file mode 100644
index 0000000..748b7ed
--- /dev/null
+++ b/misc/uuid.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2022 Pierre-Anthony Lemieux <pal@palemieux.com>
+ * Zane van Iperen <zane@zanevaniperen.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * UUID parsing and serialization utilities.
+ * The library treats the UUID as an opaque sequence of 16 unsigned bytes,
+ * i.e. ignoring the internal layout of the UUID, which depends on the type
+ * of the UUID.
+ *
+ * @author Pierre-Anthony Lemieux <pal@palemieux.com>
+ * @author Zane van Iperen <zane@zanevaniperen.com>
+ */
+
+#ifndef AVUTIL_UUID_H
+#define AVUTIL_UUID_H
+
+#include <stdint.h>
+#include <string.h>
+
+#define AV_PRI_UUID \
+ "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-" \
+ "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
+
+#define AV_PRI_URN_UUID \
+ "urn:uuid:%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-" \
+ "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
+
+/* AV_UUID_ARG() is used together with AV_PRI_UUID() or AV_PRI_URN_UUID
+ * to print UUIDs, e.g.
+ * av_log(NULL, AV_LOG_DEBUG, "UUID: " AV_PRI_UUID, AV_UUID_ARG(uuid));
+ */
+#define AV_UUID_ARG(x) \
+ (x)[ 0], (x)[ 1], (x)[ 2], (x)[ 3], \
+ (x)[ 4], (x)[ 5], (x)[ 6], (x)[ 7], \
+ (x)[ 8], (x)[ 9], (x)[10], (x)[11], \
+ (x)[12], (x)[13], (x)[14], (x)[15]
+
+#define AV_UUID_LEN 16
+
+/* Binary representation of a UUID */
+typedef uint8_t AVUUID[AV_UUID_LEN];
+
+/**
+ * Parses a string representation of a UUID formatted according to IETF RFC 4122
+ * into an AVUUID. The parsing is case-insensitive. The string must be 37
+ * characters long, including the terminating NUL character.
+ *
+ * Example string representation: "2fceebd0-7017-433d-bafb-d073a7116696"
+ *
+ * @param[in] in String representation of a UUID,
+ * e.g. 2fceebd0-7017-433d-bafb-d073a7116696
+ * @param[out] uu AVUUID
+ * @return A non-zero value in case of an error.
+ */
+int av_uuid_parse(const char *in, AVUUID uu);
+
+/**
+ * Parses a URN representation of a UUID, as specified at IETF RFC 4122,
+ * into an AVUUID. The parsing is case-insensitive. The string must be 46
+ * characters long, including the terminating NUL character.
+ *
+ * Example string representation: "urn:uuid:2fceebd0-7017-433d-bafb-d073a7116696"
+ *
+ * @param[in] in URN UUID
+ * @param[out] uu AVUUID
+ * @return A non-zero value in case of an error.
+ */
+int av_uuid_urn_parse(const char *in, AVUUID uu);
+
+/**
+ * Parses a string representation of a UUID formatted according to IETF RFC 4122
+ * into an AVUUID. The parsing is case-insensitive.
+ *
+ * @param[in] in_start Pointer to the first character of the string representation
+ * @param[in] in_end Pointer to the character after the last character of the
+ * string representation. That memory location is never
+ * accessed. It is an error if `in_end - in_start != 36`.
+ * @param[out] uu AVUUID
+ * @return A non-zero value in case of an error.
+ */
+int av_uuid_parse_range(const char *in_start, const char *in_end, AVUUID uu);
+
+/**
+ * Serializes a AVUUID into a string representation according to IETF RFC 4122.
+ * The string is lowercase and always 37 characters long, including the
+ * terminating NUL character.
+ *
+ * @param[in] uu AVUUID
+ * @param[out] out Pointer to an array of no less than 37 characters.
+ */
+void av_uuid_unparse(const AVUUID uu, char *out);
+
+/**
+ * Compares two UUIDs for equality.
+ *
+ * @param[in] uu1 AVUUID
+ * @param[in] uu2 AVUUID
+ * @return Nonzero if uu1 and uu2 are identical, 0 otherwise
+ */
+static inline int av_uuid_equal(const AVUUID uu1, const AVUUID uu2)
+{
+ return memcmp(uu1, uu2, AV_UUID_LEN) == 0;
+}
+
+/**
+ * Copies the bytes of src into dest.
+ *
+ * @param[out] dest AVUUID
+ * @param[in] src AVUUID
+ */
+static inline void av_uuid_copy(AVUUID dest, const AVUUID src)
+{
+ memcpy(dest, src, AV_UUID_LEN);
+}
+
+/**
+ * Sets a UUID to the nil UUID, i.e. a UUID with have all
+ * its 128 bits set to zero.
+ *
+ * @param[in,out] uu UUID to be set to the nil UUID
+ */
+static inline void av_uuid_nil(AVUUID uu)
+{
+ memset(uu, 0, AV_UUID_LEN);
+}
+
+#endif /* AVUTIL_UUID_H */